← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~arjan-i/openlp/images_groups into lp:openlp

 

Arjan Schrijver has proposed merging lp:~arjan-i/openlp/images_groups into lp:openlp.

Requested reviews:
  OpenLP Core (openlp-core)

For more details, see:
https://code.launchpad.net/~arjan-i/openlp/images_groups/+merge/144658

I'm working on allowing images to be grouped in the media list. It's 
not ready, but I would like the core devs to take a look at it. The 
reason behind this request is that I want to know if I'm on the right 
track for getting it merged some time in the future.

The feedback I hope to get can be in all areas:
- functionality
- design
- code style
- generic thoughts
- bugs

Be warned that when you run this version, it will remove your list of 
images from the main configuration file and put it in an SQLite 
database. There is no rollback for that besides backing up your 
configuration before trying this branch.

Note: this is not a real merge proposal. It is meant for reviewing only.

This branch is now also rebased on the current trunk with the new settings management.
-- 
https://code.launchpad.net/~arjan-i/openlp/images_groups/+merge/144658
Your team OpenLP Core is requested to review the proposed merge of lp:~arjan-i/openlp/images_groups into lp:openlp.
=== modified file 'openlp/core/lib/__init__.py'
--- openlp/core/lib/__init__.py	2013-01-23 20:30:59 +0000
+++ openlp/core/lib/__init__.py	2013-01-24 09:36:22 +0000
@@ -390,7 +390,7 @@
 from eventreceiver import Receiver
 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 settingsmanager import SettingsManager

=== modified file 'openlp/core/lib/mediamanageritem.py'
--- openlp/core/lib/mediamanageritem.py	2013-01-18 18:50:46 +0000
+++ openlp/core/lib/mediamanageritem.py	2013-01-24 09:36:22 +0000
@@ -36,7 +36,7 @@
 from PyQt4 import QtCore, QtGui
 
 from openlp.core.lib import SettingsManager, OpenLPToolbar, ServiceItem, StringContent, build_icon, translate, \
-    Receiver, ListWidgetWithDnD, ServiceItemContext, Settings, UiStrings
+    Receiver, TreeWidgetWithDnD, ServiceItemContext, Settings, UiStrings
 from openlp.core.lib.searchedit import SearchEdit
 from openlp.core.lib.ui import create_widget_action, critical_error_message_box
 
@@ -212,8 +212,7 @@
         Creates the main widget for listing items the media item is tracking
         """
         # Add the List widget
-        self.listView = ListWidgetWithDnD(self, self.plugin.name)
-        self.listView.setSpacing(1)
+        self.listView = TreeWidgetWithDnD(self, self.plugin.name)
         self.listView.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
         self.listView.setAlternatingRowColors(True)
         self.listView.setObjectName(u'%sListView' % self.plugin.name)
@@ -366,9 +365,9 @@
         """
         names = []
         full_list = []
-        for count in range(self.listView.count()):
-            names.append(self.listView.item(count).text())
-            full_list.append(self.listView.item(count).data(QtCore.Qt.UserRole))
+        for count in range(self.listView.topLevelItemCount()):
+            names.append(self.listView.topLevelItem(count).text(0))
+            full_list.append(self.listView.topLevelItem(count).data(0, QtCore.Qt.UserRole))
         duplicates_found = False
         files_added = False
         for file in files:
@@ -383,7 +382,6 @@
             self.loadList(full_list)
             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())
         if duplicates_found:
             critical_error_message_box(UiStrings().Duplicate,
                 translate('OpenLP.MediaManagerItem', 'Duplicate files were found on import and were ignored.'))
@@ -403,9 +401,9 @@
         """
         count = 0
         file_list = []
-        while count < self.listView.count():
-            bitem = self.listView.item(count)
-            filename = bitem.data(QtCore.Qt.UserRole)
+        while count < self.listView.topLevelItemCount():
+            bitem = self.listView.topLevelItem(count)
+            filename = bitem.data(0, QtCore.Qt.UserRole)
             file_list.append(filename)
             count += 1
         return file_list
@@ -498,8 +496,8 @@
             self.plugin.liveController.addServiceItem(serviceItem)
 
     def createItemFromId(self, item_id):
-        item = QtGui.QListWidgetItem()
-        item.setData(QtCore.Qt.UserRole, item_id)
+        item = QtGui.QTreeWidgetItem()
+        item.setData(0, QtCore.Qt.UserRole, item_id)
         return item
 
     def onAddClick(self):
@@ -570,15 +568,16 @@
         """
         Checks if the listView is empty and adds a "No Search Results" item.
         """
-        if self.listView.count():
+        if self.listView.topLevelItemCount():
             return
         message = translate('OpenLP.MediaManagerItem', 'No Search Results')
-        item = QtGui.QListWidgetItem(message)
+        item = QtGui.QTreeWidgetItem(message)
+        item.setText(0, message)
         item.setFlags(QtCore.Qt.NoItemFlags)
         font = QtGui.QFont()
         font.setItalic(True)
-        item.setFont(font)
-        self.listView.addItem(item)
+        item.setFont(0, font)
+        self.listView.addTopLevelItem(item)
 
     def _getIdOfItemToGenerate(self, item, remoteItem):
         """
@@ -595,7 +594,7 @@
                 item = self.listView.currentItem()
                 if item is None:
                     return False
-                item_id = item.data(QtCore.Qt.UserRole)
+                item_id = item.data(0, QtCore.Qt.UserRole)
             else:
                 item_id = remoteItem
         else:
@@ -610,7 +609,7 @@
         if self.autoSelectId == -1:
             item = self.listView.currentItem()
             if item:
-                self.autoSelectId = item.data(QtCore.Qt.UserRole)
+                self.autoSelectId = item.data(0, QtCore.Qt.UserRole)
 
     def search(self, string, showError=True):
         """

=== added file 'openlp/core/lib/treewidgetwithdnd.py'
--- openlp/core/lib/treewidgetwithdnd.py	1970-01-01 00:00:00 +0000
+++ openlp/core/lib/treewidgetwithdnd.py	2013-01-24 09:36:22 +0000
@@ -0,0 +1,114 @@
+# -*- 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 Receiver
+
+class TreeWidgetWithDnD(QtGui.QTreeWidget):
+    """
+    Provide a list widget to store objects and handle drag and drop events
+    """
+    def __init__(self, parent=None, name=u''):
+        """
+        Initialise the list widget
+        """
+        QtGui.QTreeWidget.__init__(self, parent)
+        self.mimeDataText = name
+        self.header().close()
+        self.defaultIndentation = self.indentation()
+        self.setIndentation(0)
+        assert(self.mimeDataText)
+
+    def activateDnD(self):
+        """
+        Activate DnD of widget
+        """
+        self.setAcceptDrops(True)
+        self.setDragDropMode(QtGui.QAbstractItemView.DragDrop)
+        QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'%s_dnd' % self.mimeDataText),
+            self.parent().loadFile)
+
+    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
+        """
+        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):
+        if event.mimeData().hasUrls():
+            event.accept()
+        else:
+            event.ignore()
+
+    def dragMoveEvent(self, event):
+        if event.mimeData().hasUrls():
+            event.setDropAction(QtCore.Qt.CopyAction)
+            event.accept()
+        else:
+            event.ignore()
+
+    def dropEvent(self, event):
+        """
+        Receive drop event check if it is a file 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 in listing:
+                        files.append(os.path.join(localFile, file))
+            Receiver.send_message(u'%s_dnd' % self.mimeDataText, files)
+        else:
+            event.ignore()

=== modified file 'openlp/core/lib/uistrings.py'
--- openlp/core/lib/uistrings.py	2013-01-16 20:23:02 +0000
+++ openlp/core/lib/uistrings.py	2013-01-24 09:36:22 +0000
@@ -58,6 +58,7 @@
         """
         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')

=== modified file 'openlp/core/resources.py'
--- openlp/core/resources.py	2012-12-29 20:56:56 +0000
+++ openlp/core/resources.py	2013-01-24 09:36:22 +0000
@@ -54736,6 +54736,56 @@
 \x89\x3e\x62\x80\x70\x10\xc3\x84\x9d\xb0\x11\x66\xa2\x8b\xe8\x6c\
 \x7e\xaa\x0f\xd9\x9d\x89\x07\xa1\xb5\x4b\xa0\x00\x00\x00\x00\x49\
 \x45\x4e\x44\xae\x42\x60\x82\
+\x00\x00\x02\xfa\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\
+\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\
+\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\
+\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\x01\
+\x42\x28\x9b\x78\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdb\x05\x08\
+\x12\x0c\x27\xaf\x64\x6c\xaa\x00\x00\x02\x7a\x49\x44\x41\x54\x38\
+\xcb\x75\x93\x4b\x48\x94\x51\x14\xc7\x7f\xe7\x7e\xd7\xc9\xc7\x44\
+\x0f\xe8\x81\x15\x11\xd4\x2a\x2b\xe8\xe1\xae\xd2\x4a\x43\x82\x28\
+\x1a\x83\x08\x6a\xd5\xaa\x5d\x41\x24\x04\x6d\x02\x0b\x5a\x44\x11\
+\xb4\xab\x55\xf4\x58\x48\x60\x14\x49\x2e\x4c\xa4\xa0\x24\x7a\x40\
+\x0a\x16\xbd\x4c\x1c\x2b\xc7\x79\x98\xf3\x7d\xf7\x9e\x16\xdf\x8c\
+\x8c\x50\x77\x73\xe1\xc0\xf9\xfd\x7f\xe7\x70\x2f\xfc\xe7\xa4\xce\
+\x77\xcf\x2d\x5c\xe2\x50\x53\x77\xf3\x87\xa3\xcf\x8e\xba\xa6\xee\
+\xe6\x21\x2e\x71\x08\x40\x52\xe7\x1f\xbe\x70\x62\x1a\x51\x10\x00\
+\x15\x6c\x22\x78\x7f\xef\x5c\x4b\xc3\x6c\xf3\x55\x0e\xb4\x6c\x6d\
+\xe9\xda\xb5\x64\x8b\x9e\x5d\xdb\x29\x17\x47\x3a\xb4\x77\xfc\x95\
+\xf4\x0c\xf4\xa4\xa4\xed\xd4\x6d\x6d\xdd\xdb\x86\xba\x10\x63\x84\
+\x20\xb0\x3c\xee\xed\xd3\x44\x22\x21\x88\x61\x9e\x5b\xc0\xdd\x55\
+\xad\x34\xac\x4c\xea\xc2\xdc\xa8\xf4\xb5\x2b\x3b\xee\x0b\x93\xc9\
+\x7a\x7d\x3b\x36\x3a\x6c\x73\x99\x09\xa6\x0b\x53\xbc\x1e\x99\xc0\
+\x60\x58\xb3\xac\x86\x9d\x8d\x9b\xc4\x23\x28\x50\xab\x4b\xb8\x33\
+\x3c\xc5\x9b\x13\x19\x29\x0b\xf5\xb5\x2b\x80\xc8\x65\x59\x67\xa7\
+\x7e\x8d\x91\xc9\xe6\x89\xa6\xf3\x04\x06\x86\x3e\x67\x71\xa1\x23\
+\x9d\xfe\x46\x2e\x33\x49\x55\x34\x1f\xda\x40\x6e\x08\x75\xbf\x21\
+\xd7\xa1\x24\x3b\x85\xfc\x22\x60\x0d\xc3\x36\x33\x31\xca\x9b\xc1\
+\x17\x7c\xfc\x9a\x26\x30\x02\x08\x7f\xa6\xb3\xec\xdb\xbd\x99\x83\
+\xfb\x0f\x22\xde\xf0\x34\x6b\xb8\xf0\xe5\x18\xf9\x92\x41\x7e\x11\
+\xb0\x02\x52\xe6\x54\x5a\x52\x1d\x37\xf5\xf4\xc9\x23\x64\x0b\x33\
+\x18\x13\x5b\xaa\x0a\x33\x33\x45\x86\x47\x7e\x13\x46\x4a\x35\xb5\
+\x0c\x26\x1f\x70\xdb\x9f\x21\xfc\x91\x23\x51\x9f\xe4\x78\xcd\x35\
+\xd2\xb7\x3e\x61\x35\xa8\xa5\xff\xe5\x0f\xa2\x62\x31\x6e\x2e\xa5\
+\x18\x11\x34\x80\x2a\x81\x22\x05\x36\xe4\xdb\xb8\xa2\x87\xb1\x0b\
+\x12\x44\xf9\x19\xbc\x73\xf4\x98\xeb\x58\x55\xc1\x08\x50\x4a\x0f\
+\x00\x57\x06\x79\x28\x02\x16\x25\x24\x24\xe4\x57\x09\xaf\x54\xf9\
+\x3a\x50\xb0\xaa\x10\x79\xc5\x79\x45\x80\x08\x30\x80\x53\x10\x89\
+\xdf\x46\xb1\x04\x34\x65\xb0\x82\xf5\x4a\xa4\x8a\x55\x55\xbc\x53\
+\x22\x17\x03\xca\x63\x28\x71\xb3\xaf\x78\x8c\x5a\x31\x62\xe4\x3c\
+\xea\x15\x2b\x22\x18\x13\x10\x18\x3f\x3b\x42\x39\xcd\x97\x6e\xa9\
+\x80\xc5\x75\xc5\x04\x01\x06\xc1\x8e\xfd\xcc\xf0\xe8\xf9\x3b\xd4\
+\x47\x73\x92\x4c\x85\x85\xfe\xe3\xaf\xd8\x44\x0d\xdf\xbe\x8f\x63\
+\x57\x2f\x5f\xcc\xa6\x8d\xeb\x71\x51\x38\x9b\x54\xa9\x5a\x59\xf3\
+\x15\xe0\xc4\xbc\x1a\x06\xc6\x9f\x63\xa7\x0a\xc5\x27\xc5\xd0\xb7\
+\x46\xa1\x22\x02\x5e\xe3\x45\x56\x11\x2f\xb1\xac\x14\x96\x40\x56\
+\x00\x55\x1c\x8e\x42\xa8\x5d\xc2\xd2\x6d\x7b\x88\xc2\xed\x80\xa0\
+\x6a\x10\x04\x55\x99\x1d\x5f\x15\x44\x4a\x02\x12\xcb\x89\x78\x14\
+\xa5\xba\xb6\xff\x2f\x62\x10\x26\x9d\x60\xcb\xdf\xa1\x00\x00\x00\
+\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
 \x00\x00\x0a\x90\
 \x89\
 \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
@@ -71235,6 +71285,10 @@
 \x00\x69\
 \x00\x63\x00\x6f\x00\x6e\
 \x00\x06\
+\x07\x03\x7d\xc3\
+\x00\x69\
+\x00\x6d\x00\x61\x00\x67\x00\x65\x00\x73\
+\x00\x06\
 \x06\x8f\x92\xc3\
 \x00\x62\
 \x00\x69\x00\x62\x00\x6c\x00\x65\x00\x73\
@@ -71366,6 +71420,11 @@
 \x00\x62\
 \x00\x69\x00\x62\x00\x6c\x00\x65\x00\x73\x00\x5f\x00\x73\x00\x65\x00\x61\x00\x72\x00\x63\x00\x68\x00\x5f\x00\x75\x00\x6e\x00\x6c\
 \x00\x6f\x00\x63\x00\x6b\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x13\
+\x0f\x86\x85\x67\
+\x00\x69\
+\x00\x6d\x00\x61\x00\x67\x00\x65\x00\x5f\x00\x6e\x00\x65\x00\x77\x00\x5f\x00\x67\x00\x72\x00\x6f\x00\x75\x00\x70\x00\x2e\x00\x70\
+\x00\x6e\x00\x67\
 \x00\x15\
 \x00\x76\xd9\xc7\
 \x00\x6f\
@@ -71833,148 +71892,150 @@
 "
 
 qt_resource_struct = "\
-\x00\x00\x00\x00\x00\x02\x00\x00\x00\x12\x00\x00\x00\x01\
-\x00\x00\x00\xe6\x00\x02\x00\x00\x00\x06\x00\x00\x00\x88\
-\x00\x00\x00\x00\x00\x02\x00\x00\x00\x04\x00\x00\x00\x84\
-\x00\x00\x00\xb4\x00\x02\x00\x00\x00\x08\x00\x00\x00\x7c\
-\x00\x00\x00\xd6\x00\x02\x00\x00\x00\x14\x00\x00\x00\x68\
-\x00\x00\x00\x2c\x00\x02\x00\x00\x00\x02\x00\x00\x00\x66\
-\x00\x00\x00\x14\x00\x02\x00\x00\x00\x02\x00\x00\x00\x64\
-\x00\x00\x00\xf4\x00\x02\x00\x00\x00\x05\x00\x00\x00\x5f\
-\x00\x00\x01\x2e\x00\x02\x00\x00\x00\x09\x00\x00\x00\x56\
-\x00\x00\x00\x8c\x00\x02\x00\x00\x00\x0a\x00\x00\x00\x4c\
-\x00\x00\x00\xc4\x00\x02\x00\x00\x00\x03\x00\x00\x00\x49\
-\x00\x00\x00\x3c\x00\x02\x00\x00\x00\x10\x00\x00\x00\x39\
-\x00\x00\x00\x62\x00\x02\x00\x00\x00\x03\x00\x00\x00\x36\
-\x00\x00\x00\x78\x00\x02\x00\x00\x00\x01\x00\x00\x00\x35\
-\x00\x00\x01\x42\x00\x02\x00\x00\x00\x03\x00\x00\x00\x32\
-\x00\x00\x00\x9e\x00\x02\x00\x00\x00\x08\x00\x00\x00\x2a\
-\x00\x00\x01\x1a\x00\x02\x00\x00\x00\x01\x00\x00\x00\x29\
-\x00\x00\x00\x4e\x00\x02\x00\x00\x00\x11\x00\x00\x00\x18\
-\x00\x00\x01\x06\x00\x02\x00\x00\x00\x05\x00\x00\x00\x13\
-\x00\x00\x04\x4c\x00\x00\x00\x00\x00\x01\x00\x0a\xa7\x31\
-\x00\x00\x03\x8a\x00\x00\x00\x00\x00\x01\x00\x00\x23\xa9\
-\x00\x00\x03\xec\x00\x00\x00\x00\x00\x01\x00\x05\x65\x6d\
-\x00\x00\x04\x1a\x00\x00\x00\x00\x00\x01\x00\x08\x06\x4f\
-\x00\x00\x03\xbc\x00\x00\x00\x00\x00\x01\x00\x02\xc4\x8b\
-\x00\x00\x10\x18\x00\x00\x00\x00\x00\x01\x00\x10\xd7\x23\
-\x00\x00\x10\x88\x00\x00\x00\x00\x00\x01\x00\x10\xde\x6e\
-\x00\x00\x0f\xc8\x00\x00\x00\x00\x00\x01\x00\x10\xd1\xc7\
-\x00\x00\x11\x66\x00\x00\x00\x00\x00\x01\x00\x10\xf0\xec\
-\x00\x00\x10\xe8\x00\x00\x00\x00\x00\x01\x00\x10\xe6\x84\
-\x00\x00\x0f\x70\x00\x00\x00\x00\x00\x01\x00\x10\xca\x90\
-\x00\x00\x11\xb8\x00\x00\x00\x00\x00\x01\x00\x10\xf6\xf3\
-\x00\x00\x10\xb0\x00\x00\x00\x00\x00\x01\x00\x10\xe1\xe2\
-\x00\x00\x12\x06\x00\x00\x00\x00\x00\x01\x00\x10\xfc\x1f\
-\x00\x00\x0f\x9a\x00\x00\x00\x00\x00\x01\x00\x10\xcd\x2e\
-\x00\x00\x11\x92\x00\x00\x00\x00\x00\x01\x00\x10\xf4\x1f\
-\x00\x00\x11\x3c\x00\x00\x00\x00\x00\x01\x00\x10\xee\x74\
-\x00\x00\x0f\xee\x00\x00\x00\x00\x00\x01\x00\x10\xd4\xa1\
-\x00\x00\x11\xe2\x00\x00\x00\x00\x00\x01\x00\x10\xf9\xd8\
-\x00\x00\x11\x10\x00\x00\x00\x00\x00\x01\x00\x10\xe9\xc9\
-\x00\x00\x10\x64\x00\x00\x00\x00\x00\x01\x00\x10\xdb\x71\
-\x00\x00\x10\x3e\x00\x00\x00\x00\x00\x01\x00\x10\xd9\x5a\
-\x00\x00\x03\x66\x00\x00\x00\x00\x00\x01\x00\x00\x21\x92\
-\x00\x00\x0c\x30\x00\x00\x00\x00\x00\x01\x00\x0f\x4c\x08\
-\x00\x00\x0c\xb0\x00\x00\x00\x00\x00\x01\x00\x0f\x52\xce\
-\x00\x00\x0b\xb4\x00\x00\x00\x00\x00\x01\x00\x0f\x45\x04\
-\x00\x00\x0c\x58\x00\x00\x00\x00\x00\x01\x00\x0f\x4e\xa8\
-\x00\x00\x0c\xda\x00\x00\x00\x00\x00\x01\x00\x0f\x55\x99\
-\x00\x00\x0b\xfe\x00\x00\x00\x00\x00\x01\x00\x0f\x4a\x1e\
-\x00\x00\x0b\xd8\x00\x00\x00\x00\x00\x01\x00\x0f\x47\xab\
-\x00\x00\x0c\x8e\x00\x00\x00\x00\x00\x01\x00\x0f\x50\x2a\
-\x00\x00\x01\x5c\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
-\x00\x00\x01\xc2\x00\x00\x00\x00\x00\x01\x00\x00\x05\xe6\
-\x00\x00\x01\x8e\x00\x00\x00\x00\x00\x01\x00\x00\x02\xfe\
-\x00\x00\x0e\xc0\x00\x00\x00\x00\x00\x01\x00\x0f\x70\xe7\
-\x00\x00\x0e\xea\x00\x00\x00\x00\x00\x01\x00\x0f\x76\x51\
-\x00\x00\x0f\x1a\x00\x00\x00\x00\x00\x01\x00\x0f\xe4\xda\
-\x00\x00\x0f\x3a\x00\x00\x00\x00\x00\x01\x00\x0f\xeb\x8b\
-\x00\x00\x12\x88\x00\x00\x00\x00\x00\x01\x00\x11\x03\x33\
-\x00\x00\x12\x2e\x00\x00\x00\x00\x00\x01\x00\x10\xfe\x32\
-\x00\x00\x14\xa4\x00\x00\x00\x00\x00\x01\x00\x11\x2d\x38\
-\x00\x00\x13\x2e\x00\x00\x00\x00\x00\x01\x00\x11\x0b\x3d\
-\x00\x00\x12\xca\x00\x00\x00\x00\x00\x01\x00\x11\x06\x86\
-\x00\x00\x12\x54\x00\x00\x00\x00\x00\x01\x00\x11\x01\x63\
-\x00\x00\x13\xf6\x00\x00\x00\x00\x00\x01\x00\x11\x1e\x13\
-\x00\x00\x13\x94\x00\x00\x00\x00\x00\x01\x00\x11\x15\xe3\
-\x00\x00\x14\xca\x00\x00\x00\x00\x00\x01\x00\x11\x30\x1a\
-\x00\x00\x14\x70\x00\x00\x00\x00\x00\x01\x00\x11\x29\x5b\
-\x00\x00\x13\xc0\x00\x00\x00\x00\x00\x01\x00\x11\x1a\xef\
-\x00\x00\x14\xfc\x00\x00\x00\x00\x00\x01\x00\x11\x33\xd7\
-\x00\x00\x14\x18\x00\x00\x00\x00\x00\x01\x00\x11\x22\x82\
-\x00\x00\x13\x5c\x00\x00\x00\x00\x00\x01\x00\x11\x13\x6d\
-\x00\x00\x14\x46\x00\x00\x00\x00\x00\x01\x00\x11\x26\xd3\
-\x00\x00\x12\xf8\x00\x00\x00\x00\x00\x01\x00\x11\x08\x8d\
-\x00\x00\x0a\x10\x00\x00\x00\x00\x00\x01\x00\x0e\xec\xf7\
-\x00\x00\x0a\x30\x00\x00\x00\x00\x00\x01\x00\x0e\xf0\xd4\
-\x00\x00\x09\xea\x00\x00\x00\x00\x00\x01\x00\x0e\xea\x59\
-\x00\x00\x0d\x9e\x00\x00\x00\x00\x00\x01\x00\x0f\x5f\xb5\
-\x00\x00\x0e\x3c\x00\x00\x00\x00\x00\x01\x00\x0f\x6a\x2a\
-\x00\x00\x0e\x96\x00\x00\x00\x00\x00\x01\x00\x0f\x6e\xe3\
-\x00\x00\x0d\x34\x00\x00\x00\x00\x00\x01\x00\x0f\x5b\xc5\
-\x00\x00\x0e\x18\x00\x00\x00\x00\x00\x01\x00\x0f\x66\xf7\
-\x00\x00\x0d\x68\x00\x00\x00\x00\x00\x01\x00\x0f\x5d\xbc\
-\x00\x00\x0d\xd2\x00\x00\x00\x00\x00\x01\x00\x0f\x61\xb3\
-\x00\x00\x0d\xf4\x00\x00\x00\x00\x00\x01\x00\x0f\x63\xc6\
-\x00\x00\x0e\x72\x00\x00\x00\x00\x00\x01\x00\x0f\x6c\x12\
-\x00\x00\x0d\x0c\x00\x00\x00\x00\x00\x01\x00\x0f\x59\x09\
-\x00\x00\x02\xee\x00\x00\x00\x00\x00\x01\x00\x00\x1b\x20\
-\x00\x00\x02\x72\x00\x00\x00\x00\x00\x01\x00\x00\x11\xc5\
-\x00\x00\x02\x16\x00\x00\x00\x00\x00\x01\x00\x00\x0c\x03\
-\x00\x00\x02\xa0\x00\x00\x00\x00\x00\x01\x00\x00\x15\x7b\
-\x00\x00\x01\xee\x00\x00\x00\x00\x00\x01\x00\x00\x09\x05\
-\x00\x00\x02\x3c\x00\x00\x00\x00\x00\x01\x00\x00\x0f\x9a\
-\x00\x00\x02\xc8\x00\x00\x00\x00\x00\x01\x00\x00\x18\xbd\
-\x00\x00\x03\x3e\x00\x00\x00\x00\x00\x01\x00\x00\x1f\x8c\
-\x00\x00\x03\x16\x00\x00\x00\x00\x00\x01\x00\x00\x1d\x72\
-\x00\x00\x04\xea\x00\x00\x00\x00\x00\x01\x00\x0d\x51\x59\
-\x00\x00\x04\x7c\x00\x00\x00\x00\x00\x01\x00\x0d\x48\x13\
-\x00\x00\x04\xb8\x00\x00\x00\x00\x00\x01\x00\x0d\x50\x06\
-\x00\x00\x05\x52\x00\x00\x00\x00\x00\x01\x00\x0d\x56\x1f\
-\x00\x00\x05\x20\x00\x00\x00\x00\x00\x01\x00\x0d\x54\x57\
-\x00\x00\x15\x64\x00\x00\x00\x00\x00\x01\x00\x11\x3d\x0b\
-\x00\x00\x15\x98\x00\x00\x00\x00\x00\x01\x00\x11\x3f\xdb\
-\x00\x00\x15\x20\x00\x00\x00\x00\x00\x01\x00\x11\x37\x8a\
-\x00\x00\x15\x44\x00\x00\x00\x00\x00\x01\x00\x11\x3a\x88\
-\x00\x00\x06\xde\x00\x00\x00\x00\x00\x01\x00\x0e\xaa\x8e\
-\x00\x00\x07\xc4\x00\x00\x00\x00\x00\x01\x00\x0e\xbb\x82\
-\x00\x00\x09\xca\x00\x00\x00\x00\x00\x01\x00\x0e\xe6\x66\
-\x00\x00\x09\x98\x00\x00\x00\x00\x00\x01\x00\x0e\xdf\x50\
-\x00\x00\x08\x7e\x00\x00\x00\x00\x00\x01\x00\x0e\xc4\x50\
-\x00\x00\x09\x1e\x00\x00\x00\x00\x00\x01\x00\x0e\xd7\x0f\
-\x00\x00\x09\x48\x00\x00\x00\x00\x00\x01\x00\x0e\xd9\x44\
-\x00\x00\x08\x1e\x00\x00\x00\x00\x00\x01\x00\x0e\xc0\x33\
-\x00\x00\x08\x4c\x00\x00\x00\x00\x00\x01\x00\x0e\xc2\xb3\
-\x00\x00\x07\x52\x00\x00\x00\x00\x00\x01\x00\x0e\xb4\x35\
-\x00\x00\x07\xa4\x00\x00\x00\x00\x00\x01\x00\x0e\xb8\x76\
-\x00\x00\x07\xec\x00\x00\x00\x00\x00\x01\x00\x0e\xbe\xe0\
-\x00\x00\x08\xd0\x00\x00\x00\x00\x00\x01\x00\x0e\xca\xc5\
-\x00\x00\x08\xa4\x00\x00\x00\x00\x00\x01\x00\x0e\xc8\x62\
-\x00\x00\x09\x6a\x00\x00\x00\x00\x00\x01\x00\x0e\xdd\x48\
-\x00\x00\x06\xb0\x00\x00\x00\x00\x00\x01\x00\x0e\xa3\xd0\
-\x00\x00\x07\x02\x00\x00\x00\x00\x00\x01\x00\x0e\xad\x7a\
-\x00\x00\x07\x2e\x00\x00\x00\x00\x00\x01\x00\x0e\xb0\xbc\
-\x00\x00\x07\x82\x00\x00\x00\x00\x00\x01\x00\x0e\xb5\x2e\
-\x00\x00\x08\xee\x00\x00\x00\x00\x00\x01\x00\x0e\xcd\x67\
-\x00\x00\x0a\xc4\x00\x00\x00\x00\x00\x01\x00\x0f\x2a\x0c\
-\x00\x00\x0a\xf8\x00\x00\x00\x00\x00\x01\x00\x0f\x2d\x0f\
-\x00\x00\x0b\x6e\x00\x00\x00\x00\x00\x01\x00\x0f\x3b\x47\
-\x00\x00\x0a\x94\x00\x00\x00\x00\x00\x01\x00\x0f\x27\xb3\
-\x00\x00\x0b\x3e\x00\x00\x00\x00\x00\x01\x00\x0f\x38\x14\
-\x00\x00\x0b\x90\x00\x00\x00\x00\x00\x01\x00\x0f\x3e\x1d\
-\x00\x00\x0a\x52\x00\x00\x00\x00\x00\x01\x00\x0e\xf4\x0e\
-\x00\x00\x0b\x1a\x00\x00\x00\x00\x00\x01\x00\x0f\x30\xe2\
-\x00\x00\x15\xc8\x00\x00\x00\x00\x00\x01\x00\x11\x42\xd4\
-\x00\x00\x16\x2a\x00\x00\x00\x00\x00\x01\x00\x11\x49\x4a\
-\x00\x00\x15\xfc\x00\x00\x00\x00\x00\x01\x00\x11\x46\x36\
-\x00\x00\x16\x52\x00\x00\x00\x00\x00\x01\x00\x11\x4b\xe8\
-\x00\x00\x06\x18\x00\x00\x00\x00\x00\x01\x00\x0d\x91\xaf\
-\x00\x00\x05\xb8\x00\x00\x00\x00\x00\x01\x00\x0d\x62\x6f\
-\x00\x00\x05\x88\x00\x00\x00\x00\x00\x01\x00\x0d\x57\xdb\
-\x00\x00\x05\xe8\x00\x00\x00\x00\x00\x01\x00\x0d\x77\xa4\
-\x00\x00\x06\x48\x00\x00\x00\x00\x00\x01\x00\x0d\x95\xcc\
-\x00\x00\x06\x7c\x00\x00\x00\x00\x00\x01\x00\x0e\x5e\x65\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x13\x00\x00\x00\x01\
+\x00\x00\x00\xe6\x00\x02\x00\x00\x00\x06\x00\x00\x00\x8a\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x04\x00\x00\x00\x86\
+\x00\x00\x00\xb4\x00\x02\x00\x00\x00\x08\x00\x00\x00\x7e\
+\x00\x00\x00\xd6\x00\x02\x00\x00\x00\x14\x00\x00\x00\x6a\
+\x00\x00\x00\x2c\x00\x02\x00\x00\x00\x02\x00\x00\x00\x68\
+\x00\x00\x00\x14\x00\x02\x00\x00\x00\x02\x00\x00\x00\x66\
+\x00\x00\x01\x06\x00\x02\x00\x00\x00\x05\x00\x00\x00\x61\
+\x00\x00\x00\xf4\x00\x02\x00\x00\x00\x01\x00\x00\x00\x60\
+\x00\x00\x01\x40\x00\x02\x00\x00\x00\x09\x00\x00\x00\x57\
+\x00\x00\x00\x8c\x00\x02\x00\x00\x00\x0a\x00\x00\x00\x4d\
+\x00\x00\x00\xc4\x00\x02\x00\x00\x00\x03\x00\x00\x00\x4a\
+\x00\x00\x00\x3c\x00\x02\x00\x00\x00\x10\x00\x00\x00\x3a\
+\x00\x00\x00\x62\x00\x02\x00\x00\x00\x03\x00\x00\x00\x37\
+\x00\x00\x00\x78\x00\x02\x00\x00\x00\x01\x00\x00\x00\x36\
+\x00\x00\x01\x54\x00\x02\x00\x00\x00\x03\x00\x00\x00\x33\
+\x00\x00\x00\x9e\x00\x02\x00\x00\x00\x08\x00\x00\x00\x2b\
+\x00\x00\x01\x2c\x00\x02\x00\x00\x00\x01\x00\x00\x00\x2a\
+\x00\x00\x00\x4e\x00\x02\x00\x00\x00\x11\x00\x00\x00\x19\
+\x00\x00\x01\x18\x00\x02\x00\x00\x00\x05\x00\x00\x00\x14\
+\x00\x00\x04\x5e\x00\x00\x00\x00\x00\x01\x00\x0a\xa7\x31\
+\x00\x00\x03\x9c\x00\x00\x00\x00\x00\x01\x00\x00\x23\xa9\
+\x00\x00\x03\xfe\x00\x00\x00\x00\x00\x01\x00\x05\x65\x6d\
+\x00\x00\x04\x2c\x00\x00\x00\x00\x00\x01\x00\x08\x06\x4f\
+\x00\x00\x03\xce\x00\x00\x00\x00\x00\x01\x00\x02\xc4\x8b\
+\x00\x00\x10\x56\x00\x00\x00\x00\x00\x01\x00\x10\xda\x21\
+\x00\x00\x10\xc6\x00\x00\x00\x00\x00\x01\x00\x10\xe1\x6c\
+\x00\x00\x10\x06\x00\x00\x00\x00\x00\x01\x00\x10\xd4\xc5\
+\x00\x00\x11\xa4\x00\x00\x00\x00\x00\x01\x00\x10\xf3\xea\
+\x00\x00\x11\x26\x00\x00\x00\x00\x00\x01\x00\x10\xe9\x82\
+\x00\x00\x0f\xae\x00\x00\x00\x00\x00\x01\x00\x10\xcd\x8e\
+\x00\x00\x11\xf6\x00\x00\x00\x00\x00\x01\x00\x10\xf9\xf1\
+\x00\x00\x10\xee\x00\x00\x00\x00\x00\x01\x00\x10\xe4\xe0\
+\x00\x00\x12\x44\x00\x00\x00\x00\x00\x01\x00\x10\xff\x1d\
+\x00\x00\x0f\xd8\x00\x00\x00\x00\x00\x01\x00\x10\xd0\x2c\
+\x00\x00\x11\xd0\x00\x00\x00\x00\x00\x01\x00\x10\xf7\x1d\
+\x00\x00\x11\x7a\x00\x00\x00\x00\x00\x01\x00\x10\xf1\x72\
+\x00\x00\x10\x2c\x00\x00\x00\x00\x00\x01\x00\x10\xd7\x9f\
+\x00\x00\x12\x20\x00\x00\x00\x00\x00\x01\x00\x10\xfc\xd6\
+\x00\x00\x11\x4e\x00\x00\x00\x00\x00\x01\x00\x10\xec\xc7\
+\x00\x00\x10\xa2\x00\x00\x00\x00\x00\x01\x00\x10\xde\x6f\
+\x00\x00\x10\x7c\x00\x00\x00\x00\x00\x01\x00\x10\xdc\x58\
+\x00\x00\x03\x78\x00\x00\x00\x00\x00\x01\x00\x00\x21\x92\
+\x00\x00\x0c\x6e\x00\x00\x00\x00\x00\x01\x00\x0f\x4f\x06\
+\x00\x00\x0c\xee\x00\x00\x00\x00\x00\x01\x00\x0f\x55\xcc\
+\x00\x00\x0b\xf2\x00\x00\x00\x00\x00\x01\x00\x0f\x48\x02\
+\x00\x00\x0c\x96\x00\x00\x00\x00\x00\x01\x00\x0f\x51\xa6\
+\x00\x00\x0d\x18\x00\x00\x00\x00\x00\x01\x00\x0f\x58\x97\
+\x00\x00\x0c\x3c\x00\x00\x00\x00\x00\x01\x00\x0f\x4d\x1c\
+\x00\x00\x0c\x16\x00\x00\x00\x00\x00\x01\x00\x0f\x4a\xa9\
+\x00\x00\x0c\xcc\x00\x00\x00\x00\x00\x01\x00\x0f\x53\x28\
+\x00\x00\x01\x6e\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
+\x00\x00\x01\xd4\x00\x00\x00\x00\x00\x01\x00\x00\x05\xe6\
+\x00\x00\x01\xa0\x00\x00\x00\x00\x00\x01\x00\x00\x02\xfe\
+\x00\x00\x0e\xfe\x00\x00\x00\x00\x00\x01\x00\x0f\x73\xe5\
+\x00\x00\x0f\x28\x00\x00\x00\x00\x00\x01\x00\x0f\x79\x4f\
+\x00\x00\x0f\x58\x00\x00\x00\x00\x00\x01\x00\x0f\xe7\xd8\
+\x00\x00\x0f\x78\x00\x00\x00\x00\x00\x01\x00\x0f\xee\x89\
+\x00\x00\x12\xc6\x00\x00\x00\x00\x00\x01\x00\x11\x06\x31\
+\x00\x00\x12\x6c\x00\x00\x00\x00\x00\x01\x00\x11\x01\x30\
+\x00\x00\x14\xe2\x00\x00\x00\x00\x00\x01\x00\x11\x30\x36\
+\x00\x00\x13\x6c\x00\x00\x00\x00\x00\x01\x00\x11\x0e\x3b\
+\x00\x00\x13\x08\x00\x00\x00\x00\x00\x01\x00\x11\x09\x84\
+\x00\x00\x12\x92\x00\x00\x00\x00\x00\x01\x00\x11\x04\x61\
+\x00\x00\x14\x34\x00\x00\x00\x00\x00\x01\x00\x11\x21\x11\
+\x00\x00\x13\xd2\x00\x00\x00\x00\x00\x01\x00\x11\x18\xe1\
+\x00\x00\x15\x08\x00\x00\x00\x00\x00\x01\x00\x11\x33\x18\
+\x00\x00\x14\xae\x00\x00\x00\x00\x00\x01\x00\x11\x2c\x59\
+\x00\x00\x13\xfe\x00\x00\x00\x00\x00\x01\x00\x11\x1d\xed\
+\x00\x00\x15\x3a\x00\x00\x00\x00\x00\x01\x00\x11\x36\xd5\
+\x00\x00\x14\x56\x00\x00\x00\x00\x00\x01\x00\x11\x25\x80\
+\x00\x00\x13\x9a\x00\x00\x00\x00\x00\x01\x00\x11\x16\x6b\
+\x00\x00\x14\x84\x00\x00\x00\x00\x00\x01\x00\x11\x29\xd1\
+\x00\x00\x13\x36\x00\x00\x00\x00\x00\x01\x00\x11\x0b\x8b\
+\x00\x00\x0a\x4e\x00\x00\x00\x00\x00\x01\x00\x0e\xef\xf5\
+\x00\x00\x0a\x6e\x00\x00\x00\x00\x00\x01\x00\x0e\xf3\xd2\
+\x00\x00\x0a\x28\x00\x00\x00\x00\x00\x01\x00\x0e\xed\x57\
+\x00\x00\x0d\xdc\x00\x00\x00\x00\x00\x01\x00\x0f\x62\xb3\
+\x00\x00\x0e\x7a\x00\x00\x00\x00\x00\x01\x00\x0f\x6d\x28\
+\x00\x00\x0e\xd4\x00\x00\x00\x00\x00\x01\x00\x0f\x71\xe1\
+\x00\x00\x0d\x72\x00\x00\x00\x00\x00\x01\x00\x0f\x5e\xc3\
+\x00\x00\x0e\x56\x00\x00\x00\x00\x00\x01\x00\x0f\x69\xf5\
+\x00\x00\x0d\xa6\x00\x00\x00\x00\x00\x01\x00\x0f\x60\xba\
+\x00\x00\x0e\x10\x00\x00\x00\x00\x00\x01\x00\x0f\x64\xb1\
+\x00\x00\x0e\x32\x00\x00\x00\x00\x00\x01\x00\x0f\x66\xc4\
+\x00\x00\x0e\xb0\x00\x00\x00\x00\x00\x01\x00\x0f\x6f\x10\
+\x00\x00\x0d\x4a\x00\x00\x00\x00\x00\x01\x00\x0f\x5c\x07\
+\x00\x00\x03\x00\x00\x00\x00\x00\x00\x01\x00\x00\x1b\x20\
+\x00\x00\x02\x84\x00\x00\x00\x00\x00\x01\x00\x00\x11\xc5\
+\x00\x00\x02\x28\x00\x00\x00\x00\x00\x01\x00\x00\x0c\x03\
+\x00\x00\x02\xb2\x00\x00\x00\x00\x00\x01\x00\x00\x15\x7b\
+\x00\x00\x02\x00\x00\x00\x00\x00\x00\x01\x00\x00\x09\x05\
+\x00\x00\x02\x4e\x00\x00\x00\x00\x00\x01\x00\x00\x0f\x9a\
+\x00\x00\x02\xda\x00\x00\x00\x00\x00\x01\x00\x00\x18\xbd\
+\x00\x00\x03\x50\x00\x00\x00\x00\x00\x01\x00\x00\x1f\x8c\
+\x00\x00\x03\x28\x00\x00\x00\x00\x00\x01\x00\x00\x1d\x72\
+\x00\x00\x05\x9a\x00\x00\x00\x00\x00\x01\x00\x0d\x57\xdb\
+\x00\x00\x04\xfc\x00\x00\x00\x00\x00\x01\x00\x0d\x51\x59\
+\x00\x00\x04\x8e\x00\x00\x00\x00\x00\x01\x00\x0d\x48\x13\
+\x00\x00\x04\xca\x00\x00\x00\x00\x00\x01\x00\x0d\x50\x06\
+\x00\x00\x05\x64\x00\x00\x00\x00\x00\x01\x00\x0d\x56\x1f\
+\x00\x00\x05\x32\x00\x00\x00\x00\x00\x01\x00\x0d\x54\x57\
+\x00\x00\x15\xa2\x00\x00\x00\x00\x00\x01\x00\x11\x40\x09\
+\x00\x00\x15\xd6\x00\x00\x00\x00\x00\x01\x00\x11\x42\xd9\
+\x00\x00\x15\x5e\x00\x00\x00\x00\x00\x01\x00\x11\x3a\x88\
+\x00\x00\x15\x82\x00\x00\x00\x00\x00\x01\x00\x11\x3d\x86\
+\x00\x00\x07\x1c\x00\x00\x00\x00\x00\x01\x00\x0e\xad\x8c\
+\x00\x00\x08\x02\x00\x00\x00\x00\x00\x01\x00\x0e\xbe\x80\
+\x00\x00\x0a\x08\x00\x00\x00\x00\x00\x01\x00\x0e\xe9\x64\
+\x00\x00\x09\xd6\x00\x00\x00\x00\x00\x01\x00\x0e\xe2\x4e\
+\x00\x00\x08\xbc\x00\x00\x00\x00\x00\x01\x00\x0e\xc7\x4e\
+\x00\x00\x09\x5c\x00\x00\x00\x00\x00\x01\x00\x0e\xda\x0d\
+\x00\x00\x09\x86\x00\x00\x00\x00\x00\x01\x00\x0e\xdc\x42\
+\x00\x00\x08\x5c\x00\x00\x00\x00\x00\x01\x00\x0e\xc3\x31\
+\x00\x00\x08\x8a\x00\x00\x00\x00\x00\x01\x00\x0e\xc5\xb1\
+\x00\x00\x07\x90\x00\x00\x00\x00\x00\x01\x00\x0e\xb7\x33\
+\x00\x00\x07\xe2\x00\x00\x00\x00\x00\x01\x00\x0e\xbb\x74\
+\x00\x00\x08\x2a\x00\x00\x00\x00\x00\x01\x00\x0e\xc1\xde\
+\x00\x00\x09\x0e\x00\x00\x00\x00\x00\x01\x00\x0e\xcd\xc3\
+\x00\x00\x08\xe2\x00\x00\x00\x00\x00\x01\x00\x0e\xcb\x60\
+\x00\x00\x09\xa8\x00\x00\x00\x00\x00\x01\x00\x0e\xe0\x46\
+\x00\x00\x06\xee\x00\x00\x00\x00\x00\x01\x00\x0e\xa6\xce\
+\x00\x00\x07\x40\x00\x00\x00\x00\x00\x01\x00\x0e\xb0\x78\
+\x00\x00\x07\x6c\x00\x00\x00\x00\x00\x01\x00\x0e\xb3\xba\
+\x00\x00\x07\xc0\x00\x00\x00\x00\x00\x01\x00\x0e\xb8\x2c\
+\x00\x00\x09\x2c\x00\x00\x00\x00\x00\x01\x00\x0e\xd0\x65\
+\x00\x00\x0b\x02\x00\x00\x00\x00\x00\x01\x00\x0f\x2d\x0a\
+\x00\x00\x0b\x36\x00\x00\x00\x00\x00\x01\x00\x0f\x30\x0d\
+\x00\x00\x0b\xac\x00\x00\x00\x00\x00\x01\x00\x0f\x3e\x45\
+\x00\x00\x0a\xd2\x00\x00\x00\x00\x00\x01\x00\x0f\x2a\xb1\
+\x00\x00\x0b\x7c\x00\x00\x00\x00\x00\x01\x00\x0f\x3b\x12\
+\x00\x00\x0b\xce\x00\x00\x00\x00\x00\x01\x00\x0f\x41\x1b\
+\x00\x00\x0a\x90\x00\x00\x00\x00\x00\x01\x00\x0e\xf7\x0c\
+\x00\x00\x0b\x58\x00\x00\x00\x00\x00\x01\x00\x0f\x33\xe0\
+\x00\x00\x16\x06\x00\x00\x00\x00\x00\x01\x00\x11\x45\xd2\
+\x00\x00\x16\x68\x00\x00\x00\x00\x00\x01\x00\x11\x4c\x48\
+\x00\x00\x16\x3a\x00\x00\x00\x00\x00\x01\x00\x11\x49\x34\
+\x00\x00\x16\x90\x00\x00\x00\x00\x00\x01\x00\x11\x4e\xe6\
+\x00\x00\x06\x56\x00\x00\x00\x00\x00\x01\x00\x0d\x94\xad\
+\x00\x00\x05\xf6\x00\x00\x00\x00\x00\x01\x00\x0d\x65\x6d\
+\x00\x00\x05\xc6\x00\x00\x00\x00\x00\x01\x00\x0d\x5a\xd9\
+\x00\x00\x06\x26\x00\x00\x00\x00\x00\x01\x00\x0d\x7a\xa2\
+\x00\x00\x06\x86\x00\x00\x00\x00\x00\x01\x00\x0d\x98\xca\
+\x00\x00\x06\xba\x00\x00\x00\x00\x00\x01\x00\x0e\x61\x63\
 "
 
 def qInitResources():

=== modified file 'openlp/plugins/bibles/lib/mediaitem.py'
--- openlp/plugins/bibles/lib/mediaitem.py	2013-01-21 23:39:10 +0000
+++ openlp/plugins/bibles/lib/mediaitem.py	2013-01-24 09:36:22 +0000
@@ -76,7 +76,7 @@
         """
         Check if the first item is a second bible item or not.
         """
-        bitem = self.listView.item(0)
+        bitem = self.listView.topLevelItem(0)
         if not bitem.flags() & QtCore.Qt.ItemIsSelectable:
             # The item is the "No Search Results" item.
             self.listView.clear()
@@ -95,7 +95,7 @@
             self.displayResults(bible, second_bible)
 
     def _decodeQtObject(self, bitem, key):
-        reference = bitem.data(QtCore.Qt.UserRole)
+        reference = bitem.data(0, QtCore.Qt.UserRole)
         obj = reference[unicode(key)]
         return unicode(obj).strip()
 
@@ -634,7 +634,7 @@
             self.second_search_results = self.plugin.manager.get_verses(second_bible, versetext, book_ref_id)
         if not self.advancedLockButton.isChecked():
             self.listView.clear()
-        if self.listView.count() != 0:
+        if self.listView.topLevelItemCount() != 0:
             self.__checkSecondBible(bible, second_bible)
         elif self.search_results:
             self.displayResults(bible, second_bible)
@@ -691,7 +691,7 @@
                 self.second_search_results = bibles[second_bible].get_verses(text)
         if not self.quickLockButton.isChecked():
             self.listView.clear()
-        if self.listView.count() != 0 and self.search_results:
+        if self.listView.topLevelItemCount() != 0 and self.search_results:
             self.__checkSecondBible(bible, second_bible)
         elif self.search_results:
             self.displayResults(bible, second_bible)
@@ -707,7 +707,7 @@
         """
         items = self.buildDisplayResults(bible, second_bible, self.search_results)
         for bible_verse in items:
-            self.listView.addItem(bible_verse)
+            self.listView.addTopLevelItem(bible_verse)
         self.listView.selectAll()
         self.search_results = {}
         self.second_search_results = {}
@@ -766,8 +766,9 @@
                     second_version)
             else:
                 bible_text = u'%s %d%s%d (%s)' % (book, verse.chapter, verse_separator, verse.verse, version)
-            bible_verse = QtGui.QListWidgetItem(bible_text)
-            bible_verse.setData(QtCore.Qt.UserRole, data)
+            bible_verse = QtGui.QTreeWidgetItem(bible_text)
+            bible_verse.setText(0, bible_text)
+            bible_verse.setData(0, QtCore.Qt.UserRole, data)
             items.append(bible_verse)
         return items
 
@@ -971,7 +972,7 @@
         return []
 
     def createItemFromId(self, item_id):
-        item = QtGui.QListWidgetItem()
+        item = QtGui.QTreeWidgetItem()
         bible = self.quickVersionComboBox.currentText()
         search_results = self.plugin.manager.get_verses(bible, item_id, False)
         items = self.buildDisplayResults(bible, u'', search_results)

=== modified file 'openlp/plugins/custom/lib/mediaitem.py'
--- openlp/plugins/custom/lib/mediaitem.py	2013-01-11 00:19:11 +0000
+++ openlp/plugins/custom/lib/mediaitem.py	2013-01-24 09:36:22 +0000
@@ -105,9 +105,10 @@
         self.listView.clear()
         custom_slides.sort()
         for custom_slide in custom_slides:
-            custom_name = QtGui.QListWidgetItem(custom_slide.title)
-            custom_name.setData(QtCore.Qt.UserRole, custom_slide.id)
-            self.listView.addItem(custom_name)
+            custom_name = QtGui.QTreeWidgetItem(custom_slide.title)
+            custom_name.setText(0, custom_slide.title)
+            custom_name.setData(0, QtCore.Qt.UserRole, custom_slide.id)
+            self.listView.addTopLevelItem(custom_name)
             # Auto-select the custom.
             if custom_slide.id == self.autoSelectId:
                 self.listView.setCurrentItem(custom_name)
@@ -154,7 +155,7 @@
         """
         if check_item_selected(self.listView, UiStrings().SelectEdit):
             item = self.listView.currentItem()
-            item_id = item.data(QtCore.Qt.UserRole)
+            item_id = item.data(0, QtCore.Qt.UserRole)
             self.edit_custom_form.loadCustom(item_id, False)
             self.edit_custom_form.exec_()
             self.autoSelectId = -1

=== added directory 'openlp/plugins/images/forms'
=== added file 'openlp/plugins/images/forms/__init__.py'
--- openlp/plugins/images/forms/__init__.py	1970-01-01 00:00:00 +0000
+++ openlp/plugins/images/forms/__init__.py	2013-01-24 09:36:22 +0000
@@ -0,0 +1,58 @@
+# -*- 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
+

=== added file 'openlp/plugins/images/forms/addgroupdialog.py'
--- openlp/plugins/images/forms/addgroupdialog.py	1970-01-01 00:00:00 +0000
+++ openlp/plugins/images/forms/addgroupdialog.py	2013-01-24 09:36:22 +0000
@@ -0,0 +1,63 @@
+# -*- 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, addGroupDialog):
+        addGroupDialog.setObjectName(u'addGroupDialog')
+        addGroupDialog.resize(300, 10)
+        self.dialogLayout = QtGui.QVBoxLayout(addGroupDialog)
+        self.dialogLayout.setObjectName(u'dialogLayout')
+        self.nameLayout = QtGui.QFormLayout()
+        self.nameLayout.setObjectName(u'nameLayout')
+        self.parentGroupLabel = QtGui.QLabel(addGroupDialog)
+        self.parentGroupLabel.setObjectName(u'parentGroupLabel')
+        self.parentGroupComboBox = QtGui.QComboBox(addGroupDialog)
+        self.parentGroupComboBox.setObjectName(u'parentGroupComboBox')
+        self.nameLayout.addRow(self.parentGroupLabel, self.parentGroupComboBox)
+        self.nameLabel = QtGui.QLabel(addGroupDialog)
+        self.nameLabel.setObjectName(u'nameLabel')
+        self.nameEdit = QtGui.QLineEdit(addGroupDialog)
+        self.nameEdit.setObjectName(u'nameEdit')
+        self.nameLabel.setBuddy(self.nameEdit)
+        self.nameLayout.addRow(self.nameLabel, self.nameEdit)
+        self.dialogLayout.addLayout(self.nameLayout)
+        self.buttonBox = create_button_box(addGroupDialog, u'buttonBox', [u'cancel', u'save'])
+        self.dialogLayout.addWidget(self.buttonBox)
+        self.retranslateUi(addGroupDialog)
+        addGroupDialog.setMaximumHeight(addGroupDialog.sizeHint().height())
+
+    def retranslateUi(self, addGroupDialog):
+        addGroupDialog.setWindowTitle(translate('ImagePlugin.AddGroupForm', 'Add group'))
+        self.parentGroupLabel.setText(translate('ImagePlugin.AddGroupForm', 'Parent group:'))
+        self.nameLabel.setText(translate('ImagePlugin.AddGroupForm', 'Group name:'))

=== added file 'openlp/plugins/images/forms/addgroupform.py'
--- openlp/plugins/images/forms/addgroupform.py	1970-01-01 00:00:00 +0000
+++ openlp/plugins/images/forms/addgroupform.py	2013-01-24 09:36:22 +0000
@@ -0,0 +1,60 @@
+# -*- 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):
+    """
+    Class documentation goes here.
+    """
+    def __init__(self, parent=None):
+        """
+        Constructor
+        """
+        QtGui.QDialog.__init__(self, parent)
+        self.setupUi(self)
+
+    def exec_(self, clear=True):
+        if clear:
+            self.nameEdit.clear()
+        self.nameEdit.setFocus()
+        return QtGui.QDialog.exec_(self)
+
+    def accept(self):
+        if not self.nameEdit.text():
+            critical_error_message_box(message=translate('ImagePlugin.AddGroupForm',
+                'You need to type in a group name.'))
+            self.nameEdit.setFocus()
+            return False
+        else:
+            return QtGui.QDialog.accept(self)

=== added file 'openlp/plugins/images/forms/choosegroupdialog.py'
--- openlp/plugins/images/forms/choosegroupdialog.py	1970-01-01 00:00:00 +0000
+++ openlp/plugins/images/forms/choosegroupdialog.py	2013-01-24 09:36:22 +0000
@@ -0,0 +1,61 @@
+# -*- 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):
+    def setupUi(self, chooseGroupDialog):
+        chooseGroupDialog.setObjectName(u'chooseGroupDialog')
+        chooseGroupDialog.resize(440, 119)
+        self.chooseGroupLayout = QtGui.QFormLayout(chooseGroupDialog)
+        self.chooseGroupLayout.setFieldGrowthPolicy(QtGui.QFormLayout.ExpandingFieldsGrow)
+        self.chooseGroupLayout.setMargin(8)
+        self.chooseGroupLayout.setSpacing(8)
+        self.chooseGroupLayout.setObjectName(u'chooseGroupLayout')
+        self.groupQuestionLabel = QtGui.QLabel(chooseGroupDialog)
+        self.groupQuestionLabel.setWordWrap(True)
+        self.groupQuestionLabel.setObjectName(u'groupQuestionLabel')
+        self.chooseGroupLayout.setWidget(1, QtGui.QFormLayout.SpanningRole, self.groupQuestionLabel)
+        self.groupComboBox = QtGui.QComboBox(chooseGroupDialog)
+        self.groupComboBox.setObjectName(u'groupComboBox')
+        self.chooseGroupLayout.setWidget(2, QtGui.QFormLayout.FieldRole, self.groupComboBox)
+        self.groupButtonBox = create_button_box(chooseGroupDialog, u'buttonBox', [u'ok'])
+        self.chooseGroupLayout.setWidget(3, QtGui.QFormLayout.FieldRole, self.groupButtonBox)
+
+        self.retranslateUi(chooseGroupDialog)
+        QtCore.QMetaObject.connectSlotsByName(chooseGroupDialog)
+
+    def retranslateUi(self, chooseGroupDialog):
+        chooseGroupDialog.setWindowTitle(translate('ImagePlugin.ChooseGroupForm', 'Choose group'))
+        self.groupQuestionLabel.setText(translate('ImagePlugin.ChooseGroupForm',
+            'To which group do you want these images to be added?'))
+

=== added file 'openlp/plugins/images/forms/choosegroupform.py'
--- openlp/plugins/images/forms/choosegroupform.py	1970-01-01 00:00:00 +0000
+++ openlp/plugins/images/forms/choosegroupform.py	2013-01-24 09:36:22 +0000
@@ -0,0 +1,46 @@
+# -*- 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.choosegroupdialog import Ui_ChooseGroupDialog
+
+class ChooseGroupForm(QtGui.QDialog, Ui_ChooseGroupDialog):
+    """
+    Class documentation goes here.
+    """
+    def __init__(self, parent=None):
+        """
+        Constructor
+        """
+        QtGui.QDialog.__init__(self, parent)
+        self.setupUi(self)
+

=== modified file 'openlp/plugins/images/imageplugin.py'
--- openlp/plugins/images/imageplugin.py	2013-01-18 19:24:06 +0000
+++ openlp/plugins/images/imageplugin.py	2013-01-24 09:36:22 +0000
@@ -32,11 +32,14 @@
 import logging
 
 from openlp.core.lib import Plugin, StringContent, build_icon, translate, Receiver, ImageSource, Settings
+from openlp.core.lib.db import Manager
 from openlp.plugins.images.lib import ImageMediaItem, ImageTab
+from openlp.plugins.images.lib.db import init_schema
 
 log = logging.getLogger(__name__)
 
 __default_settings__ = {
+        u'images/db type': u'sqlite',
         u'images/background color': u'#000000',
         u'images/images files': []
     }
@@ -47,6 +50,7 @@
 
     def __init__(self, plugin_helpers):
         Plugin.__init__(self, u'images', __default_settings__, plugin_helpers, 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)

=== added file 'openlp/plugins/images/lib/db.py'
--- openlp/plugins/images/lib/db.py	1970-01-01 00:00:00 +0000
+++ openlp/plugins/images/lib/db.py	2013-01-24 09:36:22 +0000
@@ -0,0 +1,97 @@
+# -*- 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

=== modified file 'openlp/plugins/images/lib/mediaitem.py'
--- openlp/plugins/images/lib/mediaitem.py	2013-01-18 19:24:06 +0000
+++ openlp/plugins/images/lib/mediaitem.py	2013-01-24 09:36:22 +0000
@@ -37,6 +37,8 @@
     UiStrings
 from openlp.core.lib.ui import 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__)
 
@@ -51,6 +53,10 @@
         MediaManagerItem.__init__(self, parent, plugin, icon)
         self.quickPreviewAllowed = True
         self.hasSearch = True
+        self.manager = plugin.manager
+        self.choosegroupform = ChooseGroupForm(self)
+        self.addgroupform = AddGroupForm(self)
+        self.fillGroupsComboBox(self.addgroupform.parentGroupComboBox)
         QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'live_theme_changed'), self.liveThemeChanged)
         # Allow DnD from the desktop
         self.listView.activateDnD()
@@ -60,6 +66,8 @@
             '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)
@@ -76,20 +84,50 @@
         log.debug(u'initialise')
         self.listView.clear()
         self.listView.setIconSize(QtCore.QSize(88, 50))
+        self.listView.setIndentation(self.listView.defaultIndentation)
         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)
+        # Import old images list
+        images_old = Settings().value(self.settingsSection +  u'/images files')
+        if len(images_old) > 0:
+            for imageFile in images_old:
+                imagefilename = ImageFilenames()
+                imagefilename.group_id = 0
+                imagefilename.filename = imageFile
+                success = self.manager.save_object(imagefilename)
+            Settings().setValue(self.settingsSection + u'/images files', [])
+            Settings().remove(self.settingsSection + u'/images files')
+            Settings().remove(self.settingsSection + u'/images count')
+        # Load images from the database
+        self.loadFullList(self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.filename), True)
 
     def addListViewToToolBar(self):
         MediaManagerItem.addListViewToToolBar(self)
         self.listView.addAction(self.replaceAction)
 
+    def addStartHeaderBar(self):
+        self.addGroupAction = self.toolbar.addToolbarAction(u'addGroupAction',
+            icon=u':/images/image_new_group.png', triggers=self.onAddGroupClick)
+
     def addEndHeaderBar(self):
         self.replaceAction = self.toolbar.addToolbarAction(u'replaceAction',
             icon=u':/slides/slide_blank.png', triggers=self.onReplaceClick)
         self.resetAction = self.toolbar.addToolbarAction(u'resetAction',
             icon=u':/system/system_close.png', visible=False, triggers=self.onResetClick)
 
+    def recursivelyDeleteGroup(self, image_group):
+        """
+        Recursively deletes a group and all groups and images in it
+        """
+        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.recursivelyDeleteGroup(group)
+            self.manager.delete_object(ImageGroups, group.id)
+
     def onDeleteClick(self):
         """
         Remove an image item from the list
@@ -97,49 +135,130 @@
         # 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)
+            item_list = self.listView.selectedItems()
             Receiver.send_message(u'cursor_busy')
-            self.plugin.formParent.displayProgressBar(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.plugin.formParent.displayProgressBar(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)))
+                        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.recursivelyDeleteGroup(item_data)
+                            self.manager.delete_object(ImageGroups, row_item.data(0, QtCore.Qt.UserRole).id)
+                            if item_data.parent_id is 0:
+                                self.listView.takeTopLevelItem(self.listView.indexOfTopLevelItem(row_item))
+                            else:
+                                row_item.parent().removeChild(row_item)
                 self.plugin.formParent.incrementProgressBar()
-            Settings().setValue(self.settingsSection + u'/images files', self.getFileList())
             self.plugin.formParent.finishedProgressBar()
             Receiver.send_message(u'cursor_normal')
         self.listView.blockSignals(False)
 
-    def loadList(self, images, initialLoad=False):
+    def addSubGroups(self, groupList, parentGroupId):
+        """
+        Recursively add subgroups to the given parent group
+        """
+        image_groups = self.manager.get_all_objects(ImageGroups, ImageGroups.parent_id == parentGroupId)
+        image_groups.sort(cmp=locale_compare, key=lambda group_object: group_object.group_name)
+        for image_group in image_groups:
+            group = QtGui.QTreeWidgetItem()
+            group.setText(0, image_group.group_name)
+            group.setData(0, QtCore.Qt.UserRole, image_group)
+            if parentGroupId is 0:
+                self.listView.addTopLevelItem(group)
+            else:
+                groupList[parentGroupId].addChild(group)
+            groupList[image_group.id] = group
+            self.addSubGroups(groupList, image_group.id)
+
+    def fillGroupsComboBox(self, comboBox, parentGroupId=0, prefix='', showTopLevelGroup=True):
+        """
+        Recursively add groups to the combobox in the 'Add group' dialog
+        """
+        if parentGroupId is 0:
+            comboBox.clear()
+            if showTopLevelGroup is True:
+                comboBox.addItem(translate('ImagePlugin.MediaItem', '-- Top-level group --'), 0)
+        image_groups = self.manager.get_all_objects(ImageGroups, ImageGroups.parent_id == parentGroupId)
+        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.fillGroupsComboBox(comboBox, image_group.id, prefix+'   ')
+
+    def loadFullList(self, images, initialLoad=False):
+        """
+        Replace the list of images and groups in the interface.
+        """
         if not initialLoad:
             Receiver.send_message(u'cursor_busy')
             self.plugin.formParent.displayProgressBar(len(images))
+        self.listView.clear()
+        # Load the list of groups and add them to the treeView
+        group_items = {}
+        self.addSubGroups(group_items, 0)
         # 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)
+                    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 is 0:
+                if 0 not in group_items:
+                    # The 'Imported' group is only displayed when there are files that were imported from the
+                    # configuration file
+                    imported_group = QtGui.QTreeWidgetItem()
+                    imported_group.setText(0, translate('ImagePlugin.MediaItem', 'Imported'))
+                    self.listView.insertTopLevelItem(0, imported_group)
+                    group_items[0] = imported_group
+            group_items[imageFile.group_id].addChild(item_name)
             if not initialLoad:
                 self.plugin.formParent.incrementProgressBar()
         if not initialLoad:
             self.plugin.formParent.finishedProgressBar()
             Receiver.send_message(u'cursor_normal')
 
+    def loadList(self, images, initialLoad=False):
+        """
+        Add new images to the database. This method is called when adding images using the Add button or DnD.
+        """
+        # Ask which group the images should be saved in
+        self.fillGroupsComboBox(self.choosegroupform.groupComboBox, showTopLevelGroup=False)
+        if self.choosegroupform.exec_():
+            group_id = self.choosegroupform.groupComboBox.itemData(
+                self.choosegroupform.groupComboBox.currentIndex(), QtCore.Qt.UserRole)
+            # Save the new images in the database
+            for filename in images:
+                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)
+                success = self.manager.save_object(imageFile)
+            self.loadFullList(self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.filename),
+                initialLoad)
+
     def generateSlideData(self, service_item, item=None, xmlVersion=False,
         remote=False, context=ServiceItemContext.Service):
         background = QtGui.QColor(Settings().value(self.settingsSection + u'/background color'))
@@ -149,7 +268,11 @@
             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)
@@ -158,8 +281,16 @@
         service_item.theme = -1
         missing_items = []
         missing_items_filenames = []
-        for bitem in items:
-            filename = bitem.data(QtCore.Qt.UserRole)
+        # 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):
+                        items.append(bitem.child(index))
+                items.remove(bitem)
+        # 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)
@@ -182,11 +313,58 @@
             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 __checkObject(self, objects, newObject, edit):
+        """
+        Utility method to check for an existing object.
+
+        ``edit``
+            If we edit an item, this should be *True*.
+        """
+        if objects:
+            # If we edit an existing object, we need to make sure that we do
+            # not return False when nothing has changed.
+            if edit:
+                for object in objects:
+                    if object.id != newObject.id:
+                        return False
+                return True
+            else:
+                return False
+        else:
+            return True
+
+    def checkGroupName(self, newGroup, edit=False):
+        """
+        Returns *False* if the given Group already exists, otherwise *True*.
+        """
+        groups = self.manager.get_all_objects(ImageGroups, ImageGroups.group_name == newGroup.group_name)
+        return self.__checkObject(groups, newGroup, edit)
+
+    def onAddGroupClick(self):
+        """
+        Called to add a new group
+        """
+        if self.addgroupform.exec_():
+            new_group = ImageGroups.populate(parent_id=self.addgroupform.parentGroupComboBox.itemData(
+                self.addgroupform.parentGroupComboBox.currentIndex(), QtCore.Qt.UserRole),
+                group_name=self.addgroupform.nameEdit.text())
+            if self.checkGroupName(new_group):
+                if self.manager.save_object(new_group):
+                    self.loadFullList(self.manager.get_all_objects(ImageFilenames,
+                        order_by_ref=ImageFilenames.filename))
+                    self.fillGroupsComboBox(self.addgroupform.parentGroupComboBox)
+                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 backgound with the image selected,
@@ -207,9 +385,8 @@
         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]
+            filename = bitem.data(0, QtCore.Qt.UserRole).filename
             if os.path.exists(filename):
                 if self.plugin.liveController.display.directImage(filename, background):
                     self.resetAction.setVisible(True)

=== modified file 'openlp/plugins/media/lib/mediaitem.py'
--- openlp/plugins/media/lib/mediaitem.py	2013-01-20 12:23:22 +0000
+++ openlp/plugins/media/lib/mediaitem.py	2013-01-24 09:36:22 +0000
@@ -147,7 +147,7 @@
         if check_item_selected(self.listView,
                 translate('MediaPlugin.MediaItem', 'You must select a media file to replace the background with.')):
             item = self.listView.currentItem()
-            filename = item.data(QtCore.Qt.UserRole)
+            filename = item.data(0, QtCore.Qt.UserRole)
             if os.path.exists(filename):
                 service_item = ServiceItem()
                 service_item.title = u'webkit'
@@ -171,7 +171,7 @@
             item = self.listView.currentItem()
             if item is None:
                 return False
-        filename = item.data(QtCore.Qt.UserRole)
+        filename = item.data(0, QtCore.Qt.UserRole)
         if not os.path.exists(filename):
             if not remote:
                 # File is no longer present
@@ -253,7 +253,7 @@
             row_list = [item.row() for item in self.listView.selectedIndexes()]
             row_list.sort(reverse=True)
             for row in row_list:
-                self.listView.takeItem(row)
+                self.listView.takeTopLevelItem(row)
             Settings().setValue(self.settingsSection + u'/media files', self.getFileList())
 
     def loadList(self, media):
@@ -264,24 +264,28 @@
             track_info = QtCore.QFileInfo(track)
             if not os.path.exists(track):
                 filename = os.path.split(unicode(track))[1]
-                item_name = QtGui.QListWidgetItem(filename)
-                item_name.setIcon(ERROR)
-                item_name.setData(QtCore.Qt.UserRole, track)
+                item_name = QtGui.QTreeWidgetItem(filename)
+                item_name.setText(0, filename)
+                item_name.setIcon(0, ERROR)
+                item_name.setData(0, QtCore.Qt.UserRole, track)
             elif track_info.isFile():
                 filename = os.path.split(unicode(track))[1]
-                item_name = QtGui.QListWidgetItem(filename)
+                item_name = QtGui.QTreeWidgetItem(filename)
+                item_name.setText(0, filename)
                 if u'*.%s' % (filename.split(u'.')[-1].lower()) in self.plugin.mediaController.audio_extensions_list:
-                    item_name.setIcon(AUDIO)
+                    item_name.setIcon(0, AUDIO)
                 else:
-                    item_name.setIcon(VIDEO)
-                item_name.setData(QtCore.Qt.UserRole, track)
+                    item_name.setIcon(0, VIDEO)
+                item_name.setData(0, QtCore.Qt.UserRole, track)
             else:
                 filename = os.path.split(unicode(track))[1]
-                item_name = QtGui.QListWidgetItem(filename)
-                item_name.setIcon(build_icon(DVDICON))
-                item_name.setData(QtCore.Qt.UserRole, track)
-            item_name.setToolTip(track)
-            self.listView.addItem(item_name)
+                item_name = QtGui.QTreeWidgetItem(filename)
+                item_name.setText(0, filename)
+                item_name.setIcon(0, build_icon(DVDICON))
+                item_name.setData(0, QtCore.Qt.UserRole, track)
+            item_name.setToolTip(0, track)
+            self.listView.addTopLevelItem(item_name)
+        Settings().setValue(self.settingsSection + u'/media files', self.getFileList())
 
     def getList(self, type=MediaType.Audio):
         media = Settings().value(self.settingsSection + u'/media files')

=== modified file 'openlp/plugins/presentations/lib/mediaitem.py'
--- openlp/plugins/presentations/lib/mediaitem.py	2013-01-18 19:24:06 +0000
+++ openlp/plugins/presentations/lib/mediaitem.py	2013-01-24 09:36:22 +0000
@@ -164,11 +164,12 @@
                 continue
             filename = os.path.split(unicode(file))[1]
             if not os.path.exists(file):
-                item_name = QtGui.QListWidgetItem(filename)
-                item_name.setIcon(build_icon(ERROR))
-                item_name.setData(QtCore.Qt.UserRole, file)
-                item_name.setToolTip(file)
-                self.listView.addItem(item_name)
+                item_name = QtGui.QTreeWidgetItem(filename)
+                item_name.setText(0, filename)
+                item_name.setIcon(0, build_icon(ERROR))
+                item_name.setData(0, QtCore.Qt.UserRole, file)
+                item_name.setToolTip(0, file)
+                self.listView.addTopLevelItem(item_name)
             else:
                 if titles.count(filename) > 0:
                     if not initialLoad:
@@ -201,11 +202,13 @@
                         critical_error_message_box(UiStrings().UnsupportedFile,
                             translate('PresentationPlugin.MediaItem', 'This type of presentation is not supported.'))
                         continue
-                item_name = QtGui.QListWidgetItem(filename)
-                item_name.setData(QtCore.Qt.UserRole, file)
-                item_name.setIcon(icon)
-                item_name.setToolTip(file)
-                self.listView.addItem(item_name)
+                item_name = QtGui.QTreeWidgetItem(filename)
+                item_name.setText(0, filename)
+                item_name.setData(0, QtCore.Qt.UserRole, file)
+                item_name.setIcon(0, icon)
+                item_name.setToolTip(0, file)
+                self.listView.addTopLevelItem(item_name)
+        Settings().setValue(self.settingsSection + u'/presentations files', self.getFileList())
         Receiver.send_message(u'cursor_normal')
         if not initialLoad:
             self.plugin.formParent.finishedProgressBar()
@@ -231,7 +234,7 @@
             self.plugin.formParent.finishedProgressBar()
             Receiver.send_message(u'cursor_normal')
             for row in row_list:
-                self.listView.takeItem(row)
+                self.listView.takeTopLevelItem(row)
             Settings().setValue(self.settingsSection + u'/presentations files', self.getFileList())
 
     def generateSlideData(self, service_item, item=None, xmlVersion=False,

=== modified file 'openlp/plugins/songs/lib/mediaitem.py'
--- openlp/plugins/songs/lib/mediaitem.py	2013-01-11 00:19:11 +0000
+++ openlp/plugins/songs/lib/mediaitem.py	2013-01-24 09:36:22 +0000
@@ -237,9 +237,10 @@
             author_list = [author.display_name for author in song.authors]
             song_title = unicode(song.title)
             song_detail = u'%s (%s)' % (song_title, create_separated_list(author_list))
-            song_name = QtGui.QListWidgetItem(song_detail)
-            song_name.setData(QtCore.Qt.UserRole, song.id)
-            self.listView.addItem(song_name)
+            song_name = QtGui.QTreeWidgetItem(song_detail)
+            song_name.setText(0, song_detail)
+            song_name.setData(0, QtCore.Qt.UserRole, song.id)
+            self.listView.addTopLevelItem(song_name)
             # Auto-select the item if name has been set
             if song.id == self.autoSelectId:
                 self.listView.setCurrentItem(song_name)
@@ -254,9 +255,10 @@
                 if song.temporary:
                     continue
                 song_detail = u'%s (%s)' % (author.display_name, song.title)
-                song_name = QtGui.QListWidgetItem(song_detail)
-                song_name.setData(QtCore.Qt.UserRole, song.id)
-                self.listView.addItem(song_name)
+                song_name = QtGui.QTreeWidgetItem(song_detail)
+                song_name.setText(0, song_detail)
+                song_name.setData(0, QtCore.Qt.UserRole, song.id)
+                self.listView.addTopLevelItem(song_name)
 
     def displayResultsBook(self, searchresults, song_number=False):
         log.debug(u'display results Book')
@@ -271,9 +273,10 @@
                 if song_number and not song_number in song.song_number:
                     continue
                 song_detail = u'%s - %s (%s)' % (book.name, song.song_number, song.title)
-                song_name = QtGui.QListWidgetItem(song_detail)
-                song_name.setData(QtCore.Qt.UserRole, song.id)
-                self.listView.addItem(song_name)
+                song_name = QtGui.QTreeWidgetItem(song_detail)
+                song_name.setText(0, song_detail)
+                song_name.setData(0, QtCore.Qt.UserRole, song.id)
+                self.listView.addTopLevelItem(song_name)
 
     def onClearTextButtonClick(self):
         """
@@ -352,7 +355,7 @@
         log.debug(u'onEditClick')
         if check_item_selected(self.listView, UiStrings().SelectEdit):
             self.editItem = self.listView.currentItem()
-            item_id = self.editItem.data(QtCore.Qt.UserRole)
+            item_id = self.editItem.data(0, QtCore.Qt.UserRole)
             self.editSongForm.loadSong(item_id, False)
             self.editSongForm.exec_()
             self.autoSelectId = -1
@@ -401,7 +404,7 @@
         log.debug(u'onCloneClick')
         if check_item_selected(self.listView, UiStrings().SelectEdit):
             self.editItem = self.listView.currentItem()
-            item_id = self.editItem.data(QtCore.Qt.UserRole)
+            item_id = self.editItem.data(0, QtCore.Qt.UserRole)
             old_song = self.plugin.manager.get_object(Song, item_id)
             song_xml = self.openLyrics.song_to_xml(old_song)
             new_song = self.openLyrics.xml_to_song(song_xml)

=== added file 'resources/forms/imagesaddgroupdialog.ui'
--- resources/forms/imagesaddgroupdialog.ui	1970-01-01 00:00:00 +0000
+++ resources/forms/imagesaddgroupdialog.ui	2013-01-24 09:36:22 +0000
@@ -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>

=== added file 'resources/forms/imageschoosegroupdialog.ui'
--- resources/forms/imageschoosegroupdialog.ui	1970-01-01 00:00:00 +0000
+++ resources/forms/imageschoosegroupdialog.ui	2013-01-24 09:36:22 +0000
@@ -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>

=== added file 'resources/images/image_new_group.png'
Binary files resources/images/image_new_group.png	1970-01-01 00:00:00 +0000 and resources/images/image_new_group.png	2013-01-24 09:36:22 +0000 differ
=== modified file 'resources/images/openlp-2.qrc'
--- resources/images/openlp-2.qrc	2012-12-06 19:26:50 +0000
+++ resources/images/openlp-2.qrc	2013-01-24 09:36:22 +0000
@@ -21,6 +21,9 @@
     <file>song_topic_edit.png</file>
     <file>song_book_edit.png</file>
   </qresource>
+  <qresource prefix="images">
+    <file>image_new_group.png</file>
+  </qresource>
   <qresource prefix="bibles">
     <file>bibles_search_text.png</file>
     <file>bibles_search_reference.png</file>


Follow ups