Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt pytest ruff Pillow
pip install -r requirements.txt pytest pytest-xdist ruff Pillow

- name: Compile sources
run: |
Expand All @@ -59,4 +59,4 @@ jobs:
PYTHONPATH: ${{ github.workspace }}
QT_QPA_PLATFORM: offscreen
run: |
python -m pytest -ra -v
python -m pytest -ra -v -n 2
18 changes: 18 additions & 0 deletions STORE_ISSUES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# ExplorerPro – Store-Fehler und Versionsstand

## Installierte Store-Version: 1.0.0.0

Lokaler Paketbefund vom 28.09.2026: `Geiger.ExplorerPro_1.0.0.0_neutral__9jp3kxz2tnb3w`; die enthaltene `ExplorerPro.exe` stammt vom 30.08.2026. Das lokal gebaute Paket `releases/ExplorerPro.msix` hat dagegen die Identity `1.0.6.0` und eine andere EXE. Ein neuer Store-Release ist mit diesem Befund nicht belegt.

## Offene Fehler

| Ticket | Schwere | Beschreibung | Fundort | Status |
|---|---|---|---|---|
| T-20260928-288887892 | P1 | Sidebar und Vorschau können nach einem Neustart trotz aktivierter Ansicht auf Breite null bleiben. | `src/app.py`, `src/gui/main_window.py` | IN ARBEIT |
| T-20260928-288887892 | P1 | Ein Ordnerwechsel aktualisiert den Pfad, lädt aber keine Dateizeilen; dadurch bleiben Vorschau, Umbenennen und Dateiaktionen unerreichbar. | `src/gui/browser/file_browser.py` | IN ARBEIT |
| T-20260928-288887892 | P1 | Umbenennen und Kontextmenü-Aktionen müssen am installierten Paket und am neuen Build tatsächlich ausgelöst werden; der installierte Binärstand enthält den Fix vom 26.09.2026 nicht. | `src/gui/browser/file_browser.py`, Paketversion | IN ARBEIT |
| T-20260928-288887892 | P1 | „Python-Skript ausführen“ startet im gefrorenen Build erneut ExplorerPro statt eines Python-Interpreters. | `src/modules/editor/quick_editor.py` | IN ARBEIT |

## Geplanter nächster Build: 1.0.7.0

Der lokale Build und der Test der entpackten Anwendung gehören zum Ticket. Eine Store-Einreichung ist nicht beauftragt. Vor einer späteren Einreichung sind die Icon-Collage und das Sicht-OK des Nutzers erforderlich.
33 changes: 24 additions & 9 deletions src/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,23 @@ def _load_settings(self):
self.center_on_screen()

# Splitter-Größen
main_sizes = settings.value("splitter/main")
if main_sizes:
self.main_splitter.setSizes([int(s) for s in main_sizes])

right_sizes = settings.value("splitter/right")
if right_sizes:
self.right_splitter.setSizes([int(s) for s in right_sizes])
for key, splitter in (
("splitter/main", self.main_splitter),
("splitter/right", self.right_splitter),
):
saved = settings.value(key)
try:
sizes = [int(size) for size in saved]
except (TypeError, ValueError):
continue
# Qt saves a hidden splitter child as size 0. Restoring that value
# collapses the panel even though its View action remains checked.
if len(sizes) == splitter.count() and all(size > 0 for size in sizes):
splitter.setSizes(sizes)

# Older sessions saved zero-width panels while their menu actions stayed checked.
self._toggle_sidebar()
self._toggle_preview()

def _setup_connections(self):
"""Verbindet Signale und Slots"""
Expand Down Expand Up @@ -242,8 +252,13 @@ def closeEvent(self, event):
# Einstellungen speichern
settings = QSettings()
settings.setValue("window/geometry", self.saveGeometry())
settings.setValue("splitter/main", self.main_splitter.sizes())
settings.setValue("splitter/right", self.right_splitter.sizes())
for key, splitter in (
("splitter/main", self.main_splitter),
("splitter/right", self.right_splitter),
):
sizes = splitter.sizes()
if all(size > 0 for size in sizes):
settings.setValue(key, sizes)

logging.info("ExplorerPro beendet")
super().closeEvent(event)
19 changes: 10 additions & 9 deletions src/gui/browser/file_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@ def _setup_ui(self):

# Datei-System-Model
self.model = QFileSystemModel()
self.model.setRootPath("")
self.model.setFilter(
QDir.Filter.AllEntries |
QDir.Filter.NoDotAndDotDot
Expand All @@ -131,9 +130,6 @@ def _setup_ui(self):
# Tabellen-View (DnD-fähige Unterklasse für startDrag-Override)
self.table = _DnDTableView(self)
self.table.setModel(self.proxy)
self.table.setRootIndex(self.proxy.mapFromSource(
self.model.index(QDir.rootPath())
))

# Spalten konfigurieren
self.table.setSelectionBehavior(
Expand Down Expand Up @@ -219,7 +215,9 @@ def navigate_to(self, path: str):

self._current_path = path

source_index = self.model.index(path)
# QFileSystemModel loads directories asynchronously. index(path) alone
# can point at a valid directory while its rows remain empty forever.
source_index = self.model.setRootPath(path)
proxy_index = self.proxy.mapFromSource(source_index)
self.table.setRootIndex(proxy_index)

Expand All @@ -235,7 +233,7 @@ def go_back(self):
self._history_index -= 1
path = self._history[self._history_index]
self._current_path = path
source_index = self.model.index(path)
source_index = self.model.setRootPath(path)
proxy_index = self.proxy.mapFromSource(source_index)
self.table.setRootIndex(proxy_index)
self._update_file_count()
Expand All @@ -248,7 +246,7 @@ def go_forward(self):
self._history_index += 1
path = self._history[self._history_index]
self._current_path = path
source_index = self.model.index(path)
source_index = self.model.setRootPath(path)
proxy_index = self.proxy.mapFromSource(source_index)
self.table.setRootIndex(proxy_index)
self._update_file_count()
Expand All @@ -265,8 +263,8 @@ def go_up(self):
def refresh(self):
"""Aktualisiert die Ansicht"""
if self._current_path:
self.model.setRootPath("")
self.model.setRootPath(self._current_path)
source_index = self.model.setRootPath(self._current_path)
self.table.setRootIndex(self.proxy.mapFromSource(source_index))
self._update_file_count()

def set_show_hidden_files(self, show: bool):
Expand Down Expand Up @@ -305,6 +303,9 @@ def _show_context_menu(self, pos):
menu = QMenu(self)

if index.isValid():
# A right click on another row must act on that row, not on an old selection.
if not self.table.selectionModel().isRowSelected(index.row(), index.parent()):
self.table.selectRow(index.row())
source_index = self.proxy.mapToSource(index)
file_path = self.model.filePath(source_index)
is_file = os.path.isfile(file_path)
Expand Down
27 changes: 24 additions & 3 deletions src/gui/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -539,16 +539,37 @@ def _create_new_folder(self):

def _toggle_sidebar(self):
"""Sidebar ein-/ausblenden"""
self.sidebar.setVisible(self.toggle_sidebar.isChecked())
self._set_splitter_panel_visible(
self.main_splitter, self.sidebar, self.toggle_sidebar.isChecked(), 250
)

def _toggle_preview(self):
"""Preview ein-/ausblenden"""
self.preview_panel.setVisible(self.toggle_preview.isChecked())
self._set_splitter_panel_visible(
self.right_splitter, self.preview_panel, self.toggle_preview.isChecked(), 350
)

@staticmethod
def _set_splitter_panel_visible(splitter, panel, visible: bool, preferred_width: int):
"""Restore a panel collapsed to zero by a persisted splitter state."""
panel.setVisible(visible)
if not visible:
return
sizes = splitter.sizes()
index = splitter.indexOf(panel)
if index < 0 or sizes[index] > 0:
return
other = 1 - index
total = sum(sizes) or splitter.width() or preferred_width * 3
panel_width = min(preferred_width, max(1, total // 3))
sizes[index] = panel_width
sizes[other] = max(1, total - panel_width)
splitter.setSizes(sizes)

def show_file_metadata(self, path: str, focus_tags: bool = False):
"""Blendet das Vorschau-/Metadaten-Panel ein und zeigt die Datei dort an."""
self.toggle_preview.setChecked(True)
self.preview_panel.setVisible(True)
self._toggle_preview()
self.preview_panel.show_preview(path)
if focus_tags:
tags_edit = self.preview_panel.metadata_panel.tags_edit
Expand Down
36 changes: 35 additions & 1 deletion src/modules/editor/quick_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
"""

import json
import os
import shutil
import sys
from pathlib import Path
from typing import Optional
Expand All @@ -24,6 +26,31 @@
from .syntax_highlighter import get_lexer_for_extension


def find_python_interpreter() -> str | None:
"""Return a real Python executable, never the frozen ExplorerPro executable."""
if not getattr(sys, "frozen", False):
return sys.executable

configured = os.environ.get("EXPLORERPRO_PYTHON", "").strip().strip('"')
candidates = [configured] if configured else [shutil.which(name) for name in ("py", "python", "python3")]
for candidate in candidates:
if not candidate:
continue
path = Path(candidate)
if not path.is_absolute():
resolved = shutil.which(candidate)
if not resolved:
continue
path = Path(resolved)
try:
if (path.is_file() and path.stat().st_size > 0
and path.resolve() != Path(sys.executable).resolve()):
return str(path)
except OSError:
continue
return None


def _validate_json(text: str) -> tuple:
"""Validiert JSON-Text. Gibt (ok: bool, nachricht: str) zurück."""
try:
Expand Down Expand Up @@ -500,7 +527,14 @@ def _run_code(self):
self._process.finished.connect(self._process_finished)

if ext == '.py':
self._process.start(sys.executable, [self.filepath])
python = find_python_interpreter()
if python is None:
message = ("Kein Python-Interpreter gefunden. Installieren Sie Python oder "
"setzen Sie EXPLORERPRO_PYTHON auf die echte python.exe.")
self._add_output(message + "\n", "#F14C4C")
QMessageBox.warning(self, "Python-Skript ausführen", message)
return
self._process.start(python, [self.filepath])
elif ext == '.js':
self._process.start('node', [self.filepath])
else:
Expand Down
Loading
Loading