DarkMode ist existent yeah
Dieser Commit ist enthalten in:
@ -5,8 +5,14 @@
|
||||
"Bash(nslookup:*)",
|
||||
"WebFetch(domain:multilogin.com)",
|
||||
"WebFetch(domain:dicloak.com)",
|
||||
"WebFetch(domain:support.google.com)"
|
||||
"WebFetch(domain:support.google.com)",
|
||||
"Bash(python3 -m pip list:*)",
|
||||
"Bash(python3:*)",
|
||||
"Bash(grep:*)"
|
||||
],
|
||||
"deny": []
|
||||
"deny": [],
|
||||
"additionalDirectories": [
|
||||
"/mnt/a/GiTea/Styleguide"
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -127,6 +127,9 @@ class MainController:
|
||||
# Signals verbinden
|
||||
self.connect_signals()
|
||||
|
||||
# Remove unused theme_toggled connection
|
||||
# The theme toggle is now handled directly in MainWindow
|
||||
|
||||
# Platform Selector Signal-Verbindungen
|
||||
if hasattr(self.view.platform_selector, 'export_requested'):
|
||||
self.view.platform_selector.export_requested.connect(
|
||||
@ -195,9 +198,6 @@ class MainController:
|
||||
# Zurück-Button verbinden
|
||||
self.view.back_to_selector_requested.connect(self.show_platform_selector)
|
||||
|
||||
# Theme-Toggle verbinden
|
||||
self.view.theme_toggled.connect(self.on_theme_toggled)
|
||||
|
||||
def on_platform_selected(self, platform: str):
|
||||
"""Wird aufgerufen, wenn eine Plattform ausgewählt wird."""
|
||||
logger.info(f"Plattform ausgewählt: {platform}")
|
||||
|
||||
53
resources/icons/intelsight-dark.svg
Normale Datei
53
resources/icons/intelsight-dark.svg
Normale Datei
@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="450" height="100" viewBox="0 0 450 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap');
|
||||
</style>
|
||||
|
||||
<!-- Accurate shield matching original -->
|
||||
<g id="shield-eye-accurate-dark">
|
||||
<!-- Angular shield shape -->
|
||||
<path d="M 35 30
|
||||
L 65 30
|
||||
L 75 40
|
||||
L 75 80
|
||||
L 50 115
|
||||
L 25 80
|
||||
L 25 40
|
||||
L 35 30 Z"
|
||||
fill="none"
|
||||
stroke="#FFFFFF"
|
||||
stroke-width="3.5"
|
||||
stroke-linejoin="miter"/>
|
||||
|
||||
<!-- Eye centered in shield -->
|
||||
<g transform="translate(50, 65)">
|
||||
<!-- Almond/football shaped eye -->
|
||||
<ellipse cx="0" cy="0" rx="24" ry="13"
|
||||
fill="none"
|
||||
stroke="#FFFFFF"
|
||||
stroke-width="3.5"/>
|
||||
|
||||
<!-- Circular iris -->
|
||||
<circle cx="0" cy="0" r="10"
|
||||
fill="none"
|
||||
stroke="#FFFFFF"
|
||||
stroke-width="3.5"/>
|
||||
|
||||
<!-- Pupil -->
|
||||
<circle cx="0" cy="0" r="4" fill="#FFFFFF"/>
|
||||
</g>
|
||||
</g>
|
||||
</defs>
|
||||
|
||||
<!-- Dark version for dark theme -->
|
||||
<g transform="translate(20, 50)">
|
||||
<!-- Shield centered vertically with text -->
|
||||
<g transform="translate(0, -72.5)">
|
||||
<use href="#shield-eye-accurate-dark"/>
|
||||
</g>
|
||||
<!-- Text aligned with shield center -->
|
||||
<text x="90" y="5" font-family="'Poppins', sans-serif" font-size="46" font-weight="600" fill="#FFFFFF">IntelSight</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
Nachher Breite: | Höhe: | Größe: 1.6 KiB |
@ -17,7 +17,7 @@
|
||||
L 25 40
|
||||
L 35 30 Z"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke="#232D53"
|
||||
stroke-width="3.5"
|
||||
stroke-linejoin="miter"/>
|
||||
|
||||
@ -26,28 +26,28 @@
|
||||
<!-- Almond/football shaped eye -->
|
||||
<ellipse cx="0" cy="0" rx="24" ry="13"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke="#232D53"
|
||||
stroke-width="3.5"/>
|
||||
|
||||
<!-- Circular iris -->
|
||||
<circle cx="0" cy="0" r="10"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke="#232D53"
|
||||
stroke-width="3.5"/>
|
||||
|
||||
<!-- Pupil -->
|
||||
<circle cx="0" cy="0" r="4" fill="currentColor"/>
|
||||
<circle cx="0" cy="0" r="4" fill="#232D53"/>
|
||||
</g>
|
||||
</g>
|
||||
</defs>
|
||||
|
||||
<!-- Light version -->
|
||||
<!-- Light version with IntelSight corporate colors -->
|
||||
<g transform="translate(20, 50)">
|
||||
<!-- Shield centered vertically with text -->
|
||||
<g transform="translate(0, -72.5)" color="#232D53">
|
||||
<g transform="translate(0, -72.5)">
|
||||
<use href="#shield-eye-accurate"/>
|
||||
</g>
|
||||
<!-- Text aligned with shield center - NO TAGLINE -->
|
||||
<!-- Text aligned with shield center -->
|
||||
<text x="90" y="5" font-family="'Poppins', sans-serif" font-size="46" font-weight="600" fill="#232D53">IntelSight</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
Vorher Breite: | Höhe: | Größe: 1.7 KiB Nachher Breite: | Höhe: | Größe: 1.6 KiB |
@ -1 +1,691 @@
|
||||
/* Auto-generated empty stylesheet */
|
||||
/*
|
||||
* AccountForger Dark Mode Theme
|
||||
* Based on IntelSight Corporate Design System
|
||||
* Color Palette from CORPORATE_DESIGN_DARK_MODE.md
|
||||
*/
|
||||
|
||||
/* ==================== COLOR VARIABLES (Reference) ==================== */
|
||||
/*
|
||||
* Primary: #232D53 (Dark Blue)
|
||||
* Accent: #00D4FF (Cyan)
|
||||
* Accent Hover: #00B8E6 (Darker Cyan)
|
||||
* Background: #000000 (Black)
|
||||
* Secondary BG: #1A1F3A (Dark Blue for cards)
|
||||
* Sidebar: #0A0A0A (Almost Black)
|
||||
* Text Primary: #FFFFFF (White)
|
||||
* Text Secondary: rgba(255, 255, 255, 0.7) (70% White)
|
||||
* Text Tertiary: rgba(255, 255, 255, 0.6) (60% White)
|
||||
* Error: #FF4444 (Red)
|
||||
* Success: #4CAF50 (Green)
|
||||
* Warning: #FFC107 (Yellow)
|
||||
* Info: #2196F3 (Blue)
|
||||
*/
|
||||
|
||||
/* ==================== MAIN WINDOW ==================== */
|
||||
QMainWindow {
|
||||
background-color: #000000;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
/* ==================== WIDGETS & CONTAINERS ==================== */
|
||||
QWidget {
|
||||
background-color: #000000;
|
||||
color: #FFFFFF;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* Platform selector main container */
|
||||
QWidget#main_container {
|
||||
background-color: #000000;
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
/* Tab content areas */
|
||||
QStackedWidget {
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
/* ==================== HEADERS & LABELS ==================== */
|
||||
QLabel {
|
||||
color: #FFFFFF;
|
||||
background-color: transparent;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
/* Title Labels */
|
||||
QLabel#platform_title {
|
||||
font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
color: #FFFFFF;
|
||||
padding: 16px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
/* Secondary text */
|
||||
QLabel#secondary_text {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
/* ==================== PLATFORM BUTTONS (Clean styling) ==================== */
|
||||
/* Platform button container */
|
||||
PlatformButton {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/* Platform icon buttons */
|
||||
QPushButton#platform_icon_button {
|
||||
background-color: #1A1F3A;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 16px;
|
||||
padding: 32px;
|
||||
}
|
||||
|
||||
QPushButton#platform_icon_button:hover {
|
||||
background-color: #232D53;
|
||||
border: 1px solid #00D4FF;
|
||||
}
|
||||
|
||||
QPushButton#platform_icon_button:pressed {
|
||||
background-color: #2A3560;
|
||||
border: 1px solid #00D4FF;
|
||||
}
|
||||
|
||||
QPushButton#platform_icon_button:disabled {
|
||||
background-color: rgba(26, 31, 58, 0.5);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* Platform name labels */
|
||||
QLabel#platform_name_label {
|
||||
color: #FFFFFF;
|
||||
font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
/* ==================== PRIMARY BUTTONS ==================== */
|
||||
QPushButton {
|
||||
background-color: #00D4FF;
|
||||
color: #000000;
|
||||
border: none;
|
||||
border-radius: 24px;
|
||||
padding: 0 32px;
|
||||
font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
min-height: 48px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
QPushButton:hover {
|
||||
background-color: #00B8E6;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
QPushButton:pressed {
|
||||
background-color: #0099CC;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
QPushButton:disabled {
|
||||
background-color: rgba(0, 212, 255, 0.3);
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* Secondary Button */
|
||||
QPushButton#secondary_button {
|
||||
background-color: transparent;
|
||||
color: #FFFFFF;
|
||||
border: 1px solid #232D53;
|
||||
}
|
||||
|
||||
QPushButton#secondary_button:hover {
|
||||
background-color: #232D53;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
/* Icon Buttons */
|
||||
QPushButton#icon_button {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
QPushButton#icon_button:hover {
|
||||
background-color: rgba(35, 45, 83, 0.5);
|
||||
}
|
||||
|
||||
/* ==================== INPUT FIELDS ==================== */
|
||||
QLineEdit, QSpinBox, QTextEdit, QPlainTextEdit {
|
||||
background-color: #232D53;
|
||||
color: #FFFFFF;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
QLineEdit:focus, QSpinBox:focus, QTextEdit:focus, QPlainTextEdit:focus {
|
||||
background-color: #2A3560;
|
||||
outline: 2px solid #00D4FF;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
QLineEdit:disabled, QSpinBox:disabled, QTextEdit:disabled {
|
||||
background-color: rgba(35, 45, 83, 0.5);
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
/* Placeholder text */
|
||||
QLineEdit {
|
||||
selection-background-color: #00D4FF;
|
||||
selection-color: #000000;
|
||||
}
|
||||
|
||||
/* ==================== DROPDOWNS / COMBOBOX ==================== */
|
||||
QComboBox {
|
||||
background-color: #232D53;
|
||||
color: #FFFFFF;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 8px 16px;
|
||||
min-height: 32px;
|
||||
}
|
||||
|
||||
QComboBox:hover {
|
||||
background-color: #2A3560;
|
||||
}
|
||||
|
||||
QComboBox:focus {
|
||||
outline: 2px solid #00D4FF;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
QComboBox::drop-down {
|
||||
border: none;
|
||||
width: 20px;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
QComboBox::down-arrow {
|
||||
image: none;
|
||||
border-left: 5px solid transparent;
|
||||
border-right: 5px solid transparent;
|
||||
border-top: 5px solid #00D4FF;
|
||||
width: 0;
|
||||
height: 0;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
QComboBox QAbstractItemView {
|
||||
background-color: rgba(26, 31, 58, 0.95);
|
||||
border: 1px solid #00D4FF;
|
||||
border-radius: 8px;
|
||||
selection-background-color: #232D53;
|
||||
selection-color: #00D4FF;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
/* ==================== CHECKBOXES & RADIO BUTTONS ==================== */
|
||||
QCheckBox, QRadioButton {
|
||||
spacing: 8px;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
QCheckBox::indicator, QRadioButton::indicator {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid #232D53;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
QCheckBox::indicator {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
QRadioButton::indicator {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
QCheckBox::indicator:checked, QRadioButton::indicator:checked {
|
||||
background-color: #00D4FF;
|
||||
border-color: #00D4FF;
|
||||
}
|
||||
|
||||
QCheckBox::indicator:checked {
|
||||
image: url(:/icons/check.svg);
|
||||
}
|
||||
|
||||
QRadioButton::indicator:checked {
|
||||
background-color: #00D4FF;
|
||||
}
|
||||
|
||||
/* ==================== GROUP BOXES ==================== */
|
||||
QGroupBox {
|
||||
background-color: #1A1F3A;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 16px;
|
||||
padding: 32px;
|
||||
margin-top: 16px;
|
||||
font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
font-weight: 600;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
QGroupBox::title {
|
||||
color: #00D4FF;
|
||||
font-size: 16px;
|
||||
subcontrol-origin: margin;
|
||||
left: 16px;
|
||||
padding: 0 8px;
|
||||
background-color: #1A1F3A;
|
||||
}
|
||||
|
||||
/* ==================== TABS ==================== */
|
||||
QTabWidget::pane {
|
||||
background-color: #000000;
|
||||
border: none;
|
||||
}
|
||||
|
||||
QTabBar::tab {
|
||||
background-color: transparent;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
padding: 12px 24px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
}
|
||||
|
||||
QTabBar::tab:hover {
|
||||
background-color: rgba(35, 45, 83, 0.3);
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
QTabBar::tab:selected {
|
||||
color: #FFFFFF;
|
||||
background-color: transparent;
|
||||
border-bottom: 2px solid #00D4FF;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Tab Navigation Component */
|
||||
TabNavigation {
|
||||
background-color: #0A0A0A;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
TabNavigation QPushButton {
|
||||
background-color: transparent;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
padding: 16px 24px;
|
||||
font-weight: 500;
|
||||
min-height: 48px;
|
||||
}
|
||||
|
||||
TabNavigation QPushButton:hover {
|
||||
background-color: rgba(35, 45, 83, 0.3);
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
TabNavigation QPushButton:checked {
|
||||
color: #00D4FF;
|
||||
background-color: transparent;
|
||||
border-bottom: 2px solid #00D4FF;
|
||||
}
|
||||
|
||||
/* Badge in tabs */
|
||||
TabNavigation QLabel#badge {
|
||||
background-color: #00D4FF;
|
||||
color: #000000;
|
||||
padding: 2px 6px;
|
||||
border-radius: 10px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* ==================== TABLES ==================== */
|
||||
QTableWidget {
|
||||
background-color: #000000;
|
||||
alternate-background-color: rgba(26, 31, 58, 0.3);
|
||||
gridline-color: rgba(255, 255, 255, 0.1);
|
||||
border: none;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
QTableWidget::item {
|
||||
padding: 12px 16px;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
QTableWidget::item:selected {
|
||||
background-color: #232D53;
|
||||
color: #00D4FF;
|
||||
}
|
||||
|
||||
QHeaderView::section {
|
||||
background-color: #232D53;
|
||||
color: #FFFFFF;
|
||||
padding: 12px 16px;
|
||||
border: none;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
font-size: 13px;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
/* ==================== SCROLLBARS ==================== */
|
||||
QScrollBar:vertical {
|
||||
background-color: #1A1F3A;
|
||||
width: 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
QScrollBar::handle:vertical {
|
||||
background-color: #00D4FF;
|
||||
min-height: 20px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
QScrollBar::handle:vertical:hover {
|
||||
background-color: #00B8E6;
|
||||
}
|
||||
|
||||
QScrollBar:horizontal {
|
||||
background-color: #1A1F3A;
|
||||
height: 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
QScrollBar::handle:horizontal {
|
||||
background-color: #00D4FF;
|
||||
min-width: 20px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
QScrollBar::handle:horizontal:hover {
|
||||
background-color: #00B8E6;
|
||||
}
|
||||
|
||||
QScrollBar::add-line, QScrollBar::sub-line {
|
||||
border: none;
|
||||
background: none;
|
||||
}
|
||||
|
||||
/* ==================== PROGRESS BAR ==================== */
|
||||
QProgressBar {
|
||||
background-color: #232D53;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
height: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
QProgressBar::chunk {
|
||||
background-color: #00D4FF;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* ==================== STATUS BAR ==================== */
|
||||
QStatusBar {
|
||||
background-color: #0A0A0A;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-size: 13px;
|
||||
padding: 8px;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* ==================== MENU BAR ==================== */
|
||||
QMenuBar {
|
||||
background-color: #0A0A0A;
|
||||
color: #FFFFFF;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
QMenuBar::item {
|
||||
padding: 8px 16px;
|
||||
background-color: transparent;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
QMenuBar::item:selected {
|
||||
background-color: #232D53;
|
||||
color: #00D4FF;
|
||||
}
|
||||
|
||||
QMenu {
|
||||
background-color: rgba(26, 31, 58, 0.95);
|
||||
border: 1px solid rgba(0, 212, 255, 0.2);
|
||||
border-radius: 8px;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
QMenu::item {
|
||||
padding: 8px 32px;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
QMenu::item:selected {
|
||||
background-color: #232D53;
|
||||
color: #00D4FF;
|
||||
}
|
||||
|
||||
/* ==================== TOOLTIPS ==================== */
|
||||
QToolTip {
|
||||
background-color: #232D53;
|
||||
color: #FFFFFF;
|
||||
border: 1px solid rgba(0, 212, 255, 0.2);
|
||||
border-radius: 8px;
|
||||
padding: 8px 12px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* ==================== DIALOGS ==================== */
|
||||
QDialog {
|
||||
background-color: #000000;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
/* Dialog Title Bar */
|
||||
QDialog QWidget#title_bar {
|
||||
background-color: #232D53;
|
||||
min-height: 40px;
|
||||
border-radius: 8px 8px 0 0;
|
||||
}
|
||||
|
||||
/* ==================== MESSAGE BOXES ==================== */
|
||||
QMessageBox {
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
QMessageBox QLabel {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
QMessageBox QPushButton {
|
||||
background-color: #232D53;
|
||||
color: #FFFFFF;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
padding: 6px 20px;
|
||||
min-width: 80px;
|
||||
min-height: 26px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
QMessageBox QPushButton:hover {
|
||||
background-color: #2A3560;
|
||||
border-color: #00D4FF;
|
||||
}
|
||||
|
||||
QMessageBox QPushButton:pressed {
|
||||
background-color: #1A1F3A;
|
||||
}
|
||||
|
||||
/* Delete Button - Red accent */
|
||||
QMessageBox QPushButton[text="Löschen"], QMessageBox QPushButton[text="Delete"] {
|
||||
background-color: #FF4444;
|
||||
color: #FFFFFF;
|
||||
border: none;
|
||||
}
|
||||
|
||||
QMessageBox QPushButton[text="Löschen"]:hover, QMessageBox QPushButton[text="Delete"]:hover {
|
||||
background-color: #FF6666;
|
||||
}
|
||||
|
||||
/* ==================== ACCOUNT CARDS ==================== */
|
||||
/* Account card widget */
|
||||
QWidget#account_card {
|
||||
background-color: #1A1F3A;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 16px;
|
||||
padding: 32px;
|
||||
}
|
||||
|
||||
QWidget#account_card:hover {
|
||||
background-color: #232D53;
|
||||
border: 1px solid #00D4FF;
|
||||
}
|
||||
|
||||
/* ==================== LOG OUTPUT ==================== */
|
||||
QTextEdit#log_output {
|
||||
background-color: #1A1F3A;
|
||||
color: #FFFFFF;
|
||||
font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', monospace;
|
||||
font-size: 13px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
/* ==================== BADGES & TAGS ==================== */
|
||||
QLabel#badge {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
color: #FFFFFF;
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
QLabel#badge_primary {
|
||||
background-color: #00D4FF;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
QLabel#badge_success {
|
||||
background-color: #4CAF50;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
QLabel#badge_warning {
|
||||
background-color: #FFC107;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
QLabel#badge_error {
|
||||
background-color: #FF4444;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
/* ==================== SPECIAL EFFECTS ==================== */
|
||||
/* Glow effect for important elements */
|
||||
.glow {
|
||||
box-shadow: 0 0 20px rgba(0, 212, 255, 0.3);
|
||||
}
|
||||
|
||||
/* Success text */
|
||||
.success {
|
||||
color: #4CAF50;
|
||||
}
|
||||
|
||||
/* Error text */
|
||||
.error {
|
||||
color: #FF4444;
|
||||
}
|
||||
|
||||
/* Warning text */
|
||||
.warning {
|
||||
color: #FFC107;
|
||||
}
|
||||
|
||||
/* Info text */
|
||||
.info {
|
||||
color: #2196F3;
|
||||
}
|
||||
|
||||
/* ==================== LANGUAGE DROPDOWN ==================== */
|
||||
LanguageDropdown {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
LanguageDropdown QComboBox {
|
||||
background-color: #232D53;
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
/* ==================== FORGE ANIMATION ==================== */
|
||||
ForgeAnimationWidget {
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
/* ==================== MODERN MESSAGE BOX ==================== */
|
||||
ModernMessageBox {
|
||||
background-color: #000000;
|
||||
border: 1px solid #232D53;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
ModernMessageBox QLabel#title {
|
||||
color: #00D4FF;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
ModernMessageBox QLabel#message {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
/* ==================== SIDEBAR (if present) ==================== */
|
||||
QWidget#sidebar {
|
||||
background-color: #0A0A0A;
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* ==================== PLATFORM GRID VIEW ==================== */
|
||||
PlatformGridView {
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
/* ==================== ACCOUNTS OVERVIEW ==================== */
|
||||
AccountsOverviewView {
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
/* Account cards in overview */
|
||||
AccountCard {
|
||||
background-color: #1A1F3A;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
AccountCard:hover {
|
||||
background-color: #232D53;
|
||||
border: 1px solid #00D4FF;
|
||||
}
|
||||
|
||||
/* ==================== TRANSITIONS ==================== */
|
||||
/* Note: Qt doesn't support CSS transitions directly,
|
||||
but these are documented for custom implementation */
|
||||
* {
|
||||
/* Standard transition: all 0.3s ease */
|
||||
/* Fast transition: all 0.2s ease (for smaller elements) */
|
||||
}
|
||||
@ -433,6 +433,38 @@ QMessageBox QPushButton[text="Löschen"]:pressed {
|
||||
background-color: #B71C1C;
|
||||
}
|
||||
|
||||
/* ==================== PLATFORM BUTTONS (Clean styling) ==================== */
|
||||
/* Platform icon buttons */
|
||||
QPushButton#platform_icon_button {
|
||||
background-color: #F5F7FF;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 16px;
|
||||
padding: 32px;
|
||||
}
|
||||
|
||||
QPushButton#platform_icon_button:hover {
|
||||
background-color: #E8EBFF;
|
||||
border: 1px solid #0099CC;
|
||||
}
|
||||
|
||||
QPushButton#platform_icon_button:pressed {
|
||||
background-color: #DCE2FF;
|
||||
border: 1px solid #0099CC;
|
||||
}
|
||||
|
||||
QPushButton#platform_icon_button:disabled {
|
||||
background-color: #F0F0F0;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* Platform name labels */
|
||||
QLabel#platform_name_label {
|
||||
color: #232D53;
|
||||
font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
/* ==================== SPECIAL COLORS ==================== */
|
||||
/* Success */
|
||||
.success {
|
||||
|
||||
65
test_logo_display.py
Normale Datei
65
test_logo_display.py
Normale Datei
@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Debug script to check what's happening with logo switching
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Add project root to path
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from themes.theme_config import ThemeConfig
|
||||
|
||||
def check_logo_files():
|
||||
"""Check the actual logo files and their paths."""
|
||||
print("=" * 60)
|
||||
print("LOGO FILE ANALYSIS")
|
||||
print("=" * 60)
|
||||
|
||||
base_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
# Check what's in the config
|
||||
light_theme = ThemeConfig.get_theme('light')
|
||||
dark_theme = ThemeConfig.get_theme('dark')
|
||||
|
||||
print(f"\nTheme Config:")
|
||||
print(f" Light theme logo_path: {light_theme.get('logo_path', 'NOT SET')}")
|
||||
print(f" Dark theme logo_path: {dark_theme.get('logo_path', 'NOT SET')}")
|
||||
|
||||
# Check actual files
|
||||
icons_dir = os.path.join(base_dir, "resources", "icons")
|
||||
print(f"\nIcon files in {icons_dir}:")
|
||||
|
||||
for file in os.listdir(icons_dir):
|
||||
if "intelsight" in file.lower():
|
||||
file_path = os.path.join(icons_dir, file)
|
||||
file_size = os.path.getsize(file_path)
|
||||
print(f" - {file} ({file_size} bytes)")
|
||||
|
||||
# Test the path resolution
|
||||
print("\n" + "=" * 60)
|
||||
print("PATH RESOLUTION TEST")
|
||||
print("=" * 60)
|
||||
|
||||
# Simulate what happens in get_icon_path
|
||||
for theme_name in ['light', 'dark']:
|
||||
theme = ThemeConfig.get_theme(theme_name)
|
||||
logo_name = theme.get('logo_path', 'intelsight-logo.svg').replace('.svg', '')
|
||||
full_path = os.path.join(base_dir, "resources", "icons", f"{logo_name}.svg")
|
||||
|
||||
print(f"\n{theme_name.upper()} theme:")
|
||||
print(f" logo_name from config: {logo_name}")
|
||||
print(f" full_path: {full_path}")
|
||||
print(f" file exists: {os.path.exists(full_path)}")
|
||||
|
||||
if os.path.exists(full_path):
|
||||
# Check if it's actually an SVG
|
||||
with open(full_path, 'r') as f:
|
||||
first_line = f.readline().strip()
|
||||
is_svg = '<svg' in first_line.lower() or '<?xml' in first_line.lower()
|
||||
print(f" is valid SVG: {is_svg}")
|
||||
print(f" first line: {first_line[:50]}...")
|
||||
|
||||
if __name__ == "__main__":
|
||||
check_logo_files()
|
||||
88
test_logo_switching.py
Normale Datei
88
test_logo_switching.py
Normale Datei
@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script to verify logo switching logic without PyQt5
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Add project root to path
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from themes.theme_config import ThemeConfig
|
||||
|
||||
def test_logo_paths():
|
||||
"""Test that logo paths are correctly configured."""
|
||||
print("=" * 60)
|
||||
print("LOGO SWITCHING TEST")
|
||||
print("=" * 60)
|
||||
|
||||
# Test light theme logo
|
||||
light_theme = ThemeConfig.get_theme('light')
|
||||
light_logo = light_theme.get('logo_path', 'NOT_FOUND')
|
||||
print(f"\n✓ Light theme logo: {light_logo}")
|
||||
|
||||
# Test dark theme logo
|
||||
dark_theme = ThemeConfig.get_theme('dark')
|
||||
dark_logo = dark_theme.get('logo_path', 'NOT_FOUND')
|
||||
print(f"✓ Dark theme logo: {dark_logo}")
|
||||
|
||||
# Check if files exist
|
||||
base_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
light_path = os.path.join(base_dir, "resources", "icons", light_logo)
|
||||
dark_path = os.path.join(base_dir, "resources", "icons", dark_logo)
|
||||
|
||||
print(f"\n✓ Light logo exists: {os.path.exists(light_path)} ({light_path})")
|
||||
print(f"✓ Dark logo exists: {os.path.exists(dark_path)} ({dark_path})")
|
||||
|
||||
# Simulate theme manager logic
|
||||
print("\n" + "=" * 60)
|
||||
print("SIMULATING THEME MANAGER LOGIC")
|
||||
print("=" * 60)
|
||||
|
||||
class MockThemeManager:
|
||||
def __init__(self):
|
||||
self.base_dir = base_dir
|
||||
self.current_theme = 'light'
|
||||
|
||||
def get_icon_path(self, icon_name):
|
||||
if icon_name == "intelsight-logo":
|
||||
theme = ThemeConfig.get_theme(self.current_theme)
|
||||
logo_name = theme.get('logo_path', 'intelsight-logo.svg').replace('.svg', '')
|
||||
return os.path.join(self.base_dir, "resources", "icons", f"{logo_name}.svg")
|
||||
return os.path.join(self.base_dir, "resources", "icons", f"{icon_name}.svg")
|
||||
|
||||
tm = MockThemeManager()
|
||||
|
||||
# Test light theme
|
||||
tm.current_theme = 'light'
|
||||
light_result = tm.get_icon_path("intelsight-logo")
|
||||
print(f"\nLight theme path: {light_result}")
|
||||
print(f"File exists: {os.path.exists(light_result)}")
|
||||
|
||||
# Test dark theme
|
||||
tm.current_theme = 'dark'
|
||||
dark_result = tm.get_icon_path("intelsight-logo")
|
||||
print(f"\nDark theme path: {dark_result}")
|
||||
print(f"File exists: {os.path.exists(dark_result)}")
|
||||
|
||||
# Check if paths are different
|
||||
if light_result != dark_result:
|
||||
print("\n✅ SUCCESS: Different logos for different themes!")
|
||||
else:
|
||||
print("\n❌ ERROR: Same logo path for both themes!")
|
||||
return False
|
||||
|
||||
# Check actual file names
|
||||
if "intelsight-logo" in light_result and "intelsight-dark" in dark_result:
|
||||
print("✅ SUCCESS: Correct logo files selected!")
|
||||
else:
|
||||
print("❌ ERROR: Wrong logo files!")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = test_logo_paths()
|
||||
sys.exit(0 if success else 1)
|
||||
279
test_theme_system.py
Normale Datei
279
test_theme_system.py
Normale Datei
@ -0,0 +1,279 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Comprehensive test script for the theme system refactoring.
|
||||
Tests all components without requiring PyQt5.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
# Add project root to path
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
def test_theme_config():
|
||||
"""Test theme configuration module."""
|
||||
print("\n=== Testing Theme Configuration ===")
|
||||
try:
|
||||
from themes.theme_config import ThemeConfig
|
||||
|
||||
# Test light theme
|
||||
light = ThemeConfig.get_theme('light')
|
||||
print(f"✓ Light theme loaded: {len(light)} color definitions")
|
||||
|
||||
# Test dark theme
|
||||
dark = ThemeConfig.get_theme('dark')
|
||||
print(f"✓ Dark theme loaded: {len(dark)} color definitions")
|
||||
|
||||
# Check critical keys
|
||||
critical_keys = [
|
||||
'bg_primary', 'bg_secondary', 'bg_tertiary',
|
||||
'text_primary', 'text_secondary', 'text_tertiary',
|
||||
'accent', 'accent_hover', 'accent_pressed',
|
||||
'error', 'error_dark', 'success', 'warning',
|
||||
'border_default', 'border_subtle',
|
||||
'logo_path'
|
||||
]
|
||||
|
||||
for key in critical_keys:
|
||||
if key not in light:
|
||||
print(f"✗ Missing in light theme: {key}")
|
||||
return False
|
||||
if key not in dark:
|
||||
print(f"✗ Missing in dark theme: {key}")
|
||||
return False
|
||||
|
||||
print(f"✓ All {len(critical_keys)} critical keys present in both themes")
|
||||
|
||||
# Test sizes, fonts, etc
|
||||
sizes = ThemeConfig.FONT_SIZES
|
||||
fonts = ThemeConfig.FONTS
|
||||
weights = ThemeConfig.FONT_WEIGHTS
|
||||
spacing = ThemeConfig.SPACING
|
||||
radius = ThemeConfig.RADIUS
|
||||
|
||||
print(f"✓ Font sizes defined: {list(sizes.keys())}")
|
||||
print(f"✓ Font families defined: {list(fonts.keys())}")
|
||||
print(f"✓ Font weights defined: {list(weights.keys())}")
|
||||
print(f"✓ Spacing values defined: {list(spacing.keys())}")
|
||||
print(f"✓ Border radius defined: {list(radius.keys())}")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ Error testing theme config: {e}")
|
||||
return False
|
||||
|
||||
def test_qss_generator():
|
||||
"""Test QSS generation."""
|
||||
print("\n=== Testing QSS Generator ===")
|
||||
try:
|
||||
from themes.qss_generator import QSSGenerator
|
||||
|
||||
# Generate light theme QSS
|
||||
light_qss = QSSGenerator.generate('light')
|
||||
print(f"✓ Light theme QSS generated: {len(light_qss)} characters")
|
||||
|
||||
# Generate dark theme QSS
|
||||
dark_qss = QSSGenerator.generate('dark')
|
||||
print(f"✓ Dark theme QSS generated: {len(dark_qss)} characters")
|
||||
|
||||
# Check for key selectors
|
||||
key_selectors = [
|
||||
'QMainWindow', 'QPushButton', 'QLabel', 'QLineEdit',
|
||||
'QTextEdit', 'QScrollArea', 'QFrame', 'QWidget',
|
||||
'QMenuBar', 'QTabBar', 'QDialog'
|
||||
]
|
||||
|
||||
missing_light = []
|
||||
missing_dark = []
|
||||
|
||||
for selector in key_selectors:
|
||||
if selector not in light_qss:
|
||||
missing_light.append(selector)
|
||||
if selector not in dark_qss:
|
||||
missing_dark.append(selector)
|
||||
|
||||
if missing_light:
|
||||
print(f"✗ Missing selectors in light QSS: {missing_light}")
|
||||
if missing_dark:
|
||||
print(f"✗ Missing selectors in dark QSS: {missing_dark}")
|
||||
|
||||
if not missing_light and not missing_dark:
|
||||
print(f"✓ All {len(key_selectors)} key selectors present in both themes")
|
||||
|
||||
# Check for object name selectors (our custom ones)
|
||||
custom_selectors = [
|
||||
'#platform_button', '#filter_button', '#account_username',
|
||||
'#account_login_btn', '#account_export_btn', '#account_delete_btn',
|
||||
'#logo_button', '#dark_mode_toggle'
|
||||
]
|
||||
|
||||
found_custom = []
|
||||
for selector in custom_selectors:
|
||||
if selector in light_qss:
|
||||
found_custom.append(selector)
|
||||
|
||||
print(f"✓ Custom selectors found: {len(found_custom)}/{len(custom_selectors)}")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ Error testing QSS generator: {e}")
|
||||
return False
|
||||
|
||||
def test_file_structure():
|
||||
"""Test that all required files exist."""
|
||||
print("\n=== Testing File Structure ===")
|
||||
|
||||
required_files = [
|
||||
'themes/__init__.py',
|
||||
'themes/theme_config.py',
|
||||
'themes/qss_generator.py',
|
||||
'views/base/__init__.py',
|
||||
'views/base/theme_aware_widget.py',
|
||||
'views/widgets/dark_mode_toggle.py',
|
||||
'utils/theme_manager.py',
|
||||
'resources/icons/intelsight-logo.svg',
|
||||
'resources/icons/intelsight-dark.svg'
|
||||
]
|
||||
|
||||
missing = []
|
||||
for file in required_files:
|
||||
path = Path(file)
|
||||
if path.exists():
|
||||
print(f"✓ {file}")
|
||||
else:
|
||||
print(f"✗ Missing: {file}")
|
||||
missing.append(file)
|
||||
|
||||
if missing:
|
||||
print(f"\n✗ Missing {len(missing)} required files")
|
||||
return False
|
||||
else:
|
||||
print(f"\n✓ All {len(required_files)} required files present")
|
||||
return True
|
||||
|
||||
def test_no_hardcoded_colors():
|
||||
"""Check for hardcoded colors in view files."""
|
||||
print("\n=== Checking for Hardcoded Colors ===")
|
||||
|
||||
import re
|
||||
|
||||
# Pattern to match hex colors
|
||||
hex_pattern = re.compile(r'#[0-9A-Fa-f]{6}|#[0-9A-Fa-f]{3}')
|
||||
rgb_pattern = re.compile(r'rgb\s*\([^)]+\)|rgba\s*\([^)]+\)')
|
||||
|
||||
view_files = [
|
||||
'views/main_window.py',
|
||||
'views/platform_selector.py',
|
||||
'views/components/tab_navigation.py',
|
||||
'views/components/platform_grid_view.py',
|
||||
'views/components/accounts_overview_view.py',
|
||||
'views/widgets/platform_button.py',
|
||||
'views/widgets/account_card.py'
|
||||
]
|
||||
|
||||
files_with_colors = []
|
||||
|
||||
for file in view_files:
|
||||
if not Path(file).exists():
|
||||
continue
|
||||
|
||||
with open(file, 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
# Skip import statements and comments
|
||||
lines = content.split('\n')
|
||||
for i, line in enumerate(lines):
|
||||
# Skip comments and imports
|
||||
if line.strip().startswith('#') or line.strip().startswith('from') or line.strip().startswith('import'):
|
||||
continue
|
||||
|
||||
# Check for hex colors
|
||||
if 'setStyleSheet' in line and (hex_pattern.search(line) or rgb_pattern.search(line)):
|
||||
files_with_colors.append((file, i+1, line.strip()))
|
||||
|
||||
if files_with_colors:
|
||||
print(f"✗ Found {len(files_with_colors)} lines with hardcoded colors:")
|
||||
for file, line_no, line in files_with_colors[:5]: # Show first 5
|
||||
print(f" {file}:{line_no}: {line[:60]}...")
|
||||
return False
|
||||
else:
|
||||
print(f"✓ No hardcoded colors found in {len(view_files)} view files")
|
||||
return True
|
||||
|
||||
def test_imports():
|
||||
"""Test that all imports work correctly."""
|
||||
print("\n=== Testing Imports ===")
|
||||
|
||||
test_imports = [
|
||||
('themes.theme_config', 'ThemeConfig'),
|
||||
('themes.qss_generator', 'QSSGenerator'),
|
||||
('views.base.theme_aware_widget', 'ThemeAwareWidget'),
|
||||
]
|
||||
|
||||
failed = []
|
||||
|
||||
for module_name, class_name in test_imports:
|
||||
try:
|
||||
module = __import__(module_name, fromlist=[class_name])
|
||||
if hasattr(module, class_name):
|
||||
print(f"✓ {module_name}.{class_name}")
|
||||
else:
|
||||
print(f"✗ {module_name} missing {class_name}")
|
||||
failed.append(f"{module_name}.{class_name}")
|
||||
except ImportError as e:
|
||||
# Check if it's just PyQt5 missing
|
||||
if 'PyQt5' in str(e):
|
||||
print(f"⚠ {module_name}.{class_name} (requires PyQt5)")
|
||||
else:
|
||||
print(f"✗ {module_name}.{class_name}: {e}")
|
||||
failed.append(f"{module_name}.{class_name}")
|
||||
|
||||
if failed:
|
||||
print(f"\n✗ {len(failed)} imports failed")
|
||||
return False
|
||||
else:
|
||||
print(f"\n✓ All critical imports successful (PyQt5-dependent modules skipped)")
|
||||
return True
|
||||
|
||||
def main():
|
||||
"""Run all tests."""
|
||||
print("=" * 60)
|
||||
print("THEME SYSTEM COMPREHENSIVE TEST")
|
||||
print("=" * 60)
|
||||
|
||||
results = []
|
||||
|
||||
# Run tests
|
||||
results.append(("File Structure", test_file_structure()))
|
||||
results.append(("Theme Config", test_theme_config()))
|
||||
results.append(("QSS Generator", test_qss_generator()))
|
||||
results.append(("Imports", test_imports()))
|
||||
results.append(("No Hardcoded Colors", test_no_hardcoded_colors()))
|
||||
|
||||
# Summary
|
||||
print("\n" + "=" * 60)
|
||||
print("TEST SUMMARY")
|
||||
print("=" * 60)
|
||||
|
||||
passed = sum(1 for _, result in results if result)
|
||||
total = len(results)
|
||||
|
||||
for name, result in results:
|
||||
status = "✓ PASS" if result else "✗ FAIL"
|
||||
print(f"{status}: {name}")
|
||||
|
||||
print(f"\nTotal: {passed}/{total} tests passed")
|
||||
|
||||
if passed == total:
|
||||
print("\n✅ ALL TESTS PASSED! Theme system is properly configured.")
|
||||
else:
|
||||
print(f"\n⚠️ {total - passed} tests failed. Review the output above.")
|
||||
|
||||
return 0 if passed == total else 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
8
themes/__init__.py
Normale Datei
8
themes/__init__.py
Normale Datei
@ -0,0 +1,8 @@
|
||||
"""
|
||||
Theme System Package
|
||||
"""
|
||||
|
||||
from themes.theme_config import ThemeConfig
|
||||
from themes.qss_generator import QSSGenerator
|
||||
|
||||
__all__ = ['ThemeConfig', 'QSSGenerator']
|
||||
805
themes/qss_generator.py
Normale Datei
805
themes/qss_generator.py
Normale Datei
@ -0,0 +1,805 @@
|
||||
"""
|
||||
QSS Generator - Dynamically generates Qt Stylesheets from Theme Configuration
|
||||
"""
|
||||
|
||||
from themes.theme_config import ThemeConfig
|
||||
|
||||
|
||||
class QSSGenerator:
|
||||
"""
|
||||
Generates complete QSS stylesheets from theme configuration.
|
||||
This ensures all styling is consistent and centralized.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def generate(theme_name: str) -> str:
|
||||
"""
|
||||
Generate complete QSS for the specified theme.
|
||||
|
||||
Args:
|
||||
theme_name: 'light' or 'dark'
|
||||
|
||||
Returns:
|
||||
Complete QSS stylesheet as string
|
||||
"""
|
||||
theme = ThemeConfig.get_theme(theme_name)
|
||||
fonts = ThemeConfig.FONTS
|
||||
sizes = ThemeConfig.FONT_SIZES
|
||||
weights = ThemeConfig.FONT_WEIGHTS
|
||||
spacing = ThemeConfig.SPACING
|
||||
radius = ThemeConfig.RADIUS
|
||||
|
||||
qss = f"""
|
||||
/* ==================== AUTO-GENERATED THEME: {theme_name.upper()} ==================== */
|
||||
/* Generated from themes/theme_config.py - DO NOT EDIT DIRECTLY */
|
||||
|
||||
/* ==================== MAIN WINDOW ==================== */
|
||||
QMainWindow {{
|
||||
background-color: {theme['bg_primary']};
|
||||
color: {theme['text_primary']};
|
||||
}}
|
||||
|
||||
/* ==================== BASE WIDGETS ==================== */
|
||||
QWidget {{
|
||||
background-color: {theme['bg_primary']};
|
||||
color: {theme['text_primary']};
|
||||
font-family: {fonts['secondary']};
|
||||
font-size: {sizes['base']};
|
||||
}}
|
||||
|
||||
/* Content Stack - Main content area */
|
||||
QStackedWidget#content_stack {{
|
||||
background-color: {theme['bg_secondary']};
|
||||
}}
|
||||
|
||||
/* ==================== LABELS ==================== */
|
||||
QLabel {{
|
||||
color: {theme['text_primary']};
|
||||
background-color: transparent;
|
||||
padding: 2px;
|
||||
}}
|
||||
|
||||
/* Platform Title */
|
||||
QLabel#platform_title {{
|
||||
font-family: {fonts['primary']};
|
||||
font-size: {sizes['3xl']};
|
||||
font-weight: {weights['bold']};
|
||||
color: {theme['text_accent']};
|
||||
padding: {spacing['md']};
|
||||
letter-spacing: 1px;
|
||||
}}
|
||||
|
||||
/* Secondary Labels */
|
||||
QLabel#secondary_text {{
|
||||
color: {theme['text_secondary']};
|
||||
}}
|
||||
|
||||
/* Platform Name Labels */
|
||||
QLabel#platform_name_label {{
|
||||
color: {theme['platform_label']};
|
||||
font-family: {fonts['primary']};
|
||||
font-weight: {weights['semibold']};
|
||||
letter-spacing: 0.5px;
|
||||
}}
|
||||
|
||||
/* ==================== PLATFORM BUTTONS ==================== */
|
||||
PlatformButton {{
|
||||
background-color: transparent;
|
||||
}}
|
||||
|
||||
QPushButton#platform_icon_button {{
|
||||
background-color: {theme['platform_bg']};
|
||||
border: 1px solid {theme['platform_border']};
|
||||
border-radius: {radius['xl']};
|
||||
padding: {spacing['xl']};
|
||||
}}
|
||||
|
||||
QPushButton#platform_icon_button:hover {{
|
||||
background-color: {theme['platform_hover']};
|
||||
border: 1px solid {theme['platform_border_hover']};
|
||||
}}
|
||||
|
||||
QPushButton#platform_icon_button:pressed {{
|
||||
background-color: {theme['platform_pressed']};
|
||||
border: 1px solid {theme['platform_border_hover']};
|
||||
}}
|
||||
|
||||
QPushButton#platform_icon_button:disabled {{
|
||||
background-color: {theme['bg_tertiary']};
|
||||
opacity: 0.5;
|
||||
}}
|
||||
|
||||
/* ==================== BUTTONS ==================== */
|
||||
QPushButton {{
|
||||
background-color: {theme['btn_primary_bg']};
|
||||
color: {theme['btn_primary_text']};
|
||||
border: none;
|
||||
border-radius: {radius['round']};
|
||||
padding: 0 {spacing['xl']};
|
||||
font-family: {fonts['primary']};
|
||||
font-size: {sizes['base']};
|
||||
font-weight: {weights['semibold']};
|
||||
min-height: 48px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}}
|
||||
|
||||
QPushButton:hover {{
|
||||
background-color: {theme['accent_hover']};
|
||||
}}
|
||||
|
||||
QPushButton:pressed {{
|
||||
background-color: {theme['accent_pressed']};
|
||||
}}
|
||||
|
||||
QPushButton:disabled {{
|
||||
background-color: {theme['bg_tertiary']};
|
||||
color: {theme['text_tertiary']};
|
||||
opacity: 0.5;
|
||||
}}
|
||||
|
||||
/* Secondary Buttons */
|
||||
QPushButton#secondary_button {{
|
||||
background-color: {theme['btn_secondary_bg']};
|
||||
color: {theme['btn_secondary_text']};
|
||||
border: 1px solid {theme['btn_secondary_border']};
|
||||
}}
|
||||
|
||||
QPushButton#secondary_button:hover {{
|
||||
background-color: {theme['bg_hover']};
|
||||
}}
|
||||
|
||||
/* Danger Buttons */
|
||||
QPushButton#danger_button {{
|
||||
background-color: {theme['btn_danger_bg']};
|
||||
color: {theme['btn_danger_text']};
|
||||
}}
|
||||
|
||||
QPushButton#danger_button:hover {{
|
||||
background-color: {theme['error']};
|
||||
}}
|
||||
|
||||
/* ==================== TAB NAVIGATION ==================== */
|
||||
TabNavigation, QWidget#tab_navigation {{
|
||||
background-color: {theme['nav_bg']};
|
||||
border-bottom: 1px solid {theme['nav_border']};
|
||||
}}
|
||||
|
||||
TabNavigation QPushButton {{
|
||||
background-color: {theme['nav_item']};
|
||||
color: {theme['nav_text']};
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
padding: 6px {spacing['lg']} {spacing['sm']};
|
||||
font-weight: {weights['medium']};
|
||||
min-height: 48px;
|
||||
text-transform: none;
|
||||
}}
|
||||
|
||||
TabNavigation QPushButton:hover {{
|
||||
background-color: {theme['nav_item_hover']};
|
||||
color: {theme['text_primary']};
|
||||
}}
|
||||
|
||||
TabNavigation QPushButton:checked {{
|
||||
background-color: {theme['nav_item_active']};
|
||||
color: {theme['nav_text_active']};
|
||||
border-bottom: 2px solid {theme['accent']};
|
||||
}}
|
||||
|
||||
/* Tab Badge */
|
||||
TabNavigation QLabel#badge {{
|
||||
background-color: {theme['accent']};
|
||||
color: {theme['btn_primary_text']};
|
||||
padding: 2px 6px;
|
||||
border-radius: 10px;
|
||||
font-size: {sizes['xs']};
|
||||
font-weight: {weights['semibold']};
|
||||
}}
|
||||
|
||||
/* ==================== ACCOUNTS OVERVIEW ==================== */
|
||||
AccountsOverviewView, QWidget#accounts_overview {{
|
||||
background-color: {theme['bg_secondary']};
|
||||
}}
|
||||
|
||||
/* Filter Sidebar */
|
||||
QWidget#filter_sidebar {{
|
||||
background-color: {theme['surface_sidebar']};
|
||||
border-right: 1px solid {theme['border_default']};
|
||||
}}
|
||||
|
||||
/* Filter Buttons */
|
||||
QPushButton#filter_button {{
|
||||
background-color: transparent;
|
||||
color: {theme['text_secondary']};
|
||||
border: none;
|
||||
padding: {spacing['sm']} {spacing['md']};
|
||||
text-align: left;
|
||||
font-size: {sizes['base']};
|
||||
}}
|
||||
|
||||
QPushButton#filter_button:hover {{
|
||||
background-color: {theme['bg_hover']};
|
||||
color: {theme['text_primary']};
|
||||
}}
|
||||
|
||||
QPushButton#filter_button[selected="true"] {{
|
||||
background-color: {theme['bg_selected']};
|
||||
color: {theme['text_accent']};
|
||||
border-left: 3px solid {theme['accent']};
|
||||
}}
|
||||
|
||||
/* Content Area */
|
||||
QWidget#accounts_content {{
|
||||
background-color: {theme['bg_secondary']};
|
||||
}}
|
||||
|
||||
/* ==================== ACCOUNT CARDS ==================== */
|
||||
AccountCard, QFrame#account_card {{
|
||||
background-color: {theme['surface_card']};
|
||||
border: 1px solid {theme['border_subtle']};
|
||||
border-radius: {radius['lg']};
|
||||
padding: {spacing['md']};
|
||||
}}
|
||||
|
||||
AccountCard:hover, QFrame#account_card:hover {{
|
||||
border: 1px solid {theme['accent']};
|
||||
}}
|
||||
|
||||
/* Account Card Buttons */
|
||||
QPushButton#login_button {{
|
||||
background-color: {theme['accent']};
|
||||
color: {theme['btn_primary_text']};
|
||||
border-radius: {radius['md']};
|
||||
padding: {spacing['sm']} {spacing['md']};
|
||||
font-size: {sizes['sm']};
|
||||
min-height: 36px;
|
||||
}}
|
||||
|
||||
QPushButton#login_button:hover {{
|
||||
background-color: {theme['accent_hover']};
|
||||
}}
|
||||
|
||||
QPushButton#export_button {{
|
||||
background-color: {theme['success']};
|
||||
color: #FFFFFF;
|
||||
border-radius: {radius['md']};
|
||||
padding: {spacing['sm']} {spacing['md']};
|
||||
font-size: {sizes['sm']};
|
||||
min-height: 36px;
|
||||
}}
|
||||
|
||||
QPushButton#delete_button {{
|
||||
background-color: {theme['btn_danger_bg']};
|
||||
color: {theme['btn_danger_text']};
|
||||
border-radius: {radius['md']};
|
||||
padding: {spacing['sm']} {spacing['md']};
|
||||
font-size: {sizes['sm']};
|
||||
min-height: 36px;
|
||||
}}
|
||||
|
||||
/* ==================== INPUT FIELDS ==================== */
|
||||
QLineEdit, QSpinBox, QTextEdit, QPlainTextEdit {{
|
||||
background-color: {theme['input_bg']};
|
||||
color: {theme['text_primary']};
|
||||
border: 1px solid {theme['input_border']};
|
||||
border-radius: {radius['md']};
|
||||
padding: {spacing['sm']} {spacing['md']};
|
||||
font-size: {sizes['base']};
|
||||
}}
|
||||
|
||||
QLineEdit:focus, QSpinBox:focus, QTextEdit:focus, QPlainTextEdit:focus {{
|
||||
background-color: {theme['input_focus_bg']};
|
||||
border: 2px solid {theme['input_focus_border']};
|
||||
outline: none;
|
||||
}}
|
||||
|
||||
QLineEdit:disabled, QSpinBox:disabled, QTextEdit:disabled {{
|
||||
background-color: {theme['bg_tertiary']};
|
||||
color: {theme['text_tertiary']};
|
||||
}}
|
||||
|
||||
/* ==================== COMBOBOX ==================== */
|
||||
QComboBox {{
|
||||
background-color: {theme['input_bg']};
|
||||
color: {theme['text_primary']};
|
||||
border: 1px solid {theme['input_border']};
|
||||
border-radius: {radius['md']};
|
||||
padding: {spacing['sm']} {spacing['md']};
|
||||
min-height: 32px;
|
||||
}}
|
||||
|
||||
QComboBox:hover {{
|
||||
border: 1px solid {theme['accent']};
|
||||
}}
|
||||
|
||||
QComboBox:focus {{
|
||||
border: 2px solid {theme['input_focus_border']};
|
||||
}}
|
||||
|
||||
QComboBox::drop-down {{
|
||||
border: none;
|
||||
width: 20px;
|
||||
background-color: transparent;
|
||||
}}
|
||||
|
||||
QComboBox::down-arrow {{
|
||||
image: none;
|
||||
border-left: 5px solid transparent;
|
||||
border-right: 5px solid transparent;
|
||||
border-top: 5px solid {theme['accent']};
|
||||
width: 0;
|
||||
height: 0;
|
||||
margin-right: 5px;
|
||||
}}
|
||||
|
||||
QComboBox QAbstractItemView {{
|
||||
background-color: {theme['surface_card']};
|
||||
border: 1px solid {theme['border_default']};
|
||||
border-radius: {radius['md']};
|
||||
selection-background-color: {theme['bg_selected']};
|
||||
selection-color: {theme['text_accent']};
|
||||
color: {theme['text_primary']};
|
||||
}}
|
||||
|
||||
/* ==================== TABLES ==================== */
|
||||
QTableWidget {{
|
||||
background-color: {theme['surface_card']};
|
||||
alternate-background-color: {theme['bg_secondary']};
|
||||
gridline-color: {theme['border_subtle']};
|
||||
border: none;
|
||||
color: {theme['text_primary']};
|
||||
}}
|
||||
|
||||
QTableWidget::item {{
|
||||
padding: {spacing['sm']} {spacing['md']};
|
||||
color: {theme['text_primary']};
|
||||
}}
|
||||
|
||||
QTableWidget::item:selected {{
|
||||
background-color: {theme['bg_selected']};
|
||||
color: {theme['text_accent']};
|
||||
}}
|
||||
|
||||
QHeaderView::section {{
|
||||
background-color: {theme['bg_tertiary']};
|
||||
color: {theme['text_primary']};
|
||||
padding: {spacing['sm']} {spacing['md']};
|
||||
border: none;
|
||||
font-weight: {weights['semibold']};
|
||||
text-transform: uppercase;
|
||||
font-size: {sizes['sm']};
|
||||
letter-spacing: 0.5px;
|
||||
}}
|
||||
|
||||
/* ==================== SCROLL AREAS ==================== */
|
||||
QScrollArea {{
|
||||
background-color: {theme['bg_primary']};
|
||||
border: none;
|
||||
}}
|
||||
|
||||
QScrollArea > QWidget > QWidget {{
|
||||
background-color: {theme['bg_primary']};
|
||||
}}
|
||||
|
||||
/* ==================== SCROLLBARS ==================== */
|
||||
QScrollBar:vertical {{
|
||||
background-color: {theme['scrollbar_bg']};
|
||||
width: 8px;
|
||||
border-radius: 4px;
|
||||
}}
|
||||
|
||||
QScrollBar::handle:vertical {{
|
||||
background-color: {theme['scrollbar_handle']};
|
||||
min-height: 20px;
|
||||
border-radius: 4px;
|
||||
}}
|
||||
|
||||
QScrollBar::handle:vertical:hover {{
|
||||
background-color: {theme['scrollbar_handle_hover']};
|
||||
}}
|
||||
|
||||
QScrollBar:horizontal {{
|
||||
background-color: {theme['scrollbar_bg']};
|
||||
height: 8px;
|
||||
border-radius: 4px;
|
||||
}}
|
||||
|
||||
QScrollBar::handle:horizontal {{
|
||||
background-color: {theme['scrollbar_handle']};
|
||||
min-width: 20px;
|
||||
border-radius: 4px;
|
||||
}}
|
||||
|
||||
QScrollBar::handle:horizontal:hover {{
|
||||
background-color: {theme['scrollbar_handle_hover']};
|
||||
}}
|
||||
|
||||
QScrollBar::add-line, QScrollBar::sub-line {{
|
||||
border: none;
|
||||
background: none;
|
||||
}}
|
||||
|
||||
/* ==================== PROGRESS BAR ==================== */
|
||||
QProgressBar {{
|
||||
background-color: {theme['bg_tertiary']};
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
height: 8px;
|
||||
text-align: center;
|
||||
}}
|
||||
|
||||
QProgressBar::chunk {{
|
||||
background-color: {theme['accent']};
|
||||
border-radius: 4px;
|
||||
}}
|
||||
|
||||
/* ==================== TOOLTIPS ==================== */
|
||||
QToolTip {{
|
||||
background-color: {theme['bg_tertiary']};
|
||||
color: {theme['text_primary']};
|
||||
border: 1px solid {theme['border_default']};
|
||||
border-radius: {radius['md']};
|
||||
padding: {spacing['sm']} {spacing['sm']};
|
||||
font-size: {sizes['sm']};
|
||||
}}
|
||||
|
||||
/* ==================== MESSAGE BOXES ==================== */
|
||||
QMessageBox {{
|
||||
background-color: {theme['bg_primary']};
|
||||
}}
|
||||
|
||||
QMessageBox QLabel {{
|
||||
color: {theme['text_primary']};
|
||||
}}
|
||||
|
||||
QMessageBox QPushButton {{
|
||||
background-color: {theme['bg_tertiary']};
|
||||
color: {theme['text_primary']};
|
||||
border: 1px solid {theme['border_default']};
|
||||
border-radius: 4px;
|
||||
padding: 6px 20px;
|
||||
min-width: 80px;
|
||||
min-height: 26px;
|
||||
font-weight: {weights['medium']};
|
||||
text-transform: none;
|
||||
}}
|
||||
|
||||
QMessageBox QPushButton:hover {{
|
||||
background-color: {theme['bg_hover']};
|
||||
border-color: {theme['accent']};
|
||||
}}
|
||||
|
||||
/* Delete Buttons in Message Boxes */
|
||||
QMessageBox QPushButton[text="Löschen"], QMessageBox QPushButton[text="Delete"] {{
|
||||
background-color: {theme['btn_danger_bg']};
|
||||
color: {theme['btn_danger_text']};
|
||||
border: none;
|
||||
}}
|
||||
|
||||
/* ==================== STATUS BAR ==================== */
|
||||
QStatusBar {{
|
||||
background-color: {theme['bg_secondary']};
|
||||
color: {theme['text_secondary']};
|
||||
font-size: {sizes['sm']};
|
||||
padding: {spacing['sm']};
|
||||
border-top: 1px solid {theme['border_subtle']};
|
||||
}}
|
||||
|
||||
/* ==================== MENU BAR ==================== */
|
||||
QMenuBar {{
|
||||
background-color: {theme['bg_primary']};
|
||||
color: {theme['text_primary']};
|
||||
padding: 4px;
|
||||
}}
|
||||
|
||||
QMenuBar::item {{
|
||||
padding: {spacing['sm']} {spacing['md']};
|
||||
background-color: transparent;
|
||||
color: {theme['text_primary']};
|
||||
}}
|
||||
|
||||
QMenuBar::item:selected {{
|
||||
background-color: {theme['bg_hover']};
|
||||
color: {theme['text_accent']};
|
||||
}}
|
||||
|
||||
/* Logo button in menu bar */
|
||||
QPushButton#logo_button {{
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
padding: 5px;
|
||||
}}
|
||||
|
||||
QPushButton#logo_button:hover {{
|
||||
background-color: {theme['bg_hover']};
|
||||
border-radius: {radius['sm']};
|
||||
}}
|
||||
|
||||
QWidget#menubar_right_container {{
|
||||
background: transparent;
|
||||
}}
|
||||
|
||||
/* ==================== CHECKBOXES & RADIO BUTTONS ==================== */
|
||||
QCheckBox, QRadioButton {{
|
||||
spacing: 8px;
|
||||
color: {theme['text_primary']};
|
||||
}}
|
||||
|
||||
QCheckBox::indicator, QRadioButton::indicator {{
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid {theme['border_strong']};
|
||||
background-color: {theme['input_bg']};
|
||||
}}
|
||||
|
||||
QCheckBox::indicator {{
|
||||
border-radius: 4px;
|
||||
}}
|
||||
|
||||
QRadioButton::indicator {{
|
||||
border-radius: 10px;
|
||||
}}
|
||||
|
||||
QCheckBox::indicator:checked, QRadioButton::indicator:checked {{
|
||||
background-color: {theme['accent']};
|
||||
border-color: {theme['accent']};
|
||||
}}
|
||||
|
||||
/* ==================== GROUP BOXES ==================== */
|
||||
QGroupBox {{
|
||||
background-color: {theme['surface_card']};
|
||||
border: 1px solid {theme['border_default']};
|
||||
border-radius: {radius['xl']};
|
||||
padding: {spacing['xl']};
|
||||
margin-top: {spacing['md']};
|
||||
font-family: {fonts['primary']};
|
||||
font-weight: {weights['semibold']};
|
||||
color: {theme['text_primary']};
|
||||
}}
|
||||
|
||||
QGroupBox::title {{
|
||||
color: {theme['text_accent']};
|
||||
font-size: {sizes['lg']};
|
||||
subcontrol-origin: margin;
|
||||
left: {spacing['md']};
|
||||
padding: 0 {spacing['sm']};
|
||||
background-color: {theme['surface_card']};
|
||||
}}
|
||||
|
||||
/* ==================== TABS ==================== */
|
||||
QTabWidget::pane {{
|
||||
background-color: {theme['bg_secondary']};
|
||||
border: none;
|
||||
}}
|
||||
|
||||
QTabBar::tab {{
|
||||
background-color: transparent;
|
||||
color: {theme['text_secondary']};
|
||||
padding: {spacing['sm']} {spacing['lg']};
|
||||
font-size: {sizes['base']};
|
||||
font-weight: {weights['medium']};
|
||||
border: none;
|
||||
}}
|
||||
|
||||
QTabBar::tab:hover {{
|
||||
background-color: {theme['bg_hover']};
|
||||
color: {theme['text_primary']};
|
||||
}}
|
||||
|
||||
QTabBar::tab:selected {{
|
||||
color: {theme['text_accent']};
|
||||
background-color: transparent;
|
||||
border-bottom: 2px solid {theme['accent']};
|
||||
font-weight: {weights['semibold']};
|
||||
}}
|
||||
|
||||
/* ==================== DIALOGS ==================== */
|
||||
QDialog {{
|
||||
background-color: {theme['surface_modal']};
|
||||
color: {theme['text_primary']};
|
||||
}}
|
||||
|
||||
/* ==================== LOG OUTPUT ==================== */
|
||||
QTextEdit#log_output {{
|
||||
background-color: {theme['bg_tertiary']};
|
||||
color: {theme['text_primary']};
|
||||
font-family: {fonts['monospace']};
|
||||
font-size: {sizes['sm']};
|
||||
border: none;
|
||||
border-radius: {radius['md']};
|
||||
padding: {spacing['md']};
|
||||
}}
|
||||
|
||||
/* ==================== BADGES ==================== */
|
||||
QLabel#badge {{
|
||||
background-color: {theme['bg_tertiary']};
|
||||
color: {theme['text_primary']};
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: {sizes['xs']};
|
||||
font-weight: {weights['semibold']};
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}}
|
||||
|
||||
QLabel#badge_primary {{
|
||||
background-color: {theme['accent']};
|
||||
color: {theme['btn_primary_text']};
|
||||
}}
|
||||
|
||||
QLabel#badge_success {{
|
||||
background-color: {theme['success']};
|
||||
color: #FFFFFF;
|
||||
}}
|
||||
|
||||
QLabel#badge_warning {{
|
||||
background-color: {theme['warning']};
|
||||
color: #000000;
|
||||
}}
|
||||
|
||||
QLabel#badge_error {{
|
||||
background-color: {theme['error']};
|
||||
color: #FFFFFF;
|
||||
}}
|
||||
|
||||
/* ==================== ACCOUNTS OVERVIEW SPECIFIC ==================== */
|
||||
QLabel#platform_header {{
|
||||
color: {theme['text_primary']};
|
||||
padding: 8px 0;
|
||||
font-family: {fonts['primary']};
|
||||
font-size: {sizes['lg']};
|
||||
font-weight: {weights['semibold']};
|
||||
}}
|
||||
|
||||
QPushButton#delete_confirm_button {{
|
||||
background-color: {theme['error']};
|
||||
color: #FFFFFF;
|
||||
border: 1px solid {theme['error']};
|
||||
border-radius: {radius['sm']};
|
||||
padding: 6px 20px;
|
||||
min-width: 80px;
|
||||
min-height: 26px;
|
||||
font-weight: {weights['medium']};
|
||||
}}
|
||||
|
||||
QPushButton#delete_confirm_button:hover {{
|
||||
background-color: {theme['error_dark']};
|
||||
border-color: {theme['error_dark']};
|
||||
}}
|
||||
|
||||
QPushButton#delete_confirm_button:pressed {{
|
||||
background-color: {theme['error_dark']};
|
||||
}}
|
||||
|
||||
/* ==================== ACCOUNT CARD ==================== */
|
||||
QFrame#accountCard {{
|
||||
background-color: {theme['surface_card']};
|
||||
border: 2px solid {theme['border_default']};
|
||||
border-radius: {radius['md']};
|
||||
padding: 16px;
|
||||
}}
|
||||
|
||||
QFrame#accountCard:hover {{
|
||||
border: 2px solid {theme['accent']};
|
||||
}}
|
||||
|
||||
QFrame#accountCard[status="active"] {{
|
||||
background-color: {theme['success_bg']};
|
||||
border: 2px solid {theme['success']};
|
||||
}}
|
||||
|
||||
QFrame#accountCard[status="active"]:hover {{
|
||||
border: 2px solid {theme['success']};
|
||||
}}
|
||||
|
||||
QFrame#accountCard[status="inactive"] {{
|
||||
background-color: {theme['error_bg']};
|
||||
border: 2px solid {theme['error']};
|
||||
}}
|
||||
|
||||
QFrame#accountCard[status="inactive"]:hover {{
|
||||
border: 2px solid {theme['error_dark']};
|
||||
}}
|
||||
|
||||
QLabel#account_username {{
|
||||
color: {theme['text_primary']};
|
||||
font-family: {fonts['primary']};
|
||||
font-size: 16px;
|
||||
font-weight: {weights['semibold']};
|
||||
}}
|
||||
|
||||
QLabel#account_detail_text {{
|
||||
color: {theme['text_secondary']};
|
||||
font-size: {sizes['sm']};
|
||||
font-family: {fonts['primary']};
|
||||
}}
|
||||
|
||||
QLabel#account_date_text {{
|
||||
color: {theme['text_tertiary']};
|
||||
font-size: {sizes['xs']};
|
||||
font-family: {fonts['primary']};
|
||||
}}
|
||||
|
||||
QPushButton#account_icon_btn {{
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 2px;
|
||||
min-width: 20px;
|
||||
max-width: 20px;
|
||||
min-height: 20px;
|
||||
max-height: 20px;
|
||||
}}
|
||||
|
||||
QPushButton#account_icon_btn:hover {{
|
||||
background-color: {theme['bg_hover']};
|
||||
border-radius: {radius['sm']};
|
||||
}}
|
||||
|
||||
QPushButton#account_login_btn {{
|
||||
background-color: {theme['accent']};
|
||||
color: {theme['btn_primary_text']};
|
||||
border: none;
|
||||
border-radius: {radius['sm']};
|
||||
padding: 6px 16px;
|
||||
font-size: {sizes['xs']};
|
||||
font-weight: {weights['medium']};
|
||||
font-family: {fonts['primary']};
|
||||
min-width: 60px;
|
||||
}}
|
||||
|
||||
QPushButton#account_login_btn:hover {{
|
||||
background-color: {theme['accent_hover']};
|
||||
}}
|
||||
|
||||
QPushButton#account_login_btn:pressed {{
|
||||
background-color: {theme['accent_pressed']};
|
||||
}}
|
||||
|
||||
QPushButton#account_export_btn {{
|
||||
background-color: {theme['success']};
|
||||
color: #FFFFFF;
|
||||
border: none;
|
||||
border-radius: {radius['sm']};
|
||||
padding: 4px 12px;
|
||||
font-size: {sizes['xs']};
|
||||
font-weight: {weights['medium']};
|
||||
font-family: {fonts['primary']};
|
||||
min-width: 120px;
|
||||
min-height: 36px;
|
||||
text-align: center;
|
||||
}}
|
||||
|
||||
QPushButton#account_export_btn:hover {{
|
||||
background-color: {theme['success']};
|
||||
opacity: 0.9;
|
||||
}}
|
||||
|
||||
QPushButton#account_export_btn:pressed {{
|
||||
background-color: {theme['success']};
|
||||
opacity: 0.8;
|
||||
}}
|
||||
|
||||
QPushButton#account_delete_btn {{
|
||||
background-color: {theme['error']};
|
||||
color: #FFFFFF;
|
||||
border: none;
|
||||
border-radius: {radius['sm']};
|
||||
padding: 8px 16px;
|
||||
font-size: {sizes['xs']};
|
||||
font-weight: {weights['medium']};
|
||||
font-family: {fonts['primary']};
|
||||
min-width: 90px;
|
||||
}}
|
||||
|
||||
QPushButton#account_delete_btn:hover {{
|
||||
background-color: {theme['error_dark']};
|
||||
}}
|
||||
|
||||
QPushButton#account_delete_btn:pressed {{
|
||||
background-color: {theme['error_dark']};
|
||||
opacity: 0.9;
|
||||
}}
|
||||
"""
|
||||
|
||||
return qss
|
||||
247
themes/theme_config.py
Normale Datei
247
themes/theme_config.py
Normale Datei
@ -0,0 +1,247 @@
|
||||
"""
|
||||
Theme Configuration - Single Source of Truth for all UI Colors and Styles
|
||||
Based on IntelSight Corporate Design System
|
||||
"""
|
||||
|
||||
class ThemeConfig:
|
||||
"""
|
||||
Centralized theme configuration.
|
||||
All colors, fonts, and styling values should be defined here.
|
||||
NO hardcoded colors in widgets!
|
||||
"""
|
||||
|
||||
THEMES = {
|
||||
'light': {
|
||||
# ========== BACKGROUNDS ==========
|
||||
'bg_primary': '#FFFFFF', # Main window background
|
||||
'bg_secondary': '#F8FAFC', # Content areas, cards
|
||||
'bg_tertiary': '#F5F7FF', # Input fields, subtle backgrounds
|
||||
'bg_hover': '#F0F4FF', # Hover states
|
||||
'bg_selected': '#E8EBFF', # Selected items
|
||||
|
||||
# ========== SURFACES ==========
|
||||
'surface_card': '#FFFFFF', # Card backgrounds
|
||||
'surface_modal': '#FFFFFF', # Modal backgrounds
|
||||
'surface_sidebar': '#FAFBFC', # Sidebar background
|
||||
|
||||
# ========== TEXT ==========
|
||||
'text_primary': '#1E1E1E', # Main text
|
||||
'text_secondary': '#666666', # Secondary text
|
||||
'text_tertiary': '#999999', # Disabled/hint text
|
||||
'text_accent': '#232D53', # Headers, important text
|
||||
|
||||
# ========== BRAND COLORS ==========
|
||||
'accent': '#0099CC', # Primary accent (buttons, links)
|
||||
'accent_hover': '#0078A3', # Accent hover state
|
||||
'accent_pressed': '#005C7A', # Accent pressed state
|
||||
|
||||
# ========== PLATFORM BUTTONS ==========
|
||||
'platform_bg': '#F5F7FF', # Platform button background
|
||||
'platform_hover': '#E8EBFF', # Platform button hover
|
||||
'platform_pressed': '#DCE2FF', # Platform button pressed
|
||||
'platform_border': 'transparent', # Platform button border
|
||||
'platform_border_hover': '#0099CC', # Platform button border hover
|
||||
'platform_label': '#232D53', # Platform name text
|
||||
|
||||
# ========== NAVIGATION ==========
|
||||
'nav_bg': '#FFFFFF', # Navigation background
|
||||
'nav_border': '#E2E8F0', # Navigation borders
|
||||
'nav_item': 'transparent', # Nav item background
|
||||
'nav_item_hover': '#F7FAFC', # Nav item hover
|
||||
'nav_item_active': '#E6F2FF', # Nav item active
|
||||
'nav_text': '#2D3748', # Nav text
|
||||
'nav_text_active': '#1E40AF', # Nav active text
|
||||
|
||||
# ========== INPUTS ==========
|
||||
'input_bg': '#F5F7FF', # Input background
|
||||
'input_border': '#E0E6FF', # Input border
|
||||
'input_focus_bg': '#FFFFFF', # Input focused background
|
||||
'input_focus_border': '#0099CC', # Input focused border
|
||||
|
||||
# ========== BUTTONS ==========
|
||||
'btn_primary_bg': '#0099CC', # Primary button
|
||||
'btn_primary_text': '#FFFFFF', # Primary button text
|
||||
'btn_secondary_bg': 'transparent', # Secondary button
|
||||
'btn_secondary_text': '#232D53', # Secondary button text
|
||||
'btn_secondary_border': '#232D53', # Secondary button border
|
||||
'btn_danger_bg': '#F44336', # Danger button
|
||||
'btn_danger_text': '#FFFFFF', # Danger button text
|
||||
|
||||
# ========== STATUS COLORS ==========
|
||||
'success': '#4CAF50', # Success states
|
||||
'success_bg': '#E8F5E9', # Success background
|
||||
'warning': '#FFC107', # Warning states
|
||||
'warning_bg': '#FFF8E1', # Warning background
|
||||
'error': '#F44336', # Error states
|
||||
'error_dark': '#D32F2F', # Error dark for hover
|
||||
'error_bg': '#FFEBEE', # Error background
|
||||
'info': '#2196F3', # Info states
|
||||
'info_bg': '#E3F2FD', # Info background
|
||||
|
||||
# ========== BORDERS ==========
|
||||
'border_default': '#E0E6FF', # Default borders
|
||||
'border_subtle': '#F0F0F0', # Subtle borders
|
||||
'border_strong': '#CCCCCC', # Strong borders
|
||||
|
||||
# ========== SHADOWS ==========
|
||||
'shadow_sm': '0 1px 3px rgba(0,0,0,0.12)',
|
||||
'shadow_md': '0 4px 6px rgba(0,0,0,0.15)',
|
||||
'shadow_lg': '0 10px 20px rgba(0,0,0,0.15)',
|
||||
|
||||
# ========== SCROLLBAR ==========
|
||||
'scrollbar_bg': '#F5F7FF',
|
||||
'scrollbar_handle': '#0099CC',
|
||||
'scrollbar_handle_hover': '#0078A3',
|
||||
|
||||
# ========== LOGO ==========
|
||||
'logo_path': 'intelsight-logo.svg',
|
||||
},
|
||||
|
||||
'dark': {
|
||||
# ========== BACKGROUNDS ==========
|
||||
'bg_primary': '#000000', # Main window background
|
||||
'bg_secondary': '#0A0A0A', # Content areas
|
||||
'bg_tertiary': '#1A1F3A', # Cards, elevated surfaces
|
||||
'bg_hover': '#232D53', # Hover states
|
||||
'bg_selected': '#2A3560', # Selected items
|
||||
|
||||
# ========== SURFACES ==========
|
||||
'surface_card': '#1A1F3A', # Card backgrounds
|
||||
'surface_modal': '#0A0A0A', # Modal backgrounds
|
||||
'surface_sidebar': '#0A0A0A', # Sidebar background
|
||||
|
||||
# ========== TEXT ==========
|
||||
'text_primary': '#FFFFFF', # Main text
|
||||
'text_secondary': 'rgba(255,255,255,0.7)', # Secondary text
|
||||
'text_tertiary': 'rgba(255,255,255,0.5)', # Disabled/hint text
|
||||
'text_accent': '#00D4FF', # Headers, important text
|
||||
|
||||
# ========== BRAND COLORS ==========
|
||||
'accent': '#00D4FF', # Primary accent (buttons, links)
|
||||
'accent_hover': '#00B8E6', # Accent hover state
|
||||
'accent_pressed': '#0099CC', # Accent pressed state
|
||||
|
||||
# ========== PLATFORM BUTTONS ==========
|
||||
'platform_bg': '#1A1F3A', # Platform button background
|
||||
'platform_hover': '#232D53', # Platform button hover
|
||||
'platform_pressed': '#2A3560', # Platform button pressed
|
||||
'platform_border': 'transparent', # Platform button border
|
||||
'platform_border_hover': '#00D4FF', # Platform button border hover
|
||||
'platform_label': '#FFFFFF', # Platform name text
|
||||
|
||||
# ========== NAVIGATION ==========
|
||||
'nav_bg': '#0A0A0A', # Navigation background
|
||||
'nav_border': 'rgba(255,255,255,0.1)', # Navigation borders
|
||||
'nav_item': 'transparent', # Nav item background
|
||||
'nav_item_hover': 'rgba(35,45,83,0.3)', # Nav item hover
|
||||
'nav_item_active': '#232D53', # Nav item active
|
||||
'nav_text': 'rgba(255,255,255,0.6)', # Nav text
|
||||
'nav_text_active': '#00D4FF', # Nav active text
|
||||
|
||||
# ========== INPUTS ==========
|
||||
'input_bg': '#232D53', # Input background
|
||||
'input_border': 'transparent', # Input border
|
||||
'input_focus_bg': '#2A3560', # Input focused background
|
||||
'input_focus_border': '#00D4FF', # Input focused border
|
||||
|
||||
# ========== BUTTONS ==========
|
||||
'btn_primary_bg': '#00D4FF', # Primary button
|
||||
'btn_primary_text': '#000000', # Primary button text
|
||||
'btn_secondary_bg': 'transparent', # Secondary button
|
||||
'btn_secondary_text': '#FFFFFF', # Secondary button text
|
||||
'btn_secondary_border': '#232D53', # Secondary button border
|
||||
'btn_danger_bg': '#FF4444', # Danger button
|
||||
'btn_danger_text': '#FFFFFF', # Danger button text
|
||||
|
||||
# ========== STATUS COLORS ==========
|
||||
'success': '#4CAF50', # Success states
|
||||
'success_bg': 'rgba(76,175,80,0.2)', # Success background
|
||||
'warning': '#FFC107', # Warning states
|
||||
'warning_bg': 'rgba(255,193,7,0.2)', # Warning background
|
||||
'error': '#FF4444', # Error states
|
||||
'error_dark': '#CC0000', # Error dark for hover
|
||||
'error_bg': 'rgba(255,68,68,0.2)', # Error background
|
||||
'info': '#2196F3', # Info states
|
||||
'info_bg': 'rgba(33,150,243,0.2)', # Info background
|
||||
|
||||
# ========== BORDERS ==========
|
||||
'border_default': 'rgba(255,255,255,0.1)', # Default borders
|
||||
'border_subtle': 'rgba(255,255,255,0.05)', # Subtle borders
|
||||
'border_strong': 'rgba(255,255,255,0.2)', # Strong borders
|
||||
|
||||
# ========== SHADOWS ==========
|
||||
'shadow_sm': '0 1px 3px rgba(0,0,0,0.3)',
|
||||
'shadow_md': '0 4px 6px rgba(0,0,0,0.4)',
|
||||
'shadow_lg': '0 10px 20px rgba(0,0,0,0.5)',
|
||||
|
||||
# ========== SCROLLBAR ==========
|
||||
'scrollbar_bg': '#1A1F3A',
|
||||
'scrollbar_handle': '#00D4FF',
|
||||
'scrollbar_handle_hover': '#00B8E6',
|
||||
|
||||
# ========== LOGO ==========
|
||||
'logo_path': 'intelsight-dark.svg',
|
||||
}
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_theme(cls, theme_name: str) -> dict:
|
||||
"""Get theme configuration by name"""
|
||||
return cls.THEMES.get(theme_name, cls.THEMES['light'])
|
||||
|
||||
@classmethod
|
||||
def get_color(cls, theme_name: str, color_key: str) -> str:
|
||||
"""Get specific color from theme"""
|
||||
theme = cls.get_theme(theme_name)
|
||||
return theme.get(color_key, '#000000')
|
||||
|
||||
# ========== TYPOGRAPHY ==========
|
||||
FONTS = {
|
||||
'primary': "'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
|
||||
'secondary': "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif",
|
||||
'monospace': "'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', monospace",
|
||||
}
|
||||
|
||||
FONT_SIZES = {
|
||||
'xs': '11px',
|
||||
'sm': '12px',
|
||||
'base': '14px',
|
||||
'lg': '16px',
|
||||
'xl': '18px',
|
||||
'2xl': '24px',
|
||||
'3xl': '32px',
|
||||
}
|
||||
|
||||
FONT_WEIGHTS = {
|
||||
'regular': '400',
|
||||
'medium': '500',
|
||||
'semibold': '600',
|
||||
'bold': '700',
|
||||
}
|
||||
|
||||
# ========== SPACING ==========
|
||||
SPACING = {
|
||||
'xs': '4px',
|
||||
'sm': '8px',
|
||||
'md': '16px',
|
||||
'lg': '24px',
|
||||
'xl': '32px',
|
||||
'2xl': '40px',
|
||||
}
|
||||
|
||||
# ========== BORDER RADIUS ==========
|
||||
RADIUS = {
|
||||
'sm': '4px',
|
||||
'md': '8px',
|
||||
'lg': '12px',
|
||||
'xl': '16px',
|
||||
'round': '24px',
|
||||
'full': '50%',
|
||||
}
|
||||
|
||||
# ========== TRANSITIONS ==========
|
||||
TRANSITIONS = {
|
||||
'fast': '0.2s ease',
|
||||
'normal': '0.3s ease',
|
||||
'slow': '0.5s ease',
|
||||
}
|
||||
@ -1,5 +1,7 @@
|
||||
"""
|
||||
Theme Manager - Verwaltet das Erscheinungsbild der Anwendung (nur Light Mode)
|
||||
Theme Manager - Verwaltet das Erscheinungsbild der Anwendung (Light & Dark Mode)
|
||||
Enhanced with Dark Mode support based on Corporate Design Guidelines
|
||||
Now using centralized theme configuration
|
||||
"""
|
||||
|
||||
import os
|
||||
@ -8,17 +10,26 @@ import logging
|
||||
from typing import Dict, Any, Optional
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
from PyQt5.QtGui import QPalette, QColor
|
||||
from PyQt5.QtCore import Qt, QSettings
|
||||
from PyQt5.QtCore import Qt, QSettings, pyqtSignal, QObject
|
||||
|
||||
# Import new theme system
|
||||
from themes.theme_config import ThemeConfig
|
||||
from themes.qss_generator import QSSGenerator
|
||||
|
||||
logger = logging.getLogger("theme_manager")
|
||||
|
||||
class ThemeManager:
|
||||
class ThemeManager(QObject):
|
||||
"""
|
||||
Verwaltet das Erscheinungsbild der Anwendung.
|
||||
Supports Light and Dark themes with smooth transitions.
|
||||
"""
|
||||
|
||||
# Signal emitted when theme changes
|
||||
theme_changed = pyqtSignal(str)
|
||||
|
||||
# Themennamen
|
||||
LIGHT_THEME = "light"
|
||||
DARK_THEME = "dark"
|
||||
|
||||
def __init__(self, app: QApplication):
|
||||
"""
|
||||
@ -27,9 +38,12 @@ class ThemeManager:
|
||||
Args:
|
||||
app: Die QApplication-Instanz
|
||||
"""
|
||||
super().__init__()
|
||||
self.app = app
|
||||
self.settings = QSettings("Chimaira", "SocialMediaAccountGenerator")
|
||||
self.current_theme = self.LIGHT_THEME
|
||||
|
||||
# Load saved theme preference or default to light
|
||||
self.current_theme = self.settings.value("theme", self.LIGHT_THEME)
|
||||
|
||||
# Basisverzeichnis ermitteln
|
||||
self.base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
@ -38,86 +52,133 @@ class ThemeManager:
|
||||
os.makedirs(os.path.join(self.base_dir, "resources", "themes"), exist_ok=True)
|
||||
os.makedirs(os.path.join(self.base_dir, "resources", "icons"), exist_ok=True)
|
||||
|
||||
# Lade QSS-Dateien für Themes
|
||||
# Generate QSS from theme configuration
|
||||
self.qss_generator = QSSGenerator()
|
||||
self.theme_stylesheets = {
|
||||
self.LIGHT_THEME: self._load_stylesheet("light.qss")
|
||||
self.LIGHT_THEME: self.qss_generator.generate(self.LIGHT_THEME),
|
||||
self.DARK_THEME: self.qss_generator.generate(self.DARK_THEME)
|
||||
}
|
||||
|
||||
# Wende das Light Theme an
|
||||
self.apply_theme(self.LIGHT_THEME)
|
||||
# Apply saved theme
|
||||
self.apply_theme(self.current_theme)
|
||||
|
||||
logger.info(f"ThemeManager initialisiert mit Theme: {self.current_theme}")
|
||||
logger.info(f"ThemeManager initialized with theme: {self.current_theme}")
|
||||
|
||||
def _load_stylesheet(self, filename: str) -> str:
|
||||
"""Lädt ein QSS-Stylesheet aus einer Datei."""
|
||||
try:
|
||||
stylesheet_path = os.path.join(self.base_dir, "resources", "themes", filename)
|
||||
if os.path.exists(stylesheet_path):
|
||||
with open(stylesheet_path, 'r', encoding='utf-8') as f:
|
||||
return f.read()
|
||||
else:
|
||||
logger.warning(f"Stylesheet-Datei nicht gefunden: {stylesheet_path}")
|
||||
# Erzeuge eine leere Stylesheet-Datei, wenn sie nicht existiert
|
||||
with open(stylesheet_path, 'w', encoding='utf-8') as f:
|
||||
f.write("/* Auto-generated empty stylesheet */\n")
|
||||
return ""
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Laden des Stylesheets {filename}: {e}")
|
||||
return ""
|
||||
def regenerate_stylesheets(self):
|
||||
"""Regenerate stylesheets from theme configuration"""
|
||||
self.theme_stylesheets = {
|
||||
self.LIGHT_THEME: self.qss_generator.generate(self.LIGHT_THEME),
|
||||
self.DARK_THEME: self.qss_generator.generate(self.DARK_THEME)
|
||||
}
|
||||
|
||||
def get_color(self, color_key: str) -> str:
|
||||
"""Get a color from the current theme"""
|
||||
return ThemeConfig.get_color(self.current_theme, color_key)
|
||||
|
||||
def apply_theme(self, theme_name: str) -> bool:
|
||||
"""
|
||||
Wendet das Light Theme auf die Anwendung an.
|
||||
Wendet das angegebene Theme auf die Anwendung an.
|
||||
|
||||
Args:
|
||||
theme_name: Wird ignoriert, immer Light Theme verwendet
|
||||
theme_name: Name des Themes ("light" oder "dark")
|
||||
|
||||
Returns:
|
||||
bool: True, wenn das Theme erfolgreich angewendet wurde, sonst False
|
||||
"""
|
||||
try:
|
||||
# Palette für das Light Theme erstellen
|
||||
# Validate theme name
|
||||
if theme_name not in [self.LIGHT_THEME, self.DARK_THEME]:
|
||||
logger.warning(f"Unknown theme '{theme_name}', falling back to light theme")
|
||||
theme_name = self.LIGHT_THEME
|
||||
|
||||
# Create palette based on theme
|
||||
palette = QPalette()
|
||||
|
||||
# Light Theme Palette
|
||||
palette.setColor(QPalette.Window, QColor(240, 240, 240))
|
||||
palette.setColor(QPalette.WindowText, Qt.black)
|
||||
palette.setColor(QPalette.Base, Qt.white)
|
||||
palette.setColor(QPalette.AlternateBase, QColor(245, 245, 245))
|
||||
palette.setColor(QPalette.ToolTipBase, Qt.white)
|
||||
palette.setColor(QPalette.ToolTipText, Qt.black)
|
||||
palette.setColor(QPalette.Text, Qt.black)
|
||||
palette.setColor(QPalette.Button, QColor(240, 240, 240))
|
||||
palette.setColor(QPalette.ButtonText, Qt.black)
|
||||
palette.setColor(QPalette.BrightText, Qt.red)
|
||||
palette.setColor(QPalette.Link, QColor(0, 0, 255))
|
||||
palette.setColor(QPalette.Highlight, QColor(42, 130, 218))
|
||||
palette.setColor(QPalette.HighlightedText, Qt.white)
|
||||
# Get colors from theme configuration
|
||||
theme = ThemeConfig.get_theme(theme_name)
|
||||
|
||||
# Palette auf die Anwendung anwenden
|
||||
if theme_name == self.DARK_THEME:
|
||||
# Dark Theme Palette from configuration
|
||||
palette.setColor(QPalette.Window, QColor(theme['bg_primary']))
|
||||
palette.setColor(QPalette.WindowText, QColor(theme['text_primary']))
|
||||
palette.setColor(QPalette.Base, QColor(theme['bg_tertiary']))
|
||||
palette.setColor(QPalette.AlternateBase, QColor(theme['bg_secondary']))
|
||||
palette.setColor(QPalette.ToolTipBase, QColor(theme['bg_tertiary']))
|
||||
palette.setColor(QPalette.ToolTipText, QColor(theme['text_primary']))
|
||||
palette.setColor(QPalette.Text, QColor(theme['text_primary']))
|
||||
palette.setColor(QPalette.Button, QColor(theme['bg_tertiary']))
|
||||
palette.setColor(QPalette.ButtonText, QColor(theme['text_primary']))
|
||||
palette.setColor(QPalette.BrightText, QColor(theme['accent']))
|
||||
palette.setColor(QPalette.Link, QColor(theme['accent']))
|
||||
palette.setColor(QPalette.Highlight, QColor(theme['accent']))
|
||||
palette.setColor(QPalette.HighlightedText, QColor(theme['btn_primary_text']))
|
||||
else:
|
||||
# Light Theme Palette from configuration
|
||||
palette.setColor(QPalette.Window, QColor(theme['bg_primary']))
|
||||
palette.setColor(QPalette.WindowText, QColor(theme['text_primary']))
|
||||
palette.setColor(QPalette.Base, QColor(theme['bg_primary']))
|
||||
palette.setColor(QPalette.AlternateBase, QColor(theme['bg_secondary']))
|
||||
palette.setColor(QPalette.ToolTipBase, QColor(theme['bg_primary']))
|
||||
palette.setColor(QPalette.ToolTipText, QColor(theme['text_primary']))
|
||||
palette.setColor(QPalette.Text, QColor(theme['text_primary']))
|
||||
palette.setColor(QPalette.Button, QColor(theme['bg_secondary']))
|
||||
palette.setColor(QPalette.ButtonText, QColor(theme['text_primary']))
|
||||
palette.setColor(QPalette.BrightText, QColor(theme['error']))
|
||||
palette.setColor(QPalette.Link, QColor(theme['accent']))
|
||||
palette.setColor(QPalette.Highlight, QColor(theme['accent']))
|
||||
palette.setColor(QPalette.HighlightedText, QColor(theme['btn_primary_text']))
|
||||
|
||||
# Apply palette to application
|
||||
self.app.setPalette(palette)
|
||||
|
||||
# Stylesheet anwenden
|
||||
self.app.setStyleSheet(self.theme_stylesheets.get(self.LIGHT_THEME, ""))
|
||||
# Apply stylesheet
|
||||
stylesheet = self.theme_stylesheets.get(theme_name, "")
|
||||
self.app.setStyleSheet(stylesheet)
|
||||
|
||||
# Aktuelles Theme speichern
|
||||
self.current_theme = self.LIGHT_THEME
|
||||
self.settings.setValue("theme", self.LIGHT_THEME)
|
||||
# Save current theme
|
||||
self.current_theme = theme_name
|
||||
self.settings.setValue("theme", theme_name)
|
||||
|
||||
logger.info(f"Theme '{self.LIGHT_THEME}' erfolgreich angewendet")
|
||||
# Update logo if main window is available
|
||||
self._update_logo(theme_name)
|
||||
|
||||
# Emit signal for theme change
|
||||
self.theme_changed.emit(theme_name)
|
||||
|
||||
logger.info(f"Theme '{theme_name}' successfully applied")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Anwenden des Themes '{self.LIGHT_THEME}': {e}")
|
||||
logger.error(f"Error applying theme '{theme_name}': {e}")
|
||||
return False
|
||||
|
||||
def toggle_theme(self) -> str:
|
||||
"""
|
||||
Toggles between Light and Dark theme.
|
||||
|
||||
Returns:
|
||||
str: The name of the newly applied theme
|
||||
"""
|
||||
new_theme = self.DARK_THEME if self.current_theme == self.LIGHT_THEME else self.LIGHT_THEME
|
||||
self.apply_theme(new_theme)
|
||||
return new_theme
|
||||
|
||||
def is_dark_mode(self) -> bool:
|
||||
"""
|
||||
Check if dark mode is currently active.
|
||||
|
||||
Returns:
|
||||
bool: True if dark mode is active, False otherwise
|
||||
"""
|
||||
return self.current_theme == self.DARK_THEME
|
||||
|
||||
def get_current_theme(self) -> str:
|
||||
"""Gibt den Namen des aktuellen Themes zurück."""
|
||||
return self.LIGHT_THEME
|
||||
return self.current_theme
|
||||
|
||||
def get_icon_path(self, icon_name: str) -> str:
|
||||
"""
|
||||
Gibt den Pfad zum Icon zurück.
|
||||
Gibt den Pfad zum Icon zurück (theme-aware).
|
||||
|
||||
Args:
|
||||
icon_name: Name des Icons (ohne Dateierweiterung)
|
||||
@ -126,8 +187,38 @@ class ThemeManager:
|
||||
str: Pfad zum Icon
|
||||
"""
|
||||
# Social Media Icons bleiben unverändert (immer farbig)
|
||||
if icon_name in ["instagram", "facebook", "twitter", "tiktok", "vk"]:
|
||||
if icon_name in ["instagram", "facebook", "twitter", "tiktok", "vk", "gmail", "ok"]:
|
||||
return os.path.join(self.base_dir, "resources", "icons", f"{icon_name}.svg")
|
||||
|
||||
# Für andere Icons, die möglicherweise Theme-spezifisch sind
|
||||
# Logo is theme-specific
|
||||
if icon_name == "intelsight-logo":
|
||||
theme = ThemeConfig.get_theme(self.current_theme)
|
||||
logo_name = theme.get('logo_path', 'intelsight-logo.svg').replace('.svg', '')
|
||||
return os.path.join(self.base_dir, "resources", "icons", f"{logo_name}.svg")
|
||||
|
||||
# For other icons
|
||||
return os.path.join(self.base_dir, "resources", "icons", f"{icon_name}.svg")
|
||||
|
||||
def _update_logo(self, theme_name: str):
|
||||
"""
|
||||
Updates the logo based on the current theme.
|
||||
|
||||
Args:
|
||||
theme_name: Name of the theme ("light" or "dark")
|
||||
"""
|
||||
# Skip this method - logo will be updated directly in MainWindow._on_theme_toggled
|
||||
# This avoids circular import issues
|
||||
logger.debug(f"_update_logo called for theme: {theme_name} (delegating to MainWindow)")
|
||||
|
||||
def get_color(self, color_key: str) -> str:
|
||||
"""
|
||||
Get a color from the current theme.
|
||||
|
||||
Args:
|
||||
color_key: Key of the color in theme configuration
|
||||
|
||||
Returns:
|
||||
Color value as hex string
|
||||
"""
|
||||
theme = ThemeConfig.get_theme(self.current_theme)
|
||||
return theme.get(color_key, '')
|
||||
@ -14,6 +14,12 @@ class AboutDialog(QDialog):
|
||||
# Remove the standard "?" help button that appears on some platforms
|
||||
self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
|
||||
self.language_manager = language_manager
|
||||
self.theme_manager = None
|
||||
|
||||
# Try to get theme manager from parent (MainWindow)
|
||||
if parent and hasattr(parent, 'theme_manager'):
|
||||
self.theme_manager = parent.theme_manager
|
||||
|
||||
self._setup_ui()
|
||||
if self.language_manager:
|
||||
self.language_manager.language_changed.connect(self.update_texts)
|
||||
@ -21,49 +27,54 @@ class AboutDialog(QDialog):
|
||||
|
||||
def _setup_ui(self):
|
||||
self.setWindowTitle("About")
|
||||
# Dialog-Größe festlegen für bessere Zentrierung
|
||||
self.setMinimumWidth(500)
|
||||
self.setMinimumHeight(400)
|
||||
# Dialog-Größe festlegen
|
||||
self.setFixedSize(550, 450) # Fixed size for consistent appearance
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setContentsMargins(30, 30, 30, 30)
|
||||
layout.setSpacing(20)
|
||||
layout.setAlignment(Qt.AlignCenter) # Layout auch zentrieren
|
||||
layout.setContentsMargins(20, 20, 40, 40) # Less margin on top/left for logo
|
||||
layout.setSpacing(25)
|
||||
|
||||
# Add logo
|
||||
# Add logo in top-left corner
|
||||
logo_label = QLabel()
|
||||
logo_label.setAlignment(Qt.AlignCenter)
|
||||
logo_label.setAlignment(Qt.AlignLeft) # Align left instead of center
|
||||
|
||||
# Get the logo path
|
||||
# Get the theme-aware logo path
|
||||
if self.theme_manager:
|
||||
# Use theme manager to get correct logo based on current theme
|
||||
logo_path = self.theme_manager.get_icon_path("intelsight-logo")
|
||||
else:
|
||||
# Fallback to light logo if no theme manager
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
parent_dir = os.path.dirname(current_dir)
|
||||
logo_path = os.path.join(parent_dir, "resources", "icons", "intelsight-logo.svg")
|
||||
|
||||
if os.path.exists(logo_path):
|
||||
# Load logo and display it at a larger size
|
||||
# Load logo and display it at a smaller size for corner placement
|
||||
logo_pixmap = QPixmap(logo_path)
|
||||
# Scale the logo to a reasonable size while maintaining aspect ratio
|
||||
# Scale the logo smaller for top-left corner
|
||||
scaled_pixmap = logo_pixmap.scaled(
|
||||
300, 120, # Etwas kleiner für bessere Proportionen
|
||||
200, 60, # Smaller size for corner placement
|
||||
Qt.KeepAspectRatio,
|
||||
Qt.SmoothTransformation
|
||||
)
|
||||
logo_label.setPixmap(scaled_pixmap)
|
||||
# Feste Größe für das Label setzen, um Zentrierung zu gewährleisten
|
||||
logo_label.setFixedSize(scaled_pixmap.size())
|
||||
else:
|
||||
# Fallback if logo not found
|
||||
logo_label.setText("IntelSight")
|
||||
logo_label.setStyleSheet("font-size: 24px; font-weight: bold;")
|
||||
logo_label.setStyleSheet("font-size: 18px; font-weight: bold;")
|
||||
|
||||
# Logo mit Alignment hinzufügen
|
||||
layout.addWidget(logo_label, 0, Qt.AlignCenter)
|
||||
# Logo in top-left corner
|
||||
layout.addWidget(logo_label, 0, Qt.AlignLeft | Qt.AlignTop)
|
||||
|
||||
# Add some space after logo
|
||||
layout.addSpacing(20)
|
||||
|
||||
self.info_label = QLabel()
|
||||
self.info_label.setAlignment(Qt.AlignCenter)
|
||||
self.info_label.setWordWrap(True)
|
||||
self.info_label.setMaximumWidth(450) # Maximale Breite für bessere Lesbarkeit
|
||||
layout.addWidget(self.info_label, 0, Qt.AlignCenter)
|
||||
layout.addWidget(self.info_label, 1, Qt.AlignCenter) # Use stretch factor 1
|
||||
|
||||
# Spacer für bessere vertikale Verteilung
|
||||
layout.addStretch()
|
||||
|
||||
7
views/base/__init__.py
Normale Datei
7
views/base/__init__.py
Normale Datei
@ -0,0 +1,7 @@
|
||||
"""
|
||||
Base View Components - Theme-aware base classes for all UI elements
|
||||
"""
|
||||
|
||||
from views.base.theme_aware_widget import ThemeAwareWidget
|
||||
|
||||
__all__ = ['ThemeAwareWidget']
|
||||
99
views/base/theme_aware_widget.py
Normale Datei
99
views/base/theme_aware_widget.py
Normale Datei
@ -0,0 +1,99 @@
|
||||
"""
|
||||
Theme-Aware Base Widget - Base class for all custom widgets
|
||||
Provides automatic theme updates and color access
|
||||
"""
|
||||
|
||||
from PyQt5.QtWidgets import QWidget
|
||||
from PyQt5.QtCore import pyqtSlot
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ThemeAwareWidget(QWidget):
|
||||
"""
|
||||
Base class for all custom widgets that need theme support.
|
||||
Automatically connects to theme changes and provides helper methods.
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
"""
|
||||
Initialize theme-aware widget.
|
||||
|
||||
Args:
|
||||
parent: Parent widget
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self._theme_manager = None
|
||||
self._setup_theme_connection()
|
||||
|
||||
def _setup_theme_connection(self):
|
||||
"""Setup connection to theme manager for automatic updates."""
|
||||
try:
|
||||
# Try to find theme manager in main window
|
||||
main_window = self.window()
|
||||
if main_window and hasattr(main_window, 'theme_manager'):
|
||||
self._theme_manager = main_window.theme_manager
|
||||
# Connect to theme change signal
|
||||
self._theme_manager.theme_changed.connect(self._on_theme_changed)
|
||||
logger.debug(f"{self.__class__.__name__} connected to theme manager")
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not connect to theme manager: {e}")
|
||||
|
||||
@pyqtSlot(str)
|
||||
def _on_theme_changed(self, theme_name: str):
|
||||
"""
|
||||
Called when theme changes.
|
||||
Override in subclasses to handle theme-specific updates.
|
||||
|
||||
Args:
|
||||
theme_name: Name of the new theme ('light' or 'dark')
|
||||
"""
|
||||
# Subclasses should override this method
|
||||
pass
|
||||
|
||||
def get_theme_color(self, color_key: str) -> str:
|
||||
"""
|
||||
Get a color from the current theme.
|
||||
|
||||
Args:
|
||||
color_key: Key of the color in theme configuration
|
||||
|
||||
Returns:
|
||||
Color value as hex string, or empty string if not found
|
||||
"""
|
||||
if self._theme_manager:
|
||||
return self._theme_manager.get_color(color_key)
|
||||
return ''
|
||||
|
||||
def get_current_theme(self) -> str:
|
||||
"""
|
||||
Get the current theme name.
|
||||
|
||||
Returns:
|
||||
Current theme name ('light' or 'dark'), or 'light' as default
|
||||
"""
|
||||
if self._theme_manager:
|
||||
return self._theme_manager.get_current_theme()
|
||||
return 'light'
|
||||
|
||||
def is_dark_mode(self) -> bool:
|
||||
"""
|
||||
Check if dark mode is currently active.
|
||||
|
||||
Returns:
|
||||
True if dark mode is active, False otherwise
|
||||
"""
|
||||
if self._theme_manager:
|
||||
return self._theme_manager.is_dark_mode()
|
||||
return False
|
||||
|
||||
def showEvent(self, event):
|
||||
"""
|
||||
Override showEvent to ensure theme connection is established.
|
||||
Some widgets might not have access to main window during __init__.
|
||||
"""
|
||||
super().showEvent(event)
|
||||
# Try to setup theme connection again if not established
|
||||
if not self._theme_manager:
|
||||
self._setup_theme_connection()
|
||||
@ -33,12 +33,7 @@ class SidebarFilter(QWidget):
|
||||
def init_ui(self):
|
||||
"""Initialisiert die UI nach Styleguide"""
|
||||
self.setMaximumWidth(260) # Styleguide: Sidebar-Breite
|
||||
self.setStyleSheet("""
|
||||
QWidget {
|
||||
background-color: #FFFFFF;
|
||||
border-right: 1px solid #E2E8F0;
|
||||
}
|
||||
""")
|
||||
self.setObjectName("filter_sidebar") # For QSS targeting
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setContentsMargins(20, 20, 20, 20)
|
||||
@ -70,29 +65,7 @@ class SidebarFilter(QWidget):
|
||||
btn = QPushButton(f"{name} (0)")
|
||||
btn.setObjectName(key)
|
||||
btn.setCursor(Qt.PointingHandCursor)
|
||||
btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
text-align: left;
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
color: #4A5568;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #F7FAFC;
|
||||
color: #2D3748;
|
||||
}
|
||||
QPushButton[selected="true"] {
|
||||
background-color: #E6F2FF;
|
||||
color: #1E40AF;
|
||||
font-weight: 500;
|
||||
border-left: 3px solid #3182CE;
|
||||
padding-left: 13px;
|
||||
}
|
||||
""")
|
||||
btn.setObjectName("filter_button") # For QSS targeting
|
||||
btn.clicked.connect(lambda: self._on_filter_clicked(key))
|
||||
|
||||
# Erste Option als aktiv setzen
|
||||
@ -155,11 +128,7 @@ class AccountsOverviewView(QWidget):
|
||||
|
||||
def init_ui(self):
|
||||
"""Initialisiert die UI nach Styleguide"""
|
||||
self.setStyleSheet("""
|
||||
QWidget {
|
||||
background-color: #F8FAFC;
|
||||
}
|
||||
""")
|
||||
self.setObjectName("accounts_overview") # For QSS targeting
|
||||
|
||||
# Hauptlayout
|
||||
main_layout = QHBoxLayout(self)
|
||||
@ -173,7 +142,7 @@ class AccountsOverviewView(QWidget):
|
||||
|
||||
# Content Area
|
||||
content_widget = QWidget()
|
||||
content_widget.setStyleSheet("background-color: #F8FAFC;")
|
||||
content_widget.setObjectName("accounts_content") # For QSS targeting
|
||||
content_layout = QVBoxLayout(content_widget)
|
||||
content_layout.setContentsMargins(40, 30, 40, 30)
|
||||
content_layout.setSpacing(20)
|
||||
@ -185,10 +154,7 @@ class AccountsOverviewView(QWidget):
|
||||
title_font = QFont("Poppins", 24)
|
||||
title_font.setBold(True)
|
||||
self.title.setFont(title_font)
|
||||
self.title.setStyleSheet("""
|
||||
color: #1A365D;
|
||||
font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
""")
|
||||
self.title.setObjectName("section_title") # For QSS targeting
|
||||
header_layout.addWidget(self.title)
|
||||
|
||||
header_layout.addStretch()
|
||||
@ -198,34 +164,11 @@ class AccountsOverviewView(QWidget):
|
||||
# Scroll Area für Accounts
|
||||
self.scroll = QScrollArea()
|
||||
self.scroll.setWidgetResizable(True)
|
||||
self.scroll.setStyleSheet("""
|
||||
QScrollArea {
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
QScrollBar:vertical {
|
||||
background-color: #F1F5F9;
|
||||
width: 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
QScrollBar::handle:vertical {
|
||||
background-color: #CBD5E0;
|
||||
min-height: 20px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
QScrollBar::handle:vertical:hover {
|
||||
background-color: #A0AEC0;
|
||||
}
|
||||
QScrollBar::add-line:vertical,
|
||||
QScrollBar::sub-line:vertical {
|
||||
border: none;
|
||||
background: none;
|
||||
}
|
||||
""")
|
||||
# No inline styles - handled by QSS
|
||||
|
||||
# Grid Container
|
||||
self.container = QWidget()
|
||||
self.container.setStyleSheet("background-color: transparent;")
|
||||
self.container.setObjectName("grid_container") # For QSS targeting
|
||||
self.grid_layout = QGridLayout(self.container)
|
||||
self.grid_layout.setSpacing(24) # Styleguide Grid-Gap
|
||||
self.grid_layout.setContentsMargins(0, 0, 0, 0)
|
||||
@ -300,11 +243,7 @@ class AccountsOverviewView(QWidget):
|
||||
header_font = QFont("Poppins", 18)
|
||||
header_font.setWeight(QFont.DemiBold)
|
||||
header.setFont(header_font)
|
||||
header.setStyleSheet("""
|
||||
color: #1A365D;
|
||||
padding: 8px 0;
|
||||
font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
""")
|
||||
header.setObjectName("platform_header") # For QSS targeting
|
||||
self.grid_layout.addWidget(header, row, 0, 1, 3)
|
||||
row += 1
|
||||
|
||||
@ -364,26 +303,8 @@ class AccountsOverviewView(QWidget):
|
||||
cancel_button = msg_box.addButton("Abbrechen", QMessageBox.NoRole)
|
||||
msg_box.setDefaultButton(cancel_button) # Abbrechen als Standard
|
||||
|
||||
# Explizites Styling für den Löschen-Button
|
||||
delete_button.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #F44336;
|
||||
color: #FFFFFF;
|
||||
border: 1px solid #D32F2F;
|
||||
border-radius: 4px;
|
||||
padding: 6px 20px;
|
||||
min-width: 80px;
|
||||
min-height: 26px;
|
||||
font-weight: 500;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #D32F2F;
|
||||
border-color: #B71C1C;
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background-color: #B71C1C;
|
||||
}
|
||||
""")
|
||||
# Set object name for QSS targeting
|
||||
delete_button.setObjectName("delete_confirm_button")
|
||||
|
||||
msg_box.exec_()
|
||||
|
||||
|
||||
@ -45,19 +45,11 @@ class PlatformGridView(QWidget):
|
||||
title_font.setBold(True)
|
||||
self.title_label.setFont(title_font)
|
||||
|
||||
self.title_label.setStyleSheet("""
|
||||
QLabel {
|
||||
color: #1A365D;
|
||||
font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
""")
|
||||
|
||||
layout.addWidget(self.title_label)
|
||||
|
||||
# Container für Plattform-Grid
|
||||
platforms_container = QWidget()
|
||||
platforms_container.setStyleSheet("background: transparent;")
|
||||
platforms_container.setObjectName("platforms_container") # For QSS targeting
|
||||
grid_layout = QGridLayout(platforms_container)
|
||||
grid_layout.setSpacing(24) # Styleguide Grid-Gap
|
||||
|
||||
|
||||
@ -32,13 +32,8 @@ class TabNavigation(QWidget):
|
||||
# Feste Höhe nach Styleguide
|
||||
self.setFixedHeight(48)
|
||||
|
||||
# Basis-Styling
|
||||
self.setStyleSheet("""
|
||||
QWidget {
|
||||
background-color: #FFFFFF;
|
||||
border-bottom: 1px solid #E2E8F0;
|
||||
}
|
||||
""")
|
||||
# Set object name for QSS targeting - NO inline styles!
|
||||
self.setObjectName("tab_navigation")
|
||||
|
||||
# Layout
|
||||
layout = QHBoxLayout(self)
|
||||
@ -70,30 +65,7 @@ class TabNavigation(QWidget):
|
||||
font.setWeight(QFont.Medium)
|
||||
btn.setFont(font)
|
||||
|
||||
btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
padding: 12px 24px;
|
||||
font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: #4A5568;
|
||||
min-width: 100px;
|
||||
}
|
||||
QPushButton:checked {
|
||||
color: #1A365D;
|
||||
border-bottom-color: #3182CE;
|
||||
}
|
||||
QPushButton:hover:!checked {
|
||||
color: #2D3748;
|
||||
background-color: #F7FAFC;
|
||||
}
|
||||
QPushButton:pressed {
|
||||
color: #1A365D;
|
||||
}
|
||||
""")
|
||||
# No inline styles - handled by QSS generator
|
||||
|
||||
return btn
|
||||
|
||||
|
||||
@ -27,7 +27,6 @@ class MainWindow(QMainWindow):
|
||||
# Signale
|
||||
platform_selected = pyqtSignal(str)
|
||||
back_to_selector_requested = pyqtSignal()
|
||||
theme_toggled = pyqtSignal()
|
||||
|
||||
def __init__(self, theme_manager=None, language_manager=None, db_manager=None):
|
||||
super().__init__()
|
||||
@ -143,32 +142,53 @@ class MainWindow(QMainWindow):
|
||||
self.add_platform_tabs(platform_controller)
|
||||
|
||||
def _create_menus(self):
|
||||
"""Erstellt die Menüeinträge."""
|
||||
"""Erstellt die Menüeinträge und Dark Mode Toggle."""
|
||||
# Erstelle ein Logo-Button anstelle des Text-Menüs
|
||||
logo_widget = QPushButton()
|
||||
logo_widget.setIcon(QIcon(os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
|
||||
"resources", "icons", "intelsight-logo.svg")))
|
||||
logo_widget.setIconSize(QSize(120, 40))
|
||||
logo_widget.setFlat(True)
|
||||
logo_widget.setCursor(Qt.PointingHandCursor)
|
||||
logo_widget.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
padding: 5px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
border-radius: 4px;
|
||||
}
|
||||
""")
|
||||
logo_widget.clicked.connect(self._show_about_dialog)
|
||||
self.logo_widget = QPushButton() # Store as instance variable for easier access
|
||||
|
||||
# Add logo to menu bar
|
||||
self.menuBar().setCornerWidget(logo_widget, Qt.TopLeftCorner)
|
||||
# Get the correct logo based on current theme
|
||||
if self.theme_manager:
|
||||
logo_path = self.theme_manager.get_icon_path("intelsight-logo")
|
||||
else:
|
||||
# Fallback if no theme manager
|
||||
logo_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
|
||||
"resources", "icons", "intelsight-logo.svg")
|
||||
|
||||
self.logo_widget.setIcon(QIcon(logo_path))
|
||||
self.logo_widget.setIconSize(QSize(120, 40))
|
||||
self.logo_widget.setFlat(True)
|
||||
self.logo_widget.setCursor(Qt.PointingHandCursor)
|
||||
self.logo_widget.setObjectName("logo_button") # For QSS targeting
|
||||
self.logo_widget.clicked.connect(self._show_about_dialog)
|
||||
|
||||
# Add logo to menu bar (left side)
|
||||
self.menuBar().setCornerWidget(self.logo_widget, Qt.TopLeftCorner)
|
||||
|
||||
# Create container for dark mode toggle (right side)
|
||||
right_container = QWidget()
|
||||
right_container.setObjectName("menubar_right_container") # For QSS targeting
|
||||
right_layout = QHBoxLayout(right_container)
|
||||
right_layout.setContentsMargins(0, 5, 10, 5) # Add some right margin
|
||||
|
||||
# Import and create Dark Mode Toggle
|
||||
from views.widgets.dark_mode_toggle import DarkModeToggle
|
||||
self.dark_mode_toggle = DarkModeToggle(self)
|
||||
|
||||
# Set initial state based on current theme
|
||||
if self.theme_manager:
|
||||
initial_dark = self.theme_manager.is_dark_mode()
|
||||
self.dark_mode_toggle.setChecked(initial_dark)
|
||||
|
||||
# Connect toggle to theme manager
|
||||
self.dark_mode_toggle.toggled.connect(self._on_theme_toggled)
|
||||
|
||||
right_layout.addWidget(self.dark_mode_toggle)
|
||||
|
||||
# Add dark mode toggle to menu bar (right side)
|
||||
self.menuBar().setCornerWidget(right_container, Qt.TopRightCorner)
|
||||
|
||||
# Store reference for language updates
|
||||
self.about_action = logo_widget
|
||||
self.about_action = self.logo_widget
|
||||
|
||||
|
||||
def _show_about_dialog(self):
|
||||
@ -177,6 +197,17 @@ class MainWindow(QMainWindow):
|
||||
dialog.exec_()
|
||||
|
||||
|
||||
def _on_theme_toggled(self, is_dark):
|
||||
"""Handle theme toggle from the dark mode switch."""
|
||||
if self.theme_manager:
|
||||
if is_dark:
|
||||
self.theme_manager.apply_theme(self.theme_manager.DARK_THEME)
|
||||
else:
|
||||
self.theme_manager.apply_theme(self.theme_manager.LIGHT_THEME)
|
||||
|
||||
# Explicitly update logo after theme change
|
||||
self.update_logo()
|
||||
|
||||
def refresh_language_ui(self):
|
||||
"""
|
||||
Aktualisiert alle UI-Texte nach einem Sprachwechsel.
|
||||
@ -239,3 +270,42 @@ class MainWindow(QMainWindow):
|
||||
def add_log_widget(self, text_widget):
|
||||
"""Fügt einen GUI-Handler zum Logger hinzu."""
|
||||
add_gui_handler(logger, text_widget)
|
||||
|
||||
def update_logo(self, theme_name: str = None):
|
||||
"""
|
||||
Updates the logo based on the current theme.
|
||||
|
||||
Args:
|
||||
theme_name: Name of the theme ("light" or "dark") - not used, kept for compatibility
|
||||
"""
|
||||
try:
|
||||
# Use stored reference to logo widget
|
||||
if hasattr(self, 'logo_widget') and self.logo_widget and self.theme_manager:
|
||||
# Get the new logo path from theme manager based on current theme
|
||||
current_theme = self.theme_manager.get_current_theme()
|
||||
logo_path = self.theme_manager.get_icon_path("intelsight-logo")
|
||||
|
||||
print(f"DEBUG: Updating logo for theme '{current_theme}'")
|
||||
print(f"DEBUG: Logo path: {logo_path}")
|
||||
print(f"DEBUG: File exists: {os.path.exists(logo_path)}")
|
||||
|
||||
if os.path.exists(logo_path):
|
||||
icon = QIcon(logo_path)
|
||||
self.logo_widget.setIcon(icon)
|
||||
# Force update
|
||||
self.logo_widget.update()
|
||||
self.logo_widget.repaint()
|
||||
|
||||
logger.info(f"Logo updated to {logo_path} for theme {current_theme}")
|
||||
print(f"DEBUG: Logo icon set successfully")
|
||||
else:
|
||||
logger.warning(f"Logo file not found: {logo_path}")
|
||||
print(f"DEBUG: Logo file not found!")
|
||||
else:
|
||||
print(f"DEBUG: Cannot update logo - missing components:")
|
||||
print(f" - has logo_widget: {hasattr(self, 'logo_widget')}")
|
||||
print(f" - logo_widget exists: {hasattr(self, 'logo_widget') and self.logo_widget}")
|
||||
print(f" - theme_manager exists: {self.theme_manager is not None}")
|
||||
except Exception as e:
|
||||
logger.error(f"Could not update logo: {e}")
|
||||
print(f"DEBUG: Exception updating logo: {e}")
|
||||
@ -48,7 +48,7 @@ class PlatformSelector(QWidget):
|
||||
|
||||
# Content Container mit gestapelten Widgets
|
||||
self.content_stack = QStackedWidget()
|
||||
self.content_stack.setStyleSheet("background-color: #F8FAFC;")
|
||||
self.content_stack.setObjectName("content_stack") # For QSS targeting, no inline styles!
|
||||
|
||||
# Platform Grid View (Tab 0)
|
||||
self.platform_grid = PlatformGridView(self.language_manager)
|
||||
|
||||
@ -85,10 +85,7 @@ class AccountCard(QFrame):
|
||||
username_font = QFont("Poppins", 16)
|
||||
username_font.setWeight(QFont.DemiBold)
|
||||
username_label.setFont(username_font)
|
||||
username_label.setStyleSheet("""
|
||||
color: #1A365D;
|
||||
font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
""")
|
||||
username_label.setObjectName("account_username") # For QSS targeting
|
||||
info_layout.addWidget(username_label)
|
||||
info_layout.addStretch()
|
||||
|
||||
@ -97,25 +94,7 @@ class AccountCard(QFrame):
|
||||
# Login Button
|
||||
self.login_btn = QPushButton("Login")
|
||||
self.login_btn.setCursor(Qt.PointingHandCursor)
|
||||
self.login_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #3182CE;
|
||||
color: #FFFFFF;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
padding: 6px 16px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
min-width: 60px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #2563EB;
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background-color: #1D4ED8;
|
||||
}
|
||||
""")
|
||||
self.login_btn.setObjectName("account_login_btn") # For QSS targeting
|
||||
self.login_btn.clicked.connect(lambda: self._on_login_clicked())
|
||||
header_layout.addWidget(self.login_btn)
|
||||
|
||||
@ -136,32 +115,14 @@ class AccountCard(QFrame):
|
||||
email_layout.setSpacing(8)
|
||||
|
||||
email_label = QLabel(self.account_data.get("email", ""))
|
||||
email_label.setStyleSheet("""
|
||||
color: #4A5568;
|
||||
font-size: 13px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
""")
|
||||
email_label.setObjectName("account_detail_text") # For QSS targeting
|
||||
email_layout.addWidget(email_label)
|
||||
|
||||
# Email Copy Button
|
||||
email_copy_btn = QPushButton()
|
||||
email_copy_btn.setToolTip("E-Mail kopieren")
|
||||
email_copy_btn.setCursor(Qt.PointingHandCursor)
|
||||
email_copy_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 2px;
|
||||
min-width: 20px;
|
||||
max-width: 20px;
|
||||
min-height: 20px;
|
||||
max-height: 20px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #F7FAFC;
|
||||
border-radius: 4px;
|
||||
}
|
||||
""")
|
||||
email_copy_btn.setObjectName("account_icon_btn") # For QSS targeting
|
||||
self.copy_icon = IconFactory.get_icon("copy", size=16)
|
||||
self.check_icon = IconFactory.get_icon("check", size=16, color="#10B981")
|
||||
self.email_copy_btn = email_copy_btn
|
||||
@ -183,32 +144,14 @@ class AccountCard(QFrame):
|
||||
pass_layout.setSpacing(8)
|
||||
|
||||
self.password_label = QLabel("••••••••")
|
||||
self.password_label.setStyleSheet("""
|
||||
color: #4A5568;
|
||||
font-size: 13px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
""")
|
||||
self.password_label.setObjectName("account_detail_text") # For QSS targeting
|
||||
pass_layout.addWidget(self.password_label)
|
||||
|
||||
# Copy Button
|
||||
copy_btn = QPushButton()
|
||||
copy_btn.setToolTip("Passwort kopieren")
|
||||
copy_btn.setCursor(Qt.PointingHandCursor)
|
||||
copy_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 2px;
|
||||
min-width: 20px;
|
||||
max-width: 20px;
|
||||
min-height: 20px;
|
||||
max-height: 20px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #F7FAFC;
|
||||
border-radius: 4px;
|
||||
}
|
||||
""")
|
||||
copy_btn.setObjectName("account_icon_btn") # For QSS targeting
|
||||
self.password_copy_btn = copy_btn
|
||||
self.password_copy_btn.setIcon(self.copy_icon)
|
||||
self.password_copy_btn.setIconSize(QSize(16, 16))
|
||||
@ -219,21 +162,7 @@ class AccountCard(QFrame):
|
||||
self.visibility_btn = QPushButton()
|
||||
self.visibility_btn.setToolTip("Passwort anzeigen")
|
||||
self.visibility_btn.setCursor(Qt.PointingHandCursor)
|
||||
self.visibility_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 2px;
|
||||
min-width: 20px;
|
||||
max-width: 20px;
|
||||
min-height: 20px;
|
||||
max-height: 20px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #F7FAFC;
|
||||
border-radius: 4px;
|
||||
}
|
||||
""")
|
||||
self.visibility_btn.setObjectName("account_icon_btn") # For QSS targeting
|
||||
self.eye_icon = IconFactory.get_icon("eye", size=16)
|
||||
self.eye_slash_icon = IconFactory.get_icon("eye-slash", size=16)
|
||||
self.visibility_btn.setIcon(self.eye_icon)
|
||||
@ -249,11 +178,7 @@ class AccountCard(QFrame):
|
||||
details_grid.addWidget(date_icon, 2, 0)
|
||||
|
||||
date_label = QLabel(self.account_data.get("created_at", ""))
|
||||
date_label.setStyleSheet("""
|
||||
color: #A0AEC0;
|
||||
font-size: 12px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
""")
|
||||
date_label.setObjectName("account_date_text") # For QSS targeting
|
||||
details_grid.addWidget(date_label, 2, 1)
|
||||
|
||||
layout.addLayout(details_grid)
|
||||
@ -265,27 +190,7 @@ class AccountCard(QFrame):
|
||||
# Export Button
|
||||
self.export_btn = QPushButton("Profil\nexportieren")
|
||||
self.export_btn.setCursor(Qt.PointingHandCursor)
|
||||
self.export_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #10B981;
|
||||
color: #FFFFFF;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
padding: 4px 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
min-width: 120px;
|
||||
min-height: 36px;
|
||||
text-align: center;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #059669;
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background-color: #047857;
|
||||
}
|
||||
""")
|
||||
self.export_btn.setObjectName("account_export_btn") # For QSS targeting
|
||||
self.export_btn.clicked.connect(lambda: self.export_requested.emit(self.account_data))
|
||||
actions_layout.addWidget(self.export_btn)
|
||||
|
||||
@ -294,25 +199,7 @@ class AccountCard(QFrame):
|
||||
# Delete Button
|
||||
self.delete_btn = QPushButton("Löschen")
|
||||
self.delete_btn.setCursor(Qt.PointingHandCursor)
|
||||
self.delete_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #DC2626;
|
||||
color: #FFFFFF;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
padding: 8px 16px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
min-width: 90px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #B91C1C;
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background-color: #991B1B;
|
||||
}
|
||||
""")
|
||||
self.delete_btn.setObjectName("account_delete_btn") # For QSS targeting
|
||||
self.delete_btn.clicked.connect(lambda: self.delete_requested.emit(self.account_data))
|
||||
actions_layout.addWidget(self.delete_btn)
|
||||
|
||||
@ -379,40 +266,14 @@ class AccountCard(QFrame):
|
||||
self.password_copy_timer.stop()
|
||||
|
||||
def _apply_status_styling(self):
|
||||
"""Wendet Status-basiertes Styling mit Pastel-Hintergrund und farbiger Umrandung an"""
|
||||
"""Wendet Status-basiertes Styling an"""
|
||||
# Status aus Account-Daten lesen - Standard ist "active" (grün)
|
||||
status = self.account_data.get("status", "active")
|
||||
|
||||
# Status-Farben definieren (nur Grün/Rot)
|
||||
status_styles = {
|
||||
"active": {
|
||||
"background": "#F0FDF4", # Sehr helles Mintgrün
|
||||
"border": "#10B981", # Kräftiges Grün
|
||||
"hover_border": "#059669" # Dunkleres Grün beim Hover
|
||||
},
|
||||
"inactive": {
|
||||
"background": "#FEF2F2", # Sehr helles Rosa
|
||||
"border": "#EF4444", # Kräftiges Rot
|
||||
"hover_border": "#DC2626" # Dunkleres Rot beim Hover
|
||||
}
|
||||
}
|
||||
|
||||
# Aktueller Status oder Fallback auf active (grün)
|
||||
current_style = status_styles.get(status, status_styles["active"])
|
||||
|
||||
# CSS-Styling anwenden
|
||||
self.setStyleSheet(f"""
|
||||
QFrame#accountCard {{
|
||||
background-color: {current_style["background"]};
|
||||
border: 2px solid {current_style["border"]};
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
}}
|
||||
QFrame#accountCard:hover {{
|
||||
border: 2px solid {current_style["hover_border"]};
|
||||
background-color: {current_style["background"]};
|
||||
}}
|
||||
""")
|
||||
# Set a property that QSS can use for styling
|
||||
self.setProperty("status", status)
|
||||
# Force style refresh
|
||||
self.setStyle(self.style())
|
||||
|
||||
def update_status(self, new_status: str):
|
||||
"""Aktualisiert den Status der Account-Karte und das Styling"""
|
||||
|
||||
233
views/widgets/dark_mode_toggle.py
Normale Datei
233
views/widgets/dark_mode_toggle.py
Normale Datei
@ -0,0 +1,233 @@
|
||||
"""
|
||||
Dark Mode Toggle Widget for PyQt5
|
||||
Based on Corporate Design Guidelines with smooth animations
|
||||
"""
|
||||
|
||||
from PyQt5.QtWidgets import QWidget
|
||||
from PyQt5.QtCore import Qt, pyqtSignal, QPropertyAnimation, QEasingCurve, QRectF, pyqtProperty, QByteArray
|
||||
from PyQt5.QtGui import QPainter, QColor, QBrush, QPen
|
||||
from PyQt5.QtSvg import QSvgRenderer
|
||||
|
||||
|
||||
class DarkModeToggle(QWidget):
|
||||
"""
|
||||
Animated Toggle-Switch for Dark/Light Mode with embedded SVG Icons.
|
||||
|
||||
Features:
|
||||
- Smooth 200ms animation with InOutCubic easing
|
||||
- Embedded sun/moon icons (no external files needed)
|
||||
- Corporate design colors (#CBD5E0 for light, #00D4FF for dark)
|
||||
- Size: 60x30 pixels
|
||||
- PyQt5 compatible
|
||||
|
||||
Signals:
|
||||
toggled(bool): Emitted when toggle state changes (True = Dark Mode)
|
||||
"""
|
||||
|
||||
# Signal emitted when toggle state changes
|
||||
toggled = pyqtSignal(bool)
|
||||
|
||||
def __init__(self, parent=None, initial_dark_mode=False):
|
||||
"""
|
||||
Initialize the Dark Mode Toggle.
|
||||
|
||||
Args:
|
||||
parent: Parent widget
|
||||
initial_dark_mode: Initial state (False = Light Mode, True = Dark Mode)
|
||||
"""
|
||||
super().__init__(parent)
|
||||
|
||||
# Fixed size as per design spec
|
||||
self.setFixedSize(60, 30)
|
||||
self.setCursor(Qt.PointingHandCursor)
|
||||
|
||||
# State management
|
||||
self._checked = initial_dark_mode
|
||||
|
||||
# Animation property for smooth sliding
|
||||
self._handle_position = 27.0 if initial_dark_mode else 3.0
|
||||
|
||||
# Configure animation
|
||||
self._animation = QPropertyAnimation(self, b"handle_position")
|
||||
self._animation.setDuration(200) # 200ms as per spec
|
||||
self._animation.setEasingCurve(QEasingCurve.InOutCubic)
|
||||
|
||||
# Initialize SVG renderers
|
||||
self.sun_svg = QSvgRenderer()
|
||||
self.moon_svg = QSvgRenderer()
|
||||
|
||||
# Load embedded SVG content
|
||||
self._load_svg_icons()
|
||||
|
||||
# Tooltip for accessibility
|
||||
self._update_tooltip()
|
||||
|
||||
def _load_svg_icons(self):
|
||||
"""Load embedded SVG icons for sun and moon."""
|
||||
|
||||
# Sun icon for Light Mode (black strokes)
|
||||
sun_svg_content = b'''<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 3V4M12 20V21M4 12H3M6.31412 6.31412L5.5 5.5M17.6859 6.31412L18.5 5.5M6.31412 17.69L5.5 18.5001M17.6859 17.69L18.5 18.5001M21 12H20M16 12C16 14.2091 14.2091 16 12 16C9.79086 16 8 14.2091 8 12C8 9.79086 9.79086 8 12 8C14.2091 8 16 9.79086 16 12Z" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>'''
|
||||
|
||||
# Moon icon for Dark Mode (dark blue strokes matching primary color)
|
||||
moon_svg_content = b'''<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.32031 11.6835C3.32031 16.6541 7.34975 20.6835 12.3203 20.6835C16.1075 20.6835 19.3483 18.3443 20.6768 15.032C19.6402 15.4486 18.5059 15.6834 17.3203 15.6834C12.3497 15.6834 8.32031 11.654 8.32031 6.68342C8.32031 5.50338 8.55165 4.36259 8.96453 3.32996C5.65605 4.66028 3.32031 7.89912 3.32031 11.6835Z" stroke="#232D53" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>'''
|
||||
|
||||
# Load SVG data into renderers
|
||||
self.sun_svg.load(QByteArray(sun_svg_content))
|
||||
self.moon_svg.load(QByteArray(moon_svg_content))
|
||||
|
||||
# Property for animation
|
||||
def get_handle_position(self):
|
||||
"""Get current handle position for animation."""
|
||||
return self._handle_position
|
||||
|
||||
def set_handle_position(self, pos):
|
||||
"""Set handle position and trigger repaint."""
|
||||
self._handle_position = pos
|
||||
self.update() # Trigger paintEvent
|
||||
|
||||
# Define property for QPropertyAnimation
|
||||
handle_position = pyqtProperty(float, get_handle_position, set_handle_position)
|
||||
|
||||
def paintEvent(self, event):
|
||||
"""
|
||||
Custom paint event to draw the toggle switch.
|
||||
|
||||
Draws:
|
||||
1. Track (background pill shape)
|
||||
2. Handle (white circular button)
|
||||
3. Icon (sun or moon in the handle)
|
||||
"""
|
||||
painter = QPainter(self)
|
||||
painter.setRenderHint(QPainter.Antialiasing)
|
||||
|
||||
# Draw track (pill-shaped background)
|
||||
track_rect = QRectF(0, 0, self.width(), self.height())
|
||||
track_radius = self.height() / 2
|
||||
|
||||
# Track color based on state
|
||||
if self._checked:
|
||||
# Dark mode active - cyan color from corporate design
|
||||
track_color = QColor("#00D4FF")
|
||||
else:
|
||||
# Light mode active - gray color from design spec
|
||||
track_color = QColor("#CBD5E0")
|
||||
|
||||
painter.setBrush(QBrush(track_color))
|
||||
painter.setPen(QPen(Qt.NoPen))
|
||||
painter.drawRoundedRect(track_rect, track_radius, track_radius)
|
||||
|
||||
# Draw handle (white circle that slides)
|
||||
handle_diameter = self.height() - 6 # 24px (30 - 6)
|
||||
handle_rect = QRectF(
|
||||
self._handle_position, # Animated position
|
||||
3, # 3px from top
|
||||
handle_diameter,
|
||||
handle_diameter
|
||||
)
|
||||
|
||||
# White handle
|
||||
painter.setBrush(QBrush(QColor("#FFFFFF")))
|
||||
painter.setPen(QPen(Qt.NoPen))
|
||||
painter.drawEllipse(handle_rect)
|
||||
|
||||
# Draw icon inside handle
|
||||
icon_size = 16 # Icon size within the 24px handle
|
||||
icon_rect = QRectF(
|
||||
handle_rect.x() + (handle_diameter - icon_size) / 2,
|
||||
handle_rect.y() + (handle_diameter - icon_size) / 2,
|
||||
icon_size,
|
||||
icon_size
|
||||
)
|
||||
|
||||
# Render appropriate icon
|
||||
if self._checked:
|
||||
# Dark mode - show moon icon
|
||||
self.moon_svg.render(painter, icon_rect)
|
||||
else:
|
||||
# Light mode - show sun icon
|
||||
self.sun_svg.render(painter, icon_rect)
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
"""
|
||||
Handle mouse click to toggle state.
|
||||
|
||||
Only responds to left mouse button clicks.
|
||||
"""
|
||||
if event.button() == Qt.LeftButton:
|
||||
self.toggle()
|
||||
|
||||
def toggle(self):
|
||||
"""Toggle between Light and Dark mode."""
|
||||
self.setChecked(not self._checked)
|
||||
|
||||
def setChecked(self, checked):
|
||||
"""
|
||||
Set the toggle state programmatically.
|
||||
|
||||
Args:
|
||||
checked: True for Dark Mode, False for Light Mode
|
||||
"""
|
||||
# Only proceed if state actually changes
|
||||
if self._checked == checked:
|
||||
return
|
||||
|
||||
self._checked = checked
|
||||
|
||||
# Stop any running animation
|
||||
self._animation.stop()
|
||||
|
||||
# Set animation endpoints
|
||||
if checked:
|
||||
# Animate to dark mode position (right side)
|
||||
# Handle should be at: width - handle_diameter - 3px margin
|
||||
self._animation.setEndValue(self.width() - (self.height() - 6) - 3)
|
||||
else:
|
||||
# Animate to light mode position (left side)
|
||||
self._animation.setEndValue(3.0)
|
||||
|
||||
# Start animation
|
||||
self._animation.start()
|
||||
|
||||
# Update tooltip
|
||||
self._update_tooltip()
|
||||
|
||||
# Emit signal for theme change
|
||||
self.toggled.emit(checked)
|
||||
|
||||
def isChecked(self):
|
||||
"""
|
||||
Get current toggle state.
|
||||
|
||||
Returns:
|
||||
bool: True if Dark Mode is active, False for Light Mode
|
||||
"""
|
||||
return self._checked
|
||||
|
||||
def _update_tooltip(self):
|
||||
"""Update tooltip based on current state."""
|
||||
if self._checked:
|
||||
self.setToolTip("Zu Light Mode wechseln")
|
||||
else:
|
||||
self.setToolTip("Zu Dark Mode wechseln")
|
||||
|
||||
def isDarkMode(self):
|
||||
"""
|
||||
Convenience method to check if dark mode is active.
|
||||
|
||||
Returns:
|
||||
bool: True if Dark Mode is active
|
||||
"""
|
||||
return self._checked
|
||||
|
||||
def isLightMode(self):
|
||||
"""
|
||||
Convenience method to check if light mode is active.
|
||||
|
||||
Returns:
|
||||
bool: True if Light Mode is active
|
||||
"""
|
||||
return not self._checked
|
||||
@ -38,27 +38,8 @@ class PlatformButton(QWidget):
|
||||
self.icon_button.setIconSize(QSize(120, 120)) # Größeres Icon
|
||||
|
||||
self.icon_button.setMinimumSize(150, 150)
|
||||
# Platform button styling based on Styleguide
|
||||
self.icon_button.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #F5F7FF;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 16px;
|
||||
padding: 32px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #E8EBFF;
|
||||
border: 1px solid #0099CC;
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background-color: #DCE2FF;
|
||||
border: 1px solid #0099CC;
|
||||
}
|
||||
QPushButton:disabled {
|
||||
background-color: #F0F0F0;
|
||||
opacity: 0.5;
|
||||
}
|
||||
""")
|
||||
# Set object name for QSS targeting
|
||||
self.icon_button.setObjectName("platform_icon_button")
|
||||
|
||||
# Button-Signal verbinden
|
||||
self.icon_button.clicked.connect(self.clicked)
|
||||
@ -70,15 +51,8 @@ class PlatformButton(QWidget):
|
||||
name_font.setPointSize(12)
|
||||
name_font.setBold(True)
|
||||
self.name_label.setFont(name_font)
|
||||
# Name label styling based on Styleguide
|
||||
self.name_label.setStyleSheet("""
|
||||
QLabel {
|
||||
color: #232D53;
|
||||
font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
""")
|
||||
# Set object name for QSS targeting
|
||||
self.name_label.setObjectName("platform_name_label")
|
||||
|
||||
# Widgets zum Layout hinzufügen
|
||||
layout.addWidget(self.icon_button, 0, Qt.AlignCenter)
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren