Source code for openlp.plugins.presentations.lib.mediaitem
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2017 OpenLP Developers #
# --------------------------------------------------------------------------- #
# 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 #
###############################################################################
import logging
import os
from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common import Registry, Settings, UiStrings, translate
from openlp.core.lib import MediaManagerItem, ItemCapabilities, ServiceItemContext,\
build_icon, check_item_selected, create_thumb, validate_thumb
from openlp.core.lib.ui import critical_error_message_box, create_horizontal_adjusting_combo_box
from openlp.core.common.languagemanager import get_locale_key
from openlp.plugins.presentations.lib import MessageListener
from openlp.plugins.presentations.lib.pdfcontroller import PDF_CONTROLLER_FILETYPES
log = logging.getLogger(__name__)
ERROR_IMAGE = QtGui.QImage(':/general/general_delete.png')
[docs]class PresentationMediaItem(MediaManagerItem):
"""
This is the Presentation media manager item for Presentation Items. It can present files using Openoffice and
Powerpoint
"""
presentations_go_live = QtCore.pyqtSignal(list)
presentations_add_to_service = QtCore.pyqtSignal(list)
log.info('Presentations Media Item loaded')
def __init__(self, parent, plugin, controllers):
"""
Constructor. Setup defaults
"""
self.icon_path = 'presentations/presentation'
self.controllers = controllers
super(PresentationMediaItem, self).__init__(parent, plugin)
[docs] def retranslateUi(self):
"""
The name of the plugin media displayed in UI
"""
self.on_new_prompt = translate('PresentationPlugin.MediaItem', 'Select Presentation(s)')
self.automatic = translate('PresentationPlugin.MediaItem', 'Automatic')
self.display_type_label.setText(translate('PresentationPlugin.MediaItem', 'Present using:'))
[docs] def setup_item(self):
"""
Do some additional setup.
"""
self.presentations_go_live.connect(self.go_live_remote)
self.presentations_add_to_service.connect(self.add_to_service_remote)
self.message_listener = MessageListener(self)
self.has_search = True
self.single_service_item = False
Registry().register_function('mediaitem_presentation_rebuild', self.populate_display_types)
Registry().register_function('mediaitem_suffixes', self.build_file_mask_string)
# Allow DnD from the desktop
self.list_view.activateDnD()
[docs] def build_file_mask_string(self):
"""
Build the list of file extensions to be used in the Open file dialog.
"""
file_type_string = ''
for controller in self.controllers:
if self.controllers[controller].enabled():
file_types = self.controllers[controller].supports + self.controllers[controller].also_supports
for file_type in file_types:
if file_type not in file_type_string:
file_type_string += '*.{text} '.format(text=file_type)
self.service_manager.supported_suffixes(file_type)
self.on_new_file_masks = translate('PresentationPlugin.MediaItem',
'Presentations ({text})').format(text=file_type_string)
[docs] def required_icons(self):
"""
Set which icons the media manager tab should show.
"""
MediaManagerItem.required_icons(self)
self.has_file_icon = True
self.has_new_icon = False
self.has_edit_icon = False
[docs] def add_end_header_bar(self):
"""
Display custom media manager items for presentations.
"""
self.presentation_widget = QtWidgets.QWidget(self)
self.presentation_widget.setObjectName('presentation_widget')
self.display_layout = QtWidgets.QFormLayout(self.presentation_widget)
self.display_layout.setContentsMargins(self.display_layout.spacing(), self.display_layout.spacing(),
self.display_layout.spacing(), self.display_layout.spacing())
self.display_layout.setObjectName('display_layout')
self.display_type_label = QtWidgets.QLabel(self.presentation_widget)
self.display_type_label.setObjectName('display_type_label')
self.display_type_combo_box = create_horizontal_adjusting_combo_box(self.presentation_widget,
'display_type_combo_box')
self.display_type_label.setBuddy(self.display_type_combo_box)
self.display_layout.addRow(self.display_type_label, self.display_type_combo_box)
# Add the Presentation widget to the page layout.
self.page_layout.addWidget(self.presentation_widget)
[docs] def initialise(self):
"""
Populate the media manager tab
"""
self.list_view.setIconSize(QtCore.QSize(88, 50))
files = Settings().value(self.settings_section + '/presentations files')
self.load_list(files, initial_load=True)
self.populate_display_types()
[docs] def populate_display_types(self):
"""
Load the combobox with the enabled presentation controllers, allowing user to select a specific app if settings
allow.
"""
self.display_type_combo_box.clear()
for item in self.controllers:
# For PDF reload backend, since it can have changed
if self.controllers[item].name == 'Pdf':
self.controllers[item].check_available()
# load the drop down selection
if self.controllers[item].enabled():
self.display_type_combo_box.addItem(item)
if self.display_type_combo_box.count() > 1:
self.display_type_combo_box.insertItem(0, self.automatic, userData='automatic')
self.display_type_combo_box.setCurrentIndex(0)
if Settings().value(self.settings_section + '/override app') == QtCore.Qt.Checked:
self.presentation_widget.show()
else:
self.presentation_widget.hide()
[docs] def load_list(self, files, target_group=None, initial_load=False):
"""
Add presentations into the media manager. This is called both on initial load of the plugin to populate with
existing files, and when the user adds new files via the media manager.
"""
current_list = self.get_file_list()
titles = [os.path.split(file)[1] for file in current_list]
self.application.set_busy_cursor()
if not initial_load:
self.main_window.display_progress_bar(len(files))
# Sort the presentations by its filename considering language specific characters.
files.sort(key=lambda filename: get_locale_key(os.path.split(str(filename))[1]))
for file in files:
if not initial_load:
self.main_window.increment_progress_bar()
if current_list.count(file) > 0:
continue
filename = os.path.split(file)[1]
if not os.path.exists(file):
item_name = QtWidgets.QListWidgetItem(filename)
item_name.setIcon(build_icon(ERROR_IMAGE))
item_name.setData(QtCore.Qt.UserRole, file)
item_name.setToolTip(file)
self.list_view.addItem(item_name)
else:
if titles.count(filename) > 0:
if not initial_load:
critical_error_message_box(translate('PresentationPlugin.MediaItem', 'File Exists'),
translate('PresentationPlugin.MediaItem',
'A presentation with that filename already exists.'))
continue
controller_name = self.find_controller_by_type(filename)
if controller_name:
controller = self.controllers[controller_name]
doc = controller.add_document(file)
thumb = os.path.join(doc.get_thumbnail_folder(), 'icon.png')
preview = doc.get_thumbnail_path(1, True)
if not preview and not initial_load:
doc.load_presentation()
preview = doc.get_thumbnail_path(1, True)
doc.close_presentation()
if not (preview and os.path.exists(preview)):
icon = build_icon(':/general/general_delete.png')
else:
if validate_thumb(preview, thumb):
icon = build_icon(thumb)
else:
icon = create_thumb(preview, thumb)
else:
if initial_load:
icon = build_icon(':/general/general_delete.png')
else:
critical_error_message_box(UiStrings().UnsupportedFile,
translate('PresentationPlugin.MediaItem',
'This type of presentation is not supported.'))
continue
item_name = QtWidgets.QListWidgetItem(filename)
item_name.setData(QtCore.Qt.UserRole, file)
item_name.setIcon(icon)
item_name.setToolTip(file)
self.list_view.addItem(item_name)
if not initial_load:
self.main_window.finished_progress_bar()
self.application.set_normal_cursor()
[docs] def on_delete_click(self):
"""
Remove a presentation item from the list.
"""
if check_item_selected(self.list_view, UiStrings().SelectDelete):
items = self.list_view.selectedIndexes()
row_list = [item.row() for item in items]
row_list.sort(reverse=True)
self.application.set_busy_cursor()
self.main_window.display_progress_bar(len(row_list))
for item in items:
filepath = str(item.data(QtCore.Qt.UserRole))
self.clean_up_thumbnails(filepath)
self.main_window.increment_progress_bar()
self.main_window.finished_progress_bar()
for row in row_list:
self.list_view.takeItem(row)
Settings().setValue(self.settings_section + '/presentations files', self.get_file_list())
self.application.set_normal_cursor()
[docs] def clean_up_thumbnails(self, filepath, clean_for_update=False):
"""
Clean up the files created such as thumbnails
:param filepath: File path of the presention to clean up after
:param clean_for_update: Only clean thumbnails if update is needed
:return: None
"""
for cidx in self.controllers:
root, file_ext = os.path.splitext(filepath)
file_ext = file_ext[1:]
if file_ext in self.controllers[cidx].supports or file_ext in self.controllers[cidx].also_supports:
doc = self.controllers[cidx].add_document(filepath)
if clean_for_update:
thumb_path = doc.get_thumbnail_path(1, True)
if not thumb_path or not os.path.exists(filepath) or os.path.getmtime(
thumb_path) < os.path.getmtime(filepath):
doc.presentation_deleted()
else:
doc.presentation_deleted()
doc.close_presentation()
[docs] def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False,
context=ServiceItemContext.Service, presentation_file=None):
"""
Generate the slide data. Needs to be implemented by the plugin.
:param service_item: The service item to be built on
:param item: The Song item to be used
:param xml_version: The xml version (not used)
:param remote: Triggered from remote
:param context: Why is it being generated
"""
if item:
items = [item]
else:
items = self.list_view.selectedItems()
if len(items) > 1:
return False
filename = presentation_file
if filename is None:
filename = items[0].data(QtCore.Qt.UserRole)
file_type = os.path.splitext(filename.lower())[1][1:]
if not self.display_type_combo_box.currentText():
return False
service_item.add_capability(ItemCapabilities.CanEditTitle)
if file_type in PDF_CONTROLLER_FILETYPES and context != ServiceItemContext.Service:
service_item.add_capability(ItemCapabilities.CanMaintain)
service_item.add_capability(ItemCapabilities.CanPreview)
service_item.add_capability(ItemCapabilities.CanLoop)
service_item.add_capability(ItemCapabilities.CanAppend)
service_item.name = 'images'
# force a nonexistent theme
service_item.theme = -1
for bitem in items:
filename = presentation_file
if filename is None:
filename = bitem.data(QtCore.Qt.UserRole)
(path, name) = os.path.split(filename)
service_item.title = name
if os.path.exists(filename):
processor = self.find_controller_by_type(filename)
if not processor:
return False
controller = self.controllers[processor]
service_item.processor = None
doc = controller.add_document(filename)
if doc.get_thumbnail_path(1, True) is None or not os.path.isfile(
os.path.join(doc.get_temp_folder(), 'mainslide001.png')):
doc.load_presentation()
i = 1
image = os.path.join(doc.get_temp_folder(), 'mainslide{number:0>3d}.png'.format(number=i))
thumbnail = os.path.join(doc.get_thumbnail_folder(), 'slide%d.png' % i)
while os.path.isfile(image):
service_item.add_from_image(image, name, thumbnail=thumbnail)
i += 1
image = os.path.join(doc.get_temp_folder(), 'mainslide{number:0>3d}.png'.format(number=i))
thumbnail = os.path.join(doc.get_thumbnail_folder(), 'slide{number:d}.png'.format(number=i))
service_item.add_capability(ItemCapabilities.HasThumbnails)
doc.close_presentation()
return True
else:
# File is no longer present
if not remote:
critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'),
translate('PresentationPlugin.MediaItem',
'The presentation {name} no longer exists.'
).format(name=filename))
return False
else:
service_item.processor = self.display_type_combo_box.currentText()
service_item.add_capability(ItemCapabilities.ProvidesOwnDisplay)
for bitem in items:
filename = bitem.data(QtCore.Qt.UserRole)
(path, name) = os.path.split(filename)
service_item.title = name
if os.path.exists(filename):
if self.display_type_combo_box.itemData(self.display_type_combo_box.currentIndex()) == 'automatic':
service_item.processor = self.find_controller_by_type(filename)
if not service_item.processor:
return False
controller = self.controllers[service_item.processor]
doc = controller.add_document(filename)
if doc.get_thumbnail_path(1, True) is None:
doc.load_presentation()
i = 1
img = doc.get_thumbnail_path(i, True)
if img:
# Get titles and notes
titles, notes = doc.get_titles_and_notes()
service_item.add_capability(ItemCapabilities.HasDisplayTitle)
if notes.count('') != len(notes):
service_item.add_capability(ItemCapabilities.HasNotes)
service_item.add_capability(ItemCapabilities.HasThumbnails)
while img:
# Use title and note if available
title = ''
if titles and len(titles) >= i:
title = titles[i - 1]
note = ''
if notes and len(notes) >= i:
note = notes[i - 1]
service_item.add_from_command(path, name, img, title, note)
i += 1
img = doc.get_thumbnail_path(i, True)
doc.close_presentation()
return True
else:
# File is no longer present
if not remote:
critical_error_message_box(translate('PresentationPlugin.MediaItem',
'Missing Presentation'),
translate('PresentationPlugin.MediaItem',
'The presentation {name} is incomplete, '
'please reload.').format(name=filename))
return False
else:
# File is no longer present
if not remote:
critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'),
translate('PresentationPlugin.MediaItem',
'The presentation {name} no longer exists.'
).format(name=filename))
return False
[docs] def find_controller_by_type(self, filename):
"""
Determine the default application controller to use for the selected file type. This is used if "Automatic" is
set as the preferred controller. Find the first (alphabetic) enabled controller which "supports" the extension.
If none found, then look for a controller which "also supports" it instead.
:param filename: The file name
"""
file_type = os.path.splitext(filename)[1][1:]
if not file_type:
return None
for controller in self.controllers:
if self.controllers[controller].enabled():
if file_type in self.controllers[controller].supports:
return controller
for controller in self.controllers:
if self.controllers[controller].enabled():
if file_type in self.controllers[controller].also_supports:
return controller
return None
[docs] def search(self, string, show_error):
"""
Search in files
:param string: name to be found
:param show_error: not used
:return:
"""
files = Settings().value(self.settings_section + '/presentations files')
results = []
string = string.lower()
for file in files:
filename = os.path.split(str(file))[1]
if filename.lower().find(string) > -1:
results.append([file, filename])
return results