- 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>
136 lines
4.1 KiB
Python
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)
|