Dieser Commit ist enthalten in:
Claude Project Manager
2025-08-01 23:50:28 +02:00
Commit 04585e95b6
290 geänderte Dateien mit 64086 neuen und 0 gelöschten Zeilen

111
views/about_dialog.py Normale Datei
Datei anzeigen

@ -0,0 +1,111 @@
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QLabel, QPushButton
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap, QIcon
import os
from updates.version import get_version
class AboutDialog(QDialog):
"""Dialog that shows information about the application."""
def __init__(self, language_manager=None, parent=None):
super().__init__(parent)
# Remove the standard "?" help button that appears on some platforms
self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
self.language_manager = language_manager
self._setup_ui()
if self.language_manager:
self.language_manager.language_changed.connect(self.update_texts)
self.update_texts()
def _setup_ui(self):
self.setWindowTitle("About")
# Dialog-Größe festlegen für bessere Zentrierung
self.setMinimumWidth(500)
self.setMinimumHeight(400)
layout = QVBoxLayout(self)
layout.setContentsMargins(30, 30, 30, 30)
layout.setSpacing(20)
layout.setAlignment(Qt.AlignCenter) # Layout auch zentrieren
# Add logo
logo_label = QLabel()
logo_label.setAlignment(Qt.AlignCenter)
# Get the logo path
current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(current_dir)
logo_path = os.path.join(parent_dir, "resources", "icons", "intelsight-logo.svg")
if os.path.exists(logo_path):
# Load logo and display it at a larger size
logo_pixmap = QPixmap(logo_path)
# Scale the logo to a reasonable size while maintaining aspect ratio
scaled_pixmap = logo_pixmap.scaled(
300, 120, # Etwas kleiner für bessere Proportionen
Qt.KeepAspectRatio,
Qt.SmoothTransformation
)
logo_label.setPixmap(scaled_pixmap)
# Feste Größe für das Label setzen, um Zentrierung zu gewährleisten
logo_label.setFixedSize(scaled_pixmap.size())
else:
# Fallback if logo not found
logo_label.setText("IntelSight")
logo_label.setStyleSheet("font-size: 24px; font-weight: bold;")
# Logo mit Alignment hinzufügen
layout.addWidget(logo_label, 0, Qt.AlignCenter)
self.info_label = QLabel()
self.info_label.setAlignment(Qt.AlignCenter)
self.info_label.setWordWrap(True)
self.info_label.setMaximumWidth(450) # Maximale Breite für bessere Lesbarkeit
layout.addWidget(self.info_label, 0, Qt.AlignCenter)
# Spacer für bessere vertikale Verteilung
layout.addStretch()
self.close_button = QPushButton("OK")
self.close_button.clicked.connect(self.accept)
self.close_button.setFixedWidth(100) # Feste Breite für Button
layout.addWidget(self.close_button, 0, Qt.AlignCenter)
def update_texts(self):
version_text = (
self.language_manager.get_text("main.version", f"Version {get_version()}")
if self.language_manager
else f"Version {get_version()}"
)
lm = self.language_manager
title = "AccountForger" if not lm else lm.get_text("main.title", "AccountForger")
support = (
lm.get_text(
"about_dialog.support",
"Für Support kontaktieren Sie uns unter: support@intelsight.de",
)
if lm
else "Für Support kontaktieren Sie uns unter: support@intelsight.de"
)
license_text = (
lm.get_text(
"about_dialog.license",
"Diese Software ist lizenzpflichtig und darf nur mit gültiger Lizenz verwendet werden.",
)
if lm
else "Diese Software ist lizenzpflichtig und darf nur mit gültiger Lizenz verwendet werden."
)
lines = [
f"<h2>{title}</h2>",
f"<p>{version_text}</p>",
"<p>© 2025 IntelSight UG (haftungsbeschränkt)</p>",
f"<p>{support}</p>",
f"<p>{license_text}</p>",
]
self.info_label.setText("".join(lines))
if lm:
self.setWindowTitle(lm.get_text("menu.about", "Über"))
self.close_button.setText(lm.get_text("buttons.ok", "OK"))

Datei anzeigen

@ -0,0 +1 @@
# Components for the main UI

Datei anzeigen

@ -0,0 +1,448 @@
"""
Accounts Overview View - Account-Übersicht im Mockup-Style
"""
import logging
from PyQt5.QtWidgets import (
QWidget, QHBoxLayout, QVBoxLayout, QLabel, QPushButton,
QScrollArea, QGridLayout, QFrame, QMessageBox
)
from PyQt5.QtCore import Qt, pyqtSignal
from PyQt5.QtGui import QFont
from views.widgets.account_card import AccountCard
logger = logging.getLogger("accounts_overview")
class SidebarFilter(QWidget):
"""Sidebar mit Plattform-Filter nach Styleguide"""
filter_changed = pyqtSignal(str)
def __init__(self, language_manager=None):
super().__init__()
self.language_manager = language_manager
self.filter_buttons = []
self.account_counts = {}
self.init_ui()
if self.language_manager:
self.language_manager.language_changed.connect(self.update_texts)
self.update_texts()
def init_ui(self):
"""Initialisiert die UI nach Styleguide"""
self.setMaximumWidth(260) # Styleguide: Sidebar-Breite
self.setStyleSheet("""
QWidget {
background-color: #FFFFFF;
border-right: 1px solid #E2E8F0;
}
""")
layout = QVBoxLayout(self)
layout.setContentsMargins(20, 20, 20, 20)
layout.setSpacing(8)
# Title removed - no longer needed
# Filter Buttons
self.filters = [
("Alle", "all"),
("Instagram", "instagram"),
("TikTok", "tiktok"),
("Facebook", "facebook"),
("X (Twitter)", "x"),
("VK", "vk"),
("OK.ru", "ok"),
("Gmail", "gmail")
]
for name, key in self.filters:
btn = self._create_filter_button(name, key)
self.filter_buttons.append((btn, key))
layout.addWidget(btn)
layout.addStretch()
def _create_filter_button(self, name, key):
"""Erstellt einen Filter-Button"""
btn = QPushButton(f"{name} (0)")
btn.setObjectName(key)
btn.setCursor(Qt.PointingHandCursor)
btn.setStyleSheet("""
QPushButton {
background-color: transparent;
border: none;
text-align: left;
padding: 12px 16px;
border-radius: 8px;
font-size: 14px;
color: #4A5568;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
QPushButton:hover {
background-color: #F7FAFC;
color: #2D3748;
}
QPushButton[selected="true"] {
background-color: #E6F2FF;
color: #1E40AF;
font-weight: 500;
border-left: 3px solid #3182CE;
padding-left: 13px;
}
""")
btn.clicked.connect(lambda: self._on_filter_clicked(key))
# Erste Option als aktiv setzen
if key == "all":
btn.setProperty("selected", "true")
btn.setStyle(btn.style())
return btn
def _on_filter_clicked(self, key):
"""Behandelt Filter-Klicks"""
# Update button states
for btn, btn_key in self.filter_buttons:
btn.setProperty("selected", "true" if btn_key == key else "false")
btn.setStyle(btn.style())
self.filter_changed.emit(key)
def update_counts(self, counts):
"""Aktualisiert die Account-Anzahlen"""
self.account_counts = counts
for btn, key in self.filter_buttons:
count = counts.get(key, 0)
name = next(name for name, k in self.filters if k == key)
btn.setText(f"{name} ({count})")
def update_texts(self):
"""Aktualisiert die Texte gemäß der aktuellen Sprache"""
if not self.language_manager:
return
# Title removed - no text update needed
class AccountsOverviewView(QWidget):
"""
Account-Übersicht im Mockup-Style mit:
- Sidebar-Filter
- Grid-Layout mit Account-Karten
"""
# Signals
account_login_requested = pyqtSignal(dict)
account_export_requested = pyqtSignal(dict)
account_delete_requested = pyqtSignal(dict)
export_requested = pyqtSignal() # Für Kompatibilität
def __init__(self, db_manager=None, language_manager=None):
super().__init__()
self.db_manager = db_manager
self.language_manager = language_manager
self.current_filter = "all"
self.accounts = []
self.init_ui()
if self.language_manager:
self.language_manager.language_changed.connect(self.update_texts)
self.update_texts()
def init_ui(self):
"""Initialisiert die UI nach Styleguide"""
self.setStyleSheet("""
QWidget {
background-color: #F8FAFC;
}
""")
# Hauptlayout
main_layout = QHBoxLayout(self)
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.setSpacing(0)
# Sidebar
self.sidebar = SidebarFilter(self.language_manager)
self.sidebar.filter_changed.connect(self._on_filter_changed)
main_layout.addWidget(self.sidebar)
# Content Area
content_widget = QWidget()
content_widget.setStyleSheet("background-color: #F8FAFC;")
content_layout = QVBoxLayout(content_widget)
content_layout.setContentsMargins(40, 30, 40, 30)
content_layout.setSpacing(20)
# Header
header_layout = QHBoxLayout()
self.title = QLabel("Alle Accounts")
title_font = QFont("Poppins", 24)
title_font.setBold(True)
self.title.setFont(title_font)
self.title.setStyleSheet("""
color: #1A365D;
font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
""")
header_layout.addWidget(self.title)
header_layout.addStretch()
content_layout.addLayout(header_layout)
# Scroll Area für Accounts
self.scroll = QScrollArea()
self.scroll.setWidgetResizable(True)
self.scroll.setStyleSheet("""
QScrollArea {
border: none;
background-color: transparent;
}
QScrollBar:vertical {
background-color: #F1F5F9;
width: 8px;
border-radius: 4px;
}
QScrollBar::handle:vertical {
background-color: #CBD5E0;
min-height: 20px;
border-radius: 4px;
}
QScrollBar::handle:vertical:hover {
background-color: #A0AEC0;
}
QScrollBar::add-line:vertical,
QScrollBar::sub-line:vertical {
border: none;
background: none;
}
""")
# Grid Container
self.container = QWidget()
self.container.setStyleSheet("background-color: transparent;")
self.grid_layout = QGridLayout(self.container)
self.grid_layout.setSpacing(24) # Styleguide Grid-Gap
self.grid_layout.setContentsMargins(0, 0, 0, 0)
self.scroll.setWidget(self.container)
content_layout.addWidget(self.scroll)
main_layout.addWidget(content_widget)
# Initial load
self.load_accounts()
def load_accounts(self):
"""Lädt Accounts aus der Datenbank"""
if not self.db_manager:
return
try:
# Hole alle Accounts aus der Datenbank
self.accounts = self.db_manager.get_all_accounts()
self._update_display()
self._update_sidebar_counts()
except Exception as e:
logger.error(f"Fehler beim Laden der Accounts: {e}")
self.accounts = []
QMessageBox.warning(
self,
"Fehler",
f"Fehler beim Laden der Accounts:\n{str(e)}"
)
def _update_display(self):
"""Aktualisiert die Anzeige basierend auf dem aktuellen Filter"""
# Clear existing widgets
while self.grid_layout.count():
child = self.grid_layout.takeAt(0)
if child.widget():
child.widget().deleteLater()
# Filter accounts
if self.current_filter == "all":
filtered_accounts = self.accounts
else:
filtered_accounts = [
acc for acc in self.accounts
if acc.get("platform", "").lower() == self.current_filter
]
# Gruppiere nach Plattform wenn "Alle" ausgewählt
if self.current_filter == "all":
self._display_grouped_accounts(filtered_accounts)
else:
self._display_accounts_grid(filtered_accounts)
def _display_grouped_accounts(self, accounts):
"""Zeigt Accounts gruppiert nach Plattform"""
# Gruppiere Accounts nach Plattform
platforms = {}
for acc in accounts:
platform = acc.get("platform", "unknown").lower()
if platform not in platforms:
platforms[platform] = []
platforms[platform].append(acc)
row = 0
for platform, platform_accounts in sorted(platforms.items()):
if not platform_accounts:
continue
# Platform Header
header = QLabel(platform.capitalize())
header_font = QFont("Poppins", 18)
header_font.setWeight(QFont.DemiBold)
header.setFont(header_font)
header.setStyleSheet("""
color: #1A365D;
padding: 8px 0;
font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
""")
self.grid_layout.addWidget(header, row, 0, 1, 3)
row += 1
# Accounts
col = 0
for acc in platform_accounts:
card = self._create_account_card(acc)
self.grid_layout.addWidget(card, row, col)
col += 1
if col > 2: # 3 columns
col = 0
row += 1
if col > 0:
row += 1
row += 1 # Extra space between platforms
# Add stretch
self.grid_layout.setRowStretch(row, 1)
def _display_accounts_grid(self, accounts):
"""Zeigt Accounts in einem einfachen Grid"""
row = 0
col = 0
for acc in accounts:
card = self._create_account_card(acc)
self.grid_layout.addWidget(card, row, col)
col += 1
if col > 2: # 3 columns
col = 0
row += 1
# Add stretch
self.grid_layout.setRowStretch(row + 1, 1)
def _create_account_card(self, account_data):
"""Erstellt eine Account-Karte"""
card = AccountCard(account_data, self.language_manager)
# Verbinde Signals
card.login_requested.connect(self.account_login_requested.emit)
card.export_requested.connect(self.account_export_requested.emit)
card.delete_requested.connect(self._on_delete_requested)
return card
def _on_delete_requested(self, account_data):
"""Behandelt Delete-Anfragen mit Bestätigung"""
msg_box = QMessageBox(self)
msg_box.setWindowTitle("Account löschen")
msg_box.setText(f"Möchten Sie den Account '{account_data.get('username', '')}' wirklich löschen?")
msg_box.setIcon(QMessageBox.Question)
# Custom Buttons hinzufügen
delete_button = msg_box.addButton("Löschen", QMessageBox.YesRole)
cancel_button = msg_box.addButton("Abbrechen", QMessageBox.NoRole)
msg_box.setDefaultButton(cancel_button) # Abbrechen als Standard
# Explizites Styling für den Löschen-Button
delete_button.setStyleSheet("""
QPushButton {
background-color: #F44336;
color: #FFFFFF;
border: 1px solid #D32F2F;
border-radius: 4px;
padding: 6px 20px;
min-width: 80px;
min-height: 26px;
font-weight: 500;
}
QPushButton:hover {
background-color: #D32F2F;
border-color: #B71C1C;
}
QPushButton:pressed {
background-color: #B71C1C;
}
""")
msg_box.exec_()
if msg_box.clickedButton() == delete_button:
self.account_delete_requested.emit(account_data)
# Reload accounts nach Löschung
self.load_accounts()
def _on_filter_changed(self, filter_key):
"""Behandelt Filter-Änderungen"""
self.current_filter = filter_key
# Update title
if filter_key == "all":
self.title.setText("Alle Accounts")
else:
platform_name = filter_key.capitalize()
if filter_key == "x":
platform_name = "X (Twitter)"
self.title.setText(f"{platform_name} Accounts")
self._update_display()
def _update_sidebar_counts(self):
"""Aktualisiert die Account-Anzahlen in der Sidebar"""
counts = {"all": len(self.accounts)}
# Zähle Accounts pro Plattform
for acc in self.accounts:
platform = acc.get("platform", "").lower()
if platform:
counts[platform] = counts.get(platform, 0) + 1
self.sidebar.update_counts(counts)
def update_texts(self):
"""Aktualisiert die Texte gemäß der aktuellen Sprache"""
if not self.language_manager:
return
# Update title based on current filter
if self.current_filter == "all":
self.title.setText(
self.language_manager.get_text("accounts_overview.all_accounts", "Alle Accounts")
)
else:
platform_name = self.current_filter.capitalize()
if self.current_filter == "x":
platform_name = "X (Twitter)"
self.title.setText(
self.language_manager.get_text(
f"accounts_overview.{self.current_filter}_accounts",
f"{platform_name} Accounts"
)
)
def update_session_status(self, account_id, status):
"""
Session-Status-Update deaktiviert (Session-Funktionalität entfernt).
"""
# Session-Funktionalität wurde entfernt - diese Methode macht nichts mehr
pass

Datei anzeigen

@ -0,0 +1,121 @@
"""
Platform Grid View - Zeigt die Plattform-Kacheln in einem Grid
"""
import os
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QGridLayout, QLabel
from PyQt5.QtCore import pyqtSignal, Qt
from PyQt5.QtGui import QFont
from views.widgets.platform_button import PlatformButton
class PlatformGridView(QWidget):
"""
Grid-Ansicht der Plattform-Kacheln
Wiederverwendung der existierenden PlatformButton-Komponente
"""
# Signal wird ausgelöst, wenn eine Plattform ausgewählt wird
platform_selected = pyqtSignal(str)
def __init__(self, language_manager=None):
super().__init__()
self.language_manager = language_manager
self.init_ui()
if self.language_manager:
self.language_manager.language_changed.connect(self.update_texts)
self.update_texts()
def init_ui(self):
"""Initialisiert die UI nach Styleguide"""
# Hauptlayout mit Container-Padding
layout = QVBoxLayout(self)
layout.setContentsMargins(40, 40, 40, 40)
layout.setSpacing(32)
# Titel
self.title_label = QLabel("AccountForger")
self.title_label.setAlignment(Qt.AlignCenter)
self.title_label.setObjectName("platform_title")
# Poppins Font für Titel
title_font = QFont("Poppins", 32)
title_font.setBold(True)
self.title_label.setFont(title_font)
self.title_label.setStyleSheet("""
QLabel {
color: #1A365D;
font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
margin-bottom: 20px;
}
""")
layout.addWidget(self.title_label)
# Container für Plattform-Grid
platforms_container = QWidget()
platforms_container.setStyleSheet("background: transparent;")
grid_layout = QGridLayout(platforms_container)
grid_layout.setSpacing(24) # Styleguide Grid-Gap
# Definiere verfügbare Plattformen
platforms = [
{"name": "Instagram", "enabled": True},
{"name": "Facebook", "enabled": True},
{"name": "TikTok", "enabled": True},
{"name": "X", "enabled": True},
{"name": "VK", "enabled": True},
{"name": "OK.ru", "enabled": True},
{"name": "Gmail", "enabled": True}
]
# Icon-Pfade
current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(os.path.dirname(current_dir))
icons_dir = os.path.join(parent_dir, "resources", "icons")
# Platziere Buttons in einem 2x4 Grid
for i, platform in enumerate(platforms):
row = i // 4
col = i % 4
# Icon-Pfad erstellen
platform_icon_name = platform['name'].lower()
if platform['name'] == "X":
platform_icon_name = "twitter"
elif platform['name'] == "OK.ru":
platform_icon_name = "ok"
icon_path = os.path.join(icons_dir, f"{platform_icon_name}.svg")
if not os.path.exists(icon_path):
icon_path = None
# Platform Button erstellen
button = PlatformButton(
platform["name"],
icon_path,
platform["enabled"]
)
# Signal verbinden
platform_signal_name = "x" if platform["name"] == "X" else platform["name"]
button.clicked.connect(
lambda checked=False, p=platform_signal_name: self.platform_selected.emit(p.lower())
)
grid_layout.addWidget(button, row, col, Qt.AlignCenter)
layout.addWidget(platforms_container)
layout.addStretch()
def update_texts(self):
"""Aktualisiert die Texte gemäß der aktuellen Sprache"""
if not self.language_manager:
return
self.title_label.setText(
self.language_manager.get_text("main.title", "AccountForger")
)

Datei anzeigen

@ -0,0 +1,130 @@
"""
Tab Navigation Component nach Styleguide
"""
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QPushButton, QLabel
from PyQt5.QtCore import pyqtSignal, Qt
from PyQt5.QtGui import QFont
class TabNavigation(QWidget):
"""
Tab-Navigation nach Styleguide mit zwei Modi:
- Plattformen (Standard)
- Accounts
"""
# Signal wird ausgelöst wenn Tab gewechselt wird (0=Plattformen, 1=Accounts)
tab_changed = pyqtSignal(int)
def __init__(self, language_manager=None):
super().__init__()
self.language_manager = language_manager
self.current_tab = 0
self.init_ui()
if self.language_manager:
self.language_manager.language_changed.connect(self.update_texts)
self.update_texts()
def init_ui(self):
"""Initialisiert die UI nach Styleguide"""
# Feste Höhe nach Styleguide
self.setFixedHeight(48)
# Basis-Styling
self.setStyleSheet("""
QWidget {
background-color: #FFFFFF;
border-bottom: 1px solid #E2E8F0;
}
""")
# Layout
layout = QHBoxLayout(self)
layout.setContentsMargins(40, 0, 40, 0)
layout.setSpacing(24)
# Tab Buttons erstellen
self.platform_tab = self._create_tab_button("Plattformen", True)
self.platform_tab.clicked.connect(lambda: self._on_tab_clicked(0))
layout.addWidget(self.platform_tab)
# Accounts Tab (ohne Badge)
self.accounts_tab = self._create_tab_button("Accounts", False)
self.accounts_tab.clicked.connect(lambda: self._on_tab_clicked(1))
layout.addWidget(self.accounts_tab)
# Spacer
layout.addStretch()
def _create_tab_button(self, text, active=False):
"""Erstellt einen Tab-Button nach Styleguide"""
btn = QPushButton(text)
btn.setCheckable(True)
btn.setChecked(active)
btn.setCursor(Qt.PointingHandCursor)
# Poppins Font
font = QFont("Poppins", 15)
font.setWeight(QFont.Medium)
btn.setFont(font)
btn.setStyleSheet("""
QPushButton {
background: transparent;
border: none;
border-bottom: 2px solid transparent;
padding: 12px 24px;
font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-size: 15px;
font-weight: 500;
color: #4A5568;
min-width: 100px;
}
QPushButton:checked {
color: #1A365D;
border-bottom-color: #3182CE;
}
QPushButton:hover:!checked {
color: #2D3748;
background-color: #F7FAFC;
}
QPushButton:pressed {
color: #1A365D;
}
""")
return btn
def _on_tab_clicked(self, index):
"""Behandelt Tab-Klicks"""
if self.current_tab != index:
self.current_tab = index
# Update button states
self.platform_tab.setChecked(index == 0)
self.accounts_tab.setChecked(index == 1)
# Emit signal
self.tab_changed.emit(index)
def set_active_tab(self, index):
"""Setzt den aktiven Tab programmatisch"""
self._on_tab_clicked(index)
def update_account_count(self, count):
"""Deprecated: Account-Anzahl wird nicht mehr im Tab angezeigt"""
pass
def update_texts(self):
"""Aktualisiert die Texte gemäß der aktuellen Sprache"""
if not self.language_manager:
return
self.platform_tab.setText(
self.language_manager.get_text("platform_selector.platforms_tab", "Plattformen")
)
self.accounts_tab.setText(
self.language_manager.get_text("platform_selector.accounts_tab", "Accounts")
)

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

@ -0,0 +1,7 @@
"""
Dialog-Module für die AccountForger Anwendung.
"""
from .license_activation_dialog import LicenseActivationDialog
__all__ = ['LicenseActivationDialog']

Datei anzeigen

@ -0,0 +1,69 @@
"""
Zentralisierte UI-Dialoge für Account-Erstellung
"""
from PyQt5.QtWidgets import QMessageBox
from typing import Dict, Any, Optional
class AccountInfo:
"""Datenklasse für Account-Informationen"""
def __init__(self, username: str, password: str, email: Optional[str] = None, phone: Optional[str] = None):
self.username = username
self.password = password
self.email = email
self.phone = phone
class AccountCreationResultDialog:
"""Kapselt alle UI-Dialoge für Account-Erstellung"""
@staticmethod
def show_success(parent, account_info: AccountInfo):
"""Zeigt Erfolgs-Dialog"""
message = f"Account erfolgreich erstellt!\n\n"
message += f"Benutzername: {account_info.username}\n"
message += f"Passwort: {account_info.password}\n"
if account_info.email:
message += f"E-Mail: {account_info.email}"
elif account_info.phone:
message += f"Telefon: {account_info.phone}"
QMessageBox.information(
parent,
"Erfolg",
message
)
@staticmethod
def show_error(parent, error_message: str):
"""Zeigt Fehler-Dialog"""
QMessageBox.critical(parent, "Fehler", error_message)
@staticmethod
def show_warning(parent, warning_message: str):
"""Zeigt Warnung-Dialog"""
QMessageBox.warning(parent, "Warnung", warning_message)
@staticmethod
def show_account_details(parent, platform: str, account_data: Dict[str, Any]):
"""Zeigt detaillierte Account-Informationen"""
username = account_data.get('username', '')
password = account_data.get('password', '')
email = account_data.get('email')
phone = account_data.get('phone')
account_info = AccountInfo(username, password, email, phone)
AccountCreationResultDialog.show_success(parent, account_info)
@staticmethod
def confirm_cancel(parent) -> bool:
"""Zeigt Bestätigungs-Dialog für Abbruch"""
reply = QMessageBox.question(
parent,
"Abbrechen bestätigen",
"Möchten Sie die Account-Erstellung wirklich abbrechen?",
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No
)
return reply == QMessageBox.Yes

Datei anzeigen

@ -0,0 +1,262 @@
"""
License Activation Dialog für die Lizenz-Eingabe beim Start.
"""
from PyQt5.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit,
QPushButton, QTextEdit, QGroupBox, QSizePolicy
)
from PyQt5.QtCore import Qt, pyqtSignal, QThread
from PyQt5.QtGui import QFont, QPixmap
import logging
import os
logger = logging.getLogger(__name__)
class ActivationWorker(QThread):
"""Worker Thread für die Lizenz-Aktivierung."""
finished = pyqtSignal(dict)
progress = pyqtSignal(str)
def __init__(self, license_manager, license_key):
super().__init__()
self.license_manager = license_manager
self.license_key = license_key
def run(self):
"""Führt die Aktivierung im Hintergrund aus."""
try:
self.progress.emit("Verbinde mit License Server...")
result = self.license_manager.activate_license(self.license_key)
self.finished.emit(result)
except Exception as e:
logger.error(f"Fehler bei der Aktivierung: {e}")
self.finished.emit({
"success": False,
"error": f"Unerwarteter Fehler: {str(e)}"
})
class LicenseActivationDialog(QDialog):
"""Dialog für die Lizenz-Aktivierung beim Start."""
activation_successful = pyqtSignal()
def __init__(self, license_manager, parent=None):
super().__init__(parent)
self.license_manager = license_manager
self.worker = None
self.init_ui()
def init_ui(self):
"""Initialisiert die UI."""
self.setWindowTitle("Lizenz-Aktivierung")
self.setModal(True)
self.setMinimumWidth(500)
self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
# Main Layout
layout = QVBoxLayout()
layout.setSpacing(20)
# Header mit Logo/Icon
header_layout = QHBoxLayout()
# App Icon
icon_label = QLabel()
icon_path = os.path.join("resources", "icons", "app_icon.png")
if os.path.exists(icon_path):
pixmap = QPixmap(icon_path).scaled(64, 64, Qt.KeepAspectRatio, Qt.SmoothTransformation)
icon_label.setPixmap(pixmap)
else:
icon_label.setText("🔐")
icon_label.setStyleSheet("font-size: 48px;")
# Title
title_layout = QVBoxLayout()
title_label = QLabel("Account Forger")
title_label.setStyleSheet("font-size: 24px; font-weight: bold;")
subtitle_label = QLabel("Bitte geben Sie Ihren Lizenzschlüssel ein")
subtitle_label.setStyleSheet("color: #666;")
title_layout.addWidget(title_label)
title_layout.addWidget(subtitle_label)
header_layout.addWidget(icon_label)
header_layout.addSpacing(20)
header_layout.addLayout(title_layout)
header_layout.addStretch()
layout.addLayout(header_layout)
# License Key Input
key_group = QGroupBox("Lizenzschlüssel")
key_layout = QVBoxLayout()
self.key_input = QLineEdit()
self.key_input.setPlaceholderText("XXXX-XXXX-XXXX-XXXX")
self.key_input.setStyleSheet("""
QLineEdit {
padding: 10px;
font-size: 16px;
font-family: monospace;
letter-spacing: 2px;
}
""")
# Format helper
format_label = QLabel("Format: AF-F-202506-XXXX-XXXX-XXXX")
format_label.setStyleSheet("color: #999; font-size: 12px;")
key_layout.addWidget(self.key_input)
key_layout.addWidget(format_label)
key_group.setLayout(key_layout)
layout.addWidget(key_group)
# Status/Error Display
self.status_text = QTextEdit()
self.status_text.setReadOnly(True)
self.status_text.setMaximumHeight(100)
self.status_text.hide()
self.status_text.setStyleSheet("""
QTextEdit {
border: 1px solid #ddd;
border-radius: 4px;
padding: 8px;
background-color: #f8f8f8;
}
""")
layout.addWidget(self.status_text)
# Buttons
button_layout = QHBoxLayout()
self.activate_button = QPushButton("Aktivieren")
self.activate_button.setDefault(True)
self.activate_button.setStyleSheet("""
QPushButton {
padding: 10px 30px;
font-size: 14px;
font-weight: bold;
background-color: #2196F3;
color: white;
border: none;
border-radius: 4px;
}
QPushButton:hover {
background-color: #1976D2;
}
QPushButton:disabled {
background-color: #ccc;
}
""")
self.activate_button.clicked.connect(self.activate_license)
self.exit_button = QPushButton("Beenden")
self.exit_button.setStyleSheet("""
QPushButton {
padding: 10px 30px;
font-size: 14px;
}
""")
self.exit_button.clicked.connect(self.reject)
button_layout.addStretch()
button_layout.addWidget(self.exit_button)
button_layout.addWidget(self.activate_button)
layout.addLayout(button_layout)
# Info Text
info_label = QLabel(
"Sie benötigen einen gültigen Lizenzschlüssel, um diese Software zu nutzen.\n"
"Kontaktieren Sie den Support, falls Sie noch keinen Lizenzschlüssel haben."
)
info_label.setWordWrap(True)
info_label.setStyleSheet("color: #666; font-size: 12px; padding: 10px;")
info_label.setAlignment(Qt.AlignCenter)
layout.addWidget(info_label)
self.setLayout(layout)
# Focus auf Input
self.key_input.setFocus()
# Enter zum Aktivieren
self.key_input.returnPressed.connect(self.activate_license)
def activate_license(self):
"""Startet die Lizenz-Aktivierung."""
license_key = self.key_input.text().strip()
if not license_key:
self.show_error("Bitte geben Sie einen Lizenzschlüssel ein.")
return
# UI für Aktivierung vorbereiten
self.activate_button.setEnabled(False)
self.key_input.setEnabled(False)
self.status_text.clear()
self.status_text.show()
self.status_text.append("🔄 Aktivierung läuft...")
# Worker Thread starten
self.worker = ActivationWorker(self.license_manager, license_key)
self.worker.progress.connect(self.update_status)
self.worker.finished.connect(self.activation_finished)
self.worker.start()
def update_status(self, message):
"""Aktualisiert den Status-Text."""
self.status_text.append(f"📡 {message}")
def activation_finished(self, result):
"""Verarbeitet das Ergebnis der Aktivierung."""
self.activate_button.setEnabled(True)
self.key_input.setEnabled(True)
if result.get("success"):
self.status_text.append("✅ Lizenz erfolgreich aktiviert!")
# Update Info anzeigen
if result.get("update_available"):
self.status_text.append(
f" Update verfügbar: Version {result.get('latest_version')}"
)
# Signal senden und Dialog schließen
self.activation_successful.emit()
self.accept()
else:
error = result.get("error", "Unbekannter Fehler")
self.show_error(error)
self.key_input.setFocus()
self.key_input.selectAll()
def show_error(self, error):
"""Zeigt eine Fehlermeldung an."""
self.status_text.show()
self.status_text.clear()
self.status_text.append(f"❌ Fehler: {error}")
self.status_text.setStyleSheet("""
QTextEdit {
border: 1px solid #f44336;
border-radius: 4px;
padding: 8px;
background-color: #ffebee;
color: #c62828;
}
""")
def closeEvent(self, event):
"""Verhindert das Schließen während der Aktivierung."""
if self.worker and self.worker.isRunning():
event.ignore()
else:
event.accept()

241
views/main_window.py Normale Datei
Datei anzeigen

@ -0,0 +1,241 @@
# Path: views/main_window.py
"""
Hauptfenster der AccountForger Anwendung.
"""
import os
import logging
from PyQt5.QtWidgets import (
QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QLabel, QPushButton, QStackedWidget, QTabWidget,
QAction, QMessageBox
)
from PyQt5.QtCore import Qt, pyqtSignal, QSize, QFile
from PyQt5.QtGui import QIcon, QFont, QPixmap
from localization.language_manager import LanguageManager
from views.platform_selector import PlatformSelector
from views.about_dialog import AboutDialog
from utils.logger import add_gui_handler
logger = logging.getLogger("main")
class MainWindow(QMainWindow):
"""Hauptfenster der Anwendung."""
# Signale
platform_selected = pyqtSignal(str)
back_to_selector_requested = pyqtSignal()
theme_toggled = pyqtSignal()
def __init__(self, theme_manager=None, language_manager=None, db_manager=None):
super().__init__()
# Theme Manager
self.theme_manager = theme_manager
# Language Manager
self.language_manager = language_manager
self.db_manager = db_manager
# Fenstereigenschaften setzen
self.setWindowTitle("AccountForger")
# Größere Mindest- und Startgröße, damit Plattformnamen
# (z.B. "Twitter" und "VK") nicht abgeschnitten werden und
# Tabelleninhalte genügend Platz haben
self.setMinimumSize(1450, 700)
self.resize(1450, 800)
# Hauptwidget und Layout
self.central_widget = QWidget()
self.setCentralWidget(self.central_widget)
# Haupt-Layout
self.main_layout = QVBoxLayout(self.central_widget)
# Gestapeltes Widget für Anzeige von Plattformwahl und Hauptfunktionen
self.stacked_widget = QStackedWidget()
self.main_layout.addWidget(self.stacked_widget)
# Plattform-Auswahl-Widget
self.platform_selector = PlatformSelector(self.language_manager, self.db_manager)
self.stacked_widget.addWidget(self.platform_selector)
# Container für Plattform-spezifische Tabs
self.platform_container = QWidget()
self.platform_layout = QVBoxLayout(self.platform_container)
# Header-Bereich mit Titel und Zurück-Button
self.header_widget = QWidget()
self.header_layout = QHBoxLayout(self.header_widget)
self.header_layout.setContentsMargins(10, 10, 10, 10)
# Zurück-Button
self.back_button = QPushButton("↩ Zurück")
self.back_button.setMinimumWidth(120) # Breiter für den Text
self.header_layout.addWidget(self.back_button)
# Plattform-Titel
self.platform_title = QLabel()
self.platform_title.setObjectName("platform_title") # For CSS styling
title_font = QFont()
title_font.setPointSize(24)
title_font.setBold(True)
self.platform_title.setFont(title_font)
self.platform_title.setAlignment(Qt.AlignCenter)
self.header_layout.addWidget(self.platform_title)
# Platzhalter für die rechte Seite, um die Zentrierung zu erhalten
spacer = QLabel()
spacer.setMinimumWidth(120) # Gleiche Breite wie der Button
self.header_layout.addWidget(spacer)
self.platform_layout.addWidget(self.header_widget)
# Tabs für die Plattform
self.tabs = QTabWidget()
self.platform_layout.addWidget(self.tabs)
# Stacked Widget hinzufügen
self.stacked_widget.addWidget(self.platform_container)
# Anfänglich Platform-Selektor anzeigen
self.stacked_widget.setCurrentWidget(self.platform_selector)
# Statusleiste
self.statusBar().showMessage("Bereit")
# "Über"-Menü erstellen
if self.language_manager:
self._create_menus()
# Verbinde das Sprachänderungssignal mit der UI-Aktualisierung
self.language_manager.language_changed.connect(self.refresh_language_ui)
# Verbinde Signale
self.connect_signals()
def connect_signals(self):
"""Verbindet die internen Signale."""
# Platform-Selector-Signal verbinden
self.platform_selector.platform_selected.connect(self.platform_selected)
# Zurück-Button-Signal verbinden
self.back_button.clicked.connect(self.back_to_selector_requested)
def init_platform_ui(self, platform: str, platform_controller):
"""Initialisiert die plattformspezifische UI."""
# Tabs entfernen (falls vorhanden)
while self.tabs.count() > 0:
self.tabs.removeTab(0)
# Plattform-Titel setzen - nur Platform-Name
self.platform_title.setText(f"{platform.title()}")
# Icon laden und anzeigen
if self.theme_manager:
icon_path = self.theme_manager.get_icon_path(platform.lower())
if os.path.exists(icon_path):
self.setWindowTitle(f"{platform.title()}")
self.setWindowIcon(QIcon(icon_path))
# Tabs von den Plattform-Controllern holen und hinzufügen
self.add_platform_tabs(platform_controller)
def _create_menus(self):
"""Erstellt die Menüeinträge."""
# Erstelle ein Logo-Button anstelle des Text-Menüs
logo_widget = QPushButton()
logo_widget.setIcon(QIcon(os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
"resources", "icons", "intelsight-logo.svg")))
logo_widget.setIconSize(QSize(120, 40))
logo_widget.setFlat(True)
logo_widget.setCursor(Qt.PointingHandCursor)
logo_widget.setStyleSheet("""
QPushButton {
background-color: transparent;
border: none;
padding: 5px;
}
QPushButton:hover {
background-color: rgba(0, 0, 0, 0.05);
border-radius: 4px;
}
""")
logo_widget.clicked.connect(self._show_about_dialog)
# Add logo to menu bar
self.menuBar().setCornerWidget(logo_widget, Qt.TopLeftCorner)
# Store reference for language updates
self.about_action = logo_widget
def _show_about_dialog(self):
"""Öffnet den Über-Dialog."""
dialog = AboutDialog(self.language_manager, self)
dialog.exec_()
def refresh_language_ui(self):
"""
Aktualisiert alle UI-Texte nach einem Sprachwechsel.
Diese Methode wird beim Language-Changed-Signal aufgerufen.
"""
if not self.language_manager:
return
# Fenstername aktualisieren
self.setWindowTitle(self.language_manager.get_text("main.title", "AccountForger"))
# Status-Nachricht aktualisieren
self.statusBar().showMessage(self.language_manager.get_text("status.ready", "Bereit"))
# Den Zurück-Button aktualisieren
self.back_button.setText(self.language_manager.get_text("buttons.back", "↩ Zurück"))
# Logo-Button braucht keine Text-Aktualisierung
# Die Platform Selector-View aktualisieren
if hasattr(self.platform_selector, "update_texts"):
self.platform_selector.update_texts()
# Die aktuelle Plattform-UI aktualisieren, falls vorhanden
current_platform = self.platform_title.text().lower() if self.platform_title.text() else None
if current_platform:
self.platform_title.setText(f"{current_platform.title()}")
# Tabs sind versteckt, keine Aktualisierung nötig
# Aktualisierung erzwingen
self.repaint()
def add_platform_tabs(self, platform_controller):
"""Fügt die Tabs vom Plattform-Controller hinzu."""
# Generator-Tab
if hasattr(platform_controller, "get_generator_tab"):
generator_tab = platform_controller.get_generator_tab()
self.tabs.addTab(generator_tab, "")
# Tab-Leiste verstecken, da nur ein Tab
self.tabs.tabBar().hide()
def show_platform_ui(self):
"""Zeigt die plattformspezifische UI an."""
self.stacked_widget.setCurrentWidget(self.platform_container)
def show_platform_selector(self):
"""Zeigt den Plattform-Selektor an."""
self.stacked_widget.setCurrentWidget(self.platform_selector)
self.setWindowTitle("AccountForger")
# Standard-Icon zurücksetzen
self.setWindowIcon(QIcon())
def set_status_message(self, message: str):
"""Setzt eine Nachricht in der Statusleiste."""
self.statusBar().showMessage(message)
def add_log_widget(self, text_widget):
"""Fügt einen GUI-Handler zum Logger hinzu."""
add_gui_handler(logger, text_widget)

129
views/platform_selector.py Normale Datei
Datei anzeigen

@ -0,0 +1,129 @@
# Path: views/platform_selector.py
"""
Plattformauswahl-Widget mit Tab-Navigation für die Social Media Account Generator Anwendung.
"""
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QStackedWidget
from PyQt5.QtCore import pyqtSignal
from views.components.tab_navigation import TabNavigation
from views.components.platform_grid_view import PlatformGridView
from views.components.accounts_overview_view import AccountsOverviewView
from views.widgets.language_dropdown import LanguageDropdown
class PlatformSelector(QWidget):
"""Widget zur Auswahl der Plattform mit Tab-Navigation."""
# Signal wird ausgelöst, wenn eine Plattform ausgewählt wird
platform_selected = pyqtSignal(str)
# Neue Signals für Account-Aktionen
login_requested = pyqtSignal(dict)
export_requested = pyqtSignal(dict)
def __init__(self, language_manager=None, db_manager=None):
super().__init__()
self.language_manager = language_manager
self.db_manager = db_manager
self.accounts_tab = None # Für Kompatibilität
self.init_ui()
if self.language_manager:
self.language_manager.language_changed.connect(self.update_texts)
self.update_texts()
def init_ui(self):
"""Initialisiert die Benutzeroberfläche mit Tab-Navigation."""
# Hauptlayout
main_layout = QVBoxLayout(self)
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.setSpacing(0)
# Tab Navigation
self.tab_navigation = TabNavigation(self.language_manager)
self.tab_navigation.tab_changed.connect(self._on_tab_changed)
main_layout.addWidget(self.tab_navigation)
# Content Container mit gestapelten Widgets
self.content_stack = QStackedWidget()
self.content_stack.setStyleSheet("background-color: #F8FAFC;")
# Platform Grid View (Tab 0)
self.platform_grid = PlatformGridView(self.language_manager)
self.platform_grid.platform_selected.connect(self.platform_selected.emit)
self.content_stack.addWidget(self.platform_grid)
# Accounts Overview View (Tab 1)
self.accounts_overview = AccountsOverviewView(self.db_manager, self.language_manager)
self.accounts_overview.account_login_requested.connect(self._on_login_requested)
self.accounts_overview.account_export_requested.connect(self._on_export_requested)
self.accounts_overview.account_delete_requested.connect(self._on_delete_requested)
self.content_stack.addWidget(self.accounts_overview)
# Für Kompatibilität mit MainController - accounts_tab Referenz
self.accounts_tab = self.accounts_overview
main_layout.addWidget(self.content_stack)
# Initial Tab setzen
self.content_stack.setCurrentIndex(0)
# Account-Anzahl initial laden
self._update_account_count()
def _on_tab_changed(self, index):
"""Behandelt Tab-Wechsel."""
self.content_stack.setCurrentIndex(index)
# Lade Accounts neu wenn Accounts-Tab ausgewählt wird
if index == 1:
self.load_accounts()
def load_accounts(self):
"""Lädt die Konten in der Übersicht neu."""
self.accounts_overview.load_accounts()
self._update_account_count()
def _update_account_count(self):
"""Aktualisiert die Account-Anzahl im Tab-Badge."""
if self.db_manager:
try:
accounts = self.db_manager.get_all_accounts()
self.tab_navigation.update_account_count(len(accounts))
except:
self.tab_navigation.update_account_count(0)
def _on_login_requested(self, account_data):
"""Behandelt Login-Anfragen von Account-Karten."""
self.login_requested.emit(account_data)
def _on_export_requested(self, account_data):
"""Behandelt Export-Anfragen von Account-Karten."""
# Für einzelne Account-Exports
if hasattr(self, 'export_requested'):
self.export_requested.emit([account_data])
else:
# Fallback: Direkter Export
from controllers.account_controller import AccountController
if self.db_manager:
controller = AccountController(self.db_manager)
controller.set_parent_view(self)
controller.export_accounts(None, [account_data])
def _on_delete_requested(self, account_data):
"""Behandelt Delete-Anfragen von Account-Karten."""
if self.db_manager:
try:
# Lösche den Account aus der Datenbank
self.db_manager.delete_account(account_data.get("id"))
# Lade die Accounts neu
self.load_accounts()
except Exception as e:
print(f"Fehler beim Löschen des Accounts: {e}")
def update_texts(self):
"""Aktualisiert die Texte gemäß der aktuellen Sprache."""
# Die Komponenten aktualisieren ihre Texte selbst
pass

331
views/tabs/accounts_tab.py Normale Datei
Datei anzeigen

@ -0,0 +1,331 @@
"""
Tab zur Verwaltung der erstellten Social-Media-Accounts.
"""
import logging
from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QTableWidget,
QTableWidgetItem, QPushButton, QHeaderView, QMessageBox
)
from PyQt5.QtCore import pyqtSignal, Qt, QEvent
from PyQt5.QtGui import QWheelEvent, QColor
logger = logging.getLogger("accounts_tab")
class HorizontalScrollTableWidget(QTableWidget):
"""Custom QTableWidget that supports horizontal scrolling with mouse wheel."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Install event filter to handle wheel events
self.viewport().installEventFilter(self)
def eventFilter(self, source, event):
"""Event filter to handle horizontal scrolling with mouse wheel."""
if source == self.viewport() and event.type() == QEvent.Wheel:
# Cast to QWheelEvent
wheel_event = event
# Check if horizontal scrollbar is visible and vertical is not needed
h_bar = self.horizontalScrollBar()
v_bar = self.verticalScrollBar()
# If Shift is pressed, use default behavior (horizontal scroll)
if event.modifiers() & Qt.ShiftModifier:
return super().eventFilter(source, event)
# Smart scrolling logic
if h_bar.isVisible():
# Check if vertical scrolling is at limits or not needed
vertical_at_limit = (
not v_bar.isVisible() or
(v_bar.value() == v_bar.minimum() and wheel_event.angleDelta().y() > 0) or
(v_bar.value() == v_bar.maximum() and wheel_event.angleDelta().y() < 0)
)
# If vertical is not needed or at limit, scroll horizontally
if not v_bar.isVisible() or vertical_at_limit:
# Get the delta (negative for scroll down, positive for scroll up)
delta = wheel_event.angleDelta().y()
# Calculate new position (invert delta for natural scrolling)
step = h_bar.singleStep() * 3 # Make scrolling faster
new_value = h_bar.value() - (delta // 120) * step
# Apply the new position
h_bar.setValue(new_value)
# Mark event as handled
return True
return super().eventFilter(source, event)
class AccountsTab(QWidget):
"""Widget für den Konten-Tab."""
# Signale
export_requested = pyqtSignal()
delete_requested = pyqtSignal(int) # account_id
login_requested = pyqtSignal(str, str) # account_id, platform
def __init__(self, platform_name=None, db_manager=None, language_manager=None):
super().__init__()
self.platform_name = platform_name
self.db_manager = db_manager
self.language_manager = language_manager
self.init_ui()
if self.language_manager:
self.language_manager.language_changed.connect(self.update_texts)
self.update_texts()
# Konten laden, falls db_manager vorhanden
if self.db_manager:
self.load_accounts()
def init_ui(self):
"""Initialisiert die Benutzeroberfläche."""
layout = QVBoxLayout(self)
# Konten-Tabelle mit horizontalem Mausrad-Support
self.accounts_table = HorizontalScrollTableWidget()
self.accounts_table.setColumnCount(9)
self.accounts_table.setHorizontalHeaderLabels([
"ID",
"Benutzername",
"Passwort",
"E-Mail",
"Handynummer",
"Name",
"Plattform",
"Erstellt am",
"Session"
])
# Enable horizontal scrolling and set minimum column widths
self.accounts_table.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive)
self.accounts_table.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
self.accounts_table.setHorizontalScrollMode(QTableWidget.ScrollPerPixel)
# Set minimum column widths to prevent text overlap
self.accounts_table.setColumnWidth(0, 50) # ID
self.accounts_table.setColumnWidth(1, 150) # Benutzername
self.accounts_table.setColumnWidth(2, 150) # Passwort
self.accounts_table.setColumnWidth(3, 250) # E-Mail
self.accounts_table.setColumnWidth(4, 150) # Handynummer
self.accounts_table.setColumnWidth(5, 150) # Name
self.accounts_table.setColumnWidth(6, 130) # Plattform
self.accounts_table.setColumnWidth(7, 150) # Erstellt am
# ID-Spalte verstecken
self.accounts_table.setColumnHidden(0, True)
# Explizite Zeilenhöhe setzen, um Abschneiden zu verhindern
self.accounts_table.verticalHeader().setDefaultSectionSize(40)
self.accounts_table.verticalHeader().setMinimumSectionSize(35)
layout.addWidget(self.accounts_table)
# Button-Leiste
button_layout = QHBoxLayout()
self.login_button = QPushButton("🔑 Login")
self.login_button.clicked.connect(self.on_login_clicked)
self.login_button.setToolTip("Mit gespeicherter Session einloggen")
self.export_button = QPushButton("Exportieren")
self.export_button.clicked.connect(self.on_export_clicked)
self.delete_button = QPushButton("Löschen")
self.delete_button.clicked.connect(self.on_delete_clicked)
button_layout.addWidget(self.login_button)
button_layout.addWidget(self.export_button)
button_layout.addWidget(self.delete_button)
layout.addLayout(button_layout)
def load_accounts(self):
"""Lädt Konten aus der Datenbank und zeigt sie in der Tabelle an."""
try:
if (self.platform_name and str(self.platform_name).lower() not in ["all", ""]
and hasattr(self.db_manager, "get_accounts_by_platform")):
accounts = self.db_manager.get_accounts_by_platform(self.platform_name.lower())
else:
accounts = self.db_manager.get_all_accounts()
if self.platform_name and str(self.platform_name).lower() not in ["all", ""]:
accounts = [
acc for acc in accounts
if acc.get("platform", "").lower() == str(self.platform_name).lower()
]
self.display_accounts(accounts)
except Exception as e:
logger.error(f"Fehler beim Laden der Konten: {e}")
QMessageBox.critical(self, "Fehler", f"Fehler beim Laden der Konten:\n{str(e)}")
def display_accounts(self, accounts):
"""Zeigt die Konten in der Tabelle an."""
self.accounts_table.setRowCount(len(accounts))
for row, account in enumerate(accounts):
self.accounts_table.setItem(row, 0, QTableWidgetItem(str(account.get("id", ""))))
self.accounts_table.setItem(row, 1, QTableWidgetItem(account.get("username", "")))
self.accounts_table.setItem(row, 2, QTableWidgetItem(account.get("password", "")))
self.accounts_table.setItem(row, 3, QTableWidgetItem(account.get("email", "")))
self.accounts_table.setItem(row, 4, QTableWidgetItem(account.get("phone", "")))
self.accounts_table.setItem(row, 5, QTableWidgetItem(account.get("full_name", "")))
self.accounts_table.setItem(row, 6, QTableWidgetItem(account.get("platform", "")))
self.accounts_table.setItem(row, 7, QTableWidgetItem(account.get("created_at", "")))
# Account Status hinzufügen mit visueller Darstellung
status = account.get("status", "active") # Standard: active (grün)
status_text = self._format_status_text(status)
status_item = QTableWidgetItem(status_text)
status_item.setToolTip(self._get_status_tooltip(status))
# Status-basierte Hintergrundfarben für Tabellenzellen (nur Grün/Rot)
if status == "active":
status_item.setBackground(QColor("#F0FDF4")) # Helles Grün
elif status == "inactive":
status_item.setBackground(QColor("#FEF2F2")) # Helles Rot
else:
# Fallback auf active (grün)
status_item.setBackground(QColor("#F0FDF4"))
self.accounts_table.setItem(row, 8, status_item)
def on_login_clicked(self):
"""Wird aufgerufen, wenn der Login-Button geklickt wird."""
selected_rows = self.accounts_table.selectionModel().selectedRows()
if not selected_rows:
title = "Kein Konto ausgewählt"
text = "Bitte wählen Sie ein Konto zum Einloggen aus."
if self.language_manager:
title = self.language_manager.get_text(
"accounts_tab.no_selection_title", title
)
text = self.language_manager.get_text(
"accounts_tab.no_login_selection_text", text
)
QMessageBox.warning(self, title, text)
return
row = selected_rows[0].row()
account_id = self.accounts_table.item(row, 0).text()
platform = self.accounts_table.item(row, 6).text()
self.login_requested.emit(account_id, platform)
def on_export_clicked(self):
"""Wird aufgerufen, wenn der Exportieren-Button geklickt wird."""
self.export_requested.emit()
def update_session_status(self, account_id: str, status: dict):
"""
Session-Status-Update deaktiviert (Session-Funktionalität entfernt).
"""
# Session-Funktionalität wurde entfernt - diese Methode macht nichts mehr
pass
def on_delete_clicked(self):
"""Wird aufgerufen, wenn der Löschen-Button geklickt wird."""
selected_rows = self.accounts_table.selectionModel().selectedRows()
if not selected_rows:
title = "Kein Konto ausgewählt"
text = "Bitte wählen Sie ein Konto zum Löschen aus."
if self.language_manager:
title = self.language_manager.get_text(
"accounts_tab.no_selection_title", title
)
text = self.language_manager.get_text(
"accounts_tab.no_selection_text", text
)
QMessageBox.warning(self, title, text)
return
account_id = int(self.accounts_table.item(selected_rows[0].row(), 0).text())
username = self.accounts_table.item(selected_rows[0].row(), 1).text()
q_title = "Konto löschen"
q_text = f"Möchten Sie das Konto '{username}' wirklich löschen?"
if self.language_manager:
q_title = self.language_manager.get_text("accounts_tab.delete_title", q_title)
q_text = self.language_manager.get_text(
"accounts_tab.delete_text", q_text
).format(username=username)
reply = QMessageBox.question(
self,
q_title,
q_text,
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No,
)
if reply == QMessageBox.Yes:
self.delete_requested.emit(account_id)
# Direkt aktualisieren, falls db_manager vorhanden
if self.db_manager:
success = self.db_manager.delete_account(account_id)
if success:
self.load_accounts()
suc_title = "Erfolg"
suc_text = f"Konto '{username}' wurde gelöscht."
if self.language_manager:
suc_title = self.language_manager.get_text(
"accounts_tab.delete_success_title", suc_title
)
suc_text = self.language_manager.get_text(
"accounts_tab.delete_success_text", suc_text
).format(username=username)
QMessageBox.information(self, suc_title, suc_text)
else:
err_title = "Fehler"
err_text = f"Konto '{username}' konnte nicht gelöscht werden."
if self.language_manager:
err_title = self.language_manager.get_text(
"accounts_tab.delete_error_title", err_title
)
err_text = self.language_manager.get_text(
"accounts_tab.delete_error_text", err_text
).format(username=username)
QMessageBox.critical(self, err_title, err_text)
def update_texts(self):
"""Aktualisiert UI-Texte gemäß aktueller Sprache."""
if not self.language_manager:
return
lm = self.language_manager
self.login_button.setText(lm.get_text("buttons.login", "🔑 Login"))
self.export_button.setText(lm.get_text("buttons.export", "Exportieren"))
self.delete_button.setText(lm.get_text("buttons.delete", "Löschen"))
headers = lm.get_text(
"accounts_tab.headers",
[
"ID",
"Benutzername",
"Passwort",
"E-Mail",
"Handynummer",
"Name",
"Plattform",
"Erstellt am",
],
)
self.accounts_table.setHorizontalHeaderLabels(headers)
def _format_status_text(self, status: str) -> str:
"""Formatiert den Status-Text für die Tabelle"""
status_texts = {
"active": "🟢 Funktioniert",
"inactive": "🔴 Problem"
}
return status_texts.get(status, "🟢 Funktioniert") # Standard: grün
def _get_status_tooltip(self, status: str) -> str:
"""Erstellt Tooltip-Text für den Status"""
status_tooltips = {
"active": "Account funktioniert normal - letzter Check erfolgreich",
"inactive": "Problem: Account gesperrt, eingeschränkt oder Aktion erforderlich"
}
return status_tooltips.get(status, "Account funktioniert normal")

318
views/tabs/generator_tab.py Normale Datei
Datei anzeigen

@ -0,0 +1,318 @@
# Pfad: views/tabs/generator_tab_simple.py
"""
Vereinfachter Tab zur Erstellung von Social-Media-Accounts ohne Log-Widget.
"""
import logging
from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QFormLayout, QGridLayout,
QGroupBox, QLabel, QLineEdit, QSpinBox, QRadioButton,
QCheckBox, QComboBox, QPushButton, QProgressBar,
QMessageBox, QSizePolicy, QSpacerItem
)
from PyQt5.QtCore import Qt, pyqtSignal
logger = logging.getLogger("generator_tab")
class GeneratorTab(QWidget):
"""Widget für den Account-Generator-Tab ohne Log."""
# Signale
start_requested = pyqtSignal(dict)
stop_requested = pyqtSignal()
account_created = pyqtSignal(str, dict) # (platform, account_data)
def __init__(self, platform_name, language_manager=None):
super().__init__()
self.platform_name = platform_name
self.language_manager = language_manager
self.init_ui()
if self.language_manager:
self.language_manager.language_changed.connect(self.update_texts)
self.update_texts()
def init_ui(self):
"""Initialisiert die Benutzeroberfläche."""
layout = QVBoxLayout(self)
layout.setContentsMargins(20, 20, 20, 20)
layout.setSpacing(20)
# Formularbereich - mit Grid-Layout für bessere Anordnung
self.form_group = QGroupBox()
grid_layout = QGridLayout()
grid_layout.setHorizontalSpacing(15)
grid_layout.setVerticalSpacing(10)
self.form_group.setLayout(grid_layout)
# Zeile 0: Vorname und Nachname
self.first_name_label = QLabel()
self.first_name_input = QLineEdit()
grid_layout.addWidget(self.first_name_label, 0, 0)
grid_layout.addWidget(self.first_name_input, 0, 1)
self.last_name_label = QLabel()
self.last_name_input = QLineEdit()
grid_layout.addWidget(self.last_name_label, 0, 2)
grid_layout.addWidget(self.last_name_input, 0, 3)
# Zeile 1: Alter
self.age_label = QLabel()
self.age_input = QLineEdit()
self.age_input.setMaximumWidth(100)
grid_layout.addWidget(self.age_label, 1, 0)
grid_layout.addWidget(self.age_input, 1, 1)
# Plattformspezifische Parameter hinzufügen
self.add_platform_specific_fields(grid_layout)
# Formular zum Layout hinzufügen
layout.addWidget(self.form_group)
# Fortschrittsanzeige
self.progress_bar = QProgressBar()
self.progress_bar.setRange(0, 100)
self.progress_bar.setValue(0)
self.progress_bar.setMaximumHeight(20)
self.progress_bar.setMinimumWidth(300)
self.progress_bar.hide() # Versteckt bis zur Nutzung
layout.addWidget(self.progress_bar)
# Buttons
button_layout = QHBoxLayout()
button_layout.addStretch()
self.start_button = QPushButton()
self.start_button.setMinimumWidth(150)
self.start_button.clicked.connect(self.on_start_clicked)
self.stop_button = QPushButton()
self.stop_button.setMinimumWidth(150)
self.stop_button.clicked.connect(self.on_stop_clicked)
self.stop_button.setEnabled(False)
button_layout.addWidget(self.start_button)
button_layout.addWidget(self.stop_button)
layout.addLayout(button_layout)
# Spacer am Ende
layout.addStretch()
# Event-Verbindungen entfernt - Registrierung erfolgt immer per Email
def add_platform_specific_fields(self, grid_layout):
"""
Fügt plattformspezifische Felder hinzu.
"""
platform = self.platform_name.lower()
current_row = 2 # Nach den Standard-Feldern (nur Name und Alter)
def on_start_clicked(self):
"""Wird aufgerufen, wenn der Start-Button geklickt wird."""
# Parameter sammeln
params = self.get_params()
# Eingaben validieren
valid, error_msg = self.validate_inputs(params)
if not valid:
self.show_error(error_msg)
return
# Status aktualisieren
self.set_status("Starte Account-Erstellung...")
# Signal auslösen
self.start_requested.emit(params)
def on_stop_clicked(self):
"""Wird aufgerufen, wenn der Stop-Button geklickt wird."""
self.stop_requested.emit()
def get_params(self):
"""Sammelt alle Parameter für die Account-Erstellung."""
# Vorname und Nachname kombinieren für den vollständigen Namen
first_name = self.first_name_input.text().strip()
last_name = self.last_name_input.text().strip()
full_name = f"{first_name} {last_name}"
params = {
"first_name": first_name,
"last_name": last_name,
"full_name": full_name,
"age_text": self.age_input.text().strip(),
"registration_method": "email", # Immer Email-Registrierung
"headless": False, # Browser immer sichtbar
"debug": True, # Debug ist jetzt immer aktiviert
"email_domain": "z5m7q9dk3ah2v1plx6ju.com" # Fest eingestellt
}
# Proxy ist jetzt immer deaktiviert
params["use_proxy"] = False
# Plattformspezifische Parameter
additional_params = self.get_platform_specific_params()
if additional_params:
params["additional_params"] = additional_params
return params
def validate_inputs(self, params):
"""
Validiert die Eingaben für die Account-Erstellung.
Returns:
tuple: (gültig, Fehlermeldung)
"""
# Namen prüfen
if not params.get("first_name"):
msg = "Bitte geben Sie einen Vornamen ein."
if self.language_manager:
msg = self.language_manager.get_text(
"generator_tab.first_name_error",
"Bitte geben Sie einen Vornamen ein.",
)
return False, msg
if not params.get("last_name"):
msg = "Bitte geben Sie einen Nachnamen ein."
if self.language_manager:
msg = self.language_manager.get_text(
"generator_tab.last_name_error",
"Bitte geben Sie einen Nachnamen ein.",
)
return False, msg
# Alter prüfen
age_text = params.get("age_text", "")
if not age_text:
msg = "Bitte geben Sie ein Alter ein."
if self.language_manager:
msg = self.language_manager.get_text(
"generator_tab.age_empty_error",
"Bitte geben Sie ein Alter ein.",
)
return False, msg
# Alter muss eine Zahl sein
try:
age = int(age_text)
params["age"] = age
except ValueError:
msg = "Das Alter muss eine ganze Zahl sein."
if self.language_manager:
msg = self.language_manager.get_text(
"generator_tab.age_int_error",
"Das Alter muss eine ganze Zahl sein.",
)
return False, msg
# Alter-Bereich prüfen
if age < 13 or age > 99:
msg = "Das Alter muss zwischen 13 und 99 liegen."
if self.language_manager:
msg = self.language_manager.get_text(
"generator_tab.age_range_error",
"Das Alter muss zwischen 13 und 99 liegen.",
)
return False, msg
# Telefonnummer-Validierung entfernt - nur Email-Registrierung
return True, ""
def get_platform_specific_params(self):
"""
Gibt plattformspezifische Parameter zurück.
"""
platform = self.platform_name.lower()
additional_params = {}
return additional_params
def set_running(self, running: bool):
"""Setzt den Status auf 'Wird ausgeführt' oder 'Bereit'."""
self.start_button.setEnabled(not running)
self.stop_button.setEnabled(running)
if running:
self.set_status("Läuft...")
else:
self.progress_bar.setValue(0)
self.progress_bar.hide()
def set_progress(self, value: int):
"""Setzt den Fortschritt der Fortschrittsanzeige."""
if value > 0:
self.progress_bar.show()
self.progress_bar.setValue(value)
if value >= 100:
self.progress_bar.hide()
def set_status(self, message: str):
"""Setzt die Statusnachricht."""
# Status-Label wurde entfernt
# Log to console for debugging
logger.info(f"Status: {message}")
def show_error(self, message: str):
"""Zeigt eine Fehlermeldung an."""
title = "Fehler"
if self.language_manager:
title = self.language_manager.get_text(
"generator_tab.error_title", "Fehler"
)
else:
title = "Fehler"
from views.widgets.modern_message_box import show_error
show_error(self, title, message)
self.set_status("Fehler aufgetreten")
def show_success(self, message: str):
"""Zeigt eine Erfolgsmeldung an."""
title = "Erfolg"
if self.language_manager:
title = self.language_manager.get_text(
"generator_tab.success_title", "Erfolg"
)
from views.widgets.modern_message_box import show_success
show_success(self, title, message)
self.set_status("Erfolgreich abgeschlossen")
def update_texts(self):
"""Aktualisiert UI-Texte gemäß der aktuellen Sprache."""
if not self.language_manager:
return
lm = self.language_manager
self.form_group.setTitle(lm.get_text("generator_tab.form_title", "Account-Informationen"))
self.first_name_label.setText(lm.get_text("generator_tab.first_name_label", "Vorname:"))
self.first_name_input.setPlaceholderText(lm.get_text("generator_tab.first_name_placeholder", "z.B. Max"))
self.last_name_label.setText(lm.get_text("generator_tab.last_name_label", "Nachname:"))
self.last_name_input.setPlaceholderText(lm.get_text("generator_tab.last_name_placeholder", "z.B. Mustermann"))
self.age_label.setText(lm.get_text("generator_tab.age_label", "Alter:"))
platform = self.platform_name.lower()
self.start_button.setText(lm.get_text("buttons.create", "Account erstellen"))
self.stop_button.setText(lm.get_text("buttons.cancel", "Abbrechen"))
# Kompatibilitätsmethoden für bestehenden Code
def clear_log(self):
"""Kompatibilitätsmethode - tut nichts, da kein Log vorhanden."""
pass
def add_log(self, message: str):
"""Kompatibilitätsmethode - gibt an die Konsole aus."""
logger.info(message)
def store_created_account(self, result_data: dict):
"""Speichert die erstellten Account-Daten."""
if "account_data" in result_data:
self.account_created.emit(self.platform_name, result_data["account_data"])

Datei anzeigen

@ -0,0 +1,508 @@
"""
Moderner Account Generator Tab - Ohne Log-Widget, mit besserem Layout
"""
import logging
from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QFormLayout, QGridLayout,
QGroupBox, QLabel, QLineEdit, QSpinBox, QRadioButton,
QCheckBox, QComboBox, QPushButton, QProgressBar,
QMessageBox, QFrame, QScrollArea
)
from PyQt5.QtCore import Qt, pyqtSignal, QPropertyAnimation, QEasingCurve
from PyQt5.QtGui import QFont, QPalette
logger = logging.getLogger("generator_tab")
class ModernGeneratorTab(QWidget):
"""Modernes Widget für Account-Generierung ohne Log."""
# Signale
start_requested = pyqtSignal(dict)
stop_requested = pyqtSignal()
account_created = pyqtSignal(str, dict)
def __init__(self, platform_name, language_manager=None):
super().__init__()
self.platform_name = platform_name
self.language_manager = language_manager
self.init_ui()
if self.language_manager:
self.language_manager.language_changed.connect(self.update_texts)
self.update_texts()
def init_ui(self):
"""Initialisiert die moderne Benutzeroberfläche."""
# Haupt-Layout mit Scroll-Bereich
main_layout = QVBoxLayout(self)
main_layout.setContentsMargins(0, 0, 0, 0)
# Scroll-Bereich für bessere Anpassung
scroll = QScrollArea()
scroll.setWidgetResizable(True)
scroll.setFrameShape(QFrame.NoFrame)
scroll_widget = QWidget()
scroll_layout = QVBoxLayout(scroll_widget)
scroll_layout.setContentsMargins(40, 30, 40, 30)
scroll_layout.setSpacing(30)
# Header mit Titel und Status
header_widget = self.create_header()
scroll_layout.addWidget(header_widget)
# Haupt-Formular als Card
form_card = self.create_form_card()
scroll_layout.addWidget(form_card)
# Erweiterte Optionen
options_card = self.create_options_card()
scroll_layout.addWidget(options_card)
# Action-Bereich mit Buttons
action_widget = self.create_action_area()
scroll_layout.addWidget(action_widget)
scroll_layout.addStretch()
scroll.setWidget(scroll_widget)
main_layout.addWidget(scroll)
# Event-Verbindungen
self.email_radio.toggled.connect(self.toggle_phone_input)
self.phone_radio.toggled.connect(self.toggle_phone_input)
# Initialer Status
self.toggle_phone_input()
def create_header(self):
"""Erstellt den Header-Bereich mit Titel und Status."""
header = QWidget()
layout = QHBoxLayout(header)
layout.setContentsMargins(0, 0, 0, 20)
# Titel
title = QLabel("Account erstellen")
title_font = QFont()
title_font.setPointSize(24)
title_font.setWeight(QFont.Bold)
title.setFont(title_font)
layout.addWidget(title)
layout.addStretch()
# Status-Widget entfernt für cleanes Design
return header
def create_form_card(self):
"""Erstellt die Haupt-Formular-Card."""
card = QFrame()
card.setObjectName("formCard")
card.setStyleSheet("""
#formCard {
background-color: white;
border: 1px solid #e0e0e0;
border-radius: 12px;
padding: 30px;
}
""")
layout = QVBoxLayout(card)
# Section Title
section_title = QLabel("Account-Informationen")
section_font = QFont()
section_font.setPointSize(16)
section_font.setWeight(QFont.DemiBold)
section_title.setFont(section_font)
layout.addWidget(section_title)
layout.addSpacing(20)
# Grid-Layout für bessere Anordnung
grid = QGridLayout()
grid.setHorizontalSpacing(20)
grid.setVerticalSpacing(20)
# Erste Zeile: Vorname und Nachname
self.first_name_label = self.create_label("Vorname")
self.first_name_input = self.create_input("z.B. Max")
grid.addWidget(self.first_name_label, 0, 0)
grid.addWidget(self.first_name_input, 1, 0)
self.last_name_label = self.create_label("Nachname")
self.last_name_input = self.create_input("z.B. Mustermann")
grid.addWidget(self.last_name_label, 0, 1)
grid.addWidget(self.last_name_input, 1, 1)
# Zweite Zeile: Alter
self.age_label = self.create_label("Alter")
self.age_input = self.create_input("z.B. 25")
grid.addWidget(self.age_label, 2, 0)
grid.addWidget(self.age_input, 3, 0)
# Dritte Zeile: Registrierungsmethode
self.reg_method_label = self.create_label("Registrierungsmethode")
grid.addWidget(self.reg_method_label, 4, 0, 1, 2)
# Radio Buttons in horizontaler Anordnung
radio_widget = QWidget()
radio_layout = QHBoxLayout(radio_widget)
radio_layout.setContentsMargins(0, 0, 0, 0)
self.email_radio = QRadioButton("E-Mail")
self.phone_radio = QRadioButton("Telefon")
self.email_radio.setChecked(True)
# Styling für Radio Buttons
radio_style = """
QRadioButton {
font-size: 14px;
spacing: 8px;
}
QRadioButton::indicator {
width: 18px;
height: 18px;
}
"""
self.email_radio.setStyleSheet(radio_style)
self.phone_radio.setStyleSheet(radio_style)
radio_layout.addWidget(self.email_radio)
radio_layout.addWidget(self.phone_radio)
radio_layout.addStretch()
grid.addWidget(radio_widget, 5, 0, 1, 2)
# Vierte Zeile: Kontaktfeld (E-Mail Domain oder Telefon)
self.email_domain_label = self.create_label("E-Mail Domain")
self.email_domain_input = self.create_input("")
self.email_domain_input.setText("z5m7q9dk3ah2v1plx6ju.com")
grid.addWidget(self.email_domain_label, 6, 0)
grid.addWidget(self.email_domain_input, 7, 0)
self.phone_label = self.create_label("Telefonnummer")
self.phone_input = self.create_input("z.B. +49123456789")
self.phone_label.hide()
self.phone_input.hide()
grid.addWidget(self.phone_label, 6, 1)
grid.addWidget(self.phone_input, 7, 1)
layout.addLayout(grid)
# Plattform-spezifische Felder
self.add_platform_specific_fields(layout)
return card
def create_options_card(self):
"""Erstellt die Card für erweiterte Optionen."""
card = QFrame()
card.setObjectName("optionsCard")
card.setStyleSheet("""
#optionsCard {
background-color: white;
border: 1px solid #e0e0e0;
border-radius: 12px;
padding: 30px;
}
""")
layout = QVBoxLayout(card)
# Section Title
section_title = QLabel("Erweiterte Optionen")
section_font = QFont()
section_font.setPointSize(16)
section_font.setWeight(QFont.DemiBold)
section_title.setFont(section_font)
layout.addWidget(section_title)
layout.addSpacing(15)
# Optionen als moderne Checkboxen
self.headless_check = self.create_checkbox("Browser im Hintergrund ausführen")
self.headless_check.setToolTip("Der Browser wird nicht sichtbar sein")
layout.addWidget(self.headless_check)
return card
def create_action_area(self):
"""Erstellt den Bereich mit Aktions-Buttons und Progress."""
widget = QWidget()
layout = QVBoxLayout(widget)
layout.setContentsMargins(0, 0, 0, 0)
# Progress Bar (initial versteckt)
self.progress_bar = QProgressBar()
self.progress_bar.setMinimumHeight(6)
self.progress_bar.setTextVisible(False)
self.progress_bar.setStyleSheet("""
QProgressBar {
background-color: #f0f0f0;
border: none;
border-radius: 3px;
}
QProgressBar::chunk {
background-color: #2196F3;
border-radius: 3px;
}
""")
self.progress_bar.hide()
layout.addWidget(self.progress_bar)
layout.addSpacing(20)
# Buttons
button_layout = QHBoxLayout()
button_layout.addStretch()
self.stop_button = self.create_button("Abbrechen", primary=False)
self.stop_button.clicked.connect(self.on_stop_clicked)
self.stop_button.setEnabled(False)
self.stop_button.hide()
button_layout.addWidget(self.stop_button)
self.start_button = self.create_button("Account erstellen", primary=True)
self.start_button.clicked.connect(self.on_start_clicked)
button_layout.addWidget(self.start_button)
layout.addLayout(button_layout)
return widget
def create_label(self, text):
"""Erstellt ein modernes Label."""
label = QLabel(text)
label.setStyleSheet("""
QLabel {
color: #666;
font-size: 13px;
font-weight: 500;
}
""")
return label
def create_input(self, placeholder=""):
"""Erstellt ein modernes Input-Feld."""
input_field = QLineEdit()
input_field.setPlaceholderText(placeholder)
input_field.setMinimumHeight(42)
input_field.setStyleSheet("""
QLineEdit {
border: 2px solid #e0e0e0;
border-radius: 8px;
padding: 10px 15px;
font-size: 14px;
background-color: #fafafa;
}
QLineEdit:focus {
border-color: #2196F3;
background-color: white;
}
QLineEdit:hover {
border-color: #bdbdbd;
}
""")
return input_field
def create_checkbox(self, text):
"""Erstellt eine moderne Checkbox."""
checkbox = QCheckBox(text)
checkbox.setStyleSheet("""
QCheckBox {
font-size: 14px;
color: #333;
spacing: 10px;
}
QCheckBox::indicator {
width: 20px;
height: 20px;
border-radius: 4px;
border: 2px solid #e0e0e0;
background-color: white;
}
QCheckBox::indicator:checked {
background-color: #2196F3;
border-color: #2196F3;
image: url(check_white.png);
}
QCheckBox::indicator:hover {
border-color: #2196F3;
}
""")
return checkbox
def create_button(self, text, primary=False):
"""Erstellt einen modernen Button."""
button = QPushButton(text)
button.setMinimumHeight(45)
button.setCursor(Qt.PointingHandCursor)
if primary:
button.setStyleSheet("""
QPushButton {
background-color: #2196F3;
color: white;
border: none;
border-radius: 8px;
padding: 12px 32px;
font-size: 14px;
font-weight: 600;
}
QPushButton:hover {
background-color: #1976D2;
}
QPushButton:pressed {
background-color: #0D47A1;
}
QPushButton:disabled {
background-color: #BBBBBB;
}
""")
else:
button.setStyleSheet("""
QPushButton {
background-color: white;
color: #666;
border: 2px solid #e0e0e0;
border-radius: 8px;
padding: 12px 32px;
font-size: 14px;
font-weight: 500;
}
QPushButton:hover {
border-color: #bdbdbd;
background-color: #f5f5f5;
}
QPushButton:pressed {
background-color: #eeeeee;
}
QPushButton:disabled {
color: #BBBBBB;
border-color: #eeeeee;
}
""")
return button
def add_platform_specific_fields(self, parent_layout):
"""Fügt plattformspezifische Felder hinzu."""
platform = self.platform_name.lower()
def toggle_phone_input(self):
"""Wechselt zwischen E-Mail und Telefon-Eingabe."""
if self.email_radio.isChecked():
self.email_domain_label.show()
self.email_domain_input.show()
self.phone_label.hide()
self.phone_input.hide()
else:
self.email_domain_label.hide()
self.email_domain_input.hide()
self.phone_label.show()
self.phone_input.show()
def on_start_clicked(self):
"""Wird aufgerufen, wenn der Start-Button geklickt wird."""
params = self.get_params()
valid, error_msg = self.validate_inputs(params)
if not valid:
self.show_error(error_msg)
return
# UI für laufenden Prozess anpassen
self.set_running(True)
self.progress_bar.show()
self.progress_bar.setValue(0)
# Signal auslösen
self.start_requested.emit(params)
def on_stop_clicked(self):
"""Wird aufgerufen, wenn der Stop-Button geklickt wird."""
self.stop_requested.emit()
def get_params(self):
"""Sammelt alle Parameter für die Account-Erstellung."""
first_name = self.first_name_input.text().strip()
last_name = self.last_name_input.text().strip()
full_name = f"{first_name} {last_name}"
params = {
"first_name": first_name,
"last_name": last_name,
"full_name": full_name,
"age_text": self.age_input.text().strip(),
"registration_method": "email" if self.email_radio.isChecked() else "phone",
"headless": self.headless_check.isChecked(),
"debug": True,
"email_domain": self.email_domain_input.text().strip(),
"use_proxy": False
}
if self.phone_radio.isChecked():
params["phone_number"] = self.phone_input.text().strip()
return params
def validate_inputs(self, params):
"""Validiert die Eingaben."""
if not params.get("first_name"):
return False, "Bitte geben Sie einen Vornamen ein."
if not params.get("last_name"):
return False, "Bitte geben Sie einen Nachnamen ein."
age_text = params.get("age_text", "")
if not age_text:
return False, "Bitte geben Sie ein Alter ein."
try:
age = int(age_text)
params["age"] = age
except ValueError:
return False, "Das Alter muss eine ganze Zahl sein."
if age < 13 or age > 99:
return False, "Das Alter muss zwischen 13 und 99 liegen."
if params.get("registration_method") == "phone" and not params.get("phone_number"):
return False, "Bitte geben Sie eine Telefonnummer ein."
return True, ""
def set_running(self, running: bool):
"""Setzt den Status auf 'Wird ausgeführt' oder 'Bereit'."""
if running:
self.start_button.hide()
self.stop_button.show()
self.stop_button.setEnabled(True)
# Status-Dot entfernt
# Status-Text entfernt
else:
self.start_button.show()
self.stop_button.hide()
self.stop_button.setEnabled(False)
self.progress_bar.hide()
# Status-Dot entfernt
# Status-Text entfernt
def set_progress(self, value: int):
"""Setzt den Fortschritt."""
self.progress_bar.setValue(value)
def set_status(self, message: str):
"""Setzt die Statusnachricht."""
# Status-Text entfernt
logger.info(f"Status: {message}")
def show_error(self, message: str):
"""Zeigt eine moderne Fehlermeldung an."""
from views.widgets.modern_message_box import show_error
show_error(self, "Fehler", message)
def update_texts(self):
"""Aktualisiert UI-Texte gemäß der aktuellen Sprache."""
# Hier würden die Übersetzungen implementiert
pass

315
views/tabs/settings_tab.py Normale Datei
Datei anzeigen

@ -0,0 +1,315 @@
"""
Tab für die Einstellungen der Anwendung.
"""
import logging
from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QFormLayout,
QGroupBox, QLabel, QLineEdit, QSpinBox, QTextEdit,
QPushButton, QCheckBox, QComboBox
)
from PyQt5.QtCore import pyqtSignal, Qt
logger = logging.getLogger("settings_tab")
class SettingsTab(QWidget):
"""Widget für den Einstellungen-Tab."""
# Signale
proxy_settings_saved = pyqtSignal(dict)
proxy_tested = pyqtSignal(str) # proxy_type
email_settings_saved = pyqtSignal(dict)
email_tested = pyqtSignal(dict) # email_settings
license_activated = pyqtSignal(str) # license_key
def __init__(self, platform_name, proxy_rotator=None, email_handler=None, license_manager=None, language_manager=None):
super().__init__()
self.platform_name = platform_name
self.proxy_rotator = proxy_rotator
self.email_handler = email_handler
self.license_manager = license_manager
self.language_manager = language_manager
self.init_ui()
if self.language_manager:
self.language_manager.language_changed.connect(self.update_texts)
self.update_texts()
# Einstellungen laden, falls Handler vorhanden
self.load_settings()
def init_ui(self):
"""Initialisiert die Benutzeroberfläche."""
layout = QVBoxLayout(self)
# Proxy-Einstellungen
proxy_group = QGroupBox("Proxy-Einstellungen")
proxy_layout = QVBoxLayout(proxy_group)
# IPv4 Proxies
ipv4_form = QFormLayout()
self.ipv4_proxy_input = QTextEdit()
self.ipv4_proxy_input.setPlaceholderText("Ein Proxy pro Zeile im Format: host:port:username:password")
ipv4_form.addRow("IPv4 Proxies:", self.ipv4_proxy_input)
proxy_layout.addLayout(ipv4_form)
# IPv6 Proxies
ipv6_form = QFormLayout()
self.ipv6_proxy_input = QTextEdit()
self.ipv6_proxy_input.setPlaceholderText("Ein Proxy pro Zeile im Format: host:port:username:password")
ipv6_form.addRow("IPv6 Proxies:", self.ipv6_proxy_input)
proxy_layout.addLayout(ipv6_form)
# Mobile Proxies
mobile_form = QFormLayout()
self.mobile_proxy_input = QTextEdit()
self.mobile_proxy_input.setPlaceholderText("Ein Proxy pro Zeile im Format: host:port:username:password")
mobile_form.addRow("Mobile Proxies:", self.mobile_proxy_input)
proxy_layout.addLayout(mobile_form)
# Mobile Proxy API Keys
api_key_layout = QFormLayout()
self.marsproxy_api_input = QLineEdit()
api_key_layout.addRow("MarsProxies API Key:", self.marsproxy_api_input)
self.iproyal_api_input = QLineEdit()
api_key_layout.addRow("IPRoyal API Key:", self.iproyal_api_input)
proxy_layout.addLayout(api_key_layout)
# Test-Button
proxy_button_layout = QHBoxLayout()
self.test_proxy_button = QPushButton("Proxy testen")
self.test_proxy_button.clicked.connect(self.on_test_proxy_clicked)
self.save_proxy_button = QPushButton("Proxy-Einstellungen speichern")
self.save_proxy_button.clicked.connect(self.on_save_proxy_clicked)
proxy_button_layout.addWidget(self.test_proxy_button)
proxy_button_layout.addWidget(self.save_proxy_button)
proxy_layout.addLayout(proxy_button_layout)
layout.addWidget(proxy_group)
# E-Mail-Einstellungen
email_group = QGroupBox("E-Mail-Einstellungen")
email_layout = QFormLayout(email_group)
self.imap_server_input = QLineEdit("imap.ionos.de")
email_layout.addRow("IMAP-Server:", self.imap_server_input)
self.imap_port_input = QSpinBox()
self.imap_port_input.setRange(1, 65535)
self.imap_port_input.setValue(993)
email_layout.addRow("IMAP-Port:", self.imap_port_input)
self.imap_user_input = QLineEdit()
email_layout.addRow("IMAP-Benutzername:", self.imap_user_input)
self.imap_pass_input = QLineEdit()
self.imap_pass_input.setEchoMode(QLineEdit.Password)
email_layout.addRow("IMAP-Passwort:", self.imap_pass_input)
email_button_layout = QHBoxLayout()
self.test_email_button = QPushButton("E-Mail testen")
self.test_email_button.clicked.connect(self.on_test_email_clicked)
self.save_email_button = QPushButton("E-Mail-Einstellungen speichern")
self.save_email_button.clicked.connect(self.on_save_email_clicked)
email_button_layout.addWidget(self.test_email_button)
email_button_layout.addWidget(self.save_email_button)
email_layout.addRow("", email_button_layout)
layout.addWidget(email_group)
# Plattformspezifische Einstellungen
if self.platform_name.lower() != "instagram":
self.add_platform_specific_settings(layout)
# Lizenz-Gruppe
license_group = QGroupBox("Lizenz")
license_layout = QFormLayout(license_group)
self.license_key_input = QLineEdit()
license_layout.addRow("Lizenzschlüssel:", self.license_key_input)
self.activate_license_button = QPushButton("Lizenz aktivieren")
self.activate_license_button.clicked.connect(self.on_activate_license_clicked)
license_layout.addRow("", self.activate_license_button)
layout.addWidget(license_group)
# Stretch am Ende hinzufügen
layout.addStretch(1)
def add_platform_specific_settings(self, layout):
"""
Fügt plattformspezifische Einstellungen hinzu.
Diese Methode kann in abgeleiteten Klassen überschrieben werden.
Args:
layout: Das Layout, zu dem die Einstellungen hinzugefügt werden sollen
"""
platform = self.platform_name.lower()
platform_settings_group = QGroupBox(f"{self.platform_name}-spezifische Einstellungen")
platform_settings_layout = QFormLayout(platform_settings_group)
# Je nach Plattform unterschiedliche Einstellungen
if platform == "twitter":
self.twitter_api_key = QLineEdit()
platform_settings_layout.addRow("Twitter API Key:", self.twitter_api_key)
self.twitter_api_secret = QLineEdit()
platform_settings_layout.addRow("Twitter API Secret:", self.twitter_api_secret)
elif platform == "tiktok":
self.video_upload = QCheckBox("Video automatisch hochladen")
platform_settings_layout.addRow("", self.video_upload)
self.follower_action = QCheckBox("Automatisch anderen Nutzern folgen")
platform_settings_layout.addRow("", self.follower_action)
elif platform == "facebook":
self.page_creation = QCheckBox("Seite automatisch erstellen")
platform_settings_layout.addRow("", self.page_creation)
self.privacy_level = QComboBox()
self.privacy_level.addItems(["Öffentlich", "Freunde", "Nur ich"])
platform_settings_layout.addRow("Datenschutzeinstellung:", self.privacy_level)
# Speichern-Button für plattformspezifische Einstellungen
self.platform_save_button = QPushButton(f"{self.platform_name}-Einstellungen speichern")
self.platform_save_button.clicked.connect(self.on_save_platform_settings_clicked)
platform_settings_layout.addRow("", self.platform_save_button)
layout.addWidget(platform_settings_group)
def load_settings(self):
"""Lädt die Einstellungen aus den Handlern."""
# Proxy-Einstellungen laden
if self.proxy_rotator:
try:
proxy_config = self.proxy_rotator.get_config() or {}
# IPv4 Proxies
ipv4_proxies = proxy_config.get("ipv4", [])
self.ipv4_proxy_input.setPlainText("\n".join(ipv4_proxies))
# IPv6 Proxies
ipv6_proxies = proxy_config.get("ipv6", [])
self.ipv6_proxy_input.setPlainText("\n".join(ipv6_proxies))
# Mobile Proxies
mobile_proxies = proxy_config.get("mobile", [])
self.mobile_proxy_input.setPlainText("\n".join(mobile_proxies))
# API Keys
mobile_api = proxy_config.get("mobile_api", {})
self.marsproxy_api_input.setText(mobile_api.get("marsproxies", ""))
self.iproyal_api_input.setText(mobile_api.get("iproyal", ""))
except Exception as e:
logger.error(f"Fehler beim Laden der Proxy-Einstellungen: {e}")
# E-Mail-Einstellungen laden
if self.email_handler:
try:
email_config = self.email_handler.get_config() or {}
self.imap_server_input.setText(email_config.get("imap_server", "imap.ionos.de"))
self.imap_port_input.setValue(email_config.get("imap_port", 993))
self.imap_user_input.setText(email_config.get("imap_user", ""))
self.imap_pass_input.setText(email_config.get("imap_pass", ""))
except Exception as e:
logger.error(f"Fehler beim Laden der E-Mail-Einstellungen: {e}")
# Lizenzeinstellungen laden
if self.license_manager:
try:
license_info = self.license_manager.get_license_info()
self.license_key_input.setText(license_info.get("key", ""))
except Exception as e:
logger.error(f"Fehler beim Laden der Lizenzeinstellungen: {e}")
def on_save_proxy_clicked(self):
"""Wird aufgerufen, wenn der Proxy-Speichern-Button geklickt wird."""
# Proxy-Einstellungen sammeln
settings = {
"ipv4_proxies": self.ipv4_proxy_input.toPlainText(),
"ipv6_proxies": self.ipv6_proxy_input.toPlainText(),
"mobile_proxies": self.mobile_proxy_input.toPlainText(),
"mobile_api": {
"marsproxies": self.marsproxy_api_input.text().strip(),
"iproyal": self.iproyal_api_input.text().strip()
}
}
# Signal auslösen
self.proxy_settings_saved.emit(settings)
def on_test_proxy_clicked(self):
"""Wird aufgerufen, wenn der Proxy-Test-Button geklickt wird."""
# Proxy-Typ aus der Combobox in der Generator-Tab holen
# Da wir keine direkte Referenz haben, nehmen wir den ersten Eintrag
proxy_type = "ipv4"
# Signal auslösen
self.proxy_tested.emit(proxy_type)
def on_save_email_clicked(self):
"""Wird aufgerufen, wenn der E-Mail-Speichern-Button geklickt wird."""
# E-Mail-Einstellungen sammeln
settings = {
"imap_server": self.imap_server_input.text().strip(),
"imap_port": self.imap_port_input.value(),
"imap_user": self.imap_user_input.text().strip(),
"imap_pass": self.imap_pass_input.text()
}
# Signal auslösen
self.email_settings_saved.emit(settings)
def on_test_email_clicked(self):
"""Wird aufgerufen, wenn der E-Mail-Test-Button geklickt wird."""
# E-Mail-Einstellungen sammeln
settings = {
"imap_server": self.imap_server_input.text().strip(),
"imap_port": self.imap_port_input.value(),
"imap_user": self.imap_user_input.text().strip(),
"imap_pass": self.imap_pass_input.text()
}
# Signal auslösen
self.email_tested.emit(settings)
def on_save_platform_settings_clicked(self):
"""Wird aufgerufen, wenn der Plattform-Einstellungen-Speichern-Button geklickt wird."""
# Hier könnte ein plattformspezifisches Signal ausgelöst werden
logger.info(f"{self.platform_name}-Einstellungen gespeichert")
def on_activate_license_clicked(self):
"""Wird aufgerufen, wenn der Lizenz-Aktivieren-Button geklickt wird."""
license_key = self.license_key_input.text().strip()
if not license_key:
return
# Signal auslösen
self.license_activated.emit(license_key)
def update_texts(self):
"""Aktualisiert UI-Texte gemäß aktueller Sprache."""
if not self.language_manager:
return
self.test_proxy_button.setText(self.language_manager.get_text("buttons.test_proxy", "Proxy testen"))
self.save_proxy_button.setText(self.language_manager.get_text("buttons.save_proxy", "Proxy-Einstellungen speichern"))
self.test_email_button.setText(self.language_manager.get_text("buttons.test_email", "E-Mail testen"))
self.save_email_button.setText(self.language_manager.get_text("buttons.save_email", "E-Mail-Einstellungen speichern"))
self.activate_license_button.setText(self.language_manager.get_text("buttons.activate_license", "Lizenz aktivieren"))

420
views/widgets/account_card.py Normale Datei
Datei anzeigen

@ -0,0 +1,420 @@
"""
Account Card Widget - Kompakte Account-Karte nach Styleguide
"""
from PyQt5.QtWidgets import (
QFrame, QVBoxLayout, QHBoxLayout, QLabel, QPushButton,
QGridLayout, QWidget, QApplication
)
from PyQt5.QtCore import Qt, pyqtSignal, QSize, QTimer
from PyQt5.QtGui import QFont, QPixmap
import os
from views.widgets.icon_factory import IconFactory
class AccountCard(QFrame):
"""
Kompakte Account-Karte nach Styleguide für Light Mode
"""
# Signals
login_requested = pyqtSignal(dict) # Account-Daten
export_requested = pyqtSignal(dict) # Account-Daten
delete_requested = pyqtSignal(dict) # Account-Daten
def __init__(self, account_data, language_manager=None):
super().__init__()
self.account_data = account_data
self.language_manager = language_manager
self.password_visible = False
# Timer für Icon-Animation
self.email_copy_timer = QTimer()
self.email_copy_timer.timeout.connect(self._restore_email_copy_icon)
self.password_copy_timer = QTimer()
self.password_copy_timer.timeout.connect(self._restore_password_copy_icon)
# Original Icons speichern
self.copy_icon = None
self.check_icon = None
self.init_ui()
if self.language_manager:
self.language_manager.language_changed.connect(self.update_texts)
self.update_texts()
def _on_login_clicked(self):
"""Handler für Login-Button"""
self.login_requested.emit(self.account_data)
def init_ui(self):
"""Initialisiert die UI nach Styleguide"""
self.setObjectName("accountCard")
# Status-basiertes Styling anwenden
self._apply_status_styling()
# Setze feste Breite für die Karte
self.setFixedWidth(360)
# Hauptlayout
layout = QVBoxLayout(self)
layout.setContentsMargins(16, 16, 16, 16)
layout.setSpacing(12)
# Header Zeile
header_layout = QHBoxLayout()
# Platform Icon + Username
info_layout = QHBoxLayout()
info_layout.setSpacing(8)
# Status wird jetzt über Karten-Hintergrund und Umrandung angezeigt
# Platform Icon
platform_icon = IconFactory.create_icon_label(
self.account_data.get("platform", "").lower(),
size=18
)
info_layout.addWidget(platform_icon)
# Username
username_label = QLabel(self.account_data.get("username", ""))
username_font = QFont("Poppins", 16)
username_font.setWeight(QFont.DemiBold)
username_label.setFont(username_font)
username_label.setStyleSheet("""
color: #1A365D;
font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
""")
info_layout.addWidget(username_label)
info_layout.addStretch()
header_layout.addLayout(info_layout)
# Login Button
self.login_btn = QPushButton("Login")
self.login_btn.setCursor(Qt.PointingHandCursor)
self.login_btn.setStyleSheet("""
QPushButton {
background-color: #3182CE;
color: #FFFFFF;
border: none;
border-radius: 6px;
padding: 6px 16px;
font-size: 12px;
font-weight: 500;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
min-width: 60px;
}
QPushButton:hover {
background-color: #2563EB;
}
QPushButton:pressed {
background-color: #1D4ED8;
}
""")
self.login_btn.clicked.connect(lambda: self._on_login_clicked())
header_layout.addWidget(self.login_btn)
layout.addLayout(header_layout)
# Details Grid
details_grid = QGridLayout()
details_grid.setSpacing(8)
# Email
email_icon = IconFactory.create_icon_label("mail", size=14)
details_grid.addWidget(email_icon, 0, 0)
# Email container with copy button
email_container = QWidget()
email_layout = QHBoxLayout(email_container)
email_layout.setContentsMargins(0, 0, 0, 0)
email_layout.setSpacing(8)
email_label = QLabel(self.account_data.get("email", ""))
email_label.setStyleSheet("""
color: #4A5568;
font-size: 13px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
""")
email_layout.addWidget(email_label)
# Email Copy Button
email_copy_btn = QPushButton()
email_copy_btn.setToolTip("E-Mail kopieren")
email_copy_btn.setCursor(Qt.PointingHandCursor)
email_copy_btn.setStyleSheet("""
QPushButton {
background: transparent;
border: none;
padding: 2px;
min-width: 20px;
max-width: 20px;
min-height: 20px;
max-height: 20px;
}
QPushButton:hover {
background-color: #F7FAFC;
border-radius: 4px;
}
""")
self.copy_icon = IconFactory.get_icon("copy", size=16)
self.check_icon = IconFactory.get_icon("check", size=16, color="#10B981")
self.email_copy_btn = email_copy_btn
self.email_copy_btn.setIcon(self.copy_icon)
self.email_copy_btn.setIconSize(QSize(16, 16))
email_copy_btn.clicked.connect(self._copy_email)
email_layout.addWidget(email_copy_btn)
email_layout.addStretch()
details_grid.addWidget(email_container, 0, 1)
# Password
pass_icon = IconFactory.create_icon_label("key", size=14)
details_grid.addWidget(pass_icon, 1, 0)
pass_container = QWidget()
pass_layout = QHBoxLayout(pass_container)
pass_layout.setContentsMargins(0, 0, 0, 0)
pass_layout.setSpacing(8)
self.password_label = QLabel("••••••••")
self.password_label.setStyleSheet("""
color: #4A5568;
font-size: 13px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
""")
pass_layout.addWidget(self.password_label)
# Copy Button
copy_btn = QPushButton()
copy_btn.setToolTip("Passwort kopieren")
copy_btn.setCursor(Qt.PointingHandCursor)
copy_btn.setStyleSheet("""
QPushButton {
background: transparent;
border: none;
padding: 2px;
min-width: 20px;
max-width: 20px;
min-height: 20px;
max-height: 20px;
}
QPushButton:hover {
background-color: #F7FAFC;
border-radius: 4px;
}
""")
self.password_copy_btn = copy_btn
self.password_copy_btn.setIcon(self.copy_icon)
self.password_copy_btn.setIconSize(QSize(16, 16))
copy_btn.clicked.connect(self._copy_password)
pass_layout.addWidget(copy_btn)
# Show/Hide Button
self.visibility_btn = QPushButton()
self.visibility_btn.setToolTip("Passwort anzeigen")
self.visibility_btn.setCursor(Qt.PointingHandCursor)
self.visibility_btn.setStyleSheet("""
QPushButton {
background: transparent;
border: none;
padding: 2px;
min-width: 20px;
max-width: 20px;
min-height: 20px;
max-height: 20px;
}
QPushButton:hover {
background-color: #F7FAFC;
border-radius: 4px;
}
""")
self.eye_icon = IconFactory.get_icon("eye", size=16)
self.eye_slash_icon = IconFactory.get_icon("eye-slash", size=16)
self.visibility_btn.setIcon(self.eye_icon)
self.visibility_btn.setIconSize(QSize(16, 16))
self.visibility_btn.clicked.connect(self._toggle_password)
pass_layout.addWidget(self.visibility_btn)
pass_layout.addStretch()
details_grid.addWidget(pass_container, 1, 1)
# Created Date
date_icon = IconFactory.create_icon_label("calendar", size=14)
details_grid.addWidget(date_icon, 2, 0)
date_label = QLabel(self.account_data.get("created_at", ""))
date_label.setStyleSheet("""
color: #A0AEC0;
font-size: 12px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
""")
details_grid.addWidget(date_label, 2, 1)
layout.addLayout(details_grid)
# Action buttons
actions_layout = QHBoxLayout()
actions_layout.setSpacing(8)
# Export Button
self.export_btn = QPushButton("Profil\nexportieren")
self.export_btn.setCursor(Qt.PointingHandCursor)
self.export_btn.setStyleSheet("""
QPushButton {
background-color: #10B981;
color: #FFFFFF;
border: none;
border-radius: 6px;
padding: 4px 12px;
font-size: 12px;
font-weight: 500;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
min-width: 120px;
min-height: 36px;
text-align: center;
}
QPushButton:hover {
background-color: #059669;
}
QPushButton:pressed {
background-color: #047857;
}
""")
self.export_btn.clicked.connect(lambda: self.export_requested.emit(self.account_data))
actions_layout.addWidget(self.export_btn)
actions_layout.addStretch()
# Delete Button
self.delete_btn = QPushButton("Löschen")
self.delete_btn.setCursor(Qt.PointingHandCursor)
self.delete_btn.setStyleSheet("""
QPushButton {
background-color: #DC2626;
color: #FFFFFF;
border: none;
border-radius: 6px;
padding: 8px 16px;
font-size: 12px;
font-weight: 500;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
min-width: 90px;
}
QPushButton:hover {
background-color: #B91C1C;
}
QPushButton:pressed {
background-color: #991B1B;
}
""")
self.delete_btn.clicked.connect(lambda: self.delete_requested.emit(self.account_data))
actions_layout.addWidget(self.delete_btn)
layout.addLayout(actions_layout)
def _toggle_password(self):
"""Zeigt/Versteckt das Passwort"""
self.password_visible = not self.password_visible
if self.password_visible:
self.password_label.setText(self.account_data.get("password", ""))
self.visibility_btn.setIcon(self.eye_slash_icon)
else:
self.password_label.setText("••••••••")
self.visibility_btn.setIcon(self.eye_icon)
def _copy_password(self):
"""Kopiert das Passwort in die Zwischenablage"""
clipboard = QApplication.clipboard()
clipboard.setText(self.account_data.get("password", ""))
# Icon zu Check wechseln
self.password_copy_btn.setIcon(self.check_icon)
# Timer starten um nach 2 Sekunden zurückzuwechseln
self.password_copy_timer.stop()
self.password_copy_timer.start(2000)
def _copy_email(self):
"""Kopiert die E-Mail in die Zwischenablage"""
clipboard = QApplication.clipboard()
clipboard.setText(self.account_data.get("email", ""))
# Icon zu Check wechseln
self.email_copy_btn.setIcon(self.check_icon)
# Timer starten um nach 2 Sekunden zurückzuwechseln
self.email_copy_timer.stop()
self.email_copy_timer.start(2000)
def update_texts(self):
"""Aktualisiert die Texte gemäß der aktuellen Sprache"""
if not self.language_manager:
return
self.login_btn.setText(
self.language_manager.get_text("account_card.login", "Login")
)
self.export_btn.setText(
self.language_manager.get_text("account_card.export", "Profil\nexportieren")
)
self.delete_btn.setText(
self.language_manager.get_text("account_card.delete", "Löschen")
)
def _restore_email_copy_icon(self):
"""Stellt das ursprüngliche Copy Icon für Email wieder her"""
self.email_copy_btn.setIcon(self.copy_icon)
self.email_copy_timer.stop()
def _restore_password_copy_icon(self):
"""Stellt das ursprüngliche Copy Icon für Passwort wieder her"""
self.password_copy_btn.setIcon(self.copy_icon)
self.password_copy_timer.stop()
def _apply_status_styling(self):
"""Wendet Status-basiertes Styling mit Pastel-Hintergrund und farbiger Umrandung an"""
# Status aus Account-Daten lesen - Standard ist "active" (grün)
status = self.account_data.get("status", "active")
# Status-Farben definieren (nur Grün/Rot)
status_styles = {
"active": {
"background": "#F0FDF4", # Sehr helles Mintgrün
"border": "#10B981", # Kräftiges Grün
"hover_border": "#059669" # Dunkleres Grün beim Hover
},
"inactive": {
"background": "#FEF2F2", # Sehr helles Rosa
"border": "#EF4444", # Kräftiges Rot
"hover_border": "#DC2626" # Dunkleres Rot beim Hover
}
}
# Aktueller Status oder Fallback auf active (grün)
current_style = status_styles.get(status, status_styles["active"])
# CSS-Styling anwenden
self.setStyleSheet(f"""
QFrame#accountCard {{
background-color: {current_style["background"]};
border: 2px solid {current_style["border"]};
border-radius: 8px;
padding: 16px;
}}
QFrame#accountCard:hover {{
border: 2px solid {current_style["hover_border"]};
background-color: {current_style["background"]};
}}
""")
def update_status(self, new_status: str):
"""Aktualisiert den Status der Account-Karte und das Styling"""
self.account_data["status"] = new_status
self._apply_status_styling()

Datei anzeigen

@ -0,0 +1,209 @@
"""
Account Creation Modal - Spezialisiertes Modal für Account-Erstellung
"""
import logging
from typing import Optional, List
from PyQt5.QtCore import QTimer, pyqtSignal
from PyQt5.QtWidgets import QVBoxLayout, QLabel, QProgressBar
from PyQt5.QtGui import QFont
from views.widgets.progress_modal import ProgressModal
from styles.modal_styles import ModalStyles
logger = logging.getLogger("account_creation_modal")
class AccountCreationModal(ProgressModal):
"""
Spezialisiertes Modal für Account-Erstellung mit Step-by-Step Anzeige.
"""
# Signale
step_completed = pyqtSignal(str) # Step-Name
def __init__(self, parent=None, platform: str = "Social Media", language_manager=None, style_manager=None):
self.platform = platform
self.current_step = 0
self.total_steps = 0
self.steps = []
super().__init__(parent, "account_creation", language_manager, style_manager)
# Erweitere UI für Steps
self.setup_steps_ui()
def setup_steps_ui(self):
"""Erweitert die UI um Step-Anzeige"""
container_layout = self.modal_container.layout()
# Progress Bar (zwischen Animation und Status)
self.progress_bar = QProgressBar()
self.progress_bar.setVisible(False)
self.progress_bar.setFixedHeight(self.style_manager.SIZES['progress_bar_height'])
self.progress_bar.setStyleSheet(self.style_manager.get_progress_bar_style())
# Füge Progress Bar nach Animation Widget ein (Index 2)
container_layout.insertWidget(2, self.progress_bar)
# Steps Label (zwischen Progress Bar und Status)
self.steps_label = QLabel()
self.steps_label.setVisible(False)
self.steps_label.setAlignment(self.title_label.alignment())
self.steps_label.setFont(self.style_manager.create_font('steps'))
self.steps_label.setStyleSheet(self.style_manager.get_steps_label_style())
# Füge Steps Label nach Progress Bar ein (Index 3)
container_layout.insertWidget(3, self.steps_label)
def set_steps(self, steps: List[str]):
"""
Setzt die Liste der Schritte für die Account-Erstellung.
Args:
steps: Liste der Step-Namen
"""
self.steps = steps
self.total_steps = len(steps)
self.current_step = 0
if self.total_steps > 0:
self.progress_bar.setMaximum(self.total_steps)
self.progress_bar.setValue(0)
self.progress_bar.setVisible(True)
self.steps_label.setVisible(True)
self._update_steps_display()
def start_step(self, step_name: str, detail: str = None):
"""
Startet einen neuen Schritt.
Args:
step_name: Name des Schritts
detail: Optional - Detail-Information
"""
if step_name in self.steps:
self.current_step = self.steps.index(step_name) + 1
else:
self.current_step += 1
# Progress Bar aktualisieren
if self.progress_bar.isVisible():
self.progress_bar.setValue(self.current_step)
# Status aktualisieren
self.update_status(f"🔄 {step_name}", detail)
# Steps Display aktualisieren
self._update_steps_display()
logger.info(f"Account Creation Step gestartet: {step_name} ({self.current_step}/{self.total_steps})")
def complete_step(self, step_name: str, next_step: str = None):
"""
Markiert einen Schritt als abgeschlossen.
Args:
step_name: Name des abgeschlossenen Schritts
next_step: Optional - Name des nächsten Schritts
"""
# Step-completed Signal senden
self.step_completed.emit(step_name)
# Kurz "Completed" anzeigen
self.update_status(f"{step_name} abgeschlossen")
# Nach kurzer Verzögerung nächsten Schritt starten
if next_step:
QTimer.singleShot(self.style_manager.ANIMATIONS['step_delay'], lambda: self.start_step(next_step))
logger.info(f"Account Creation Step abgeschlossen: {step_name}")
def fail_step(self, step_name: str, error_message: str, retry_callback=None):
"""
Markiert einen Schritt als fehlgeschlagen.
Args:
step_name: Name des fehlgeschlagenen Schritts
error_message: Fehlermeldung
retry_callback: Optional - Callback für Retry
"""
self.update_status(f"{step_name} fehlgeschlagen", error_message)
# Animation stoppen
self.animation_widget.stop_animation()
logger.error(f"Account Creation Step fehlgeschlagen: {step_name} - {error_message}")
def show_platform_specific_process(self):
"""Zeigt plattform-spezifische Account-Erstellung an"""
# Plattform-spezifische Steps
platform_steps = self._get_platform_steps()
self.set_steps(platform_steps)
# Titel anpassen
title = f"🔄 {self.platform} Account wird erstellt"
self.title_label.setText(title)
# Modal anzeigen
self.show_process("account_creation")
def _get_platform_steps(self) -> List[str]:
"""Gibt plattform-spezifische Schritte zurück"""
return self.style_manager.get_platform_steps(self.platform)
def _update_steps_display(self):
"""Aktualisiert die Steps-Anzeige"""
if self.total_steps > 0:
steps_text = f"Schritt {self.current_step} von {self.total_steps}"
self.steps_label.setText(steps_text)
def show_success(self, account_data: dict = None):
"""
Zeigt Erfolgs-Status an.
Args:
account_data: Optional - Account-Daten für Anzeige
"""
# Animation stoppen
self.animation_widget.stop_animation()
# Success Status
platform_name = account_data.get('platform', self.platform) if account_data else self.platform
username = account_data.get('username', '') if account_data else ''
title = f"{platform_name} Account erstellt!"
status = f"Account '{username}' wurde erfolgreich erstellt" if username else "Account wurde erfolgreich erstellt"
self.title_label.setText(title)
self.update_status(status, "Das Fenster schließt automatisch...")
# Progress Bar auf Maximum setzen
if self.progress_bar.isVisible():
self.progress_bar.setValue(self.total_steps)
# Auto-Close nach konfigurierbarer Zeit
QTimer.singleShot(self.style_manager.ANIMATIONS['auto_close_delay'], self.hide_process)
logger.info(f"Account Creation erfolgreich für: {platform_name}")
def estimate_time_remaining(self) -> str:
"""
Schätzt die verbleibende Zeit basierend auf aktueller Step.
Returns:
str: Geschätzte Zeit als String
"""
if self.total_steps == 0:
return "Unbekannt"
# Grobe Zeitschätzung: ~30 Sekunden pro Step
remaining_steps = max(0, self.total_steps - self.current_step)
estimated_seconds = remaining_steps * 30
if estimated_seconds < 60:
return f"~{estimated_seconds}s"
else:
minutes = estimated_seconds // 60
return f"~{minutes} Min"

Datei anzeigen

@ -0,0 +1,625 @@
"""
Account Creation Modal V2 - Verbessertes Design mit eindringlicher Warnung
Basierend auf dem Corporate Design Styleguide
"""
import logging
from typing import Optional, List, Dict
from datetime import datetime, timedelta
from PyQt5.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QLabel,
QPushButton, QFrame, QWidget, QScrollArea,
QProgressBar, QGraphicsOpacityEffect
)
from PyQt5.QtCore import (
Qt, QTimer, pyqtSignal, QPropertyAnimation,
QEasingCurve, QRect, QSize
)
from PyQt5.QtGui import QFont, QPainter, QBrush, QColor, QPen, QPixmap
logger = logging.getLogger("account_creation_modal_v2")
class AccountCreationModalV2(QDialog):
"""
Verbessertes Modal für Account-Erstellung mit:
- Prominenter Warnung
- Besserer Step-Visualisierung
- Größerem Fenster
- Klarerer Kommunikation
"""
# Signale
cancel_clicked = pyqtSignal()
process_completed = pyqtSignal()
def __init__(self, parent=None, platform: str = "Social Media"):
super().__init__(parent)
self.platform = platform
self.current_step = 0
self.total_steps = 0
self.steps = []
self.start_time = None
self.is_process_running = False
# Timer für Updates
self.update_timer = QTimer()
self.update_timer.timeout.connect(self.update_time_display)
self.update_timer.setInterval(1000) # Jede Sekunde
# Animation Timer für Warning
self.warning_animation_timer = QTimer()
self.warning_animation_timer.timeout.connect(self.animate_warning)
self.warning_animation_timer.setInterval(100) # Smooth animation
self.warning_opacity = 1.0
self.warning_direction = -0.02
self.init_ui()
def init_ui(self):
"""Initialisiert die UI mit verbessertem Design"""
# Modal-Eigenschaften
self.setModal(True)
self.setWindowFlags(Qt.Dialog | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
self.setAttribute(Qt.WA_TranslucentBackground)
# Größeres Fenster
self.setFixedSize(650, 700)
# Zentriere auf Parent oder Bildschirm
if self.parent():
parent_rect = self.parent().geometry()
x = parent_rect.x() + (parent_rect.width() - self.width()) // 2
y = parent_rect.y() + (parent_rect.height() - self.height()) // 2
self.move(x, y)
# Hauptlayout
main_layout = QVBoxLayout(self)
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.setSpacing(0)
# Container mit weißem Hintergrund
self.container = QFrame()
self.container.setObjectName("mainContainer")
self.container.setStyleSheet("""
QFrame#mainContainer {
background-color: #FFFFFF;
border: 1px solid #E2E8F0;
border-radius: 16px;
}
""")
container_layout = QVBoxLayout(self.container)
container_layout.setContentsMargins(0, 0, 0, 0)
container_layout.setSpacing(0)
# 1. WARNING BANNER (Sehr auffällig!)
self.warning_banner = self.create_warning_banner()
container_layout.addWidget(self.warning_banner)
# Content Area mit Padding
content_widget = QWidget()
content_layout = QVBoxLayout(content_widget)
content_layout.setContentsMargins(40, 30, 40, 40)
content_layout.setSpacing(24)
# 2. Titel-Bereich
self.title_label = QLabel(f"Account wird erstellt für {self.platform}")
self.title_label.setAlignment(Qt.AlignCenter)
self.title_label.setStyleSheet("""
QLabel {
color: #1A365D;
font-size: 28px;
font-weight: 700;
font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
margin-bottom: 8px;
}
""")
content_layout.addWidget(self.title_label)
# 3. Progress-Bereich
progress_widget = self.create_progress_section()
content_layout.addWidget(progress_widget)
# 4. Steps-Liste
self.steps_widget = self.create_steps_list()
content_layout.addWidget(self.steps_widget)
# 5. Live-Status
self.status_widget = self.create_status_section()
content_layout.addWidget(self.status_widget)
# 6. Info-Box
info_box = self.create_info_box()
content_layout.addWidget(info_box)
# Spacer
content_layout.addStretch()
# Container zum Hauptlayout hinzufügen
container_layout.addWidget(content_widget)
main_layout.addWidget(self.container)
def create_warning_banner(self) -> QFrame:
"""Erstellt den auffälligen Warning Banner"""
banner = QFrame()
banner.setObjectName("warningBanner")
banner.setFixedHeight(100)
banner.setStyleSheet("""
QFrame#warningBanner {
background-color: #DC2626;
border-top-left-radius: 16px;
border-top-right-radius: 16px;
border-bottom: none;
}
""")
layout = QVBoxLayout(banner)
layout.setContentsMargins(40, 20, 40, 20)
layout.setSpacing(8)
# Warning Icon + Haupttext
main_warning = QLabel("⚠️ NICHT DEN BROWSER BERÜHREN!")
main_warning.setAlignment(Qt.AlignCenter)
main_warning.setStyleSheet("""
QLabel {
color: #FFFFFF;
font-size: 24px;
font-weight: 700;
font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
letter-spacing: 1px;
}
""")
# Untertitel
subtitle = QLabel("Die Automatisierung läuft. Jede Interaktion kann den Prozess unterbrechen.")
subtitle.setAlignment(Qt.AlignCenter)
subtitle.setWordWrap(True)
subtitle.setStyleSheet("""
QLabel {
color: rgba(255, 255, 255, 0.9);
font-size: 14px;
font-weight: 400;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
""")
layout.addWidget(main_warning)
layout.addWidget(subtitle)
return banner
def create_progress_section(self) -> QWidget:
"""Erstellt den Progress-Bereich"""
widget = QWidget()
layout = QHBoxLayout(widget)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(24)
# Progress Bar
progress_container = QWidget()
progress_layout = QVBoxLayout(progress_container)
progress_layout.setContentsMargins(0, 0, 0, 0)
progress_layout.setSpacing(8)
self.progress_bar = QProgressBar()
self.progress_bar.setFixedHeight(12)
self.progress_bar.setStyleSheet("""
QProgressBar {
background-color: #E2E8F0;
border: none;
border-radius: 6px;
text-align: center;
}
QProgressBar::chunk {
background-color: #3182CE;
border-radius: 6px;
}
""")
self.progress_label = QLabel("Schritt 0 von 0")
self.progress_label.setStyleSheet("""
QLabel {
color: #4A5568;
font-size: 14px;
font-weight: 500;
}
""")
progress_layout.addWidget(self.progress_bar)
progress_layout.addWidget(self.progress_label)
# Zeit-Anzeige
time_container = QWidget()
time_container.setFixedWidth(180)
time_layout = QVBoxLayout(time_container)
time_layout.setContentsMargins(0, 0, 0, 0)
time_layout.setSpacing(4)
self.time_label = QLabel("~2 Min verbleibend")
self.time_label.setAlignment(Qt.AlignRight)
self.time_label.setStyleSheet("""
QLabel {
color: #1A365D;
font-size: 16px;
font-weight: 600;
font-family: 'Poppins', -apple-system, sans-serif;
}
""")
self.elapsed_label = QLabel("Verstrichene Zeit: 0:00")
self.elapsed_label.setAlignment(Qt.AlignRight)
self.elapsed_label.setStyleSheet("""
QLabel {
color: #718096;
font-size: 12px;
font-weight: 400;
}
""")
time_layout.addWidget(self.time_label)
time_layout.addWidget(self.elapsed_label)
layout.addWidget(progress_container, 1)
layout.addWidget(time_container)
return widget
def create_steps_list(self) -> QWidget:
"""Erstellt die visuelle Steps-Liste"""
container = QFrame()
container.setStyleSheet("""
QFrame {
background-color: #F8FAFC;
border: 1px solid #E2E8F0;
border-radius: 12px;
padding: 20px;
}
""")
self.steps_layout = QVBoxLayout(container)
self.steps_layout.setSpacing(12)
# Wird dynamisch befüllt
return container
def create_status_section(self) -> QWidget:
"""Erstellt den Live-Status Bereich"""
container = QFrame()
container.setStyleSheet("""
QFrame {
background-color: #1A365D;
border-radius: 12px;
padding: 20px;
min-height: 80px;
}
""")
layout = QVBoxLayout(container)
layout.setSpacing(8)
status_title = QLabel("Aktueller Status:")
status_title.setStyleSheet("""
QLabel {
color: rgba(255, 255, 255, 0.7);
font-size: 12px;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 1px;
}
""")
self.current_status_label = QLabel("Initialisiere Browser...")
self.current_status_label.setWordWrap(True)
self.current_status_label.setStyleSheet("""
QLabel {
color: #FFFFFF;
font-size: 16px;
font-weight: 400;
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
line-height: 1.5;
}
""")
layout.addWidget(status_title)
layout.addWidget(self.current_status_label)
return container
def create_info_box(self) -> QWidget:
"""Erstellt die Info-Box"""
container = QFrame()
container.setStyleSheet("""
QFrame {
background-color: #DBEAFE;
border: 1px solid #2563EB;
border-radius: 12px;
padding: 16px;
}
""")
layout = QHBoxLayout(container)
layout.setSpacing(16)
# Info Icon
icon_label = QLabel("")
icon_label.setStyleSheet("""
QLabel {
font-size: 24px;
color: #2563EB;
}
""")
# Info Text
info_layout = QVBoxLayout()
info_layout.setSpacing(4)
info_title = QLabel("Was passiert gerade?")
info_title.setStyleSheet("""
QLabel {
color: #1E40AF;
font-size: 14px;
font-weight: 600;
}
""")
self.info_text = QLabel("Der Browser simuliert menschliches Verhalten beim Ausfüllen des Formulars.")
self.info_text.setWordWrap(True)
self.info_text.setStyleSheet("""
QLabel {
color: #1E40AF;
font-size: 13px;
font-weight: 400;
line-height: 1.4;
}
""")
info_layout.addWidget(info_title)
info_layout.addWidget(self.info_text)
layout.addWidget(icon_label)
layout.addWidget(info_layout, 1)
return container
def set_steps(self, steps: List[str]):
"""Setzt die Steps und erstellt die visuelle Liste"""
self.steps = steps
self.total_steps = len(steps)
self.current_step = 0
# Progress Bar Setup
self.progress_bar.setMaximum(self.total_steps)
self.progress_bar.setValue(0)
# Clear existing steps
for i in reversed(range(self.steps_layout.count())):
self.steps_layout.itemAt(i).widget().setParent(None)
# Create step items
self.step_widgets = []
for i, step in enumerate(steps):
step_widget = self.create_step_item(i, step)
self.steps_layout.addWidget(step_widget)
self.step_widgets.append(step_widget)
def create_step_item(self, index: int, text: str) -> QWidget:
"""Erstellt ein einzelnes Step-Item"""
widget = QWidget()
layout = QHBoxLayout(widget)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(12)
# Status Icon
icon_label = QLabel()
icon_label.setFixedSize(24, 24)
icon_label.setAlignment(Qt.AlignCenter)
icon_label.setObjectName(f"stepIcon_{index}")
# Text
text_label = QLabel(text)
text_label.setObjectName(f"stepText_{index}")
# Initial state (pending)
self.update_step_appearance(widget, 'pending')
layout.addWidget(icon_label)
layout.addWidget(text_label, 1)
return widget
def update_step_appearance(self, widget: QWidget, status: str):
"""Aktualisiert das Aussehen eines Steps basierend auf Status"""
icon_label = widget.findChild(QLabel, QRegExp("stepIcon_*"))
text_label = widget.findChild(QLabel, QRegExp("stepText_*"))
if status == 'completed':
icon_label.setText("")
text_label.setStyleSheet("""
QLabel {
color: #059669;
font-size: 14px;
font-weight: 500;
}
""")
elif status == 'active':
icon_label.setText("🔄")
text_label.setStyleSheet("""
QLabel {
color: #2563EB;
font-size: 14px;
font-weight: 600;
}
""")
# Rotation animation würde hier hinzugefügt
else: # pending
icon_label.setText("")
text_label.setStyleSheet("""
QLabel {
color: #718096;
font-size: 14px;
font-weight: 400;
}
""")
def start_process(self):
"""Startet den Account-Erstellungsprozess"""
self.is_process_running = True
self.start_time = datetime.now()
self.update_timer.start()
self.warning_animation_timer.start()
self.show()
def next_step(self, status_text: str = None, info_text: str = None):
"""Geht zum nächsten Schritt"""
if self.current_step < self.total_steps:
# Aktuellen Step als aktiv markieren
if self.current_step > 0:
self.update_step_appearance(self.step_widgets[self.current_step - 1], 'completed')
if self.current_step < len(self.step_widgets):
self.update_step_appearance(self.step_widgets[self.current_step], 'active')
self.current_step += 1
self.progress_bar.setValue(self.current_step)
self.progress_label.setText(f"Schritt {self.current_step} von {self.total_steps}")
# Update status
if status_text:
self.current_status_label.setText(status_text)
# Update info
if info_text:
self.info_text.setText(info_text)
# Update time estimate
self.update_time_estimate()
def update_time_estimate(self):
"""Aktualisiert die Zeitschätzung"""
if self.total_steps > 0 and self.current_step > 0:
elapsed = (datetime.now() - self.start_time).total_seconds()
avg_time_per_step = elapsed / self.current_step
remaining_steps = self.total_steps - self.current_step
estimated_remaining = remaining_steps * avg_time_per_step
if estimated_remaining < 60:
self.time_label.setText(f"~{int(estimated_remaining)}s verbleibend")
else:
minutes = int(estimated_remaining / 60)
self.time_label.setText(f"~{minutes} Min verbleibend")
def update_time_display(self):
"""Aktualisiert die verstrichene Zeit"""
if self.start_time:
elapsed = datetime.now() - self.start_time
minutes = int(elapsed.total_seconds() / 60)
seconds = int(elapsed.total_seconds() % 60)
self.elapsed_label.setText(f"Verstrichene Zeit: {minutes}:{seconds:02d}")
def animate_warning(self):
"""Animiert den Warning Banner (Pulseffekt)"""
self.warning_opacity += self.warning_direction
if self.warning_opacity <= 0.7:
self.warning_opacity = 0.7
self.warning_direction = 0.02
elif self.warning_opacity >= 1.0:
self.warning_opacity = 1.0
self.warning_direction = -0.02
# Opacity-Effekt anwenden
effect = QGraphicsOpacityEffect()
effect.setOpacity(self.warning_opacity)
self.warning_banner.setGraphicsEffect(effect)
def set_status(self, status: str, info: str = None):
"""Setzt den aktuellen Status"""
self.current_status_label.setText(status)
if info:
self.info_text.setText(info)
def complete_process(self):
"""Beendet den Prozess erfolgreich"""
self.is_process_running = False
self.update_timer.stop()
self.warning_animation_timer.stop()
# Letzten Step als completed markieren
if self.current_step > 0 and self.current_step <= len(self.step_widgets):
self.update_step_appearance(self.step_widgets[self.current_step - 1], 'completed')
# Success message
self.current_status_label.setText("✅ Account erfolgreich erstellt!")
self.info_text.setText("Der Prozess wurde erfolgreich abgeschlossen. Das Fenster schließt sich in wenigen Sekunden.")
# Auto-close nach 3 Sekunden
QTimer.singleShot(3000, self.close)
def paintEvent(self, event):
"""Custom paint event für Transparenz"""
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
# Semi-transparenter Hintergrund
painter.fillRect(self.rect(), QColor(0, 0, 0, 120))
def keyPressEvent(self, event):
"""Verhindert das Schließen mit ESC"""
if event.key() == Qt.Key_Escape:
event.ignore()
else:
super().keyPressEvent(event)
def closeEvent(self, event):
"""Verhindert das Schließen während der Prozess läuft"""
if self.is_process_running:
event.ignore()
else:
self.update_timer.stop()
self.warning_animation_timer.stop()
super().closeEvent(event)
# Beispiel-Verwendung
if __name__ == "__main__":
from PyQt5.QtWidgets import QApplication, QMainWindow
import sys
app = QApplication(sys.argv)
# Test window
main_window = QMainWindow()
main_window.setGeometry(100, 100, 800, 600)
main_window.show()
# Create and show modal
modal = AccountCreationModalV2(main_window, "Instagram")
modal.set_steps([
"Browser vorbereiten",
"Instagram-Seite laden",
"Registrierungsformular öffnen",
"Benutzerdaten eingeben",
"Account erstellen",
"E-Mail-Verifizierung",
"Profil einrichten"
])
# Simulate process
modal.start_process()
# Simulate step progression
def next_step():
modal.next_step(
f"Führe Schritt {modal.current_step + 1} aus...",
"Der Browser füllt automatisch die erforderlichen Felder aus."
)
if modal.current_step < modal.total_steps:
QTimer.singleShot(2000, next_step)
else:
modal.complete_process()
QTimer.singleShot(1000, next_step)
sys.exit(app.exec_())

Datei anzeigen

@ -0,0 +1,272 @@
"""
Forge Animation Widget - Verbesserter Dialog für Account-Erstellung mit prominenter Warnung
"""
from PyQt5.QtWidgets import QDialog, QWidget, QVBoxLayout, QLabel, QTextEdit, QPushButton, QHBoxLayout, QFrame
from PyQt5.QtCore import Qt, pyqtSignal, QTimer
from PyQt5.QtGui import QFont, QMovie, QPixmap
class ForgeAnimationDialog(QDialog):
"""Modal-Dialog für die Account-Erstellung mit verbessertem Design"""
# Signal wenn Abbrechen geklickt wird
cancel_clicked = pyqtSignal()
# Signal wenn Dialog geschlossen wird
closed = pyqtSignal()
def __init__(self, parent=None, platform_name="", is_login=False):
super().__init__(parent)
self.platform_name = platform_name
self.is_login = is_login
self.init_ui()
# Timer für das regelmäßige Nach-vorne-Holen
self.raise_timer = QTimer()
self.raise_timer.timeout.connect(self._raise_to_front)
self.raise_timer.setInterval(500) # Alle 500ms
def init_ui(self):
"""Initialisiert die UI mit verbessertem Design"""
# Nur Dialog im Vordergrund, nicht das ganze Hauptfenster
self.setWindowFlags(Qt.Dialog | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
self.setModal(False) # Nicht modal - blockiert nicht das Hauptfenster
self.setFixedSize(650, 600) # Ursprüngliche Größe beibehalten
# Styling für Light Theme
self.setStyleSheet("""
ForgeAnimationDialog {
background-color: #FFFFFF;
border: 1px solid #E2E8F0;
border-radius: 8px;
}
""")
# Hauptlayout
layout = QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
# NEUE WARNUNG OBEN - Sehr auffällig!
warning_banner = QFrame()
warning_banner.setFixedHeight(120) # Angepasste Höhe
warning_banner.setStyleSheet("""
QFrame {
background-color: #DC2626;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
}
""")
warning_layout = QVBoxLayout(warning_banner)
warning_layout.setContentsMargins(10, 10, 10, 10) # Reduzierte Margins
warning_layout.setSpacing(5) # Weniger Abstand zwischen den Elementen
# Großer Warning Text
warning_text = QLabel("⚠️ BROWSER NICHT BERÜHREN!")
warning_text.setAlignment(Qt.AlignCenter)
warning_text.setFixedHeight(40) # Feste Höhe für das Label
warning_text.setStyleSheet("""
QLabel {
color: #FFFFFF;
font-size: 22px;
font-weight: 700;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
padding: 0px;
margin: 0px;
}
""")
warning_subtext = QLabel("Jede Interaktion kann den Prozess unterbrechen")
warning_subtext.setAlignment(Qt.AlignCenter)
warning_subtext.setFixedHeight(25) # Feste Höhe
warning_subtext.setStyleSheet("""
QLabel {
color: rgba(255, 255, 255, 0.9);
font-size: 13px;
font-weight: 400;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
padding: 0px;
margin: 0px;
}
""")
warning_layout.addWidget(warning_text)
warning_layout.addWidget(warning_subtext)
layout.addWidget(warning_banner)
# Content Container
content_container = QWidget()
content_layout = QVBoxLayout(content_container)
content_layout.setContentsMargins(30, 25, 30, 30)
content_layout.setSpacing(20)
# Titel mit Plattform-Name
platform_display = self.platform_name if self.platform_name else "Account"
if self.is_login:
title_label = QLabel(f"{platform_display}-Login läuft")
else:
title_label = QLabel(f"{platform_display}-Account wird erstellt")
title_label.setObjectName("titleLabel")
title_label.setAlignment(Qt.AlignCenter)
title_label.setStyleSheet("""
QLabel#titleLabel {
color: #1A365D;
font-size: 26px;
font-weight: 600;
font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
padding-bottom: 20px;
border: none;
}
""")
content_layout.addWidget(title_label)
# Verstecktes Status-Label für Kompatibilität
self.status_label = QLabel()
self.status_label.setVisible(False) # Nicht sichtbar
content_layout.addWidget(self.status_label)
# Log-Ausgabe (größer da mehr Platz vorhanden)
self.log_output = QTextEdit()
self.log_output.setReadOnly(True)
self.log_output.setMinimumHeight(200) # Mehr Platz für Logs
self.log_output.setStyleSheet("""
QTextEdit {
background-color: #F8FAFC;
color: #2D3748;
font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', monospace;
font-size: 12px;
border: 1px solid #CBD5E0;
border-radius: 8px;
padding: 12px;
}
""")
content_layout.addWidget(self.log_output)
# Button-Container
button_layout = QHBoxLayout()
button_layout.addStretch()
# Abbrechen-Button (weniger prominent)
self.cancel_button = QPushButton("Abbrechen")
self.cancel_button.setStyleSheet("""
QPushButton {
background-color: #F0F4F8;
color: #4A5568;
font-size: 14px;
font-weight: 500;
padding: 8px 24px;
border-radius: 24px;
border: 1px solid #E2E8F0;
min-height: 40px;
}
QPushButton:hover {
background-color: #FEE2E2;
color: #DC2626;
border-color: #DC2626;
}
QPushButton:pressed {
background-color: #DC2626;
color: #FFFFFF;
}
""")
self.cancel_button.clicked.connect(self.cancel_clicked.emit)
button_layout.addWidget(self.cancel_button)
button_layout.addStretch()
content_layout.addLayout(button_layout)
layout.addWidget(content_container)
self.setLayout(layout)
# Volle Sichtbarkeit
self.setWindowOpacity(1.0)
def start_animation(self):
"""Zeigt den Dialog an"""
self.status_label.setText("Initialisiere...")
self.raise_timer.start() # Starte Timer für Always-on-Top
def stop_animation(self):
"""Stoppt die Animation und den Timer"""
self.raise_timer.stop()
def set_status(self, status: str):
"""Aktualisiert den Status-Text"""
self.status_label.setText(status)
def add_log(self, message: str):
"""Fügt eine Log-Nachricht hinzu"""
self.log_output.append(message)
# Auto-scroll zum Ende
scrollbar = self.log_output.verticalScrollBar()
scrollbar.setValue(scrollbar.maximum())
def clear_log(self):
"""Löscht alle Log-Nachrichten"""
self.log_output.clear()
def set_progress(self, value: int):
"""Setzt den Fortschritt (0-100) - wird ignoriert da wir Spinner nutzen"""
pass # Spinner braucht keinen Fortschritt
def closeEvent(self, event):
"""Wird aufgerufen wenn der Dialog geschlossen wird"""
self.stop_animation()
self.closed.emit()
event.accept()
def keyPressEvent(self, event):
"""Verhindert das Schließen mit ESC"""
if event.key() == Qt.Key_Escape:
event.ignore()
else:
super().keyPressEvent(event)
def _raise_to_front(self):
"""Holt den Dialog in den Vordergrund"""
self.raise_()
# Nicht activateWindow() aufrufen - das holt das Hauptfenster mit
def show(self):
"""Überschreibt show() um den Dialog richtig zu positionieren"""
super().show()
self._raise_to_front() # Initial in den Vordergrund holen
# Zusätzliche Widget-Klasse für den Progress Modal
class ForgeAnimationWidget(QLabel):
"""
Einfaches Animation Widget für den Progress Modal
Kann einen Spinner oder andere Animation anzeigen
"""
def __init__(self):
super().__init__()
self.setText("⚙️") # Placeholder Icon
self.setAlignment(Qt.AlignCenter)
self.setStyleSheet("""
QLabel {
font-size: 48px;
color: #3182CE;
}
""")
# Animation Timer
self.animation_timer = QTimer()
self.animation_timer.timeout.connect(self.rotate_icon)
self.rotation_state = 0
def start_animation(self):
"""Startet die Animation"""
self.animation_timer.start(100)
def stop_animation(self):
"""Stoppt die Animation"""
self.animation_timer.stop()
def rotate_icon(self):
"""Einfache Rotation Animation"""
icons = ["⚙️", "🔧", "🔨", "⚒️"]
self.setText(icons[self.rotation_state % len(icons)])
self.rotation_state += 1

Datei anzeigen

@ -0,0 +1,370 @@
"""
Forge Animation Widget V2 - Verbessertes Design mit prominenter Warnung
Angepasst an den bestehenden Code mit minimalen Änderungen
"""
from PyQt5.QtWidgets import QDialog, QWidget, QVBoxLayout, QLabel, QTextEdit, QPushButton, QHBoxLayout, QFrame
from PyQt5.QtCore import Qt, pyqtSignal, QTimer
from PyQt5.QtGui import QFont, QMovie, QPixmap
class ForgeAnimationDialog(QDialog):
"""Modal-Dialog für die Account-Erstellung mit verbessertem Design"""
# Signal wenn Abbrechen geklickt wird
cancel_clicked = pyqtSignal()
# Signal wenn Dialog geschlossen wird
closed = pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
self.init_ui()
# Timer für das regelmäßige Nach-vorne-Holen
self.raise_timer = QTimer()
self.raise_timer.timeout.connect(self._raise_to_front)
self.raise_timer.setInterval(500) # Alle 500ms
def init_ui(self):
"""Initialisiert die UI mit verbessertem Design"""
# Nur Dialog im Vordergrund, nicht das ganze Hauptfenster
self.setWindowFlags(Qt.Dialog | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
self.setModal(False) # Nicht modal - blockiert nicht das Hauptfenster
self.setFixedSize(650, 600) # Größer für bessere Sichtbarkeit
# Styling für Light Theme
self.setStyleSheet("""
ForgeAnimationDialog {
background-color: #FFFFFF;
border: 1px solid #E2E8F0;
border-radius: 8px;
}
""")
# Hauptlayout
layout = QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
# NEUE WARNUNG OBEN - Sehr auffällig!
warning_banner = QFrame()
warning_banner.setFixedHeight(80)
warning_banner.setStyleSheet("""
QFrame {
background-color: #DC2626;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
padding: 15px 30px;
}
""")
warning_layout = QVBoxLayout(warning_banner)
warning_layout.setContentsMargins(0, 10, 0, 10)
# Großer Warning Text
warning_text = QLabel("⚠️ BROWSER NICHT BERÜHREN!")
warning_text.setAlignment(Qt.AlignCenter)
warning_text.setStyleSheet("""
QLabel {
color: #FFFFFF;
font-size: 22px;
font-weight: 700;
font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
letter-spacing: 1px;
}
""")
warning_subtext = QLabel("Jede Interaktion kann den Prozess unterbrechen")
warning_subtext.setAlignment(Qt.AlignCenter)
warning_subtext.setStyleSheet("""
QLabel {
color: rgba(255, 255, 255, 0.9);
font-size: 13px;
font-weight: 400;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
""")
warning_layout.addWidget(warning_text)
warning_layout.addWidget(warning_subtext)
layout.addWidget(warning_banner)
# Content Container
content_container = QWidget()
content_layout = QVBoxLayout(content_container)
content_layout.setContentsMargins(30, 25, 30, 30)
content_layout.setSpacing(20)
# Titel (etwas größer)
title_label = QLabel("Account wird erstellt")
title_label.setObjectName("titleLabel")
title_label.setAlignment(Qt.AlignCenter)
title_label.setStyleSheet("""
QLabel#titleLabel {
color: #1A365D;
font-size: 26px;
font-weight: 600;
font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
padding-bottom: 10px;
border: none;
}
""")
content_layout.addWidget(title_label)
# Moderne Info-Karte (angepasst)
info_frame = QFrame()
info_frame.setStyleSheet("""
QFrame {
background: #DBEAFE;
border: 1px solid #2563EB;
border-radius: 12px;
min-height: 100px;
}
""")
info_layout = QHBoxLayout(info_frame)
info_layout.setContentsMargins(20, 15, 20, 15)
info_layout.setSpacing(15)
# Icon Container
icon_container = QFrame()
icon_container.setFixedSize(50, 50)
icon_container.setStyleSheet("""
QFrame {
background: #2563EB;
border-radius: 25px;
}
""")
icon_layout = QVBoxLayout(icon_container)
icon_layout.setContentsMargins(0, 0, 0, 0)
icon_label = QLabel("🤖")
icon_label.setAlignment(Qt.AlignCenter)
icon_label.setStyleSheet("""
QLabel {
font-size: 24px;
background: transparent;
border: none;
color: white;
}
""")
icon_layout.addWidget(icon_label)
# Text Container
text_container = QFrame()
text_layout = QVBoxLayout(text_container)
text_layout.setContentsMargins(0, 0, 0, 0)
text_layout.setSpacing(5)
# Titel
info_title = QLabel("Automatisierung läuft")
info_title.setObjectName("infoTitle")
info_title.setStyleSheet("""
QLabel#infoTitle {
color: #1E40AF;
font-size: 17px;
font-weight: 600;
font-family: 'Segoe UI', -apple-system, sans-serif;
background-color: transparent;
border: none;
}
""")
# Beschreibung (deutlicher)
info_desc = QLabel("Der Browser arbeitet automatisch. Bitte warten Sie, bis der Vorgang abgeschlossen ist.")
info_desc.setObjectName("infoDesc")
info_desc.setWordWrap(True)
info_desc.setStyleSheet("""
QLabel#infoDesc {
color: #1E40AF;
font-size: 14px;
font-weight: 500;
font-family: 'Segoe UI', -apple-system, sans-serif;
background-color: transparent;
border: none;
line-height: 20px;
}
""")
text_layout.addWidget(info_title)
text_layout.addWidget(info_desc)
text_layout.addStretch()
info_layout.addWidget(icon_container)
info_layout.addWidget(text_container, 1)
content_layout.addWidget(info_frame)
# Status Container
status_container = QFrame()
status_container.setStyleSheet("""
QFrame {
background-color: #1A365D;
border-radius: 8px;
padding: 15px;
}
""")
status_layout = QVBoxLayout(status_container)
# Status-Label (größer)
self.status_label = QLabel("Initialisiere...")
self.status_label.setObjectName("statusLabel")
self.status_label.setAlignment(Qt.AlignLeft)
self.status_label.setStyleSheet("""
QLabel#statusLabel {
color: #FFFFFF;
font-size: 16px;
padding: 5px;
font-weight: 500;
border: none;
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
}
""")
status_layout.addWidget(self.status_label)
content_layout.addWidget(status_container)
# Log-Ausgabe (größer)
self.log_output = QTextEdit()
self.log_output.setReadOnly(True)
self.log_output.setMaximumHeight(180) # Etwas größer
self.log_output.setStyleSheet("""
QTextEdit {
background-color: #F8FAFC;
color: #2D3748;
font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', monospace;
font-size: 12px;
border: 1px solid #CBD5E0;
border-radius: 8px;
padding: 12px;
}
""")
content_layout.addWidget(self.log_output)
# Button-Container
button_layout = QHBoxLayout()
button_layout.addStretch()
# Abbrechen-Button (weniger prominent)
self.cancel_button = QPushButton("Abbrechen")
self.cancel_button.setStyleSheet("""
QPushButton {
background-color: #F0F4F8;
color: #4A5568;
font-size: 14px;
font-weight: 500;
padding: 8px 24px;
border-radius: 24px;
border: 1px solid #E2E8F0;
min-height: 40px;
}
QPushButton:hover {
background-color: #FEE2E2;
color: #DC2626;
border-color: #DC2626;
}
QPushButton:pressed {
background-color: #DC2626;
color: #FFFFFF;
}
""")
self.cancel_button.clicked.connect(self.cancel_clicked.emit)
button_layout.addWidget(self.cancel_button)
button_layout.addStretch()
content_layout.addLayout(button_layout)
layout.addWidget(content_container)
self.setLayout(layout)
# Volle Sichtbarkeit
self.setWindowOpacity(1.0)
def start_animation(self):
"""Zeigt den Dialog an"""
self.status_label.setText("Initialisiere...")
self.raise_timer.start() # Starte Timer für Always-on-Top
def stop_animation(self):
"""Stoppt die Animation und den Timer"""
self.raise_timer.stop()
def set_status(self, status: str):
"""Aktualisiert den Status-Text"""
self.status_label.setText(status)
def add_log(self, message: str):
"""Fügt eine Log-Nachricht hinzu"""
self.log_output.append(message)
# Auto-scroll zum Ende
scrollbar = self.log_output.verticalScrollBar()
scrollbar.setValue(scrollbar.maximum())
def clear_log(self):
"""Löscht alle Log-Nachrichten"""
self.log_output.clear()
def set_progress(self, value: int):
"""Setzt den Fortschritt (0-100) - wird ignoriert da wir Spinner nutzen"""
pass
def closeEvent(self, event):
"""Wird aufgerufen wenn der Dialog geschlossen wird"""
self.stop_animation()
self.closed.emit()
event.accept()
def keyPressEvent(self, event):
"""Verhindert das Schließen mit ESC"""
if event.key() == Qt.Key_Escape:
event.ignore()
else:
super().keyPressEvent(event)
def _raise_to_front(self):
"""Holt den Dialog in den Vordergrund"""
self.raise_()
def show(self):
"""Überschreibt show() um den Dialog richtig zu positionieren"""
super().show()
self._raise_to_front() # Initial in den Vordergrund holen
# Zusätzliche Widget-Klasse für den AccountForger
class ForgeAnimationWidget(QLabel):
"""
Einfaches Animation Widget für den Progress Modal
Kann einen Spinner oder andere Animation anzeigen
"""
def __init__(self):
super().__init__()
self.setText("⚙️") # Placeholder Icon
self.setAlignment(Qt.AlignCenter)
self.setStyleSheet("""
QLabel {
font-size: 48px;
color: #3182CE;
}
""")
# Animation Timer
self.animation_timer = QTimer()
self.animation_timer.timeout.connect(self.rotate_icon)
self.rotation_state = 0
def start_animation(self):
"""Startet die Animation"""
self.animation_timer.start(100)
def stop_animation(self):
"""Stoppt die Animation"""
self.animation_timer.stop()
def rotate_icon(self):
"""Einfache Rotation Animation"""
icons = ["⚙️", "🔧", "🔨", "⚒️"]
self.setText(icons[self.rotation_state % len(icons)])
self.rotation_state += 1

192
views/widgets/icon_factory.py Normale Datei
Datei anzeigen

@ -0,0 +1,192 @@
"""
Icon Factory - Zentrale Icon-Verwaltung nach Clean Architecture
"""
from PyQt5.QtWidgets import QLabel
from PyQt5.QtCore import Qt, QByteArray
from PyQt5.QtGui import QIcon, QPixmap, QPainter, QColor
from PyQt5.QtSvg import QSvgRenderer
import os
import logging
from config.paths import PathConfig
logger = logging.getLogger("icon_factory")
class IconFactory:
"""Factory für die Erstellung und Verwaltung von Icons"""
# Cache für geladene Icons
_icon_cache = {}
# Standard SVG Icons
ICONS = {
"mail": '''<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 8L8.44992 11.6333C9.73295 12.4886 10.3745 12.9163 11.0678 13.0825C11.6806 13.2293 12.3194 13.2293 12.9322 13.0825C13.6255 12.9163 14.2671 12.4886 15.5501 11.6333L21 8M6.2 19H17.8C18.9201 19 19.4802 19 19.908 18.782C20.2843 18.5903 20.5903 18.2843 20.782 17.908C21 17.4802 21 16.9201 21 15.8V8.2C21 7.0799 21 6.51984 20.782 6.09202C20.5903 5.71569 20.2843 5.40973 19.908 5.21799C19.4802 5 18.9201 5 17.8 5H6.2C5.0799 5 4.51984 5 4.09202 5.21799C3.71569 5.40973 3.40973 5.71569 3.21799 6.09202C3 6.51984 3 7.07989 3 8.2V15.8C3 16.9201 3 17.4802 3.21799 17.908C3.40973 18.2843 3.71569 18.5903 4.09202 18.782C4.51984 19 5.07989 19 6.2 19Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>''',
"key": '''<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.3212 10.6852L4 19L6 21M7 16L9 18M20 7.5C20 9.98528 17.9853 12 15.5 12C13.0147 12 11 9.98528 11 7.5C11 5.01472 13.0147 3 15.5 3C17.9853 3 20 5.01472 20 7.5Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>''',
"calendar": '''<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 10H21M7 3V5M17 3V5M6.2 21H17.8C18.9201 21 19.4802 21 19.908 20.782C20.2843 20.5903 20.5903 20.2843 20.782 19.908C21 19.4802 21 18.9201 21 17.8V8.2C21 7.07989 21 6.51984 20.782 6.09202C20.5903 5.71569 20.2843 5.40973 19.908 5.21799C19.4802 5 18.9201 5 17.8 5H6.2C5.0799 5 4.51984 5 4.09202 5.21799C3.71569 5.40973 3.40973 5.71569 3.21799 6.09202C3 6.51984 3 7.07989 3 8.2V17.8C3 18.9201 3 19.4802 3.21799 19.908C3.40973 20.2843 3.71569 20.5903 4.09202 20.782C4.51984 21 5.07989 21 6.2 21Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>''',
"copy": '''<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 8H7.2C6.0799 8 5.51984 8 5.09202 8.21799C4.71569 8.40973 4.40973 8.71569 4.21799 9.09202C4 9.51984 4 10.0799 4 11.2V16.8C4 17.9201 4 18.4802 4.21799 18.908C4.40973 19.2843 4.71569 19.5903 5.09202 19.782C5.51984 20 6.0799 20 7.2 20H12.8C13.9201 20 14.4802 20 14.908 19.782C15.2843 19.5903 15.5903 19.2843 15.782 18.908C16 18.4802 16 17.9201 16 16.8V16M11.2 16H16.8C17.9201 16 18.4802 16 18.908 15.782C19.2843 15.5903 19.5903 15.2843 19.782 14.908C20 14.4802 20 13.9201 20 12.8V7.2C20 6.0799 20 5.51984 19.782 5.09202C19.5903 4.71569 19.2843 4.40973 18.908 4.21799C18.4802 4 17.9201 4 16.8 4H11.2C10.0799 4 9.51984 4 9.09202 4.21799C8.71569 4.40973 8.40973 4.71569 8.21799 5.09202C8 5.51984 8 6.07989 8 7.2V12.8C8 13.9201 8 14.4802 8.21799 14.908C8.40973 15.2843 8.71569 15.5903 9.09202 15.782C9.51984 16 10.0799 16 11.2 16Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>''',
"eye": '''<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.0007 12C15.0007 13.6569 13.6576 15 12.0007 15C10.3439 15 9.00073 13.6569 9.00073 12C9.00073 10.3431 10.3439 9 12.0007 9C13.6576 9 15.0007 10.3431 15.0007 12Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.0012 5C7.52354 5 3.73326 7.94288 2.45898 12C3.73324 16.0571 7.52354 19 12.0012 19C16.4788 19 20.2691 16.0571 21.5434 12C20.2691 7.94291 16.4788 5 12.0012 5Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>''',
"eye-slash": '''<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.99902 3L20.999 21M9.8433 9.91364C9.32066 10.4536 8.99902 11.1892 8.99902 12C8.99902 13.6569 10.3422 15 11.999 15C12.8215 15 13.5667 14.669 14.1086 14.133M6.49902 6.64715C4.59972 7.90034 3.15305 9.78394 2.45703 12C3.73128 16.0571 7.52159 19 11.9992 19C13.9881 19 15.8414 18.4194 17.3988 17.4184M10.999 5.04939C11.328 5.01673 11.6617 5 11.9992 5C16.4769 5 20.2672 7.94291 21.5414 12C21.2607 12.894 20.8577 13.7338 20.3522 14.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>'''
}
@classmethod
def get_icon(cls, icon_name: str, size: int = 16, color: str = "#718096") -> QIcon:
"""
Erstellt ein QIcon aus SVG
Args:
icon_name: Name des Icons
size: Größe des Icons
color: Farbe des Icons (hex)
Returns:
QIcon Objekt
"""
cache_key = f"{icon_name}_{size}_{color}"
if cache_key in cls._icon_cache:
return cls._icon_cache[cache_key]
# Erstelle Pixmap
pixmap = cls.get_pixmap(icon_name, size, color)
icon = QIcon(pixmap)
# Cache das Icon
cls._icon_cache[cache_key] = icon
return icon
@classmethod
def get_pixmap(cls, icon_name: str, size: int = 16, color: str = "#718096") -> QPixmap:
"""
Erstellt ein QPixmap aus SVG
Args:
icon_name: Name des Icons
size: Größe des Icons
color: Farbe des Icons (hex)
Returns:
QPixmap Objekt
"""
# Versuche zuerst aus Datei zu laden
icon_path = PathConfig.get_icon_path(icon_name)
if PathConfig.file_exists(icon_path):
pixmap = QPixmap(icon_path)
if not pixmap.isNull():
return pixmap.scaled(size, size, Qt.KeepAspectRatio, Qt.SmoothTransformation)
# Fallback auf eingebettete SVGs
svg_content = cls.ICONS.get(icon_name)
if svg_content:
# Ersetze currentColor durch die angegebene Farbe
svg_content = svg_content.replace('currentColor', color)
# Erstelle SVG Renderer
renderer = QSvgRenderer(QByteArray(svg_content.encode()))
# Erstelle Pixmap
pixmap = QPixmap(size, size)
pixmap.fill(Qt.transparent)
# Rendere SVG
painter = QPainter(pixmap)
painter.setRenderHint(QPainter.Antialiasing)
renderer.render(painter)
painter.end()
return pixmap
# Fallback: Erstelle einen farbigen Kreis
logger.warning(f"Icon '{icon_name}' nicht gefunden, verwende Platzhalter")
pixmap = QPixmap(size, size)
pixmap.fill(Qt.transparent)
painter = QPainter(pixmap)
painter.setRenderHint(QPainter.Antialiasing)
painter.setBrush(QColor(color))
painter.setPen(Qt.NoPen)
painter.drawEllipse(0, 0, size, size)
painter.end()
return pixmap
@classmethod
def create_icon_label(cls, icon_name: str, size: int = 16, color: str = "#718096") -> QLabel:
"""
Erstellt ein QLabel mit Icon
Args:
icon_name: Name des Icons
size: Größe des Icons
color: Farbe des Icons (hex)
Returns:
QLabel mit Icon
"""
label = QLabel()
label.setFixedSize(size, size)
# Prüfe ob es eine Plattform ist
platform_names = ["instagram", "facebook", "twitter", "x", "tiktok", "vk", "ok", "gmail"]
if icon_name.lower() in platform_names:
# Verwende get_platform_icon für Plattformen
icon = cls.get_platform_icon(icon_name, size)
pixmap = icon.pixmap(size, size)
else:
# Normale Icons
pixmap = cls.get_pixmap(icon_name, size, color)
label.setPixmap(pixmap)
label.setScaledContents(True)
return label
@classmethod
def get_platform_icon(cls, platform: str, size: int = 18) -> QIcon:
"""
Gibt ein Platform-spezifisches Icon zurück
Args:
platform: Name der Plattform
size: Größe des Icons
Returns:
QIcon für die Plattform
"""
# Spezialbehandlung für Twitter/X
if platform.lower() == "twitter" or platform.lower() == "x":
icon_name = "twitter"
else:
icon_name = platform.lower()
# Platform Icons haben eigene Farben
platform_colors = {
"instagram": "#E4405F",
"facebook": "#1877F2",
"twitter": "#1DA1F2",
"x": "#000000",
"tiktok": "#000000",
"vk": "#0077FF"
}
color = platform_colors.get(icon_name, "#718096")
return cls.get_icon(icon_name, size, color)

Datei anzeigen

@ -0,0 +1,203 @@
# Path: views/widgets/language_dropdown.py
"""
Benutzerdefiniertes Dropdown-Widget für die Sprachauswahl mit Flaggen-Icons.
"""
import os
from PyQt5.QtWidgets import (QWidget, QComboBox, QLabel, QHBoxLayout,
QVBoxLayout, QFrame, QListWidget, QListWidgetItem,
QAbstractItemView, QApplication)
from PyQt5.QtCore import Qt, QSize, QEvent, pyqtSignal
from PyQt5.QtGui import QIcon, QPainter, QPen, QColor, QCursor
class LanguageDropdown(QWidget):
"""Benutzerdefiniertes Dropdown für die Sprachauswahl mit Flaggen."""
def __init__(self, language_manager):
super().__init__()
self.language_manager = language_manager
self.is_open = False
self.languages = {}
self.current_language = self.language_manager.get_current_language()
# QApplication-Instanz merken, um einen Event-Filter installieren zu können
self.app = QApplication.instance()
# Verfügbare Sprachen aus dem Manager holen
self.available_languages = self.language_manager.get_available_languages()
self.init_ui()
# Verbinde Signal des Language Managers
self.language_manager.language_changed.connect(self.on_language_changed)
def init_ui(self):
"""Initialisiert die Benutzeroberfläche."""
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
# Container für die aktuelle Sprachauswahl
self.current_language_container = QFrame()
self.current_language_container.setObjectName("languageSelector")
self.current_language_container.setCursor(Qt.PointingHandCursor)
self.current_language_container.setStyleSheet("""
QFrame#languageSelector {
background-color: transparent;
border-radius: 4px;
}
QFrame#languageSelector:hover {
background-color: rgba(200, 200, 200, 30);
}
""")
current_layout = QHBoxLayout(self.current_language_container)
current_layout.setContentsMargins(5, 5, 5, 5)
# Icon der aktuellen Sprache
self.current_flag = QLabel()
self.current_flag.setFixedSize(24, 16) # Correct flag aspect ratio
# Pfad zum Icon
icon_path = self.get_language_icon_path(self.current_language)
if icon_path:
self.current_flag.setPixmap(QIcon(icon_path).pixmap(QSize(24, 16)))
current_layout.addWidget(self.current_flag)
# Kleiner Pfeil nach unten
arrow_label = QLabel("")
arrow_label.setStyleSheet("font-size: 8px; color: #888888;")
current_layout.addWidget(arrow_label)
layout.addWidget(self.current_language_container)
# Dropdown-Liste (anfangs versteckt)
self.dropdown_list = QListWidget()
self.dropdown_list.setWindowFlags(Qt.Popup | Qt.FramelessWindowHint)
self.dropdown_list.setFocusPolicy(Qt.NoFocus)
self.dropdown_list.setMouseTracking(True)
self.dropdown_list.setFrameShape(QFrame.NoFrame)
self.dropdown_list.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.dropdown_list.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.dropdown_list.setSelectionMode(QAbstractItemView.NoSelection)
self.dropdown_list.setStyleSheet("""
QListWidget {
background-color: white;
border: 1px solid #CCCCCC;
border-radius: 5px;
padding: 5px;
}
QListWidget::item {
padding: 4px;
border-radius: 3px;
}
QListWidget::item:hover {
background-color: #F0F0F0;
}
""")
# Sprachen zum Dropdown hinzufügen
self.populate_dropdown()
# Event-Verbindungen
self.current_language_container.mousePressEvent = self.toggle_dropdown
self.dropdown_list.itemClicked.connect(self.on_language_selected)
# Zugänglichkeit mit Tastaturfokus
self.setFocusPolicy(Qt.StrongFocus)
self.current_language_container.setFocusPolicy(Qt.StrongFocus)
def get_language_icon_path(self, language_code):
"""Gibt den Pfad zum Icon für den angegebenen Sprachcode zurück."""
# Projektbasis-Verzeichnis ermitteln
base_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
icon_path = os.path.join(base_dir, "resources", "icons", f"{language_code}.svg")
if os.path.exists(icon_path):
return icon_path
return None
def populate_dropdown(self):
"""Füllt das Dropdown mit den verfügbaren Sprachen."""
self.dropdown_list.clear()
self.languages = {}
for code, name in self.available_languages.items():
item = QListWidgetItem(name)
# Icon erstellen
icon_path = self.get_language_icon_path(code)
if icon_path:
item.setIcon(QIcon(icon_path))
# Sprach-Code speichern
item.setData(Qt.UserRole, code)
# Zum Dropdown hinzufügen
self.dropdown_list.addItem(item)
self.languages[code] = item
def toggle_dropdown(self, event):
"""Öffnet oder schließt das Dropdown-Menü."""
if not self.is_open:
# Position des Dropdowns unter dem Button berechnen
pos = self.current_language_container.mapToGlobal(self.current_language_container.rect().bottomLeft())
self.dropdown_list.setGeometry(pos.x(), pos.y(), 120, 120) # Größe anpassen
self.dropdown_list.show()
self.is_open = True
if self.app:
self.app.installEventFilter(self)
else:
self.dropdown_list.hide()
self.is_open = False
if self.app:
self.app.removeEventFilter(self)
def on_language_selected(self, item):
"""Wird aufgerufen, wenn eine Sprache im Dropdown ausgewählt wird."""
language_code = item.data(Qt.UserRole)
# Sprache wechseln
if language_code != self.current_language:
self.language_manager.change_language(language_code)
# Dropdown schließen
self.dropdown_list.hide()
self.is_open = False
if self.app:
self.app.removeEventFilter(self)
def on_language_changed(self, language_code):
"""Wird aufgerufen, wenn sich die Sprache im LanguageManager ändert."""
self.current_language = language_code
# Icon aktualisieren
icon_path = self.get_language_icon_path(language_code)
if icon_path:
self.current_flag.setPixmap(QIcon(icon_path).pixmap(QSize(24, 16)))
# Texte aktualisieren (falls vorhanden)
if hasattr(self.parent(), "update_texts"):
self.parent().update_texts()
def keyPressEvent(self, event):
"""Behandelt Tastatureingaben für verbesserte Zugänglichkeit."""
if event.key() == Qt.Key_Space or event.key() == Qt.Key_Return:
self.toggle_dropdown(None)
else:
super().keyPressEvent(event)
def eventFilter(self, obj, event):
"""Schließt das Dropdown, wenn außerhalb geklickt wird."""
if self.is_open and event.type() == QEvent.MouseButtonPress:
# Klickposition relativ zum Dropdown ermitteln
if not self.dropdown_list.geometry().contains(event.globalPos()) and \
not self.current_language_container.geometry().contains(
self.mapFromGlobal(event.globalPos())):
self.dropdown_list.hide()
self.is_open = False
if self.app:
self.app.removeEventFilter(self)
return super().eventFilter(obj, event)

Datei anzeigen

@ -0,0 +1,295 @@
"""
Login Process Modal - Spezialisiertes Modal für Login-Prozesse
"""
import logging
from typing import Optional, Dict, Any
from PyQt5.QtCore import QTimer, pyqtSignal
from PyQt5.QtWidgets import QHBoxLayout, QLabel
from PyQt5.QtGui import QFont, QPixmap
from PyQt5.QtCore import Qt
from views.widgets.progress_modal import ProgressModal
logger = logging.getLogger("login_process_modal")
class LoginProcessModal(ProgressModal):
"""
Spezialisiertes Modal für Login-Prozesse mit Session-Wiederherstellung.
"""
# Signale
login_completed = pyqtSignal(bool, str) # success, message
session_restored = pyqtSignal(str) # platform
def __init__(self, parent=None, platform: str = "Social Media", language_manager=None):
self.platform = platform
self.login_method = "session" # "session" oder "credentials"
self.account_username = ""
super().__init__(parent, "login_process", language_manager)
# Erweitere UI für Login-spezifische Anzeige
self.setup_login_ui()
def setup_login_ui(self):
"""Erweitert die UI um Login-spezifische Elemente"""
container_layout = self.modal_container.layout()
# Platform/Account Info (zwischen Titel und Animation)
self.account_info_widget = QLabel()
self.account_info_widget.setVisible(False)
self.account_info_widget.setAlignment(Qt.AlignCenter)
info_font = QFont("Inter", 13)
info_font.setBold(True)
self.account_info_widget.setFont(info_font)
self.account_info_widget.setStyleSheet("""
QLabel {
color: #2D3748;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: rgba(66, 153, 225, 0.1);
border-radius: 8px;
padding: 8px 16px;
margin: 4px 0px;
}
""")
# Füge nach Titel ein (Index 1)
container_layout.insertWidget(1, self.account_info_widget)
# Login-Methode Indicator (nach Animation Widget)
self.method_label = QLabel()
self.method_label.setVisible(False)
self.method_label.setAlignment(Qt.AlignCenter)
method_font = QFont("Inter", 11)
self.method_label.setFont(method_font)
self.method_label.setStyleSheet("""
QLabel {
color: #4A5568;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-style: italic;
margin: 4px 0px;
}
""")
# Füge nach Animation Widget ein (Index 3, wegen account_info_widget)
container_layout.insertWidget(3, self.method_label)
def show_session_login(self, account_username: str, platform: str = None):
"""
Zeigt Session-basiertes Login an.
Args:
account_username: Benutzername des Accounts
platform: Optional - Platform überschreibung
"""
if platform:
self.platform = platform
self.account_username = account_username
self.login_method = "session"
# UI aktualisieren
self._update_login_display()
# Modal anzeigen
self.show_process("login_process")
logger.info(f"Session Login Modal angezeigt für: {account_username} @ {self.platform}")
def show_credentials_login(self, account_username: str, platform: str = None):
"""
Zeigt Credential-basiertes Login an.
Args:
account_username: Benutzername des Accounts
platform: Optional - Platform überschreibung
"""
if platform:
self.platform = platform
self.account_username = account_username
self.login_method = "credentials"
# UI aktualisieren
self._update_login_display()
# Modal anzeigen
self.show_process("login_process")
logger.info(f"Credentials Login Modal angezeigt für: {account_username} @ {self.platform}")
def _update_login_display(self):
"""Aktualisiert die Anzeige basierend auf Login-Methode"""
# Titel anpassen
title = f"🔐 {self.platform} Login"
self.title_label.setText(title)
# Account Info anzeigen
if self.account_username:
account_info = f"👤 {self.account_username}"
self.account_info_widget.setText(account_info)
self.account_info_widget.setVisible(True)
# Methode anzeigen
if self.login_method == "session":
method_text = "🔄 Session wird wiederhergestellt"
status_text = "Gespeicherte Anmeldedaten werden geladen..."
else:
method_text = "🔑 Anmeldedaten werden eingegeben"
status_text = "Benutzername und Passwort werden verwendet..."
self.method_label.setText(method_text)
self.method_label.setVisible(True)
# Status setzen
self.update_status(status_text)
def update_login_progress(self, step: str, detail: str = None):
"""
Aktualisiert den Login-Fortschritt.
Args:
step: Aktueller Schritt
detail: Optional - Detail-Information
"""
step_emojis = {
'browser_init': '🌐',
'page_load': '📄',
'session_restore': '🔄',
'credential_input': '✍️',
'verification': '🔐',
'success_check': '',
'finalizing': '🏁'
}
emoji = step_emojis.get(step, '')
status_text = f"{emoji} {step.replace('_', ' ').title()}"
self.update_status(status_text, detail)
logger.debug(f"Login Progress: {step} - {detail or 'No detail'}")
def show_session_restored(self, platform_data: Dict[str, Any] = None):
"""
Zeigt erfolgreiche Session-Wiederherstellung an.
Args:
platform_data: Optional - Platform-spezifische Daten
"""
self.update_status("✅ Session erfolgreich wiederhergestellt", "Anmeldung abgeschlossen")
# Session restored Signal
self.session_restored.emit(self.platform)
# Auto-Close nach 2 Sekunden
QTimer.singleShot(2000, lambda: self._complete_login(True, "Session wiederhergestellt"))
logger.info(f"Session erfolgreich wiederhergestellt für: {self.account_username} @ {self.platform}")
def show_credentials_success(self):
"""Zeigt erfolgreiche Credential-Anmeldung an"""
self.update_status("✅ Anmeldung erfolgreich", "Account ist bereit")
# Auto-Close nach 2 Sekunden
QTimer.singleShot(2000, lambda: self._complete_login(True, "Anmeldung erfolgreich"))
logger.info(f"Credential-Login erfolgreich für: {self.account_username} @ {self.platform}")
def show_login_failed(self, reason: str, retry_available: bool = False):
"""
Zeigt fehlgeschlagene Anmeldung an.
Args:
reason: Grund für Fehlschlag
retry_available: Ob Retry möglich ist
"""
# Animation stoppen
self.animation_widget.stop_animation()
error_title = "❌ Anmeldung fehlgeschlagen"
if self.login_method == "session":
error_detail = "Session ist abgelaufen oder ungültig"
else:
error_detail = "Benutzername oder Passwort falsch"
self.title_label.setText(error_title)
self.update_status(reason, error_detail)
# Auto-Close nach 4 Sekunden
auto_close_time = 4000
QTimer.singleShot(auto_close_time, lambda: self._complete_login(False, reason))
logger.error(f"Login fehlgeschlagen für: {self.account_username} @ {self.platform} - {reason}")
def show_session_expired(self):
"""Zeigt abgelaufene Session an"""
self.show_login_failed("Session ist abgelaufen", retry_available=True)
# Spezielle Behandlung für Session-Ablauf
self.method_label.setText("⚠️ Manuelle Anmeldung erforderlich")
def show_captcha_required(self):
"""Zeigt an, dass Captcha erforderlich ist"""
self.update_status("🤖 Captcha-Verifizierung erforderlich", "Bitte manuell lösen...")
# Längere Auto-Close Zeit für Captcha
QTimer.singleShot(10000, lambda: self._complete_login(False, "Captcha erforderlich"))
def show_two_factor_required(self):
"""Zeigt an, dass Zwei-Faktor-Authentifizierung erforderlich ist"""
self.update_status("📱 Zwei-Faktor-Authentifizierung", "Code wird benötigt...")
# Längere Auto-Close Zeit für 2FA
QTimer.singleShot(8000, lambda: self._complete_login(False, "2FA erforderlich"))
def _complete_login(self, success: bool, message: str):
"""
Schließt den Login-Prozess ab.
Args:
success: Ob Login erfolgreich war
message: Abschließende Nachricht
"""
# Signal senden
self.login_completed.emit(success, message)
# Modal verstecken
self.hide_process()
logger.info(f"Login-Prozess abgeschlossen: {success} - {message}")
def get_platform_icon_path(self) -> Optional[str]:
"""
Gibt den Pfad zum Platform-Icon zurück.
Returns:
Optional[str]: Icon-Pfad oder None
"""
try:
import os
current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(os.path.dirname(os.path.dirname(current_dir)))
icons_dir = os.path.join(parent_dir, "resources", "icons")
platform_name = self.platform.lower().replace('.', '').replace(' ', '')
if platform_name == "x":
platform_name = "twitter"
elif platform_name == "okru":
platform_name = "ok"
icon_path = os.path.join(icons_dir, f"{platform_name}.svg")
if os.path.exists(icon_path):
return icon_path
except Exception as e:
logger.warning(f"Konnte Platform-Icon nicht laden: {e}")
return None

Datei anzeigen

@ -0,0 +1,316 @@
"""
Moderne, schöne MessageBox als Alternative zu den hässlichen Qt Standard-Dialogen
"""
from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QLabel,
QPushButton, QFrame, QGraphicsDropShadowEffect)
from PyQt5.QtCore import Qt, QTimer, pyqtSignal
from PyQt5.QtGui import QFont, QPixmap, QPainter, QColor, QIcon
class ModernMessageBox(QDialog):
"""Moderne, schöne MessageBox mit glasmorphism Design"""
clicked = pyqtSignal(str) # "ok", "cancel", "yes", "no"
def __init__(self, parent=None, title="", message="", msg_type="info", buttons=None):
super().__init__(parent)
self.msg_type = msg_type.lower()
self.result_value = None
if buttons is None:
buttons = ["OK"]
self.init_ui(title, message, buttons)
self.setup_animations()
def init_ui(self, title, message, buttons):
"""Initialisiert die moderne UI"""
# Dialog-Eigenschaften
self.setWindowFlags(Qt.Dialog | Qt.FramelessWindowHint)
self.setAttribute(Qt.WA_TranslucentBackground, True)
self.setModal(True)
self.setFixedSize(400, 250)
# Hauptlayout
main_layout = QVBoxLayout(self)
main_layout.setContentsMargins(20, 20, 20, 20)
# Container mit Glasmorphism-Effekt
self.container = QFrame()
self.container.setObjectName("messageContainer")
# Schatten-Effekt
shadow = QGraphicsDropShadowEffect()
shadow.setBlurRadius(30)
shadow.setXOffset(0)
shadow.setYOffset(10)
shadow.setColor(QColor(0, 0, 0, 80))
self.container.setGraphicsEffect(shadow)
# Container-Layout
container_layout = QVBoxLayout(self.container)
container_layout.setSpacing(20)
container_layout.setContentsMargins(30, 25, 30, 25)
# Header mit Icon und Titel
header_layout = QHBoxLayout()
# Icon basierend auf Nachrichtentyp
icon_label = QLabel()
icon_label.setFixedSize(32, 32)
icon_label.setAlignment(Qt.AlignCenter)
icon_text = self.get_icon_for_type(self.msg_type)
icon_label.setText(icon_text)
icon_label.setObjectName(f"icon{self.msg_type.title()}")
# Titel
title_label = QLabel(title)
title_label.setObjectName("messageTitle")
title_label.setWordWrap(True)
header_layout.addWidget(icon_label)
header_layout.addWidget(title_label, 1)
header_layout.setSpacing(15)
# Nachricht
message_label = QLabel(message)
message_label.setObjectName("messageText")
message_label.setWordWrap(True)
message_label.setAlignment(Qt.AlignLeft | Qt.AlignTop)
# Buttons
button_layout = QHBoxLayout()
button_layout.addStretch()
for i, button_text in enumerate(buttons):
btn = QPushButton(button_text)
btn.setObjectName("messageButton" if i == 0 else "messageButtonSecondary")
btn.setMinimumHeight(35)
btn.setMinimumWidth(80)
btn.clicked.connect(lambda checked, text=button_text.lower(): self.button_clicked(text))
button_layout.addWidget(btn)
if i < len(buttons) - 1:
button_layout.addSpacing(10)
# Layout zusammenfügen
container_layout.addLayout(header_layout)
container_layout.addWidget(message_label, 1)
container_layout.addLayout(button_layout)
main_layout.addWidget(self.container)
# Stil anwenden
self.apply_styles()
def get_icon_for_type(self, msg_type):
"""Gibt das passende Icon für den Nachrichtentyp zurück"""
icons = {
"info": "",
"success": "",
"warning": "⚠️",
"error": "",
"critical": "🚨",
"question": ""
}
return icons.get(msg_type, "")
def apply_styles(self):
"""Wendet die modernen Styles an"""
# Farben basierend auf Typ
colors = {
"info": {
"bg": "rgba(59, 130, 246, 0.1)",
"border": "rgba(59, 130, 246, 0.3)",
"accent": "#3B82F6"
},
"success": {
"bg": "rgba(34, 197, 94, 0.1)",
"border": "rgba(34, 197, 94, 0.3)",
"accent": "#22C55E"
},
"warning": {
"bg": "rgba(245, 158, 11, 0.1)",
"border": "rgba(245, 158, 11, 0.3)",
"accent": "#F59E0B"
},
"error": {
"bg": "rgba(239, 68, 68, 0.1)",
"border": "rgba(239, 68, 68, 0.3)",
"accent": "#EF4444"
},
"critical": {
"bg": "rgba(220, 38, 38, 0.1)",
"border": "rgba(220, 38, 38, 0.3)",
"accent": "#DC2626"
},
"question": {
"bg": "rgba(168, 85, 247, 0.1)",
"border": "rgba(168, 85, 247, 0.3)",
"accent": "#A855F7"
}
}
color_scheme = colors.get(self.msg_type, colors["info"])
style = f"""
QDialog {{
background: transparent;
}}
#messageContainer {{
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 rgba(255, 255, 255, 0.95),
stop:1 rgba(248, 250, 252, 0.95));
border: 1px solid {color_scheme['border']};
border-radius: 16px;
backdrop-filter: blur(10px);
}}
#messageTitle {{
font-family: 'Segoe UI', Arial, sans-serif;
font-size: 16px;
font-weight: 600;
color: #1e293b;
margin: 0;
}}
#messageText {{
font-family: 'Segoe UI', Arial, sans-serif;
font-size: 14px;
color: #475569;
line-height: 1.5;
}}
#icon{self.msg_type.title()} {{
font-size: 24px;
background: {color_scheme['bg']};
border: 1px solid {color_scheme['border']};
border-radius: 16px;
padding: 4px;
}}
#messageButton {{
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 {color_scheme['accent']},
stop:1 {color_scheme['accent']});
color: white;
border: none;
border-radius: 8px;
font-family: 'Segoe UI', Arial, sans-serif;
font-size: 13px;
font-weight: 500;
padding: 8px 16px;
}}
#messageButton:hover {{
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 {color_scheme['accent']},
stop:1 {color_scheme['accent']});
transform: translateY(-1px);
}}
#messageButton:pressed {{
transform: translateY(0px);
}}
#messageButtonSecondary {{
background: rgba(148, 163, 184, 0.1);
color: #64748b;
border: 1px solid rgba(148, 163, 184, 0.3);
border-radius: 8px;
font-family: 'Segoe UI', Arial, sans-serif;
font-size: 13px;
font-weight: 500;
padding: 8px 16px;
}}
#messageButtonSecondary:hover {{
background: rgba(148, 163, 184, 0.2);
border-color: rgba(148, 163, 184, 0.5);
}}
"""
self.setStyleSheet(style)
def setup_animations(self):
"""Setzt Animationen ein"""
# Sanftes Einblenden
self.setWindowOpacity(0)
self.fade_timer = QTimer()
self.fade_timer.timeout.connect(self.fade_in)
self.opacity = 0
self.fade_timer.start(16) # ~60 FPS
def fade_in(self):
"""Fade-in Animation"""
self.opacity += 0.1
if self.opacity >= 1:
self.opacity = 1
self.fade_timer.stop()
self.setWindowOpacity(self.opacity)
def button_clicked(self, button_text):
"""Behandelt Button-Clicks"""
self.result_value = button_text
self.clicked.emit(button_text)
self.accept()
def mousePressEvent(self, event):
"""Ermöglicht das Verschieben des Dialogs"""
if event.button() == Qt.LeftButton:
self.drag_position = event.globalPos() - self.frameGeometry().topLeft()
event.accept()
def mouseMoveEvent(self, event):
"""Verschiebt den Dialog"""
if event.buttons() == Qt.LeftButton and hasattr(self, 'drag_position'):
self.move(event.globalPos() - self.drag_position)
event.accept()
# Convenience-Funktionen für einfache Nutzung
def show_info(parent=None, title="Information", message="", buttons=None):
"""Zeigt eine moderne Info-MessageBox"""
if buttons is None:
buttons = ["OK"]
dialog = ModernMessageBox(parent, title, message, "info", buttons)
return dialog.exec_()
def show_success(parent=None, title="Erfolg", message="", buttons=None):
"""Zeigt eine moderne Erfolg-MessageBox"""
if buttons is None:
buttons = ["OK"]
dialog = ModernMessageBox(parent, title, message, "success", buttons)
return dialog.exec_()
def show_warning(parent=None, title="Warnung", message="", buttons=None):
"""Zeigt eine moderne Warnung-MessageBox"""
if buttons is None:
buttons = ["OK"]
dialog = ModernMessageBox(parent, title, message, "warning", buttons)
return dialog.exec_()
def show_error(parent=None, title="Fehler", message="", buttons=None):
"""Zeigt eine moderne Fehler-MessageBox"""
if buttons is None:
buttons = ["OK"]
dialog = ModernMessageBox(parent, title, message, "error", buttons)
return dialog.exec_()
def show_critical(parent=None, title="Kritischer Fehler", message="", buttons=None):
"""Zeigt eine moderne kritische Fehler-MessageBox"""
if buttons is None:
buttons = ["OK"]
dialog = ModernMessageBox(parent, title, message, "critical", buttons)
return dialog.exec_()
def show_question(parent=None, title="Frage", message="", buttons=None):
"""Zeigt eine moderne Frage-MessageBox"""
if buttons is None:
buttons = ["Ja", "Nein"]
dialog = ModernMessageBox(parent, title, message, "question", buttons)
return dialog.exec_()

Datei anzeigen

@ -0,0 +1,89 @@
# Path: views/widgets/platform_button.py
"""
Benutzerdefinierter Button für die Plattformauswahl.
"""
import os
from PyQt5.QtWidgets import QPushButton, QVBoxLayout, QLabel, QWidget
from PyQt5.QtCore import QSize, Qt, pyqtSignal
from PyQt5.QtGui import QIcon, QFont
class PlatformButton(QWidget):
"""Angepasster Button-Widget für Plattformauswahl mit Icon."""
# Signal wenn geklickt
clicked = pyqtSignal()
def __init__(self, platform_name, icon_path=None, enabled=True):
super().__init__()
self.platform = platform_name.lower()
self.setMinimumSize(200, 200)
self.setEnabled(enabled)
# Layout für den Container
layout = QVBoxLayout(self)
layout.setAlignment(Qt.AlignCenter)
layout.setContentsMargins(10, 10, 10, 10)
# Icon-Button
self.icon_button = QPushButton()
self.icon_button.setFlat(True)
self.icon_button.setCursor(Qt.PointingHandCursor)
# Icon setzen, falls vorhanden
if icon_path and os.path.exists(icon_path):
self.icon_button.setIcon(QIcon(icon_path))
self.icon_button.setIconSize(QSize(120, 120)) # Größeres Icon
self.icon_button.setMinimumSize(150, 150)
# Platform button styling based on Styleguide
self.icon_button.setStyleSheet("""
QPushButton {
background-color: #F5F7FF;
border: 1px solid transparent;
border-radius: 16px;
padding: 32px;
}
QPushButton:hover {
background-color: #E8EBFF;
border: 1px solid #0099CC;
}
QPushButton:pressed {
background-color: #DCE2FF;
padding: 23px;
}
QPushButton:disabled {
background-color: #F0F0F0;
opacity: 0.5;
}
""")
# Button-Signal verbinden
self.icon_button.clicked.connect(self.clicked)
# Name-Label
self.name_label = QLabel(platform_name)
self.name_label.setAlignment(Qt.AlignCenter)
name_font = QFont()
name_font.setPointSize(12)
name_font.setBold(True)
self.name_label.setFont(name_font)
# Name label styling based on Styleguide
self.name_label.setStyleSheet("""
QLabel {
color: #232D53;
font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-weight: 600;
letter-spacing: 0.5px;
}
""")
# Widgets zum Layout hinzufügen
layout.addWidget(self.icon_button, 0, Qt.AlignCenter)
layout.addWidget(self.name_label, 0, Qt.AlignCenter)
# Styling für den deaktivierten Zustand
if not enabled:
self.setStyleSheet("opacity: 0.5;")

Datei anzeigen

@ -0,0 +1,312 @@
"""
Progress Modal - Basis-Klasse für Prozess-Modals
"""
import logging
from typing import Optional, Dict, Any
from PyQt5.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QLabel,
QPushButton, QFrame, QGraphicsBlurEffect
)
from PyQt5.QtCore import Qt, QTimer, pyqtSignal, QPropertyAnimation, QEasingCurve
from PyQt5.QtGui import QFont, QMovie, QPainter, QBrush, QColor
from views.widgets.forge_animation_widget import ForgeAnimationWidget
from styles.modal_styles import ModalStyles
logger = logging.getLogger("progress_modal")
class ProgressModal(QDialog):
"""
Basis-Klasse für Progress-Modals während Automatisierungsprozessen.
Zeigt den Benutzer eine nicht-unterbrechbare Fortschrittsanzeige.
"""
# Signale
force_closed = pyqtSignal() # Wird ausgelöst wenn Modal zwangsweise geschlossen wird
def __init__(self, parent=None, modal_type: str = "generic", language_manager=None, style_manager=None):
super().__init__(parent)
self.modal_type = modal_type
self.language_manager = language_manager
self.style_manager = style_manager or ModalStyles()
self.is_process_running = False
self.auto_close_timer = None
self.fade_animation = None
# Modal-Texte laden
self.modal_texts = self.style_manager.get_modal_texts()
self.init_ui()
self.setup_animations()
if self.language_manager:
self.language_manager.language_changed.connect(self.update_texts)
self.update_texts()
def init_ui(self):
"""Initialisiert die UI nach AccountForger Styleguide"""
# Modal-Eigenschaften
self.setModal(True)
self.setWindowFlags(Qt.Dialog | Qt.FramelessWindowHint)
# Transparenz entfernt - solider Hintergrund
# self.setAttribute(Qt.WA_TranslucentBackground)
self.setFixedSize(
self.style_manager.SIZES['modal_width'],
self.style_manager.SIZES['modal_height']
)
# Zentriere auf Parent oder Bildschirm
if self.parent():
parent_rect = self.parent().geometry()
x = parent_rect.x() + (parent_rect.width() - self.width()) // 2
y = parent_rect.y() + (parent_rect.height() - self.height()) // 2
self.move(x, y)
# Hauptlayout
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
# Modal-Container mit solidem Hintergrund
self.modal_container = QFrame()
self.modal_container.setObjectName("modal_container")
self.modal_container.setStyleSheet(self.style_manager.get_modal_container_style())
# Container-Layout
container_layout = QVBoxLayout(self.modal_container)
padding = self.style_manager.SIZES['padding_large']
container_layout.setContentsMargins(padding, padding, padding, padding)
container_layout.setSpacing(self.style_manager.SIZES['spacing_default'])
# Titel
self.title_label = QLabel()
self.title_label.setAlignment(Qt.AlignCenter)
self.title_label.setObjectName("modal_title")
self.title_label.setFont(self.style_manager.create_font('title'))
self.title_label.setStyleSheet(self.style_manager.get_title_label_style())
container_layout.addWidget(self.title_label)
# Animation Widget (Spinner/Forge Animation)
self.animation_widget = ForgeAnimationWidget()
animation_size = self.style_manager.SIZES['animation_size']
self.animation_widget.setFixedSize(animation_size, animation_size)
self.animation_widget.setAlignment(Qt.AlignCenter)
animation_layout = QHBoxLayout()
animation_layout.addStretch()
animation_layout.addWidget(self.animation_widget)
animation_layout.addStretch()
container_layout.addLayout(animation_layout)
# Subtitle/Status
self.status_label = QLabel()
self.status_label.setAlignment(Qt.AlignCenter)
self.status_label.setObjectName("modal_status")
self.status_label.setFont(self.style_manager.create_font('status'))
self.status_label.setStyleSheet(self.style_manager.get_status_label_style())
container_layout.addWidget(self.status_label)
# Detail-Status (optional)
self.detail_label = QLabel()
self.detail_label.setAlignment(Qt.AlignCenter)
self.detail_label.setObjectName("modal_detail")
self.detail_label.setVisible(False)
self.detail_label.setFont(self.style_manager.create_font('detail'))
self.detail_label.setStyleSheet(self.style_manager.get_detail_label_style())
container_layout.addWidget(self.detail_label)
layout.addWidget(self.modal_container)
# Failsafe Timer
self.failsafe_timer = QTimer()
self.failsafe_timer.setSingleShot(True)
self.failsafe_timer.timeout.connect(self._force_close)
def setup_animations(self):
"""Richtet Fade-Animationen ein"""
self.fade_animation = QPropertyAnimation(self, b"windowOpacity")
self.fade_animation.setDuration(self.style_manager.ANIMATIONS['fade_duration'])
self.fade_animation.setEasingCurve(QEasingCurve.OutCubic)
def _get_modal_texts(self) -> Dict[str, Dict[str, str]]:
"""Gibt die Modal-Texte zurück"""
# Diese Methode ist jetzt nur für Rückwärtskompatibilität
# Die echten Texte kommen vom style_manager
return self.style_manager.get_modal_texts()
def show_process(self, process_type: Optional[str] = None):
"""
Zeigt das Modal für einen bestimmten Prozess-Typ an.
Args:
process_type: Optional - überschreibt den Modal-Typ
"""
if process_type:
self.modal_type = process_type
self.is_process_running = True
# Texte aktualisieren
self.update_texts()
# Animation starten
self.animation_widget.start_animation()
# Failsafe Timer starten
self.failsafe_timer.start(self.style_manager.ANIMATIONS['failsafe_timeout'])
# Modal anzeigen ohne Fade-In (immer voll sichtbar)
self.setWindowOpacity(1.0)
self.show()
# Fade-Animation deaktiviert für volle Sichtbarkeit
# if self.fade_animation:
# self.fade_animation.setStartValue(0.0)
# self.fade_animation.setEndValue(1.0)
# self.fade_animation.start()
logger.info(f"Progress Modal angezeigt für: {self.modal_type}")
def hide_process(self):
"""Versteckt das Modal mit Fade-Out Animation"""
if not self.is_process_running:
return
self.is_process_running = False
# Timer stoppen
self.failsafe_timer.stop()
# Animation stoppen
self.animation_widget.stop_animation()
# Fade-Out Animation deaktiviert - sofort verstecken
self._finish_hide()
# if self.fade_animation:
# self.fade_animation.setStartValue(1.0)
# self.fade_animation.setEndValue(0.0)
# self.fade_animation.finished.connect(self._finish_hide)
# self.fade_animation.start()
# else:
# self._finish_hide()
logger.info(f"Progress Modal versteckt für: {self.modal_type}")
def _finish_hide(self):
"""Beendet das Verstecken des Modals"""
self.hide()
if self.fade_animation:
self.fade_animation.finished.disconnect()
def update_status(self, status: str, detail: str = None):
"""
Aktualisiert den Status-Text des Modals.
Args:
status: Haupt-Status-Text
detail: Optional - Detail-Text
"""
self.status_label.setText(status)
if detail:
self.detail_label.setText(detail)
self.detail_label.setVisible(True)
else:
self.detail_label.setVisible(False)
def show_error(self, error_message: str, auto_close_seconds: int = 3):
"""
Zeigt eine Fehlermeldung im Modal an.
Args:
error_message: Fehlermeldung
auto_close_seconds: Sekunden bis automatisches Schließen
"""
self.title_label.setText("❌ Fehler aufgetreten")
self.status_label.setText(error_message)
self.detail_label.setVisible(False)
# Animation stoppen
self.animation_widget.stop_animation()
# Auto-Close Timer
if auto_close_seconds > 0:
self.auto_close_timer = QTimer()
self.auto_close_timer.setSingleShot(True)
self.auto_close_timer.timeout.connect(self.hide_process)
self.auto_close_timer.start(auto_close_seconds * 1000)
def _force_close(self):
"""Zwangsschließung nach Timeout"""
logger.warning(f"Progress Modal Timeout erreicht für: {self.modal_type}")
self.force_closed.emit()
self.hide_process()
def update_texts(self):
"""Aktualisiert die Texte gemäß der aktuellen Sprache"""
if not self.language_manager:
# Fallback zu Standardtexten
texts = self.modal_texts.get(self.modal_type, self.modal_texts['generic'])
self.title_label.setText(texts['title'])
self.status_label.setText(texts['status'])
if texts['detail']:
self.detail_label.setText(texts['detail'])
self.detail_label.setVisible(True)
return
# Multilingual texts (für zukünftige Erweiterung)
title_key = f"modal.{self.modal_type}.title"
status_key = f"modal.{self.modal_type}.status"
detail_key = f"modal.{self.modal_type}.detail"
# Fallback zu Standardtexten wenn Übersetzung nicht vorhanden
texts = self.modal_texts.get(self.modal_type, self.modal_texts['generic'])
self.title_label.setText(
self.language_manager.get_text(title_key, texts['title'])
)
self.status_label.setText(
self.language_manager.get_text(status_key, texts['status'])
)
detail_text = self.language_manager.get_text(detail_key, texts['detail'])
if detail_text:
self.detail_label.setText(detail_text)
self.detail_label.setVisible(True)
else:
self.detail_label.setVisible(False)
def paintEvent(self, event):
"""Custom Paint Event für soliden Hintergrund"""
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
# Solider Hintergrund (komplett undurchsichtig)
overlay_color = QColor(self.style_manager.COLORS['overlay'])
brush = QBrush(overlay_color)
painter.fillRect(self.rect(), brush)
super().paintEvent(event)
def keyPressEvent(self, event):
"""Verhindert das Schließen mit Escape während Prozess läuft"""
if self.is_process_running and event.key() == Qt.Key_Escape:
event.ignore()
return
super().keyPressEvent(event)
def closeEvent(self, event):
"""Verhindert das Schließen während Prozess läuft"""
if self.is_process_running:
event.ignore()
return
super().closeEvent(event)