qt.filebrowser/src/utils/clipboard.py
data 1f43e234d3 v1.1.0: Vollständiges Rechtsklick-Kontextmenü
- Kopieren/Ausschneiden/Einfügen (Ctrl+C, X, V)
- Neue Datei mit Vorlagen (Python, JS, HTML, CSS, JSON, MD, Shell)
- Neuer Ordner erstellen
- Archiv-Funktionen (ZIP, TAR, TAR.GZ, TAR.BZ2, TAR.XZ)
- Entpacken von Archiven (+ 7z, RAR wenn installiert)
- Eigenschaften-Dialog (Größe, Berechtigungen, Zeitstempel)
- Terminal hier öffnen (F4)
- Hover-Farben für Themes korrigiert
- README.md Dokumentation hinzugefügt

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-10 09:42:52 +01:00

136 lines
4.1 KiB
Python

"""Clipboard-Manager für Dateioperationen."""
import os
import shutil
from enum import Enum
from typing import Optional
from PyQt6.QtCore import QObject, pyqtSignal
class ClipboardMode(Enum):
"""Modus für Zwischenablage-Operationen."""
COPY = "copy"
CUT = "cut"
class FileClipboard(QObject):
"""Singleton-Clipboard für Dateioperationen."""
_instance: Optional['FileClipboard'] = None
# Signale
clipboard_changed = pyqtSignal()
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self):
if self._initialized:
return
super().__init__()
self._files: list[str] = []
self._mode: ClipboardMode = ClipboardMode.COPY
self._initialized = True
@classmethod
def instance(cls) -> 'FileClipboard':
"""Gibt die Singleton-Instanz zurück."""
if cls._instance is None:
cls._instance = FileClipboard()
return cls._instance
def copy(self, paths: list[str]):
"""Kopiert Dateien in die Zwischenablage."""
self._files = [p for p in paths if os.path.exists(p)]
self._mode = ClipboardMode.COPY
self.clipboard_changed.emit()
def cut(self, paths: list[str]):
"""Schneidet Dateien aus (zum Verschieben)."""
self._files = [p for p in paths if os.path.exists(p)]
self._mode = ClipboardMode.CUT
self.clipboard_changed.emit()
def paste(self, target_folder: str) -> tuple[int, list[str]]:
"""
Fügt Dateien im Zielordner ein.
Returns:
Tuple (Anzahl erfolgreicher Operationen, Liste der Fehler)
"""
if not self._files or not os.path.isdir(target_folder):
return 0, ["Keine Dateien zum Einfügen oder ungültiger Zielordner"]
success_count = 0
errors = []
for source_path in self._files:
if not os.path.exists(source_path):
errors.append(f"{os.path.basename(source_path)}: Datei nicht gefunden")
continue
name = os.path.basename(source_path)
target_path = os.path.join(target_folder, name)
# Bei Namenskonflikt umbenennen
target_path = self._get_unique_path(target_path)
try:
if self._mode == ClipboardMode.COPY:
if os.path.isdir(source_path):
shutil.copytree(source_path, target_path)
else:
shutil.copy2(source_path, target_path)
else: # CUT
shutil.move(source_path, target_path)
success_count += 1
except PermissionError:
errors.append(f"{name}: Keine Berechtigung")
except shutil.Error as e:
errors.append(f"{name}: {str(e)}")
except Exception as e:
errors.append(f"{name}: {str(e)}")
# Bei Ausschneiden die Zwischenablage leeren
if self._mode == ClipboardMode.CUT:
self.clear()
return success_count, errors
def _get_unique_path(self, path: str) -> str:
"""Generiert einen eindeutigen Pfad bei Namenskonflikten."""
if not os.path.exists(path):
return path
base, ext = os.path.splitext(path)
counter = 1
while os.path.exists(path):
path = f"{base} ({counter}){ext}"
counter += 1
return path
def clear(self):
"""Leert die Zwischenablage."""
self._files = []
self.clipboard_changed.emit()
def has_files(self) -> bool:
"""Prüft ob Dateien in der Zwischenablage sind."""
return len(self._files) > 0
def get_files(self) -> list[str]:
"""Gibt die Dateien in der Zwischenablage zurück."""
return self._files.copy()
def get_mode(self) -> ClipboardMode:
"""Gibt den aktuellen Modus zurück."""
return self._mode
def get_file_count(self) -> int:
"""Gibt die Anzahl der Dateien zurück."""
return len(self._files)