openlp/openlp/core/ui/servicemanager.py

1775 lines
85 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
2019-04-13 13:00:22 +00:00
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
2022-02-01 10:10:57 +00:00
# Copyright (c) 2008-2022 OpenLP Developers #
2019-04-13 13:00:22 +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, either version 3 of the License, or #
# (at your option) any later version. #
# #
# 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, see <https://www.gnu.org/licenses/>. #
##########################################################################
2013-02-01 21:34:23 +00:00
"""
The service manager sets up, loads, saves and manages services.
"""
2013-12-26 08:56:53 +00:00
import html
2016-03-31 16:34:22 +00:00
import json
import shutil
2011-02-04 12:57:48 +00:00
import os
import zipfile
2017-12-27 16:06:36 +00:00
from contextlib import suppress
from datetime import datetime, timedelta
from pathlib import Path
2018-01-22 20:41:30 +00:00
from tempfile import NamedTemporaryFile
2015-11-07 00:49:40 +00:00
from PyQt5 import QtCore, QtGui, QtWidgets
2010-03-03 17:48:37 +00:00
2020-05-07 05:18:37 +00:00
from openlp.core.common import ThemeLevel, delete_file, sha256_file_hash
2019-07-31 21:01:22 +00:00
from openlp.core.state import State
2016-03-31 16:34:22 +00:00
from openlp.core.common.actions import ActionList, CategoryOrder
2017-10-07 07:05:07 +00:00
from openlp.core.common.applocation import AppLocation
from openlp.core.common.enum import ServiceItemType
2017-10-07 07:05:07 +00:00
from openlp.core.common.i18n import UiStrings, format_time, translate
from openlp.core.common.json import OpenLPJSONDecoder, OpenLPJSONEncoder
2017-10-23 22:09:57 +00:00
from openlp.core.common.mixins import LogMixin, RegistryProperties
from openlp.core.common.registry import Registry, RegistryBase
2020-05-07 05:18:37 +00:00
from openlp.core.lib import build_icon, ItemCapabilities
from openlp.core.lib.exceptions import ValidationError
2018-10-02 04:39:42 +00:00
from openlp.core.lib.plugin import PluginStatus
2020-05-07 05:18:37 +00:00
from openlp.core.lib.serviceitem import ServiceItem
2018-10-02 04:39:42 +00:00
from openlp.core.lib.ui import create_widget_action, critical_error_message_box, find_and_set_in_combo_box
from openlp.core.ui.icons import UiIcons
2019-06-11 18:40:20 +00:00
from openlp.core.ui.media import AUDIO_EXT, VIDEO_EXT
from openlp.core.ui.serviceitemeditform import ServiceItemEditForm
from openlp.core.ui.servicenoteform import ServiceNoteForm
from openlp.core.ui.starttimeform import StartTimeForm
2017-10-23 22:09:57 +00:00
from openlp.core.widgets.dialogs import FileDialog
from openlp.core.widgets.toolbar import OpenLPToolbar
2013-02-02 19:49:56 +00:00
2013-02-01 21:34:23 +00:00
2015-11-07 00:49:40 +00:00
class ServiceManagerList(QtWidgets.QTreeWidget):
2013-02-02 19:49:56 +00:00
"""
Set up key bindings and mouse behaviour for the service list
"""
2013-12-26 08:56:53 +00:00
def __init__(self, service_manager, parent=None):
2013-02-01 21:34:23 +00:00
"""
Constructor
"""
2013-07-18 14:32:23 +00:00
super(ServiceManagerList, self).__init__(parent)
2017-10-23 22:09:57 +00:00
self.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop)
self.setAlternatingRowColors(True)
self.setHeaderHidden(True)
self.setExpandsOnDoubleClick(False)
2013-12-26 08:56:53 +00:00
self.service_manager = service_manager
2013-02-02 19:49:56 +00:00
2017-10-23 22:09:57 +00:00
def dragEnterEvent(self, event):
"""
React to a drag enter event
"""
event.accept()
def dragMoveEvent(self, event):
"""
React to a drage move event
"""
event.accept()
2013-02-02 19:49:56 +00:00
def keyPressEvent(self, event):
"""
Capture Key press and respond accordingly.
2014-01-01 09:33:07 +00:00
:param event:
2013-02-02 19:49:56 +00:00
"""
if isinstance(event, QtGui.QKeyEvent):
# here accept the event and do something
if event.key() == QtCore.Qt.Key_Up:
2013-12-26 08:56:53 +00:00
self.service_manager.on_move_selection_up()
2013-02-02 19:49:56 +00:00
event.accept()
elif event.key() == QtCore.Qt.Key_Down:
2013-12-26 08:56:53 +00:00
self.service_manager.on_move_selection_down()
2013-02-02 19:49:56 +00:00
event.accept()
elif event.key() == QtCore.Qt.Key_Right:
self.service_manager.on_expand_selection()
event.accept()
elif event.key() == QtCore.Qt.Key_Left:
self.service_manager.on_collapse_selection()
event.accept()
2013-02-02 19:49:56 +00:00
elif event.key() == QtCore.Qt.Key_Delete:
self.service_manager.on_delete_from_service()
2013-02-02 19:49:56 +00:00
event.accept()
event.ignore()
else:
event.ignore()
def mouseMoveEvent(self, event):
"""
2013-03-29 12:53:07 +00:00
Drag and drop event does not care what data is selected as the recipient will use events to request the data
move just tell it what plugin to call
2014-01-01 09:33:07 +00:00
:param event:
2013-02-02 19:49:56 +00:00
"""
if event.buttons() != QtCore.Qt.LeftButton:
event.ignore()
return
if not self.itemAt(self.mapFromGlobal(QtGui.QCursor.pos())):
event.ignore()
return
drag = QtGui.QDrag(self)
mime_data = QtCore.QMimeData()
drag.setMimeData(mime_data)
2013-08-31 18:17:38 +00:00
mime_data.setText('ServiceManager')
2015-11-07 00:49:40 +00:00
drag.exec(QtCore.Qt.CopyAction)
2013-02-02 19:49:56 +00:00
2013-02-01 21:34:23 +00:00
class Ui_ServiceManager(object):
2013-02-02 19:49:56 +00:00
"""
UI part of the Service Manager
"""
def setup_ui(self, widget):
"""
Define the UI
2014-01-01 09:33:07 +00:00
:param widget:
2013-02-02 19:49:56 +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-02-02 19:49:56 +00:00
# Create the top toolbar
2017-10-23 22:09:57 +00:00
self.toolbar = OpenLPToolbar(self)
2018-04-07 16:16:42 +00:00
self.toolbar.add_toolbar_action('newService', text=UiStrings().NewService, icon=UiIcons().new,
2013-12-24 15:55:01 +00:00
tooltip=UiStrings().CreateService, triggers=self.on_new_service_clicked)
2013-08-31 18:17:38 +00:00
self.toolbar.add_toolbar_action('openService', text=UiStrings().OpenService,
2018-04-07 20:31:54 +00:00
icon=UiIcons().open,
2013-12-24 15:55:01 +00:00
tooltip=translate('OpenLP.ServiceManager', 'Load an existing service.'),
triggers=self.on_load_service_clicked)
2013-08-31 18:17:38 +00:00
self.toolbar.add_toolbar_action('saveService', text=UiStrings().SaveService,
2018-04-07 11:12:31 +00:00
icon=UiIcons().save,
2013-12-24 21:24:52 +00:00
tooltip=translate('OpenLP.ServiceManager', 'Save this service.'),
2013-12-24 15:55:01 +00:00
triggers=self.decide_save_method)
2013-02-02 19:49:56 +00:00
self.toolbar.addSeparator()
2016-05-20 16:22:06 +00:00
self.theme_label = QtWidgets.QLabel('{theme}:'.format(theme=UiStrings().Theme), widget)
2015-11-07 00:49:40 +00:00
self.theme_label.setContentsMargins(3, 3, 3, 3)
2013-08-31 18:17:38 +00:00
self.theme_label.setObjectName('theme_label')
2013-03-07 06:48:09 +00:00
self.toolbar.add_toolbar_widget(self.theme_label)
2015-11-07 00:49:40 +00:00
self.theme_combo_box = QtWidgets.QComboBox(self.toolbar)
2013-02-02 19:49:56 +00:00
self.theme_combo_box.setToolTip(translate('OpenLP.ServiceManager', 'Select a theme for the service.'))
2015-11-07 00:49:40 +00:00
self.theme_combo_box.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToMinimumContentsLength)
self.theme_combo_box.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
2013-08-31 18:17:38 +00:00
self.theme_combo_box.setObjectName('theme_combo_box')
2013-03-07 06:48:09 +00:00
self.toolbar.add_toolbar_widget(self.theme_combo_box)
2013-08-31 18:17:38 +00:00
self.toolbar.setObjectName('toolbar')
2013-02-02 19:49:56 +00:00
self.layout.addWidget(self.toolbar)
# Create the service manager list
2013-12-31 21:02:35 +00:00
self.service_manager_list = ServiceManagerList(widget)
2013-02-02 19:49:56 +00:00
self.service_manager_list.setEditTriggers(
2015-11-07 00:49:40 +00:00
QtWidgets.QAbstractItemView.CurrentChanged |
QtWidgets.QAbstractItemView.DoubleClicked |
QtWidgets.QAbstractItemView.EditKeyPressed)
2013-02-02 19:49:56 +00:00
self.service_manager_list.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
2013-02-14 21:50:10 +00:00
self.service_manager_list.customContextMenuRequested.connect(self.context_menu)
2013-08-31 18:17:38 +00:00
self.service_manager_list.setObjectName('service_manager_list')
2013-02-02 19:49:56 +00:00
# enable drop
2017-10-23 22:09:57 +00:00
self.service_manager_list.dropEvent = self.drop_event
2013-02-02 19:49:56 +00:00
self.layout.addWidget(self.service_manager_list)
# Add the bottom toolbar
2013-12-31 21:02:35 +00:00
self.order_toolbar = OpenLPToolbar(widget)
2013-02-02 19:49:56 +00:00
action_list = ActionList.get_instance()
action_list.add_category(UiStrings().Service, CategoryOrder.standard_toolbar)
2017-10-23 22:09:57 +00:00
self.move_top_action = self.order_toolbar.add_toolbar_action(
'moveTop',
text=translate('OpenLP.ServiceManager', 'Move to &top'), icon=UiIcons().move_start,
2013-02-02 19:49:56 +00:00
tooltip=translate('OpenLP.ServiceManager', 'Move item to the top of the service.'),
can_shortcuts=True, category=UiStrings().Service, triggers=self.on_service_top)
2017-10-23 22:09:57 +00:00
self.move_up_action = self.order_toolbar.add_toolbar_action(
'moveUp',
text=translate('OpenLP.ServiceManager', 'Move &up'), icon=UiIcons().move_up,
2013-02-02 19:49:56 +00:00
tooltip=translate('OpenLP.ServiceManager', 'Move item up one position in the service.'),
can_shortcuts=True, category=UiStrings().Service, triggers=self.on_service_up)
2017-10-23 22:09:57 +00:00
self.move_down_action = self.order_toolbar.add_toolbar_action(
'moveDown',
text=translate('OpenLP.ServiceManager', 'Move &down'), icon=UiIcons().move_down,
2013-02-02 19:49:56 +00:00
tooltip=translate('OpenLP.ServiceManager', 'Move item down one position in the service.'),
can_shortcuts=True, category=UiStrings().Service, triggers=self.on_service_down)
2017-10-23 22:09:57 +00:00
self.move_bottom_action = self.order_toolbar.add_toolbar_action(
'moveBottom',
text=translate('OpenLP.ServiceManager', 'Move to &bottom'), icon=UiIcons().move_end,
2013-02-02 19:49:56 +00:00
tooltip=translate('OpenLP.ServiceManager', 'Move item to the end of the service.'),
can_shortcuts=True, category=UiStrings().Service, triggers=self.on_service_end)
2013-02-02 19:49:56 +00:00
self.order_toolbar.addSeparator()
2017-10-23 22:09:57 +00:00
self.delete_action = self.order_toolbar.add_toolbar_action(
'delete', can_shortcuts=True,
2018-04-07 20:31:54 +00:00
text=translate('OpenLP.ServiceManager', '&Delete From Service'), icon=UiIcons().delete,
2013-02-02 19:49:56 +00:00
tooltip=translate('OpenLP.ServiceManager', 'Delete the selected item from the service.'),
triggers=self.on_delete_from_service)
2013-02-02 19:49:56 +00:00
self.order_toolbar.addSeparator()
2017-10-23 22:09:57 +00:00
self.expand_action = self.order_toolbar.add_toolbar_action(
'expand', can_shortcuts=True,
2018-04-08 18:21:22 +00:00
text=translate('OpenLP.ServiceManager', '&Expand all'), icon=UiIcons().plus,
2013-02-02 19:49:56 +00:00
tooltip=translate('OpenLP.ServiceManager', 'Expand all the service items.'),
2013-12-26 08:56:53 +00:00
category=UiStrings().Service, triggers=self.on_expand_all)
2017-10-23 22:09:57 +00:00
self.collapse_action = self.order_toolbar.add_toolbar_action(
'collapse', can_shortcuts=True,
2018-04-08 18:21:22 +00:00
text=translate('OpenLP.ServiceManager', '&Collapse all'), icon=UiIcons().minus,
2013-02-02 19:49:56 +00:00
tooltip=translate('OpenLP.ServiceManager', 'Collapse all the service items.'),
2013-12-26 08:56:53 +00:00
category=UiStrings().Service, triggers=self.on_collapse_all)
2013-02-02 19:49:56 +00:00
self.order_toolbar.addSeparator()
2017-10-23 22:09:57 +00:00
self.make_live_action = self.order_toolbar.add_toolbar_action(
'make_live', can_shortcuts=True,
2018-04-07 20:31:54 +00:00
text=translate('OpenLP.ServiceManager', 'Go Live'), icon=UiIcons().live,
2013-02-02 19:49:56 +00:00
tooltip=translate('OpenLP.ServiceManager', 'Send the selected item to Live.'),
category=UiStrings().Service,
triggers=self.on_make_live_action_triggered)
2013-02-02 19:49:56 +00:00
self.layout.addWidget(self.order_toolbar)
# Connect up our signals and slots
2013-02-14 21:50:10 +00:00
self.theme_combo_box.activated.connect(self.on_theme_combo_box_selected)
self.service_manager_list.doubleClicked.connect(self.on_double_click_live)
self.service_manager_list.clicked.connect(self.on_single_click_preview)
2013-02-14 21:50:10 +00:00
self.service_manager_list.itemCollapsed.connect(self.collapsed)
self.service_manager_list.itemExpanded.connect(self.expanded)
2013-02-02 19:49:56 +00:00
# Last little bits of setting up
2020-06-06 16:05:36 +00:00
self.service_theme = self.settings.value('servicemanager/service theme')
2019-03-10 21:01:39 +00:00
self.service_path = AppLocation.get_section_data_path('servicemanager')
2013-02-02 19:49:56 +00:00
# build the drag and drop context menu
2015-11-07 00:49:40 +00:00
self.dnd_menu = QtWidgets.QMenu()
2013-12-26 08:56:53 +00:00
self.new_action = self.dnd_menu.addAction(translate('OpenLP.ServiceManager', '&Add New Item'))
2018-04-08 19:54:28 +00:00
self.new_action.setIcon(UiIcons().edit)
2013-12-26 08:56:53 +00:00
self.add_to_action = self.dnd_menu.addAction(translate('OpenLP.ServiceManager', '&Add to Selected Item'))
2018-04-08 19:54:28 +00:00
self.add_to_action.setIcon(UiIcons().edit)
2013-02-02 19:49:56 +00:00
# build the context menu
2015-11-07 00:49:40 +00:00
self.menu = QtWidgets.QMenu()
2013-02-02 19:49:56 +00:00
self.edit_action = create_widget_action(self.menu, text=translate('OpenLP.ServiceManager', '&Edit Item'),
2018-04-07 20:31:54 +00:00
icon=UiIcons().edit, triggers=self.remote_edit)
2013-04-02 10:19:33 +00:00
self.rename_action = create_widget_action(self.menu, text=translate('OpenLP.ServiceManager', '&Rename...'),
2018-04-07 20:31:54 +00:00
icon=UiIcons().edit,
2014-04-13 07:54:34 +00:00
triggers=self.on_service_item_rename)
2013-02-02 19:49:56 +00:00
self.maintain_action = create_widget_action(self.menu, text=translate('OpenLP.ServiceManager', '&Reorder Item'),
2018-04-07 20:31:54 +00:00
icon=UiIcons().edit,
2013-12-26 08:56:53 +00:00
triggers=self.on_service_item_edit_form)
2013-02-02 19:49:56 +00:00
self.notes_action = create_widget_action(self.menu, text=translate('OpenLP.ServiceManager', '&Notes'),
2018-04-07 20:31:54 +00:00
icon=UiIcons().notes,
2013-12-26 08:56:53 +00:00
triggers=self.on_service_item_note_form)
2013-02-02 19:49:56 +00:00
self.time_action = create_widget_action(self.menu, text=translate('OpenLP.ServiceManager', '&Start Time'),
2018-05-08 19:45:34 +00:00
icon=UiIcons().time, triggers=self.on_start_time_form)
2013-08-31 18:17:38 +00:00
self.auto_start_action = create_widget_action(self.menu, text='',
2018-05-06 20:11:06 +00:00
icon=UiIcons().active,
2013-12-26 08:56:53 +00:00
triggers=self.on_auto_start)
2013-02-02 19:49:56 +00:00
# Add already existing delete action to the menu.
2017-10-23 22:09:57 +00:00
self.menu.addAction(self.delete_action)
2013-02-02 19:49:56 +00:00
self.create_custom_action = create_widget_action(self.menu,
2013-12-26 08:56:53 +00:00
text=translate('OpenLP.ServiceManager', 'Create New &Custom '
'Slide'),
2018-04-07 20:31:54 +00:00
icon=UiIcons().clone,
2013-12-26 08:56:53 +00:00
triggers=self.create_custom)
2013-02-02 19:49:56 +00:00
self.menu.addSeparator()
# Add AutoPlay menu actions
2015-11-07 00:49:40 +00:00
self.auto_play_slides_menu = QtWidgets.QMenu(translate('OpenLP.ServiceManager', '&Auto play slides'))
self.menu.addMenu(self.auto_play_slides_menu)
2015-11-07 00:49:40 +00:00
auto_play_slides_group = QtWidgets.QActionGroup(self.auto_play_slides_menu)
auto_play_slides_group.setExclusive(True)
self.auto_play_slides_loop = create_widget_action(self.auto_play_slides_menu,
2013-12-26 08:56:53 +00:00
text=translate('OpenLP.ServiceManager', 'Auto play slides '
'&Loop'),
checked=False, triggers=self.toggle_auto_play_slides_loop)
auto_play_slides_group.addAction(self.auto_play_slides_loop)
self.auto_play_slides_once = create_widget_action(self.auto_play_slides_menu,
2013-12-26 08:56:53 +00:00
text=translate('OpenLP.ServiceManager', 'Auto play slides '
'&Once'),
checked=False, triggers=self.toggle_auto_play_slides_once)
auto_play_slides_group.addAction(self.auto_play_slides_once)
self.auto_play_slides_menu.addSeparator()
self.timed_slide_interval = create_widget_action(self.auto_play_slides_menu,
2013-12-26 08:56:53 +00:00
text=translate('OpenLP.ServiceManager', '&Delay between '
'slides'),
triggers=self.on_timed_slide_interval)
2013-02-02 19:49:56 +00:00
self.menu.addSeparator()
self.preview_action = create_widget_action(self.menu, text=translate('OpenLP.ServiceManager', 'Show &Preview'),
2018-04-07 20:31:54 +00:00
icon=UiIcons().preview, triggers=self.make_preview)
2013-02-02 19:49:56 +00:00
# Add already existing make live action to the menu.
2017-10-23 22:09:57 +00:00
self.menu.addAction(self.make_live_action)
2013-02-02 19:49:56 +00:00
self.menu.addSeparator()
2015-11-07 00:49:40 +00:00
self.theme_menu = QtWidgets.QMenu(translate('OpenLP.ServiceManager', '&Change Item Theme'))
2013-02-02 19:49:56 +00:00
self.menu.addMenu(self.theme_menu)
2017-10-23 22:09:57 +00:00
self.service_manager_list.addActions([self.move_down_action, self.move_up_action, self.make_live_action,
self.move_top_action, self.move_bottom_action, self.expand_action,
self.collapse_action])
2013-02-02 19:49:56 +00:00
2013-01-27 07:36:04 +00:00
2017-10-23 22:09:57 +00:00
class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixin, RegistryProperties):
2013-01-27 07:36:04 +00:00
"""
2013-03-29 12:53:07 +00:00
Manages the services. This involves taking text strings from plugins and adding them to the service. This service
can then be zipped up with all the resources used into one OSZ or OSZL file for use on any OpenLP installation.
2013-03-29 12:53:07 +00:00
Also handles the UI tasks of moving things up and down etc.
2013-01-27 07:36:04 +00:00
"""
2015-11-07 00:49:40 +00:00
servicemanager_set_item = QtCore.pyqtSignal(int)
2020-04-26 21:37:35 +00:00
servicemanager_set_item_by_uuid = QtCore.pyqtSignal(str)
2015-11-07 00:49:40 +00:00
servicemanager_next_item = QtCore.pyqtSignal()
servicemanager_previous_item = QtCore.pyqtSignal()
servicemanager_new_file = QtCore.pyqtSignal()
2020-10-28 04:06:56 +00:00
theme_update_service = QtCore.pyqtSignal()
2015-11-07 00:49:40 +00:00
2013-01-27 07:36:04 +00:00
def __init__(self, parent=None):
"""
Sets up the service manager, toolbars, list view, et al.
"""
2017-10-23 22:09:57 +00:00
super().__init__(parent)
2013-01-27 07:36:04 +00:00
self.service_items = []
self.suffixes = set()
self.add_media_suffixes()
self.drop_position = -1
2013-01-27 07:36:04 +00:00
self.service_id = 0
# is a new service and has not been saved
self._modified = False
2017-11-18 23:14:28 +00:00
self._service_path = None
2013-01-27 07:36:04 +00:00
self.service_has_all_original_files = True
self.list_double_clicked = False
2020-05-07 05:18:37 +00:00
self.servicefile_version = None
def bootstrap_initialise(self):
2014-01-01 09:33:07 +00:00
"""
To be called as part of initialisation
"""
2013-01-27 07:36:04 +00:00
self.setup_ui(self)
2013-03-17 21:20:40 +00:00
# Need to use event as called across threads and UI is updated
2015-11-07 00:49:40 +00:00
self.servicemanager_set_item.connect(self.on_set_item)
2020-04-26 21:37:35 +00:00
self.servicemanager_set_item_by_uuid.connect(self.set_item_by_uuid)
2015-11-07 00:49:40 +00:00
self.servicemanager_next_item.connect(self.next_item)
self.servicemanager_previous_item.connect(self.previous_item)
self.servicemanager_new_file.connect(self.new_file)
2022-01-16 13:15:09 +00:00
# This signal is used to update the theme on the ui thread from the web api thread
self.theme_update_service.connect(self.on_service_theme_change)
Registry().register_function('theme_update_list', self.update_theme_list)
Registry().register_function('theme_level_changed', self.on_theme_level_changed)
Registry().register_function('config_screen_changed', self.regenerate_service_items)
Registry().register_function('theme_change_global', self.regenerate_service_items)
Registry().register_function('theme_change_service', self.regenerate_changed_service_items)
Registry().register_function('mediaitem_suffix_reset', self.reset_supported_suffixes)
2013-01-27 07:36:04 +00:00
def bootstrap_post_set_up(self):
"""
Can be set up as a late setup
"""
self.service_note_form = ServiceNoteForm()
self.service_item_edit_form = ServiceItemEditForm()
self.start_time_form = StartTimeForm()
def add_media_suffixes(self):
"""
Add the suffixes supported by :mod:`openlp.core.ui.media.vlcplayer`
"""
self.suffixes.update(AUDIO_EXT)
self.suffixes.update(VIDEO_EXT)
2013-01-27 07:36:04 +00:00
def set_modified(self, modified=True):
"""
2013-03-29 12:53:07 +00:00
Setter for property "modified". Sets whether or not the current service has been modified.
2014-01-01 09:33:07 +00:00
:param modified: Indicates if the service has new or removed items. Used to trigger a remote update.
"""
2012-04-10 19:38:25 +00:00
if modified:
2013-01-21 07:29:43 +00:00
self.service_id += 1
self._modified = modified
2017-11-22 21:39:40 +00:00
if self._service_path:
service_file = self._service_path.name
else:
service_file = translate('OpenLP.ServiceManager', 'Untitled Service')
2013-03-16 11:05:52 +00:00
self.main_window.set_service_modified(modified, service_file)
2013-01-27 07:36:04 +00:00
def is_modified(self):
"""
Getter for boolean property "modified".
"""
return self._modified
2017-09-06 21:36:31 +00:00
def set_file_name(self, file_path):
"""
Setter for service file.
2014-01-01 09:57:06 +00:00
:param Path file_path: The service file name
2017-09-06 21:36:31 +00:00
:rtype: None
"""
2017-11-18 23:14:28 +00:00
self._service_path = file_path
2017-12-09 08:22:14 +00:00
self.set_modified(self.is_modified())
self.settings.setValue('servicemanager/last file', file_path)
if file_path and file_path.suffix == '.oszl':
2017-09-18 06:20:06 +00:00
self._save_lite = True
else:
self._save_lite = False
2013-01-27 07:36:04 +00:00
def file_name(self):
"""
Return the current file name including path.
2017-11-18 23:14:28 +00:00
:rtype: Path
"""
2017-11-18 23:14:28 +00:00
return self._service_path
2013-01-27 07:36:04 +00:00
def short_file_name(self):
"""
Return the current file name, excluding the path.
"""
if self._service_path:
return self._service_path.name
2013-01-27 07:36:04 +00:00
def reset_supported_suffixes(self):
"""
2012-11-26 17:57:36 +00:00
Resets the Suffixes list.
"""
self.suffixes.clear()
2013-12-31 20:29:03 +00:00
def supported_suffixes(self, suffix_list):
2012-08-24 20:06:56 +00:00
"""
2013-03-29 12:53:07 +00:00
Adds Suffixes supported to the master list. Called from Plugins.
2012-08-24 20:06:56 +00:00
:param list[str] | str suffix_list: New suffix(s) to be supported
2012-08-24 20:06:56 +00:00
"""
2014-06-05 16:25:37 +00:00
if isinstance(suffix_list, str):
self.suffixes.add(suffix_list)
2014-06-05 16:25:37 +00:00
else:
self.suffixes.update(suffix_list)
2010-05-05 19:14:48 +00:00
def on_new_service_clicked(self):
"""
Create a new service.
"""
2013-01-27 07:36:04 +00:00
if self.is_modified():
result = self.save_modified_service()
2015-11-07 00:49:40 +00:00
if result == QtWidgets.QMessageBox.Cancel:
return False
2015-11-07 00:49:40 +00:00
elif result == QtWidgets.QMessageBox.Save:
2013-01-27 07:36:04 +00:00
if not self.decide_save_method():
return False
2021-08-18 17:59:29 +00:00
if not self.service_items and self.settings.value('advanced/new service message'):
do_not_show_again = QtWidgets.QCheckBox(translate('OpenLP.Ui', 'Do not show this message again'), None)
message_box = QtWidgets.QMessageBox(QtWidgets.QMessageBox.Information,
translate('OpenLP.Ui', 'Create a new service.'),
translate('OpenLP.Ui', 'You already have a blank new service.\n'
'Add some items to it then press Save'),
QtWidgets.QMessageBox.Ok,
self)
message_box.setCheckBox(do_not_show_again)
message_box.exec()
if message_box.checkBox().isChecked():
self.settings.setValue('advanced/new service message', False)
2013-01-27 07:36:04 +00:00
self.new_file()
def on_load_service_clicked(self, checked):
"""
Handle the `fileOpenItem` action
2019-05-04 18:25:59 +00:00
:param bool checked: Not used.
:rtype: None
"""
self.load_service()
def load_service(self, file_path=None):
2011-08-02 18:17:07 +00:00
"""
2013-03-29 12:53:07 +00:00
Loads the service file and saves the existing one it there is one unchanged.
2011-08-02 18:17:07 +00:00
:param Path | None file_path: The service file to the loaded.
2011-08-02 18:17:07 +00:00
"""
2013-01-27 07:36:04 +00:00
if self.is_modified():
result = self.save_modified_service()
2015-11-07 00:49:40 +00:00
if result == QtWidgets.QMessageBox.Cancel:
return False
2015-11-07 00:49:40 +00:00
elif result == QtWidgets.QMessageBox.Save:
2013-01-27 07:36:04 +00:00
self.decide_save_method()
if not file_path:
file_path, filter_used = FileDialog.getOpenFileName(
self.main_window,
2011-08-02 18:17:07 +00:00
translate('OpenLP.ServiceManager', 'Open File'),
2020-06-06 16:05:36 +00:00
self.settings.value('servicemanager/last directory'),
2015-11-07 00:49:40 +00:00
translate('OpenLP.ServiceManager', 'OpenLP Service Files (*.osz *.oszl)'))
if not file_path:
2011-08-02 18:17:07 +00:00
return False
2020-06-06 16:05:36 +00:00
self.settings.setValue('servicemanager/last directory', file_path.parent)
self.load_file(file_path)
2013-01-27 07:36:04 +00:00
def save_modified_service(self):
2013-01-21 11:11:47 +00:00
"""
Check to see if a service needs to be saved.
"""
2015-11-07 00:49:40 +00:00
return QtWidgets.QMessageBox.question(self.main_window,
translate('OpenLP.ServiceManager', 'Modified Service'),
translate('OpenLP.ServiceManager',
'The current service has been modified. Would you like to save '
'this service?'),
QtWidgets.QMessageBox.Save | QtWidgets.QMessageBox.Discard |
QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Save)
def on_recent_service_clicked(self, checked):
2013-01-21 11:11:47 +00:00
"""
Load a recent file as the service triggered by mainwindow recent service list.
:param bool checked: Not used
2013-01-21 11:11:47 +00:00
"""
if self.is_modified():
result = self.save_modified_service()
if result == QtWidgets.QMessageBox.Cancel:
return False
elif result == QtWidgets.QMessageBox.Save:
self.decide_save_method()
sender = self.sender()
self.load_file(Path(sender.data()))
2013-01-27 07:36:04 +00:00
def new_file(self):
"""
Create a blank new service file.
"""
2013-01-27 07:36:04 +00:00
self.service_manager_list.clear()
self.service_items = []
2017-09-06 21:36:31 +00:00
self.set_file_name(None)
2013-01-21 07:29:43 +00:00
self.service_id += 1
2013-01-27 07:36:04 +00:00
self.set_modified(False)
self.settings.setValue('servicemanager/last file', None)
2013-02-03 09:07:31 +00:00
self.plugin_manager.new_service_created()
self.live_controller.slide_count = 0
2014-01-04 16:33:20 +00:00
def create_basic_service(self):
"""
Create the initial service array with the base items to be saved.
2015-09-08 19:13:59 +00:00
:return: service array
2014-01-04 16:33:20 +00:00
"""
service = []
2020-05-07 05:18:37 +00:00
# Regarding openlp-servicefile-version:
# 1: OpenLP 1? Not used.
# 2: OpenLP 2 (default when loading a service file without openlp-servicefile-version)
# 3: The new format introduced in OpenLP 3.0.
# Note that the servicefile-version numbering is not expected to follow the OpenLP version numbering.
2016-07-16 22:20:56 +00:00
core = {
'lite-service': self._save_lite,
2020-05-07 05:18:37 +00:00
'service-theme': self.service_theme,
'openlp-servicefile-version': 3
2016-07-16 22:20:56 +00:00
}
2014-01-04 16:33:20 +00:00
service.append({'openlp_core': core})
return service
2017-12-27 16:06:36 +00:00
def get_write_file_list(self):
2018-01-14 09:01:00 +00:00
"""
2018-01-20 09:30:30 +00:00
Get a list of files used in the service and files that are missing.
2018-01-14 09:01:00 +00:00
2018-01-20 09:30:30 +00:00
:return: A list of files used in the service that exist, and a list of files that don't.
2020-05-07 05:18:37 +00:00
:rtype: (list[Path], list[str])
2018-01-14 09:01:00 +00:00
"""
write_list = []
missing_list = []
2020-05-07 05:18:37 +00:00
# Run through all items
2013-01-27 07:36:04 +00:00
for item in self.service_items:
2020-05-07 05:18:37 +00:00
# If the item has files, see if they exists
if item['service_item'].uses_file():
for frame in item['service_item'].get_frames():
path_from = item['service_item'].get_frame_path(frame=frame)
2020-05-07 05:18:37 +00:00
path_from_path = Path(path_from)
if item['service_item'].stored_filename:
sha256_file_name = item['service_item'].stored_filename
else:
sha256_file_name = sha256_file_hash(path_from_path) + os.path.splitext(path_from)[1]
path_from_tuple = (path_from_path, sha256_file_name)
if path_from_tuple in write_list or str(path_from_path) in missing_list:
continue
if not os.path.exists(path_from):
2020-05-07 05:18:37 +00:00
missing_list.append(str(path_from_path))
else:
2020-05-07 05:18:37 +00:00
write_list.append(path_from_tuple)
# For items that has thumbnails, add them to the list
if item['service_item'].is_capable(ItemCapabilities.HasThumbnails):
thumbnail_path = item['service_item'].get_thumbnail_path()
if not thumbnail_path:
continue
2020-05-07 05:18:37 +00:00
thumbnail_path_parent = Path(thumbnail_path).parent
if item['service_item'].is_command():
# Run through everything in the thumbnail folder and add pictures
for filename in os.listdir(thumbnail_path):
# Skip non-pictures
if os.path.splitext(filename)[1] not in ['.png', '.jpg']:
continue
filename_path = Path(thumbnail_path) / Path(filename)
# Create a thumbnail path in the zip/service file
service_path = filename_path.relative_to(thumbnail_path_parent)
write_list.append((filename_path, service_path))
elif item['service_item'].is_image():
# Find all image thumbnails and store them
# All image thumbnails will be put in a folder named 'thumbnails'
for frame in item['service_item'].get_frames():
if 'thumbnail' in frame:
filename_path = Path(thumbnail_path) / Path(frame['thumbnail'])
# Create a thumbnail path in the zip/service file
service_path = filename_path.relative_to(thumbnail_path_parent)
path_from_tuple = (filename_path, service_path)
if path_from_tuple in write_list:
continue
write_list.append(path_from_tuple)
for audio_path in item['service_item'].background_audio:
2020-05-07 05:18:37 +00:00
service_path = sha256_file_hash(audio_path) + os.path.splitext(audio_path)[1]
audio_path_tuple = (audio_path, service_path)
if audio_path_tuple in write_list:
continue
2020-05-07 05:18:37 +00:00
write_list.append(audio_path_tuple)
2017-12-27 16:06:36 +00:00
return write_list, missing_list
def save_file(self):
"""
Save the current service file.
2013-03-29 12:53:07 +00:00
A temporary file is created so that we don't overwrite the existing one and leave a mangled service file should
there be an error when saving. Audio files are also copied into the service manager directory, and then packaged
into the zip file.
"""
2017-12-27 16:06:36 +00:00
file_path = self.file_name()
self.log_debug('ServiceManager.save_file - {name}'.format(name=file_path))
self.application.set_busy_cursor()
2014-01-04 16:33:20 +00:00
service = self.create_basic_service()
write_list = []
missing_list = []
if not self._save_lite:
write_list, missing_list = self.get_write_file_list()
if missing_list:
self.application.set_normal_cursor()
title = translate('OpenLP.ServiceManager', 'Service File(s) Missing')
message = translate('OpenLP.ServiceManager',
'The following file(s) in the service are missing: {name}\n\n'
'These files will be removed if you continue to save.'
).format(name='\n\t'.join(missing_list))
answer = QtWidgets.QMessageBox.critical(self, title, message,
QtWidgets.QMessageBox.StandardButtons(
QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel))
if answer == QtWidgets.QMessageBox.Cancel:
return False
# Check if item contains a missing file.
2013-01-27 07:36:04 +00:00
for item in list(self.service_items):
if not self._save_lite:
item['service_item'].remove_invalid_frames(missing_list)
if item['service_item'].missing_frames():
self.service_items.remove(item)
continue
service_item = item['service_item'].get_service_repr(self._save_lite)
# Add the service item to the service.
service.append({'serviceitem': service_item})
2013-01-27 20:36:18 +00:00
self.repaint_service_list(-1, -1)
service_content = json.dumps(service, cls=OpenLPJSONEncoder)
service_content_size = len(bytes(service_content, encoding='utf-8'))
total_size = service_content_size
2020-05-07 05:18:37 +00:00
for local_file_item, zip_file_item in write_list:
total_size += local_file_item.stat().st_size
2013-12-31 20:29:03 +00:00
self.log_debug('ServiceManager.save_file - ZIP contents size is %i bytes' % total_size)
self.main_window.display_progress_bar(1000)
try:
2018-01-22 20:41:30 +00:00
with NamedTemporaryFile(dir=str(file_path.parent), prefix='.') as temp_file, \
2017-12-27 16:06:36 +00:00
zipfile.ZipFile(temp_file, 'w') as zip_file:
# First we add service contents..
zip_file.writestr('service_data.osj', service_content)
self.main_window.increment_progress_bar(service_content_size / total_size * 1000)
2017-12-27 16:06:36 +00:00
# Finally add all the listed media files.
2020-05-07 05:18:37 +00:00
for local_file_item, zip_file_item in write_list:
zip_file.write(str(local_file_item), str(zip_file_item))
self.main_window.increment_progress_bar(local_file_item.stat().st_size / total_size * 1000)
with suppress(FileNotFoundError):
file_path.unlink()
2021-04-25 06:18:50 +00:00
# Try to link rather than copy to prevent writing another file
try:
os.link(temp_file.name, file_path)
except OSError:
shutil.copyfile(temp_file.name, file_path)
2020-06-06 16:05:36 +00:00
self.settings.setValue('servicemanager/last directory', file_path.parent)
2017-12-27 16:06:36 +00:00
except (PermissionError, OSError) as error:
2018-01-22 21:37:00 +00:00
self.log_exception('Failed to save service to disk: {name}'.format(name=file_path))
2017-12-27 16:06:36 +00:00
self.main_window.error_message(
translate('OpenLP.ServiceManager', 'Error Saving File'),
2018-01-21 07:40:26 +00:00
translate('OpenLP.ServiceManager',
'There was an error saving your file.\n\n{error}').format(error=error))
2013-01-27 07:36:04 +00:00
return self.save_file_as()
2013-03-16 11:05:52 +00:00
self.main_window.finished_progress_bar()
2013-02-03 19:23:12 +00:00
self.application.set_normal_cursor()
2017-12-27 16:06:36 +00:00
self.main_window.add_recent_file(file_path)
self.set_modified(False)
return True
2012-09-14 16:35:07 +00:00
def save_file_as(self):
"""
2013-03-29 12:53:07 +00:00
Get a file name and then call :func:`ServiceManager.save_file` to save the file.
"""
default_service_enabled = self.settings.value('advanced/default service enabled')
if default_service_enabled:
service_day = self.settings.value('advanced/default service day')
if service_day == 7:
2012-09-16 15:33:05 +00:00
local_time = datetime.now()
else:
service_hour = self.settings.value('advanced/default service hour')
service_minute = self.settings.value('advanced/default service minute')
now = datetime.now()
day_delta = service_day - now.weekday()
if day_delta < 0:
day_delta += 7
time = now + timedelta(days=day_delta)
2012-09-16 15:33:05 +00:00
local_time = time.replace(hour=service_hour, minute=service_minute)
default_pattern = self.settings.value('advanced/default service name')
2013-01-27 07:36:04 +00:00
default_file_name = format_time(default_pattern, local_time)
else:
2013-08-31 18:17:38 +00:00
default_file_name = ''
default_file_path = Path(default_file_name)
2020-06-06 16:05:36 +00:00
directory_path = self.settings.value('servicemanager/last directory')
if directory_path:
default_file_path = directory_path / default_file_path
lite_filter = translate('OpenLP.ServiceManager', 'OpenLP Service Files - lite (*.oszl)')
packaged_filter = translate('OpenLP.ServiceManager', 'OpenLP Service Files (*.osz)')
2017-11-18 23:14:28 +00:00
if self._service_path and self._service_path.suffix == '.oszl':
default_filter = lite_filter
else:
default_filter = packaged_filter
# SaveAs from osz to oszl is not valid as the files will be deleted on exit which is not sensible or usable in
# the long term.
2017-11-18 23:14:28 +00:00
if self._service_path and self._service_path.suffix == '.oszl' or self.service_has_all_original_files:
2017-09-06 21:36:31 +00:00
file_path, filter_used = FileDialog.getSaveFileName(
self.main_window, UiStrings().SaveService, default_file_path,
'{packaged};; {lite}'.format(packaged=packaged_filter, lite=lite_filter),
default_filter)
2012-11-01 16:36:45 +00:00
else:
2017-09-06 21:36:31 +00:00
file_path, filter_used = FileDialog.getSaveFileName(
self.main_window, UiStrings().SaveService, default_file_path,
'{packaged};;'.format(packaged=packaged_filter))
2017-09-06 21:36:31 +00:00
if not file_path:
return False
if filter_used == lite_filter:
file_path = file_path.with_suffix('.oszl')
else:
file_path = file_path.with_suffix('.osz')
2017-09-06 21:36:31 +00:00
self.set_file_name(file_path)
2013-01-27 07:36:04 +00:00
self.decide_save_method()
def decide_save_method(self):
"""
Determine which type of save method to use.
"""
2013-01-27 07:36:04 +00:00
if not self.file_name():
return self.save_file_as()
return self.save_file()
def load_file(self, file_path):
"""
Load an existing service file.
:param Path file_path: The service file to load.
"""
# If the file_path is a string, this method will fail. Typecase to Path
file_path = Path(file_path)
2022-01-21 07:49:55 +00:00
try:
if not file_path.exists():
return False
except OSError:
# if the filename, directory name, or volume label syntax is incorrect it can cause an exception
return False
service_data = None
2013-02-03 19:23:12 +00:00
self.application.set_busy_cursor()
try:
2020-05-07 05:18:37 +00:00
# TODO: figure out a way to use the presentation thumbnails from the service file
with zipfile.ZipFile(str(file_path)) as zip_file:
compressed_size = 0
for zip_info in zip_file.infolist():
compressed_size += zip_info.compress_size
self.main_window.display_progress_bar(compressed_size)
2020-05-07 05:18:37 +00:00
# First find the osj-file to find out how to handle the file
for zip_info in zip_file.infolist():
# The json file has been called 'service_data.osj' since OpenLP 3.0
if zip_info.filename == 'service_data.osj' or zip_info.filename.endswith('osj'):
with zip_file.open(zip_info, 'r') as json_file:
service_data = json_file.read()
break
if service_data:
items = json.loads(service_data, cls=OpenLPJSONDecoder)
else:
raise ValidationError(msg='No service data found')
# Extract the service file version
for item in items:
if 'openlp_core' in item:
item = item['openlp_core']
self.servicefile_version = item.get('openlp-servicefile-version', 2)
break
self.log_debug('Service format version: %{ver}'.format(ver=self.servicefile_version))
for zip_info in zip_file.infolist():
self.log_debug('Extract file: {name}'.format(name=zip_info.filename))
# The json file has been called 'service_data.osj' since OpenLP 3.0
if zip_info.filename == 'service_data.osj' or zip_info.filename.endswith('osj'):
with zip_file.open(zip_info, 'r') as json_file:
service_data = json_file.read()
else:
2020-05-07 05:18:37 +00:00
# Service files from earlier versions than 3 expects that all files are extracted
# into the root of the service folder.
if self.servicefile_version and self.servicefile_version < 3:
zip_info.filename = os.path.basename(zip_info.filename.replace('/', os.path.sep))
zip_file.extract(zip_info, str(self.service_path))
self.main_window.increment_progress_bar(zip_info.compress_size)
2020-05-07 05:18:37 +00:00
# Handle the content
2013-01-27 07:36:04 +00:00
self.new_file()
2014-01-04 16:33:20 +00:00
self.process_service_items(items)
self.set_file_name(file_path)
self.main_window.add_recent_file(file_path)
2013-01-27 07:36:04 +00:00
self.set_modified(False)
self.settings.setValue('servicemanager/last file', file_path)
2018-10-27 04:13:33 +00:00
except (NameError, OSError, ValidationError, zipfile.BadZipFile):
self.application.set_normal_cursor()
self.log_exception('Problem loading service file {name}'.format(name=file_path))
critical_error_message_box(
message=translate('OpenLP.ServiceManager',
'The service file {file_path} could not be loaded because it is either corrupt, '
'inaccessible, or not a valid OpenLP 2 or OpenLP 3 service file.'
).format(file_path=file_path))
2013-03-16 11:05:52 +00:00
self.main_window.finished_progress_bar()
2013-02-03 19:23:12 +00:00
self.application.set_normal_cursor()
2013-01-27 20:36:18 +00:00
self.repaint_service_list(-1, -1)
2014-01-04 16:33:20 +00:00
def process_service_items(self, service_items):
"""
Process all the array of service items loaded from the saved service
:param service_items: list of service_items
"""
for item in service_items:
self.main_window.increment_progress_bar()
service_item = ServiceItem()
if 'openlp_core' in item:
item = item['openlp_core']
self._save_lite = item.get('lite-service', False)
2014-01-04 16:33:20 +00:00
theme = item.get('service-theme', None)
if theme:
find_and_set_in_combo_box(self.theme_combo_box, theme, set_missing=False)
if theme == self.theme_combo_box.currentText():
2019-08-30 11:28:10 +00:00
self.service_theme = theme
2014-01-04 16:33:20 +00:00
else:
if self._save_lite:
2020-05-07 05:18:37 +00:00
service_item.set_from_service(item, version=self.servicefile_version)
2014-01-04 16:33:20 +00:00
else:
2020-05-07 05:18:37 +00:00
service_item.set_from_service(item, self.service_path, self.servicefile_version)
2014-01-04 16:33:20 +00:00
service_item.validate_item(self.suffixes)
if service_item.is_capable(ItemCapabilities.OnLoadUpdate):
new_item = Registry().get(service_item.name).service_load(service_item)
if new_item:
service_item = new_item
self.add_service_item(service_item, repaint=False)
2013-12-26 08:56:53 +00:00
def load_last_file(self):
"""
2013-03-29 12:53:07 +00:00
Load the last service item from the service manager when the service was last closed. Can be blank if there was
no service present.
"""
file_path = self.settings.value('servicemanager/last file')
if file_path:
self.load_file(file_path)
2013-01-27 07:36:04 +00:00
def context_menu(self, point):
2013-01-21 11:11:47 +00:00
"""
2018-02-18 16:48:49 +00:00
The Right click context menu from the Service item list
2014-01-01 09:33:07 +00:00
:param point: The location of the cursor.
2013-01-21 11:11:47 +00:00
"""
2013-01-27 07:36:04 +00:00
item = self.service_manager_list.itemAt(point)
2010-03-12 19:39:15 +00:00
if item is None:
return
2012-05-17 20:06:23 +00:00
if item.parent():
2012-05-19 09:13:32 +00:00
pos = item.parent().data(0, QtCore.Qt.UserRole)
2010-03-05 09:24:42 +00:00
else:
2012-05-19 09:13:32 +00:00
pos = item.data(0, QtCore.Qt.UserRole)
2013-01-27 07:36:04 +00:00
service_item = self.service_items[pos - 1]
2013-01-27 10:18:06 +00:00
self.edit_action.setVisible(False)
2013-04-02 19:28:56 +00:00
self.rename_action.setVisible(False)
self.create_custom_action.setVisible(False)
2013-01-27 10:18:06 +00:00
self.maintain_action.setVisible(False)
self.notes_action.setVisible(False)
self.time_action.setVisible(False)
self.auto_start_action.setVisible(False)
2013-08-31 18:17:38 +00:00
if service_item['service_item'].is_capable(ItemCapabilities.CanEdit) and service_item['service_item'].edit_id:
2013-01-27 10:18:06 +00:00
self.edit_action.setVisible(True)
2013-09-05 07:23:59 +00:00
if service_item['service_item'].is_capable(ItemCapabilities.CanEditTitle):
2013-04-02 19:28:56 +00:00
self.rename_action.setVisible(True)
2013-08-31 18:17:38 +00:00
if service_item['service_item'].is_capable(ItemCapabilities.CanMaintain):
2013-01-27 10:18:06 +00:00
self.maintain_action.setVisible(True)
2010-03-05 09:24:42 +00:00
if item.parent() is None:
2013-01-27 10:18:06 +00:00
self.notes_action.setVisible(True)
2013-08-31 18:17:38 +00:00
if service_item['service_item'].is_capable(ItemCapabilities.CanLoop) and \
len(service_item['service_item'].get_frames()) > 1:
self.auto_play_slides_menu.menuAction().setVisible(True)
2013-08-31 18:17:38 +00:00
self.auto_play_slides_once.setChecked(service_item['service_item'].auto_play_slides_once)
self.auto_play_slides_loop.setChecked(service_item['service_item'].auto_play_slides_loop)
self.timed_slide_interval.setChecked(service_item['service_item'].timed_slide_interval > 0)
if service_item['service_item'].timed_slide_interval > 0:
2016-05-20 16:22:06 +00:00
delay_suffix = ' {text} s'.format(text=str(service_item['service_item'].timed_slide_interval))
2012-11-09 18:44:25 +00:00
else:
2013-08-31 18:17:38 +00:00
delay_suffix = ' ...'
2013-02-01 21:34:23 +00:00
self.timed_slide_interval.setText(
translate('OpenLP.ServiceManager', '&Delay between slides') + delay_suffix)
2012-12-25 20:22:41 +00:00
# TODO for future: make group explains itself more visually
2012-11-09 18:44:25 +00:00
else:
self.auto_play_slides_menu.menuAction().setVisible(False)
if service_item['service_item'].is_capable(ItemCapabilities.HasVariableStartTime) and \
not service_item['service_item'].is_capable(ItemCapabilities.IsOptical):
2013-01-27 10:18:06 +00:00
self.time_action.setVisible(True)
2013-08-31 18:17:38 +00:00
if service_item['service_item'].is_capable(ItemCapabilities.CanAutoStartForLive):
2013-01-27 10:18:06 +00:00
self.auto_start_action.setVisible(True)
2013-08-31 18:17:38 +00:00
if service_item['service_item'].will_auto_start:
2013-01-27 10:18:06 +00:00
self.auto_start_action.setText(translate('OpenLP.ServiceManager', '&Auto Start - active'))
2018-05-06 20:11:06 +00:00
self.auto_start_action.setIcon(UiIcons().active)
else:
self.auto_start_action.setIcon(UiIcons().inactive)
self.auto_start_action.setText(translate('OpenLP.ServiceManager', '&Auto Start - inactive'))
2013-08-31 18:17:38 +00:00
if service_item['service_item'].is_text():
2019-08-04 13:13:33 +00:00
for plugin in State().list_plugins():
2013-08-31 18:17:38 +00:00
if plugin.name == 'custom' and plugin.status == PluginStatus.Active:
self.create_custom_action.setVisible(True)
break
2013-01-27 07:36:04 +00:00
self.theme_menu.menuAction().setVisible(False)
2011-05-27 06:29:14 +00:00
# Set up the theme menu.
2013-08-31 18:17:38 +00:00
if service_item['service_item'].is_text() and self.renderer.theme_level == ThemeLevel.Song:
2013-01-27 07:36:04 +00:00
self.theme_menu.menuAction().setVisible(True)
2011-05-27 06:29:14 +00:00
# The service item does not have a theme, check the "Default".
2013-08-31 18:17:38 +00:00
if service_item['service_item'].theme is None:
2013-01-27 10:18:06 +00:00
theme_action = self.theme_menu.defaultAction()
2011-05-26 08:15:56 +00:00
else:
2015-11-07 00:49:40 +00:00
theme_action = self.theme_menu.findChild(QtWidgets.QAction, service_item['service_item'].theme)
2013-01-27 10:18:06 +00:00
if theme_action is not None:
theme_action.setChecked(True)
2015-11-07 00:49:40 +00:00
self.menu.exec(self.service_manager_list.mapToGlobal(point))
def on_service_item_note_form(self):
"""
Allow the service note to be edited
"""
2013-01-27 07:36:04 +00:00
item = self.find_service_item()[0]
2013-08-31 18:17:38 +00:00
self.service_note_form.text_edit.setPlainText(self.service_items[item]['service_item'].notes)
2015-11-07 00:49:40 +00:00
if self.service_note_form.exec():
2013-08-31 18:17:38 +00:00
self.service_items[item]['service_item'].notes = self.service_note_form.text_edit.toPlainText()
2013-01-27 20:36:18 +00:00
self.repaint_service_list(item, -1)
2013-01-27 07:36:04 +00:00
self.set_modified()
2010-03-03 17:48:37 +00:00
def on_start_time_form(self):
"""
Opens a dialog to type in service item notes.
"""
2013-01-27 07:36:04 +00:00
item = self.find_service_item()[0]
2013-03-06 22:23:01 +00:00
self.start_time_form.item = self.service_items[item]
2015-11-07 00:49:40 +00:00
if self.start_time_form.exec():
2013-01-27 20:36:18 +00:00
self.repaint_service_list(item, -1)
def toggle_auto_play_slides_once(self):
2012-11-09 18:44:25 +00:00
"""
2013-03-29 12:53:07 +00:00
Toggle Auto play slide once. Inverts auto play once option for the item
2012-11-09 18:44:25 +00:00
"""
2013-01-27 07:36:04 +00:00
item = self.find_service_item()[0]
2013-08-31 18:17:38 +00:00
service_item = self.service_items[item]['service_item']
2012-11-14 11:47:15 +00:00
service_item.auto_play_slides_once = not service_item.auto_play_slides_once
if service_item.auto_play_slides_once:
service_item.auto_play_slides_loop = False
2013-01-27 07:36:04 +00:00
self.auto_play_slides_loop.setChecked(False)
2012-11-14 11:47:15 +00:00
if service_item.auto_play_slides_once and service_item.timed_slide_interval == 0:
2020-06-06 16:05:36 +00:00
service_item.timed_slide_interval = self.settings.value('core/loop delay')
2013-01-27 07:36:04 +00:00
self.set_modified()
2012-11-09 18:44:25 +00:00
def toggle_auto_play_slides_loop(self):
2012-11-09 18:44:25 +00:00
"""
Toggle Auto play slide loop.
"""
2013-01-27 07:36:04 +00:00
item = self.find_service_item()[0]
2013-08-31 18:17:38 +00:00
service_item = self.service_items[item]['service_item']
2012-11-14 11:47:15 +00:00
service_item.auto_play_slides_loop = not service_item.auto_play_slides_loop
if service_item.auto_play_slides_loop:
service_item.auto_play_slides_once = False
2013-01-27 07:36:04 +00:00
self.auto_play_slides_once.setChecked(False)
2012-11-14 11:47:15 +00:00
if service_item.auto_play_slides_loop and service_item.timed_slide_interval == 0:
2020-06-06 16:05:36 +00:00
service_item.timed_slide_interval = self.settings.value('core/loop delay')
2013-01-27 07:36:04 +00:00
self.set_modified()
2012-11-09 18:44:25 +00:00
def on_timed_slide_interval(self):
2012-11-09 18:44:25 +00:00
"""
2012-12-29 22:52:19 +00:00
Shows input dialog for enter interval in seconds for delay
2012-11-09 18:44:25 +00:00
"""
2013-01-27 07:36:04 +00:00
item = self.find_service_item()[0]
2013-08-31 18:17:38 +00:00
service_item = self.service_items[item]['service_item']
2012-11-14 11:47:15 +00:00
if service_item.timed_slide_interval == 0:
2020-06-06 16:05:36 +00:00
timed_slide_interval = self.settings.value('core/loop delay')
2012-11-09 18:44:25 +00:00
else:
2012-11-14 11:47:15 +00:00
timed_slide_interval = service_item.timed_slide_interval
2015-11-07 00:49:40 +00:00
timed_slide_interval, ok = QtWidgets.QInputDialog.getInt(self, translate('OpenLP.ServiceManager',
2013-12-24 21:24:52 +00:00
'Input delay'),
translate('OpenLP.ServiceManager',
'Delay between slides in seconds.'),
2013-12-24 15:55:01 +00:00
timed_slide_interval, 0, 180, 1)
2012-11-09 18:44:25 +00:00
if ok:
2012-11-14 11:47:15 +00:00
service_item.timed_slide_interval = timed_slide_interval
2013-01-21 11:11:47 +00:00
if service_item.timed_slide_interval != 0 and not service_item.auto_play_slides_loop \
and not service_item.auto_play_slides_once:
2012-11-14 11:47:15 +00:00
service_item.auto_play_slides_loop = True
elif service_item.timed_slide_interval == 0:
service_item.auto_play_slides_loop = False
service_item.auto_play_slides_once = False
2013-01-27 07:36:04 +00:00
self.set_modified()
2012-11-09 18:44:25 +00:00
def on_auto_start(self):
"""
Toggles to Auto Start Setting.
"""
2013-01-27 07:36:04 +00:00
item = self.find_service_item()[0]
2013-08-31 18:17:38 +00:00
self.service_items[item]['service_item'].will_auto_start = \
not self.service_items[item]['service_item'].will_auto_start
def on_service_item_edit_form(self):
2012-08-24 20:06:56 +00:00
"""
2013-03-29 12:53:07 +00:00
Opens a dialog to edit the service item and update the service display if changes are saved.
2012-08-24 20:06:56 +00:00
"""
2013-01-27 07:36:04 +00:00
item = self.find_service_item()[0]
2013-08-31 18:17:38 +00:00
self.service_item_edit_form.set_service_item(self.service_items[item]['service_item'])
2015-11-07 00:49:40 +00:00
if self.service_item_edit_form.exec():
2013-03-06 22:23:01 +00:00
self.add_service_item(self.service_item_edit_form.get_service_item(),
replace=item, expand=self.service_items[item]['expanded'])
2013-02-03 09:07:31 +00:00
def preview_live(self, unique_identifier, row):
"""
2013-03-29 12:53:07 +00:00
Called by the SlideController to request a preview item be made live and allows the next preview to be updated
if relevant.
2013-02-03 09:07:31 +00:00
2014-01-01 09:33:07 +00:00
:param unique_identifier: Reference to the service_item
:param row: individual row number
"""
2013-01-27 07:36:04 +00:00
for sitem in self.service_items:
2013-08-31 18:17:38 +00:00
if sitem['service_item'].unique_identifier == unique_identifier:
item = self.service_manager_list.topLevelItem(sitem['order'] - 1)
2013-01-27 07:36:04 +00:00
self.service_manager_list.setCurrentItem(item)
self.make_live(int(row))
return
2013-01-27 07:36:04 +00:00
def next_item(self):
"""
Called by the SlideController to select the next service item.
"""
2014-01-09 19:52:20 +00:00
if not self.service_manager_list.selectedItems():
2010-02-04 19:25:32 +00:00
return
2014-01-09 19:52:20 +00:00
selected = self.service_manager_list.selectedItems()[0]
2013-12-26 08:56:53 +00:00
look_for = 0
2015-11-07 00:49:40 +00:00
service_iterator = QtWidgets.QTreeWidgetItemIterator(self.service_manager_list)
2013-12-26 08:56:53 +00:00
while service_iterator.value():
if look_for == 1 and service_iterator.value().parent() is None:
self.service_manager_list.setCurrentItem(service_iterator.value())
2013-01-27 07:36:04 +00:00
self.make_live()
return
2013-12-26 08:56:53 +00:00
if service_iterator.value() == selected:
look_for = 1
service_iterator += 1
2013-02-03 09:07:31 +00:00
def previous_item(self, last_slide=False):
2010-04-26 21:29:40 +00:00
"""
Called by the SlideController to select the previous service item.
2013-02-03 09:07:31 +00:00
2014-01-01 09:33:07 +00:00
:param last_slide: Is this the last slide in the service_item.
2010-04-26 21:29:40 +00:00
"""
2014-01-09 19:52:20 +00:00
if not self.service_manager_list.selectedItems():
2010-04-26 21:29:40 +00:00
return
2014-01-09 19:52:20 +00:00
selected = self.service_manager_list.selectedItems()[0]
2013-12-26 08:56:53 +00:00
prev_item = None
prev_item_last_slide = None
2015-11-07 00:49:40 +00:00
service_iterator = QtWidgets.QTreeWidgetItemIterator(self.service_manager_list)
2013-12-26 08:56:53 +00:00
while service_iterator.value():
# Found the selected/current service item
2013-12-26 08:56:53 +00:00
if service_iterator.value() == selected:
if last_slide and prev_item_last_slide:
# Go to the last slide of the previous service item
2013-12-26 08:56:53 +00:00
pos = prev_item.data(0, QtCore.Qt.UserRole)
2013-08-31 18:17:38 +00:00
check_expanded = self.service_items[pos - 1]['expanded']
2013-12-26 08:56:53 +00:00
self.service_manager_list.setCurrentItem(prev_item_last_slide)
if not check_expanded:
2013-12-26 08:56:53 +00:00
self.service_manager_list.collapseItem(prev_item)
2013-01-27 07:36:04 +00:00
self.make_live()
2013-12-26 08:56:53 +00:00
self.service_manager_list.setCurrentItem(prev_item)
elif prev_item:
2019-05-23 20:30:46 +00:00
# Go to the first slide of the previous service item
2013-12-26 08:56:53 +00:00
self.service_manager_list.setCurrentItem(prev_item)
2013-01-27 07:36:04 +00:00
self.make_live()
2010-04-26 21:29:40 +00:00
return
# Found the previous service item root
2013-12-26 08:56:53 +00:00
if service_iterator.value().parent() is None:
prev_item = service_iterator.value()
# Found the last slide of the previous item
2013-12-26 08:56:53 +00:00
if service_iterator.value().parent() is prev_item:
prev_item_last_slide = service_iterator.value()
# Go to next item in the tree
2013-12-26 08:56:53 +00:00
service_iterator += 1
2010-04-26 21:29:40 +00:00
def on_set_item(self, message):
"""
2013-03-20 20:17:00 +00:00
Called by a signal to select a specific item and make it live usually from remote.
2014-01-01 09:33:07 +00:00
:param message: The data passed in from a remove message
"""
2014-06-01 17:12:00 +00:00
self.log_debug(message)
2013-01-27 07:36:04 +00:00
self.set_item(int(message))
2010-05-04 20:01:45 +00:00
2013-01-27 07:36:04 +00:00
def set_item(self, index):
"""
Makes a specific item in the service live.
2014-01-01 09:33:07 +00:00
:param index: The index of the service item list to be actioned.
"""
2014-06-01 17:12:00 +00:00
if 0 <= index < self.service_manager_list.topLevelItemCount():
2013-01-27 07:36:04 +00:00
item = self.service_manager_list.topLevelItem(index)
self.service_manager_list.setCurrentItem(item)
self.make_live()
2020-04-26 21:37:35 +00:00
def set_item_by_uuid(self, unique_identifier):
"""
Makes a specific item in the service live. Called directly by the API layer
:param unique_identifier: Unique Identifier for the item.
"""
for sitem in self.service_items:
if sitem['service_item'].unique_identifier == unique_identifier:
item = self.service_manager_list.topLevelItem(sitem['order'] - 1)
self.service_manager_list.setCurrentItem(item)
self.make_live()
2020-04-26 21:37:35 +00:00
return
2013-01-27 07:36:04 +00:00
def on_move_selection_up(self):
"""
2013-03-29 12:53:07 +00:00
Moves the cursor selection up the window. Called by the up arrow.
"""
2013-01-27 07:36:04 +00:00
item = self.service_manager_list.currentItem()
item_before = self.service_manager_list.itemAbove(item)
2013-12-26 08:56:53 +00:00
if item_before is None:
return
2013-12-26 08:56:53 +00:00
self.service_manager_list.setCurrentItem(item_before)
2013-01-27 07:36:04 +00:00
def on_move_selection_down(self):
"""
2013-03-29 12:53:07 +00:00
Moves the cursor selection down the window. Called by the down arrow.
"""
2013-01-27 07:36:04 +00:00
item = self.service_manager_list.currentItem()
2013-12-26 08:56:53 +00:00
item_after = self.service_manager_list.itemBelow(item)
if item_after is None:
return
2013-12-26 08:56:53 +00:00
self.service_manager_list.setCurrentItem(item_after)
def on_expand_selection(self):
"""
Expands cursor selection on the window. Called by the right arrow
"""
item = self.service_manager_list.currentItem()
2017-06-15 18:05:38 +00:00
# Since we only have 2 levels we find them by checking for children
if item.childCount():
if not self.service_manager_list.isExpanded(self.service_manager_list.currentIndex()):
self.service_manager_list.expandItem(item)
self.service_manager.expanded(item)
# If not expanded, Expand it
self.service_manager_list.setCurrentItem(self.service_manager_list.itemBelow(item))
# Then move selection down to child whether it needed to be expanded or not
def on_collapse_selection(self):
"""
Collapses cursor selection on the window Called by the left arrow
"""
item = self.service_manager_list.currentItem()
2017-06-15 18:05:38 +00:00
# Since we only have 2 levels we find them by checking for children
if item.childCount():
if self.service_manager_list.isExpanded(self.service_manager_list.currentIndex()):
self.service_manager_list.collapseItem(item)
self.service_manager.collapsed(item)
else: # If selection is lower level
self.service_manager_list.collapseItem(item.parent())
self.service_manager.collapsed(item.parent())
self.service_manager_list.setCurrentItem(item.parent())
def on_collapse_all(self):
"""
Collapse all the service items.
"""
2013-01-27 07:36:04 +00:00
for item in self.service_items:
2013-08-31 18:17:38 +00:00
item['expanded'] = False
2013-01-27 07:36:04 +00:00
self.service_manager_list.collapseAll()
def collapsed(self, item):
"""
2013-03-29 12:53:07 +00:00
Record if an item is collapsed. Used when repainting the list to get the correct state.
2014-01-01 09:33:07 +00:00
:param item: The service item to be checked
"""
2012-05-19 09:13:32 +00:00
pos = item.data(0, QtCore.Qt.UserRole)
# Only set root items as collapsed, and since we only have 2 levels we find them by checking for children
if item.childCount():
self.service_items[pos - 1]['expanded'] = False
def on_expand_all(self):
"""
Collapse all the service items.
"""
2013-01-27 07:36:04 +00:00
for item in self.service_items:
2013-08-31 18:17:38 +00:00
item['expanded'] = True
2013-01-27 07:36:04 +00:00
self.service_manager_list.expandAll()
def expanded(self, item):
"""
2013-03-29 12:53:07 +00:00
Record if an item is collapsed. Used when repainting the list to get the correct state.
2014-01-01 09:33:07 +00:00
:param item: The service item to be checked
"""
2012-05-19 09:13:32 +00:00
pos = item.data(0, QtCore.Qt.UserRole)
# Only set root items as expanded, and since we only have 2 levels we find them by checking for children
if item.childCount():
self.service_items[pos - 1]['expanded'] = True
def on_service_top(self):
"""
Move the current ServiceItem to the top of the list.
"""
2013-01-27 07:36:04 +00:00
item, child = self.find_service_item()
if item < len(self.service_items) and item != -1:
temp = self.service_items[item]
self.service_items.remove(self.service_items[item])
self.service_items.insert(0, temp)
2013-01-27 20:36:18 +00:00
self.repaint_service_list(0, child)
2013-01-27 07:36:04 +00:00
self.set_modified()
def on_service_up(self):
"""
Move the current ServiceItem one position up in the list.
"""
2013-01-27 07:36:04 +00:00
item, child = self.find_service_item()
if item > 0:
2013-01-27 07:36:04 +00:00
temp = self.service_items[item]
self.service_items.remove(self.service_items[item])
self.service_items.insert(item - 1, temp)
2013-01-27 20:36:18 +00:00
self.repaint_service_list(item - 1, child)
2013-01-27 07:36:04 +00:00
self.set_modified()
def on_service_down(self):
"""
Move the current ServiceItem one position down in the list.
"""
2013-01-27 07:36:04 +00:00
item, child = self.find_service_item()
if item < len(self.service_items) and item != -1:
temp = self.service_items[item]
self.service_items.remove(self.service_items[item])
self.service_items.insert(item + 1, temp)
2013-01-27 20:36:18 +00:00
self.repaint_service_list(item + 1, child)
2013-01-27 07:36:04 +00:00
self.set_modified()
def on_service_end(self):
"""
Move the current ServiceItem to the bottom of the list.
"""
2013-01-27 07:36:04 +00:00
item, child = self.find_service_item()
if item < len(self.service_items) and item != -1:
temp = self.service_items[item]
self.service_items.remove(self.service_items[item])
self.service_items.insert(len(self.service_items), temp)
2013-01-27 20:36:18 +00:00
self.repaint_service_list(len(self.service_items) - 1, child)
2013-01-27 07:36:04 +00:00
self.set_modified()
def on_delete_from_service(self):
"""
Remove the current ServiceItem from the list.
"""
2013-01-27 07:36:04 +00:00
item = self.find_service_item()[0]
if item != -1:
2013-01-27 07:36:04 +00:00
self.service_items.remove(self.service_items[item])
2013-01-27 20:36:18 +00:00
self.repaint_service_list(item - 1, -1)
2013-01-27 07:36:04 +00:00
self.set_modified()
2013-03-06 22:23:01 +00:00
def repaint_service_list(self, service_item, service_item_child):
"""
2013-03-29 12:53:07 +00:00
Clear the existing service list and prepaint all the items. This is used when moving items as the move takes
place in a supporting list, and when regenerating all the items due to theme changes.
2014-01-01 09:33:07 +00:00
:param service_item: The item which changed. (int)
:param service_item_child: The child of the ``service_item``, which will be selected. (int)
"""
# Correct order of items in array
count = 1
2012-12-08 16:20:52 +00:00
self.service_has_all_original_files = True
2013-01-27 07:36:04 +00:00
for item in self.service_items:
2013-08-31 18:17:38 +00:00
item['order'] = count
count += 1
if not item['service_item'].has_original_file_path:
2012-12-08 16:20:52 +00:00
self.service_has_all_original_files = False
# Repaint the screen
2013-01-27 07:36:04 +00:00
self.service_manager_list.clear()
2013-03-11 19:11:46 +00:00
self.service_manager_list.clearSelection()
for item_index, item in enumerate(self.service_items):
2013-12-26 08:56:53 +00:00
service_item_from_item = item['service_item']
2015-11-07 00:49:40 +00:00
tree_widget_item = QtWidgets.QTreeWidgetItem(self.service_manager_list)
2013-12-26 08:56:53 +00:00
if service_item_from_item.is_valid:
2019-08-30 11:28:10 +00:00
icon = service_item_from_item.icon.pixmap(80, 80).toImage()
icon = icon.scaled(80, 80, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
2013-12-26 08:56:53 +00:00
if service_item_from_item.notes:
2018-04-21 05:47:20 +00:00
overlay = UiIcons().notes.pixmap(40, 40).toImage()
overlay = overlay.scaled(40, 40, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
2010-05-04 20:01:45 +00:00
painter = QtGui.QPainter(icon)
painter.drawImage(0, 0, overlay)
painter.end()
2013-12-26 08:56:53 +00:00
tree_widget_item.setIcon(0, build_icon(icon))
elif service_item_from_item.temporary_edit:
2018-04-08 19:54:28 +00:00
overlay = QtGui.QImage(UiIcons().upload)
2012-12-10 18:04:58 +00:00
overlay = overlay.scaled(40, 40, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
2011-12-10 08:45:17 +00:00
painter = QtGui.QPainter(icon)
painter.drawImage(40, 0, overlay)
painter.end()
2013-12-26 08:56:53 +00:00
tree_widget_item.setIcon(0, build_icon(icon))
2010-05-04 20:01:45 +00:00
else:
2018-04-21 05:47:20 +00:00
tree_widget_item.setIcon(0, service_item_from_item.icon)
2010-03-06 08:00:36 +00:00
else:
2018-04-08 19:54:28 +00:00
tree_widget_item.setIcon(0, UiIcons().delete)
2013-12-26 08:56:53 +00:00
tree_widget_item.setText(0, service_item_from_item.get_display_title())
tips = []
2013-12-26 08:56:53 +00:00
if service_item_from_item.temporary_edit:
2016-05-20 16:22:06 +00:00
text1 = translate('OpenLP.ServiceManager', 'Edit')
text2 = translate('OpenLP.ServiceManager', 'Service copy only')
tips.append('<strong>{text1}:</strong> <em>{text2}</em>'.format(text1=text1, text2=text2))
2013-12-26 08:56:53 +00:00
if service_item_from_item.theme and service_item_from_item.theme != -1:
2016-05-20 16:22:06 +00:00
text = translate('OpenLP.ServiceManager', 'Slide theme')
tips.append('<strong>{text1}:</strong> <em>{text2}</em>'.format(text1=text,
text2=service_item_from_item.theme))
2013-12-26 08:56:53 +00:00
if service_item_from_item.notes:
2016-05-20 16:22:06 +00:00
text1 = translate('OpenLP.ServiceManager', 'Notes')
text2 = html.escape(service_item_from_item.notes)
tips.append('<strong>{text1}: </strong> {text2}'.format(text1=text1, text2=text2))
2013-08-31 18:17:38 +00:00
if item['service_item'].is_capable(ItemCapabilities.HasVariableStartTime):
tips.append(item['service_item'].get_media_time())
2018-02-18 16:48:49 +00:00
if item['service_item'].is_capable(ItemCapabilities.HasMetaData):
2018-02-20 22:03:32 +00:00
for meta in item['service_item'].metadata:
tips.append(meta)
2013-12-26 08:56:53 +00:00
tree_widget_item.setToolTip(0, '<br>'.join(tips))
tree_widget_item.setData(0, QtCore.Qt.UserRole, item['order'])
tree_widget_item.setSelected(item['selected'])
2014-07-11 21:45:01 +00:00
# Add the children to their parent tree_widget_item.
2019-08-30 11:28:10 +00:00
for slide_index, slide in enumerate(service_item_from_item.get_frames()):
2015-11-07 00:49:40 +00:00
child = QtWidgets.QTreeWidgetItem(tree_widget_item)
2013-10-28 02:33:28 +00:00
# prefer to use a display_title
2019-08-30 11:28:10 +00:00
if service_item_from_item.is_capable(ItemCapabilities.HasDisplayTitle) or \
service_item_from_item.service_item_type is not ServiceItemType.Text:
text = slide['title'].replace('\n', ' ')
2019-08-30 11:28:10 +00:00
else:
text = service_item_from_item.get_rendered_frame(slide_index, clean=True)
2011-01-22 07:31:32 +00:00
child.setText(0, text[:40])
child.setData(0, QtCore.Qt.UserRole, slide_index)
if service_item == item_index:
if item['expanded'] and service_item_child == slide_index:
2013-01-27 07:36:04 +00:00
self.service_manager_list.setCurrentItem(child)
2013-03-06 22:23:01 +00:00
elif service_item_child == -1:
2013-12-26 08:56:53 +00:00
self.service_manager_list.setCurrentItem(tree_widget_item)
tree_widget_item.setExpanded(item['expanded'])
2013-02-01 20:40:23 +00:00
def clean_up(self):
2009-09-19 21:45:50 +00:00
"""
2013-12-26 08:56:53 +00:00
Empties the service_path of temporary files on system exit.
2009-09-19 21:45:50 +00:00
"""
2020-05-07 05:18:37 +00:00
for file_name in os.listdir(self.service_path):
file_path = Path(self.service_path, file_name)
if os.path.isdir(file_path):
shutil.rmtree(file_path, True)
else:
delete_file(file_path)
def on_theme_combo_box_selected(self, current_index):
"""
Set the theme for the current service.
2014-01-01 09:33:07 +00:00
:param current_index: The combo box index for the selected item
"""
2022-01-16 13:15:09 +00:00
new_service_theme = self.theme_combo_box.currentText()
self.settings.setValue('servicemanager/service theme', new_service_theme)
Registry().execute('theme_change_service')
2009-06-26 16:39:16 +00:00
2022-01-16 13:15:09 +00:00
def on_theme_level_changed(self):
"""
2013-03-29 12:53:07 +00:00
The theme may have changed in the settings dialog so make sure the theme combo box is in the correct state.
"""
visible = self.settings.value('themes/theme level') != ThemeLevel.Global
2016-05-17 21:28:27 +00:00
self.toolbar.actions['theme_combo_box'].setVisible(visible)
self.toolbar.actions['theme_label'].setVisible(visible)
self.regenerate_service_items()
2022-01-16 13:15:09 +00:00
def on_service_theme_change(self):
2020-04-01 18:26:57 +00:00
"""
2022-01-16 13:15:09 +00:00
Set the theme for the current service from the settings
2020-04-01 18:26:57 +00:00
"""
self.service_theme = self.settings.value('servicemanager/service theme')
2020-04-01 18:26:57 +00:00
find_and_set_in_combo_box(self.theme_combo_box, self.service_theme)
2022-01-16 13:15:09 +00:00
Registry().execute('theme_change_service')
def regenerate_changed_service_items(self):
"""
Regenerate the changed service items, marking the service as unsaved.
"""
self.regenerate_service_items(changed=True)
2020-04-01 18:26:57 +00:00
2013-12-26 08:56:53 +00:00
def regenerate_service_items(self, changed=False):
"""
2013-03-29 12:53:07 +00:00
Rebuild the service list as things have changed and a repaint is the easiest way to do this.
2014-01-01 09:33:07 +00:00
:param changed: True if the list has changed for new / removed items. False for a theme change.
"""
2013-02-03 19:23:12 +00:00
self.application.set_busy_cursor()
2022-01-16 13:15:09 +00:00
was_modified = self.is_modified()
2010-08-26 05:01:29 +00:00
# force reset of renderer as theme data has changed
2012-12-08 16:20:52 +00:00
self.service_has_all_original_files = True
2013-01-27 07:36:04 +00:00
if self.service_items:
for item in self.service_items:
2013-08-31 18:17:38 +00:00
item['selected'] = False
2015-11-07 00:49:40 +00:00
service_iterator = QtWidgets.QTreeWidgetItemIterator(self.service_manager_list)
2013-12-26 08:56:53 +00:00
selected_item = None
while service_iterator.value():
if service_iterator.value().isSelected():
selected_item = service_iterator.value()
service_iterator += 1
if selected_item is not None:
if selected_item.parent() is None:
pos = selected_item.data(0, QtCore.Qt.UserRole)
2011-06-04 18:08:48 +00:00
else:
2013-12-26 08:56:53 +00:00
pos = selected_item.parent().data(0, QtCore.Qt.UserRole)
2013-08-31 18:17:38 +00:00
self.service_items[pos - 1]['selected'] = True
2013-12-26 08:56:53 +00:00
temp_service_items = self.service_items
2013-01-27 07:36:04 +00:00
self.service_manager_list.clear()
self.service_items = []
2013-12-26 08:56:53 +00:00
self.is_new = True
for item in temp_service_items:
2013-08-31 18:17:38 +00:00
self.add_service_item(item['service_item'], False, expand=item['expanded'], repaint=False,
2013-12-26 08:56:53 +00:00
selected=item['selected'])
2013-03-29 12:53:07 +00:00
# Set to False as items may have changed rendering does not impact the saved song so True may also be valid
2022-01-16 13:15:09 +00:00
self.set_modified(changed or was_modified)
2011-06-04 18:08:48 +00:00
# Repaint it once only at the end
2013-01-27 20:36:18 +00:00
self.repaint_service_list(-1, -1)
2013-02-03 19:23:12 +00:00
self.application.set_normal_cursor()
def replace_service_item(self, new_item):
"""
2013-03-29 12:53:07 +00:00
Using the service item passed replace the one with the same edit id if found.
2014-01-01 09:33:07 +00:00
:param new_item: a new service item to up date an existing one.
"""
2013-01-27 07:36:04 +00:00
for item_count, item in enumerate(self.service_items):
if item['service_item'].edit_id == new_item.edit_id and item['service_item'].name == new_item.name:
new_item.merge(item['service_item'])
item['service_item'] = new_item
2013-01-27 20:36:18 +00:00
self.repaint_service_list(item_count + 1, 0)
self.live_controller.replace_service_manager_item(new_item)
2013-01-27 07:36:04 +00:00
self.set_modified()
def add_service_item(self, item, rebuild=False, expand=None, replace=-1, repaint=True, selected=False,
position=-1):
"""
Add a Service item to the list
2014-01-01 09:33:07 +00:00
:param item: Service Item to be added
:param rebuild: Do we need to rebuild the live display (Default False)
:param expand: Override the default expand settings. (Tristate)
:param replace: The service item to be replaced
2014-01-01 09:33:07 +00:00
:param repaint: Do we need to repaint the service item list (Default True)
:param selected: Has the item been selected (Default False)
:param position: The position where the item is dropped (Default -1)
"""
2010-11-19 18:05:49 +00:00
# if not passed set to config value
if expand is None:
expand = self.settings.value('advanced/expand service item')
item.from_service = True
if position != -1:
self.drop_position = position
if replace > -1:
item.merge(self.service_items[replace]['service_item'])
self.service_items[replace]['service_item'] = item
self.repaint_service_list(replace, -1)
2013-06-16 07:54:16 +00:00
self.live_controller.replace_service_manager_item(item)
2009-10-29 09:18:26 +00:00
else:
2019-08-30 11:28:10 +00:00
item.render_text_items()
2010-09-30 05:12:06 +00:00
# nothing selected for dnd
if self.drop_position == -1:
if isinstance(item, list):
2014-01-01 09:33:07 +00:00
for ind_item in item:
self.service_items.append({'service_item': ind_item,
2013-12-26 08:56:53 +00:00
'order': len(self.service_items) + 1,
'expanded': expand, 'selected': selected})
else:
2013-08-31 18:17:38 +00:00
self.service_items.append({'service_item': item,
2013-12-26 08:56:53 +00:00
'order': len(self.service_items) + 1,
'expanded': expand, 'selected': selected})
2011-04-29 08:45:36 +00:00
if repaint:
2013-01-27 20:36:18 +00:00
self.repaint_service_list(len(self.service_items) - 1, -1)
2009-10-29 09:18:26 +00:00
else:
2013-01-27 07:36:04 +00:00
self.service_items.insert(self.drop_position,
{'service_item': item, 'order': self.drop_position,
'expanded': expand, 'selected': selected})
2013-01-27 20:36:18 +00:00
self.repaint_service_list(self.drop_position, -1)
2010-09-30 05:12:06 +00:00
# if rebuilding list make sure live is fixed.
if rebuild:
2013-06-16 07:54:16 +00:00
self.live_controller.replace_service_manager_item(item)
self.drop_position = -1
2013-01-27 07:36:04 +00:00
self.set_modified()
def make_preview(self):
"""
Send the current item to the Preview slide controller
"""
2013-02-03 19:23:12 +00:00
self.application.set_busy_cursor()
2013-01-27 07:36:04 +00:00
item, child = self.find_service_item()
2013-08-31 18:17:38 +00:00
if self.service_items[item]['service_item'].is_valid:
self.preview_controller.add_service_manager_item(self.service_items[item]['service_item'], child)
2010-05-06 16:49:12 +00:00
else:
2012-12-10 18:04:58 +00:00
critical_error_message_box(translate('OpenLP.ServiceManager', 'Missing Display Handler'),
2013-12-24 21:24:52 +00:00
translate('OpenLP.ServiceManager',
'Your item cannot be displayed as there is no handler to display it'))
2013-02-03 19:23:12 +00:00
self.application.set_normal_cursor()
2013-01-27 20:36:18 +00:00
def get_service_item(self):
2010-03-16 20:22:28 +00:00
"""
Send the current item to the Preview slide controller
"""
2013-01-27 07:36:04 +00:00
item = self.find_service_item()[0]
2010-03-16 20:22:28 +00:00
if item == -1:
return False
else:
2013-08-31 18:17:38 +00:00
return self.service_items[item]['service_item']
def on_double_click_live(self):
2011-03-25 06:13:42 +00:00
"""
2013-03-29 12:53:07 +00:00
Send the current item to the Live slide controller but triggered by a tablewidget click event.
2011-03-25 06:13:42 +00:00
"""
self.list_double_clicked = True
2013-01-27 07:36:04 +00:00
self.make_live()
2011-03-25 06:13:42 +00:00
def on_single_click_preview(self):
"""
2016-01-19 07:02:47 +00:00
If single click previewing is enabled, and triggered by a tablewidget click event,
start a timeout to verify a double-click hasn't triggered.
"""
if self.settings.value('advanced/single click service preview'):
if not self.list_double_clicked:
# If a double click has not registered start a timer, otherwise wait for the existing timer to finish.
2016-01-19 07:02:47 +00:00
QtCore.QTimer.singleShot(QtWidgets.QApplication.instance().doubleClickInterval(),
self.on_single_click_preview_timeout)
def on_single_click_preview_timeout(self):
"""
2016-01-19 07:02:47 +00:00
If a single click ok, but double click not triggered, send the current item to the Preview slide controller.
"""
if self.list_double_clicked:
# If a double click has registered, clear it.
self.list_double_clicked = False
else:
# Otherwise preview the item.
self.make_preview()
2013-01-27 07:36:04 +00:00
def make_live(self, row=-1):
"""
Send the current item to the Live slide controller
2014-01-01 09:33:07 +00:00
:param row: Row number to be displayed if from preview. -1 is passed if the value is not set
"""
2013-01-27 07:36:04 +00:00
item, child = self.find_service_item()
# No items in service
if item == -1:
return
if row != -1:
child = row
2013-02-03 19:23:12 +00:00
self.application.set_busy_cursor()
2013-08-31 18:17:38 +00:00
if self.service_items[item]['service_item'].is_valid:
self.live_controller.add_service_manager_item(self.service_items[item]['service_item'], child)
2020-06-06 16:05:36 +00:00
if self.settings.value('core/auto preview'):
2010-05-04 20:01:45 +00:00
item += 1
2013-01-27 07:36:04 +00:00
if self.service_items and item < len(self.service_items) and \
2013-08-31 18:17:38 +00:00
self.service_items[item]['service_item'].is_capable(ItemCapabilities.CanPreview):
self.preview_controller.add_service_manager_item(self.service_items[item]['service_item'], 0)
self.live_controller.preview_widget.setFocus()
2010-05-04 20:01:45 +00:00
else:
2012-12-10 18:04:58 +00:00
critical_error_message_box(translate('OpenLP.ServiceManager', 'Missing Display Handler'),
2013-12-24 15:55:01 +00:00
translate('OpenLP.ServiceManager',
'Your item cannot be displayed as the plugin required to display it '
'is missing or inactive'))
2013-02-21 17:30:18 +00:00
self.application.set_normal_cursor()
def remote_edit(self):
2009-10-29 09:18:26 +00:00
"""
2013-01-27 09:57:03 +00:00
Triggers a remote edit to a plugin to allow item to be edited.
2009-10-29 09:18:26 +00:00
"""
2013-02-01 20:40:23 +00:00
item = self.find_service_item()[0]
2013-08-31 18:17:38 +00:00
if self.service_items[item]['service_item'].is_capable(ItemCapabilities.CanEdit):
new_item = Registry().get(self.service_items[item]['service_item'].name). \
on_remote_edit(self.service_items[item]['service_item'].edit_id)
2013-01-27 09:57:03 +00:00
if new_item:
self.add_service_item(new_item, replace=item)
2009-10-29 09:18:26 +00:00
def on_service_item_rename(self):
2013-04-02 10:19:33 +00:00
"""
Opens a dialog to rename the service item.
"""
item = self.find_service_item()[0]
2013-09-05 07:23:59 +00:00
if not self.service_items[item]['service_item'].is_capable(ItemCapabilities.CanEditTitle):
2013-04-02 19:28:56 +00:00
return
2013-09-05 07:23:59 +00:00
title = self.service_items[item]['service_item'].title
2015-11-07 00:49:40 +00:00
title, ok = QtWidgets.QInputDialog.getText(self, translate('OpenLP.ServiceManager', 'Rename item title'),
translate('OpenLP.ServiceManager', 'Title:'),
QtWidgets.QLineEdit.Normal, self.tr(title))
2013-04-02 10:19:33 +00:00
if ok:
2013-09-10 20:29:45 +00:00
self.service_items[item]['service_item'].title = title
2013-04-02 10:19:33 +00:00
self.repaint_service_list(item, -1)
self.set_modified()
def create_custom(self):
"""
Saves the current text item as a custom slide
"""
2013-01-27 07:36:04 +00:00
item = self.find_service_item()[0]
2013-08-31 18:17:38 +00:00
Registry().execute('custom_create_from_service', self.service_items[item]['service_item'])
2013-01-27 07:36:04 +00:00
def find_service_item(self):
"""
2014-03-20 19:10:31 +00:00
Finds the first selected ServiceItem in the list and returns the position of the service_item_from_item and its
selected child item. For example, if the third child item (in the Slidecontroller known as slide) in the
second service item is selected this will return::
2011-01-20 16:12:07 +00:00
(1, 2)
"""
2013-01-27 07:36:04 +00:00
items = self.service_manager_list.selectedItems()
2013-03-06 22:23:01 +00:00
service_item = -1
service_item_child = -1
for item in items:
2013-01-21 07:29:43 +00:00
parent_item = item.parent()
if parent_item is None:
2013-03-06 22:23:01 +00:00
service_item = item.data(0, QtCore.Qt.UserRole)
else:
2013-03-06 22:23:01 +00:00
service_item = parent_item.data(0, QtCore.Qt.UserRole)
service_item_child = item.data(0, QtCore.Qt.UserRole)
2012-05-07 08:21:21 +00:00
# Adjust for zero based arrays.
2013-03-06 22:23:01 +00:00
service_item -= 1
2012-05-07 08:21:21 +00:00
# Only process the first item on the list for this method.
break
2013-03-06 22:23:01 +00:00
return service_item, service_item_child
2013-01-27 07:36:04 +00:00
def drop_event(self, event):
"""
2013-03-29 12:53:07 +00:00
Receive drop event and trigger an internal event to get the plugins to build and push the correct service item.
The drag event payload carries the plugin name
2014-01-01 09:33:07 +00:00
:param event: Handle of the event passed
"""
link = event.mimeData()
2012-03-12 22:12:16 +00:00
if link.hasUrls():
2011-07-27 18:28:35 +00:00
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
2012-03-12 22:12:16 +00:00
for url in link.urls():
file_path = Path(url.toLocalFile())
if file_path.suffix == '.osz':
self.load_service(file_path)
elif file_path.suffix == '.oszl':
2012-09-14 16:35:07 +00:00
# todo correct
self.load_service(file_path)
2012-03-12 22:12:16 +00:00
elif link.hasText():
2012-05-17 18:57:01 +00:00
plugin = link.text()
2013-01-27 07:36:04 +00:00
item = self.service_manager_list.itemAt(event.pos())
2010-09-30 05:12:06 +00:00
# ServiceManager started the drag and drop
2013-08-31 18:17:38 +00:00
if plugin == 'ServiceManager':
2013-12-26 08:56:53 +00:00
start_pos, child = self.find_service_item()
2010-11-28 13:41:52 +00:00
# If no items selected
2013-12-26 08:56:53 +00:00
if start_pos == -1:
2010-11-28 13:41:52 +00:00
return
2010-02-27 00:11:26 +00:00
if item is None:
end_pos = len(self.service_items) - 1
2009-10-11 09:31:27 +00:00
else:
end_pos = get_parent_item_data(item) - 1
2013-12-26 08:56:53 +00:00
service_item = self.service_items[start_pos]
if start_pos != end_pos:
self.service_items.remove(service_item)
self.service_items.insert(end_pos, service_item)
self.repaint_service_list(end_pos, child)
self.set_modified()
2009-10-11 09:31:27 +00:00
else:
2010-09-30 05:12:06 +00:00
# we are not over anything so drop
2010-05-01 13:05:17 +00:00
replace = False
2010-06-09 17:09:32 +00:00
if item is None:
2013-01-27 07:36:04 +00:00
self.drop_position = len(self.service_items)
2010-03-14 07:56:39 +00:00
else:
# we are over something so lets investigate
pos = get_parent_item_data(item) - 1
2013-12-26 08:56:53 +00:00
service_item = self.service_items[pos]
if (plugin == service_item['service_item'].name and
service_item['service_item'].is_capable(ItemCapabilities.CanAppend)):
2015-11-07 00:49:40 +00:00
action = self.dnd_menu.exec(QtGui.QCursor.pos())
2010-09-30 05:12:06 +00:00
# New action required
2013-12-26 08:56:53 +00:00
if action == self.new_action:
self.drop_position = get_parent_item_data(item)
2010-09-30 05:12:06 +00:00
# Append to existing action
2013-12-26 08:56:53 +00:00
if action == self.add_to_action:
self.drop_position = get_parent_item_data(item)
2010-05-24 22:37:20 +00:00
item.setSelected(True)
replace = True
2010-05-01 13:05:17 +00:00
else:
self.drop_position = get_parent_item_data(item) - 1
2016-05-20 16:22:06 +00:00
Registry().execute('{plugin}_add_service_item'.format(plugin=plugin), replace)
else:
self.log_warning('Unrecognised item')
2013-01-27 07:36:04 +00:00
def update_theme_list(self, theme_list):
"""
Called from ThemeManager when the Themes have changed
2014-01-01 09:33:07 +00:00
:param theme_list: A list of current themes to be displayed
"""
2013-01-27 07:36:04 +00:00
self.theme_combo_box.clear()
self.theme_menu.clear()
2013-08-31 18:17:38 +00:00
self.theme_combo_box.addItem('')
2015-11-07 00:49:40 +00:00
theme_group = QtWidgets.QActionGroup(self.theme_menu)
2013-01-27 07:36:04 +00:00
theme_group.setExclusive(True)
2013-08-31 18:17:38 +00:00
theme_group.setObjectName('theme_group')
2013-03-29 12:53:07 +00:00
# Create a "Default" theme, which allows the user to reset the item's theme to the service theme or global
# theme.
2013-12-26 08:56:53 +00:00
default_theme = create_widget_action(self.theme_menu, text=UiStrings().Default, checked=False,
triggers=self.on_theme_change_action)
self.theme_menu.setDefaultAction(default_theme)
theme_group.addAction(default_theme)
2013-01-27 07:36:04 +00:00
self.theme_menu.addSeparator()
for theme in theme_list:
2013-01-27 07:36:04 +00:00
self.theme_combo_box.addItem(theme)
theme_group.addAction(create_widget_action(self.theme_menu, theme, text=theme, checked=False,
2013-12-24 15:55:01 +00:00
triggers=self.on_theme_change_action))
2013-01-27 07:36:04 +00:00
find_and_set_in_combo_box(self.theme_combo_box, self.service_theme)
def on_theme_change_action(self, field=None):
2013-01-21 17:22:10 +00:00
"""
Handles theme change events
"""
2012-05-17 18:57:01 +00:00
theme = self.sender().objectName()
2011-05-27 06:29:14 +00:00
# No object name means that the "Default" theme is supposed to be used.
if not theme:
theme = None
2013-01-27 07:36:04 +00:00
item = self.find_service_item()[0]
2013-08-31 18:17:38 +00:00
self.service_items[item]['service_item'].update_theme(theme)
2013-12-26 08:56:53 +00:00
self.regenerate_service_items(True)
def on_make_live_action_triggered(self, checked):
"""
Handle `make_live_action` when the action is triggered.
:param bool checked: Not Used.
:rtype: None
"""
self.make_live()
def get_drop_position(self):
"""
Getter for drop_position. Used in: MediaManagerItem
"""
return self.drop_position
def get_parent_item_data(item):
"""
Finds and returns the parent item for any item
:param item: The service item list item to be checked.
"""
parent_item = item.parent()
if parent_item is None:
return item.data(0, QtCore.Qt.UserRole)
else:
return parent_item.data(0, QtCore.Qt.UserRole)