mirror of https://gitlab.com/openlp/openlp.git
306 lines
14 KiB
Python
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))
|