DarkMode ist existent yeah

Dieser Commit ist enthalten in:
Claude Project Manager
2025-08-10 17:46:30 +02:00
Ursprung 61cd1216d0
Commit 2644c4e111
24 geänderte Dateien mit 2930 neuen und 426 gelöschten Zeilen

Datei anzeigen

@ -5,8 +5,14 @@
"Bash(nslookup:*)", "Bash(nslookup:*)",
"WebFetch(domain:multilogin.com)", "WebFetch(domain:multilogin.com)",
"WebFetch(domain:dicloak.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"
]
} }
} }

Datei anzeigen

@ -127,6 +127,9 @@ class MainController:
# Signals verbinden # Signals verbinden
self.connect_signals() self.connect_signals()
# Remove unused theme_toggled connection
# The theme toggle is now handled directly in MainWindow
# Platform Selector Signal-Verbindungen # Platform Selector Signal-Verbindungen
if hasattr(self.view.platform_selector, 'export_requested'): if hasattr(self.view.platform_selector, 'export_requested'):
self.view.platform_selector.export_requested.connect( self.view.platform_selector.export_requested.connect(
@ -195,9 +198,6 @@ class MainController:
# Zurück-Button verbinden # Zurück-Button verbinden
self.view.back_to_selector_requested.connect(self.show_platform_selector) 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): def on_platform_selected(self, platform: str):
"""Wird aufgerufen, wenn eine Plattform ausgewählt wird.""" """Wird aufgerufen, wenn eine Plattform ausgewählt wird."""
logger.info(f"Plattform ausgewählt: {platform}") logger.info(f"Plattform ausgewählt: {platform}")

Datei anzeigen

@ -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&amp;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

Datei anzeigen

@ -17,7 +17,7 @@
L 25 40 L 25 40
L 35 30 Z" L 35 30 Z"
fill="none" fill="none"
stroke="currentColor" stroke="#232D53"
stroke-width="3.5" stroke-width="3.5"
stroke-linejoin="miter"/> stroke-linejoin="miter"/>
@ -26,28 +26,28 @@
<!-- Almond/football shaped eye --> <!-- Almond/football shaped eye -->
<ellipse cx="0" cy="0" rx="24" ry="13" <ellipse cx="0" cy="0" rx="24" ry="13"
fill="none" fill="none"
stroke="currentColor" stroke="#232D53"
stroke-width="3.5"/> stroke-width="3.5"/>
<!-- Circular iris --> <!-- Circular iris -->
<circle cx="0" cy="0" r="10" <circle cx="0" cy="0" r="10"
fill="none" fill="none"
stroke="currentColor" stroke="#232D53"
stroke-width="3.5"/> stroke-width="3.5"/>
<!-- Pupil --> <!-- Pupil -->
<circle cx="0" cy="0" r="4" fill="currentColor"/> <circle cx="0" cy="0" r="4" fill="#232D53"/>
</g> </g>
</g> </g>
</defs> </defs>
<!-- Light version --> <!-- Light version with IntelSight corporate colors -->
<g transform="translate(20, 50)"> <g transform="translate(20, 50)">
<!-- Shield centered vertically with text --> <!-- Shield centered vertically with text -->
<g transform="translate(0, -72.5)" color="#232D53"> <g transform="translate(0, -72.5)">
<use href="#shield-eye-accurate"/> <use href="#shield-eye-accurate"/>
</g> </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> <text x="90" y="5" font-family="'Poppins', sans-serif" font-size="46" font-weight="600" fill="#232D53">IntelSight</text>
</g> </g>
</svg> </svg>

Vorher

Breite:  |  Höhe:  |  Größe: 1.7 KiB

Nachher

Breite:  |  Höhe:  |  Größe: 1.6 KiB

Datei anzeigen

@ -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) */
}

Datei anzeigen

@ -433,6 +433,38 @@ QMessageBox QPushButton[text="Löschen"]:pressed {
background-color: #B71C1C; 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 ==================== */ /* ==================== SPECIAL COLORS ==================== */
/* Success */ /* Success */
.success { .success {

65
test_logo_display.py Normale Datei
Datei anzeigen

@ -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
Datei anzeigen

@ -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
Datei anzeigen

@ -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
Datei anzeigen

@ -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
Datei anzeigen

@ -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
Datei anzeigen

@ -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',
}

Datei anzeigen

@ -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 import os
@ -8,17 +10,26 @@ import logging
from typing import Dict, Any, Optional from typing import Dict, Any, Optional
from PyQt5.QtWidgets import QApplication from PyQt5.QtWidgets import QApplication
from PyQt5.QtGui import QPalette, QColor 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") logger = logging.getLogger("theme_manager")
class ThemeManager: class ThemeManager(QObject):
""" """
Verwaltet das Erscheinungsbild der Anwendung. Verwaltet das Erscheinungsbild der Anwendung.
Supports Light and Dark themes with smooth transitions.
""" """
# Signal emitted when theme changes
theme_changed = pyqtSignal(str)
# Themennamen # Themennamen
LIGHT_THEME = "light" LIGHT_THEME = "light"
DARK_THEME = "dark"
def __init__(self, app: QApplication): def __init__(self, app: QApplication):
""" """
@ -27,9 +38,12 @@ class ThemeManager:
Args: Args:
app: Die QApplication-Instanz app: Die QApplication-Instanz
""" """
super().__init__()
self.app = app self.app = app
self.settings = QSettings("Chimaira", "SocialMediaAccountGenerator") 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 # Basisverzeichnis ermitteln
self.base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 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", "themes"), exist_ok=True)
os.makedirs(os.path.join(self.base_dir, "resources", "icons"), 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.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 # Apply saved theme
self.apply_theme(self.LIGHT_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: def regenerate_stylesheets(self):
"""Lädt ein QSS-Stylesheet aus einer Datei.""" """Regenerate stylesheets from theme configuration"""
try: self.theme_stylesheets = {
stylesheet_path = os.path.join(self.base_dir, "resources", "themes", filename) self.LIGHT_THEME: self.qss_generator.generate(self.LIGHT_THEME),
if os.path.exists(stylesheet_path): self.DARK_THEME: self.qss_generator.generate(self.DARK_THEME)
with open(stylesheet_path, 'r', encoding='utf-8') as f: }
return f.read()
else: def get_color(self, color_key: str) -> str:
logger.warning(f"Stylesheet-Datei nicht gefunden: {stylesheet_path}") """Get a color from the current theme"""
# Erzeuge eine leere Stylesheet-Datei, wenn sie nicht existiert return ThemeConfig.get_color(self.current_theme, color_key)
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 apply_theme(self, theme_name: str) -> bool: 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: Args:
theme_name: Wird ignoriert, immer Light Theme verwendet theme_name: Name des Themes ("light" oder "dark")
Returns: Returns:
bool: True, wenn das Theme erfolgreich angewendet wurde, sonst False bool: True, wenn das Theme erfolgreich angewendet wurde, sonst False
""" """
try: 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() palette = QPalette()
# Light Theme Palette # Get colors from theme configuration
palette.setColor(QPalette.Window, QColor(240, 240, 240)) theme = ThemeConfig.get_theme(theme_name)
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)
# 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) self.app.setPalette(palette)
# Stylesheet anwenden # Apply stylesheet
self.app.setStyleSheet(self.theme_stylesheets.get(self.LIGHT_THEME, "")) stylesheet = self.theme_stylesheets.get(theme_name, "")
self.app.setStyleSheet(stylesheet)
# Aktuelles Theme speichern # Save current theme
self.current_theme = self.LIGHT_THEME self.current_theme = theme_name
self.settings.setValue("theme", self.LIGHT_THEME) 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 return True
except Exception as e: 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 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: def get_current_theme(self) -> str:
"""Gibt den Namen des aktuellen Themes zurück.""" """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: 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: Args:
icon_name: Name des Icons (ohne Dateierweiterung) icon_name: Name des Icons (ohne Dateierweiterung)
@ -126,8 +187,38 @@ class ThemeManager:
str: Pfad zum Icon str: Pfad zum Icon
""" """
# Social Media Icons bleiben unverändert (immer farbig) # 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") 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
return os.path.join(self.base_dir, "resources", "icons", f"{icon_name}.svg") 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, '')

Datei anzeigen

@ -14,6 +14,12 @@ class AboutDialog(QDialog):
# Remove the standard "?" help button that appears on some platforms # Remove the standard "?" help button that appears on some platforms
self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
self.language_manager = language_manager 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() self._setup_ui()
if self.language_manager: if self.language_manager:
self.language_manager.language_changed.connect(self.update_texts) self.language_manager.language_changed.connect(self.update_texts)
@ -21,49 +27,54 @@ class AboutDialog(QDialog):
def _setup_ui(self): def _setup_ui(self):
self.setWindowTitle("About") self.setWindowTitle("About")
# Dialog-Größe festlegen für bessere Zentrierung # Dialog-Größe festlegen
self.setMinimumWidth(500) self.setFixedSize(550, 450) # Fixed size for consistent appearance
self.setMinimumHeight(400)
layout = QVBoxLayout(self) layout = QVBoxLayout(self)
layout.setContentsMargins(30, 30, 30, 30) layout.setContentsMargins(20, 20, 40, 40) # Less margin on top/left for logo
layout.setSpacing(20) layout.setSpacing(25)
layout.setAlignment(Qt.AlignCenter) # Layout auch zentrieren
# Add logo # Add logo in top-left corner
logo_label = QLabel() 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
current_dir = os.path.dirname(os.path.abspath(__file__)) if self.theme_manager:
parent_dir = os.path.dirname(current_dir) # Use theme manager to get correct logo based on current theme
logo_path = os.path.join(parent_dir, "resources", "icons", "intelsight-logo.svg") 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): 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) 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( scaled_pixmap = logo_pixmap.scaled(
300, 120, # Etwas kleiner für bessere Proportionen 200, 60, # Smaller size for corner placement
Qt.KeepAspectRatio, Qt.KeepAspectRatio,
Qt.SmoothTransformation Qt.SmoothTransformation
) )
logo_label.setPixmap(scaled_pixmap) logo_label.setPixmap(scaled_pixmap)
# Feste Größe für das Label setzen, um Zentrierung zu gewährleisten
logo_label.setFixedSize(scaled_pixmap.size()) logo_label.setFixedSize(scaled_pixmap.size())
else: else:
# Fallback if logo not found # Fallback if logo not found
logo_label.setText("IntelSight") 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 # Logo in top-left corner
layout.addWidget(logo_label, 0, Qt.AlignCenter) layout.addWidget(logo_label, 0, Qt.AlignLeft | Qt.AlignTop)
# Add some space after logo
layout.addSpacing(20)
self.info_label = QLabel() self.info_label = QLabel()
self.info_label.setAlignment(Qt.AlignCenter) self.info_label.setAlignment(Qt.AlignCenter)
self.info_label.setWordWrap(True) self.info_label.setWordWrap(True)
self.info_label.setMaximumWidth(450) # Maximale Breite für bessere Lesbarkeit 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 # Spacer für bessere vertikale Verteilung
layout.addStretch() layout.addStretch()

7
views/base/__init__.py Normale Datei
Datei anzeigen

@ -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']

Datei anzeigen

@ -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()

Datei anzeigen

@ -33,12 +33,7 @@ class SidebarFilter(QWidget):
def init_ui(self): def init_ui(self):
"""Initialisiert die UI nach Styleguide""" """Initialisiert die UI nach Styleguide"""
self.setMaximumWidth(260) # Styleguide: Sidebar-Breite self.setMaximumWidth(260) # Styleguide: Sidebar-Breite
self.setStyleSheet(""" self.setObjectName("filter_sidebar") # For QSS targeting
QWidget {
background-color: #FFFFFF;
border-right: 1px solid #E2E8F0;
}
""")
layout = QVBoxLayout(self) layout = QVBoxLayout(self)
layout.setContentsMargins(20, 20, 20, 20) layout.setContentsMargins(20, 20, 20, 20)
@ -70,29 +65,7 @@ class SidebarFilter(QWidget):
btn = QPushButton(f"{name} (0)") btn = QPushButton(f"{name} (0)")
btn.setObjectName(key) btn.setObjectName(key)
btn.setCursor(Qt.PointingHandCursor) btn.setCursor(Qt.PointingHandCursor)
btn.setStyleSheet(""" btn.setObjectName("filter_button") # For QSS targeting
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.clicked.connect(lambda: self._on_filter_clicked(key)) btn.clicked.connect(lambda: self._on_filter_clicked(key))
# Erste Option als aktiv setzen # Erste Option als aktiv setzen
@ -155,11 +128,7 @@ class AccountsOverviewView(QWidget):
def init_ui(self): def init_ui(self):
"""Initialisiert die UI nach Styleguide""" """Initialisiert die UI nach Styleguide"""
self.setStyleSheet(""" self.setObjectName("accounts_overview") # For QSS targeting
QWidget {
background-color: #F8FAFC;
}
""")
# Hauptlayout # Hauptlayout
main_layout = QHBoxLayout(self) main_layout = QHBoxLayout(self)
@ -173,7 +142,7 @@ class AccountsOverviewView(QWidget):
# Content Area # Content Area
content_widget = QWidget() content_widget = QWidget()
content_widget.setStyleSheet("background-color: #F8FAFC;") content_widget.setObjectName("accounts_content") # For QSS targeting
content_layout = QVBoxLayout(content_widget) content_layout = QVBoxLayout(content_widget)
content_layout.setContentsMargins(40, 30, 40, 30) content_layout.setContentsMargins(40, 30, 40, 30)
content_layout.setSpacing(20) content_layout.setSpacing(20)
@ -185,10 +154,7 @@ class AccountsOverviewView(QWidget):
title_font = QFont("Poppins", 24) title_font = QFont("Poppins", 24)
title_font.setBold(True) title_font.setBold(True)
self.title.setFont(title_font) self.title.setFont(title_font)
self.title.setStyleSheet(""" self.title.setObjectName("section_title") # For QSS targeting
color: #1A365D;
font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
""")
header_layout.addWidget(self.title) header_layout.addWidget(self.title)
header_layout.addStretch() header_layout.addStretch()
@ -198,34 +164,11 @@ class AccountsOverviewView(QWidget):
# Scroll Area für Accounts # Scroll Area für Accounts
self.scroll = QScrollArea() self.scroll = QScrollArea()
self.scroll.setWidgetResizable(True) self.scroll.setWidgetResizable(True)
self.scroll.setStyleSheet(""" # No inline styles - handled by QSS
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;
}
""")
# Grid Container # Grid Container
self.container = QWidget() 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 = QGridLayout(self.container)
self.grid_layout.setSpacing(24) # Styleguide Grid-Gap self.grid_layout.setSpacing(24) # Styleguide Grid-Gap
self.grid_layout.setContentsMargins(0, 0, 0, 0) self.grid_layout.setContentsMargins(0, 0, 0, 0)
@ -300,11 +243,7 @@ class AccountsOverviewView(QWidget):
header_font = QFont("Poppins", 18) header_font = QFont("Poppins", 18)
header_font.setWeight(QFont.DemiBold) header_font.setWeight(QFont.DemiBold)
header.setFont(header_font) header.setFont(header_font)
header.setStyleSheet(""" header.setObjectName("platform_header") # For QSS targeting
color: #1A365D;
padding: 8px 0;
font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
""")
self.grid_layout.addWidget(header, row, 0, 1, 3) self.grid_layout.addWidget(header, row, 0, 1, 3)
row += 1 row += 1
@ -364,26 +303,8 @@ class AccountsOverviewView(QWidget):
cancel_button = msg_box.addButton("Abbrechen", QMessageBox.NoRole) cancel_button = msg_box.addButton("Abbrechen", QMessageBox.NoRole)
msg_box.setDefaultButton(cancel_button) # Abbrechen als Standard msg_box.setDefaultButton(cancel_button) # Abbrechen als Standard
# Explizites Styling für den Löschen-Button # Set object name for QSS targeting
delete_button.setStyleSheet(""" delete_button.setObjectName("delete_confirm_button")
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;
}
""")
msg_box.exec_() msg_box.exec_()

Datei anzeigen

@ -45,19 +45,11 @@ class PlatformGridView(QWidget):
title_font.setBold(True) title_font.setBold(True)
self.title_label.setFont(title_font) 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) layout.addWidget(self.title_label)
# Container für Plattform-Grid # Container für Plattform-Grid
platforms_container = QWidget() platforms_container = QWidget()
platforms_container.setStyleSheet("background: transparent;") platforms_container.setObjectName("platforms_container") # For QSS targeting
grid_layout = QGridLayout(platforms_container) grid_layout = QGridLayout(platforms_container)
grid_layout.setSpacing(24) # Styleguide Grid-Gap grid_layout.setSpacing(24) # Styleguide Grid-Gap

Datei anzeigen

@ -32,13 +32,8 @@ class TabNavigation(QWidget):
# Feste Höhe nach Styleguide # Feste Höhe nach Styleguide
self.setFixedHeight(48) self.setFixedHeight(48)
# Basis-Styling # Set object name for QSS targeting - NO inline styles!
self.setStyleSheet(""" self.setObjectName("tab_navigation")
QWidget {
background-color: #FFFFFF;
border-bottom: 1px solid #E2E8F0;
}
""")
# Layout # Layout
layout = QHBoxLayout(self) layout = QHBoxLayout(self)
@ -70,30 +65,7 @@ class TabNavigation(QWidget):
font.setWeight(QFont.Medium) font.setWeight(QFont.Medium)
btn.setFont(font) btn.setFont(font)
btn.setStyleSheet(""" # No inline styles - handled by QSS generator
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;
}
""")
return btn return btn

Datei anzeigen

@ -27,7 +27,6 @@ class MainWindow(QMainWindow):
# Signale # Signale
platform_selected = pyqtSignal(str) platform_selected = pyqtSignal(str)
back_to_selector_requested = pyqtSignal() back_to_selector_requested = pyqtSignal()
theme_toggled = pyqtSignal()
def __init__(self, theme_manager=None, language_manager=None, db_manager=None): def __init__(self, theme_manager=None, language_manager=None, db_manager=None):
super().__init__() super().__init__()
@ -143,32 +142,53 @@ class MainWindow(QMainWindow):
self.add_platform_tabs(platform_controller) self.add_platform_tabs(platform_controller)
def _create_menus(self): 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 # Erstelle ein Logo-Button anstelle des Text-Menüs
logo_widget = QPushButton() self.logo_widget = QPushButton() # Store as instance variable for easier access
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)
# Add logo to menu bar # Get the correct logo based on current theme
self.menuBar().setCornerWidget(logo_widget, Qt.TopLeftCorner) 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 # Store reference for language updates
self.about_action = logo_widget self.about_action = self.logo_widget
def _show_about_dialog(self): def _show_about_dialog(self):
@ -177,6 +197,17 @@ class MainWindow(QMainWindow):
dialog.exec_() 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): def refresh_language_ui(self):
""" """
Aktualisiert alle UI-Texte nach einem Sprachwechsel. Aktualisiert alle UI-Texte nach einem Sprachwechsel.
@ -238,4 +269,43 @@ class MainWindow(QMainWindow):
def add_log_widget(self, text_widget): def add_log_widget(self, text_widget):
"""Fügt einen GUI-Handler zum Logger hinzu.""" """Fügt einen GUI-Handler zum Logger hinzu."""
add_gui_handler(logger, text_widget) 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}")

Datei anzeigen

@ -48,7 +48,7 @@ class PlatformSelector(QWidget):
# Content Container mit gestapelten Widgets # Content Container mit gestapelten Widgets
self.content_stack = QStackedWidget() 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) # Platform Grid View (Tab 0)
self.platform_grid = PlatformGridView(self.language_manager) self.platform_grid = PlatformGridView(self.language_manager)

Datei anzeigen

@ -85,10 +85,7 @@ class AccountCard(QFrame):
username_font = QFont("Poppins", 16) username_font = QFont("Poppins", 16)
username_font.setWeight(QFont.DemiBold) username_font.setWeight(QFont.DemiBold)
username_label.setFont(username_font) username_label.setFont(username_font)
username_label.setStyleSheet(""" username_label.setObjectName("account_username") # For QSS targeting
color: #1A365D;
font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
""")
info_layout.addWidget(username_label) info_layout.addWidget(username_label)
info_layout.addStretch() info_layout.addStretch()
@ -97,25 +94,7 @@ class AccountCard(QFrame):
# Login Button # Login Button
self.login_btn = QPushButton("Login") self.login_btn = QPushButton("Login")
self.login_btn.setCursor(Qt.PointingHandCursor) self.login_btn.setCursor(Qt.PointingHandCursor)
self.login_btn.setStyleSheet(""" self.login_btn.setObjectName("account_login_btn") # For QSS targeting
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.clicked.connect(lambda: self._on_login_clicked()) self.login_btn.clicked.connect(lambda: self._on_login_clicked())
header_layout.addWidget(self.login_btn) header_layout.addWidget(self.login_btn)
@ -136,32 +115,14 @@ class AccountCard(QFrame):
email_layout.setSpacing(8) email_layout.setSpacing(8)
email_label = QLabel(self.account_data.get("email", "")) email_label = QLabel(self.account_data.get("email", ""))
email_label.setStyleSheet(""" email_label.setObjectName("account_detail_text") # For QSS targeting
color: #4A5568;
font-size: 13px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
""")
email_layout.addWidget(email_label) email_layout.addWidget(email_label)
# Email Copy Button # Email Copy Button
email_copy_btn = QPushButton() email_copy_btn = QPushButton()
email_copy_btn.setToolTip("E-Mail kopieren") email_copy_btn.setToolTip("E-Mail kopieren")
email_copy_btn.setCursor(Qt.PointingHandCursor) email_copy_btn.setCursor(Qt.PointingHandCursor)
email_copy_btn.setStyleSheet(""" email_copy_btn.setObjectName("account_icon_btn") # For QSS targeting
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.copy_icon = IconFactory.get_icon("copy", size=16) self.copy_icon = IconFactory.get_icon("copy", size=16)
self.check_icon = IconFactory.get_icon("check", size=16, color="#10B981") self.check_icon = IconFactory.get_icon("check", size=16, color="#10B981")
self.email_copy_btn = email_copy_btn self.email_copy_btn = email_copy_btn
@ -183,32 +144,14 @@ class AccountCard(QFrame):
pass_layout.setSpacing(8) pass_layout.setSpacing(8)
self.password_label = QLabel("••••••••") self.password_label = QLabel("••••••••")
self.password_label.setStyleSheet(""" self.password_label.setObjectName("account_detail_text") # For QSS targeting
color: #4A5568;
font-size: 13px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
""")
pass_layout.addWidget(self.password_label) pass_layout.addWidget(self.password_label)
# Copy Button # Copy Button
copy_btn = QPushButton() copy_btn = QPushButton()
copy_btn.setToolTip("Passwort kopieren") copy_btn.setToolTip("Passwort kopieren")
copy_btn.setCursor(Qt.PointingHandCursor) copy_btn.setCursor(Qt.PointingHandCursor)
copy_btn.setStyleSheet(""" copy_btn.setObjectName("account_icon_btn") # For QSS targeting
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.password_copy_btn = copy_btn self.password_copy_btn = copy_btn
self.password_copy_btn.setIcon(self.copy_icon) self.password_copy_btn.setIcon(self.copy_icon)
self.password_copy_btn.setIconSize(QSize(16, 16)) self.password_copy_btn.setIconSize(QSize(16, 16))
@ -219,21 +162,7 @@ class AccountCard(QFrame):
self.visibility_btn = QPushButton() self.visibility_btn = QPushButton()
self.visibility_btn.setToolTip("Passwort anzeigen") self.visibility_btn.setToolTip("Passwort anzeigen")
self.visibility_btn.setCursor(Qt.PointingHandCursor) self.visibility_btn.setCursor(Qt.PointingHandCursor)
self.visibility_btn.setStyleSheet(""" self.visibility_btn.setObjectName("account_icon_btn") # For QSS targeting
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.eye_icon = IconFactory.get_icon("eye", size=16) self.eye_icon = IconFactory.get_icon("eye", size=16)
self.eye_slash_icon = IconFactory.get_icon("eye-slash", size=16) self.eye_slash_icon = IconFactory.get_icon("eye-slash", size=16)
self.visibility_btn.setIcon(self.eye_icon) self.visibility_btn.setIcon(self.eye_icon)
@ -249,11 +178,7 @@ class AccountCard(QFrame):
details_grid.addWidget(date_icon, 2, 0) details_grid.addWidget(date_icon, 2, 0)
date_label = QLabel(self.account_data.get("created_at", "")) date_label = QLabel(self.account_data.get("created_at", ""))
date_label.setStyleSheet(""" date_label.setObjectName("account_date_text") # For QSS targeting
color: #A0AEC0;
font-size: 12px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
""")
details_grid.addWidget(date_label, 2, 1) details_grid.addWidget(date_label, 2, 1)
layout.addLayout(details_grid) layout.addLayout(details_grid)
@ -265,27 +190,7 @@ class AccountCard(QFrame):
# Export Button # Export Button
self.export_btn = QPushButton("Profil\nexportieren") self.export_btn = QPushButton("Profil\nexportieren")
self.export_btn.setCursor(Qt.PointingHandCursor) self.export_btn.setCursor(Qt.PointingHandCursor)
self.export_btn.setStyleSheet(""" self.export_btn.setObjectName("account_export_btn") # For QSS targeting
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.clicked.connect(lambda: self.export_requested.emit(self.account_data)) self.export_btn.clicked.connect(lambda: self.export_requested.emit(self.account_data))
actions_layout.addWidget(self.export_btn) actions_layout.addWidget(self.export_btn)
@ -294,25 +199,7 @@ class AccountCard(QFrame):
# Delete Button # Delete Button
self.delete_btn = QPushButton("Löschen") self.delete_btn = QPushButton("Löschen")
self.delete_btn.setCursor(Qt.PointingHandCursor) self.delete_btn.setCursor(Qt.PointingHandCursor)
self.delete_btn.setStyleSheet(""" self.delete_btn.setObjectName("account_delete_btn") # For QSS targeting
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.clicked.connect(lambda: self.delete_requested.emit(self.account_data)) self.delete_btn.clicked.connect(lambda: self.delete_requested.emit(self.account_data))
actions_layout.addWidget(self.delete_btn) actions_layout.addWidget(self.delete_btn)
@ -379,40 +266,14 @@ class AccountCard(QFrame):
self.password_copy_timer.stop() self.password_copy_timer.stop()
def _apply_status_styling(self): 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 aus Account-Daten lesen - Standard ist "active" (grün)
status = self.account_data.get("status", "active") status = self.account_data.get("status", "active")
# Status-Farben definieren (nur Grün/Rot) # Set a property that QSS can use for styling
status_styles = { self.setProperty("status", status)
"active": { # Force style refresh
"background": "#F0FDF4", # Sehr helles Mintgrün self.setStyle(self.style())
"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"]};
}}
""")
def update_status(self, new_status: str): def update_status(self, new_status: str):
"""Aktualisiert den Status der Account-Karte und das Styling""" """Aktualisiert den Status der Account-Karte und das Styling"""

Datei anzeigen

@ -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

Datei anzeigen

@ -38,27 +38,8 @@ class PlatformButton(QWidget):
self.icon_button.setIconSize(QSize(120, 120)) # Größeres Icon self.icon_button.setIconSize(QSize(120, 120)) # Größeres Icon
self.icon_button.setMinimumSize(150, 150) self.icon_button.setMinimumSize(150, 150)
# Platform button styling based on Styleguide # Set object name for QSS targeting
self.icon_button.setStyleSheet(""" self.icon_button.setObjectName("platform_icon_button")
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;
}
""")
# Button-Signal verbinden # Button-Signal verbinden
self.icon_button.clicked.connect(self.clicked) self.icon_button.clicked.connect(self.clicked)
@ -70,15 +51,8 @@ class PlatformButton(QWidget):
name_font.setPointSize(12) name_font.setPointSize(12)
name_font.setBold(True) name_font.setBold(True)
self.name_label.setFont(name_font) self.name_label.setFont(name_font)
# Name label styling based on Styleguide # Set object name for QSS targeting
self.name_label.setStyleSheet(""" self.name_label.setObjectName("platform_name_label")
QLabel {
color: #232D53;
font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-weight: 600;
letter-spacing: 0.5px;
}
""")
# Widgets zum Layout hinzufügen # Widgets zum Layout hinzufügen
layout.addWidget(self.icon_button, 0, Qt.AlignCenter) layout.addWidget(self.icon_button, 0, Qt.AlignCenter)