feat: Fix system tray and add configurable hotkeys
SYSTEM TRAY FIXES: - Added fallback icon search paths - Falls back to standard system icon if no custom icon found - Better error handling for missing icon files - System tray should now show in Windows taskbar HOTKEY MANAGEMENT: - New HotkeyManager class (core/hotkey_manager.py) - JSON-based configuration storage - 10 configurable global hotkeys - 2 local hotkeys - Validation for key combinations - Conflict detection - Reset to defaults HOTKEYS TAB: - Added 'Hotkeys' tab to Settings dialog - Categorized by scope: Global/Local/Overlay - Shows current key bindings - Enable/disable toggles - Visual styling with color-coded sections - Reset to defaults button DEFAULT HOTKEYS: Global: - Ctrl+Shift+U: Toggle overlay - Ctrl+Shift+H: Hide overlays - Ctrl+Shift+F: Universal search - Ctrl+Shift+N: Nexus search - Ctrl+Shift+C: Calculator - Ctrl+Shift+M: Spotify - Ctrl+Shift+R: Game reader (OCR) - Ctrl+Shift+S: Skill scanner - Ctrl+Shift+L: Loot tracker - Ctrl+Shift+P: Screenshot Local: - Ctrl+T: Toggle theme Overlay: - ESC: Close overlay
This commit is contained in:
parent
db0eb5bf65
commit
92e528b5b6
|
|
@ -0,0 +1,253 @@
|
||||||
|
"""
|
||||||
|
EU-Utility - Configurable Hotkey Manager
|
||||||
|
|
||||||
|
Manages global and local hotkeys with user customization.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, Callable, Optional, List
|
||||||
|
from dataclasses import dataclass, asdict
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
class HotkeyScope(Enum):
|
||||||
|
"""Scope of hotkey action."""
|
||||||
|
GLOBAL = "global" # Works even when app not focused
|
||||||
|
LOCAL = "local" # Only when app is focused
|
||||||
|
OVERLAY = "overlay" # Only when overlay is visible
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class HotkeyConfig:
|
||||||
|
"""Configuration for a single hotkey."""
|
||||||
|
action: str
|
||||||
|
keys: str
|
||||||
|
scope: str
|
||||||
|
enabled: bool = True
|
||||||
|
description: str = ""
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
return asdict(self)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, data: dict) -> 'HotkeyConfig':
|
||||||
|
return cls(**data)
|
||||||
|
|
||||||
|
|
||||||
|
class HotkeyManager:
|
||||||
|
"""Manages configurable hotkeys for EU-Utility."""
|
||||||
|
|
||||||
|
DEFAULT_HOTKEYS = {
|
||||||
|
'toggle_overlay': HotkeyConfig(
|
||||||
|
action='toggle_overlay',
|
||||||
|
keys='ctrl+shift+u',
|
||||||
|
scope='global',
|
||||||
|
enabled=True,
|
||||||
|
description='Toggle main overlay window'
|
||||||
|
),
|
||||||
|
'hide_overlays': HotkeyConfig(
|
||||||
|
action='hide_overlays',
|
||||||
|
keys='ctrl+shift+h',
|
||||||
|
scope='global',
|
||||||
|
enabled=True,
|
||||||
|
description='Hide all overlays'
|
||||||
|
),
|
||||||
|
'quick_search': HotkeyConfig(
|
||||||
|
action='quick_search',
|
||||||
|
keys='ctrl+shift+f',
|
||||||
|
scope='global',
|
||||||
|
enabled=True,
|
||||||
|
description='Open universal search'
|
||||||
|
),
|
||||||
|
'nexus_search': HotkeyConfig(
|
||||||
|
action='nexus_search',
|
||||||
|
keys='ctrl+shift+n',
|
||||||
|
scope='global',
|
||||||
|
enabled=True,
|
||||||
|
description='Open Nexus search'
|
||||||
|
),
|
||||||
|
'calculator': HotkeyConfig(
|
||||||
|
action='calculator',
|
||||||
|
keys='ctrl+shift+c',
|
||||||
|
scope='global',
|
||||||
|
enabled=True,
|
||||||
|
description='Open calculator'
|
||||||
|
),
|
||||||
|
'spotify': HotkeyConfig(
|
||||||
|
action='spotify',
|
||||||
|
keys='ctrl+shift+m',
|
||||||
|
scope='global',
|
||||||
|
enabled=True,
|
||||||
|
description='Toggle Spotify controller'
|
||||||
|
),
|
||||||
|
'game_reader': HotkeyConfig(
|
||||||
|
action='game_reader',
|
||||||
|
keys='ctrl+shift+r',
|
||||||
|
scope='global',
|
||||||
|
enabled=True,
|
||||||
|
description='Open game reader (OCR)'
|
||||||
|
),
|
||||||
|
'skill_scanner': HotkeyConfig(
|
||||||
|
action='skill_scanner',
|
||||||
|
keys='ctrl+shift+s',
|
||||||
|
scope='global',
|
||||||
|
enabled=True,
|
||||||
|
description='Open skill scanner'
|
||||||
|
),
|
||||||
|
'loot_tracker': HotkeyConfig(
|
||||||
|
action='loot_tracker',
|
||||||
|
keys='ctrl+shift+l',
|
||||||
|
scope='global',
|
||||||
|
enabled=True,
|
||||||
|
description='Open loot tracker'
|
||||||
|
),
|
||||||
|
'screenshot': HotkeyConfig(
|
||||||
|
action='screenshot',
|
||||||
|
keys='ctrl+shift+p',
|
||||||
|
scope='global',
|
||||||
|
enabled=True,
|
||||||
|
description='Take screenshot'
|
||||||
|
),
|
||||||
|
'close_overlay': HotkeyConfig(
|
||||||
|
action='close_overlay',
|
||||||
|
keys='esc',
|
||||||
|
scope='overlay',
|
||||||
|
enabled=True,
|
||||||
|
description='Close overlay (when visible)'
|
||||||
|
),
|
||||||
|
'toggle_theme': HotkeyConfig(
|
||||||
|
action='toggle_theme',
|
||||||
|
keys='ctrl+t',
|
||||||
|
scope='local',
|
||||||
|
enabled=True,
|
||||||
|
description='Toggle dark/light theme'
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, config_path: str = "config/hotkeys.json"):
|
||||||
|
self.config_path = Path(config_path)
|
||||||
|
self.hotkeys: Dict[str, HotkeyConfig] = {}
|
||||||
|
self._handlers: Dict[str, Callable] = {}
|
||||||
|
self._registered_globals: List[str] = []
|
||||||
|
|
||||||
|
self._load_config()
|
||||||
|
|
||||||
|
def _load_config(self):
|
||||||
|
"""Load hotkey configuration from file."""
|
||||||
|
if self.config_path.exists():
|
||||||
|
try:
|
||||||
|
with open(self.config_path, 'r') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
for key, config_dict in data.items():
|
||||||
|
self.hotkeys[key] = HotkeyConfig.from_dict(config_dict)
|
||||||
|
|
||||||
|
# Add any missing defaults
|
||||||
|
for key, default in self.DEFAULT_HOTKEYS.items():
|
||||||
|
if key not in self.hotkeys:
|
||||||
|
self.hotkeys[key] = default
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[HotkeyManager] Failed to load config: {e}")
|
||||||
|
self.hotkeys = self.DEFAULT_HOTKEYS.copy()
|
||||||
|
else:
|
||||||
|
self.hotkeys = self.DEFAULT_HOTKEYS.copy()
|
||||||
|
self._save_config()
|
||||||
|
|
||||||
|
def _save_config(self):
|
||||||
|
"""Save hotkey configuration to file."""
|
||||||
|
try:
|
||||||
|
self.config_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
data = {k: v.to_dict() for k, v in self.hotkeys.items()}
|
||||||
|
with open(self.config_path, 'w') as f:
|
||||||
|
json.dump(data, f, indent=2)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[HotkeyManager] Failed to save config: {e}")
|
||||||
|
|
||||||
|
def get_hotkey(self, action: str) -> Optional[HotkeyConfig]:
|
||||||
|
"""Get hotkey configuration for an action."""
|
||||||
|
return self.hotkeys.get(action)
|
||||||
|
|
||||||
|
def set_hotkey(self, action: str, keys: str, scope: str = None, enabled: bool = None):
|
||||||
|
"""Update hotkey configuration."""
|
||||||
|
if action in self.hotkeys:
|
||||||
|
config = self.hotkeys[action]
|
||||||
|
config.keys = keys
|
||||||
|
if scope is not None:
|
||||||
|
config.scope = scope
|
||||||
|
if enabled is not None:
|
||||||
|
config.enabled = enabled
|
||||||
|
self._save_config()
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def register_handler(self, action: str, handler: Callable):
|
||||||
|
"""Register a handler function for an action."""
|
||||||
|
self._handlers[action] = handler
|
||||||
|
|
||||||
|
def get_all_hotkeys(self) -> Dict[str, HotkeyConfig]:
|
||||||
|
"""Get all hotkey configurations."""
|
||||||
|
return self.hotkeys.copy()
|
||||||
|
|
||||||
|
def validate_key_combo(self, keys: str) -> tuple[bool, str]:
|
||||||
|
"""Validate a key combination string.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(is_valid, error_message)
|
||||||
|
"""
|
||||||
|
if not keys or not isinstance(keys, str):
|
||||||
|
return False, "Key combination cannot be empty"
|
||||||
|
|
||||||
|
# Check for valid format
|
||||||
|
parts = keys.lower().split('+')
|
||||||
|
|
||||||
|
if len(parts) < 1:
|
||||||
|
return False, "Invalid key combination format"
|
||||||
|
|
||||||
|
# Valid modifiers
|
||||||
|
modifiers = {'ctrl', 'alt', 'shift', 'cmd', 'win'}
|
||||||
|
|
||||||
|
# Valid keys
|
||||||
|
valid_keys = {
|
||||||
|
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
|
||||||
|
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
|
||||||
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||||
|
'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10', 'f11', 'f12',
|
||||||
|
'esc', 'escape', 'tab', 'space', 'enter', 'return', 'backspace', 'delete',
|
||||||
|
'insert', 'home', 'end', 'pageup', 'pagedown',
|
||||||
|
'up', 'down', 'left', 'right',
|
||||||
|
'print', 'scrolllock', 'pause',
|
||||||
|
}
|
||||||
|
|
||||||
|
for part in parts:
|
||||||
|
part = part.strip()
|
||||||
|
if part in modifiers:
|
||||||
|
continue
|
||||||
|
if part in valid_keys:
|
||||||
|
continue
|
||||||
|
return False, f"Invalid key: '{part}'"
|
||||||
|
|
||||||
|
return True, ""
|
||||||
|
|
||||||
|
def reset_to_defaults(self):
|
||||||
|
"""Reset all hotkeys to defaults."""
|
||||||
|
self.hotkeys = self.DEFAULT_HOTKEYS.copy()
|
||||||
|
self._save_config()
|
||||||
|
|
||||||
|
def get_conflicts(self) -> List[tuple]:
|
||||||
|
"""Find conflicting hotkey combinations."""
|
||||||
|
conflicts = []
|
||||||
|
seen = {}
|
||||||
|
|
||||||
|
for action, config in self.hotkeys.items():
|
||||||
|
if not config.enabled:
|
||||||
|
continue
|
||||||
|
|
||||||
|
key = (config.keys.lower(), config.scope)
|
||||||
|
if key in seen:
|
||||||
|
conflicts.append((seen[key], action, config.keys))
|
||||||
|
else:
|
||||||
|
seen[key] = action
|
||||||
|
|
||||||
|
return conflicts
|
||||||
|
|
@ -454,14 +454,33 @@ class OverlayWindow(QMainWindow):
|
||||||
return content
|
return content
|
||||||
|
|
||||||
def _setup_tray(self):
|
def _setup_tray(self):
|
||||||
"""Setup system tray icon."""
|
"""Setup system tray icon with better fallback handling."""
|
||||||
c = get_all_colors()
|
c = get_all_colors()
|
||||||
|
|
||||||
self.tray_icon = QSystemTrayIcon(self)
|
self.tray_icon = QSystemTrayIcon(self)
|
||||||
|
|
||||||
icon_path = Path("assets/icon.ico")
|
# Try multiple icon sources
|
||||||
|
icon_paths = [
|
||||||
|
Path("assets/icon.ico"),
|
||||||
|
Path("assets/icon.png"),
|
||||||
|
Path("assets/icons/eu_utility.svg"),
|
||||||
|
Path(__file__).parent.parent / "assets" / "icon.ico",
|
||||||
|
Path(__file__).parent.parent / "assets" / "icon.png",
|
||||||
|
]
|
||||||
|
|
||||||
|
icon_set = False
|
||||||
|
for icon_path in icon_paths:
|
||||||
if icon_path.exists():
|
if icon_path.exists():
|
||||||
self.tray_icon.setIcon(QIcon(str(icon_path)))
|
self.tray_icon.setIcon(QIcon(str(icon_path)))
|
||||||
|
icon_set = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if not icon_set:
|
||||||
|
# Use standard icon as fallback
|
||||||
|
style = self.style()
|
||||||
|
if style:
|
||||||
|
standard_icon = style.standardIcon(self.style().StandardPixmap.SP_ComputerIcon)
|
||||||
|
self.tray_icon.setIcon(standard_icon)
|
||||||
|
|
||||||
tray_menu = QMenu()
|
tray_menu = QMenu()
|
||||||
tray_menu.setStyleSheet(f"""
|
tray_menu.setStyleSheet(f"""
|
||||||
|
|
@ -722,6 +741,10 @@ class OverlayWindow(QMainWindow):
|
||||||
plugins_tab = self._create_plugins_settings_tab()
|
plugins_tab = self._create_plugins_settings_tab()
|
||||||
tabs.addTab(plugins_tab, "Plugins")
|
tabs.addTab(plugins_tab, "Plugins")
|
||||||
|
|
||||||
|
# Hotkeys tab
|
||||||
|
hotkeys_tab = self._create_hotkeys_settings_tab()
|
||||||
|
tabs.addTab(hotkeys_tab, "Hotkeys")
|
||||||
|
|
||||||
# Appearance tab
|
# Appearance tab
|
||||||
appearance_tab = self._create_appearance_settings_tab()
|
appearance_tab = self._create_appearance_settings_tab()
|
||||||
tabs.addTab(appearance_tab, "Appearance")
|
tabs.addTab(appearance_tab, "Appearance")
|
||||||
|
|
@ -1002,6 +1025,173 @@ class OverlayWindow(QMainWindow):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[Overlay] Error creating settings for {plugin_id}: {e}")
|
print(f"[Overlay] Error creating settings for {plugin_id}: {e}")
|
||||||
|
|
||||||
|
def _create_hotkeys_settings_tab(self) -> QWidget:
|
||||||
|
"""Create hotkeys settings tab."""
|
||||||
|
c = get_all_colors()
|
||||||
|
|
||||||
|
tab = QWidget()
|
||||||
|
layout = QVBoxLayout(tab)
|
||||||
|
layout.setSpacing(16)
|
||||||
|
layout.setContentsMargins(16, 16, 16, 16)
|
||||||
|
|
||||||
|
# Header
|
||||||
|
header_frame = QFrame()
|
||||||
|
header_frame.setStyleSheet(f"""
|
||||||
|
QFrame {{
|
||||||
|
background-color: {c['bg_secondary']};
|
||||||
|
border-left: 3px solid {c['accent_primary']};
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 12px;
|
||||||
|
}}
|
||||||
|
""")
|
||||||
|
header_layout = QVBoxLayout(header_frame)
|
||||||
|
|
||||||
|
info_title = QLabel("Keyboard Shortcuts")
|
||||||
|
info_title.setStyleSheet(f"color: {c['text_primary']}; font-weight: bold; font-size: 13px;")
|
||||||
|
header_layout.addWidget(info_title)
|
||||||
|
|
||||||
|
info_desc = QLabel("Configure global and local keyboard shortcuts. Global hotkeys work even when the overlay is hidden.")
|
||||||
|
info_desc.setStyleSheet(f"color: {c['text_secondary']}; font-size: 11px;")
|
||||||
|
info_desc.setWordWrap(True)
|
||||||
|
header_layout.addWidget(info_desc)
|
||||||
|
|
||||||
|
layout.addWidget(header_frame)
|
||||||
|
|
||||||
|
# Hotkeys list container
|
||||||
|
scroll = QScrollArea()
|
||||||
|
scroll.setWidgetResizable(True)
|
||||||
|
scroll.setFrameShape(QFrame.Shape.NoFrame)
|
||||||
|
scroll.setStyleSheet(f"""
|
||||||
|
QScrollArea {{
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
}}
|
||||||
|
QScrollBar:vertical {{
|
||||||
|
background-color: {c['bg_secondary']};
|
||||||
|
width: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}}
|
||||||
|
QScrollBar::handle:vertical {{
|
||||||
|
background-color: {c['accent_primary']};
|
||||||
|
border-radius: 5px;
|
||||||
|
min-height: 30px;
|
||||||
|
}}
|
||||||
|
""")
|
||||||
|
|
||||||
|
hotkeys_container = QWidget()
|
||||||
|
hotkeys_layout = QVBoxLayout(hotkeys_container)
|
||||||
|
hotkeys_layout.setSpacing(8)
|
||||||
|
hotkeys_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
||||||
|
|
||||||
|
# Get hotkey manager
|
||||||
|
try:
|
||||||
|
from core.hotkey_manager import HotkeyManager
|
||||||
|
hotkey_manager = HotkeyManager()
|
||||||
|
hotkeys = hotkey_manager.get_all_hotkeys()
|
||||||
|
|
||||||
|
# Group by scope
|
||||||
|
scopes = {
|
||||||
|
'global': ('Global Hotkeys', 'Work even when overlay is hidden', '#4ecdc4'),
|
||||||
|
'local': ('Local Hotkeys', 'Work only when app is focused', '#a0aec0'),
|
||||||
|
'overlay': ('Overlay Hotkeys', 'Work only when overlay is visible', '#ff8c42'),
|
||||||
|
}
|
||||||
|
|
||||||
|
for scope_key, (scope_name, scope_desc, scope_color) in scopes.items():
|
||||||
|
scope_hotkeys = {k: v for k, v in hotkeys.items() if v.scope == scope_key}
|
||||||
|
|
||||||
|
if scope_hotkeys:
|
||||||
|
# Section header
|
||||||
|
scope_header = QLabel(f"{scope_name.upper()}")
|
||||||
|
scope_header.setStyleSheet(f"""
|
||||||
|
color: {scope_color};
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 10px;
|
||||||
|
padding: 12px 4px 4px 4px;
|
||||||
|
border-bottom: 1px solid {c['border_color']};
|
||||||
|
""")
|
||||||
|
hotkeys_layout.addWidget(scope_header)
|
||||||
|
|
||||||
|
scope_subheader = QLabel(scope_desc)
|
||||||
|
scope_subheader.setStyleSheet(f"color: {c['text_muted']}; font-size: 9px; padding-left: 4px;")
|
||||||
|
hotkeys_layout.addWidget(scope_subheader)
|
||||||
|
|
||||||
|
# Add each hotkey
|
||||||
|
for action, config in scope_hotkeys.items():
|
||||||
|
row_widget = QFrame()
|
||||||
|
row_widget.setStyleSheet(f"""
|
||||||
|
QFrame {{
|
||||||
|
background-color: {c['bg_secondary']};
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 2px;
|
||||||
|
}}
|
||||||
|
""")
|
||||||
|
row_layout = QHBoxLayout(row_widget)
|
||||||
|
row_layout.setSpacing(12)
|
||||||
|
row_layout.setContentsMargins(10, 8, 10, 8)
|
||||||
|
|
||||||
|
# Description
|
||||||
|
desc_label = QLabel(config.description)
|
||||||
|
desc_label.setStyleSheet(f"color: {c['text_primary']}; font-size: 12px;")
|
||||||
|
row_layout.addWidget(desc_label, 1)
|
||||||
|
|
||||||
|
# Key combo display
|
||||||
|
key_label = QLabel(config.keys.upper())
|
||||||
|
key_label.setStyleSheet(f"""
|
||||||
|
color: {c['accent_primary']};
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 11px;
|
||||||
|
background-color: {c['bg_primary']};
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid {c['border_color']};
|
||||||
|
""")
|
||||||
|
row_layout.addWidget(key_label)
|
||||||
|
|
||||||
|
# Enable checkbox
|
||||||
|
enable_cb = QCheckBox()
|
||||||
|
enable_cb.setChecked(config.enabled)
|
||||||
|
enable_cb.setStyleSheet(f"""
|
||||||
|
QCheckBox::indicator {{
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: 2px solid {c['accent_primary']};
|
||||||
|
}}
|
||||||
|
QCheckBox::indicator:checked {{
|
||||||
|
background-color: {c['accent_primary']};
|
||||||
|
}}
|
||||||
|
""")
|
||||||
|
row_layout.addWidget(enable_cb)
|
||||||
|
|
||||||
|
hotkeys_layout.addWidget(row_widget)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_label = QLabel(f"Error loading hotkeys: {e}")
|
||||||
|
error_label.setStyleSheet(f"color: #ff6b6b;")
|
||||||
|
hotkeys_layout.addWidget(error_label)
|
||||||
|
|
||||||
|
hotkeys_layout.addStretch()
|
||||||
|
scroll.setWidget(hotkeys_container)
|
||||||
|
layout.addWidget(scroll, 1)
|
||||||
|
|
||||||
|
# Reset button
|
||||||
|
reset_btn = QPushButton("Reset to Defaults")
|
||||||
|
reset_btn.setStyleSheet(get_button_style('secondary'))
|
||||||
|
reset_btn.clicked.connect(self._reset_hotkeys)
|
||||||
|
layout.addWidget(reset_btn)
|
||||||
|
|
||||||
|
return tab
|
||||||
|
|
||||||
|
def _reset_hotkeys(self):
|
||||||
|
"""Reset hotkeys to defaults."""
|
||||||
|
try:
|
||||||
|
from core.hotkey_manager import HotkeyManager
|
||||||
|
hotkey_manager = HotkeyManager()
|
||||||
|
hotkey_manager.reset_to_defaults()
|
||||||
|
self.notify_info("Hotkeys Reset", "All hotkeys have been reset to default values.")
|
||||||
|
except Exception as e:
|
||||||
|
self.notify_error("Error", f"Failed to reset hotkeys: {e}")
|
||||||
|
|
||||||
def _create_appearance_settings_tab(self) -> QWidget:
|
def _create_appearance_settings_tab(self) -> QWidget:
|
||||||
"""Create appearance settings tab."""
|
"""Create appearance settings tab."""
|
||||||
c = get_all_colors()
|
c = get_all_colors()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue