Skip to content
Open
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
2 changes: 2 additions & 0 deletions src/controller/file/read.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import proto.UniverseControl_pb2
from controller.file.deserialization.migrations import replace_old_filter_configurations
from controller.file.deserialization.post_load_operations import link_patched_fixtures
from controller.file.recently_used import register_opened_file
from controller.utils.process_notifications import get_process_notifier
from model import BoardConfiguration, Filter, Scene, UIPage, Universe
from model.color_hsi import ColorHSI
Expand Down Expand Up @@ -175,6 +176,7 @@ def read_document(file_name: str, board_configuration: BoardConfiguration) -> bo
logger.exception("Unable to update show UI window count: %s", e)
update_window_count(0, board_configuration)
board_configuration.broadcaster.show_file_loaded.emit()
register_opened_file(file_name)
pn.close()
return True

Expand Down
47 changes: 47 additions & 0 deletions src/controller/file/recently_used.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""Provides functions to query and update recently used files."""

from __future__ import annotations

import os

_STORAGE_PATH = os.path.join(os.path.expanduser("~"), ".local", "share", "missionDMX")
_STORAGE_FILE = os.path.join(_STORAGE_PATH, "recently_used.list")

if not os.path.exists(_STORAGE_PATH):
os.makedirs(_STORAGE_PATH, mode=0o770, exist_ok=True)

if not os.path.exists(_STORAGE_FILE):
with open(_STORAGE_FILE, "w") as f:
f.write("")
del f

def get_recently_used_files() -> list[str]:
"""Method returns the list of recently used files.

Returns:
A list of the recently used files in descending order.

"""
real_entries = []
with open(_STORAGE_FILE, "r") as f:
entries = f.readlines()
for entry in entries:
if entry.strip() == "":
continue
real_entries.append(entry.replace("\n", ""))
return real_entries

def register_opened_file(path: str) -> None:
"""Registers a file as being opened.

Args:
path: The path to the file which was opened.

"""
path = os.path.expanduser(path.strip())
existing_entries = get_recently_used_files()
new_entries = [path]
new_entries.extend(f"\n{entry}" for i, entry in enumerate(existing_entries) if
entry.strip() != "" and entry != path and i < 10)
with open(_STORAGE_FILE, "w") as output_file:
output_file.writelines(new_entries)
40 changes: 28 additions & 12 deletions src/view/dialogs/selection_dialog.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,53 @@
"""Contains a selection dialog."""

from collections.abc import Callable
from typing import override
from __future__ import annotations

from typing import TYPE_CHECKING, override

from PySide6.QtGui import QStandardItem, QStandardItemModel, Qt
from PySide6.QtWidgets import QAbstractItemView, QDialog, QDialogButtonBox, QFormLayout, QLabel, QListView, QWidget

if TYPE_CHECKING:
from collections.abc import Callable

from PySide6.QtWidgets import QStyledItemDelegate


class SelectionDialog(QDialog):
"""A dialog allowing the user to select items in a list."""

def __init__(self, title: str, message: str, items: list[str], parent: QWidget | None = None,
multi_selection_allowed: bool = True, selected_callback: Callable | None = None) -> None:
multi_selection_allowed: bool = True, selected_callback: Callable | None = None,
widget_delegate: QStyledItemDelegate | None = None) -> None:
"""Initialize the dialog.

It is possible to specify the item widget using widget_class.

Args:
title: The window title of the dialog.
message: The displayed message or help text of the dialog.
items: The list of items to present in the dialog.
parent: The parent Qt widget of the dialog.
multi_selection_allowed: Whether the dialog should allow selection of multiple items.
selected_callback: Optional callback function that will be called when selection is completed.
widget_delegate: A QStyledItemDelegate that can be used to style items.

"""
super().__init__(parent)
self._multi_selection_allowed = multi_selection_allowed
form = QFormLayout(self)
form.addRow(QLabel(message))
self.list_view = QListView(self)
if widget_delegate is not None:
self.list_view.setItemDelegate(widget_delegate)
self.list_view.setUniformItemSizes(False)
form.addRow(self.list_view)
model = QStandardItemModel(self.list_view)
self.setWindowTitle(title)
for item_name in items:
item = QStandardItem(item_name)
# TODO use radio buttons if multi_selection_allowed == False
item.setCheckable(True)
item.setCheckable(multi_selection_allowed)
item.setEditable(False)
model.appendRow(item)
self.list_view.setModel(model)
button_box = QDialogButtonBox(
Expand All @@ -50,14 +64,16 @@ def __init__(self, title: str, message: str, items: list[str], parent: QWidget |
@property
def selected_items(self) -> list[str]:
"""Get the items the user selected."""
selected = []
model = self.list_view.model()
i = 0
while model.item(i):
if model.item(i).checkState() == Qt.CheckState.Checked:
selected.append(model.item(i).text())
i += 1
return selected
if self._multi_selection_allowed:
selected = []
i = 0
while model.item(i):
if model.item(i).checkState() == Qt.CheckState.Checked:
selected.append(model.item(i).text())
i += 1
return selected
return [model.item(index.row()).text() for index in self.list_view.selectedIndexes()]

@override
def accept(self) -> None:
Expand Down
20 changes: 20 additions & 0 deletions src/view/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

import proto.RealTimeControl_pb2
import style
from controller.file.read import read_document
from controller.file.recently_used import get_recently_used_files
from controller.file.showfile_dialogs import _save_show_file, show_load_showfile_dialog, show_save_showfile_dialog
from controller.network import NetworkManager
from controller.utils.process_notifications import get_global_process_state, get_progress_changed_signal
Expand All @@ -23,13 +25,15 @@
from view.console_mode.console_universe_selector import UniverseSelector
from view.dialogs.asset_mgmt_dialog import AssetManagementDialog
from view.dialogs.colum_dialog import ColumnDialog
from view.dialogs.selection_dialog import SelectionDialog
from view.logging_view.logging_widget import LoggingWidget
from view.main_widget import MainWidget
from view.misc.console_dock_widget import ConsoleDockWidget
from view.misc.settings.settings_dialog import SettingsDialog
from view.patch_view.patch_mode import PatchMode
from view.show_mode.editor.showmanager import ShowEditorWidget
from view.show_mode.player.showplayer import ShowPlayerWidget
from view.utility_widgets.file_list_label import FileListLabel, FileListLabelDelegate
from view.utility_widgets.wizzards.patch_plan_export import PatchPlanExportWizard
from view.utility_widgets.wizzards.theater_scene_wizard import TheaterSceneWizard

Expand Down Expand Up @@ -191,6 +195,7 @@ def _setup_menubar(self) -> None:
],
"File": [
("&Load Showfile", lambda: show_load_showfile_dialog(self, self._board_configuration), "O"),
("Open Recent", self._open_recent, "Shift+O"),
("Save Showfile", self._save_show, "S"),
("&Save Showfile As", lambda: show_save_showfile_dialog(self, self._board_configuration), "Shift+S"),
("---", None, None),
Expand Down Expand Up @@ -405,3 +410,18 @@ def _toggle_terminal(self) -> None:
def _open_asset_mgmt_dialog(self) -> None:
self._settings_dialog = AssetManagementDialog(self, self._board_configuration.file_path)
self._settings_dialog.show()

def _open_recent(self) -> None:
recently_opened_show_files = get_recently_used_files()
self._settings_dialog = SelectionDialog("Open Recent", "Please select the show file to load.",
recently_opened_show_files, self, False,
self._open_file_selected, FileListLabelDelegate())
self._settings_dialog.setMinimumWidth(800)
self._settings_dialog.setMinimumHeight(600)
self._settings_dialog.show()

def _open_file_selected(self, diag: SelectionDialog) -> None:
if len(diag.selected_items) < 1:
return
read_document(diag.selected_items[0], self._board_configuration)
self._settings_dialog = None
79 changes: 79 additions & 0 deletions src/view/utility_widgets/file_list_label.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"""Contains a label displaying primary file name and path."""
from __future__ import annotations

import os
from typing import TYPE_CHECKING, override

from PySide6.QtCore import Qt
from PySide6.QtGui import QColor, QFontMetrics
from PySide6.QtWidgets import QHBoxLayout, QLabel, QStyle, QStyledItemDelegate, QVBoxLayout, QWidget

if TYPE_CHECKING:
from PySide6.QtCore import QModelIndex
from PySide6.QtGui import QPainter
from PySide6.QtWidgets import QStyleOptionViewItem


class FileListLabel(QWidget):
"""Displays primary file name and path."""

def __init__(self, path: str, parent: QWidget | None = None) -> None:
"""Initializes the label for the given path."""
super().__init__(parent)
layout = QVBoxLayout()
file_layout = QHBoxLayout()
primary_file_name, extension = os.path.splitext(os.path.basename(path))
primary_label = QLabel(primary_file_name)
primary_label.setStyleSheet("font-weight: bold; font-size: large;")
file_layout.addWidget(primary_label)
file_layout.addWidget(QLabel(extension))
file_layout.addStretch()
layout.addLayout(file_layout)
layout.addWidget(QLabel(path))
self.setLayout(layout)


class FileListLabelDelegate(QStyledItemDelegate):
"""Delegate for displaying primary file name and path."""

def __init__(self, parent: QWidget | None = None) -> None:
"""Initializes the delegate."""
super().__init__(parent)
self._color_background = QColor(64, 64, 64)
self._color_selected = QColor(64, 64, 100)

@override
def sizeHint(self, option: QStyleOptionViewItem, index: QModelIndex, /) -> None:
sh = super().sizeHint(option, index)
sh.setHeight(sh.height() * 3)
return sh

@override
def paint(self, painter: QPainter, option: QStyleOptionViewItem, index: QModelIndex, /) -> None:
path = index.data(Qt.ItemDataRole.DisplayRole)
rect = option.rect
x, y, w, h = rect.left(), rect.top(), rect.width(), rect.height()
primary_file_name, extension = os.path.splitext(os.path.basename(path))
painter.save()
painter.fillRect(x, y, w, h,
self._color_selected if option.state & QStyle.State_Selected else self._color_background)

font = painter.font()
font.setPixelSize(20)
font.setBold(True)
painter.setFont(font)
fm = QFontMetrics(font)
y += fm.height()
painter.drawText(x, y, primary_file_name)
x += fm.horizontalAdvance(primary_file_name)
font.setBold(False)
painter.setFont(font)
painter.drawText(x, y, extension)

x = rect.left()
font.setPixelSize(14)
fm = QFontMetrics(font)
y += fm.height()
painter.setFont(font)
painter.drawText(x, y, path)
painter.restore()
Loading