Fix: Gelöschte Dateien verschwinden sofort aus der Liste
- Gelöschte Einträge werden direkt aus dem Model entfernt statt Verzeichnis neu zu laden - Verzeichnis-Cache wird vor dem Einlesen invalidiert (os.listdir statt os.scandir) - Selektion wird beim Refresh/Löschen zurückgesetzt - PDF Preview: Darkmode Toggle hinzugefügt Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
1f43e234d3
commit
044e9a848d
3 changed files with 99 additions and 13 deletions
|
|
@ -386,6 +386,7 @@ class MainWindow(QMainWindow):
|
||||||
self.file_list.folder_entered.connect(self._navigate_to)
|
self.file_list.folder_entered.connect(self._navigate_to)
|
||||||
self.file_list.file_rename_requested.connect(self._rename_file)
|
self.file_list.file_rename_requested.connect(self._rename_file)
|
||||||
self.file_list.file_delete_requested.connect(self._delete_file)
|
self.file_list.file_delete_requested.connect(self._delete_file)
|
||||||
|
self.file_list.files_delete_requested.connect(self._delete_files)
|
||||||
self.file_list.file_move_requested.connect(self._move_file)
|
self.file_list.file_move_requested.connect(self._move_file)
|
||||||
self.file_list.files_dropped.connect(self._handle_files_dropped)
|
self.file_list.files_dropped.connect(self._handle_files_dropped)
|
||||||
|
|
||||||
|
|
@ -648,16 +649,22 @@ class MainWindow(QMainWindow):
|
||||||
dialog = DeleteDialog(paths, self)
|
dialog = DeleteDialog(paths, self)
|
||||||
if dialog.exec():
|
if dialog.exec():
|
||||||
errors = []
|
errors = []
|
||||||
|
deleted_paths = []
|
||||||
for path in paths:
|
for path in paths:
|
||||||
try:
|
try:
|
||||||
if os.path.isdir(path):
|
if os.path.isdir(path):
|
||||||
shutil.rmtree(path)
|
shutil.rmtree(path)
|
||||||
else:
|
else:
|
||||||
os.remove(path)
|
os.remove(path)
|
||||||
|
deleted_paths.append(path)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
errors.append(f"{os.path.basename(path)}: {e}")
|
errors.append(f"{os.path.basename(path)}: {e}")
|
||||||
|
|
||||||
self._refresh()
|
# Gelöschte Einträge direkt aus der Liste entfernen
|
||||||
|
if deleted_paths:
|
||||||
|
self.file_list.remove_paths(deleted_paths)
|
||||||
|
self.folder_tree.refresh()
|
||||||
|
self._update_count()
|
||||||
|
|
||||||
if errors:
|
if errors:
|
||||||
QMessageBox.warning(
|
QMessageBox.warning(
|
||||||
|
|
|
||||||
|
|
@ -116,25 +116,33 @@ class FileListModel(QAbstractTableModel):
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
entries = os.scandir(path)
|
# Verzeichnis-Cache invalidieren
|
||||||
|
try:
|
||||||
|
fd = os.open(path, os.O_RDONLY | os.O_DIRECTORY)
|
||||||
|
os.close(fd)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
folders = []
|
folders = []
|
||||||
files = []
|
files = []
|
||||||
|
|
||||||
for entry in entries:
|
for name in os.listdir(path):
|
||||||
|
entry_path = os.path.join(path, name)
|
||||||
try:
|
try:
|
||||||
stat = entry.stat()
|
stat = os.stat(entry_path)
|
||||||
|
is_dir = os.path.isdir(entry_path)
|
||||||
item = FileItem(
|
item = FileItem(
|
||||||
name=entry.name,
|
name=name,
|
||||||
path=entry.path,
|
path=entry_path,
|
||||||
is_dir=entry.is_dir(),
|
is_dir=is_dir,
|
||||||
size=stat.st_size if entry.is_file() else 0,
|
size=stat.st_size if not is_dir else 0,
|
||||||
modified=datetime.fromtimestamp(stat.st_mtime)
|
modified=datetime.fromtimestamp(stat.st_mtime)
|
||||||
)
|
)
|
||||||
if entry.is_dir():
|
if is_dir:
|
||||||
folders.append(item)
|
folders.append(item)
|
||||||
else:
|
else:
|
||||||
files.append(item)
|
files.append(item)
|
||||||
except (PermissionError, OSError):
|
except (PermissionError, OSError, FileNotFoundError):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Natürliche Sortierung
|
# Natürliche Sortierung
|
||||||
|
|
@ -159,6 +167,18 @@ class FileListModel(QAbstractTableModel):
|
||||||
if self.current_path:
|
if self.current_path:
|
||||||
self.set_path(self.current_path)
|
self.set_path(self.current_path)
|
||||||
|
|
||||||
|
def remove_paths(self, paths: list[str]):
|
||||||
|
"""Entfernt Einträge mit den angegebenen Pfaden aus dem Model."""
|
||||||
|
paths_set = set(paths)
|
||||||
|
# Finde Indizes der zu löschenden Items (von hinten nach vorne)
|
||||||
|
indices_to_remove = [i for i, item in enumerate(self.items) if item.path in paths_set]
|
||||||
|
indices_to_remove.sort(reverse=True)
|
||||||
|
|
||||||
|
for idx in indices_to_remove:
|
||||||
|
self.beginRemoveRows(QModelIndex(), idx, idx)
|
||||||
|
del self.items[idx]
|
||||||
|
self.endRemoveRows()
|
||||||
|
|
||||||
|
|
||||||
class FileListWidget(QTableView):
|
class FileListWidget(QTableView):
|
||||||
"""Dateiliste mit Kontextmenü und Drag & Drop."""
|
"""Dateiliste mit Kontextmenü und Drag & Drop."""
|
||||||
|
|
@ -167,7 +187,8 @@ class FileListWidget(QTableView):
|
||||||
file_double_clicked = pyqtSignal(str) # path
|
file_double_clicked = pyqtSignal(str) # path
|
||||||
folder_entered = pyqtSignal(str) # path
|
folder_entered = pyqtSignal(str) # path
|
||||||
file_rename_requested = pyqtSignal(str) # path
|
file_rename_requested = pyqtSignal(str) # path
|
||||||
file_delete_requested = pyqtSignal(str) # path
|
file_delete_requested = pyqtSignal(str) # path (einzelne Datei)
|
||||||
|
files_delete_requested = pyqtSignal(object) # paths (mehrere Dateien)
|
||||||
file_move_requested = pyqtSignal(str) # path
|
file_move_requested = pyqtSignal(str) # path
|
||||||
files_dropped = pyqtSignal(list, str) # source_paths, target_folder
|
files_dropped = pyqtSignal(list, str) # source_paths, target_folder
|
||||||
|
|
||||||
|
|
@ -433,6 +454,14 @@ class FileListWidget(QTableView):
|
||||||
props_action.triggered.connect(lambda: self.properties_requested.emit([item.path]))
|
props_action.triggered.connect(lambda: self.properties_requested.emit([item.path]))
|
||||||
menu.addAction(props_action)
|
menu.addAction(props_action)
|
||||||
|
|
||||||
|
menu.addSeparator()
|
||||||
|
|
||||||
|
# Aktualisieren
|
||||||
|
refresh_action = QAction("🔄 Aktualisieren", self)
|
||||||
|
refresh_action.setShortcut("F5")
|
||||||
|
refresh_action.triggered.connect(self.refresh_requested.emit)
|
||||||
|
menu.addAction(refresh_action)
|
||||||
|
|
||||||
def _build_multi_item_context_menu(self, menu: QMenu, items: list[FileItem]):
|
def _build_multi_item_context_menu(self, menu: QMenu, items: list[FileItem]):
|
||||||
"""Erstellt Kontextmenü für mehrere Elemente."""
|
"""Erstellt Kontextmenü für mehrere Elemente."""
|
||||||
paths = [item.path for item in items]
|
paths = [item.path for item in items]
|
||||||
|
|
@ -470,6 +499,14 @@ class FileListWidget(QTableView):
|
||||||
props_action.triggered.connect(lambda: self.properties_requested.emit(paths))
|
props_action.triggered.connect(lambda: self.properties_requested.emit(paths))
|
||||||
menu.addAction(props_action)
|
menu.addAction(props_action)
|
||||||
|
|
||||||
|
menu.addSeparator()
|
||||||
|
|
||||||
|
# Aktualisieren
|
||||||
|
refresh_action = QAction("🔄 Aktualisieren", self)
|
||||||
|
refresh_action.setShortcut("F5")
|
||||||
|
refresh_action.triggered.connect(self.refresh_requested.emit)
|
||||||
|
menu.addAction(refresh_action)
|
||||||
|
|
||||||
def _open_external(self, path: str):
|
def _open_external(self, path: str):
|
||||||
"""Öffnet eine Datei mit der Standard-Anwendung."""
|
"""Öffnet eine Datei mit der Standard-Anwendung."""
|
||||||
try:
|
try:
|
||||||
|
|
@ -522,8 +559,8 @@ class FileListWidget(QTableView):
|
||||||
|
|
||||||
def _delete_items(self, items: list):
|
def _delete_items(self, items: list):
|
||||||
"""Löscht mehrere Elemente."""
|
"""Löscht mehrere Elemente."""
|
||||||
for item in items:
|
paths = [item.path for item in items]
|
||||||
self.file_delete_requested.emit(item.path)
|
self.files_delete_requested.emit(paths)
|
||||||
|
|
||||||
def get_selected_items(self) -> list[FileItem]:
|
def get_selected_items(self) -> list[FileItem]:
|
||||||
"""Gibt die ausgewählten Elemente zurück."""
|
"""Gibt die ausgewählten Elemente zurück."""
|
||||||
|
|
@ -542,8 +579,14 @@ class FileListWidget(QTableView):
|
||||||
|
|
||||||
def refresh(self):
|
def refresh(self):
|
||||||
"""Aktualisiert die Dateiliste."""
|
"""Aktualisiert die Dateiliste."""
|
||||||
|
self.clearSelection()
|
||||||
self.list_model.refresh()
|
self.list_model.refresh()
|
||||||
|
|
||||||
|
def remove_paths(self, paths: list[str]):
|
||||||
|
"""Entfernt Einträge mit den angegebenen Pfaden aus der Liste."""
|
||||||
|
self.clearSelection()
|
||||||
|
self.list_model.remove_paths(paths)
|
||||||
|
|
||||||
# Drag & Drop
|
# Drag & Drop
|
||||||
def startDrag(self, supportedActions):
|
def startDrag(self, supportedActions):
|
||||||
"""Startet einen Drag-Vorgang."""
|
"""Startet einen Drag-Vorgang."""
|
||||||
|
|
|
||||||
|
|
@ -532,6 +532,16 @@ class PdfPreview(QWidget):
|
||||||
self.draw_btn.clicked.connect(self._open_in_draw)
|
self.draw_btn.clicked.connect(self._open_in_draw)
|
||||||
toolbar_layout.addWidget(self.draw_btn)
|
toolbar_layout.addWidget(self.draw_btn)
|
||||||
|
|
||||||
|
toolbar_layout.addSpacing(8)
|
||||||
|
|
||||||
|
# Darkmode Toggle
|
||||||
|
self.darkmode_btn = QPushButton("🌙")
|
||||||
|
self.darkmode_btn.setToolTip("Darkmode umschalten")
|
||||||
|
self.darkmode_btn.setFixedSize(32, 28)
|
||||||
|
self.darkmode_btn.setCheckable(True)
|
||||||
|
self.darkmode_btn.clicked.connect(self._toggle_darkmode)
|
||||||
|
toolbar_layout.addWidget(self.darkmode_btn)
|
||||||
|
|
||||||
layout.addWidget(toolbar)
|
layout.addWidget(toolbar)
|
||||||
|
|
||||||
# PDF Document und View
|
# PDF Document und View
|
||||||
|
|
@ -564,9 +574,14 @@ class PdfPreview(QWidget):
|
||||||
self.page_combo.setCurrentIndex(i)
|
self.page_combo.setCurrentIndex(i)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
# Darkmode
|
||||||
|
darkmode = self.settings.value('pdf_darkmode', False, type=bool)
|
||||||
|
self.darkmode_btn.setChecked(darkmode)
|
||||||
|
|
||||||
# Einstellungen anwenden
|
# Einstellungen anwenden
|
||||||
self._apply_zoom_mode()
|
self._apply_zoom_mode()
|
||||||
self._apply_page_mode()
|
self._apply_page_mode()
|
||||||
|
self._apply_darkmode(darkmode)
|
||||||
|
|
||||||
def _save_settings(self):
|
def _save_settings(self):
|
||||||
"""Speichert PDF-Einstellungen."""
|
"""Speichert PDF-Einstellungen."""
|
||||||
|
|
@ -614,6 +629,27 @@ class PdfPreview(QWidget):
|
||||||
self.pdf_view.setPageMode(mode)
|
self.pdf_view.setPageMode(mode)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
def _toggle_darkmode(self):
|
||||||
|
"""Schaltet den Darkmode um."""
|
||||||
|
is_dark = self.darkmode_btn.isChecked()
|
||||||
|
self._apply_darkmode(is_dark)
|
||||||
|
self.settings.setValue('pdf_darkmode', is_dark)
|
||||||
|
|
||||||
|
def _apply_darkmode(self, enabled: bool):
|
||||||
|
"""Wendet den Darkmode an."""
|
||||||
|
if enabled:
|
||||||
|
self.darkmode_btn.setText("☀️")
|
||||||
|
self.darkmode_btn.setToolTip("Lightmode umschalten")
|
||||||
|
self.pdf_view.setStyleSheet("""
|
||||||
|
QPdfView {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
else:
|
||||||
|
self.darkmode_btn.setText("🌙")
|
||||||
|
self.darkmode_btn.setToolTip("Darkmode umschalten")
|
||||||
|
self.pdf_view.setStyleSheet("")
|
||||||
|
|
||||||
def load_pdf(self, path: str) -> bool:
|
def load_pdf(self, path: str) -> bool:
|
||||||
"""Lädt eine PDF-Datei."""
|
"""Lädt eine PDF-Datei."""
|
||||||
self._current_path = path
|
self._current_path = path
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue