This branch implements the 'images groups' feature. It has the following features:

- convert old 'images files' setting to SQLite database
- existing images are loaded as top-level items to minimize the change for users
- groups can be nested
- images can be moved between groups by drag&drop
- new images can be added directly to a group by drag&drop, or by clicking the 'Add images' button
- image groups can be used as service items with multiple images

bzr-revno: 2214
This commit is contained in:
Arjan Schrijver 2013-03-19 09:38:01 +02:00 committed by Raoul Snyman
commit ce528140ae
28 changed files with 1493 additions and 93 deletions

View File

@ -377,6 +377,7 @@ from uistrings import UiStrings
from screen import ScreenList
from settings import Settings
from listwidgetwithdnd import ListWidgetWithDnD
from treewidgetwithdnd import TreeWidgetWithDnD
from formattingtags import FormattingTags
from spelltextedit import SpellTextEdit
from plugin import PluginStatus, StringContent, Plugin

View File

@ -311,16 +311,16 @@ class MediaManagerItem(QtGui.QWidget):
self.validateAndLoad(files)
self.application.set_normal_cursor()
def loadFile(self, files):
def loadFile(self, data):
"""
Turn file from Drag and Drop into an array so the Validate code can run it.
``files``
The list of files to be loaded
``data``
A dictionary containing the list of files to be loaded and the target
"""
new_files = []
error_shown = False
for file_name in files:
for file_name in data['files']:
file_type = file_name.split(u'.')[-1]
if file_type.lower() not in self.onNewFileMasks:
if not error_shown:
@ -330,15 +330,27 @@ class MediaManagerItem(QtGui.QWidget):
else:
new_files.append(file_name)
if new_files:
self.validateAndLoad(new_files)
self.validateAndLoad(new_files, data['target'])
def validateAndLoad(self, files):
def dnd_move_internal(self, target):
"""
Handle internal moving of media manager items
``target``
The target of the DnD action
"""
pass
def validateAndLoad(self, files, target_group=None):
"""
Process a list for files either from the File Dialog or from Drag and
Drop
``files``
The files to be loaded.
``target_group``
The QTreeWidgetItem of the group that will be the parent of the added files
"""
names = []
full_list = []
@ -347,16 +359,17 @@ class MediaManagerItem(QtGui.QWidget):
full_list.append(self.listView.item(count).data(QtCore.Qt.UserRole))
duplicates_found = False
files_added = False
for file in files:
filename = os.path.split(unicode(file))[1]
for file_path in files:
filename = os.path.split(unicode(file_path))[1]
if filename in names:
duplicates_found = True
else:
files_added = True
full_list.append(file)
full_list.append(filename)
if full_list and files_added:
self.listView.clear()
self.loadList(full_list)
if target_group is None:
self.listView.clear()
self.loadList(full_list, target_group)
last_dir = os.path.split(unicode(files[0]))[0]
Settings().setValue(self.settingsSection + u'/last directory', last_dir)
Settings().setValue(u'%s/%s files' % (self.settingsSection, self.settingsSection), self.getFileList())
@ -387,7 +400,7 @@ class MediaManagerItem(QtGui.QWidget):
file_list.append(filename)
return file_list
def loadList(self, list):
def loadList(self, list, target_group):
"""
Load a list. Needs to be implemented by the plugin.
"""

View File

@ -216,6 +216,15 @@ class Plugin(QtCore.QObject):
if self.mediaItemClass:
self.mediaItem = self.mediaItemClass(self.main_window.media_dock_manager.media_dock, self)
def upgrade_settings(self, settings):
"""
Upgrade the settings of this plugin.
``settings``
The Settings object containing the old settings.
"""
pass
def addImportMenuItem(self, importMenu):
"""
Create a menu item and add it to the "Import" menu.
@ -300,24 +309,10 @@ class Plugin(QtCore.QObject):
# FIXME: Remove after 2.2 release.
# This is needed to load the list of images/media/presentation from the config saved
# before the settings rewrite.
if self.mediaItemClass is not None:
# We need QSettings instead of Settings here to bypass our central settings dict.
# Do NOT do this anywhere else!
settings = QtCore.QSettings()
settings.beginGroup(self.settingsSection)
if settings.contains(u'%s count' % self.name):
list_count = int(settings.value(u'%s count' % self.name, 0))
loaded_list = []
if list_count:
for counter in range(list_count):
item = settings.value(u'%s %d' % (self.name, counter), u'')
if item:
loaded_list.append(item)
settings.remove(u'%s %d' % (self.name, counter))
settings.remove(u'%s count' % self.name)
# Now save the list to the config using our Settings class.
Settings().setValue(u'%s/%s files' % (self.settingsSection, self.name), loaded_list)
settings.endGroup()
if self.mediaItemClass is not None and self.name != u'images':
loaded_list = Settings().get_files_from_config(self)
# Now save the list to the config using our Settings class.
Settings().setValue(u'%s/%s files' % (self.settingsSection, self.name), loaded_list)
def uses_theme(self, theme):
"""

View File

@ -183,6 +183,17 @@ class PluginManager(object):
if plugin.status is not PluginStatus.Disabled:
plugin.addToolsMenuItem(self.main_window.tools_menu)
def hook_upgrade_plugin_settings(self, settings):
"""
Loop through all the plugins and give them an opportunity to upgrade their settings.
``settings``
The Settings object containing the old settings.
"""
for plugin in self.plugins:
if plugin.status is not PluginStatus.Disabled:
plugin.upgrade_settings(settings)
def initialise_plugins(self):
"""
Loop through all the plugins and give them an opportunity to

View File

@ -439,3 +439,32 @@ class Settings(QtCore.QSettings):
if isinstance(default_value, int):
return int(setting)
return setting
def get_files_from_config(self, plugin):
"""
This removes the settings needed for old way we saved files (e. g. the image paths for the image plugin). A list
of file paths are returned.
**Note**: Only a list of paths is returned; this does not convert anything!
``plugin``
The Plugin object.The caller has to convert/save the list himself; o
"""
files_list = []
# We need QSettings instead of Settings here to bypass our central settings dict.
# Do NOT do this anywhere else!
settings = QtCore.QSettings(self.fileName(), Settings.IniFormat)
settings.beginGroup(plugin.settingsSection)
if settings.contains(u'%s count' % plugin.name):
# Get the count.
list_count = int(settings.value(u'%s count' % plugin.name, 0))
if list_count:
for counter in range(list_count):
# The keys were named e. g.: "image 0"
item = settings.value(u'%s %d' % (plugin.name, counter), u'')
if item:
files_list.append(item)
settings.remove(u'%s %d' % (plugin.name, counter))
settings.remove(u'%s count' % plugin.name)
settings.endGroup()
return files_list

View File

@ -0,0 +1,151 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2013 Raoul Snyman #
# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
Extend QTreeWidget to handle drag and drop functionality
"""
import os
from PyQt4 import QtCore, QtGui
from openlp.core.lib import Registry
class TreeWidgetWithDnD(QtGui.QTreeWidget):
"""
Provide a tree widget to store objects and handle drag and drop events
"""
def __init__(self, parent=None, name=u''):
"""
Initialise the tree widget
"""
QtGui.QTreeWidget.__init__(self, parent)
self.mimeDataText = name
self.allow_internal_dnd = False
self.header().close()
self.defaultIndentation = self.indentation()
self.setIndentation(0)
self.setAnimated(True)
assert(self.mimeDataText)
def activateDnD(self):
"""
Activate DnD of widget
"""
self.setAcceptDrops(True)
self.setDragDropMode(QtGui.QAbstractItemView.DragDrop)
Registry().register_function((u'%s_dnd' % self.mimeDataText), self.parent().loadFile)
Registry().register_function((u'%s_dnd_internal' % self.mimeDataText), self.parent().dnd_move_internal)
def mouseMoveEvent(self, event):
"""
Drag and drop event does not care what data is selected as the recipient will use events to request the data
move just tell it what plugin to call
``event``
The event that occurred
"""
if event.buttons() != QtCore.Qt.LeftButton:
event.ignore()
return
if not self.selectedItems():
event.ignore()
return
drag = QtGui.QDrag(self)
mimeData = QtCore.QMimeData()
drag.setMimeData(mimeData)
mimeData.setText(self.mimeDataText)
drag.start(QtCore.Qt.CopyAction)
def dragEnterEvent(self, event):
"""
Receive drag enter event, check if it is a file or internal object and allow it if it is.
``event``
The event that occurred
"""
if event.mimeData().hasUrls():
event.accept()
elif self.allow_internal_dnd:
event.accept()
else:
event.ignore()
def dragMoveEvent(self, event):
"""
Receive drag move event, check if it is a file or internal object and allow it if it is.
``event``
The event that occurred
"""
QtGui.QTreeWidget.dragMoveEvent(self, event)
if event.mimeData().hasUrls():
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
elif self.allow_internal_dnd:
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
else:
event.ignore()
def dropEvent(self, event):
"""
Receive drop event, check if it is a file or internal object and process it if it is.
``event``
Handle of the event pint passed
"""
if event.mimeData().hasUrls():
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
files = []
for url in event.mimeData().urls():
localFile = url.toLocalFile()
if os.path.isfile(localFile):
files.append(localFile)
elif os.path.isdir(localFile):
listing = os.listdir(localFile)
for file_name in listing:
files.append(os.path.join(localFile, file_name))
Registry().execute(u'%s_dnd' % self.mimeDataText, {'files': files, 'target': self.itemAt(event.pos())})
elif self.allow_internal_dnd:
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
Registry().execute(u'%s_dnd_internal' % self.mimeDataText, self.itemAt(event.pos()))
else:
event.ignore()
# Convenience methods for emulating a QListWidget. This helps keeping MediaManagerItem simple.
def addItem(self, item):
self.addTopLevelItem(item)
def count(self):
return self.topLevelItemCount()
def item(self, index):
return self.topLevelItem(index)

View File

@ -58,6 +58,7 @@ class UiStrings(object):
"""
self.About = translate('OpenLP.Ui', 'About')
self.Add = translate('OpenLP.Ui', '&Add')
self.AddGroup = translate('OpenLP.Ui', 'Add group')
self.Advanced = translate('OpenLP.Ui', 'Advanced')
self.AllFiles = translate('OpenLP.Ui', 'All Files')
self.Automatic = translate('OpenLP.Ui', 'Automatic')

View File

@ -686,7 +686,8 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
``message``
The message to be displayed.
"""
self.application.splash.close()
if hasattr(self.application, u'splash'):
self.application.splash.close()
QtGui.QMessageBox.critical(self, title, message)
def warning_message(self, title, message):
@ -699,7 +700,8 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
``message``
The message to be displayed.
"""
self.application.splash.close()
if hasattr(self.application, u'splash'):
self.application.splash.close()
QtGui.QMessageBox.warning(self, title, message)
def information_message(self, title, message):
@ -712,7 +714,8 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
``message``
The message to be displayed.
"""
self.application.splash.close()
if hasattr(self.application, u'splash'):
self.application.splash.close()
QtGui.QMessageBox.information(self, title, message)
def on_help_web_site_clicked(self):
@ -816,8 +819,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
setting_sections.extend([self.header_section])
setting_sections.extend([u'crashreport'])
# Add plugin sections.
for plugin in self.plugin_manager.plugins:
setting_sections.extend([plugin.name])
setting_sections.extend([plugin.name for plugin in self.plugin_manager.plugins])
# Copy the settings file to the tmp dir, because we do not want to change the original one.
temp_directory = os.path.join(unicode(gettempdir()), u'openlp')
check_directory_exists(temp_directory)
@ -825,11 +827,13 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
shutil.copyfile(import_file_name, temp_config)
settings = Settings()
import_settings = Settings(temp_config, Settings.IniFormat)
# Convert image files
log.info(u'hook upgrade_plugin_settings')
self.plugin_manager.hook_upgrade_plugin_settings(import_settings)
# Remove/rename old settings to prepare the import.
import_settings.remove_obsolete_settings()
# Lets do a basic sanity check. If it contains this string we can
# assume it was created by OpenLP and so we'll load what we can
# from it, and just silently ignore anything we don't recognise
# Lets do a basic sanity check. If it contains this string we can assume it was created by OpenLP and so we'll
# load what we can from it, and just silently ignore anything we don't recognise.
if import_settings.value(u'SettingsImport/type') != u'OpenLP_settings_export':
QtGui.QMessageBox.critical(self, translate('OpenLP.MainWindow', 'Import settings'),
translate('OpenLP.MainWindow', 'The file you have selected does not appear to be a valid OpenLP '
@ -864,9 +868,8 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
settings.setValue(u'file_date_imported', now.strftime("%Y-%m-%d %H:%M"))
settings.endGroup()
settings.sync()
# We must do an immediate restart or current configuration will
# overwrite what was just imported when application terminates
# normally. We need to exit without saving configuration.
# We must do an immediate restart or current configuration will overwrite what was just imported when
# application terminates normally. We need to exit without saving configuration.
QtGui.QMessageBox.information(self, translate('OpenLP.MainWindow', 'Import settings'),
translate('OpenLP.MainWindow', 'OpenLP will now close. Imported settings will '
'be applied the next time you start OpenLP.'),

View File

@ -99,7 +99,7 @@ class CustomMediaItem(MediaManagerItem):
self.loadList(self.manager.get_all_objects(CustomSlide, order_by_ref=CustomSlide.title))
self.config_update()
def loadList(self, custom_slides):
def loadList(self, custom_slides, target_group=None):
# Sort out what custom we want to select after loading the list.
self.saveAutoSelectId()
self.listView.clear()

View File

@ -0,0 +1,57 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2013 Raoul Snyman #
# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
Forms in OpenLP are made up of two classes. One class holds all the graphical
elements, like buttons and lists, and the other class holds all the functional
code, like slots and loading and saving.
The first class, commonly known as the **Dialog** class, is typically named
``Ui_<name>Dialog``. It is a slightly modified version of the class that the
``pyuic4`` command produces from Qt4's .ui file. Typical modifications will be
converting most strings from "" to u'' and using OpenLP's ``translate()``
function for translating strings.
The second class, commonly known as the **Form** class, is typically named
``<name>Form``. This class is the one which is instantiated and used. It uses
dual inheritance to inherit from (usually) QtGui.QDialog and the Ui class
mentioned above, like so::
class AuthorsForm(QtGui.QDialog, Ui_AuthorsDialog):
def __init__(self, parent=None):
QtGui.QDialog.__init__(self, parent)
self.setupUi(self)
This allows OpenLP to use ``self.object`` for all the GUI elements while keeping
them separate from the functionality, so that it is easier to recreate the GUI
from the .ui files later if necessary.
"""
from addgroupform import AddGroupForm
from choosegroupform import ChooseGroupForm

View File

@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2013 Raoul Snyman #
# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
from PyQt4 import QtGui
from openlp.core.lib import translate
from openlp.core.lib.ui import create_button_box
class Ui_AddGroupDialog(object):
def setupUi(self, add_group_dialog):
add_group_dialog.setObjectName(u'add_group_dialog')
add_group_dialog.resize(300, 10)
self.dialog_layout = QtGui.QVBoxLayout(add_group_dialog)
self.dialog_layout.setObjectName(u'dialog_layout')
self.name_layout = QtGui.QFormLayout()
self.name_layout.setObjectName(u'name_layout')
self.parent_group_label = QtGui.QLabel(add_group_dialog)
self.parent_group_label.setObjectName(u'parent_group_label')
self.parent_group_combobox = QtGui.QComboBox(add_group_dialog)
self.parent_group_combobox.setObjectName(u'parent_group_combobox')
self.name_layout.addRow(self.parent_group_label, self.parent_group_combobox)
self.name_label = QtGui.QLabel(add_group_dialog)
self.name_label.setObjectName(u'name_label')
self.name_edit = QtGui.QLineEdit(add_group_dialog)
self.name_edit.setObjectName(u'name_edit')
self.name_label.setBuddy(self.name_edit)
self.name_layout.addRow(self.name_label, self.name_edit)
self.dialog_layout.addLayout(self.name_layout)
self.button_box = create_button_box(add_group_dialog, u'button_box', [u'cancel', u'save'])
self.dialog_layout.addWidget(self.button_box)
self.retranslateUi(add_group_dialog)
add_group_dialog.setMaximumHeight(add_group_dialog.sizeHint().height())
def retranslateUi(self, add_group_dialog):
add_group_dialog.setWindowTitle(translate('ImagePlugin.AddGroupForm', 'Add group'))
self.parent_group_label.setText(translate('ImagePlugin.AddGroupForm', 'Parent group:'))
self.name_label.setText(translate('ImagePlugin.AddGroupForm', 'Group name:'))

View File

@ -0,0 +1,83 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2013 Raoul Snyman #
# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
from PyQt4 import QtGui
from openlp.core.lib import translate
from openlp.core.lib.ui import critical_error_message_box
from openlp.plugins.images.forms.addgroupdialog import Ui_AddGroupDialog
class AddGroupForm(QtGui.QDialog, Ui_AddGroupDialog):
"""
This class implements the 'Add group' form for the Images plugin.
"""
def __init__(self, parent=None):
"""
Constructor
"""
QtGui.QDialog.__init__(self, parent)
self.setupUi(self)
def exec_(self, clear=True, show_top_level_group=False, selected_group=None):
"""
Show the form
``clear``
Set to False if the text input box should not be cleared when showing the dialog (default: True)
``show_top_level_group``
Set to True when "-- Top level group --" should be showed as first item (default: False)
``selected_group``
The ID of the group that should be selected by default when showing the dialog
"""
if clear:
self.name_edit.clear()
self.name_edit.setFocus()
if show_top_level_group and not self.parent_group_combobox.top_level_group_added:
self.parent_group_combobox.insertItem(0, translate('ImagePlugin.MediaItem', '-- Top-level group --'), 0)
self.parent_group_combobox.top_level_group_added = True
if selected_group is not None:
for i in range(self.parent_group_combobox.count()):
if self.parent_group_combobox.itemData(i) == selected_group:
self.parent_group_combobox.setCurrentIndex(i)
return QtGui.QDialog.exec_(self)
def accept(self):
"""
Override the accept() method from QDialog to make sure something is entered in the text input box
"""
if not self.name_edit.text():
critical_error_message_box(message=translate('ImagePlugin.AddGroupForm',
'You need to type in a group name.'))
self.name_edit.setFocus()
return False
else:
return QtGui.QDialog.accept(self)

View File

@ -0,0 +1,94 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2013 Raoul Snyman #
# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
from PyQt4 import QtCore, QtGui
from openlp.core.lib import translate
from openlp.core.lib.ui import create_button_box
class Ui_ChooseGroupDialog(object):
"""
The UI for the "Choose Image Group" form.
"""
def setupUi(self, choose_group_dialog):
"""
Set up the UI.
``choose_group_dialog``
The form object (not the class).
"""
choose_group_dialog.setObjectName(u'choose_group_dialog')
choose_group_dialog.resize(399, 119)
self.choose_group_layout = QtGui.QFormLayout(choose_group_dialog)
self.choose_group_layout.setFieldGrowthPolicy(QtGui.QFormLayout.ExpandingFieldsGrow)
self.choose_group_layout.setMargin(8)
self.choose_group_layout.setSpacing(8)
self.choose_group_layout.setLabelAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
self.choose_group_layout.setObjectName(u'choose_group_layout')
self.group_question_label = QtGui.QLabel(choose_group_dialog)
self.group_question_label.setWordWrap(True)
self.group_question_label.setObjectName(u'group_question_label')
self.choose_group_layout.setWidget(1, QtGui.QFormLayout.SpanningRole, self.group_question_label)
self.nogroup_radio_button = QtGui.QRadioButton(choose_group_dialog)
self.nogroup_radio_button.setChecked(True)
self.nogroup_radio_button.setObjectName(u'nogroup_radio_button')
self.choose_group_layout.setWidget(2, QtGui.QFormLayout.LabelRole, self.nogroup_radio_button)
self.existing_radio_button = QtGui.QRadioButton(choose_group_dialog)
self.existing_radio_button.setChecked(False)
self.existing_radio_button.setObjectName(u'existing_radio_button')
self.choose_group_layout.setWidget(3, QtGui.QFormLayout.LabelRole, self.existing_radio_button)
self.group_combobox = QtGui.QComboBox(choose_group_dialog)
self.group_combobox.setObjectName(u'group_combobox')
self.choose_group_layout.setWidget(3, QtGui.QFormLayout.FieldRole, self.group_combobox)
self.new_radio_button = QtGui.QRadioButton(choose_group_dialog)
self.new_radio_button.setChecked(False)
self.new_radio_button.setObjectName(u'new_radio_button')
self.choose_group_layout.setWidget(4, QtGui.QFormLayout.LabelRole, self.new_radio_button)
self.new_group_edit = QtGui.QLineEdit(choose_group_dialog)
self.new_group_edit.setObjectName(u'new_group_edit')
self.choose_group_layout.setWidget(4, QtGui.QFormLayout.FieldRole, self.new_group_edit)
self.group_button_box = create_button_box(choose_group_dialog, u'buttonBox', [u'ok'])
self.choose_group_layout.setWidget(5, QtGui.QFormLayout.FieldRole, self.group_button_box)
self.retranslateUi(choose_group_dialog)
QtCore.QMetaObject.connectSlotsByName(choose_group_dialog)
def retranslateUi(self, choose_group_dialog):
"""
Translate the UI on the fly.
``choose_group_dialog``
The form object (not the class).
"""
choose_group_dialog.setWindowTitle(translate('ImagePlugin.ChooseGroupForm', 'Select Image Group'))
self.group_question_label.setText(translate('ImagePlugin.ChooseGroupForm', 'Add images to group:'))
self.nogroup_radio_button.setText(translate('ImagePlugin.ChooseGroupForm', 'No group'))
self.existing_radio_button.setText(translate('ImagePlugin.ChooseGroupForm', 'Existing group'))
self.new_radio_button.setText(translate('ImagePlugin.ChooseGroupForm', 'New group'))

View File

@ -0,0 +1,57 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2013 Raoul Snyman #
# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
from PyQt4 import QtGui
from openlp.plugins.images.forms.choosegroupdialog import Ui_ChooseGroupDialog
class ChooseGroupForm(QtGui.QDialog, Ui_ChooseGroupDialog):
"""
This class implements the 'Choose group' form for the Images plugin.
"""
def __init__(self, parent=None):
"""
Constructor
"""
QtGui.QDialog.__init__(self, parent)
self.setupUi(self)
def exec_(self, selected_group=None):
"""
Show the form
``selected_group``
The ID of the group that should be selected by default when showing the dialog
"""
if selected_group is not None:
for i in range(self.group_combobox.count()):
if self.group_combobox.itemData(i) == selected_group:
self.group_combobox.setCurrentIndex(i)
return QtGui.QDialog.exec_(self)

View File

@ -32,13 +32,16 @@ from PyQt4 import QtGui
import logging
from openlp.core.lib import Plugin, StringContent, Registry, ImageSource, Settings, build_icon, translate
from openlp.core.lib.db import Manager
from openlp.plugins.images.lib import ImageMediaItem, ImageTab
from openlp.plugins.images.lib.db import init_schema, ImageFilenames
log = logging.getLogger(__name__)
__default_settings__ = {
u'images/images files': []
}
u'images/db type': u'sqlite',
u'images/background color': u'#000000',
}
class ImagePlugin(Plugin):
@ -46,6 +49,7 @@ class ImagePlugin(Plugin):
def __init__(self):
Plugin.__init__(self, u'images', __default_settings__, ImageMediaItem, ImageTab)
self.manager = Manager(u'images', init_schema)
self.weight = -7
self.iconPath = u':/plugins/plugin_images.png'
self.icon = build_icon(self.iconPath)
@ -64,6 +68,29 @@ class ImagePlugin(Plugin):
'provided by the theme.')
return about_text
def app_startup(self):
"""
Perform tasks on application startup
"""
Plugin.app_startup(self)
# Convert old settings-based image list to the database
files_from_config = Settings().get_files_from_config(self)
if files_from_config:
log.debug(u'Importing images list from old config: %s' % files_from_config)
self.mediaItem.save_new_images_list(files_from_config)
def upgrade_settings(self, settings):
"""
Upgrade the settings of this plugin.
``settings``
The Settings object containing the old settings.
"""
files_from_config = settings.get_files_from_config(self)
if files_from_config:
log.debug(u'Importing images list from old config: %s' % files_from_config)
self.mediaItem.save_new_images_list(files_from_config)
def set_plugin_text_strings(self):
"""
Called to define all translatable texts of the plugin
@ -74,8 +101,7 @@ class ImagePlugin(Plugin):
u'plural': translate('ImagePlugin', 'Images', 'name plural')
}
## Name for MediaDockManager, SettingsManager ##
self.textStrings[StringContent.VisibleName] = {u'title': translate('ImagePlugin', 'Images', 'container title')
}
self.textStrings[StringContent.VisibleName] = {u'title': translate('ImagePlugin', 'Images', 'container title')}
# Middle Header Bar
tooltips = {
u'load': translate('ImagePlugin', 'Load a new image.'),

View File

@ -0,0 +1,99 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2013 Raoul Snyman #
# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
The :mod:`db` module provides the database and schema that is the backend for the Images plugin
"""
from sqlalchemy import Column, ForeignKey, Table, types
from sqlalchemy.orm import mapper, relation, reconstructor
from openlp.core.lib.db import BaseModel, init_db
class ImageGroups(BaseModel):
"""
ImageGroups model
"""
pass
class ImageFilenames(BaseModel):
"""
ImageFilenames model
"""
pass
def init_schema(url):
"""
Setup the images database connection and initialise the database schema.
``url``
The database to setup
The images database contains the following tables:
* image_groups
* image_filenames
**image_groups Table**
This table holds the names of the images groups. It has the following columns:
* id
* parent_id
* group_name
**image_filenames Table**
This table holds the filenames of the images and the group they belong to. It has the following columns:
* id
* group_id
* filename
"""
session, metadata = init_db(url)
# Definition of the "image_groups" table
image_groups_table = Table(u'image_groups', metadata,
Column(u'id', types.Integer(), primary_key=True),
Column(u'parent_id', types.Integer()),
Column(u'group_name', types.Unicode(128))
)
# Definition of the "image_filenames" table
image_filenames_table = Table(u'image_filenames', metadata,
Column(u'id', types.Integer(), primary_key=True),
Column(u'group_id', types.Integer(), ForeignKey(u'image_groups.id'), default=None),
Column(u'filename', types.Unicode(255), nullable=False)
)
mapper(ImageGroups, image_groups_table)
mapper(ImageFilenames, image_filenames_table)
metadata.create_all(checkfirst=True)
return session

View File

@ -32,6 +32,7 @@ from PyQt4 import QtGui
from openlp.core.lib import Registry, SettingsTab, Settings, UiStrings, translate
class ImageTab(SettingsTab):
"""
ImageTab is the images settings tab in the settings dialog.

View File

@ -32,13 +32,17 @@ import os
from PyQt4 import QtCore, QtGui
from openlp.core.lib import MediaManagerItem, ItemCapabilities, Registry, ServiceItemContext, Settings, UiStrings, \
build_icon, check_item_selected, check_directory_exists, create_thumb, translate, validate_thumb
from openlp.core.lib.ui import critical_error_message_box
from openlp.core.lib import ItemCapabilities, MediaManagerItem, Registry, ServiceItemContext, Settings, \
StringContent, TreeWidgetWithDnD, UiStrings, build_icon, check_directory_exists, check_item_selected, \
create_thumb, translate, validate_thumb
from openlp.core.lib.ui import create_widget_action, critical_error_message_box
from openlp.core.utils import AppLocation, delete_file, locale_compare, get_images_filter
from openlp.plugins.images.forms import AddGroupForm, ChooseGroupForm
from openlp.plugins.images.lib.db import ImageFilenames, ImageGroups
log = logging.getLogger(__name__)
class ImageMediaItem(MediaManagerItem):
"""
This is the custom media manager item for images.
@ -50,6 +54,11 @@ class ImageMediaItem(MediaManagerItem):
MediaManagerItem.__init__(self, parent, plugin)
self.quickPreviewAllowed = True
self.hasSearch = 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(u'live_theme_changed', self.live_theme_changed)
# Allow DnD from the desktop
self.listView.activateDnD()
@ -59,6 +68,8 @@ class ImageMediaItem(MediaManagerItem):
'Select Image(s)')
file_formats = get_images_filter()
self.onNewFileMasks = u'%s;;%s (*.*) (*)' % (file_formats, UiStrings().AllFiles)
self.addGroupAction.setText(UiStrings().AddGroup)
self.addGroupAction.setToolTip(UiStrings().AddGroup)
self.replaceAction.setText(UiStrings().ReplaceBG)
self.replaceAction.setToolTip(UiStrings().ReplaceLiveBG)
self.resetAction.setText(UiStrings().ResetBG)
@ -75,70 +86,444 @@ class ImageMediaItem(MediaManagerItem):
log.debug(u'initialise')
self.listView.clear()
self.listView.setIconSize(QtCore.QSize(88, 50))
self.listView.setIndentation(self.listView.defaultIndentation)
self.listView.allow_internal_dnd = True
self.servicePath = os.path.join(AppLocation.get_section_data_path(self.settingsSection), u'thumbnails')
check_directory_exists(self.servicePath)
self.loadList(Settings().value(self.settingsSection + u'/images files'), True)
# Load images from the database
self.loadFullList(
self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.filename), initial_load=True)
def addListViewToToolBar(self):
MediaManagerItem.addListViewToToolBar(self)
"""
Creates the main widget for listing items the media item is tracking.
This method overloads MediaManagerItem.addListViewToToolBar
"""
# Add the List widget
self.listView = TreeWidgetWithDnD(self, self.plugin.name)
self.listView.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
self.listView.setAlternatingRowColors(True)
self.listView.setObjectName(u'%sTreeView' % self.plugin.name)
# Add to pageLayout
self.pageLayout.addWidget(self.listView)
# define and add the context menu
self.listView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
if self.hasEditIcon:
create_widget_action(self.listView,
text=self.plugin.getString(StringContent.Edit)[u'title'],
icon=u':/general/general_edit.png',
triggers=self.onEditClick)
create_widget_action(self.listView, separator=True)
if self.hasDeleteIcon:
create_widget_action(self.listView,
text=self.plugin.getString(StringContent.Delete)[u'title'],
icon=u':/general/general_delete.png',
shortcuts=[QtCore.Qt.Key_Delete], triggers=self.onDeleteClick)
create_widget_action(self.listView, separator=True)
create_widget_action(self.listView,
text=self.plugin.getString(StringContent.Preview)[u'title'],
icon=u':/general/general_preview.png',
shortcuts=[QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return],
triggers=self.onPreviewClick)
create_widget_action(self.listView,
text=self.plugin.getString(StringContent.Live)[u'title'],
icon=u':/general/general_live.png',
shortcuts=[QtCore.Qt.ShiftModifier | QtCore.Qt.Key_Enter,
QtCore.Qt.ShiftModifier | QtCore.Qt.Key_Return],
triggers=self.onLiveClick)
create_widget_action(self.listView,
text=self.plugin.getString(StringContent.Service)[u'title'],
icon=u':/general/general_add.png',
shortcuts=[QtCore.Qt.Key_Plus, QtCore.Qt.Key_Equal],
triggers=self.onAddClick)
if self.addToServiceItem:
create_widget_action(self.listView, separator=True)
create_widget_action(self.listView,
text=translate('OpenLP.MediaManagerItem', '&Add to selected Service Item'),
icon=u':/general/general_add.png',
triggers=self.onAddEditClick)
self.addCustomContextActions()
# Create the context menu and add all actions from the listView.
self.menu = QtGui.QMenu()
self.menu.addActions(self.listView.actions())
self.listView.doubleClicked.connect(self.onDoubleClicked)
self.listView.itemSelectionChanged.connect(self.onSelectionChange)
self.listView.customContextMenuRequested.connect(self.contextMenu)
self.listView.addAction(self.replaceAction)
def addCustomContextActions(self):
"""
Add custom actions to the context menu
"""
create_widget_action(self.listView, separator=True)
create_widget_action(self.listView,
text=UiStrings().AddGroup,
icon=u':/images/image_new_group.png',
triggers=self.onAddGroupClick)
create_widget_action(self.listView,
text=self.plugin.getString(StringContent.Load)[u'tooltip'],
icon=u':/general/general_open.png',
triggers=self.onFileClick)
def addStartHeaderBar(self):
"""
Add custom buttons to the start of the toolbar
"""
self.addGroupAction = self.toolbar.add_toolbar_action(u'addGroupAction',
icon=u':/images/image_new_group.png', triggers=self.onAddGroupClick)
def addEndHeaderBar(self):
"""
Add custom buttons to the end of the toolbar
"""
self.replaceAction = self.toolbar.add_toolbar_action(u'replaceAction',
icon=u':/slides/slide_blank.png', triggers=self.onReplaceClick)
self.resetAction = self.toolbar.add_toolbar_action(u'resetAction',
icon=u':/system/system_close.png', visible=False, triggers=self.onResetClick)
def recursively_delete_group(self, image_group):
"""
Recursively deletes a group and all groups and images in it
``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.servicePath, os.path.split(image.filename)[1]))
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 onDeleteClick(self):
"""
Remove an image item from the list
"""
# Turn off auto preview triggers.
self.listView.blockSignals(True)
if check_item_selected(self.listView, translate('ImagePlugin.MediaItem','You must select an image to delete.')):
row_list = [item.row() for item in self.listView.selectedIndexes()]
row_list.sort(reverse=True)
if check_item_selected(self.listView, translate('ImagePlugin.MediaItem',
'You must select an image or group to delete.')):
item_list = self.listView.selectedItems()
self.application.set_busy_cursor()
self.main_window.display_progress_bar(len(row_list))
for row in row_list:
text = self.listView.item(row)
if text:
delete_file(os.path.join(self.servicePath, text.text()))
self.listView.takeItem(row)
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.servicePath, row_item.text(0)))
if item_data.group_id == 0:
self.listView.takeTopLevelItem(self.listView.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.listView.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.listView.takeTopLevelItem(self.listView.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()
Settings.setValue(self.settingsSection + u'/images files', self.getFileList())
self.main_window.finished_progress_bar()
self.application.set_normal_cursor()
self.listView.blockSignals(False)
def loadList(self, images, initialLoad=False):
self.application.set_busy_cursor()
if not initialLoad:
def add_sub_groups(self, group_list, parent_group_id):
"""
Recursively add subgroups to the given parent group in a QTreeWidget
``group_list``
The List object that contains all QTreeWidgetItems
``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(cmp=locale_compare, key=lambda group_object: group_object.group_name)
folder_icon = build_icon(u':/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.listView.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
``combobox``
The QComboBox to add the options to
``parent_group_id``
The ID of the group that will be added
``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(cmp=locale_compare, key=lambda group_object: 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
``group_id``
The ID of the group that will be expanded
``root_item``
This option is only used for recursion purposes
"""
return_value = False
if root_item is None:
root_item = self.listView.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 loadFullList(self, images, initial_load=False, open_group=None):
"""
Replace the list of images and groups in the interface.
``images``
A List of ImageFilenames objects that will be used to reload the mediamanager list
``initial_load``
When set to False, the busy cursor and progressbar will be shown while loading images
``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.listView.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(cmp=locale_compare, key=lambda filename: os.path.split(unicode(filename))[1])
images.sort(cmp=locale_compare, key=lambda image_object: os.path.split(unicode(image_object.filename))[1])
for imageFile in images:
filename = os.path.split(unicode(imageFile))[1]
log.debug(u'Loading image: %s', imageFile.filename)
filename = os.path.split(imageFile.filename)[1]
thumb = os.path.join(self.servicePath, filename)
if not os.path.exists(unicode(imageFile)):
if not os.path.exists(imageFile.filename):
icon = build_icon(u':/general/general_delete.png')
else:
if validate_thumb(unicode(imageFile), thumb):
if validate_thumb(imageFile.filename, thumb):
icon = build_icon(thumb)
else:
icon = create_thumb(unicode(imageFile), thumb)
item_name = QtGui.QListWidgetItem(filename)
item_name.setIcon(icon)
item_name.setToolTip(imageFile)
item_name.setData(QtCore.Qt.UserRole, imageFile)
self.listView.addItem(item_name)
if not initialLoad:
icon = create_thumb(imageFile.filename, thumb)
item_name = QtGui.QTreeWidgetItem(filename)
item_name.setText(0, filename)
item_name.setIcon(0, icon)
item_name.setToolTip(0, imageFile.filename)
item_name.setData(0, QtCore.Qt.UserRole, imageFile)
if imageFile.group_id == 0:
self.listView.addTopLevelItem(item_name)
else:
group_items[imageFile.group_id].addChild(item_name)
if not initial_load:
self.main_window.increment_progress_bar()
if not initialLoad:
if not initial_load:
self.main_window.finished_progress_bar()
self.application.set_normal_cursor()
def validateAndLoad(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.
``files``
A List of strings containing the filenames of the files to be loaded
``target_group``
The QTreeWidgetItem of the group that will be the parent of the added files
"""
self.application.set_normal_cursor()
self.loadList(files, target_group)
last_dir = os.path.split(unicode(files[0]))[0]
Settings().setValue(self.settingsSection + u'/last directory', last_dir)
def loadList(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.
``images``
A List of strings containing the filenames of the files to be loaded
``target_group``
The QTreeWidgetItem of the group that will be the parent of the added files
``initial_load``
When set to False, the busy cursor and progressbar will be shown while loading images
"""
if target_group is None:
# Find out if a group must be pre-selected
preselect_group = None
selected_items = self.listView.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)
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.loadFullList(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.
``images_list``
A List of strings containing image filenames
``group_id``
The ID of the group to save the images in
``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 type(filename) is not str and type(filename) is not unicode:
continue
log.debug(u'Adding new image: %s', filename)
imageFile = ImageFilenames()
imageFile.group_id = group_id
imageFile.filename = unicode(filename)
self.manager.save_object(imageFile)
self.main_window.increment_progress_bar()
if reload_list and images_list:
self.loadFullList(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
``target``
This contains the QTreeWidget that is the target of the DnD action
"""
items_to_move = self.listView.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.listView.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.listView.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(cmp=locale_compare, key=lambda item: item.text(0))
target_group.addChildren(group_items)
image_items.sort(cmp=locale_compare, key=lambda item: item.text(0))
target_group.addChildren(image_items)
def generateSlideData(self, service_item, item=None, xmlVersion=False,
remote=False, context=ServiceItemContext.Service):
background = QtGui.QColor(Settings().value(self.settingsSection + u'/background color'))
@ -148,7 +533,11 @@ class ImageMediaItem(MediaManagerItem):
items = self.listView.selectedItems()
if not items:
return False
service_item.title = unicode(self.plugin.nameStrings[u'plural'])
# Determine service item title
if isinstance(items[0].data(0, QtCore.Qt.UserRole), ImageGroups):
service_item.title = items[0].text(0)
else:
service_item.title = unicode(self.plugin.nameStrings[u'plural'])
service_item.add_capability(ItemCapabilities.CanMaintain)
service_item.add_capability(ItemCapabilities.CanPreview)
service_item.add_capability(ItemCapabilities.CanLoop)
@ -157,8 +546,19 @@ class ImageMediaItem(MediaManagerItem):
service_item.theme = -1
missing_items = []
missing_items_filenames = []
# Expand groups to images
for bitem in items:
filename = bitem.data(QtCore.Qt.UserRole)
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):
items.append(bitem.child(index))
items.remove(bitem)
# Don't try to display empty groups
if not items:
return False
# Find missing files
for bitem in items:
filename = bitem.data(0, QtCore.Qt.UserRole).filename
if not os.path.exists(filename):
missing_items.append(bitem)
missing_items_filenames.append(filename)
@ -181,11 +581,57 @@ class ImageMediaItem(MediaManagerItem):
return False
# Continue with the existing images.
for bitem in items:
filename = bitem.data(QtCore.Qt.UserRole)
filename = bitem.data(0, QtCore.Qt.UserRole).filename
name = os.path.split(filename)[1]
service_item.add_from_image(filename, name, background)
return True
def check_group_exists(self, new_group):
"""
Returns *True* if the given Group already exists in the database, otherwise *False*.
``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 onAddGroupClick(self):
"""
Called to add a new group
"""
# Find out if a group must be pre-selected
preselect_group = 0
selected_items = self.listView.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.loadFullList(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 onResetClick(self):
"""
Called to reset the Live background with the image selected,
@ -206,9 +652,11 @@ class ImageMediaItem(MediaManagerItem):
if check_item_selected(self.listView,
translate('ImagePlugin.MediaItem', 'You must select an image to replace the background with.')):
background = QtGui.QColor(Settings().value(self.settingsSection + u'/background color'))
item = self.listView.selectedIndexes()[0]
bitem = self.listView.item(item.row())
filename = bitem.data(QtCore.Qt.UserRole)
bitem = self.listView.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.resetAction.setVisible(True)
@ -221,11 +669,10 @@ class ImageMediaItem(MediaManagerItem):
'the image file "%s" no longer exists.') % filename)
def search(self, string, showError):
files = Settings().value(self.settingsSection + u'/images files')
files = self.manager.get_all_objects(ImageFilenames, filter_clause=ImageFilenames.filename.contains(string),
order_by_ref=ImageFilenames.filename)
results = []
string = string.lower()
for file in files:
filename = os.path.split(unicode(file))[1]
if filename.lower().find(string) > -1:
results.append([file, filename])
for file_object in files:
filename = os.path.split(unicode(file_object.filename))[1]
results.append([file_object.filename, filename])
return results

View File

@ -252,7 +252,7 @@ class MediaMediaItem(MediaManagerItem):
self.listView.takeItem(row)
Settings().setValue(self.settingsSection + u'/media files', self.getFileList())
def loadList(self, media):
def loadList(self, media, target_group=None):
# Sort the media by its filename considering language specific
# characters.
media.sort(cmp=locale_compare, key=lambda filename: os.path.split(unicode(filename))[1])

View File

@ -120,7 +120,7 @@ class PresentationMediaItem(MediaManagerItem):
"""
self.listView.setIconSize(QtCore.QSize(88, 50))
files = Settings().value(self.settingsSection + u'/presentations files')
self.loadList(files, True)
self.loadList(files, initialLoad=True)
self.populate_display_types()
def populate_display_types(self):
@ -141,7 +141,7 @@ class PresentationMediaItem(MediaManagerItem):
else:
self.presentationWidget.hide()
def loadList(self, files, initialLoad=False):
def loadList(self, files, target_group=None, initialLoad=False):
"""
Add presentations into the media manager
This is called both on initial load of the plugin to populate with

View File

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AddGroupDialog</class>
<widget class="QDialog" name="AddGroupDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>365</width>
<height>119</height>
</rect>
</property>
<property name="windowTitle">
<string>Add group</string>
</property>
<layout class="QFormLayout" name="AddGroupLayout">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::ExpandingFieldsGrow</enum>
</property>
<property name="horizontalSpacing">
<number>8</number>
</property>
<property name="verticalSpacing">
<number>8</number>
</property>
<property name="margin">
<number>8</number>
</property>
<item row="1" column="0">
<widget class="QLabel" name="ParentGroupLabel">
<property name="text">
<string>Parent group:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="ParentGroupComboBox"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="GroupNameLabel">
<property name="text">
<string>Group name:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="GroupNameEdit"/>
</item>
<item row="3" column="1">
<widget class="QDialogButtonBox" name="GroupButtonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Save</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="../images/openlp-2.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ChooseGroupDialog</class>
<widget class="QDialog" name="ChooseGroupDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>440</width>
<height>119</height>
</rect>
</property>
<property name="windowTitle">
<string>Choose group</string>
</property>
<layout class="QFormLayout" name="addGroupLayout">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::ExpandingFieldsGrow</enum>
</property>
<property name="horizontalSpacing">
<number>8</number>
</property>
<property name="verticalSpacing">
<number>8</number>
</property>
<property name="margin">
<number>8</number>
</property>
<item row="1" column="0" colspan="2">
<widget class="QLabel" name="groupQuestionLabel">
<property name="text">
<string>To which group do you want these images to be added?</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="groupComboBox"/>
</item>
<item row="3" column="1">
<widget class="QDialogButtonBox" name="groupButtonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Save</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="../images/openlp-2.qrc"/>
</resources>
<connections/>
</ui>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 B

View File

@ -21,6 +21,10 @@
<file>song_topic_edit.png</file>
<file>song_book_edit.png</file>
</qresource>
<qresource prefix="images">
<file>image_group.png</file>
<file>image_new_group.png</file>
</qresource>
<qresource prefix="bibles">
<file>bibles_search_text.png</file>
<file>bibles_search_reference.png</file>

View File

@ -6,7 +6,7 @@ from unittest import TestCase
from mock import MagicMock
from openlp.core.lib.pluginmanager import PluginManager
from openlp.core.lib import Registry, PluginStatus
from openlp.core.lib import Settings, Registry, PluginStatus
class TestPluginManager(TestCase):
@ -184,7 +184,7 @@ class TestPluginManager(TestCase):
# WHEN: We run hook_export_menu()
plugin_manager.hook_export_menu()
# THEN: The addExportMenuItem() method should have been called
# THEN: The addExportMenuItem() method should not have been called
assert mocked_plugin.addExportMenuItem.call_count == 0, \
u'The addExportMenuItem() method should not have been called.'
@ -204,6 +204,41 @@ class TestPluginManager(TestCase):
# THEN: The addExportMenuItem() method should have been called
mocked_plugin.addExportMenuItem.assert_called_with(self.mocked_main_window.file_export_menu)
def hook_upgrade_plugin_settings_with_disabled_plugin_test(self):
"""
Test running the hook_upgrade_plugin_settings() method with a disabled plugin
"""
# GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Disabled
mocked_plugin = MagicMock()
mocked_plugin.status = PluginStatus.Disabled
plugin_manager = PluginManager()
plugin_manager.plugins = [mocked_plugin]
settings = Settings()
# WHEN: We run hook_upgrade_plugin_settings()
plugin_manager.hook_upgrade_plugin_settings(settings)
# THEN: The upgrade_settings() method should not have been called
assert mocked_plugin.upgrade_settings.call_count == 0, \
u'The upgrade_settings() method should not have been called.'
def hook_upgrade_plugin_settings_with_active_plugin_test(self):
"""
Test running the hook_upgrade_plugin_settings() method with an active plugin
"""
# GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Active
mocked_plugin = MagicMock()
mocked_plugin.status = PluginStatus.Active
plugin_manager = PluginManager()
plugin_manager.plugins = [mocked_plugin]
settings = Settings()
# WHEN: We run hook_upgrade_plugin_settings()
plugin_manager.hook_upgrade_plugin_settings(settings)
# THEN: The addExportMenuItem() method should have been called
mocked_plugin.upgrade_settings.assert_called_with(settings)
def hook_tools_menu_with_disabled_plugin_test(self):
"""
Test running the hook_tools_menu() method with a disabled plugin

View File

@ -0,0 +1,112 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
"""
This module contains tests for the lib submodule of the Images plugin.
"""
from unittest import TestCase
from mock import MagicMock, patch
from openlp.core.lib import Registry
from openlp.plugins.images.lib.db import ImageFilenames
from openlp.plugins.images.lib.mediaitem import ImageMediaItem
class TestImageMediaItem(TestCase):
"""
This is a test case to test various methods in the ImageMediaItem class.
"""
def setUp(self):
self.mocked_main_window = MagicMock()
Registry.create()
Registry().register(u'service_list', MagicMock())
Registry().register(u'main_window', self.mocked_main_window)
mocked_parent = MagicMock()
mocked_plugin = MagicMock()
with patch(u'openlp.plugins.images.lib.mediaitem.ImageMediaItem.__init__') as mocked_init:
mocked_init.return_value = None
self.mediaitem = ImageMediaItem(mocked_parent, mocked_plugin)
def save_new_images_list_empty_list_test(self):
"""
Test that the save_new_images_list() method handles empty lists gracefully
"""
# GIVEN: An empty image_list
image_list = []
with patch(u'openlp.plugins.images.lib.mediaitem.ImageMediaItem.loadFullList') as mocked_loadFullList:
self.mediaitem.manager = MagicMock()
# WHEN: We run save_new_images_list with the empty list
self.mediaitem.save_new_images_list(image_list)
# THEN: The save_object() method should not have been called
assert self.mediaitem.manager.save_object.call_count == 0, \
u'The save_object() method should not have been called'
def save_new_images_list_single_image_with_reload_test(self):
"""
Test that the save_new_images_list() calls loadFullList() when reload_list is set to True
"""
# GIVEN: A list with 1 image
image_list = [ u'test_image.jpg' ]
with patch(u'openlp.plugins.images.lib.mediaitem.ImageMediaItem.loadFullList') as mocked_loadFullList:
ImageFilenames.filename = ''
self.mediaitem.manager = MagicMock()
# WHEN: We run save_new_images_list with reload_list=True
self.mediaitem.save_new_images_list(image_list, reload_list=True)
# THEN: loadFullList() should have been called
assert mocked_loadFullList.call_count == 1, u'loadFullList() should have been called'
# CLEANUP: Remove added attribute from ImageFilenames
delattr(ImageFilenames, 'filename')
def save_new_images_list_single_image_without_reload_test(self):
"""
Test that the save_new_images_list() doesn't call loadFullList() when reload_list is set to False
"""
# GIVEN: A list with 1 image
image_list = [ u'test_image.jpg' ]
with patch(u'openlp.plugins.images.lib.mediaitem.ImageMediaItem.loadFullList') as mocked_loadFullList:
self.mediaitem.manager = MagicMock()
# WHEN: We run save_new_images_list with reload_list=False
self.mediaitem.save_new_images_list(image_list, reload_list=False)
# THEN: loadFullList() should not have been called
assert mocked_loadFullList.call_count == 0, u'loadFullList() should not have been called'
def save_new_images_list_multiple_images_test(self):
"""
Test that the save_new_images_list() saves all images in the list
"""
# GIVEN: A list with 3 images
image_list = [ u'test_image_1.jpg', u'test_image_2.jpg', u'test_image_3.jpg' ]
with patch(u'openlp.plugins.images.lib.mediaitem.ImageMediaItem.loadFullList') as mocked_loadFullList:
self.mediaitem.manager = MagicMock()
# WHEN: We run save_new_images_list with the list of 3 images
self.mediaitem.save_new_images_list(image_list, reload_list=False)
# THEN: loadFullList() should not have been called
assert self.mediaitem.manager.save_object.call_count == 3, \
u'loadFullList() should have been called three times'
def save_new_images_list_other_objects_in_list_test(self):
"""
Test that the save_new_images_list() ignores everything in the provided list except strings
"""
# GIVEN: A list with images and objects
image_list = [ u'test_image_1.jpg', None, True, ImageFilenames(), 'test_image_2.jpg' ]
with patch(u'openlp.plugins.images.lib.mediaitem.ImageMediaItem.loadFullList') as mocked_loadFullList:
self.mediaitem.manager = MagicMock()
# WHEN: We run save_new_images_list with the list of images and objects
self.mediaitem.save_new_images_list(image_list, reload_list=False)
# THEN: loadFullList() should not have been called
assert self.mediaitem.manager.save_object.call_count == 2, \
u'loadFullList() should have been called only once'