PySide6/PJSUA2-basiertes Desktop-Softphone mit: - SIP-Telefonie (Anrufe, DTMF, Halten, Transfer, Konferenz) - CardDAV-Kontaktsync mit Write-Back (Multi-Account) - Kontaktverwaltung mit Suche und Bearbeiten-Dialog - Anrufliste mit Namensauflösung - Favoriten-Panel (manuell + häufig angerufen) - BLF-Panel (SIP Presence Monitoring) - Responsives Layout (kompakt/erweitert) - Click-to-Call (tel:/sip: URI via D-Bus) - KDE-Integration (Tray, Benachrichtigungen) - PipeWire/PulseAudio Audio-Routing - AppImage Build-Support (PyInstaller + appimagetool) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
188 lines
6.8 KiB
Python
188 lines
6.8 KiB
Python
"""Favoriten-Panel - Manuelle Favoriten + häufig angerufene Nummern."""
|
|
|
|
from collections import Counter
|
|
|
|
from PySide6.QtWidgets import (
|
|
QWidget, QVBoxLayout, QLabel, QPushButton, QScrollArea,
|
|
QSizePolicy,
|
|
)
|
|
from PySide6.QtCore import Signal, Qt
|
|
from PySide6.QtGui import QFont
|
|
|
|
|
|
class FavoritenPanel(QWidget):
|
|
"""Kompaktes Panel mit Favoriten und häufig angerufenen Nummern."""
|
|
|
|
# Signal: Nummer zum Anrufen
|
|
anrufen = Signal(str)
|
|
# Signal: Favoriten-Liste hat sich geändert
|
|
favoriten_geaendert = Signal()
|
|
|
|
def __init__(self, config_manager, parent=None):
|
|
super().__init__(parent)
|
|
self._config = config_manager
|
|
self._kontakte_liste = [] # Für Namensauflösung
|
|
self._ui_aufbauen()
|
|
|
|
def _ui_aufbauen(self):
|
|
layout = QVBoxLayout(self)
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
layout.setSpacing(2)
|
|
|
|
# Überschrift
|
|
titel = QLabel("Favoriten")
|
|
titel_font = QFont()
|
|
titel_font.setPointSize(11)
|
|
titel_font.setBold(True)
|
|
titel.setFont(titel_font)
|
|
titel.setStyleSheet("padding: 4px;")
|
|
layout.addWidget(titel)
|
|
|
|
# Scrollbarer Bereich für die Buttons
|
|
scroll = QScrollArea()
|
|
scroll.setWidgetResizable(True)
|
|
scroll.setFrameShape(QScrollArea.Shape.NoFrame)
|
|
scroll.setHorizontalScrollBarPolicy(
|
|
Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
|
|
|
self._inhalt = QWidget()
|
|
self._inhalt_layout = QVBoxLayout(self._inhalt)
|
|
self._inhalt_layout.setContentsMargins(0, 0, 0, 0)
|
|
self._inhalt_layout.setSpacing(2)
|
|
self._inhalt_layout.addStretch()
|
|
|
|
scroll.setWidget(self._inhalt)
|
|
layout.addWidget(scroll)
|
|
|
|
def aktualisieren(self):
|
|
"""Favoriten-Anzeige komplett neu aufbauen."""
|
|
# Alte Buttons entfernen
|
|
while self._inhalt_layout.count() > 1: # Stretch behalten
|
|
item = self._inhalt_layout.takeAt(0)
|
|
if item.widget():
|
|
item.widget().deleteLater()
|
|
|
|
max_anzeige = (
|
|
self._config.get("favoriten", "max_anzeige") or 10)
|
|
|
|
# Manuelle Favoriten
|
|
manuell = self._config.get("favoriten", "manuell") or []
|
|
for fav in manuell[:max_anzeige]:
|
|
name = fav.get("name", "")
|
|
nummer = fav.get("nummer", "")
|
|
if nummer:
|
|
self._button_hinzufuegen(name, nummer, manuell=True)
|
|
|
|
# Häufig angerufen (Rest auffüllen)
|
|
rest = max_anzeige - len(manuell)
|
|
if rest > 0:
|
|
haeufige = self._haeufige_nummern(rest, manuell)
|
|
if haeufige and manuell:
|
|
# Trennlinie
|
|
trenn = QLabel("Häufig angerufen")
|
|
trenn.setStyleSheet(
|
|
"color: #888; font-size: 10px; padding: 6px 4px 2px;")
|
|
self._inhalt_layout.insertWidget(
|
|
self._inhalt_layout.count() - 1, trenn)
|
|
for nummer, anzahl in haeufige:
|
|
name = self._name_fuer_nummer(nummer)
|
|
self._button_hinzufuegen(
|
|
name or nummer, nummer, manuell=False)
|
|
|
|
def _button_hinzufuegen(self, name, nummer, manuell=False):
|
|
"""Einen Favoriten-Button ins Layout einfügen."""
|
|
if name and name != nummer:
|
|
text = f"{name}\n{nummer}"
|
|
else:
|
|
text = nummer
|
|
|
|
farbe = "#FFD700" if manuell else "#ddd"
|
|
hover_bg = "rgba(255,215,0,0.12)" if manuell else "rgba(76,175,80,0.12)"
|
|
|
|
btn = QPushButton(text)
|
|
btn.setStyleSheet(
|
|
f"QPushButton {{ "
|
|
f" text-align: left; padding: 8px 10px; "
|
|
f" font-size: 12px; border: 1px solid #555; "
|
|
f" border-radius: 6px; background: rgba(255,255,255,0.04); "
|
|
f" color: {farbe}; "
|
|
f"}}"
|
|
f"QPushButton:hover {{ "
|
|
f" background: {hover_bg}; border-color: {farbe}; "
|
|
f"}}"
|
|
f"QPushButton:pressed {{ "
|
|
f" background: rgba(255,255,255,0.1); "
|
|
f"}}"
|
|
)
|
|
btn.setCursor(Qt.CursorShape.PointingHandCursor)
|
|
btn.setSizePolicy(
|
|
QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
|
btn.setMinimumHeight(40 if "\n" in text else 34)
|
|
btn.setToolTip(f"Anrufen: {nummer}")
|
|
|
|
btn.clicked.connect(
|
|
lambda checked, n=nummer: self.anrufen.emit(n))
|
|
|
|
# Vor dem Stretch einfügen
|
|
self._inhalt_layout.insertWidget(
|
|
self._inhalt_layout.count() - 1, btn)
|
|
|
|
def _haeufige_nummern(self, limit, manuell_favoriten):
|
|
"""Top-N häufig angerufene Nummern aus der Anrufliste berechnen."""
|
|
anrufe = self._config.anrufliste_laden()
|
|
if not anrufe:
|
|
return []
|
|
|
|
# Manuelle Favoriten-Nummern (zum Ausschließen)
|
|
manuell_nummern = {
|
|
f.get("nummer", "") for f in manuell_favoriten}
|
|
|
|
# Zählen
|
|
zaehler = Counter()
|
|
for anruf in anrufe:
|
|
nummer = anruf.get("nummer", "")
|
|
if nummer and nummer not in manuell_nummern:
|
|
zaehler[nummer] += 1
|
|
|
|
return zaehler.most_common(limit)
|
|
|
|
def _name_fuer_nummer(self, nummer):
|
|
"""Name für eine Nummer in der Kontaktliste suchen."""
|
|
for kontakt in self._kontakte_liste:
|
|
nummern = kontakt.get("nummern", {})
|
|
if nummer in nummern.values():
|
|
return kontakt.get("name", "")
|
|
if kontakt.get("nummer") == nummer:
|
|
return kontakt.get("name", "")
|
|
return ""
|
|
|
|
def kontakte_setzen(self, kontakte):
|
|
"""Kontaktliste für die Namensauflösung setzen."""
|
|
self._kontakte_liste = kontakte or []
|
|
|
|
def favorit_hinzufuegen(self, name, nummer):
|
|
"""Kontakt als manuellen Favoriten hinzufügen."""
|
|
manuell = self._config.get("favoriten", "manuell") or []
|
|
# Duplikat prüfen
|
|
for fav in manuell:
|
|
if fav.get("nummer") == nummer:
|
|
return # Schon vorhanden
|
|
manuell.append({"name": name, "nummer": nummer})
|
|
self._config.set("favoriten", "manuell", manuell)
|
|
self._config.speichern()
|
|
self.aktualisieren()
|
|
self.favoriten_geaendert.emit()
|
|
|
|
def favorit_entfernen(self, nummer):
|
|
"""Kontakt aus manuellen Favoriten entfernen."""
|
|
manuell = self._config.get("favoriten", "manuell") or []
|
|
manuell = [f for f in manuell if f.get("nummer") != nummer]
|
|
self._config.set("favoriten", "manuell", manuell)
|
|
self._config.speichern()
|
|
self.aktualisieren()
|
|
self.favoriten_geaendert.emit()
|
|
|
|
def ist_favorit(self, nummer):
|
|
"""Prüft ob eine Nummer als manueller Favorit gespeichert ist."""
|
|
manuell = self._config.get("favoriten", "manuell") or []
|
|
return any(f.get("nummer") == nummer for f in manuell)
|