예제 #1
0
 def setup_item(self):
     """
     Do some additional setup.
     """
     self.quick_preview_allowed = True
     self.has_search = True
     self.manager = self.plugin.manager
     self.choose_group_form = ChooseGroupForm(self)
     self.add_group_form = AddGroupForm(self)
     self.fill_groups_combobox(self.choose_group_form.group_combobox)
     self.fill_groups_combobox(self.add_group_form.parent_group_combobox)
     Registry().register_function('live_theme_changed', self.live_theme_changed)
     # Allow DnD from the desktop.
     self.list_view.activateDnD()
예제 #2
0
 def __init__(self, parent, plugin):
     self.icon_path = 'images/image'
     super(ImageMediaItem, self).__init__(parent, plugin)
     self.quick_preview_allowed = True
     self.has_search = True
     self.manager = plugin.manager
     self.choose_group_form = ChooseGroupForm(self)
     self.add_group_form = AddGroupForm(self)
     self.fill_groups_combobox(self.choose_group_form.group_combobox)
     self.fill_groups_combobox(self.add_group_form.parent_group_combobox)
     Registry().register_function('live_theme_changed', self.live_theme_changed)
     # Allow DnD from the desktop.
     self.list_view.activateDnD()
예제 #3
0
class ImageMediaItem(MediaManagerItem):
    """
    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
        self.choose_group_form = None
        self.add_group_form = None
        super(ImageMediaItem, self).__init__(parent, plugin)

    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.manager = self.plugin.manager
        self.choose_group_form = ChooseGroupForm(self)
        self.add_group_form = AddGroupForm(self)
        self.fill_groups_combobox(self.choose_group_form.group_combobox)
        self.fill_groups_combobox(self.add_group_form.parent_group_combobox)
        Registry().register_function('live_theme_changed',
                                     self.live_theme_changed)
        # Allow DnD from the desktop.
        self.list_view.activateDnD()

    def retranslateUi(self):
        self.on_new_prompt = translate('ImagePlugin.MediaItem',
                                       'Select Image(s)')
        file_formats = get_images_filter()
        self.on_new_file_masks = '%s;;%s (*)' % (file_formats,
                                                 UiStrings().AllFiles)
        self.add_group_action.setText(UiStrings().AddGroup)
        self.add_group_action.setToolTip(UiStrings().AddGroup)
        self.replace_action.setText(UiStrings().ReplaceBG)
        self.replace_action.setToolTip(UiStrings().ReplaceLiveBG)
        self.reset_action.setText(UiStrings().ResetBG)
        self.reset_action.setToolTip(UiStrings().ResetLiveBG)

    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
        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 = os.path.join(
            AppLocation.get_section_data_path(self.settings_section),
            'thumbnails')
        check_directory_exists(self.service_path)
        # Load images from the database
        self.load_full_list(self.manager.get_all_objects(
            ImageFilenames, order_by_ref=ImageFilenames.filename),
                            initial_load=True)

    def add_list_view_to_toolbar(self):
        """
        Creates the main widget for listing items the media item is tracking. This method overloads
        MediaManagerItem.add_list_view_to_toolbar.
        """
        # Add the List widget
        self.list_view = TreeWidgetWithDnD(self, self.plugin.name)
        self.list_view.setSelectionMode(
            QtWidgets.QAbstractItemView.ExtendedSelection)
        self.list_view.setAlternatingRowColors(True)
        self.list_view.setObjectName('%sTreeView' % self.plugin.name)
        # Add to pageLayout
        self.page_layout.addWidget(self.list_view)
        # define and add the context menu
        self.list_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        if self.has_edit_icon:
            create_widget_action(self.list_view,
                                 text=self.plugin.get_string(
                                     StringContent.Edit)['title'],
                                 icon=':/general/general_edit.png',
                                 triggers=self.on_edit_click)
            create_widget_action(self.list_view, separator=True)
        create_widget_action(
            self.list_view,
            'listView%s%sItem' %
            (self.plugin.name.title(), StringContent.Preview.title()),
            text=self.plugin.get_string(StringContent.Preview)['title'],
            icon=':/general/general_preview.png',
            can_shortcuts=True,
            triggers=self.on_preview_click)
        create_widget_action(
            self.list_view,
            'listView%s%sItem' %
            (self.plugin.name.title(), StringContent.Live.title()),
            text=self.plugin.get_string(StringContent.Live)['title'],
            icon=':/general/general_live.png',
            can_shortcuts=True,
            triggers=self.on_live_click)
        create_widget_action(
            self.list_view,
            'listView%s%sItem' %
            (self.plugin.name.title(), StringContent.Service.title()),
            can_shortcuts=True,
            text=self.plugin.get_string(StringContent.Service)['title'],
            icon=':/general/general_add.png',
            triggers=self.on_add_click)
        if self.add_to_service_item:
            create_widget_action(self.list_view, separator=True)
            create_widget_action(self.list_view,
                                 text=translate(
                                     'OpenLP.MediaManagerItem',
                                     '&Add to selected Service Item'),
                                 icon=':/general/general_add.png',
                                 triggers=self.on_add_edit_click)
            create_widget_action(self.list_view, separator=True)
        if self.has_delete_icon:
            create_widget_action(
                self.list_view,
                'listView%s%sItem' %
                (self.plugin.name.title(), StringContent.Delete.title()),
                text=self.plugin.get_string(StringContent.Delete)['title'],
                icon=':/general/general_delete.png',
                can_shortcuts=True,
                triggers=self.on_delete_click)
        self.add_custom_context_actions()
        # Create the context menu and add all actions from the list_view.
        self.menu = QtWidgets.QMenu()
        self.menu.addActions(self.list_view.actions())
        self.list_view.doubleClicked.connect(self.on_double_clicked)
        self.list_view.itemSelectionChanged.connect(self.on_selection_change)
        self.list_view.customContextMenuRequested.connect(self.context_menu)
        self.list_view.addAction(self.replace_action)

    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().AddGroup,
                             icon=':/images/image_new_group.png',
                             triggers=self.on_add_group_click)
        create_widget_action(self.list_view,
                             text=self.plugin.get_string(
                                 StringContent.Load)['tooltip'],
                             icon=':/general/general_open.png',
                             triggers=self.on_file_click)

    def add_start_header_bar(self):
        """
        Add custom buttons to the start of the toolbar.
        """
        self.add_group_action = self.toolbar.add_toolbar_action(
            'add_group_action',
            icon=':/images/image_new_group.png',
            triggers=self.on_add_group_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=':/slides/slide_blank.png',
            triggers=self.on_replace_click)
        self.reset_action = self.toolbar.add_toolbar_action(
            'reset_action',
            icon=':/system/system_close.png',
            visible=False,
            triggers=self.on_reset_click)

    def recursively_delete_group(self, image_group):
        """
        Recursively deletes a group and all groups and images in it.

        :param image_group: The ImageGroups instance of the group that will be deleted.
        """
        images = self.manager.get_all_objects(
            ImageFilenames, ImageFilenames.group_id == image_group.id)
        for image in images:
            delete_file(
                os.path.join(self.service_path,
                             os.path.split(image.filename)[1]))
            delete_file(self.generate_thumbnail_path(image))
            self.manager.delete_object(ImageFilenames, image.id)
        image_groups = self.manager.get_all_objects(
            ImageGroups, ImageGroups.parent_id == image_group.id)
        for group in image_groups:
            self.recursively_delete_group(group)
            self.manager.delete_object(ImageGroups, group.id)

    def on_delete_click(self):
        """
        Remove an image item from the list.
        """
        # Turn off auto preview triggers.
        self.list_view.blockSignals(True)
        if check_item_selected(
                self.list_view,
                translate('ImagePlugin.MediaItem',
                          'You must select an image or group to delete.')):
            item_list = self.list_view.selectedItems()
            self.application.set_busy_cursor()
            self.main_window.display_progress_bar(len(item_list))
            for row_item in item_list:
                if row_item:
                    item_data = row_item.data(0, QtCore.Qt.UserRole)
                    if isinstance(item_data, ImageFilenames):
                        delete_file(
                            os.path.join(self.service_path, row_item.text(0)))
                        delete_file(self.generate_thumbnail_path(item_data))
                        if item_data.group_id == 0:
                            self.list_view.takeTopLevelItem(
                                self.list_view.indexOfTopLevelItem(row_item))
                        else:
                            row_item.parent().removeChild(row_item)
                        self.manager.delete_object(
                            ImageFilenames,
                            row_item.data(0, QtCore.Qt.UserRole).id)
                    elif isinstance(item_data, ImageGroups):
                        if QtWidgets.QMessageBox.question(
                                self.list_view.parent(),
                                translate('ImagePlugin.MediaItem',
                                          'Remove group'),
                                translate(
                                    'ImagePlugin.MediaItem',
                                    'Are you sure you want to remove "%s" and everything in it?'
                                ) % item_data.group_name,
                                QtWidgets.QMessageBox.StandardButtons(
                                    QtWidgets.QMessageBox.Yes
                                    | QtWidgets.QMessageBox.No)
                        ) == QtWidgets.QMessageBox.Yes:
                            self.recursively_delete_group(item_data)
                            self.manager.delete_object(
                                ImageGroups,
                                row_item.data(0, QtCore.Qt.UserRole).id)
                            if item_data.parent_id == 0:
                                self.list_view.takeTopLevelItem(
                                    self.list_view.indexOfTopLevelItem(
                                        row_item))
                            else:
                                row_item.parent().removeChild(row_item)
                            self.fill_groups_combobox(
                                self.choose_group_form.group_combobox)
                            self.fill_groups_combobox(
                                self.add_group_form.parent_group_combobox)
                self.main_window.increment_progress_bar()
            self.main_window.finished_progress_bar()
            self.application.set_normal_cursor()
        self.list_view.blockSignals(False)

    def add_sub_groups(self, group_list, parent_group_id):
        """
        Recursively add subgroups to the given parent group in a QTreeWidget.

        :param group_list: The List object that contains all QTreeWidgetItems.
        :param parent_group_id: The ID of the group that will be added recursively.
        """
        image_groups = self.manager.get_all_objects(
            ImageGroups, ImageGroups.parent_id == parent_group_id)
        image_groups.sort(
            key=lambda group_object: get_locale_key(group_object.group_name))
        folder_icon = build_icon(':/images/image_group.png')
        for image_group in image_groups:
            group = QtWidgets.QTreeWidgetItem()
            group.setText(0, image_group.group_name)
            group.setData(0, QtCore.Qt.UserRole, image_group)
            group.setIcon(0, folder_icon)
            if parent_group_id == 0:
                self.list_view.addTopLevelItem(group)
            else:
                group_list[parent_group_id].addChild(group)
            group_list[image_group.id] = group
            self.add_sub_groups(group_list, image_group.id)

    def fill_groups_combobox(self, combobox, parent_group_id=0, prefix=''):
        """
        Recursively add groups to the combobox in the 'Add group' dialog.

        :param combobox: The QComboBox to add the options to.
        :param parent_group_id: The ID of the group that will be added.
        :param prefix: A string containing the prefix that will be added in front of the groupname for each level of
            the tree.
        """
        if parent_group_id == 0:
            combobox.clear()
            combobox.top_level_group_added = False
        image_groups = self.manager.get_all_objects(
            ImageGroups, ImageGroups.parent_id == parent_group_id)
        image_groups.sort(
            key=lambda group_object: get_locale_key(group_object.group_name))
        for image_group in image_groups:
            combobox.addItem(prefix + image_group.group_name, image_group.id)
            self.fill_groups_combobox(combobox, image_group.id, prefix + '   ')

    def expand_group(self, group_id, root_item=None):
        """
        Expand groups in the widget recursively.

        :param group_id: The ID of the group that will be expanded.
        :param root_item: This option is only used for recursion purposes.
        """
        return_value = False
        if root_item is None:
            root_item = self.list_view.invisibleRootItem()
        for i in range(root_item.childCount()):
            child = root_item.child(i)
            if self.expand_group(group_id, child):
                child.setExpanded(True)
                return_value = True
        if isinstance(root_item.data(0, QtCore.Qt.UserRole), ImageGroups):
            if root_item.data(0, QtCore.Qt.UserRole).id == group_id:
                return True
        return return_value

    def generate_thumbnail_path(self, image):
        """
        Generate a path to the thumbnail

        :param image: An instance of ImageFileNames
        :return: A path to the thumbnail of type str
        """
        ext = os.path.splitext(image.filename)[1].lower()
        return os.path.join(self.service_path,
                            '{}{}'.format(str(image.id), ext))

    def load_full_list(self, images, initial_load=False, open_group=None):
        """
        Replace the list of images and groups in the interface.

        :param images: A List of Image Filenames objects that will be used to reload the mediamanager list.
        :param initial_load: When set to False, the busy cursor and progressbar will be shown while loading images.
        :param open_group: ImageGroups object of the group that must be expanded after reloading the list in the
            interface.
        """
        if not initial_load:
            self.application.set_busy_cursor()
            self.main_window.display_progress_bar(len(images))
        self.list_view.clear()
        # Load the list of groups and add them to the treeView.
        group_items = {}
        self.add_sub_groups(group_items, parent_group_id=0)
        if open_group is not None:
            self.expand_group(open_group.id)
        # Sort the images by its filename considering language specific.
        # characters.
        images.sort(key=lambda image_object: get_locale_key(
            os.path.split(str(image_object.filename))[1]))
        for image_file in images:
            log.debug('Loading image: %s', image_file.filename)
            filename = os.path.split(image_file.filename)[1]
            thumb = self.generate_thumbnail_path(image_file)
            if not os.path.exists(image_file.filename):
                icon = build_icon(':/general/general_delete.png')
            else:
                if validate_thumb(image_file.filename, thumb):
                    icon = build_icon(thumb)
                else:
                    icon = create_thumb(image_file.filename, thumb)
            item_name = QtWidgets.QTreeWidgetItem([filename])
            item_name.setText(0, filename)
            item_name.setIcon(0, icon)
            item_name.setToolTip(0, image_file.filename)
            item_name.setData(0, QtCore.Qt.UserRole, image_file)
            if image_file.group_id == 0:
                self.list_view.addTopLevelItem(item_name)
            else:
                group_items[image_file.group_id].addChild(item_name)
            if not initial_load:
                self.main_window.increment_progress_bar()
        if not initial_load:
            self.main_window.finished_progress_bar()
        self.application.set_normal_cursor()

    def validate_and_load(self, files, target_group=None):
        """
        Process a list for files either from the File Dialog or from Drag and Drop.
        This method is overloaded from MediaManagerItem.

        :param files: A List of strings containing the filenames of the files to be loaded
        :param target_group: The QTreeWidgetItem of the group that will be the parent of the added files
        """
        self.application.set_normal_cursor()
        self.load_list(files, target_group)
        last_dir = os.path.split(files[0])[0]
        Settings().setValue(self.settings_section + '/last directory',
                            last_dir)

    def load_list(self, images, target_group=None, initial_load=False):
        """
        Add new images to the database. This method is called when adding images using the Add button or DnD.

        :param images: A List of strings containing the filenames of the files to be loaded
        :param target_group: The QTreeWidgetItem of the group that will be the parent of the added files
        :param initial_load: When set to False, the busy cursor and progressbar will be shown while loading images
        """
        parent_group = None
        if target_group is None:
            # Find out if a group must be pre-selected
            preselect_group = None
            selected_items = self.list_view.selectedItems()
            if selected_items:
                selected_item = selected_items[0]
                if isinstance(selected_item.data(0, QtCore.Qt.UserRole),
                              ImageFilenames):
                    selected_item = selected_item.parent()
                if isinstance(selected_item, QtWidgets.QTreeWidgetItem):
                    if isinstance(selected_item.data(0, QtCore.Qt.UserRole),
                                  ImageGroups):
                        preselect_group = selected_item.data(
                            0, QtCore.Qt.UserRole).id
            # Enable and disable parts of the 'choose group' form
            if preselect_group is None:
                self.choose_group_form.nogroup_radio_button.setChecked(True)
                self.choose_group_form.nogroup_radio_button.setFocus()
                self.choose_group_form.existing_radio_button.setChecked(False)
                self.choose_group_form.new_radio_button.setChecked(False)
            else:
                self.choose_group_form.nogroup_radio_button.setChecked(False)
                self.choose_group_form.existing_radio_button.setChecked(True)
                self.choose_group_form.new_radio_button.setChecked(False)
                self.choose_group_form.group_combobox.setFocus()
            if self.manager.get_object_count(ImageGroups) == 0:
                self.choose_group_form.existing_radio_button.setDisabled(True)
                self.choose_group_form.group_combobox.setDisabled(True)
            else:
                self.choose_group_form.existing_radio_button.setDisabled(False)
                self.choose_group_form.group_combobox.setDisabled(False)
            # Ask which group the images should be saved in
            if self.choose_group_form.exec(selected_group=preselect_group):
                if self.choose_group_form.nogroup_radio_button.isChecked():
                    # User chose 'No group'
                    parent_group = ImageGroups()
                    parent_group.id = 0
                elif self.choose_group_form.existing_radio_button.isChecked():
                    # User chose 'Existing group'
                    group_id = self.choose_group_form.group_combobox.itemData(
                        self.choose_group_form.group_combobox.currentIndex(),
                        QtCore.Qt.UserRole)
                    parent_group = self.manager.get_object_filtered(
                        ImageGroups, ImageGroups.id == group_id)
                elif self.choose_group_form.new_radio_button.isChecked():
                    # User chose 'New group'
                    parent_group = ImageGroups()
                    parent_group.parent_id = 0
                    parent_group.group_name = self.choose_group_form.new_group_edit.text(
                    )
                    self.manager.save_object(parent_group)
                    self.fill_groups_combobox(
                        self.choose_group_form.group_combobox)
                    self.fill_groups_combobox(
                        self.add_group_form.parent_group_combobox)
        else:
            parent_group = target_group.data(0, QtCore.Qt.UserRole)
            if isinstance(parent_group, ImageFilenames):
                if parent_group.group_id == 0:
                    parent_group = ImageGroups()
                    parent_group.id = 0
                else:
                    parent_group = target_group.parent().data(
                        0, QtCore.Qt.UserRole)
        # If no valid parent group is found, do nothing
        if not isinstance(parent_group, ImageGroups):
            return
        # Initialize busy cursor and progress bar
        self.application.set_busy_cursor()
        self.main_window.display_progress_bar(len(images))
        # Save the new images in the database
        self.save_new_images_list(images,
                                  group_id=parent_group.id,
                                  reload_list=False)
        self.load_full_list(self.manager.get_all_objects(
            ImageFilenames, order_by_ref=ImageFilenames.filename),
                            initial_load=initial_load,
                            open_group=parent_group)
        self.application.set_normal_cursor()

    def save_new_images_list(self, images_list, group_id=0, reload_list=True):
        """
        Convert a list of image filenames to ImageFilenames objects and save them in the database.

        :param images_list: A List of strings containing image filenames
        :param group_id: The ID of the group to save the images in
        :param reload_list: This boolean is set to True when the list in the interface should be reloaded after saving
            the new images
        """
        for filename in images_list:
            if not isinstance(filename, str):
                continue
            log.debug('Adding new image: %s', filename)
            image_file = ImageFilenames()
            image_file.group_id = group_id
            image_file.filename = str(filename)
            self.manager.save_object(image_file)
            self.main_window.increment_progress_bar()
        if reload_list and images_list:
            self.load_full_list(
                self.manager.get_all_objects(
                    ImageFilenames, order_by_ref=ImageFilenames.filename))

    def dnd_move_internal(self, target):
        """
        Handle drag-and-drop moving of images within the media manager

        :param target: This contains the QTreeWidget that is the target of the DnD action
        """
        items_to_move = self.list_view.selectedItems()
        # Determine group to move images to
        target_group = target
        if target_group is not None and isinstance(
                target_group.data(0, QtCore.Qt.UserRole), ImageFilenames):
            target_group = target.parent()
        # Move to toplevel
        if target_group is None:
            target_group = self.list_view.invisibleRootItem()
            target_group.setData(0, QtCore.Qt.UserRole, ImageGroups())
            target_group.data(0, QtCore.Qt.UserRole).id = 0
        # Move images in the treeview
        items_to_save = []
        for item in items_to_move:
            if isinstance(item.data(0, QtCore.Qt.UserRole), ImageFilenames):
                if isinstance(item.parent(), QtWidgets.QTreeWidgetItem):
                    item.parent().removeChild(item)
                else:
                    self.list_view.invisibleRootItem().removeChild(item)
                target_group.addChild(item)
                item.setSelected(True)
                item_data = item.data(0, QtCore.Qt.UserRole)
                item_data.group_id = target_group.data(0,
                                                       QtCore.Qt.UserRole).id
                items_to_save.append(item_data)
        target_group.setExpanded(True)
        # Update the group ID's of the images in the database
        self.manager.save_objects(items_to_save)
        # Sort the target group
        group_items = []
        image_items = []
        for item in target_group.takeChildren():
            if isinstance(item.data(0, QtCore.Qt.UserRole), ImageGroups):
                group_items.append(item)
            if isinstance(item.data(0, QtCore.Qt.UserRole), ImageFilenames):
                image_items.append(item)
        group_items.sort(key=lambda item: get_locale_key(item.text(0)))
        target_group.addChildren(group_items)
        image_items.sort(key=lambda item: get_locale_key(item.text(0)))
        target_group.addChildren(image_items)

    def generate_slide_data(self,
                            service_item,
                            item=None,
                            xml_version=False,
                            remote=False,
                            context=ServiceItemContext.Service):
        """
        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
        """
        background = QtGui.QColor(Settings().value(self.settings_section +
                                                   '/background color'))
        if item:
            items = [item]
        else:
            items = self.list_view.selectedItems()
            if not items:
                return False
        # Determine service item title
        if isinstance(items[0].data(0, QtCore.Qt.UserRole),
                      ImageGroups) or len(items) == 1:
            service_item.title = items[0].text(0)
        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)
        # force a nonexistent theme
        service_item.theme = -1
        missing_items_file_names = []
        images = []
        # Expand groups to images
        for bitem in items:
            if isinstance(
                    bitem.data(0, QtCore.Qt.UserRole),
                    ImageGroups) 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),
                            ImageFilenames):
                        images.append(
                            bitem.child(index).data(0, QtCore.Qt.UserRole))
            elif isinstance(bitem.data(0, QtCore.Qt.UserRole), ImageFilenames):
                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 os.path.exists(image.filename):
                missing_items_file_names.append(image.filename)
        # We cannot continue, as all images do not exist.
        if not images:
            if not remote:
                critical_error_message_box(
                    translate('ImagePlugin.MediaItem', 'Missing Image(s)'),
                    translate('ImagePlugin.MediaItem',
                              'The following image(s) no longer exist: %s') %
                    '\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: %s\n'
                          'Do you want to add the other images anyway?') % '\n'.join(missing_items_file_names),
                QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Yes)) == \
                QtWidgets.QMessageBox.No:
            return False
        # Continue with the existing images.
        for image in images:
            name = os.path.split(image.filename)[1]
            thumbnail = self.generate_thumbnail_path(image)
            service_item.add_from_image(image.filename, name, background,
                                        thumbnail)
        return True

    def check_group_exists(self, new_group):
        """
        Returns *True* if the given Group already exists in the database, otherwise *False*.

        :param new_group: The ImageGroups object that contains the name of the group that will be checked
        """
        groups = self.manager.get_all_objects(
            ImageGroups, ImageGroups.group_name == new_group.group_name)
        if groups:
            return True
        else:
            return False

    def on_add_group_click(self):
        """
        Called to add a new group
        """
        # Find out if a group must be pre-selected
        preselect_group = 0
        selected_items = self.list_view.selectedItems()
        if selected_items:
            selected_item = selected_items[0]
            if isinstance(selected_item.data(0, QtCore.Qt.UserRole),
                          ImageFilenames):
                selected_item = selected_item.parent()
            if isinstance(selected_item, QtWidgets.QTreeWidgetItem):
                if isinstance(selected_item.data(0, QtCore.Qt.UserRole),
                              ImageGroups):
                    preselect_group = selected_item.data(
                        0, QtCore.Qt.UserRole).id
        # Show 'add group' dialog
        if self.add_group_form.exec(show_top_level_group=True,
                                    selected_group=preselect_group):
            new_group = ImageGroups.populate(
                parent_id=self.add_group_form.parent_group_combobox.itemData(
                    self.add_group_form.parent_group_combobox.currentIndex(),
                    QtCore.Qt.UserRole),
                group_name=self.add_group_form.name_edit.text())
            if not self.check_group_exists(new_group):
                if self.manager.save_object(new_group):
                    self.load_full_list(
                        self.manager.get_all_objects(
                            ImageFilenames,
                            order_by_ref=ImageFilenames.filename))
                    self.expand_group(new_group.id)
                    self.fill_groups_combobox(
                        self.choose_group_form.group_combobox)
                    self.fill_groups_combobox(
                        self.add_group_form.parent_group_combobox)
                else:
                    critical_error_message_box(
                        message=translate('ImagePlugin.AddGroupForm',
                                          'Could not add the new group.'))
            else:
                critical_error_message_box(message=translate(
                    'ImagePlugin.AddGroupForm', 'This group already exists.'))

    def on_reset_click(self):
        """
        Called to reset the Live background with the image selected.
        """
        self.reset_action.setVisible(False)
        self.live_controller.display.reset_image()

    def live_theme_changed(self):
        """
        Triggered by the change of theme in the slide controller.
        """
        self.reset_action.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.')
        ):
            background = QtGui.QColor(Settings().value(self.settings_section +
                                                       '/background color'))
            bitem = self.list_view.selectedItems()[0]
            if not isinstance(bitem.data(0, QtCore.Qt.UserRole),
                              ImageFilenames):
                # Only continue when an image is selected.
                return
            filename = bitem.data(0, QtCore.Qt.UserRole).filename
            if os.path.exists(filename):
                if self.live_controller.display.direct_image(
                        filename, background):
                    self.reset_action.setVisible(True)
                else:
                    critical_error_message_box(
                        UiStrings().LiveBGError,
                        translate('ImagePlugin.MediaItem',
                                  'There was no display item to amend.'))
            else:
                critical_error_message_box(
                    UiStrings().LiveBGError,
                    translate(
                        'ImagePlugin.MediaItem',
                        'There was a problem replacing your background, '
                        'the image file "%s" no longer exists.') % filename)

    def search(self, string, show_error=True):
        """
        Perform a search on the image file names.

        :param string: The glob to search for
        :param show_error: Unused.
        """
        files = self.manager.get_all_objects(
            ImageFilenames,
            filter_clause=ImageFilenames.filename.contains(string),
            order_by_ref=ImageFilenames.filename)
        results = []
        for file_object in files:
            filename = os.path.split(str(file_object.filename))[1]
            results.append([file_object.filename, filename])
        return results

    def create_item_from_id(self, item_id):
        """
        Create a media item from an item id. Overridden from the parent method to change the item type.

        :param item_id: Id to make live
        """
        item = QtWidgets.QTreeWidgetItem()
        item_data = self.manager.get_object_filtered(
            ImageFilenames, ImageFilenames.filename == item_id)
        item.setText(0, os.path.basename(item_data.filename))
        item.setData(0, QtCore.Qt.UserRole, item_data)
        return item
예제 #4
0
class ImageMediaItem(MediaManagerItem):
    """
    This is the custom media manager item for images.
    """
    log.info('Image Media Item loaded')

    def __init__(self, parent, plugin):
        self.icon_path = 'images/image'
        self.manager = None
        self.choose_group_form = None
        self.add_group_form = None
        super(ImageMediaItem, self).__init__(parent, plugin)

    def setup_item(self):
        """
        Do some additional setup.
        """
        self.quick_preview_allowed = True
        self.has_search = True
        self.manager = self.plugin.manager
        self.choose_group_form = ChooseGroupForm(self)
        self.add_group_form = AddGroupForm(self)
        self.fill_groups_combobox(self.choose_group_form.group_combobox)
        self.fill_groups_combobox(self.add_group_form.parent_group_combobox)
        Registry().register_function('live_theme_changed', self.live_theme_changed)
        # Allow DnD from the desktop.
        self.list_view.activateDnD()

    def retranslateUi(self):
        self.on_new_prompt = translate('ImagePlugin.MediaItem', 'Select Image(s)')
        file_formats = get_images_filter()
        self.on_new_file_masks = '%s;;%s (*)' % (file_formats, UiStrings().AllFiles)
        self.add_group_action.setText(UiStrings().AddGroup)
        self.add_group_action.setToolTip(UiStrings().AddGroup)
        self.replace_action.setText(UiStrings().ReplaceBG)
        self.replace_action.setToolTip(UiStrings().ReplaceLiveBG)
        self.reset_action.setText(UiStrings().ResetBG)
        self.reset_action.setToolTip(UiStrings().ResetLiveBG)

    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
        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 = os.path.join(AppLocation.get_section_data_path(self.settings_section), 'thumbnails')
        check_directory_exists(self.service_path)
        # Load images from the database
        self.load_full_list(
            self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.filename), initial_load=True)

    def add_list_view_to_toolbar(self):
        """
        Creates the main widget for listing items the media item is tracking. This method overloads
        MediaManagerItem.add_list_view_to_toolbar.
        """
        # Add the List widget
        self.list_view = TreeWidgetWithDnD(self, self.plugin.name)
        self.list_view.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
        self.list_view.setAlternatingRowColors(True)
        self.list_view.setObjectName('%sTreeView' % self.plugin.name)
        # Add to pageLayout
        self.page_layout.addWidget(self.list_view)
        # define and add the context menu
        self.list_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        if self.has_edit_icon:
            create_widget_action(
                self.list_view,
                text=self.plugin.get_string(StringContent.Edit)['title'],
                icon=':/general/general_edit.png',
                triggers=self.on_edit_click)
            create_widget_action(self.list_view, separator=True)
        if self.has_delete_icon:
            create_widget_action(
                self.list_view,
                'listView%s%sItem' % (self.plugin.name.title(), StringContent.Delete.title()),
                text=self.plugin.get_string(StringContent.Delete)['title'],
                icon=':/general/general_delete.png',
                can_shortcuts=True, triggers=self.on_delete_click)
            create_widget_action(self.list_view, separator=True)
        create_widget_action(
            self.list_view,
            'listView%s%sItem' % (self.plugin.name.title(), StringContent.Preview.title()),
            text=self.plugin.get_string(StringContent.Preview)['title'],
            icon=':/general/general_preview.png',
            can_shortcuts=True,
            triggers=self.on_preview_click)
        create_widget_action(
            self.list_view,
            'listView%s%sItem' % (self.plugin.name.title(), StringContent.Live.title()),
            text=self.plugin.get_string(StringContent.Live)['title'],
            icon=':/general/general_live.png',
            can_shortcuts=True,
            triggers=self.on_live_click)
        create_widget_action(
            self.list_view,
            'listView%s%sItem' % (self.plugin.name.title(), StringContent.Service.title()),
            can_shortcuts=True,
            text=self.plugin.get_string(StringContent.Service)['title'],
            icon=':/general/general_add.png',
            triggers=self.on_add_click)
        if self.add_to_service_item:
            create_widget_action(self.list_view, separator=True)
            create_widget_action(
                self.list_view,
                text=translate('OpenLP.MediaManagerItem', '&Add to selected Service Item'),
                icon=':/general/general_add.png',
                triggers=self.on_add_edit_click)
        self.add_custom_context_actions()
        # Create the context menu and add all actions from the list_view.
        self.menu = QtGui.QMenu()
        self.menu.addActions(self.list_view.actions())
        self.list_view.doubleClicked.connect(self.on_double_clicked)
        self.list_view.itemSelectionChanged.connect(self.on_selection_change)
        self.list_view.customContextMenuRequested.connect(self.context_menu)
        self.list_view.addAction(self.replace_action)

    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().AddGroup, icon=':/images/image_new_group.png', triggers=self.on_add_group_click)
        create_widget_action(
            self.list_view,
            text=self.plugin.get_string(StringContent.Load)['tooltip'],
            icon=':/general/general_open.png', triggers=self.on_file_click)

    def add_start_header_bar(self):
        """
        Add custom buttons to the start of the toolbar.
        """
        self.add_group_action = self.toolbar.add_toolbar_action('add_group_action',
                                                                icon=':/images/image_new_group.png',
                                                                triggers=self.on_add_group_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=':/slides/slide_blank.png',
                                                              triggers=self.on_replace_click)
        self.reset_action = self.toolbar.add_toolbar_action('reset_action',
                                                            icon=':/system/system_close.png',
                                                            visible=False, triggers=self.on_reset_click)

    def recursively_delete_group(self, image_group):
        """
        Recursively deletes a group and all groups and images in it.

        :param image_group: The ImageGroups instance of the group that will be deleted.
        """
        images = self.manager.get_all_objects(ImageFilenames, ImageFilenames.group_id == image_group.id)
        for image in images:
            delete_file(os.path.join(self.service_path, os.path.split(image.filename)[1]))
            delete_file(self.generate_thumbnail_path(image))
            self.manager.delete_object(ImageFilenames, image.id)
        image_groups = self.manager.get_all_objects(ImageGroups, ImageGroups.parent_id == image_group.id)
        for group in image_groups:
            self.recursively_delete_group(group)
            self.manager.delete_object(ImageGroups, group.id)

    def on_delete_click(self):
        """
        Remove an image item from the list.
        """
        # Turn off auto preview triggers.
        self.list_view.blockSignals(True)
        if check_item_selected(self.list_view, translate('ImagePlugin.MediaItem',
                                                         'You must select an image or group to delete.')):
            item_list = self.list_view.selectedItems()
            self.application.set_busy_cursor()
            self.main_window.display_progress_bar(len(item_list))
            for row_item in item_list:
                if row_item:
                    item_data = row_item.data(0, QtCore.Qt.UserRole)
                    if isinstance(item_data, ImageFilenames):
                        delete_file(os.path.join(self.service_path, row_item.text(0)))
                        delete_file(self.generate_thumbnail_path(item_data))
                        if item_data.group_id == 0:
                            self.list_view.takeTopLevelItem(self.list_view.indexOfTopLevelItem(row_item))
                        else:
                            row_item.parent().removeChild(row_item)
                        self.manager.delete_object(ImageFilenames, row_item.data(0, QtCore.Qt.UserRole).id)
                    elif isinstance(item_data, ImageGroups):
                        if QtGui.QMessageBox.question(
                                self.list_view.parent(),
                                translate('ImagePlugin.MediaItem', 'Remove group'),
                                translate('ImagePlugin.MediaItem',
                                          'Are you sure you want to remove "%s" and everything in it?') %
                                item_data.group_name,
                                QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Yes |
                                                                  QtGui.QMessageBox.No)) == QtGui.QMessageBox.Yes:
                            self.recursively_delete_group(item_data)
                            self.manager.delete_object(ImageGroups, row_item.data(0, QtCore.Qt.UserRole).id)
                            if item_data.parent_id == 0:
                                self.list_view.takeTopLevelItem(self.list_view.indexOfTopLevelItem(row_item))
                            else:
                                row_item.parent().removeChild(row_item)
                            self.fill_groups_combobox(self.choose_group_form.group_combobox)
                            self.fill_groups_combobox(self.add_group_form.parent_group_combobox)
                self.main_window.increment_progress_bar()
            self.main_window.finished_progress_bar()
            self.application.set_normal_cursor()
        self.list_view.blockSignals(False)

    def add_sub_groups(self, group_list, parent_group_id):
        """
        Recursively add subgroups to the given parent group in a QTreeWidget.

        :param group_list: The List object that contains all QTreeWidgetItems.
        :param parent_group_id: The ID of the group that will be added recursively.
        """
        image_groups = self.manager.get_all_objects(ImageGroups, ImageGroups.parent_id == parent_group_id)
        image_groups.sort(key=lambda group_object: get_locale_key(group_object.group_name))
        folder_icon = build_icon(':/images/image_group.png')
        for image_group in image_groups:
            group = QtGui.QTreeWidgetItem()
            group.setText(0, image_group.group_name)
            group.setData(0, QtCore.Qt.UserRole, image_group)
            group.setIcon(0, folder_icon)
            if parent_group_id == 0:
                self.list_view.addTopLevelItem(group)
            else:
                group_list[parent_group_id].addChild(group)
            group_list[image_group.id] = group
            self.add_sub_groups(group_list, image_group.id)

    def fill_groups_combobox(self, combobox, parent_group_id=0, prefix=''):
        """
        Recursively add groups to the combobox in the 'Add group' dialog.

        :param combobox: The QComboBox to add the options to.
        :param parent_group_id: The ID of the group that will be added.
        :param prefix: A string containing the prefix that will be added in front of the groupname for each level of
        the tree.
        """
        if parent_group_id == 0:
            combobox.clear()
            combobox.top_level_group_added = False
        image_groups = self.manager.get_all_objects(ImageGroups, ImageGroups.parent_id == parent_group_id)
        image_groups.sort(key=lambda group_object: get_locale_key(group_object.group_name))
        for image_group in image_groups:
            combobox.addItem(prefix + image_group.group_name, image_group.id)
            self.fill_groups_combobox(combobox, image_group.id, prefix + '   ')

    def expand_group(self, group_id, root_item=None):
        """
        Expand groups in the widget recursively.

        :param group_id: The ID of the group that will be expanded.
        :param root_item: This option is only used for recursion purposes.
        """
        return_value = False
        if root_item is None:
            root_item = self.list_view.invisibleRootItem()
        for i in range(root_item.childCount()):
            child = root_item.child(i)
            if self.expand_group(group_id, child):
                child.setExpanded(True)
                return_value = True
        if isinstance(root_item.data(0, QtCore.Qt.UserRole), ImageGroups):
            if root_item.data(0, QtCore.Qt.UserRole).id == group_id:
                return True
        return return_value

    def generate_thumbnail_path(self, image):
        """
        Generate a path to the thumbnail

        :param image: An instance of ImageFileNames
        :return: A path to the thumbnail of type str
        """
        ext = os.path.splitext(image.filename)[1].lower()
        return os.path.join(self.service_path, '{}{}'.format(str(image.id), ext))

    def load_full_list(self, images, initial_load=False, open_group=None):
        """
        Replace the list of images and groups in the interface.

        :param images: A List of Image Filenames objects that will be used to reload the mediamanager list.
        :param initial_load: When set to False, the busy cursor and progressbar will be shown while loading images.
        :param open_group: ImageGroups object of the group that must be expanded after reloading the list in the
        interface.
        """
        if not initial_load:
            self.application.set_busy_cursor()
            self.main_window.display_progress_bar(len(images))
        self.list_view.clear()
        # Load the list of groups and add them to the treeView.
        group_items = {}
        self.add_sub_groups(group_items, parent_group_id=0)
        if open_group is not None:
            self.expand_group(open_group.id)
        # Sort the images by its filename considering language specific.
        # characters.
        images.sort(key=lambda image_object: get_locale_key(os.path.split(str(image_object.filename))[1]))
        for image_file in images:
            log.debug('Loading image: %s', image_file.filename)
            filename = os.path.split(image_file.filename)[1]
            thumb = self.generate_thumbnail_path(image_file)
            if not os.path.exists(image_file.filename):
                icon = build_icon(':/general/general_delete.png')
            else:
                if validate_thumb(image_file.filename, thumb):
                    icon = build_icon(thumb)
                else:
                    icon = create_thumb(image_file.filename, thumb)
            item_name = QtGui.QTreeWidgetItem([filename])
            item_name.setText(0, filename)
            item_name.setIcon(0, icon)
            item_name.setToolTip(0, image_file.filename)
            item_name.setData(0, QtCore.Qt.UserRole, image_file)
            if image_file.group_id == 0:
                self.list_view.addTopLevelItem(item_name)
            else:
                group_items[image_file.group_id].addChild(item_name)
            if not initial_load:
                self.main_window.increment_progress_bar()
        if not initial_load:
            self.main_window.finished_progress_bar()
        self.application.set_normal_cursor()

    def validate_and_load(self, files, target_group=None):
        """
        Process a list for files either from the File Dialog or from Drag and Drop.
        This method is overloaded from MediaManagerItem.

        :param files: A List of strings containing the filenames of the files to be loaded
        :param target_group: The QTreeWidgetItem of the group that will be the parent of the added files
        """
        self.application.set_normal_cursor()
        self.load_list(files, target_group)
        last_dir = os.path.split(files[0])[0]
        Settings().setValue(self.settings_section + '/last directory', last_dir)

    def load_list(self, images, target_group=None, initial_load=False):
        """
        Add new images to the database. This method is called when adding images using the Add button or DnD.

        :param images: A List of strings containing the filenames of the files to be loaded
        :param target_group: The QTreeWidgetItem of the group that will be the parent of the added files
        :param initial_load: When set to False, the busy cursor and progressbar will be shown while loading images
        """
        parent_group = None
        if target_group is None:
            # Find out if a group must be pre-selected
            preselect_group = None
            selected_items = self.list_view.selectedItems()
            if selected_items:
                selected_item = selected_items[0]
                if isinstance(selected_item.data(0, QtCore.Qt.UserRole), ImageFilenames):
                    selected_item = selected_item.parent()
                if isinstance(selected_item, QtGui.QTreeWidgetItem):
                    if isinstance(selected_item.data(0, QtCore.Qt.UserRole), ImageGroups):
                        preselect_group = selected_item.data(0, QtCore.Qt.UserRole).id
            # Enable and disable parts of the 'choose group' form
            if preselect_group is None:
                self.choose_group_form.nogroup_radio_button.setChecked(True)
                self.choose_group_form.nogroup_radio_button.setFocus()
                self.choose_group_form.existing_radio_button.setChecked(False)
                self.choose_group_form.new_radio_button.setChecked(False)
            else:
                self.choose_group_form.nogroup_radio_button.setChecked(False)
                self.choose_group_form.existing_radio_button.setChecked(True)
                self.choose_group_form.new_radio_button.setChecked(False)
                self.choose_group_form.group_combobox.setFocus()
            if self.manager.get_object_count(ImageGroups) == 0:
                self.choose_group_form.existing_radio_button.setDisabled(True)
                self.choose_group_form.group_combobox.setDisabled(True)
            else:
                self.choose_group_form.existing_radio_button.setDisabled(False)
                self.choose_group_form.group_combobox.setDisabled(False)
            # Ask which group the images should be saved in
            if self.choose_group_form.exec_(selected_group=preselect_group):
                if self.choose_group_form.nogroup_radio_button.isChecked():
                    # User chose 'No group'
                    parent_group = ImageGroups()
                    parent_group.id = 0
                elif self.choose_group_form.existing_radio_button.isChecked():
                    # User chose 'Existing group'
                    group_id = self.choose_group_form.group_combobox.itemData(
                        self.choose_group_form.group_combobox.currentIndex(), QtCore.Qt.UserRole)
                    parent_group = self.manager.get_object_filtered(ImageGroups, ImageGroups.id == group_id)
                elif self.choose_group_form.new_radio_button.isChecked():
                    # User chose 'New group'
                    parent_group = ImageGroups()
                    parent_group.parent_id = 0
                    parent_group.group_name = self.choose_group_form.new_group_edit.text()
                    self.manager.save_object(parent_group)
                    self.fill_groups_combobox(self.choose_group_form.group_combobox)
                    self.fill_groups_combobox(self.add_group_form.parent_group_combobox)
        else:
            parent_group = target_group.data(0, QtCore.Qt.UserRole)
            if isinstance(parent_group, ImageFilenames):
                if parent_group.group_id == 0:
                    parent_group = ImageGroups()
                    parent_group.id = 0
                else:
                    parent_group = target_group.parent().data(0, QtCore.Qt.UserRole)
        # If no valid parent group is found, do nothing
        if not isinstance(parent_group, ImageGroups):
            return
        # Initialize busy cursor and progress bar
        self.application.set_busy_cursor()
        self.main_window.display_progress_bar(len(images))
        # Save the new images in the database
        self.save_new_images_list(images, group_id=parent_group.id, reload_list=False)
        self.load_full_list(self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.filename),
                            initial_load=initial_load, open_group=parent_group)
        self.application.set_normal_cursor()

    def save_new_images_list(self, images_list, group_id=0, reload_list=True):
        """
        Convert a list of image filenames to ImageFilenames objects and save them in the database.

        :param images_list: A List of strings containing image filenames
        :param group_id: The ID of the group to save the images in
        :param reload_list: This boolean is set to True when the list in the interface should be reloaded after saving
        the new images
        """
        for filename in images_list:
            if not isinstance(filename, str):
                continue
            log.debug('Adding new image: %s', filename)
            image_file = ImageFilenames()
            image_file.group_id = group_id
            image_file.filename = str(filename)
            self.manager.save_object(image_file)
            self.main_window.increment_progress_bar()
        if reload_list and images_list:
            self.load_full_list(self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.filename))

    def dnd_move_internal(self, target):
        """
        Handle drag-and-drop moving of images within the media manager

        :param target: This contains the QTreeWidget that is the target of the DnD action
        """
        items_to_move = self.list_view.selectedItems()
        # Determine group to move images to
        target_group = target
        if target_group is not None and isinstance(target_group.data(0, QtCore.Qt.UserRole), ImageFilenames):
            target_group = target.parent()
        # Move to toplevel
        if target_group is None:
            target_group = self.list_view.invisibleRootItem()
            target_group.setData(0, QtCore.Qt.UserRole, ImageGroups())
            target_group.data(0, QtCore.Qt.UserRole).id = 0
        # Move images in the treeview
        items_to_save = []
        for item in items_to_move:
            if isinstance(item.data(0, QtCore.Qt.UserRole), ImageFilenames):
                if isinstance(item.parent(), QtGui.QTreeWidgetItem):
                    item.parent().removeChild(item)
                else:
                    self.list_view.invisibleRootItem().removeChild(item)
                target_group.addChild(item)
                item.setSelected(True)
                item_data = item.data(0, QtCore.Qt.UserRole)
                item_data.group_id = target_group.data(0, QtCore.Qt.UserRole).id
                items_to_save.append(item_data)
        target_group.setExpanded(True)
        # Update the group ID's of the images in the database
        self.manager.save_objects(items_to_save)
        # Sort the target group
        group_items = []
        image_items = []
        for item in target_group.takeChildren():
            if isinstance(item.data(0, QtCore.Qt.UserRole), ImageGroups):
                group_items.append(item)
            if isinstance(item.data(0, QtCore.Qt.UserRole), ImageFilenames):
                image_items.append(item)
        group_items.sort(key=lambda item: get_locale_key(item.text(0)))
        target_group.addChildren(group_items)
        image_items.sort(key=lambda item: get_locale_key(item.text(0)))
        target_group.addChildren(image_items)

    def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False,
                            context=ServiceItemContext.Service):
        """
        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
        """
        background = QtGui.QColor(Settings().value(self.settings_section + '/background color'))
        if item:
            items = [item]
        else:
            items = self.list_view.selectedItems()
            if not items:
                return False
        # Determine service item title
        if isinstance(items[0].data(0, QtCore.Qt.UserRole), ImageGroups) or len(items) == 1:
            service_item.title = items[0].text(0)
        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)
        # force a nonexistent theme
        service_item.theme = -1
        missing_items_file_names = []
        images = []
        # Expand groups to images
        for bitem in items:
            if isinstance(bitem.data(0, QtCore.Qt.UserRole), ImageGroups) 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), ImageFilenames):
                        images.append(bitem.child(index).data(0, QtCore.Qt.UserRole))
            elif isinstance(bitem.data(0, QtCore.Qt.UserRole), ImageFilenames):
                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 os.path.exists(image.filename):
                missing_items_file_names.append(image.filename)
        # We cannot continue, as all images do not exist.
        if not images:
            if not remote:
                critical_error_message_box(
                    translate('ImagePlugin.MediaItem', 'Missing Image(s)'),
                    translate('ImagePlugin.MediaItem', 'The following image(s) no longer exist: %s')
                    % '\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 QtGui.QMessageBox.question(
                self, translate('ImagePlugin.MediaItem', 'Missing Image(s)'),
                translate('ImagePlugin.MediaItem', 'The following image(s) no longer exist: %s\n'
                          'Do you want to add the other images anyway?') % '\n'.join(missing_items_file_names),
                QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.No | QtGui.QMessageBox.Yes)) == \
                QtGui.QMessageBox.No:
            return False
        # Continue with the existing images.
        for image in images:
            name = os.path.split(image.filename)[1]
            thumbnail = self.generate_thumbnail_path(image)
            service_item.add_from_image(image.filename, name, background, thumbnail)
        return True

    def check_group_exists(self, new_group):
        """
        Returns *True* if the given Group already exists in the database, otherwise *False*.

        :param new_group: The ImageGroups object that contains the name of the group that will be checked
        """
        groups = self.manager.get_all_objects(ImageGroups, ImageGroups.group_name == new_group.group_name)
        if groups:
            return True
        else:
            return False

    def on_add_group_click(self):
        """
        Called to add a new group
        """
        # Find out if a group must be pre-selected
        preselect_group = 0
        selected_items = self.list_view.selectedItems()
        if selected_items:
            selected_item = selected_items[0]
            if isinstance(selected_item.data(0, QtCore.Qt.UserRole), ImageFilenames):
                selected_item = selected_item.parent()
            if isinstance(selected_item, QtGui.QTreeWidgetItem):
                if isinstance(selected_item.data(0, QtCore.Qt.UserRole), ImageGroups):
                    preselect_group = selected_item.data(0, QtCore.Qt.UserRole).id
        # Show 'add group' dialog
        if self.add_group_form.exec_(show_top_level_group=True, selected_group=preselect_group):
            new_group = ImageGroups.populate(parent_id=self.add_group_form.parent_group_combobox.itemData(
                self.add_group_form.parent_group_combobox.currentIndex(), QtCore.Qt.UserRole),
                group_name=self.add_group_form.name_edit.text())
            if not self.check_group_exists(new_group):
                if self.manager.save_object(new_group):
                    self.load_full_list(self.manager.get_all_objects(
                        ImageFilenames, order_by_ref=ImageFilenames.filename))
                    self.expand_group(new_group.id)
                    self.fill_groups_combobox(self.choose_group_form.group_combobox)
                    self.fill_groups_combobox(self.add_group_form.parent_group_combobox)
                else:
                    critical_error_message_box(
                        message=translate('ImagePlugin.AddGroupForm', 'Could not add the new group.'))
            else:
                critical_error_message_box(message=translate('ImagePlugin.AddGroupForm', 'This group already exists.'))

    def on_reset_click(self):
        """
        Called to reset the Live background with the image selected.
        """
        self.reset_action.setVisible(False)
        self.live_controller.display.reset_image()

    def live_theme_changed(self):
        """
        Triggered by the change of theme in the slide controller.
        """
        self.reset_action.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.')):
            background = QtGui.QColor(Settings().value(self.settings_section + '/background color'))
            bitem = self.list_view.selectedItems()[0]
            if not isinstance(bitem.data(0, QtCore.Qt.UserRole), ImageFilenames):
                # Only continue when an image is selected.
                return
            filename = bitem.data(0, QtCore.Qt.UserRole).filename
            if os.path.exists(filename):
                if self.live_controller.display.direct_image(filename, background):
                    self.reset_action.setVisible(True)
                else:
                    critical_error_message_box(
                        UiStrings().LiveBGError,
                        translate('ImagePlugin.MediaItem', 'There was no display item to amend.'))
            else:
                critical_error_message_box(
                    UiStrings().LiveBGError,
                    translate('ImagePlugin.MediaItem', 'There was a problem replacing your background, '
                              'the image file "%s" no longer exists.') % filename)

    def search(self, string, show_error=True):
        """
        Perform a search on the image file names.

        :param string: The glob to search for
        :param show_error: Unused.
        """
        files = self.manager.get_all_objects(
            ImageFilenames, filter_clause=ImageFilenames.filename.contains(string),
            order_by_ref=ImageFilenames.filename)
        results = []
        for file_object in files:
            filename = os.path.split(str(file_object.filename))[1]
            results.append([file_object.filename, filename])
        return results

    def create_item_from_id(self, item_id):
        """
        Create a media item from an item id. Overridden from the parent method to change the item type.

        :param item_id: Id to make live
        """
        item = QtGui.QTreeWidgetItem()
        item_data = self.manager.get_object_filtered(ImageFilenames, ImageFilenames.filename == item_id)
        item.setText(0, os.path.basename(item_data.filename))
        item.setData(0, QtCore.Qt.UserRole, item_data)
        return item