# Copyright 2026 European Union
# Author: Bulgheroni Antonio (antonio.bulgheroni@ec.europa.eu)
# SPDX-License-Identifier: EUPL-1.2
"""Reusable line editor with a role-aware filesystem browse button."""
from __future__ import annotations
from collections.abc import Callable
from pathlib import Path
from typing import Any
from PySide6.QtCore import QEvent, Qt, Signal
from PySide6.QtWidgets import QFileDialog, QHBoxLayout, QLineEdit, QToolButton, QWidget
from mafw.processor.path_types import PathRole
[docs]
class PathEditor(QWidget):
"""Inline path editor whose browse action depends on a :class:`~mafw.processor.PathRole`."""
value_changed = Signal(str)
editing_finished = Signal()
focused = Signal()
def __init__(
self,
parent: QWidget | None = None,
role: PathRole = PathRole.INPUT_FILE,
on_selected: Callable[[str], None] | None = None,
on_browse_started: Callable[[], None] | None = None,
on_browse_cancelled: Callable[[], None] | None = None,
) -> None:
"""Build the editor for the given *role*.
:param parent: Optional parent widget.
:param role: The browse behaviour to use; defaults to input-file selection.
:param on_selected: Optional callback for values chosen in a browse dialog.
"""
super().__init__(parent)
self._role = role
self._on_selected = on_selected
self._on_browse_started = on_browse_started
self._on_browse_cancelled = on_browse_cancelled
self._browsing = False
self._line_edit = QLineEdit()
self._line_edit.textChanged.connect(self.value_changed)
self._line_edit.editingFinished.connect(self._finish_editing)
self._line_edit.installEventFilter(self)
self._browse_button = QToolButton()
self._browse_button.setText('Browse')
self._browse_button.setFocusPolicy(Qt.FocusPolicy.NoFocus)
self._browse_button.clicked.connect(self._browse)
layout = QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(self._line_edit)
layout.addWidget(self._browse_button)
self.setFocusProxy(self._line_edit)
[docs]
def value(self) -> str:
"""Return the currently selected path string."""
return self._line_edit.text()
[docs]
def set_value(self, value: Any) -> None:
"""Update the displayed path string."""
self._line_edit.setText('' if value is None else str(value))
[docs]
def _browse(self) -> None:
"""Open the file dialog matching the configured path role."""
current = self._line_edit.text()
role = self._role
line_edit = self._line_edit
on_selected = self._on_selected
on_browse_started = self._on_browse_started
on_browse_cancelled = self._on_browse_cancelled
self._browsing = True
try:
if on_browse_started is not None:
on_browse_started()
if role is PathRole.DIRECTORY:
selected = QFileDialog.getExistingDirectory(None, 'Select folder', current)
elif role is PathRole.OUTPUT_FILE:
selected, _ = QFileDialog.getSaveFileName(None, 'Select new file', current)
else:
selected, _ = QFileDialog.getOpenFileName(None, 'Select file', current)
if selected:
path = str(Path(selected))
if on_selected is not None:
on_selected(path)
else:
line_edit.setText(path)
elif on_browse_cancelled is not None:
on_browse_cancelled()
finally:
self._browsing = False
[docs]
def _finish_editing(self) -> None:
"""Avoid committing a blank row while Browse temporarily owns focus."""
if not self._browsing:
self.editing_finished.emit()
[docs]
def eventFilter(self, watched: Any, event: QEvent) -> bool: # noqa: N802
"""Notify a containing table when the inline path field receives focus."""
if watched is self._line_edit and event.type() is QEvent.Type.FocusIn:
self.focused.emit()
return super().eventFilter(watched, event)