openlp/openlp/plugins/images/lib/mediaitem.py

306 lines
14 KiB
Python

# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2024 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, 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/>. #
##########################################################################
import logging
from pathlib import Path
from typing import Union
from PyQt5 import QtCore, QtWidgets
from openlp.core.common import delete_file, get_images_filter
from openlp.core.common.applocation import AppLocation
from openlp.core.common.enum import ImageThemeMode
from openlp.core.common.i18n import UiStrings, translate
from openlp.core.common.path import create_paths
from openlp.core.common.registry import Registry
from openlp.core.lib import ServiceItemContext, build_icon, check_item_selected, create_thumb, validate_thumb
from openlp.core.lib.serviceitem import ItemCapabilities
from openlp.core.lib.ui import create_widget_action, critical_error_message_box
from openlp.core.ui.icons import UiIcons
from openlp.core.ui.library import FolderLibraryItem
from openlp.plugins.images.lib.db import Folder, Item
log = logging.getLogger(__name__)
class ImageMediaItem(FolderLibraryItem):
"""
This is the custom media manager item for images.
"""
images_go_live = QtCore.pyqtSignal(list)
images_add_to_service = QtCore.pyqtSignal(list)
log.info('Image Media Item loaded')
def __init__(self, parent, plugin):
self.icon_path = 'images/image'
self.manager = None
super(ImageMediaItem, self).__init__(parent, plugin, Folder, Item)
def setup_item(self):
"""
Do some additional setup.
"""
self.images_go_live.connect(self.go_live_remote)
self.images_add_to_service.connect(self.add_to_service_remote)
self.quick_preview_allowed = True
self.has_search = True
self.list_view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
Registry().register_function('live_theme_changed', self.on_display_changed)
Registry().register_function('slidecontroller_live_started', self.on_display_changed)
# Allow DnD from the desktop.
self.list_view.activateDnD()
def retranslate_ui(self):
super().retranslate_ui()
self.on_new_prompt = translate('ImagePlugin.MediaItem', 'Select Image(s)')
file_formats = get_images_filter()
self.on_new_file_masks = '{formats};;{files} (*)'.format(formats=file_formats, files=UiStrings().AllFiles)
self.replace_action.setText(UiStrings().ReplaceBG)
self.replace_action.setToolTip(UiStrings().ReplaceLiveBG)
self.replace_action_context.setText(UiStrings().ReplaceBG)
self.replace_action_context.setToolTip(UiStrings().ReplaceLiveBG)
self.reset_action.setText(UiStrings().ResetBG)
self.reset_action.setToolTip(UiStrings().ResetLiveBG)
self.reset_action_context.setText(UiStrings().ResetBG)
self.reset_action_context.setToolTip(UiStrings().ResetLiveBG)
def required_icons(self):
"""
Set which icons the media manager tab should show.
"""
super().required_icons()
self.has_file_icon = True
self.has_new_icon = False
self.has_edit_icon = False
self.add_to_service_item = True
def initialise(self):
log.debug('initialise')
self.list_view.clear()
self.list_view.setIconSize(QtCore.QSize(88, 50))
self.list_view.setIndentation(self.list_view.default_indentation)
self.list_view.allow_internal_dnd = True
self.service_path = AppLocation.get_section_data_path(self.settings_section) / 'thumbnails'
create_paths(self.service_path)
# Load images from the database
self.load_list(self.manager.get_all_objects(Item, order_by_ref=Item.file_path), is_initial_load=True)
def add_custom_context_actions(self):
"""
Add custom actions to the context menu.
"""
create_widget_action(self.list_view, separator=True)
create_widget_action(self.list_view, text=UiStrings().AddFolder, icon=UiIcons().folder,
triggers=self.on_add_folder_click)
create_widget_action(self.list_view, text=translate('ImagePlugin', 'Add new image(s)'),
icon=UiIcons().open, triggers=self.on_file_click)
create_widget_action(self.list_view, separator=True)
self.replace_action_context = create_widget_action(self.list_view, text=UiStrings().ReplaceBG,
icon=UiIcons().theme, triggers=self.on_replace_click)
self.reset_action_context = create_widget_action(self.list_view, text=UiStrings().ReplaceLiveBG,
icon=UiIcons().close, visible=False,
triggers=self.on_reset_click)
def add_end_header_bar(self):
"""
Add custom buttons to the end of the toolbar
"""
self.replace_action = self.toolbar.add_toolbar_action('replace_action',
icon=UiIcons().theme,
triggers=self.on_replace_click)
self.reset_action = self.toolbar.add_toolbar_action('reset_action',
icon=UiIcons().close,
visible=False, triggers=self.on_reset_click)
def generate_thumbnail_path(self, item: Item) -> Path:
"""
Generate a path to the thumbnail
:param openlp.plugins.images.lib.db.Item image: The image to generate the thumbnail path for.
:return: A path to the thumbnail
:rtype: Path
"""
file_path = Path(item.file_path)
ext = file_path.suffix.lower()
return self.service_path / '{name:s}{ext}'.format(name=item.file_hash or file_path.stem, ext=ext)
def load_item(self, item: Item, is_initial_load: bool = False) -> QtWidgets.QTreeWidgetItem:
"""Given an item object, return a QTreeWidgetItem"""
tree_item = None
file_path = Path(item.file_path)
file_name = file_path.name
if not file_path.exists():
tree_item = QtWidgets.QTreeWidgetItem([file_name])
tree_item.setIcon(0, UiIcons().delete)
tree_item.setData(0, QtCore.Qt.UserRole, item)
tree_item.setToolTip(0, str(file_path))
else:
log.debug('Loading image: {name}'.format(name=item.file_path))
thumbnail_path = self.generate_thumbnail_path(item)
if validate_thumb(file_path, thumbnail_path):
icon = build_icon(thumbnail_path)
else:
size: Union[QtCore.QSize, None] = None
slide_height: Union[int, None] = self.settings.value('advanced/slide max height')
if slide_height and slide_height > 0:
size = QtCore.QSize(-1, slide_height)
icon = create_thumb(file_path, thumbnail_path, size=size)
tree_item = QtWidgets.QTreeWidgetItem([file_name])
tree_item.setData(0, QtCore.Qt.UserRole, item)
tree_item.setIcon(0, icon)
tree_item.setToolTip(0, str(file_path))
return tree_item
def delete_item(self, item: Item):
"""
Remove an image item from the list.
"""
file_path = Path(item.file_path)
if file_path.exists():
delete_file(self.service_path / file_path.name)
delete_file(self.generate_thumbnail_path(item))
def generate_slide_data(self, service_item, *, item=None, remote=False, context=ServiceItemContext.Service,
**kwargs):
"""
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 remote: Triggered from remote
:param context: Why is it being generated
:param kwargs: Consume other unused args specified by the base implementation, but not use by this one.
"""
if item:
if isinstance(item, QtCore.QModelIndex):
item = self.list_view.itemFromIndex(item)
if not item:
return False
items = [item]
else:
items = self.list_view.selectedItems()
if not items:
return False
# Determine service item title
image_item = items[0].data(0, QtCore.Qt.UserRole)
if isinstance(image_item, Folder) or len(items) == 1:
service_item.title = items[0].text(0)
if not service_item.title:
name = Path(image_item.file_path).name
if name:
service_item.title = name
else:
service_item.title = str(self.plugin.name_strings['plural'])
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.add_capability(ItemCapabilities.CanEditTitle)
service_item.add_capability(ItemCapabilities.HasThumbnails)
service_item.add_capability(ItemCapabilities.ProvidesOwnTheme)
if self.settings.value('images/background mode') == ImageThemeMode.CustomTheme:
service_item.theme = self.settings.value('images/theme')
else:
# force a nonexistent theme
service_item.theme = -1
missing_items_file_names = []
images = []
existing_images = []
# Expand groups to images
for bitem in items:
if isinstance(bitem.data(0, QtCore.Qt.UserRole), Folder) or bitem.data(0, QtCore.Qt.UserRole) is None:
for index in range(0, bitem.childCount()):
if isinstance(bitem.child(index).data(0, QtCore.Qt.UserRole), Item):
images.append(bitem.child(index).data(0, QtCore.Qt.UserRole))
elif isinstance(bitem.data(0, QtCore.Qt.UserRole), Item):
images.append(bitem.data(0, QtCore.Qt.UserRole))
# Don't try to display empty groups
if not images:
return False
# Find missing files
for image in images:
if not Path(image.file_path).exists():
missing_items_file_names.append(image.file_path)
else:
existing_images.append(image)
# We cannot continue, as all images do not exist.
if not existing_images:
if not remote:
critical_error_message_box(
translate('ImagePlugin.MediaItem', 'Missing Image(s)'),
translate('ImagePlugin.MediaItem', 'The following image(s) no longer exist: {names}'
).format(names='\n'.join(missing_items_file_names)))
return False
# We have missing as well as existing images. We ask what to do.
elif missing_items_file_names and QtWidgets.QMessageBox.question(
self, translate('ImagePlugin.MediaItem', 'Missing Image(s)'),
translate('ImagePlugin.MediaItem', 'The following image(s) no longer exist: {names}\n'
'Do you want to add the other images anyway?'
).format(names='\n'.join(missing_items_file_names))) == \
QtWidgets.QMessageBox.No:
return False
# Continue with the existing images.
for image in existing_images:
name = Path(image.file_path).name
thumbnail_path = self.generate_thumbnail_path(image)
service_item.add_from_image(Path(image.file_path), name, thumbnail_path)
return True
def on_reset_click(self):
"""
Called to reset the Live background with the image selected.
"""
self.reset_action.setVisible(False)
self.reset_action_context.setVisible(False)
self.live_controller.reload_theme()
def on_display_changed(self, service_item=None):
"""
Triggered by the change of theme in the slide controller.
"""
self.reset_action.setVisible(False)
self.reset_action_context.setVisible(False)
def on_replace_click(self):
"""
Called to replace Live background with the image selected.
"""
if check_item_selected(
self.list_view,
translate('ImagePlugin.MediaItem', 'You must select an image to replace the background with.')):
bitem = self.list_view.selectedItems()[0]
if not isinstance(bitem.data(0, QtCore.Qt.UserRole), Item):
# Only continue when an image is selected.
return
file_path = Path(bitem.data(0, QtCore.Qt.UserRole).file_path)
if file_path.exists():
self.live_controller.set_background_image(file_path)
self.reset_action.setVisible(True)
self.reset_action_context.setVisible(True)
else:
critical_error_message_box(
UiStrings().LiveBGError,
translate('ImagePlugin.MediaItem', 'There was a problem replacing your background, '
'the image file "{name}" no longer exists.').format(name=file_path))