# Copyright 2025–2026 European Union
# Author: Bulgheroni Antonio (antonio.bulgheroni@ec.europa.eu)
# SPDX-License-Identifier: EUPL-1.2
"""
Recent files management for the RadiViz application.
This module provides functionality for tracking recently opened files and
managing a menu that allows users to quickly access these files.
The :class:`RecentFilesManager` class handles the core logic for managing
recently opened files, including persistence across application sessions,
automatic cleanup of non-existent files, and integration with Qt menus.
"""
from __future__ import annotations
import json
import os
from pathlib import Path
from typing import Callable, List, Optional
from PySide6.QtCore import QObject, QSettings, Signal
from PySide6.QtWidgets import QMenu
[docs]
class RecentFilesManager(QObject):
"""
Manages a list of recently opened files and provides a menu for them.
This class tracks a configurable number of recently opened files, persists
them between application sessions, and provides functionality to update
a menu with the recent files list.
:param max_files: Maximum number of recent files to track
:type max_files: int
"""
changed = Signal()
def __init__(self, max_files: Optional[int] = 5) -> None:
"""
Initialize the RecentFilesManager.
Creates a new instance of the RecentFilesManager with the specified
maximum number of files to track.
:param max_files: Maximum number of recent files to track, defaults to 5
:type max_files: int, optional
"""
super().__init__()
self.max_files: Optional[int] = max_files
self.recent_files: List[str] = []
self.settings = QSettings('JRC', 'RadioViz')
self.menu: Optional[QMenu] = None
self.open_callback: Optional[Callable[[Path], None]] = None
self.load_recent_files()
[docs]
def add_file(self, file_path: str | Path) -> None:
"""
Add a file to the recent files list.
The file is added to the top of the list. If the file is already in the
list, it is first removed and then added to the top. If adding the file
would exceed the maximum number of files, the oldest file is removed.
:param file_path: Path to the file to add
:type file_path: str | Path
"""
# Convert to absolute path
file_path = str(Path(file_path).resolve())
# If the file is already in the list, remove it so it can be added to the top
if file_path in self.recent_files:
self.recent_files.remove(file_path)
# Add to the top of the list
self.recent_files.insert(0, file_path)
# Trim the list if needed
if self.max_files is not None and len(self.recent_files) > self.max_files:
self.recent_files = self.recent_files[: self.max_files]
# Save the updated list
self.save_recent_files()
# inform that the recent files have been changed
self.changed.emit()
[docs]
def remove_file(self, file_path: str | Path) -> None:
"""
Remove a file from the recent files list.
:param file_path: Path to the file to remove
:type file_path: str | Path
"""
# Convert to absolute path
file_path = str(Path(file_path).resolve())
# If the file is already in the list, remove it so it can be added to the top
if file_path in self.recent_files:
self.recent_files.remove(file_path)
# Save the updated list
self.save_recent_files()
self.changed.emit()
[docs]
def get_files(self) -> list[Path]:
"""
Get the list of recent files.
This method filters out any files that no longer exist on the filesystem
before returning the list.
:return: List of recent file paths
:rtype: list[pathlib.Path]
"""
# Filter out any files that no longer exist
self.recent_files = [f for f in self.recent_files if os.path.exists(f)]
return [Path(f) for f in self.recent_files]
[docs]
def clear_files(self) -> None:
"""
Clear the recent files list.
Removes all files from the recent files list and updates the saved settings.
"""
self.recent_files = []
self.save_recent_files()
self.changed.emit()
[docs]
def save_recent_files(self) -> None:
"""
Save the recent files list to settings.
Persists the recent files list to the application settings as a JSON string.
"""
self.settings.setValue('recentFiles', json.dumps(self.recent_files))
[docs]
def load_recent_files(self) -> None:
"""
Load the recent files list from settings.
Retrieves the recent files list from the application settings. If no list
exists or an error occurs during loading, an empty list is used.
:raise json.JSONDecodeError: if the stored data is not valid JSON
:raise TypeError: if the stored data is not a string
"""
if self.settings.contains('recentFiles'):
try:
self.recent_files = json.loads(self.settings.value('recentFiles'))
except (json.JSONDecodeError, TypeError):
self.recent_files = []
else:
self.recent_files = []