diff --git a/lisp/application.py b/lisp/application.py index babb50d47..07969fb25 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -206,6 +206,9 @@ def _load_from_file(self, session_file): self.__session.update_properties(session_dict["session"]) self.__session.session_file = session_file + for plugin_name, plugin_config in session_dict['session']['plugins'].items(): + self.__session.set_plugin_session_config(plugin_name, plugin_config) + # Load cues for cues_dict in session_dict.get("cues", {}): cue_type = cues_dict.pop("_type_", "Undefined") diff --git a/lisp/core/plugin.py b/lisp/core/plugin.py index f2f07a3a0..429182d7d 100644 --- a/lisp/core/plugin.py +++ b/lisp/core/plugin.py @@ -17,7 +17,10 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from lisp.core.configuration import DummyConfiguration +import inspect +from os import path + +from lisp.core.configuration import DummyConfiguration, JSONFileConfiguration # TODO: add possible additional metadata (Icon, Version, ...) @@ -42,3 +45,32 @@ def app(self): def finalize(self): """Called when the application is getting closed.""" + + @property + def SessionConfig(self): + """Returns the plugin's session-specific config. + + Fallsback to a "session.json" file (much like the app-level config "default.json" file) + or, if that doesn't exist, returns the plugin's app-level config file. + + This second fallback is for plugins that might wish to set defaults on an app-level, + but also permit the user to override those defaults in/for specific sessions. + """ + plugin_name = self.__module__.split('.')[-1] + if plugin_name in self.__app.session.plugins: + return self.__app.session.plugins[plugin_name] + + plugin_path = path.join( + path.split(inspect.getfile(self.__class__))[0], + 'session.json') + if path.exists(plugin_path): + return JSONFileConfiguration._read_json(plugin_path) + + return self.Config + + def WriteSessionConfig(self, config): + """Writes the plugin's session-specific config to the active session. + + When the user saves the showfile, it will then be written to disk. + """ + self.__app.session.set_plugin_session_config(self.__module__.split('.')[-1], config) diff --git a/lisp/core/session.py b/lisp/core/session.py index b1f1e2cd1..456e16883 100644 --- a/lisp/core/session.py +++ b/lisp/core/session.py @@ -29,6 +29,7 @@ class Session(HasInstanceProperties): layout = Property(default={}) session_file = Property(default="") + plugins = Property(default={}) def __init__(self, layout): super().__init__() @@ -60,6 +61,9 @@ def abs_path(self, rel_path): return rel_path + def set_plugin_session_config(self, plugin_name, plugin_session_config): + self.plugins[plugin_name] = plugin_session_config + def rel_path(self, abs_path): """Return a relative (to the session-file) version of the given path.""" return os.path.relpath(abs_path, start=self.path()) diff --git a/lisp/plugins/__init__.py b/lisp/plugins/__init__.py index db70e7906..db5795c80 100644 --- a/lisp/plugins/__init__.py +++ b/lisp/plugins/__init__.py @@ -52,7 +52,7 @@ def load_plugins(application): else: default_config_path = FALLBACK_CONFIG_PATH - # Load plugin configuration + # Load plugin (app-level) configuration config = JSONFileConfiguration( path.join(USER_DIR, mod_name + ".json"), default_config_path ) diff --git a/lisp/ui/mainwindow.py b/lisp/ui/mainwindow.py index d68c96e4e..b4f14ce85 100644 --- a/lisp/ui/mainwindow.py +++ b/lisp/ui/mainwindow.py @@ -46,6 +46,7 @@ from lisp.ui.logging.status import LogStatusView from lisp.ui.logging.viewer import LogViewer from lisp.ui.settings.app_configuration import AppConfigurationDialog +from lisp.ui.settings.session_configuration import SessionConfigurationDialog from lisp.ui.ui_utils import translate @@ -150,6 +151,12 @@ def __init__(self, conf, title="Linux Show Player", **kwargs): self.menuEdit.addSeparator() self.menuEdit.addAction(self.multiEdit) + # menuTools + self.sessionPreferences = QAction(self) + self.sessionPreferences.triggered.connect(self._show_session_preferences) + self.menuTools.addAction(self.sessionPreferences) + self.menuTools.addSeparator() + # menuAbout self.actionAbout = QAction(self) self.actionAbout.triggered.connect(self._show_about) @@ -225,6 +232,7 @@ def retranslateUi(self): # menuTools self.menuTools.setTitle(translate("MainWindow", "&Tools")) self.multiEdit.setText(translate("MainWindow", "Edit selection")) + self.sessionPreferences.setText(translate("MainWindow", "Session Preferences")) # menuAbout self.menuAbout.setTitle(translate("MainWindow", "&About")) self.actionAbout.setText(translate("MainWindow", "About")) @@ -310,6 +318,10 @@ def _show_preferences(self): prefUi = AppConfigurationDialog(parent=self) prefUi.exec_() + def _show_session_preferences(self): + prefUi = SessionConfigurationDialog(parent=self) + prefUi.exec_() + def _load_from_file(self): if self._check_saved(): path, _ = QFileDialog.getOpenFileName( diff --git a/lisp/ui/settings/session_configuration.py b/lisp/ui/settings/session_configuration.py new file mode 100644 index 000000000..3e497ba08 --- /dev/null +++ b/lisp/ui/settings/session_configuration.py @@ -0,0 +1,136 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2016 Francesco Ceruti +# +# Linux Show Player is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Linux Show Player is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Linux Show Player. If not, see . + +import logging +from collections import namedtuple + +from PyQt5 import QtCore +from PyQt5.QtCore import QModelIndex +from PyQt5.QtWidgets import QVBoxLayout, QDialogButtonBox, QDialog + +from lisp.core.dicttree import DictNode +from lisp.core.util import typename +from lisp.ui.settings.pages import ConfigurationPage, TreeMultiConfigurationWidget +from lisp.ui.settings.pages_tree_model import PagesTreeModel +from lisp.ui.ui_utils import translate + +logger = logging.getLogger(__name__) + +PageEntry = namedtuple('PageEntry', ('page', 'plugin')) + + +class SessionConfigurationDialog(QDialog): + PagesRegistry = DictNode() + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.setWindowTitle(translate('SessionConfiguration', 'Session preferences')) + self.setWindowModality(QtCore.Qt.WindowModal) + self.setMaximumSize(800, 510) + self.setMinimumSize(800, 510) + self.resize(800, 510) + self.setLayout(QVBoxLayout()) + + self.model = PagesTreeModel() + for r_node in SessionConfigurationDialog.PagesRegistry.children: + self._populateModel(QModelIndex(), r_node) + + self.mainPage = TreeMultiConfigurationWidget(self.model) + if len(self.PagesRegistry.children): + self.mainPage.selectFirst() + self.layout().addWidget(self.mainPage) + + self.dialogButtons = QDialogButtonBox(self) + self.dialogButtons.setStandardButtons( + QDialogButtonBox.Cancel | + QDialogButtonBox.Apply | + QDialogButtonBox.Ok + ) + self.layout().addWidget(self.dialogButtons) + + self.dialogButtons.button(QDialogButtonBox.Cancel).clicked.connect( + self.reject) + self.dialogButtons.button(QDialogButtonBox.Apply).clicked.connect( + self.applySettings) + self.dialogButtons.button(QDialogButtonBox.Ok).clicked.connect( + self.__onOk) + + def applySettings(self): + self.mainPage.applySettings() + + def _populateModel(self, m_parent, r_parent): + if r_parent.value is not None: + page = r_parent.value.page + config = r_parent.value.plugin.SessionConfig + else: + page = None + config = None + + try: + if page is None: + # The current node have no page, use the parent model-index + # as parent for it's children + mod_index = m_parent + elif issubclass(page, ConfigurationPage): + mod_index = self.model.addPage(page(config), parent=m_parent) + else: + mod_index = self.model.addPage(page(), parent=m_parent) + except Exception: + if not isinstance(page, type): + page_name = 'NoPage' + elif issubclass(page, ConfigurationPage): + page_name = page.Name + else: + page_name = page.__name__ + + logger.warning( + 'Cannot load configuration page: "{}" ({})'.format( + page_name, r_parent.path()), exc_info=True) + else: + for r_node in r_parent.children: + self._populateModel(mod_index, r_node) + + def __onOk(self): + self.applySettings() + self.accept() + + @staticmethod + def registerSettingsPage(path, page, plugin): + """ + :param path: indicate the page "position": 'category.sub.key' + :type path: str + :type page: Type[lisp.ui.settings.settings_page.ConfigurationPage] + #:type config: lisp.core.configuration.Configuration + """ + if issubclass(page, ConfigurationPage): + SessionConfigurationDialog.PagesRegistry.set( + path, PageEntry(page=page, plugin=plugin)) + else: + raise TypeError( + 'SessionConfiguration pages must be ConfigurationPage(s), not {}' + .format(typename(page)) + ) + + @staticmethod + def unregisterSettingsPage(path): + """ + :param path: indicate the page "position": 'category.sub.key' + :type path: str + """ + SessionConfigurationDialog.PagesRegistry.pop(path)