openlp/openlp/core/ui/thememanager.py

818 lines
39 KiB
Python
Raw Normal View History

2009-09-13 15:14:45 +00:00
# -*- coding: utf-8 -*-
2012-12-29 15:25:29 +00:00
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
2009-09-13 15:14:45 +00:00
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
2016-12-31 11:01:36 +00:00
# Copyright (c) 2008-2017 OpenLP Developers #
2009-09-13 15:14:45 +00:00
# --------------------------------------------------------------------------- #
# This program 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; version 2 of the License. #
# #
# This program 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 this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
2013-02-01 21:34:23 +00:00
"""
The Theme Manager manages adding, deleteing and modifying of themes.
"""
2009-09-13 15:14:45 +00:00
import os
import zipfile
import shutil
from xml.etree.ElementTree import ElementTree, XML
2015-11-07 00:49:40 +00:00
from PyQt5 import QtCore, QtGui, QtWidgets
2009-09-13 15:14:45 +00:00
2014-03-16 21:25:23 +00:00
from openlp.core.common import Registry, RegistryProperties, AppLocation, Settings, OpenLPMixin, RegistryMixin, \
2017-07-04 23:13:51 +00:00
UiStrings, check_directory_exists, translate, is_win, get_filesystem_encoding, delete_file
from openlp.core.common.path import Path, path_to_str, str_to_path
2017-08-07 20:50:01 +00:00
from openlp.core.lib import ImageSource, ValidationError, get_text_file_string, build_icon, \
2013-10-13 21:07:28 +00:00
check_item_selected, create_thumb, validate_thumb
2017-05-13 07:47:22 +00:00
from openlp.core.lib.theme import Theme, BackgroundType
from openlp.core.lib.ui import critical_error_message_box, create_widget_action
2013-12-31 20:29:03 +00:00
from openlp.core.ui import FileRenameForm, ThemeForm
2016-04-17 19:32:15 +00:00
from openlp.core.ui.lib import OpenLPToolbar
2017-08-07 20:50:01 +00:00
from openlp.core.ui.lib.filedialog import FileDialog
2016-04-03 19:44:09 +00:00
from openlp.core.common.languagemanager import get_locale_key
2009-09-13 15:14:45 +00:00
2010-02-27 15:31:23 +00:00
2013-12-31 20:29:03 +00:00
class Ui_ThemeManager(object):
2009-09-13 15:14:45 +00:00
"""
2013-12-31 20:29:03 +00:00
UI part of the Theme Manager
2009-09-13 15:14:45 +00:00
"""
2013-12-31 20:29:03 +00:00
def setup_ui(self, widget):
2013-02-01 21:34:23 +00:00
"""
2013-12-31 20:29:03 +00:00
Define the UI
2014-01-01 09:33:07 +00:00
:param widget: The screen object the the dialog is to be attached to.
2013-02-01 21:34:23 +00:00
"""
# start with the layout
2015-11-07 00:49:40 +00:00
self.layout = QtWidgets.QVBoxLayout(widget)
self.layout.setSpacing(0)
2015-11-07 00:49:40 +00:00
self.layout.setContentsMargins(0, 0, 0, 0)
2013-08-31 18:17:38 +00:00
self.layout.setObjectName('layout')
2013-12-31 21:02:35 +00:00
self.toolbar = OpenLPToolbar(widget)
2013-08-31 18:17:38 +00:00
self.toolbar.setObjectName('toolbar')
self.toolbar.add_toolbar_action('newTheme',
2013-12-23 07:03:56 +00:00
text=UiStrings().NewTheme, icon=':/themes/theme_new.png',
tooltip=translate('OpenLP.ThemeManager', 'Create a new theme.'),
triggers=self.on_add_theme)
2013-08-31 18:17:38 +00:00
self.toolbar.add_toolbar_action('editTheme',
2013-12-23 07:03:56 +00:00
text=translate('OpenLP.ThemeManager', 'Edit Theme'),
icon=':/themes/theme_edit.png',
tooltip=translate('OpenLP.ThemeManager', 'Edit a theme.'),
triggers=self.on_edit_theme)
2013-08-31 18:17:38 +00:00
self.delete_toolbar_action = self.toolbar.add_toolbar_action('delete_theme',
2013-12-23 07:03:56 +00:00
text=translate('OpenLP.ThemeManager',
'Delete Theme'),
icon=':/general/general_delete.png',
tooltip=translate('OpenLP.ThemeManager',
'Delete a theme.'),
triggers=self.on_delete_theme)
self.toolbar.addSeparator()
2013-08-31 18:17:38 +00:00
self.toolbar.add_toolbar_action('importTheme',
2013-12-23 07:03:56 +00:00
text=translate('OpenLP.ThemeManager', 'Import Theme'),
icon=':/general/general_import.png',
tooltip=translate('OpenLP.ThemeManager', 'Import a theme.'),
triggers=self.on_import_theme)
2013-08-31 18:17:38 +00:00
self.toolbar.add_toolbar_action('exportTheme',
2013-12-23 07:03:56 +00:00
text=translate('OpenLP.ThemeManager', 'Export Theme'),
icon=':/general/general_export.png',
tooltip=translate('OpenLP.ThemeManager', 'Export a theme.'),
triggers=self.on_export_theme)
self.layout.addWidget(self.toolbar)
2015-11-07 00:49:40 +00:00
self.theme_widget = QtWidgets.QWidgetAction(self.toolbar)
2013-08-31 18:17:38 +00:00
self.theme_widget.setObjectName('theme_widget')
# create theme manager list
2015-11-07 00:49:40 +00:00
self.theme_list_widget = QtWidgets.QListWidget(widget)
self.theme_list_widget.setAlternatingRowColors(True)
self.theme_list_widget.setIconSize(QtCore.QSize(88, 50))
self.theme_list_widget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
2013-08-31 18:17:38 +00:00
self.theme_list_widget.setObjectName('theme_list_widget')
self.layout.addWidget(self.theme_list_widget)
2013-02-14 21:50:10 +00:00
self.theme_list_widget.customContextMenuRequested.connect(self.context_menu)
# build the context menu
2015-11-07 00:49:40 +00:00
self.menu = QtWidgets.QMenu()
self.edit_action = create_widget_action(self.menu,
2013-12-23 07:03:56 +00:00
text=translate('OpenLP.ThemeManager', '&Edit Theme'),
icon=':/themes/theme_edit.png', triggers=self.on_edit_theme)
self.copy_action = create_widget_action(self.menu,
2013-12-23 07:03:56 +00:00
text=translate('OpenLP.ThemeManager', '&Copy Theme'),
icon=':/themes/theme_edit.png', triggers=self.on_copy_theme)
self.rename_action = create_widget_action(self.menu,
2013-12-23 07:03:56 +00:00
text=translate('OpenLP.ThemeManager', '&Rename Theme'),
icon=':/themes/theme_edit.png', triggers=self.on_rename_theme)
self.delete_action = create_widget_action(self.menu,
2013-12-23 07:03:56 +00:00
text=translate('OpenLP.ThemeManager', '&Delete Theme'),
icon=':/general/general_delete.png', triggers=self.on_delete_theme)
self.menu.addSeparator()
self.global_action = create_widget_action(self.menu,
2013-12-23 07:03:56 +00:00
text=translate('OpenLP.ThemeManager', 'Set As &Global Default'),
icon=':/general/general_export.png',
triggers=self.change_global_from_screen)
2013-12-24 06:15:41 +00:00
self.export_action = create_widget_action(self.menu,
text=translate('OpenLP.ThemeManager', '&Export Theme'),
icon=':/general/general_export.png', triggers=self.on_export_theme)
2010-12-27 10:18:09 +00:00
# Signals
2013-02-14 21:50:10 +00:00
self.theme_list_widget.doubleClicked.connect(self.change_global_from_screen)
self.theme_list_widget.currentItemChanged.connect(self.check_list_state)
2013-12-31 20:29:03 +00:00
2015-11-07 00:49:40 +00:00
class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManager, RegistryProperties):
2013-12-31 20:29:03 +00:00
"""
Manages the orders of Theme.
"""
def __init__(self, parent=None):
"""
Constructor
"""
super(ThemeManager, self).__init__(parent)
self.settings_section = 'themes'
2010-12-27 10:18:09 +00:00
# Variables
self.theme_list = []
2013-03-16 11:05:52 +00:00
self.old_background_image = None
2009-09-13 15:14:45 +00:00
2013-12-23 07:03:56 +00:00
def bootstrap_initialise(self):
"""
process the bootstrap initialise setup request
"""
2013-12-31 20:29:03 +00:00
self.setup_ui(self)
2013-12-23 07:03:56 +00:00
self.global_theme = Settings().value(self.settings_section + '/global theme')
self.build_theme_path()
self.load_first_time_themes()
def bootstrap_post_set_up(self):
"""
process the bootstrap post setup request
"""
2013-12-31 20:29:03 +00:00
self.theme_form = ThemeForm(self)
self.theme_form.path = self.path
self.file_rename_form = FileRenameForm()
Registry().register_function('theme_update_global', self.change_global_from_tab)
2014-01-04 20:58:18 +00:00
self.load_themes()
2013-12-23 07:03:56 +00:00
2013-12-31 20:29:03 +00:00
def build_theme_path(self):
"""
Set up the theme path variables
"""
2017-08-01 20:59:41 +00:00
self.path = str(AppLocation.get_section_data_path(self.settings_section))
check_directory_exists(Path(self.path))
2013-12-31 20:29:03 +00:00
self.thumb_path = os.path.join(self.path, 'thumbnails')
check_directory_exists(Path(self.thumb_path))
2013-12-31 20:29:03 +00:00
2013-12-23 07:03:56 +00:00
def check_list_state(self, item, field=None):
"""
If Default theme selected remove delete button.
2013-12-23 07:03:56 +00:00
Note for some reason a dummy field is required. Nothing is passed!
2014-01-01 09:33:07 +00:00
:param field:
:param item: Service Item to process
"""
if item is None:
return
2012-05-19 15:10:05 +00:00
real_theme_name = item.data(QtCore.Qt.UserRole)
2012-05-17 18:57:01 +00:00
theme_name = item.text()
2011-01-11 17:44:13 +00:00
# If default theme restrict actions
if real_theme_name == theme_name:
2013-03-16 11:05:52 +00:00
self.delete_toolbar_action.setVisible(True)
else:
2013-03-16 11:05:52 +00:00
self.delete_toolbar_action.setVisible(False)
def context_menu(self, point):
2010-12-28 10:56:19 +00:00
"""
2014-01-01 09:33:07 +00:00
Build the Right Click Context menu and set state depending on the type of theme.
:param point: The position of the mouse so the correct item can be found.
2010-12-28 10:56:19 +00:00
"""
item = self.theme_list_widget.itemAt(point)
if item is None:
return
2012-05-19 15:10:05 +00:00
real_theme_name = item.data(QtCore.Qt.UserRole)
2013-08-31 18:17:38 +00:00
theme_name = str(item.text())
2012-06-27 17:00:02 +00:00
visible = real_theme_name == theme_name
self.delete_action.setVisible(visible)
self.rename_action.setVisible(visible)
self.global_action.setVisible(visible)
2015-11-07 00:49:40 +00:00
self.menu.exec(self.theme_list_widget.mapToGlobal(point))
2013-03-11 19:11:46 +00:00
def change_global_from_tab(self):
2010-06-10 01:57:59 +00:00
"""
2013-03-10 20:19:42 +00:00
Change the global theme when it is changed through the Themes settings tab
2010-06-10 01:57:59 +00:00
"""
2013-08-31 18:17:38 +00:00
self.global_theme = Settings().value(self.settings_section + '/global theme')
2016-05-20 16:22:06 +00:00
self.log_debug('change_global_from_tab {text}'.format(text=self.global_theme))
2013-02-02 21:16:42 +00:00
for count in range(0, self.theme_list_widget.count()):
2010-08-28 15:49:51 +00:00
# reset the old name
item = self.theme_list_widget.item(count)
old_name = item.text()
2012-05-19 15:10:05 +00:00
new_name = item.data(QtCore.Qt.UserRole)
if old_name != new_name:
self.theme_list_widget.item(count).setText(new_name)
2010-08-28 15:49:51 +00:00
# Set the new name
2013-03-11 19:11:46 +00:00
if self.global_theme == new_name:
2016-05-20 16:22:06 +00:00
name = translate('OpenLP.ThemeManager', '{text} (default)').format(text=new_name)
self.theme_list_widget.item(count).setText(name)
2013-03-16 11:05:52 +00:00
self.delete_toolbar_action.setVisible(item not in self.theme_list_widget.selectedItems())
2009-09-13 15:14:45 +00:00
2013-02-05 21:42:15 +00:00
def change_global_from_screen(self, index=-1):
2010-06-10 01:57:59 +00:00
"""
2014-01-01 09:33:07 +00:00
Change the global theme when a theme is double clicked upon in the Theme Manager list.
:param index:
2010-06-10 01:57:59 +00:00
"""
selected_row = self.theme_list_widget.currentRow()
2013-02-02 21:16:42 +00:00
for count in range(0, self.theme_list_widget.count()):
item = self.theme_list_widget.item(count)
old_name = item.text()
2010-08-28 15:49:51 +00:00
# reset the old name
2012-05-19 15:10:05 +00:00
if old_name != item.data(QtCore.Qt.UserRole):
self.theme_list_widget.item(count).setText(item.data(QtCore.Qt.UserRole))
2010-08-28 15:49:51 +00:00
# Set the new name
2009-10-10 18:36:58 +00:00
if count == selected_row:
self.global_theme = self.theme_list_widget.item(count).text()
2016-05-20 16:22:06 +00:00
name = translate('OpenLP.ThemeManager', '{text} (default)').format(text=self.global_theme)
self.theme_list_widget.item(count).setText(name)
2013-08-31 18:17:38 +00:00
Settings().setValue(self.settings_section + '/global theme', self.global_theme)
Registry().execute('theme_update_global')
self._push_themes()
2009-09-13 15:14:45 +00:00
2013-12-23 07:03:56 +00:00
def on_add_theme(self, field=None):
2010-06-10 01:57:59 +00:00
"""
2014-01-01 09:33:07 +00:00
Loads a new theme with the default settings and then launches the theme editing form for the user to make
their customisations.
:param field:
2010-06-10 01:57:59 +00:00
"""
2017-05-13 07:47:22 +00:00
theme = Theme()
theme.set_default_header_footer()
2013-03-16 11:05:52 +00:00
self.theme_form.theme = theme
2015-11-07 00:49:40 +00:00
self.theme_form.exec()
self.load_themes()
2009-09-13 15:14:45 +00:00
2013-12-23 07:03:56 +00:00
def on_rename_theme(self, field=None):
2010-09-26 06:20:24 +00:00
"""
Renames an existing theme to a new name
2014-01-01 09:33:07 +00:00
:param field:
2010-09-26 06:20:24 +00:00
"""
2012-12-29 15:25:29 +00:00
if self._validate_theme_action(translate('OpenLP.ThemeManager', 'You must select a theme to rename.'),
2013-12-23 07:03:56 +00:00
translate('OpenLP.ThemeManager', 'Rename Confirmation'),
translate('OpenLP.ThemeManager', 'Rename {theme_name} theme?'), False, False):
item = self.theme_list_widget.currentItem()
2012-05-19 15:10:05 +00:00
old_theme_name = item.data(QtCore.Qt.UserRole)
2013-03-16 11:05:52 +00:00
self.file_rename_form.file_name_edit.setText(old_theme_name)
2015-11-07 00:49:40 +00:00
if self.file_rename_form.exec():
2013-03-16 11:05:52 +00:00
new_theme_name = self.file_rename_form.file_name_edit.text()
if old_theme_name == new_theme_name:
return
if self.check_if_theme_exists(new_theme_name):
old_theme_data = self.get_theme_data(old_theme_name)
2013-03-16 11:05:52 +00:00
self.clone_theme_data(old_theme_data, new_theme_name)
self.delete_theme(old_theme_name)
2013-01-22 21:09:43 +00:00
for plugin in self.plugin_manager.plugins:
2013-02-19 21:23:56 +00:00
if plugin.uses_theme(old_theme_name):
plugin.rename_theme(old_theme_name, new_theme_name)
2013-01-22 21:09:43 +00:00
self.renderer.update_theme(new_theme_name, old_theme_name)
self.load_themes()
2010-09-26 06:20:24 +00:00
2013-12-23 07:03:56 +00:00
def on_copy_theme(self, field=None):
2010-09-26 06:20:24 +00:00
"""
Copies an existing theme to a new name
2014-01-01 09:33:07 +00:00
:param field:
2010-09-26 06:20:24 +00:00
"""
item = self.theme_list_widget.currentItem()
2012-05-19 15:10:05 +00:00
old_theme_name = item.data(QtCore.Qt.UserRole)
2013-03-16 11:05:52 +00:00
self.file_rename_form.file_name_edit.setText(translate('OpenLP.ThemeManager',
2016-05-20 16:22:06 +00:00
'Copy of {name}',
'Copy of <theme name>').format(name=old_theme_name))
2015-11-07 00:49:40 +00:00
if self.file_rename_form.exec(True):
2013-03-16 11:05:52 +00:00
new_theme_name = self.file_rename_form.file_name_edit.text()
if self.check_if_theme_exists(new_theme_name):
theme_data = self.get_theme_data(old_theme_name)
2013-03-16 11:05:52 +00:00
self.clone_theme_data(theme_data, new_theme_name)
2010-09-26 07:39:50 +00:00
2013-03-16 11:05:52 +00:00
def clone_theme_data(self, theme_data, new_theme_name):
2010-09-26 07:39:50 +00:00
"""
2010-10-03 07:42:02 +00:00
Takes a theme and makes a new copy of it as well as saving it.
2014-01-01 09:33:07 +00:00
:param theme_data: The theme to be used
:param new_theme_name: The new theme name to save the data to
2010-09-26 07:39:50 +00:00
"""
save_to = None
save_from = None
2016-04-30 15:40:23 +00:00
if theme_data.background_type == 'image' or theme_data.background_type == 'video':
2013-08-31 18:17:38 +00:00
save_to = os.path.join(self.path, new_theme_name, os.path.split(str(theme_data.background_filename))[1])
save_from = theme_data.background_filename
theme_data.theme_name = new_theme_name
theme_data.extend_image_filename(self.path)
self.save_theme(theme_data, save_from, save_to)
self.load_themes()
2010-09-26 06:20:24 +00:00
2013-12-23 07:03:56 +00:00
def on_edit_theme(self, field=None):
2010-06-10 01:57:59 +00:00
"""
Loads the settings for the theme that is to be edited and launches the
theme editing form so the user can make their changes.
2014-01-01 09:33:07 +00:00
:param field:
2010-06-10 01:57:59 +00:00
"""
if check_item_selected(self.theme_list_widget,
2013-12-23 07:03:56 +00:00
translate('OpenLP.ThemeManager', 'You must select a theme to edit.')):
item = self.theme_list_widget.currentItem()
theme = self.get_theme_data(item.data(QtCore.Qt.UserRole))
2016-04-30 15:40:23 +00:00
if theme.background_type == 'image' or theme.background_type == 'video':
2013-03-16 11:05:52 +00:00
self.old_background_image = theme.background_filename
self.theme_form.theme = theme
2015-11-07 00:49:40 +00:00
self.theme_form.exec(True)
2013-03-16 11:05:52 +00:00
self.old_background_image = None
2013-01-22 21:09:43 +00:00
self.renderer.update_theme(theme.theme_name)
self.load_themes()
2009-09-13 15:14:45 +00:00
2013-12-23 07:03:56 +00:00
def on_delete_theme(self, field=None):
2010-06-10 01:57:59 +00:00
"""
2014-01-01 09:33:07 +00:00
Delete a theme triggered by the UI.
:param field:
2010-06-10 01:57:59 +00:00
"""
2012-12-29 15:25:29 +00:00
if self._validate_theme_action(translate('OpenLP.ThemeManager', 'You must select a theme to delete.'),
translate('OpenLP.ThemeManager', 'Delete Confirmation'),
translate('OpenLP.ThemeManager', 'Delete {theme_name} theme?')):
item = self.theme_list_widget.currentItem()
2012-05-17 18:57:01 +00:00
theme = item.text()
row = self.theme_list_widget.row(item)
self.theme_list_widget.takeItem(row)
self.delete_theme(theme)
2013-01-22 21:09:43 +00:00
self.renderer.update_theme(theme, only_delete=True)
2011-02-05 20:10:08 +00:00
# As we do not reload the themes, push out the change. Reload the
# list as the internal lists and events need to be triggered.
self._push_themes()
def delete_theme(self, theme):
"""
Delete a theme.
2014-01-01 09:33:07 +00:00
:param theme: The theme to delete.
"""
self.theme_list.remove(theme)
2016-05-20 16:22:06 +00:00
thumb = '{name}.png'.format(name=theme)
delete_file(Path(self.path, thumb))
delete_file(Path(self.thumb_path, thumb))
try:
# Windows is always unicode, so no need to encode filenames
if is_win():
shutil.rmtree(os.path.join(self.path, theme))
else:
encoding = get_filesystem_encoding()
shutil.rmtree(os.path.join(self.path, theme).encode(encoding))
2013-10-13 13:51:13 +00:00
except OSError as os_error:
shutil.Error = os_error
2016-05-20 16:22:06 +00:00
self.log_exception('Error deleting theme {name}'.format(name=theme))
2009-09-13 15:14:45 +00:00
2013-12-23 07:03:56 +00:00
def on_export_theme(self, field=None):
"""
Export the theme in a zip file
2014-01-01 09:33:07 +00:00
:param field:
"""
item = self.theme_list_widget.currentItem()
if item is None:
2012-12-29 15:25:29 +00:00
critical_error_message_box(message=translate('OpenLP.ThemeManager', 'You have not selected a theme.'))
return
2012-05-19 15:10:05 +00:00
theme = item.data(QtCore.Qt.UserRole)
2017-05-23 04:59:35 +00:00
path, filter_used = \
QtWidgets.QFileDialog.getSaveFileName(self.main_window,
translate('OpenLP.ThemeManager', 'Save Theme - ({name})').
format(name=theme),
Settings().value(self.settings_section + '/last directory export'),
translate('OpenLP.ThemeManager', 'OpenLP Themes (*.otz)'))
2013-02-03 19:23:12 +00:00
self.application.set_busy_cursor()
2009-11-07 00:00:36 +00:00
if path:
2013-08-31 18:17:38 +00:00
Settings().setValue(self.settings_section + '/last directory export', path)
if self._export_theme(path, theme):
2015-11-07 00:49:40 +00:00
QtWidgets.QMessageBox.information(self,
translate('OpenLP.ThemeManager', 'Theme Exported'),
translate('OpenLP.ThemeManager',
'Your theme has been successfully exported.'))
2013-02-03 19:23:12 +00:00
self.application.set_normal_cursor()
2009-09-13 15:14:45 +00:00
2017-05-21 07:11:36 +00:00
def _export_theme(self, theme_path, theme):
2014-06-23 13:51:56 +00:00
"""
Create the zipfile with the theme contents.
2017-05-21 07:11:36 +00:00
:param theme_path: Location where the zip file will be placed
2014-06-23 13:51:56 +00:00
:param theme: The name of the theme to be exported
"""
theme_zip = None
try:
theme_zip = zipfile.ZipFile(theme_path, 'w')
source = os.path.join(self.path, theme)
for files in os.walk(source):
for name in files[2]:
theme_zip.write(os.path.join(source, name), os.path.join(theme, name))
theme_zip.close()
return True
except OSError as ose:
self.log_exception('Export Theme Failed')
critical_error_message_box(translate('OpenLP.ThemeManager', 'Theme Export Failed'),
2015-02-10 22:25:30 +00:00
translate('OpenLP.ThemeManager', 'The theme export failed because this error '
2016-05-20 16:22:06 +00:00
'occurred: {err}').format(err=ose.strerror))
if theme_zip:
theme_zip.close()
shutil.rmtree(theme_path, True)
return False
2014-06-23 13:51:56 +00:00
2013-12-23 07:03:56 +00:00
def on_import_theme(self, field=None):
2010-06-10 01:57:59 +00:00
"""
2013-03-16 11:05:52 +00:00
Opens a file dialog to select the theme file(s) to import before attempting to extract OpenLP themes from
those files. This process will only load version 2 themes.
2014-01-01 09:33:07 +00:00
:param field:
2010-06-10 01:57:59 +00:00
"""
2017-08-07 20:50:01 +00:00
file_paths, selected_filter = FileDialog.getOpenFileNames(
self,
translate('OpenLP.ThemeManager', 'Select Theme Import File'),
str_to_path(Settings().value(self.settings_section + '/last directory import')),
translate('OpenLP.ThemeManager', 'OpenLP Themes (*.otz)'))
self.log_info('New Themes {file_paths}'.format(file_paths=file_paths))
if not file_paths:
return
2013-02-03 19:23:12 +00:00
self.application.set_busy_cursor()
2017-08-07 20:50:01 +00:00
for file_path in file_paths:
file_name = path_to_str(file_path)
2013-08-31 18:17:38 +00:00
Settings().setValue(self.settings_section + '/last directory import', str(file_name))
self.unzip_theme(file_name, self.path)
self.load_themes()
2013-02-03 19:23:12 +00:00
self.application.set_normal_cursor()
2009-09-13 15:14:45 +00:00
2013-02-19 19:50:14 +00:00
def load_first_time_themes(self):
"""
Imports any themes on start up and makes sure there is at least one theme
"""
self.application.set_busy_cursor()
2013-08-31 18:17:38 +00:00
files = AppLocation.get_files(self.settings_section, '.otz')
2013-02-19 19:50:14 +00:00
for theme_file in files:
2017-08-01 20:59:41 +00:00
theme_file = os.path.join(self.path, str(theme_file))
2013-02-19 19:50:14 +00:00
self.unzip_theme(theme_file, self.path)
delete_file(Path(theme_file))
2013-08-31 18:17:38 +00:00
files = AppLocation.get_files(self.settings_section, '.png')
2013-02-19 19:50:14 +00:00
# No themes have been found so create one
if not files:
2017-05-13 07:47:22 +00:00
theme = Theme()
2013-02-19 19:50:14 +00:00
theme.theme_name = UiStrings().Default
self._write_theme(theme, None, None)
2013-08-31 18:17:38 +00:00
Settings().setValue(self.settings_section + '/global theme', theme.theme_name)
2013-02-19 19:50:14 +00:00
self.application.set_normal_cursor()
def load_themes(self):
2009-09-13 15:14:45 +00:00
"""
2013-10-23 19:25:46 +00:00
Loads the theme lists and triggers updates across the whole system using direct calls or core functions and
events for the plugins.
2009-09-13 15:14:45 +00:00
The plugins will call back in to get the real list if they want it.
"""
self.theme_list = []
self.theme_list_widget.clear()
2013-08-31 18:17:38 +00:00
files = AppLocation.get_files(self.settings_section, '.png')
# Sort the themes by its name considering language specific
2013-08-31 18:17:38 +00:00
files.sort(key=lambda file_name: get_locale_key(str(file_name)))
2011-03-20 07:37:44 +00:00
# now process the file list of png files
for name in files:
2017-08-01 20:59:41 +00:00
name = str(name)
2011-03-20 07:37:44 +00:00
# check to see file is in theme root directory
theme = os.path.join(self.path, name)
if os.path.exists(theme):
text_name = os.path.splitext(name)[0]
if text_name == self.global_theme:
2016-05-20 16:22:06 +00:00
name = translate('OpenLP.ThemeManager', '{name} (default)').format(name=text_name)
2011-03-20 07:37:44 +00:00
else:
name = text_name
2016-05-20 16:22:06 +00:00
thumb = os.path.join(self.thumb_path, '{name}.png'.format(name=text_name))
2015-11-07 00:49:40 +00:00
item_name = QtWidgets.QListWidgetItem(name)
2011-06-12 15:17:01 +00:00
if validate_thumb(theme, thumb):
2011-03-20 07:37:44 +00:00
icon = build_icon(thumb)
else:
2011-06-12 15:17:01 +00:00
icon = create_thumb(theme, thumb)
2011-03-20 07:37:44 +00:00
item_name.setIcon(icon)
2012-05-17 15:13:09 +00:00
item_name.setData(QtCore.Qt.UserRole, text_name)
self.theme_list_widget.addItem(item_name)
self.theme_list.append(text_name)
self._push_themes()
2009-09-13 15:14:45 +00:00
def _push_themes(self):
2010-06-10 01:57:59 +00:00
"""
Notify listeners that the theme list has been updated
"""
2013-08-31 18:17:38 +00:00
Registry().execute('theme_update_list', self.get_themes())
2009-09-13 15:14:45 +00:00
def get_themes(self):
2010-06-10 01:57:59 +00:00
"""
Return the list of loaded themes
"""
return self.theme_list
2009-09-13 15:14:45 +00:00
def get_theme_data(self, theme_name):
2010-06-10 01:57:59 +00:00
"""
2017-05-30 13:55:39 +00:00
Returns a theme object from an XML or JSON file
2010-06-10 01:57:59 +00:00
2014-01-01 09:33:07 +00:00
:param theme_name: Name of the theme to load from file
2015-09-08 19:13:59 +00:00
:return: The theme object.
2010-06-10 01:57:59 +00:00
"""
2016-05-20 16:22:06 +00:00
self.log_debug('get theme data for theme {name}'.format(name=theme_name))
theme_file_path = Path(self.path, str(theme_name), '{file_name}.json'.format(file_name=theme_name))
theme_data = get_text_file_string(theme_file_path)
2017-05-13 08:22:48 +00:00
jsn = True
2017-05-13 08:37:38 +00:00
if not theme_data:
theme_file_path = theme_file_path.with_suffix('.xml')
theme_data = get_text_file_string(theme_file_path)
2017-05-13 08:22:48 +00:00
jsn = False
if not theme_data:
2013-12-23 07:03:56 +00:00
self.log_debug('No theme data - using default theme')
2017-05-13 07:47:22 +00:00
return Theme()
2010-10-11 16:14:36 +00:00
else:
2017-05-13 08:22:48 +00:00
if jsn:
return self._create_theme_from_json(theme_data, self.path)
else:
return self._create_theme_from_xml(theme_data, self.path)
2009-09-13 15:14:45 +00:00
def over_write_message_box(self, theme_name):
"""
2013-02-01 21:34:23 +00:00
Display a warning box to the user that a theme already exists
2014-01-11 17:52:01 +00:00
2014-01-01 09:33:07 +00:00
:param theme_name: Name of the theme.
2015-09-08 19:13:59 +00:00
:return: Confirm if the theme is to be overwritten.
"""
2015-11-07 00:49:40 +00:00
ret = QtWidgets.QMessageBox.question(self, translate('OpenLP.ThemeManager', 'Theme Already Exists'),
translate('OpenLP.ThemeManager',
2016-05-20 16:22:06 +00:00
'Theme {name} already exists. '
'Do you want to replace it?').format(name=theme_name),
defaultButton=QtWidgets.QMessageBox.No)
2015-11-07 00:49:40 +00:00
return ret == QtWidgets.QMessageBox.Yes
2009-09-13 15:14:45 +00:00
2013-02-02 21:16:42 +00:00
def unzip_theme(self, file_name, directory):
2009-09-13 15:14:45 +00:00
"""
2014-01-01 09:33:07 +00:00
Unzip the theme, remove the preview file if stored. Generate a new preview file. Check the XML theme version
and upgrade if necessary.
:param file_name:
:param directory:
2009-09-13 15:14:45 +00:00
"""
2016-05-20 16:22:06 +00:00
self.log_debug('Unzipping theme {name}'.format(name=file_name))
2013-02-01 21:34:23 +00:00
theme_zip = None
2012-03-10 08:22:52 +00:00
out_file = None
file_xml = None
2012-06-15 16:19:46 +00:00
abort_import = True
2017-05-30 13:55:39 +00:00
json_theme = False
theme_name = ""
try:
2013-02-01 21:34:23 +00:00
theme_zip = zipfile.ZipFile(file_name)
2017-05-21 15:30:02 +00:00
json_file = [name for name in theme_zip.namelist() if os.path.splitext(name)[1].lower() == '.json']
if len(json_file) != 1:
2017-05-30 13:55:39 +00:00
# TODO: remove XML handling at some point but would need a auto conversion to run first.
2017-05-21 15:30:02 +00:00
xml_file = [name for name in theme_zip.namelist() if os.path.splitext(name)[1].lower() == '.xml']
if len(xml_file) != 1:
self.log_error('Theme contains "{val:d}" theme files'.format(val=len(xml_file)))
raise ValidationError
xml_tree = ElementTree(element=XML(theme_zip.read(xml_file[0]))).getroot()
theme_version = xml_tree.get('version', default=None)
if not theme_version or float(theme_version) < 2.0:
self.log_error('Theme version is less than 2.0')
raise ValidationError
theme_name = xml_tree.find('name').text.strip()
else:
2017-05-30 13:55:39 +00:00
new_theme = Theme()
new_theme.load_theme(theme_zip.read(json_file[0]).decode("utf-8"))
theme_name = new_theme.theme_name
2017-05-21 15:30:02 +00:00
json_theme = True
2013-10-13 13:51:13 +00:00
theme_folder = os.path.join(directory, theme_name)
theme_exists = os.path.exists(theme_folder)
if theme_exists and not self.over_write_message_box(theme_name):
abort_import = True
return
else:
2013-10-13 13:51:13 +00:00
abort_import = False
for name in theme_zip.namelist():
out_name = name.replace('/', os.path.sep)
split_name = out_name.split(os.path.sep)
2013-10-13 13:51:13 +00:00
if split_name[-1] == '' or len(split_name) == 1:
# is directory or preview file
continue
full_name = os.path.join(directory, out_name)
check_directory_exists(Path(os.path.dirname(full_name)))
2017-05-21 15:30:02 +00:00
if os.path.splitext(name)[1].lower() == '.xml' or os.path.splitext(name)[1].lower() == '.json':
2013-10-13 13:51:13 +00:00
file_xml = str(theme_zip.read(name), 'utf-8')
out_file = open(full_name, 'w', encoding='utf-8')
2013-10-13 13:51:13 +00:00
out_file.write(file_xml)
else:
2013-10-13 13:51:13 +00:00
out_file = open(full_name, 'wb')
out_file.write(theme_zip.read(name))
out_file.close()
except (IOError, zipfile.BadZipfile):
2016-07-01 21:17:20 +00:00
self.log_exception('Importing theme from zip failed {name}'.format(name=file_name))
raise ValidationError
except ValidationError:
critical_error_message_box(translate('OpenLP.ThemeManager', 'Validation Error'),
translate('OpenLP.ThemeManager', 'File is not a valid theme.'))
finally:
# Close the files, to be able to continue creating the theme.
2013-02-01 21:34:23 +00:00
if theme_zip:
theme_zip.close()
2012-03-10 08:22:52 +00:00
if out_file:
out_file.close()
if not abort_import:
# As all files are closed, we can create the Theme.
2012-03-10 08:22:52 +00:00
if file_xml:
2017-05-21 15:30:02 +00:00
if json_theme:
theme = self._create_theme_from_json(file_xml, self.path)
else:
theme = self._create_theme_from_xml(file_xml, self.path)
self.generate_and_save_image(theme_name, theme)
2012-06-23 17:21:27 +00:00
# Only show the error message, when IOError was not raised (in
# this case the error message has already been shown).
2013-02-01 21:34:23 +00:00
elif theme_zip is not None:
critical_error_message_box(
translate('OpenLP.ThemeManager', 'Validation Error'),
2012-12-29 15:25:29 +00:00
translate('OpenLP.ThemeManager', 'File is not a valid theme.'))
2016-05-20 16:22:06 +00:00
self.log_error('Theme file does not contain XML data {name}'.format(name=file_name))
2009-09-13 15:14:45 +00:00
def check_if_theme_exists(self, theme_name):
2009-09-13 15:14:45 +00:00
"""
2010-12-27 10:18:09 +00:00
Check if theme already exists and displays error message
2010-06-10 01:57:59 +00:00
2014-01-01 09:33:07 +00:00
:param theme_name: Name of the Theme to test
2015-09-08 19:13:59 +00:00
:return: True or False if theme exists
2010-12-27 10:18:09 +00:00
"""
2012-03-10 08:22:52 +00:00
theme_dir = os.path.join(self.path, theme_name)
2010-12-27 10:18:09 +00:00
if os.path.exists(theme_dir):
critical_error_message_box(
2011-01-15 19:24:50 +00:00
translate('OpenLP.ThemeManager', 'Validation Error'),
2012-12-29 15:25:29 +00:00
translate('OpenLP.ThemeManager', 'A theme with this name already exists.'))
2010-12-27 10:18:09 +00:00
return False
return True
2009-09-13 15:14:45 +00:00
def save_theme(self, theme, image_from, image_to):
2009-09-13 15:14:45 +00:00
"""
2014-01-01 09:33:07 +00:00
Called by theme maintenance Dialog to save the theme and to trigger the reload of the theme list
:param theme: The theme data object.
:param image_from: Where the theme image is currently located.
:param image_to: Where the Theme Image is to be saved to
2009-09-13 15:14:45 +00:00
"""
self._write_theme(theme, image_from, image_to)
2012-12-29 15:25:29 +00:00
if theme.background_type == BackgroundType.to_string(BackgroundType.Image):
self.image_manager.update_image_border(theme.background_filename,
2013-12-23 07:03:56 +00:00
ImageSource.Theme,
QtGui.QColor(theme.background_border_color))
self.image_manager.process_updates()
def _write_theme(self, theme, image_from, image_to):
"""
2013-12-24 06:15:41 +00:00
Writes the theme to the disk and handles the background image if necessary
2014-01-01 09:33:07 +00:00
:param theme: The theme data object.
:param image_from: Where the theme image is currently located.
:param image_to: Where the Theme Image is to be saved to
"""
2010-11-05 19:20:41 +00:00
name = theme.theme_name
2017-05-24 19:31:48 +00:00
theme_pretty = theme.export_theme()
2009-09-13 15:14:45 +00:00
theme_dir = os.path.join(self.path, name)
check_directory_exists(Path(theme_dir))
2017-05-13 07:47:22 +00:00
theme_file = os.path.join(theme_dir, name + '.json')
2013-03-16 11:05:52 +00:00
if self.old_background_image and image_to != self.old_background_image:
delete_file(Path(self.old_background_image))
2012-03-10 08:22:52 +00:00
out_file = None
2010-12-27 10:18:09 +00:00
try:
out_file = open(theme_file, 'w', encoding='utf-8')
2017-05-13 07:47:22 +00:00
out_file.write(theme_pretty)
2010-12-27 10:18:09 +00:00
except IOError:
2013-12-23 07:03:56 +00:00
self.log_exception('Saving theme to file failed')
2010-12-27 10:18:09 +00:00
finally:
2012-03-10 08:22:52 +00:00
if out_file:
out_file.close()
if image_from and os.path.abspath(image_from) != os.path.abspath(image_to):
2009-11-07 00:00:36 +00:00
try:
# Windows is always unicode, so no need to encode filenames
if is_win():
shutil.copyfile(image_from, image_to)
else:
encoding = get_filesystem_encoding()
shutil.copyfile(image_from.encode(encoding), image_to.encode(encoding))
2013-08-31 18:17:38 +00:00
except IOError as xxx_todo_changeme:
shutil.Error = xxx_todo_changeme
2013-12-23 07:03:56 +00:00
self.log_exception('Failed to save theme image')
self.generate_and_save_image(name, theme)
2009-09-13 15:14:45 +00:00
def generate_and_save_image(self, name, theme):
"""
2013-02-01 21:34:23 +00:00
Generate and save a preview image
2014-01-01 09:33:07 +00:00
:param name: The name of the theme.
:param theme: The theme data object.
"""
frame = self.generate_image(theme)
2013-08-31 18:17:38 +00:00
sample_path_name = os.path.join(self.path, name + '.png')
if os.path.exists(sample_path_name):
os.unlink(sample_path_name)
2013-08-31 18:17:38 +00:00
frame.save(sample_path_name, 'png')
2016-05-20 16:22:06 +00:00
thumb = os.path.join(self.thumb_path, '{name}.png'.format(name=name))
create_thumb(sample_path_name, thumb, False)
2009-09-13 15:14:45 +00:00
def update_preview_images(self):
"""
Called to update the themes' preview images.
"""
2013-03-16 11:05:52 +00:00
self.main_window.display_progress_bar(len(self.theme_list))
for theme in self.theme_list:
2013-03-07 08:05:43 +00:00
self.main_window.increment_progress_bar()
self.generate_and_save_image(theme, self.get_theme_data(theme))
2013-03-16 11:05:52 +00:00
self.main_window.finished_progress_bar()
self.load_themes()
2013-10-23 19:25:46 +00:00
def generate_image(self, theme_data, force_page=False):
2009-09-13 15:14:45 +00:00
"""
2011-03-28 18:56:39 +00:00
Call the renderer to build a Sample Image
2014-01-01 09:33:07 +00:00
:param theme_data: The theme to generated a preview for.
:param force_page: Flag to tell message lines per page need to be generated.
2009-09-13 15:14:45 +00:00
"""
2013-10-23 19:25:46 +00:00
return self.renderer.generate_preview(theme_data, force_page)
2009-09-13 15:14:45 +00:00
def get_preview_image(self, theme):
2010-06-10 19:45:02 +00:00
"""
Return an image representing the look of the theme
2014-01-01 09:33:07 +00:00
:param theme: The theme to return the image for.
2010-06-10 19:45:02 +00:00
"""
2013-12-23 07:03:56 +00:00
return os.path.join(self.path, theme + '.png')
2009-09-13 15:14:45 +00:00
2017-05-13 07:47:22 +00:00
@staticmethod
def _create_theme_from_xml(theme_xml, image_path):
2010-06-10 19:45:02 +00:00
"""
Return a theme object using information parsed from XML
2014-01-01 09:33:07 +00:00
:param theme_xml: The Theme data object.
:param image_path: Where the theme image is stored
2015-09-08 19:13:59 +00:00
:return: Theme data.
2010-06-10 19:45:02 +00:00
"""
2017-05-13 07:47:22 +00:00
theme = Theme()
theme.parse(theme_xml)
2014-01-01 09:33:07 +00:00
theme.extend_image_filename(image_path)
2009-11-06 02:12:56 +00:00
return theme
2017-05-13 07:47:22 +00:00
@staticmethod
def _create_theme_from_json(theme_json, image_path):
"""
Return a theme object using information parsed from JSON
:param theme_json: The Theme data object.
:param image_path: Where the theme image is stored
:return: Theme data.
"""
theme = Theme()
theme.load_theme(theme_json)
theme.extend_image_filename(image_path)
return theme
2013-10-23 19:25:46 +00:00
def _validate_theme_action(self, select_text, confirm_title, confirm_text, test_plugin=True, confirm=True):
"""
2013-12-24 06:15:41 +00:00
Check to see if theme has been selected and the destructive action is allowed.
2014-01-01 09:33:07 +00:00
:param select_text: Text for message box if no item selected.
:param confirm_title: Confirm message title to be displayed.
:param confirm_text: Confirm message text to be displayed.
:param test_plugin: Do we check the plugins for theme usage.
:param confirm: Do we display a confirm box before run checks.
2015-09-08 19:13:59 +00:00
:return: True or False depending on the validity.
"""
2013-08-31 18:17:38 +00:00
self.global_theme = Settings().value(self.settings_section + '/global theme')
if check_item_selected(self.theme_list_widget, select_text):
item = self.theme_list_widget.currentItem()
2012-05-17 18:57:01 +00:00
theme = item.text()
# confirm deletion
2011-02-05 17:31:13 +00:00
if confirm:
2015-11-07 00:49:40 +00:00
answer = QtWidgets.QMessageBox.question(
self, confirm_title, confirm_text.format(theme_name=theme),
defaultButton=QtWidgets.QMessageBox.No)
2015-11-07 00:49:40 +00:00
if answer == QtWidgets.QMessageBox.No:
2011-02-05 17:31:13 +00:00
return False
# should be the same unless default
2012-05-19 15:10:05 +00:00
if theme != item.data(QtCore.Qt.UserRole):
critical_error_message_box(
2012-12-29 15:25:29 +00:00
message=translate('OpenLP.ThemeManager', 'You are unable to delete the default theme.'))
return False
2010-12-27 10:18:09 +00:00
# check for use in the system else where.
2013-10-23 19:25:46 +00:00
if test_plugin:
2015-10-16 16:30:13 +00:00
plugin_usage = ""
2013-01-22 21:09:43 +00:00
for plugin in self.plugin_manager.plugins:
2015-10-16 16:30:13 +00:00
used_count = plugin.uses_theme(theme)
if used_count:
2016-05-20 16:22:06 +00:00
plugin_usage = "{plug}{text}".format(plug=plugin_usage,
text=(translate('OpenLP.ThemeManager',
'{count} time(s) by {plugin}'
).format(name=used_count,
plugin=plugin.name)))
plugin_usage = "{text}\n".format(text=plugin_usage)
2015-10-16 16:30:13 +00:00
if plugin_usage:
critical_error_message_box(translate('OpenLP.ThemeManager', 'Unable to delete theme'),
2016-05-20 16:22:06 +00:00
translate('OpenLP.ThemeManager',
'Theme is currently used \n\n{text}'
).format(text=plugin_usage))
2015-10-16 16:30:13 +00:00
return False
return True
2014-03-20 19:10:31 +00:00
return False