linux.sipsoftphone/ui/favoriten_panel.py
data 48ddb4b4af Initiales Release: SIP Softphone für FreePBX/Asterisk
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>
2026-02-18 13:18:54 +01:00

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)