Initial commit
Dieser Commit ist enthalten in:
111
views/about_dialog.py
Normale Datei
111
views/about_dialog.py
Normale Datei
@ -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"))
|
||||
|
||||
1
views/components/__init__.py
Normale Datei
1
views/components/__init__.py
Normale Datei
@ -0,0 +1 @@
|
||||
# Components for the main UI
|
||||
448
views/components/accounts_overview_view.py
Normale Datei
448
views/components/accounts_overview_view.py
Normale Datei
@ -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
|
||||
121
views/components/platform_grid_view.py
Normale Datei
121
views/components/platform_grid_view.py
Normale Datei
@ -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")
|
||||
)
|
||||
130
views/components/tab_navigation.py
Normale Datei
130
views/components/tab_navigation.py
Normale Datei
@ -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
7
views/dialogs/__init__.py
Normale Datei
@ -0,0 +1,7 @@
|
||||
"""
|
||||
Dialog-Module für die AccountForger Anwendung.
|
||||
"""
|
||||
|
||||
from .license_activation_dialog import LicenseActivationDialog
|
||||
|
||||
__all__ = ['LicenseActivationDialog']
|
||||
69
views/dialogs/account_creation_result_dialog.py
Normale Datei
69
views/dialogs/account_creation_result_dialog.py
Normale Datei
@ -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
|
||||
262
views/dialogs/license_activation_dialog.py
Normale Datei
262
views/dialogs/license_activation_dialog.py
Normale Datei
@ -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
241
views/main_window.py
Normale Datei
@ -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
129
views/platform_selector.py
Normale Datei
@ -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
331
views/tabs/accounts_tab.py
Normale Datei
@ -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
318
views/tabs/generator_tab.py
Normale Datei
@ -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"])
|
||||
508
views/tabs/generator_tab_modern.py
Normale Datei
508
views/tabs/generator_tab_modern.py
Normale Datei
@ -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
315
views/tabs/settings_tab.py
Normale Datei
@ -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
420
views/widgets/account_card.py
Normale Datei
@ -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()
|
||||
209
views/widgets/account_creation_modal.py
Normale Datei
209
views/widgets/account_creation_modal.py
Normale Datei
@ -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"
|
||||
625
views/widgets/account_creation_modal_v2.py
Normale Datei
625
views/widgets/account_creation_modal_v2.py
Normale Datei
@ -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_())
|
||||
272
views/widgets/forge_animation_widget.py
Normale Datei
272
views/widgets/forge_animation_widget.py
Normale Datei
@ -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
|
||||
370
views/widgets/forge_animation_widget_v2.py
Normale Datei
370
views/widgets/forge_animation_widget_v2.py
Normale Datei
@ -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
192
views/widgets/icon_factory.py
Normale Datei
@ -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)
|
||||
203
views/widgets/language_dropdown.py
Normale Datei
203
views/widgets/language_dropdown.py
Normale Datei
@ -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)
|
||||
295
views/widgets/login_process_modal.py
Normale Datei
295
views/widgets/login_process_modal.py
Normale Datei
@ -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
|
||||
316
views/widgets/modern_message_box.py
Normale Datei
316
views/widgets/modern_message_box.py
Normale Datei
@ -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_()
|
||||
89
views/widgets/platform_button.py
Normale Datei
89
views/widgets/platform_button.py
Normale Datei
@ -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;")
|
||||
312
views/widgets/progress_modal.py
Normale Datei
312
views/widgets/progress_modal.py
Normale Datei
@ -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)
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren