diff --git a/documentation/manual/source/glossary.rst b/documentation/manual/source/glossary.rst index 41a8f4ac6..6f4ebcdd6 100644 --- a/documentation/manual/source/glossary.rst +++ b/documentation/manual/source/glossary.rst @@ -48,7 +48,7 @@ Service Manger -------------- The service manager contains the media items in your service file. This is the -area from wich your media items go live, and you can also save, open, and edit +area from which your media items go live, and you can also save, open, and edit services files. .. image:: pics/servicemanager.png diff --git a/documentation/manual/source/index.rst b/documentation/manual/source/index.rst index ac29c4360..5786af1ae 100644 --- a/documentation/manual/source/index.rst +++ b/documentation/manual/source/index.rst @@ -14,6 +14,8 @@ Contents: introduction glossary dualmonitors + mediamanager + songs Indices and tables ================== diff --git a/documentation/manual/source/mediamanager.rst b/documentation/manual/source/mediamanager.rst new file mode 100644 index 000000000..2cf1d2c57 --- /dev/null +++ b/documentation/manual/source/mediamanager.rst @@ -0,0 +1,26 @@ +============= +Media Manager +============= + +Once you get your system set up for OpenLP you will be ready to add content to +your setup. This will all happen through the **Media Manager**. The +`Media Manager` contains all the bibles, songs, presentations, media, and +everything else that you will project through OpenLP. + +Enabling the Plugins +-------------------- + +You may need to enable the plugins that came with OpenLP. As you can see below +this is what the `Media Manager` looks like with all the plugins enabled. + +.. image:: pics/mediamanager.png + +To enable the plugins navigate to :menuselection:`Settings --> Plugins` or +press `F7`. You will then want to click on the plugin to the left that you want +to enable and select **active** from the drop down box to the right. + +.. image:: pics/plugins.png + + +Now you should be ready to add content to OpenLP check out the section of this +guide on the individual plugins. diff --git a/documentation/manual/source/pics/finishedimport.png b/documentation/manual/source/pics/finishedimport.png new file mode 100644 index 000000000..d49876ec9 Binary files /dev/null and b/documentation/manual/source/pics/finishedimport.png differ diff --git a/documentation/manual/source/pics/plugins.png b/documentation/manual/source/pics/plugins.png new file mode 100644 index 000000000..b6810242b Binary files /dev/null and b/documentation/manual/source/pics/plugins.png differ diff --git a/documentation/manual/source/pics/selectsongs.png b/documentation/manual/source/pics/selectsongs.png new file mode 100644 index 000000000..20df3ba2a Binary files /dev/null and b/documentation/manual/source/pics/selectsongs.png differ diff --git a/documentation/manual/source/pics/songimporter.png b/documentation/manual/source/pics/songimporter.png new file mode 100644 index 000000000..96c39ea38 Binary files /dev/null and b/documentation/manual/source/pics/songimporter.png differ diff --git a/documentation/manual/source/pics/songimporterchoices.png b/documentation/manual/source/pics/songimporterchoices.png new file mode 100644 index 000000000..5c458838e Binary files /dev/null and b/documentation/manual/source/pics/songimporterchoices.png differ diff --git a/documentation/manual/source/pics/songselectlyrics.png b/documentation/manual/source/pics/songselectlyrics.png new file mode 100644 index 000000000..2e3d92f0d Binary files /dev/null and b/documentation/manual/source/pics/songselectlyrics.png differ diff --git a/documentation/manual/source/pics/songselectsongsearch.png b/documentation/manual/source/pics/songselectsongsearch.png new file mode 100644 index 000000000..de0ea12ca Binary files /dev/null and b/documentation/manual/source/pics/songselectsongsearch.png differ diff --git a/documentation/manual/source/pics/songusage.png b/documentation/manual/source/pics/songusage.png new file mode 100644 index 000000000..10f29f2a9 Binary files /dev/null and b/documentation/manual/source/pics/songusage.png differ diff --git a/documentation/manual/source/pics/songusagedelete.png b/documentation/manual/source/pics/songusagedelete.png new file mode 100644 index 000000000..fec1b5e5d Binary files /dev/null and b/documentation/manual/source/pics/songusagedelete.png differ diff --git a/documentation/manual/source/pics/songusagereport.png b/documentation/manual/source/pics/songusagereport.png new file mode 100644 index 000000000..c0d9df3dd Binary files /dev/null and b/documentation/manual/source/pics/songusagereport.png differ diff --git a/documentation/manual/source/pics/vistadisplaysettings.png b/documentation/manual/source/pics/vistadisplaysettings.png old mode 100755 new mode 100644 diff --git a/documentation/manual/source/pics/vistapersonalize.png b/documentation/manual/source/pics/vistapersonalize.png old mode 100755 new mode 100644 diff --git a/documentation/manual/source/pics/winsevendisplay.png b/documentation/manual/source/pics/winsevendisplay.png old mode 100755 new mode 100644 diff --git a/documentation/manual/source/pics/winsevenresolution.png b/documentation/manual/source/pics/winsevenresolution.png old mode 100755 new mode 100644 diff --git a/documentation/manual/source/pics/xpdisplaysettings.png b/documentation/manual/source/pics/xpdisplaysettings.png index eb7a8921c..e1ec66c6f 100644 Binary files a/documentation/manual/source/pics/xpdisplaysettings.png and b/documentation/manual/source/pics/xpdisplaysettings.png differ diff --git a/documentation/manual/source/songs.rst b/documentation/manual/source/songs.rst new file mode 100644 index 000000000..678a6206c --- /dev/null +++ b/documentation/manual/source/songs.rst @@ -0,0 +1,100 @@ +===== +Songs +===== + +Managing your songs in OpenLP is a relatively simple process. There are also +converters provided to get data from other formats into OpenLP. + +Song Importer +============= + +If you are using an earlier version of OpenLP or come from another software +package, you may be able to convert your existing database to work in OpenLP +2.0. To access the Song Importer :menuselection:`File --> Import --> Song`. +You will then see the Song Importer window, then click :guilabel:`Next`. + +.. image:: pics/songimporter.png + +After choosing :guilabel:`Next` you can then select from the various types of +software that OpenLP will convert songs from. + +.. image:: pics/songimporterchoices.png + +Then click on the file folder icon to choose the file of the song database you +want to import. See the following sections for information on the different +formats that OpenLP will import. + +Importing from OpenLP Version 1 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Converting from OpenLP Version 1 is a pretty simple process. You will first +need to locate your version 1 database file. + +Windows XP:: + + C:\Documents and Settings\All Users\Application Data\openlp.org\Data\songs.olp + +Windows Vista / Windows 7:: + + C:\ProgramData\openlp.org\Data\songs.olp + +After clicking :guilabel:`Next` your conversion should be complete. + +.. image:: pics/finishedimport.png + +Then press :guilabel:`Finish` and you should now be ready to use your OpenLP +version one songs. + +Importing from OpenSong +^^^^^^^^^^^^^^^^^^^^^^^ + +Converting from OpenSong you will need to locate your songs database. In the +later versions of OpenSong you are asked to define the location of this. The +songs will be located in a folder named :guilabel:`Songs`. This folder should +contain files with all your songs in them without a file extension. (file.xxx). +When you have located this folder you will then need to select the songs from +the folder. + +.. image:: pics/selectsongs.png + +On most operating systems to select all the songs, first select the first song +in the lest then press shift and select the last song in the list. After this +press :guilabel:`Next` and you should see that your import has been successful. + +.. image:: pics/finishedimport.png + +Press :guilabel:`Finish` and you will now be ready to use your songs imported +from OpenSong. + +Importing from CCLI Song Select +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To import from CCLI Song Select you must be a CCLI Subscriber and also a +subscriber of the Song Select service. For info on that go to: +http://www.ccli.com + +The first step for importing from CCLI Song Select is to log into your account. +Then search for your desired song. For this example we will be adding the song +"Amazing Grace". + +.. image:: pics/songselectsongsearch.png + +For the song you are searching for select `lyrics` This should take you to a +page displaying the lyrics and copyright info for your song. + +.. image:: pics/songselectlyrics.png + +Next, hover over the :guilabel:`Lyrics` menu from the upper right corner. Then +choose either the .txt or .usr file. You will then be asked to chose a download +location if your browser does not automatically select that for you. Select +this file from the OpenLP import window and then click :guilabel:`Next` You can +also select multiple songs for import at once on most operating systems by +selecting the first item in the list then holding shift select the last item in +the list. When finished you should see that your import has completed. + +.. image:: pics/finishedimport.png + +Press :guilabel:`Finish` and you will now be ready to use your songs imported +from CCLI SongSelect. + + diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index b6946a628..a90a4c194 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -38,48 +38,51 @@ log = logging.getLogger(__name__) # TODO make external and configurable in alpha 4 via a settings dialog html_expands = [] -html_expands.append({u'desc':u'Red', u'start tag':u'{r}', - u'start html':u'', - u'end tag':u'{/r}', u'end html':u'', u'protected':False}) -html_expands.append({u'desc':u'Black', u'start tag':u'{b}', - u'start html':u'', - u'end tag':u'{/b}', u'end html':u'', u'protected':False}) -html_expands.append({u'desc':u'Blue', u'start tag':u'{bl}', - u'start html':u'', - u'end tag':u'{/bl}', u'end html':u'', u'protected':False}) -html_expands.append({u'desc':u'Yellow', u'start tag':u'{y}', - u'start html':u'', - u'end tag':u'{/y}', u'end html':u'', u'protected':False}) -html_expands.append({u'desc':u'Green', u'start tag':u'{g}', - u'start html':u'', - u'end tag':u'{/g}', u'end html':u'', u'protected':False}) -html_expands.append({u'desc':u'Pink', u'start tag':u'{pk}', - u'start html':u'', - u'end tag':u'{/pk}', u'end html':u'', u'protected':False}) -html_expands.append({u'desc':u'Orange', u'start tag':u'{o}', - u'start html':u'', - u'end tag':u'{/o}', u'end html':u'', u'protected':False}) -html_expands.append({u'desc':u'Purple', u'start tag':u'{pp}', - u'start html':u'', - u'end tag':u'{/pp}', u'end html':u'', u'protected':False}) -html_expands.append({u'desc':u'White', u'start tag':u'{w}', - u'start html':u'', - u'end tag':u'{/w}', u'end html':u'', u'protected':False}) -html_expands.append({u'desc':u'Superscript', u'start tag':u'{su}', - u'start html':u'', u'end tag':u'{/su}', u'end html':u'', - u'protected':True}) -html_expands.append({u'desc':u'Subscript', u'start tag':u'{sb}', - u'start html':u'', u'end tag':u'{/sb}', u'end html':u'', - u'protected':True}) -html_expands.append({u'desc':u'Paragraph', u'start tag':u'{p}', - u'start html':u'

', u'end tag':u'{/p}', u'end html':u'

', - u'protected':True}) -html_expands.append({u'desc':u'Bold', u'start tag':u'{st}', - u'start html':u'', u'end tag':u'{/st}', u'end html':u'', - u'protected':True}) -html_expands.append({u'desc':u'Italics', u'start tag':u'{it}', - u'start html':u'', u'end tag':u'{/it}', u'end html':u'', - u'protected':True}) +html_expands.append({u'desc': u'Red', u'start tag': u'{r}', + u'start html': u'', + u'end tag': u'{/r}', u'end html': u'', u'protected': False}) +html_expands.append({u'desc': u'Black', u'start tag': u'{b}', + u'start html': u'', + u'end tag': u'{/b}', u'end html': u'', u'protected': False}) +html_expands.append({u'desc': u'Blue', u'start tag': u'{bl}', + u'start html': u'', + u'end tag': u'{/bl}', u'end html': u'', u'protected': False}) +html_expands.append({u'desc': u'Yellow', u'start tag': u'{y}', + u'start html': u'', + u'end tag': u'{/y}', u'end html': u'', u'protected': False}) +html_expands.append({u'desc': u'Green', u'start tag': u'{g}', + u'start html': u'', + u'end tag': u'{/g}', u'end html': u'', u'protected': False}) +html_expands.append({u'desc': u'Pink', u'start tag': u'{pk}', + u'start html': u'', + u'end tag': u'{/pk}', u'end html': u'', u'protected': False}) +html_expands.append({u'desc': u'Orange', u'start tag': u'{o}', + u'start html': u'', + u'end tag': u'{/o}', u'end html': u'', u'protected': False}) +html_expands.append({u'desc': u'Purple', u'start tag': u'{pp}', + u'start html': u'', + u'end tag': u'{/pp}', u'end html': u'', u'protected': False}) +html_expands.append({u'desc': u'White', u'start tag': u'{w}', + u'start html': u'', + u'end tag': u'{/w}', u'end html': u'', u'protected': False}) +html_expands.append({u'desc': u'Superscript', u'start tag': u'{su}', + u'start html': u'', u'end tag': u'{/su}', u'end html': u'', + u'protected': True}) +html_expands.append({u'desc': u'Subscript', u'start tag': u'{sb}', + u'start html': u'', u'end tag': u'{/sb}', u'end html': u'', + u'protected': True}) +html_expands.append({u'desc': u'Paragraph', u'start tag': u'{p}', + u'start html': u'

', u'end tag': u'{/p}', u'end html': u'

', + u'protected': True}) +html_expands.append({u'desc': u'Bold', u'start tag': u'{st}', + u'start html': u'', u'end tag': u'{/st}', u'end html': u'', + u'protected': True}) +html_expands.append({u'desc': u'Italics', u'start tag': u'{it}', + u'start html': u'', u'end tag': u'{/it}', u'end html': u'', + u'protected': True}) +html_expands.append({u'desc': u'Underline', u'start tag': u'{u}', + u'start html': u'', + u'end tag': u'{/u}', u'end html': u'', u'protected': True}) def translate(context, text, comment=None): """ diff --git a/openlp/core/lib/db.py b/openlp/core/lib/db.py index 0dc138abc..8afa02111 100644 --- a/openlp/core/lib/db.py +++ b/openlp/core/lib/db.py @@ -294,4 +294,5 @@ class Manager(object): """ if self.is_dirty: engine = create_engine(self.db_url) - engine.execute("vacuum") + if self.db_url.startswith(u'sqlite'): + engine.execute("vacuum") diff --git a/openlp/core/lib/mediamanageritem.py b/openlp/core/lib/mediamanageritem.py index 2d563962e..130da0c0e 100644 --- a/openlp/core/lib/mediamanageritem.py +++ b/openlp/core/lib/mediamanageritem.py @@ -320,15 +320,9 @@ class MediaManagerItem(QtGui.QWidget): translate('OpenLP.MediaManagerItem', '&Add to selected Service Item'), self.onAddEditClick)) - if QtCore.QSettings().value(u'advanced/double click live', - QtCore.QVariant(False)).toBool(): - QtCore.QObject.connect(self.listView, - QtCore.SIGNAL(u'doubleClicked(QModelIndex)'), - self.onLiveClick) - else: - QtCore.QObject.connect(self.listView, - QtCore.SIGNAL(u'doubleClicked(QModelIndex)'), - self.onPreviewClick) + QtCore.QObject.connect(self.listView, + QtCore.SIGNAL(u'doubleClicked(QModelIndex)'), + self.onClickPressed) def initialise(self): """ @@ -426,10 +420,20 @@ class MediaManagerItem(QtGui.QWidget): raise NotImplementedError(u'MediaManagerItem.onDeleteClick needs to ' u'be defined by the plugin') - def generateSlideData(self, service_item, item=None): + def generateSlideData(self, serviceItem, item=None, xmlVersion=False): raise NotImplementedError(u'MediaManagerItem.generateSlideData needs ' u'to be defined by the plugin') + def onClickPressed(self): + """ + Allows the list click action to be determined dynamically + """ + if QtCore.QSettings().value(u'advanced/double click live', + QtCore.QVariant(False)).toBool(): + self.onLiveClick() + else: + self.onPreviewClick() + def onPreviewClick(self): """ Preview an item by building a service item then adding that service @@ -442,10 +446,10 @@ class MediaManagerItem(QtGui.QWidget): 'You must select one or more items to preview.')) else: log.debug(self.plugin.name + u' Preview requested') - service_item = self.buildServiceItem() - if service_item: - service_item.from_plugin = True - self.parent.previewController.addServiceItem(service_item) + serviceItem = self.buildServiceItem() + if serviceItem: + serviceItem.from_plugin = True + self.parent.previewController.addServiceItem(serviceItem) def onLiveClick(self): """ @@ -459,10 +463,10 @@ class MediaManagerItem(QtGui.QWidget): 'You must select one or more items to send live.')) else: log.debug(self.plugin.name + u' Live requested') - service_item = self.buildServiceItem() - if service_item: - service_item.from_plugin = True - self.parent.liveController.addServiceItem(service_item) + serviceItem = self.buildServiceItem() + if serviceItem: + serviceItem.from_plugin = True + self.parent.liveController.addServiceItem(serviceItem) def onAddClick(self): """ @@ -474,22 +478,22 @@ class MediaManagerItem(QtGui.QWidget): translate('OpenLP.MediaManagerItem', 'You must select one or more items.')) else: - # Is it posssible to process multiple list items to generate multiple - # service items? + # Is it posssible to process multiple list items to generate + # multiple service items? if self.singleServiceItem or self.remoteTriggered: log.debug(self.plugin.name + u' Add requested') - service_item = self.buildServiceItem() - if service_item: - service_item.from_plugin = False - self.parent.serviceManager.addServiceItem(service_item, + serviceItem = self.buildServiceItem(None, True) + if serviceItem: + serviceItem.from_plugin = False + self.parent.serviceManager.addServiceItem(serviceItem, replace=self.remoteTriggered) else: items = self.listView.selectedIndexes() for item in items: - service_item = self.buildServiceItem(item) - if service_item: - service_item.from_plugin = False - self.parent.serviceManager.addServiceItem(service_item) + serviceItem = self.buildServiceItem(item, True) + if serviceItem: + serviceItem.from_plugin = False + self.parent.serviceManager.addServiceItem(serviceItem) def onAddEditClick(self): """ @@ -502,36 +506,36 @@ class MediaManagerItem(QtGui.QWidget): 'You must select one or more items')) else: log.debug(self.plugin.name + u' Add requested') - service_item = self.parent.serviceManager.getServiceItem() - if not service_item: + serviceItem = self.parent.serviceManager.getServiceItem() + if not serviceItem: QtGui.QMessageBox.information(self, translate('OpenLP.MediaManagerItem', 'No Service Item Selected'), translate('OpenLP.MediaManagerItem', 'You must select an existing service item to add to.')) - elif self.title.lower() == service_item.name.lower(): - self.generateSlideData(service_item) - self.parent.serviceManager.addServiceItem(service_item, + elif self.title.lower() == serviceItem.name.lower(): + self.generateSlideData(serviceItem) + self.parent.serviceManager.addServiceItem(serviceItem, replace=True) else: - #Turn off the remote edit update message indicator + # Turn off the remote edit update message indicator QtGui.QMessageBox.information(self, translate('OpenLP.MediaManagerItem', 'Invalid Service Item'), unicode(translate('OpenLP.MediaManagerItem', 'You must select a %s service item.')) % self.title) - def buildServiceItem(self, item=None): + def buildServiceItem(self, item=None, xmlVersion=False): """ Common method for generating a service item """ - service_item = ServiceItem(self.parent) + serviceItem = ServiceItem(self.parent) if self.serviceItemIconName: - service_item.add_icon(self.serviceItemIconName) + serviceItem.add_icon(self.serviceItemIconName) else: - service_item.add_icon(self.parent.icon_path) - if self.generateSlideData(service_item, item): - return service_item + serviceItem.add_icon(self.parent.icon_path) + if self.generateSlideData(serviceItem, item, xmlVersion): + return serviceItem else: return None diff --git a/openlp/core/lib/renderer.py b/openlp/core/lib/renderer.py index 9b8b78818..68839f16d 100644 --- a/openlp/core/lib/renderer.py +++ b/openlp/core/lib/renderer.py @@ -32,7 +32,7 @@ import logging from PyQt4 import QtWebKit from openlp.core.lib import expand_tags, build_lyrics_format_css, \ - build_lyrics_outline_css + build_lyrics_outline_css, Receiver log = logging.getLogger(__name__) @@ -92,13 +92,20 @@ class Renderer(object): (build_lyrics_format_css(self._theme, self.page_width, self.page_height), build_lyrics_outline_css(self._theme)) - def format_slide(self, words, line_break): + def format_slide(self, words, line_break, force_page=False): """ Figure out how much text can appear on a slide, using the current theme settings. ``words`` The words to be fitted on the slide. + + ``line_break`` + Add line endings after each line of text used for bibles. + + ``force_page`` + Flag to tell message lines in page. + """ log.debug(u'format_slide - Start') line_end = u'' @@ -114,13 +121,19 @@ class Renderer(object): formatted = [] html_text = u'' styled_text = u'' + line_count = 0 for line in text: + if line_count != -1: + line_count += 1 styled_line = expand_tags(line) + line_end styled_text += styled_line html = self.page_shell + styled_text + u'' self.web.setHtml(html) # Text too long so go to next page if self.web_frame.contentsSize().height() > self.page_height: + if force_page and line_count > 0: + Receiver.send_message(u'theme_line_count', line_count) + line_count = -1 if html_text.endswith(u'
'): html_text = html_text[:len(html_text)-4] formatted.append(html_text) diff --git a/openlp/core/lib/rendermanager.py b/openlp/core/lib/rendermanager.py index bf561b4b3..81cde12a0 100644 --- a/openlp/core/lib/rendermanager.py +++ b/openlp/core/lib/rendermanager.py @@ -67,8 +67,9 @@ class RenderManager(object): self.service_theme = u'' self.theme_level = u'' self.override_background = None - self.themedata = None + self.theme_data = None self.alertTab = None + self.force_page = False def update_display(self): """ @@ -80,7 +81,7 @@ class RenderManager(object): self.display.imageManager = self.image_manager self.display.setup() self.renderer.bg_frame = None - self.themedata = None + self.theme_data = None self.image_manager.update_display(self.width, self.height) def set_global_theme(self, global_theme, theme_level=ThemeLevel.Global): @@ -99,7 +100,7 @@ class RenderManager(object): self.theme_level = theme_level self.global_theme_data = \ self.theme_manager.getThemeData(self.global_theme) - self.themedata = None + self.theme_data = None def set_service_theme(self, service_theme): """ @@ -109,7 +110,7 @@ class RenderManager(object): The service-level theme to be set. """ self.service_theme = service_theme - self.themedata = None + self.theme_data = None def set_override_theme(self, theme, overrideLevels=False): """ @@ -146,19 +147,19 @@ class RenderManager(object): self.theme = self.service_theme else: self.theme = self.global_theme - if self.theme != self.renderer.theme_name or self.themedata is None \ + if self.theme != self.renderer.theme_name or self.theme_data is None \ or overrideLevels: log.debug(u'theme is now %s', self.theme) # Force the theme to be the one passed in. if overrideLevels: - self.themedata = theme + self.theme_data = theme else: - self.themedata = self.theme_manager.getThemeData(self.theme) + self.theme_data = self.theme_manager.getThemeData(self.theme) self.calculate_default(self.screens.current[u'size']) - self.renderer.set_theme(self.themedata) - self.build_text_rectangle(self.themedata) - self.image_manager.add_image(self.themedata.theme_name, - self.themedata.background_filename) + self.renderer.set_theme(self.theme_data) + self.build_text_rectangle(self.theme_data) + self.image_manager.add_image(self.theme_data.theme_name, + self.theme_data.background_filename) return self.renderer._rect, self.renderer._rect_footer def build_text_rectangle(self, theme): @@ -187,14 +188,19 @@ class RenderManager(object): theme.font_footer_height - 1) self.renderer.set_text_rectangle(main_rect, footer_rect) - def generate_preview(self, themedata): + def generate_preview(self, theme_data, force_page=False): """ Generate a preview of a theme. - ``themedata`` + ``theme_data`` The theme to generated a preview for. + + ``force_page`` + Flag to tell message lines per page need to be generated. """ log.debug(u'generate preview') + # save value for use in format_slide + self.force_page = force_page # set the default image size for previews self.calculate_default(self.screens.preview[u'size']) verse = u'The Lord said to {r}Noah{/r}: \n' \ @@ -204,23 +210,27 @@ class RenderManager(object): 'Get those children out of the muddy, muddy \n' \ '{r}C{/r}{b}h{/b}{bl}i{/bl}{y}l{/y}{g}d{/g}{pk}' \ 'r{/pk}{o}e{/o}{pp}n{/pp} of the Lord\n' + # make big page for theme edit dialog to get line count + if self.force_page: + verse = verse + verse + verse footer = [] footer.append(u'Arky Arky (Unknown)' ) footer.append(u'Public Domain') footer.append(u'CCLI 123456') # build a service item to generate preview serviceItem = ServiceItem() - serviceItem.theme = themedata + serviceItem.theme = theme_data serviceItem.add_from_text(u'', verse, footer) serviceItem.render_manager = self serviceItem.raw_footer = footer serviceItem.render(True) - self.display.buildHtml(serviceItem) - raw_html = serviceItem.get_rendered_frame(0) - preview = self.display.text(raw_html) - # Reset the real screen size for subsequent render requests - self.calculate_default(self.screens.current[u'size']) - return preview + if not self.force_page: + self.display.buildHtml(serviceItem) + raw_html = serviceItem.get_rendered_frame(0) + preview = self.display.text(raw_html) + # Reset the real screen size for subsequent render requests + self.calculate_default(self.screens.current[u'size']) + return preview def format_slide(self, words, line_break): """ @@ -228,9 +238,12 @@ class RenderManager(object): ``words`` The words to go on the slides. + + ``line_break`` + Add line endings after each line of text used for bibles. """ log.debug(u'format slide') - return self.renderer.format_slide(words, line_break) + return self.renderer.format_slide(words, line_break, self.force_page) def calculate_default(self, screen): """ diff --git a/openlp/core/lib/serviceitem.py b/openlp/core/lib/serviceitem.py index ab57e4328..b9394030a 100644 --- a/openlp/core/lib/serviceitem.py +++ b/openlp/core/lib/serviceitem.py @@ -100,6 +100,8 @@ class ServiceItem(object): self.bg_image_bytes = None self.search_string = u'' self.data_string = u'' + self.edit_id = None + self.xml_version = None self._new_item() def _new_item(self): @@ -251,7 +253,8 @@ class ServiceItem(object): u'from_plugin': self.from_plugin, u'capabilities': self.capabilities, u'search': self.search_string, - u'data': self.data_string + u'data': self.data_string, + u'xml_version': self.xml_version } service_data = [] if self.service_item_type == ServiceItemType.Text: @@ -293,6 +296,8 @@ class ServiceItem(object): if u'search' in header: self.search_string = header[u'search'] self.data_string = header[u'data'] + if u'xml_version' in header: + self.xml_version = header[u'xml_version'] if self.service_item_type == ServiceItemType.Text: for slide in serviceitem[u'serviceitem'][u'data']: self._raw_frames.append(slide) diff --git a/openlp/core/ui/advancedtab.py b/openlp/core/ui/advancedtab.py index d6289a91d..a390780ca 100644 --- a/openlp/core/ui/advancedtab.py +++ b/openlp/core/ui/advancedtab.py @@ -81,9 +81,9 @@ class AdvancedTab(SettingsTab): self.doubleClickLiveCheckBox = QtGui.QCheckBox(self.uiGroupBox) self.doubleClickLiveCheckBox.setObjectName(u'doubleClickLiveCheckBox') self.uiLayout.addWidget(self.doubleClickLiveCheckBox) - self.expandServiceItemCheckBox = QtGui.QCheckBox(self.uiGroupBox) - self.expandServiceItemCheckBox.setObjectName(u'expandServiceItemCheckBox') - self.uiLayout.addWidget(self.expandServiceItemCheckBox) +# self.expandServiceItemCheckBox = QtGui.QCheckBox(self.uiGroupBox) +# self.expandServiceItemCheckBox.setObjectName(u'expandServiceItemCheckBox') +# self.uiLayout.addWidget(self.expandServiceItemCheckBox) self.leftLayout.addWidget(self.uiGroupBox) self.expandServiceItemCheckBox = QtGui.QCheckBox(self.uiGroupBox) self.expandServiceItemCheckBox.setObjectName( @@ -146,7 +146,7 @@ class AdvancedTab(SettingsTab): self.mediaPluginCheckBox.setText(translate('OpenLP.AdvancedTab', 'Remember active media manager tab on startup')) self.doubleClickLiveCheckBox.setText(translate('OpenLP.AdvancedTab', - 'Double-click to send items straight to live (requires restart)')) + 'Double-click to send items straight to live')) self.expandServiceItemCheckBox.setText(translate('OpenLP.AdvancedTab', 'Expand new service items on creation')) # self.sharedDirGroupBox.setTitle( diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index 84b484878..90f920a50 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -36,6 +36,7 @@ from openlp.core.ui import HideMode log = logging.getLogger(__name__) #http://www.steveheffernan.com/html5-video-player/demo-video-player.html +#http://html5demos.com/two-videos class DisplayWidget(QtGui.QGraphicsView): """ @@ -99,7 +100,7 @@ class MainDisplay(DisplayWidget): self.screens = screens self.isLive = live self.alertTab = None - self.hide_mode = None + self.hideMode = None self.setWindowTitle(u'OpenLP Display') self.setStyleSheet(u'border: 0px; margin: 0px; padding: 0px;') self.setWindowFlags(QtCore.Qt.FramelessWindowHint | @@ -380,8 +381,8 @@ class MainDisplay(DisplayWidget): if self.isLive: self.setVisible(True) # if was hidden keep it hidden - if self.hide_mode and self.isLive: - self.hideDisplay(self.hide_mode) + if self.hideMode and self.isLive: + self.hideDisplay(self.hideMode) preview = QtGui.QImage(self.screen[u'size'].width(), self.screen[u'size'].height(), QtGui.QImage.Format_ARGB32_Premultiplied) @@ -411,8 +412,8 @@ class MainDisplay(DisplayWidget): if serviceItem.foot_text and serviceItem.foot_text: self.footer(serviceItem.foot_text) # if was hidden keep it hidden - if self.hide_mode and self.isLive: - self.hideDisplay(self.hide_mode) + if self.hideMode and self.isLive: + self.hideDisplay(self.hideMode) def footer(self, text): """ @@ -443,7 +444,7 @@ class MainDisplay(DisplayWidget): self.setVisible(True) if self.phononActive: self.webView.setVisible(True) - self.hide_mode = mode + self.hideMode = mode def showDisplay(self): """ @@ -458,9 +459,9 @@ class MainDisplay(DisplayWidget): if self.phononActive: self.webView.setVisible(False) self.videoPlay() + self.hideMode = None # Trigger actions when display is active again Receiver.send_message(u'maindisplay_active') - self.hide_mode = None class AudioPlayer(QtCore.QObject): """ diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 255859abe..6d721823a 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -308,7 +308,7 @@ class ServiceManager(QtGui.QWidget): self.maintainAction.setVisible(False) self.notesAction.setVisible(False) if serviceItem[u'service_item'].is_capable(ItemCapabilities.AllowsEdit)\ - and hasattr(serviceItem[u'service_item'], u'editId'): + and serviceItem[u'service_item'].edit_id: self.editAction.setVisible(True) if serviceItem[u'service_item']\ .is_capable(ItemCapabilities.AllowsMaintain): @@ -789,6 +789,8 @@ class ServiceManager(QtGui.QWidget): self.serviceName = name[len(name) - 1] self.parent.addRecentFile(filename) self.parent.serviceChanged(True, self.serviceName) + # Refresh Plugin lists + Receiver.send_message(u'plugin_list_refresh') def validateItem(self, serviceItem): """ @@ -864,7 +866,7 @@ class ServiceManager(QtGui.QWidget): editId, uuid = message.split(u':') for item in self.serviceItems: if item[u'service_item']._uuid == uuid: - item[u'service_item'].editId = editId + item[u'service_item'].edit_id = editId def replaceServiceItem(self, newItem): """ @@ -873,7 +875,7 @@ class ServiceManager(QtGui.QWidget): """ newItem.render() for itemcount, item in enumerate(self.serviceItems): - if item[u'service_item'].editId == newItem.editId and \ + if item[u'service_item'].edit_id == newItem.edit_id and \ item[u'service_item'].name == newItem.name: newItem.merge(item[u'service_item']) item[u'service_item'] = newItem @@ -983,7 +985,7 @@ class ServiceManager(QtGui.QWidget): .is_capable(ItemCapabilities.AllowsEdit): Receiver.send_message(u'%s_edit' % self.serviceItems[item][u'service_item'].name.lower(), u'L:%s' % - self.serviceItems[item][u'service_item'].editId ) + self.serviceItems[item][u'service_item'].edit_id ) def findServiceItem(self): """ @@ -1028,6 +1030,9 @@ class ServiceManager(QtGui.QWidget): # ServiceManager started the drag and drop if plugin == u'ServiceManager': startpos, startCount = self.findServiceItem() + # If no items selected + if startpos == -1: + return if item is None: endpos = len(self.serviceItems) else: diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 61fc78cd3..48810990a 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -331,10 +331,8 @@ class SlideController(QtGui.QWidget): QtCore.QObject.connect(self.PreviewListWidget, QtCore.SIGNAL(u'clicked(QModelIndex)'), self.onSlideSelected) if not self.isLive: - if QtCore.QSettings().value(u'advanced/double click live', - QtCore.QVariant(False)).toBool(): - QtCore.QObject.connect(self.PreviewListWidget, - QtCore.SIGNAL(u'doubleClicked(QModelIndex)'), self.onGoLive) + QtCore.QObject.connect(self.PreviewListWidget, + QtCore.SIGNAL(u'doubleClicked(QModelIndex)'), self.onGoLiveClick) if isLive: QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'slidecontroller_live_spin_delay'), @@ -391,6 +389,8 @@ class SlideController(QtGui.QWidget): if self.isLive: QtCore.QObject.connect(self.volumeSlider, QtCore.SIGNAL(u'sliderReleased()'), self.mediaVolume) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'maindisplay_active'), self.updatePreview) def screenSizeChanged(self): """ @@ -823,16 +823,15 @@ class SlideController(QtGui.QWidget): row) def updatePreview(self): + log.debug(u'updatePreview %s ' %self.screens.current[u'primary']) if not self.screens.current[u'primary']: # Grab now, but try again in a couple of seconds if slide change # is slow QtCore.QTimer.singleShot(0.5, self.grabMainDisplay) QtCore.QTimer.singleShot(2.5, self.grabMainDisplay) else: - label = self.PreviewListWidget.cellWidget( - self.PreviewListWidget.currentRow(), 1) - if label: - self.SlidePreview.setPixmap(label.pixmap()) + self.SlidePreview.setPixmap( + QtGui.QPixmap.fromImage(self.display.preview())) def grabMainDisplay(self): winid = QtGui.QApplication.desktop().winId() @@ -942,7 +941,15 @@ class SlideController(QtGui.QWidget): """ self.songEdit = True Receiver.send_message(u'%s_edit' % self.serviceItem.name.lower(), - u'P:%s' % self.serviceItem.editId) + u'P:%s' % self.serviceItem.edit_id) + + def onGoLiveClick(self): + """ + triggered by clicking the Preview slide items + """ + if QtCore.QSettings().value(u'advanced/double click live', + QtCore.QVariant(False)).toBool(): + self.onGoLive() def onGoLive(self): """ diff --git a/openlp/core/ui/themeform.py b/openlp/core/ui/themeform.py index 5605ec261..e3aa99c9d 100644 --- a/openlp/core/ui/themeform.py +++ b/openlp/core/ui/themeform.py @@ -29,7 +29,8 @@ import os from PyQt4 import QtCore, QtGui -from openlp.core.lib import translate, BackgroundType, BackgroundGradientType +from openlp.core.lib import translate, BackgroundType, BackgroundGradientType, \ + Receiver from openlp.core.utils import get_images_filter from themewizard import Ui_ThemeWizard @@ -99,10 +100,40 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): QtCore.QObject.connect(self, QtCore.SIGNAL(u'currentIdChanged(int)'), self.pageChanged) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'theme_line_count'), + self.updateLinesText) + QtCore.QObject.connect(self.mainSizeSpinBox, + QtCore.SIGNAL(u'valueChanged(int)'), + self.calculateLines) + QtCore.QObject.connect(self.mainSizeSpinBox, + QtCore.SIGNAL(u'editingFinished()'), + self.calculateLines) + QtCore.QObject.connect(self.lineSpacingSpinBox, + QtCore.SIGNAL(u'valueChanged(int)'), + self.calculateLines) + QtCore.QObject.connect(self.lineSpacingSpinBox, + QtCore.SIGNAL(u'editingFinished()'), + self.calculateLines) + QtCore.QObject.connect(self.outlineSizeSpinBox, + QtCore.SIGNAL(u'valueChanged(int)'), + self.calculateLines) + QtCore.QObject.connect(self.outlineSizeSpinBox, + QtCore.SIGNAL(u'editingFinished()'), + self.calculateLines) + QtCore.QObject.connect(self.shadowSizeSpinBox, + QtCore.SIGNAL(u'valueChanged(int)'), + self.calculateLines) + QtCore.QObject.connect(self.shadowSizeSpinBox, + QtCore.SIGNAL(u'editingFinished()'), + self.calculateLines) + QtCore.QObject.connect(self.mainFontComboBox, + QtCore.SIGNAL(u'activated(int)'), + self.calculateLines) def pageChanged(self, pageId): """ - Detects Page changes and updates. + Detects Page changes and updates as approprate. """ if pageId == 6: self.updateTheme() @@ -189,6 +220,22 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): self.backgroundPage.registerField( u'name', self.themeNameEdit) + def calculateLines(self): + """ + Calculate the number of lines on a page by rendering text + """ + # Do not trigger on start up + if self.page != 0: + self.updateTheme() + frame = self.thememanager.generateImage(self.theme, True) + + def updateLinesText(self, lines): + """ + Updates the lines on a page on the wizard + """ + self.mainLineCountLabel.setText(unicode(translate('OpenLP.ThemeForm', \ + '(%d lines per slide)' % int(lines)))) + def onOutlineCheckCheckBoxChanged(self, state): """ Change state as Outline check box changed @@ -199,6 +246,7 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): self.theme.font_main_outline = False self.outlineColorPushButton.setEnabled(self.theme.font_main_outline) self.outlineSizeSpinBox.setEnabled(self.theme.font_main_outline) + self.calculateLines() def onShadowCheckCheckBoxChanged(self, state): """ @@ -210,6 +258,7 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): self.theme.font_main_shadow = False self.shadowColorPushButton.setEnabled(self.theme.font_main_shadow) self.shadowSizeSpinBox.setEnabled(self.theme.font_main_shadow) + self.calculateLines() def onMainDefaultPositionCheckBox(self, value): """ @@ -249,6 +298,7 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): Set up the pages for Initial run through dialog """ log.debug(u'initializePage %s' % id) + self.page = id if id == 1: self.setBackgroundTabValues() elif id == 2: @@ -542,15 +592,24 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): (QtGui.QMessageBox.Ok), QtGui.QMessageBox.Ok) return - save_from = None - save_to = None + if self.theme.theme_name == u'-1' or self.theme.theme_name == u'None': + QtGui.QMessageBox.critical(self, + translate('OpenLP.ThemeForm', 'Theme Name Invalid'), + translate('OpenLP.ThemeForm', + 'Invalid theme name. ' + 'Please enter one.'), + (QtGui.QMessageBox.Ok), + QtGui.QMessageBox.Ok) + return + saveFrom = None + saveTo = None if self.theme.background_type == \ BackgroundType.to_string(BackgroundType.Image): filename = \ os.path.split(unicode(self.theme.background_filename))[1] - save_to = os.path.join(self.path, self.theme.theme_name, filename) - save_from = self.theme.background_filename - if self.thememanager.saveTheme(self.theme, save_from, save_to): + saveTo = os.path.join(self.path, self.theme.theme_name, filename) + saveFrom = self.theme.background_filename + if self.thememanager.saveTheme(self.theme, saveFrom, saveTo): return QtGui.QDialog.accept(self) def _colorButton(self, field): diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index e5b8d1a0d..65913a9f6 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -181,7 +181,7 @@ class ThemeManager(QtGui.QWidget): '%s (default)')) % newName self.themeListWidget.item(count).setText(name) - def changeGlobalFromScreen(self, index = -1): + def changeGlobalFromScreen(self, index=-1): """ Change the global theme when a theme is double clicked upon in the Theme Manager list @@ -252,17 +252,14 @@ class ThemeManager(QtGui.QWidget): Takes a theme and makes a new copy of it as well as saving it. """ log.debug(u'cloneThemeData') - themeData.new_document(newThemeName) - themeData.build_xml_from_attrs() - save_to = None - save_from = None + saveTo = None + saveFrom = None if themeData.background_type == u'image': - save_to = os.path.join(self.path, newThemeName, + saveTo = os.path.join(self.path, newThemeName, os.path.split(unicode(themeData.background_filename))[1]) - save_from = themeData.background_filename - theme = themeData.extract_xml() - pretty_theme = themeData.extract_formatted_xml() - self.saveTheme(newThemeName, theme, pretty_theme, save_from, save_to) + saveFrom = themeData.background_filename + themeData.theme_name = newThemeName + self.saveTheme(themeData, saveFrom, saveTo) def onEditTheme(self): """ @@ -462,17 +459,17 @@ class ThemeManager(QtGui.QWidget): """ return self.themelist - def getThemeData(self, themename): + def getThemeData(self, themeName): """ Returns a theme object from an XML file - ``themename`` + ``themeName`` Name of the theme to load from file """ - log.debug(u'getthemedata for theme %s', themename) - xml_file = os.path.join(self.path, unicode(themename), - unicode(themename) + u'.xml') - xml = get_text_file_string(xml_file) + log.debug(u'getthemedata for theme %s', themeName) + xmlFile = os.path.join(self.path, unicode(themeName), + unicode(themeName) + u'.xml') + xml = get_text_file_string(xmlFile) if not xml: return self.baseTheme() else: @@ -640,7 +637,7 @@ class ThemeManager(QtGui.QWidget): newtheme.display_vertical_align = vAlignCorrection return newtheme.extract_xml() - def saveTheme(self, theme, image_from, image_to): + def saveTheme(self, theme, imageFrom, imageTo): """ Called by thememaintenance Dialog to save the theme and to trigger the reload of the theme list @@ -673,8 +670,8 @@ class ThemeManager(QtGui.QWidget): self.deleteTheme(self.saveThemeName) if result == QtGui.QMessageBox.Yes: # Save the theme, overwriting the existing theme if necessary. - if image_to and self.oldBackgroundImage and \ - image_to != self.oldBackgroundImage: + if imageTo and self.oldBackgroundImage and \ + imageTo != self.oldBackgroundImage: try: os.remove(self.oldBackgroundImage) except OSError: @@ -688,12 +685,12 @@ class ThemeManager(QtGui.QWidget): finally: if outfile: outfile.close() - if image_from and image_from != image_to: + if imageFrom and imageFrom != imageTo: try: encoding = get_filesystem_encoding() shutil.copyfile( - unicode(image_from).encode(encoding), - unicode(image_to).encode(encoding)) + unicode(imageFrom).encode(encoding), + unicode(imageTo).encode(encoding)) except IOError: log.exception(u'Failed to save theme image') self.generateAndSaveImage(self.path, name, theme) @@ -729,7 +726,6 @@ class ThemeManager(QtGui.QWidget): def generateAndSaveImage(self, dir, name, theme): log.debug(u'generateAndSaveImage %s %s', dir, name) - #theme = self.createThemeFromXml(theme_xml, dir) theme_xml = theme.extract_xml() frame = self.generateImage(theme) samplepathname = os.path.join(self.path, name + u'.png') @@ -742,12 +738,18 @@ class ThemeManager(QtGui.QWidget): pixmap.save(thumb, u'png') log.debug(u'Theme image written to %s', samplepathname) - def generateImage(self, themedata): + def generateImage(self, themeData, forcePage=False): """ Call the RenderManager to build a Sample Image + + ``themeData`` + The theme to generated a preview for. + + ``forcePage`` + Flag to tell message lines per page need to be generated. """ - log.debug(u'generateImage \n%s ', themedata) - return self.parent.RenderManager.generate_preview(themedata) + log.debug(u'generateImage \n%s ', themeData) + return self.parent.RenderManager.generate_preview(themeData, forcePage) def getPreviewImage(self, theme): """ @@ -766,16 +768,16 @@ class ThemeManager(QtGui.QWidget): """ log.debug(u'base theme created') newtheme = ThemeXML() - return newtheme.extract_xml() + return newtheme - def createThemeFromXml(self, theme_xml, path): + def createThemeFromXml(self, themeXml, path): """ Return a theme object using information parsed from XML - ``theme_xml`` + ``themeXml`` The XML data to load into the theme """ theme = ThemeXML() - theme.parse(theme_xml) + theme.parse(themeXml) theme.extend_image_filename(path) return theme diff --git a/openlp/core/ui/themestab.py b/openlp/core/ui/themestab.py index 853865bb5..fe1f95aee 100644 --- a/openlp/core/ui/themestab.py +++ b/openlp/core/ui/themestab.py @@ -183,9 +183,14 @@ class ThemesTab(SettingsTab): def updateThemeList(self, theme_list): """ - Called from ThemeManager when the Themes have changed + Called from ThemeManager when the Themes have changed. + + ``theme_list`` + The list of available themes:: + + [u'Bible Theme', u'Song Theme'] """ - #reload as may have been triggered by the ThemeManager + # Reload as may have been triggered by the ThemeManager. self.global_theme = unicode(QtCore.QSettings().value( self.settingsSection + u'/global theme', QtCore.QVariant(u'')).toString()) diff --git a/openlp/plugins/bibles/forms/bibleimportform.py b/openlp/plugins/bibles/forms/bibleimportform.py index 2e11c30ad..749f1f938 100644 --- a/openlp/plugins/bibles/forms/bibleimportform.py +++ b/openlp/plugins/bibles/forms/bibleimportform.py @@ -133,7 +133,7 @@ class BibleImportForm(QtGui.QWizard, Ui_BibleImportWizard): self.OSISLocationEdit.setFocus() return False elif self.field(u'source_format').toInt()[0] == BibleFormat.CSV: - if self.field(u'csv_booksfile').toString() == u'': + if not self.field(u'csv_booksfile').toString(): QtGui.QMessageBox.critical(self, translate('BiblesPlugin.ImportWizardForm', 'Invalid Books File'), @@ -142,7 +142,7 @@ class BibleImportForm(QtGui.QWizard, Ui_BibleImportWizard): 'the Bible to use in the import.')) self.BooksLocationEdit.setFocus() return False - elif self.field(u'csv_versefile').toString() == u'': + elif not self.field(u'csv_versefile').toString(): QtGui.QMessageBox.critical(self, translate('BiblesPlugin.ImportWizardForm', 'Invalid Verse File'), @@ -153,7 +153,7 @@ class BibleImportForm(QtGui.QWizard, Ui_BibleImportWizard): return False elif self.field(u'source_format').toInt()[0] == \ BibleFormat.OpenSong: - if self.field(u'opensong_file').toString() == u'': + if not self.field(u'opensong_file').toString(): QtGui.QMessageBox.critical(self, translate('BiblesPlugin.ImportWizardForm', 'Invalid OpenSong Bible'), @@ -168,7 +168,7 @@ class BibleImportForm(QtGui.QWizard, Ui_BibleImportWizard): license_version = unicode(self.field(u'license_version').toString()) license_copyright = \ unicode(self.field(u'license_copyright').toString()) - if license_version == u'': + if not license_version: QtGui.QMessageBox.critical(self, translate('BiblesPlugin.ImportWizardForm', 'Empty Version Name'), @@ -176,7 +176,7 @@ class BibleImportForm(QtGui.QWizard, Ui_BibleImportWizard): 'You need to specify a version name for your Bible.')) self.VersionNameEdit.setFocus() return False - elif license_copyright == u'': + elif not license_copyright: QtGui.QMessageBox.critical(self, translate('BiblesPlugin.ImportWizardForm', 'Empty Copyright'), @@ -207,9 +207,11 @@ class BibleImportForm(QtGui.QWizard, Ui_BibleImportWizard): The index of the combo box. """ self.BibleComboBox.clear() - for bible in self.web_bible_list[index].keys(): - self.BibleComboBox.addItem(unicode( - translate('BiblesPlugin.ImportWizardForm', bible))) + bibles = [unicode(translate('BiblesPlugin.ImportWizardForm', bible)) for + bible in self.web_bible_list[index].keys()] + bibles.sort() + for bible in bibles: + self.BibleComboBox.addItem(bible) def onOsisFileButtonClicked(self): """ @@ -317,7 +319,7 @@ class BibleImportForm(QtGui.QWizard, Ui_BibleImportWizard): """ Load the list of Crosswalk and BibleGateway bibles. """ - #Load and store Crosswalk Bibles + # Load and store Crosswalk Bibles. filepath = AppLocation.get_directory(AppLocation.PluginsDir) filepath = os.path.join(filepath, u'bibles', u'resources') books_file = None @@ -341,7 +343,7 @@ class BibleImportForm(QtGui.QWizard, Ui_BibleImportWizard): finally: if books_file: books_file.close() - #Load and store BibleGateway Bibles + # Load and store BibleGateway Bibles. books_file = None try: self.web_bible_list[WebDownload.BibleGateway] = {} @@ -379,12 +381,18 @@ class BibleImportForm(QtGui.QWizard, Ui_BibleImportWizard): Receiver.send_message(u'openlp_process_events') def preImport(self): + bible_type = self.field(u'source_format').toInt()[0] self.finishButton.setVisible(False) self.ImportProgressBar.setMinimum(0) self.ImportProgressBar.setMaximum(1188) self.ImportProgressBar.setValue(0) - self.ImportProgressLabel.setText( - translate('BiblesPlugin.ImportWizardForm', 'Starting import...')) + if bible_type == BibleFormat.WebDownload: + self.ImportProgressLabel.setText(translate( + 'BiblesPlugin.ImportWizardForm', + 'Starting Registering bible...')) + else: + self.ImportProgressLabel.setText(translate( + 'BiblesPlugin.ImportWizardForm', 'Starting import...')) Receiver.send_message(u'openlp_process_events') def performImport(self): @@ -395,26 +403,26 @@ class BibleImportForm(QtGui.QWizard, Ui_BibleImportWizard): unicode(self.field(u'license_permissions').toString()) importer = None if bible_type == BibleFormat.OSIS: - # Import an OSIS bible + # Import an OSIS bible. importer = self.manager.import_bible(BibleFormat.OSIS, name=license_version, filename=unicode(self.field(u'osis_location').toString()) ) elif bible_type == BibleFormat.CSV: - # Import a CSV bible + # Import a CSV bible. importer = self.manager.import_bible(BibleFormat.CSV, name=license_version, booksfile=unicode(self.field(u'csv_booksfile').toString()), versefile=unicode(self.field(u'csv_versefile').toString()) ) elif bible_type == BibleFormat.OpenSong: - # Import an OpenSong bible + # Import an OpenSong bible. importer = self.manager.import_bible(BibleFormat.OpenSong, name=license_version, filename=unicode(self.field(u'opensong_file').toString()) ) elif bible_type == BibleFormat.WebDownload: - # Import a bible from the web + # Import a bible from the web. self.ImportProgressBar.setMaximum(1) download_location = self.field(u'web_location').toInt()[0] bible_version = unicode(self.BibleComboBox.currentText()) @@ -438,8 +446,14 @@ class BibleImportForm(QtGui.QWizard, Ui_BibleImportWizard): self.manager.save_meta_data(license_version, license_version, license_copyright, license_permissions) self.manager.reload_bibles() - self.ImportProgressLabel.setText( - translate('BiblesPlugin.ImportWizardForm', 'Finished import.')) + if bible_type == BibleFormat.WebDownload: + self.ImportProgressLabel.setText( + translate('BiblesPlugin.ImportWizardForm', 'Registered ' + 'bible. Please note, that verses will be downloaded on\n' + 'demand and thus an internet connection is required.')) + else: + self.ImportProgressLabel.setText(translate( + 'BiblesPlugin.ImportWizardForm', 'Finished import.')) else: self.ImportProgressLabel.setText( translate('BiblesPlugin.ImportWizardForm', diff --git a/openlp/plugins/bibles/lib/biblestab.py b/openlp/plugins/bibles/lib/biblestab.py index b7d1b9bde..a52c31fb7 100644 --- a/openlp/plugins/bibles/lib/biblestab.py +++ b/openlp/plugins/bibles/lib/biblestab.py @@ -110,9 +110,9 @@ class BiblesTab(SettingsTab): self.BibleThemeComboBox.setObjectName(u'BibleThemeComboBox') self.BibleThemeComboBox.addItem(QtCore.QString()) self.BibleThemeLayout.addWidget(self.BibleThemeComboBox) - self.BibleDualCheckBox = QtGui.QCheckBox(self.VerseDisplayGroupBox) - self.BibleDualCheckBox.setObjectName(u'BibleDualCheckBox') - self.VerseDisplayLayout.addWidget(self.BibleDualCheckBox, 3, 0, 1, 1) + self.BibleSecondCheckBox = QtGui.QCheckBox(self.VerseDisplayGroupBox) + self.BibleSecondCheckBox.setObjectName(u'BibleSecondCheckBox') + self.VerseDisplayLayout.addWidget(self.BibleSecondCheckBox, 3, 0, 1, 1) self.VerseDisplayLayout.addWidget(self.BibleThemeWidget, 4, 0, 1, 1) self.ChangeNoteLabel = QtGui.QLabel(self.VerseDisplayGroupBox) self.ChangeNoteLabel.setObjectName(u'ChangeNoteLabel') @@ -143,8 +143,8 @@ class BiblesTab(SettingsTab): self.LayoutStyleComboBox, QtCore.SIGNAL(u'activated(int)'), self.onLayoutStyleComboBoxChanged) QtCore.QObject.connect( - self.BibleDualCheckBox, QtCore.SIGNAL(u'stateChanged(int)'), - self.onBibleDualCheckBox) + self.BibleSecondCheckBox, QtCore.SIGNAL(u'stateChanged(int)'), + self.onBibleSecondCheckBox) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'theme_update_list'), self.updateThemeList) @@ -176,8 +176,8 @@ class BiblesTab(SettingsTab): translate('BiblesPlugin.BiblesTab', '[ And ]')) self.ChangeNoteLabel.setText(translate('BiblesPlugin.BiblesTab', 'Note:\nChanges do not affect verses already in the service.')) - self.BibleDualCheckBox.setText( - translate('BiblesPlugin.BiblesTab', 'Display dual Bible verses')) + self.BibleSecondCheckBox.setText( + translate('BiblesPlugin.BiblesTab', 'Display second Bible verses')) def onBibleThemeComboBoxChanged(self): self.bible_theme = self.BibleThemeComboBox.currentText() @@ -190,15 +190,15 @@ class BiblesTab(SettingsTab): def onNewChaptersCheckBoxChanged(self, check_state): self.show_new_chapters = False - # we have a set value convert to True/False + # We have a set value convert to True/False. if check_state == QtCore.Qt.Checked: self.show_new_chapters = True - def onBibleDualCheckBox(self, check_state): - self.dual_bibles = False - # we have a set value convert to True/False + def onBibleSecondCheckBox(self, check_state): + self.second_bibles = False + # We have a set value convert to True/False. if check_state == QtCore.Qt.Checked: - self.dual_bibles = True + self.second_bibles = True def load(self): settings = QtCore.QSettings() @@ -211,12 +211,12 @@ class BiblesTab(SettingsTab): u'verse layout style', QtCore.QVariant(0)).toInt()[0] self.bible_theme = unicode( settings.value(u'bible theme', QtCore.QVariant(u'')).toString()) - self.dual_bibles = settings.value( - u'dual bibles', QtCore.QVariant(True)).toBool() + self.second_bibles = settings.value( + u'second bibles', QtCore.QVariant(True)).toBool() self.NewChaptersCheckBox.setChecked(self.show_new_chapters) self.DisplayStyleComboBox.setCurrentIndex(self.display_style) self.LayoutStyleComboBox.setCurrentIndex(self.layout_style) - self.BibleDualCheckBox.setChecked(self.dual_bibles) + self.BibleSecondCheckBox.setChecked(self.second_bibles) settings.endGroup() def save(self): @@ -228,13 +228,18 @@ class BiblesTab(SettingsTab): QtCore.QVariant(self.display_style)) settings.setValue(u'verse layout style', QtCore.QVariant(self.layout_style)) - settings.setValue(u'dual bibles', QtCore.QVariant(self.dual_bibles)) + settings.setValue(u'second bibles', QtCore.QVariant(self.second_bibles)) settings.setValue(u'bible theme', QtCore.QVariant(self.bible_theme)) settings.endGroup() def updateThemeList(self, theme_list): """ - Called from ThemeManager when the Themes have changed + Called from ThemeManager when the Themes have changed. + + ``theme_list`` + The list of available themes:: + + [u'Bible Theme', u'Song Theme'] """ self.BibleThemeComboBox.clear() self.BibleThemeComboBox.addItem(u'') @@ -243,7 +248,7 @@ class BiblesTab(SettingsTab): index = self.BibleThemeComboBox.findText( unicode(self.bible_theme), QtCore.Qt.MatchExactly) if index == -1: - # Not Found + # Not Found. index = 0 self.bible_theme = u'' self.BibleThemeComboBox.setCurrentIndex(index) diff --git a/openlp/plugins/bibles/lib/db.py b/openlp/plugins/bibles/lib/db.py index 8f5c7dc79..9852ed16b 100644 --- a/openlp/plugins/bibles/lib/db.py +++ b/openlp/plugins/bibles/lib/db.py @@ -44,24 +44,28 @@ class BibleMeta(BaseModel): """ pass + class Testament(BaseModel): """ Bible Testaments """ pass + class Book(BaseModel): """ Song model """ pass + class Verse(BaseModel): """ Topic model """ pass + def init_schema(url): """ Setup a bible database connection and initialise the database schema. @@ -240,7 +244,7 @@ class BibleDB(QtCore.QObject, Manager): and the value is the verse text. """ log.debug(u'create_chapter %s,%s', book_id, chapter) - # text list has book and chapter as first two elements of the array + # Text list has book and chapter as first two elements of the array. for verse_number, verse_text in textlist.iteritems(): verse = Verse.populate( book_id = book_id, diff --git a/openlp/plugins/bibles/lib/http.py b/openlp/plugins/bibles/lib/http.py index e6a474f5c..fa47dd7f5 100644 --- a/openlp/plugins/bibles/lib/http.py +++ b/openlp/plugins/bibles/lib/http.py @@ -213,7 +213,7 @@ class BGExtract(object): finally: if not page: return None - cleaner = [(re.compile(' |
'), lambda match: '')] + cleaner = [(re.compile(' |
|\'\+\''), lambda match: '')] soup = None try: soup = BeautifulSoup(page, markupMassage=cleaner) @@ -364,12 +364,11 @@ class HTTPBible(BibleDB): if self.proxy_server: self.create_meta(u'proxy server', self.proxy_server) if self.proxy_username: - # store the proxy userid + # Store the proxy userid. self.create_meta(u'proxy username', self.proxy_username) if self.proxy_password: - # store the proxy password + # Store the proxy password. self.create_meta(u'proxy password', self.proxy_password) - self.wizard.incrementProgressBar('Registered.') return True def get_verses(self, reference_list): @@ -417,7 +416,7 @@ class HTTPBible(BibleDB): ## to request ac and get Acts back. bookname = search_results.book Receiver.send_message(u'openlp_process_events') - # check to see if book/chapter exists + # Check to see if book/chapter exists. db_book = self.get_book(bookname) self.create_chapter(db_book.id, search_results.chapter, search_results.verselist) diff --git a/openlp/plugins/bibles/lib/manager.py b/openlp/plugins/bibles/lib/manager.py index 9afc4a4b2..6b4d7c800 100644 --- a/openlp/plugins/bibles/lib/manager.py +++ b/openlp/plugins/bibles/lib/manager.py @@ -257,17 +257,34 @@ class BibleManager(object): 'Book Chapter:Verse-Chapter:Verse')) return None - def verse_search(self, bible, text): + def verse_search(self, bible, second_bible, text): """ Does a verse search for the given bible and text. ``bible`` The bible to seach in (unicode). + ``second_bible`` + The second bible (unicode). We do not search in this bible. + ``text`` The text to search for (unicode). """ - log.debug(u'BibleManager.verse_search("%s", "%s")', bible, text) + log.debug(u'BibleManager.verse_search("%s", "%s")', bible, text) + # Check if the bible or second_bible is a web bible. + webbible = self.db_cache[bible].get_object(BibleMeta, + u'download source') + second_webbible = u'' + if second_bible: + second_webbible = self.db_cache[second_bible].get_object(BibleMeta, + u'download source') + if webbible or second_webbible: + QtGui.QMessageBox.information(self.parent.mediaItem, + translate('BiblesPlugin.BibleManager', + 'Web Bible cannot be used'), + translate('BiblesPlugin.BibleManager', 'Text Search is not ' + 'available with Web Bibles.')) + return None if text: return self.db_cache[bible].verse_search(text) else: @@ -317,4 +334,3 @@ class BibleManager(object): """ for bible in self.db_cache: self.db_cache[bible].finalise() - diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index bb3c1b26d..949035c0b 100644 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -47,21 +47,22 @@ class BibleListView(BaseListWithDnD): self.parent().onListViewResize(event.size().width(), event.size().width()) + class BibleMediaItem(MediaManagerItem): """ This is the custom media manager item for Bibles. """ log.info(u'Bible Media Item loaded') - def __init__(self, parent, icon, title): + def __init__(self, parent, plugin, icon): self.PluginNameShort = u'Bible' self.pluginNameVisible = translate('BiblesPlugin.MediaItem', 'Bible') self.IconPath = u'songs/song' self.ListViewWithDnD_class = BibleListView - MediaManagerItem.__init__(self, parent, icon, title) - # place to store the search results for both bibles + MediaManagerItem.__init__(self, parent, plugin, icon) + # Place to store the search results for both bibles. self.search_results = {} - self.dual_search_results = {} + self.second_search_results = {} QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'bibles_load_list'), self.reloadBibles) @@ -83,7 +84,7 @@ class BibleMediaItem(MediaManagerItem): self.SearchTabWidget.sizePolicy().hasHeightForWidth()) self.SearchTabWidget.setSizePolicy(sizePolicy) self.SearchTabWidget.setObjectName(u'SearchTabWidget') - # Add the Quick Search tab + # Add the Quick Search tab. self.QuickTab = QtGui.QWidget() self.QuickTab.setObjectName(u'QuickTab') self.QuickLayout = QtGui.QGridLayout(self.QuickTab) @@ -144,7 +145,7 @@ class BibleMediaItem(MediaManagerItem): QuickSpacerItem = QtGui.QSpacerItem(20, 35, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.QuickLayout.addItem(QuickSpacerItem, 6, 2, 1, 1) - # Add the Advanced Search tab + # Add the Advanced Search tab. self.AdvancedTab = QtGui.QWidget() self.AdvancedTab.setObjectName(u'AdvancedTab') self.AdvancedLayout = QtGui.QGridLayout(self.AdvancedTab) @@ -226,7 +227,7 @@ class BibleMediaItem(MediaManagerItem): self.AdvancedLayout.addWidget(self.AdvancedMessage, 8, 0, 1, 3) self.SearchTabWidget.addTab(self.AdvancedTab, translate('BiblesPlugin.MediaItem', 'Advanced')) - # Add the search tab widget to the page layout + # Add the search tab widget to the page layout. self.pageLayout.addWidget(self.SearchTabWidget) # Combo Boxes QtCore.QObject.connect(self.AdvancedVersionComboBox, @@ -239,6 +240,10 @@ class BibleMediaItem(MediaManagerItem): QtCore.SIGNAL(u'activated(int)'), self.onAdvancedFromVerse) QtCore.QObject.connect(self.AdvancedToChapter, QtCore.SIGNAL(u'activated(int)'), self.onAdvancedToChapter) + QtCore.QObject.connect(self.QuickSearchComboBox, + QtCore.SIGNAL(u'activated(int)'), self.updateAutoCompleter) + QtCore.QObject.connect(self.QuickVersionComboBox, + QtCore.SIGNAL(u'activated(int)'), self.updateAutoCompleter) # Buttons QtCore.QObject.connect(self.AdvancedSearchButton, QtCore.SIGNAL(u'pressed()'), self.onAdvancedSearchButton) @@ -270,7 +275,7 @@ class BibleMediaItem(MediaManagerItem): def configUpdated(self): log.debug(u'configUpdated') - if QtCore.QSettings().value(self.settingsSection + u'/dual bibles', + if QtCore.QSettings().value(self.settingsSection + u'/second bibles', QtCore.QVariant(True)).toBool(): self.AdvancedSecondBibleLabel.setVisible(True) self.AdvancedSecondBibleComboBox.setVisible(True) @@ -287,7 +292,7 @@ class BibleMediaItem(MediaManagerItem): self.QuickVersionLabel.setText( translate('BiblesPlugin.MediaItem', 'Version:')) self.QuickSecondVersionLabel.setText( - translate('BiblesPlugin.MediaItem', 'Dual:')) + translate('BiblesPlugin.MediaItem', 'Second:')) self.QuickSearchLabel.setText( translate('BiblesPlugin.MediaItem', 'Search type:')) self.QuickSearchLabel.setText( @@ -299,7 +304,7 @@ class BibleMediaItem(MediaManagerItem): self.AdvancedVersionLabel.setText( translate('BiblesPlugin.MediaItem', 'Version:')) self.AdvancedSecondBibleLabel.setText( - translate('BiblesPlugin.MediaItem', 'Dual:')) + translate('BiblesPlugin.MediaItem', 'Second:')) self.AdvancedBookLabel.setText( translate('BiblesPlugin.MediaItem', 'Book:')) self.AdvancedChapterLabel.setText( @@ -331,6 +336,7 @@ class BibleMediaItem(MediaManagerItem): log.debug(u'bible manager initialise') self.parent.manager.media = self self.loadBibles() + self.updateAutoCompleter() self.configUpdated() log.debug(u'bible manager initialise complete') @@ -338,7 +344,7 @@ class BibleMediaItem(MediaManagerItem): self.QuickMessage.setText(text) self.AdvancedMessage.setText(text) Receiver.send_message(u'openlp_process_events') - # minor delay to get the events processed + # Minor delay to get the events processed. time.sleep(0.1) def onListViewResize(self, width, height): @@ -358,13 +364,15 @@ class BibleMediaItem(MediaManagerItem): translate('BiblesPlugin.MediaItem', 'No Book Found'), translate('BiblesPlugin.MediaItem', 'No matching book could be found in this Bible.')) + self.AdvancedSearchButton.setEnabled(True) def onImportClick(self): if not hasattr(self, u'import_wizard'): self.import_wizard = BibleImportForm(self, self.parent.manager, self.parent) - self.import_wizard.exec_() - self.reloadBibles() + # If the import was not canceled then reload. + if self.import_wizard.exec_(): + self.reloadBibles() def loadBibles(self): log.debug(u'Loading Bibles') @@ -374,8 +382,10 @@ class BibleMediaItem(MediaManagerItem): self.AdvancedSecondBibleComboBox.clear() self.QuickSecondBibleComboBox.addItem(u'') self.AdvancedSecondBibleComboBox.addItem(u'') + # Get all bibles and sort the list. bibles = self.parent.manager.get_bibles().keys() - # load bibles into the combo boxes + bibles.sort() + # Load the bibles into the combo boxes. first = True for bible in bibles: if bible: @@ -393,6 +403,15 @@ class BibleMediaItem(MediaManagerItem): self.loadBibles() def initialiseBible(self, bible): + """ + This initialises the given bible, which means that its book names and + their chapter numbers is added to the combo boxes on the + 'Advanced Search' Tab. This is not of any importance of the + 'Quick Search' Tab. + + ``bible`` + The bible to initialise (unicode). + """ log.debug(u'initialiseBible %s', bible) book_data = self.parent.manager.get_books(bible) self.AdvancedBookComboBox.clear() @@ -423,6 +442,25 @@ class BibleMediaItem(MediaManagerItem): self.adjustComboBox(1, verse_count, self.AdvancedFromVerse) self.adjustComboBox(1, verse_count, self.AdvancedToVerse) + def updateAutoCompleter(self): + """ + This updates the bible book completion list for the search field. The + completion depends on the bible. It is only updated when we are doing a + verse search, otherwise the auto completion list is removed. + """ + books = [] + # We have to do a 'Verse Search'. + if self.QuickSearchComboBox.currentIndex() == 0: + bibles = self.parent.manager.get_bibles() + bible = unicode(self.QuickVersionComboBox.currentText()) + if bible: + book_data = bibles[bible].get_books() + books = [book.name for book in book_data] + books.sort() + completer = QtGui.QCompleter(books) + completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive) + self.QuickSearchEdit.setCompleter(completer) + def onAdvancedVersionComboBox(self): self.initialiseBible( unicode(self.AdvancedVersionComboBox.currentText())) @@ -482,6 +520,17 @@ class BibleMediaItem(MediaManagerItem): def adjustComboBox(self, range_from, range_to, combo, restore=False): """ + Adjusts the given como box to the given values. + + ``range_from`` + The first number of the range (int). + + ``range_to`` + The last number of the range (int). + + ``combo`` + The combo box itself (QComboBox). + ``restore`` If True, then the combo's currentText will be restored after adjusting (if possible). @@ -490,16 +539,19 @@ class BibleMediaItem(MediaManagerItem): if restore: old_text = unicode(combo.currentText()) combo.clear() - for i in range(int(range_from), int(range_to) + 1): + for i in range(range_from, range_to + 1): combo.addItem(unicode(i)) if restore and combo.findText(old_text) != -1: combo.setCurrentIndex(combo.findText(old_text)) def onAdvancedSearchButton(self): + """ + Does an advanced search and saves the search results. + """ log.debug(u'Advanced Search Button pressed') self.AdvancedSearchButton.setEnabled(False) bible = unicode(self.AdvancedVersionComboBox.currentText()) - dual_bible = unicode(self.AdvancedSecondBibleComboBox.currentText()) + second_bible = unicode(self.AdvancedSecondBibleComboBox.currentText()) book = unicode(self.AdvancedBookComboBox.currentText()) chapter_from = int(self.AdvancedFromChapter.currentText()) chapter_to = int(self.AdvancedToChapter.currentText()) @@ -508,74 +560,81 @@ class BibleMediaItem(MediaManagerItem): versetext = u'%s %s:%s-%s:%s' % (book, chapter_from, verse_from, chapter_to, verse_to) self.search_results = self.parent.manager.get_verses(bible, versetext) - if dual_bible: - self.dual_search_results = self.parent.manager.get_verses( - dual_bible, versetext) + if second_bible: + self.second_search_results = self.parent.manager.get_verses( + second_bible, versetext) if self.ClearAdvancedSearchComboBox.currentIndex() == 0: self.listView.clear() if self.listView.count() != 0: + # Check if the first item is a second bible item or not. bitem = self.listView.item(0) - item_dual_bible = self._decodeQtObject(bitem, 'dual_bible') - if item_dual_bible and dual_bible or not item_dual_bible and \ - not dual_bible: - self.displayResults(bible, dual_bible) + item_second_bible = self._decodeQtObject(bitem, 'second_bible') + if item_second_bible and second_bible or not item_second_bible and \ + not second_bible: + self.displayResults(bible, second_bible) elif QtGui.QMessageBox.critical(self, translate('BiblePlugin.MediaItem', 'Error'), translate('BiblePlugin.MediaItem', 'You cannot combine single ' - 'and dual bible verses. Do you want to delete your search ' + 'and second bible verses. Do you want to delete your search ' 'results and start a new search?'), QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.No | QtGui.QMessageBox.Yes)) == QtGui.QMessageBox.Yes: self.listView.clear() - self.displayResults(bible, dual_bible) + self.displayResults(bible, second_bible) else: - self.displayResults(bible, dual_bible) + self.displayResults(bible, second_bible) self.AdvancedSearchButton.setEnabled(True) def onQuickSearchButton(self): + """ + Does a quick search and saves the search results. Quick search can + either be "Verse Search" or "Text Search". + """ log.debug(u'Quick Search Button pressed') self.QuickSearchButton.setEnabled(False) bible = unicode(self.QuickVersionComboBox.currentText()) - dual_bible = unicode(self.QuickSecondBibleComboBox.currentText()) + second_bible = unicode(self.QuickSecondBibleComboBox.currentText()) text = unicode(self.QuickSearchEdit.text()) if self.QuickSearchComboBox.currentIndex() == 0: # We are doing a 'Verse Search'. self.search_results = self.parent.manager.get_verses(bible, text) - if dual_bible and self.search_results: - self.dual_search_results = self.parent.manager.get_verses( - dual_bible, text) + if second_bible and self.search_results: + self.second_search_results = self.parent.manager.get_verses( + second_bible, text) else: - # We are doing a ' Text Search'. + # We are doing a 'Text Search'. bibles = self.parent.manager.get_bibles() - self.search_results = self.parent.manager.verse_search(bible, text) - if dual_bible and self.search_results: + self.search_results = self.parent.manager.verse_search(bible, + second_bible, text) + if second_bible and self.search_results: text = [] for verse in self.search_results: text.append((verse.book.name, verse.chapter, verse.verse, verse.verse)) - self.dual_search_results = bibles[dual_bible].get_verses(text) + self.second_search_results = \ + bibles[second_bible].get_verses(text) if self.ClearQuickSearchComboBox.currentIndex() == 0: self.listView.clear() if self.listView.count() != 0 and self.search_results: bitem = self.listView.item(0) - item_dual_bible = self._decodeQtObject(bitem, 'dual_bible') - if item_dual_bible and dual_bible or not item_dual_bible and \ - not dual_bible: - self.displayResults(bible, dual_bible) + item_second_bible = self._decodeQtObject(bitem, 'second_bible') + if item_second_bible and second_bible or not item_second_bible and \ + not second_bible: + self.displayResults(bible, second_bible) elif QtGui.QMessageBox.critical(self, translate('BiblePlugin.MediaItem', 'Error'), translate('BiblePlugin.MediaItem', 'You cannot combine single ' - 'and dual bible verses. Do you want to delete your search ' + 'and second bible verses. Do you want to delete your search ' 'results and start a new search?'), QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.No | QtGui.QMessageBox.Yes)) == QtGui.QMessageBox.Yes: self.listView.clear() - self.displayResults(bible, dual_bible) + self.displayResults(bible, second_bible) elif self.search_results: - self.displayResults(bible, dual_bible) + self.displayResults(bible, second_bible) self.QuickSearchButton.setEnabled(True) - def displayResults(self, bible, dual_bible=u''): + def displayResults(self, bible, second_bible=u''): """ Displays the search results in the media manager. All data needed for further action is saved for/in each row. @@ -583,38 +642,41 @@ class BibleMediaItem(MediaManagerItem): version = self.parent.manager.get_meta_data(bible, u'Version') copyright = self.parent.manager.get_meta_data(bible, u'Copyright') permissions = self.parent.manager.get_meta_data(bible, u'Permissions') - if dual_bible: - dual_version = self.parent.manager.get_meta_data(dual_bible, + if second_bible: + second_version = self.parent.manager.get_meta_data(second_bible, u'Version') - dual_copyright = self.parent.manager.get_meta_data(dual_bible, + second_copyright = self.parent.manager.get_meta_data(second_bible, u'Copyright') - dual_permissions = self.parent.manager.get_meta_data(dual_bible, + second_permissions = self.parent.manager.get_meta_data(second_bible, u'Permissions') - if not dual_permissions: - dual_permissions = u'' - # We count the number of rows which are maybe already present. - start_count = self.listView.count() + if not second_permissions: + second_permissions = u'' for count, verse in enumerate(self.search_results): - if dual_bible: - vdict = { - 'book': QtCore.QVariant(verse.book.name), - 'chapter': QtCore.QVariant(verse.chapter), - 'verse': QtCore.QVariant(verse.verse), - 'bible': QtCore.QVariant(bible), - 'version': QtCore.QVariant(version.value), - 'copyright': QtCore.QVariant(copyright.value), - 'permissions': QtCore.QVariant(permissions.value), - 'text': QtCore.QVariant(verse.text), - 'dual_bible': QtCore.QVariant(dual_bible), - 'dual_version': QtCore.QVariant(dual_version.value), - 'dual_copyright': QtCore.QVariant(dual_copyright.value), - 'dual_permissions': QtCore.QVariant(dual_permissions.value), - 'dual_text': QtCore.QVariant( - self.dual_search_results[count].text) - } + if second_bible: + try: + vdict = { + 'book': QtCore.QVariant(verse.book.name), + 'chapter': QtCore.QVariant(verse.chapter), + 'verse': QtCore.QVariant(verse.verse), + 'bible': QtCore.QVariant(bible), + 'version': QtCore.QVariant(version.value), + 'copyright': QtCore.QVariant(copyright.value), + 'permissions': QtCore.QVariant(permissions.value), + 'text': QtCore.QVariant(verse.text), + 'second_bible': QtCore.QVariant(second_bible), + 'second_version': QtCore.QVariant(second_version.value), + 'second_copyright': QtCore.QVariant( + second_copyright.value), + 'second_permissions': QtCore.QVariant( + second_permissions.value), + 'second_text': QtCore.QVariant( + self.second_search_results[count].text) + } + except IndexError: + break bible_text = u' %s %d:%d (%s, %s)' % (verse.book.name, verse.chapter, verse.verse, version.value, - dual_version.value) + second_version.value) else: vdict = { 'book': QtCore.QVariant(verse.book.name), @@ -625,22 +687,20 @@ class BibleMediaItem(MediaManagerItem): 'copyright': QtCore.QVariant(copyright.value), 'permissions': QtCore.QVariant(permissions.value), 'text': QtCore.QVariant(verse.text), - 'dual_bible': QtCore.QVariant(u''), - 'dual_version': QtCore.QVariant(u''), - 'dual_copyright': QtCore.QVariant(u''), - 'dual_permissions': QtCore.QVariant(u''), - 'dual_text': QtCore.QVariant(u'') + 'second_bible': QtCore.QVariant(u''), + 'second_version': QtCore.QVariant(u''), + 'second_copyright': QtCore.QVariant(u''), + 'second_permissions': QtCore.QVariant(u''), + 'second_text': QtCore.QVariant(u'') } - bible_text = u' %s %d:%d (%s)' % (verse.book.name, + bible_text = u'%s %d:%d (%s)' % (verse.book.name, verse.chapter, verse.verse, version.value) bible_verse = QtGui.QListWidgetItem(bible_text) bible_verse.setData(QtCore.Qt.UserRole, QtCore.QVariant(vdict)) self.listView.addItem(bible_verse) - row = self.listView.setCurrentRow(count + start_count) - if row: - row.setSelected(True) + self.listView.selectAll() self.search_results = {} - self.dual_search_results = {} + self.second_search_results = {} def _decodeQtObject(self, bitem, key): reference = bitem.data(QtCore.Qt.UserRole) @@ -651,7 +711,7 @@ class BibleMediaItem(MediaManagerItem): obj = obj.toPyObject() return unicode(obj) - def generateSlideData(self, service_item, item=None): + def generateSlideData(self, service_item, item=None, xmlVersion=False): """ Generates and formats the slides for the service item as well as the service item's title. @@ -661,7 +721,7 @@ class BibleMediaItem(MediaManagerItem): if len(items) == 0: return False bible_text = u'' - old_chapter = u'' + old_chapter = -1 raw_footer = [] raw_slides = [] raw_title = [] @@ -676,22 +736,22 @@ class BibleMediaItem(MediaManagerItem): copyright = self._decodeQtObject(bitem, 'copyright') permissions = self._decodeQtObject(bitem, 'permissions') text = self._decodeQtObject(bitem, 'text') - dual_bible = self._decodeQtObject(bitem, 'dual_bible') - dual_version = self._decodeQtObject(bitem, 'dual_version') - dual_copyright = self._decodeQtObject(bitem, 'dual_copyright') - dual_permissions = self._decodeQtObject(bitem, 'dual_permissions') - dual_text = self._decodeQtObject(bitem, 'dual_text') + second_bible = self._decodeQtObject(bitem, 'second_bible') + second_version = self._decodeQtObject(bitem, 'second_version') + second_copyright = self._decodeQtObject(bitem, 'second_copyright') + second_permissions = self._decodeQtObject(bitem, 'second_permissions') + second_text = self._decodeQtObject(bitem, 'second_text') verse_text = self.formatVerse(old_chapter, chapter, verse) footer = u'%s (%s %s %s)' % (book, version, copyright, permissions) if footer not in raw_footer: raw_footer.append(footer) - if dual_bible: - footer = u'%s (%s %s %s)' % (book, dual_version, dual_copyright, - dual_permissions) + if second_bible: + footer = u'%s (%s %s %s)' % (book, second_version, + second_copyright, second_permissions) if footer not in raw_footer: raw_footer.append(footer) bible_text = u'%s %s\n\n%s %s' % (verse_text, text, verse_text, - dual_text) + second_text) raw_slides.append(bible_text) bible_text = u'' # If we are 'Verse Per Slide' then create a new slide. @@ -719,7 +779,7 @@ class BibleMediaItem(MediaManagerItem): raw_slides.append(bible_text) bible_text = u'' # Service Item: Capabilities - if self.parent.settings_tab.layout_style == 2 and not dual_bible: + if self.parent.settings_tab.layout_style == 2 and not second_bible: # Split the line but do not replace line breaks in renderer. service_item.add_capability(ItemCapabilities.NoLineBreaks) service_item.add_capability(ItemCapabilities.AllowsPreview) @@ -749,6 +809,12 @@ class BibleMediaItem(MediaManagerItem): This methode is called, when we have to change the title, because we are at the end of a verse range. E. g. if we want to add Genesis 1:1-6 as well as Daniel 2:14. + + ``start_item`` + The first item of a range. + + ``old_item`` + The last item of a range. """ old_bitem = self.listView.item(old_item.row()) old_chapter = int(self._decodeQtObject(old_bitem, 'chapter')) @@ -758,18 +824,18 @@ class BibleMediaItem(MediaManagerItem): start_chapter = int(self._decodeQtObject(start_bitem, 'chapter')) start_verse = int(self._decodeQtObject(start_bitem, 'verse')) start_bible = self._decodeQtObject(start_bitem, 'bible') - start_dual_bible = self._decodeQtObject(start_bitem, 'dual_bible') - if start_dual_bible: + start_second_bible = self._decodeQtObject(start_bitem, 'second_bible') + if start_second_bible: if start_verse == old_verse and start_chapter == old_chapter: title = u'%s %s:%s (%s, %s)' % (start_book, start_chapter, - start_verse, start_bible, start_dual_bible) + start_verse, start_bible, start_second_bible) elif start_chapter == old_chapter: title = u'%s %s:%s-%s (%s, %s)' % (start_book, start_chapter, - start_verse, old_verse, start_bible, start_dual_bible) + start_verse, old_verse, start_bible, start_second_bible) else: title = u'%s %s:%s-%s:%s (%s, %s)' % (start_book, start_chapter, start_verse, old_chapter, old_verse, start_bible, - start_dual_bible) + start_second_bible) else: if start_verse == old_verse and start_chapter == old_chapter: title = u'%s %s:%s (%s)' % (start_book, start_chapter, @@ -785,34 +851,62 @@ class BibleMediaItem(MediaManagerItem): def checkTitle(self, item, old_item): """ This methode checks if we are at the end of an verse range. If that is - the case, we return True, else False. E. g. if we added Genesis 1:1-6, - but the next verse is Daniel 2:14. + the case, we return True, otherwise False. E. g. if we added + Genesis 1:1-6, but the next verse is Daniel 2:14, we return True. + + ``item`` + The item we are dealing with at the moment. + + ``old_item`` + The item we were previously dealing with. """ + # Get all the necessary meta data. bitem = self.listView.item(item.row()) book = self._decodeQtObject(bitem, 'book') chapter = int(self._decodeQtObject(bitem, 'chapter')) verse = int(self._decodeQtObject(bitem, 'verse')) bible = self._decodeQtObject(bitem, 'bible') - dual_bible = self._decodeQtObject(bitem, 'dual_bible') + second_bible = self._decodeQtObject(bitem, 'second_bible') old_bitem = self.listView.item(old_item.row()) old_book = self._decodeQtObject(old_bitem, 'book') old_chapter = int(self._decodeQtObject(old_bitem, 'chapter')) old_verse = int(self._decodeQtObject(old_bitem, 'verse')) old_bible = self._decodeQtObject(old_bitem, 'bible') - old_dual_bible = self._decodeQtObject(old_bitem, 'dual_bible') - if old_bible != bible or old_dual_bible != dual_bible or \ + old_second_bible = self._decodeQtObject(old_bitem, 'second_bible') + if old_bible != bible or old_second_bible != second_bible or \ old_book != book: + # The bible, second bible or book has changed. return True elif old_verse + 1 != verse and old_chapter == chapter: + # We are still in the same chapter, but a verse has been skipped. return True elif old_chapter + 1 == chapter and (verse != 1 or old_verse != self.parent.manager.get_verse_count( old_bible, old_book, old_chapter)): + # We are in the following chapter, but the last verse was not the + # last verse of the chapter or the current verse is not the + # first one of the chapter. return True else: return False def formatVerse(self, old_chapter, chapter, verse): + """ + Formats and returns the text, each verse starts with, for the given + chapter and verse. The text is either surrounded by round, square, + curly brackets or no brackets at all. For example:: + + u'{su}1:1{/su}' + + ``old_chapter`` + The previous verse's chapter number (int). + + ``chapter`` + The chapter number (int). + + ``verse`` + The verse number (int). + """ if not self.parent.settings_tab.show_new_chapters or \ old_chapter != chapter: verse_text = u'%s:%s' % (chapter, verse) diff --git a/openlp/plugins/custom/forms/editcustomform.py b/openlp/plugins/custom/forms/editcustomform.py index 65a73337a..c5281574b 100644 --- a/openlp/plugins/custom/forms/editcustomform.py +++ b/openlp/plugins/custom/forms/editcustomform.py @@ -46,7 +46,6 @@ class EditCustomForm(QtGui.QDialog, Ui_CustomEditDialog): Constructor """ QtGui.QDialog.__init__(self, parent) - #self.parent = parent self.setupUi(self) # Connecting signals and slots self.previewButton = QtGui.QPushButton() @@ -124,8 +123,9 @@ class EditCustomForm(QtGui.QDialog, Ui_CustomEditDialog): self.slideListView.addItem(slide[1]) theme = self.customSlide.theme_name id = self.themeComboBox.findText(theme, QtCore.Qt.MatchExactly) + # No theme match if id == -1: - id = 0 # Not Found + id = 0 self.themeComboBox.setCurrentIndex(id) else: self.themeComboBox.setCurrentIndex(0) @@ -264,7 +264,7 @@ class EditCustomForm(QtGui.QDialog, Ui_CustomEditDialog): self.titleEdit.setFocus() return False, translate('CustomPlugin.EditCustomForm', 'You need to type in a title.') - # We must have one slide. + # We must have at least one slide. if self.slideListView.count() == 0: return False, translate('CustomPlugin.EditCustomForm', 'You need to add at least one slide') diff --git a/openlp/plugins/custom/forms/editcustomslideform.py b/openlp/plugins/custom/forms/editcustomslideform.py index 5f535c8bc..72c7dbb4a 100644 --- a/openlp/plugins/custom/forms/editcustomslideform.py +++ b/openlp/plugins/custom/forms/editcustomslideform.py @@ -50,7 +50,7 @@ class EditCustomSlideForm(QtGui.QDialog, Ui_CustomSlideEditDialog): def setText(self, text): """ Set the text for slideTextEdit. - + ``text`` The text (unicode). """ @@ -67,7 +67,7 @@ class EditCustomSlideForm(QtGui.QDialog, Ui_CustomSlideEditDialog): def onSplitButtonPressed(self): """ - Splits a slide in two slides. + Adds a slide split at the cursor. """ if self.slideTextEdit.textCursor().columnNumber() != 0: self.slideTextEdit.insertPlainText(u'\n') diff --git a/openlp/plugins/custom/lib/customxmlhandler.py b/openlp/plugins/custom/lib/customxmlhandler.py index b554f9657..907c3470d 100644 --- a/openlp/plugins/custom/lib/customxmlhandler.py +++ b/openlp/plugins/custom/lib/customxmlhandler.py @@ -43,6 +43,7 @@ import logging from xml.dom.minidom import Document from xml.etree.ElementTree import ElementTree, XML, dump +from lxml import etree, objectify from xml.parsers.expat import ExpatError log = logging.getLogger(__name__) @@ -55,14 +56,14 @@ class CustomXMLBuilder(object): def __init__(self): """ - Set up the song builder. + Set up the custom builder. """ # Create the minidom document self.custom_xml = Document() def new_document(self): """ - Create a new song XML document. + Create a new custom XML document. """ # Create the base element self.song = self.custom_xml.createElement(u'song') @@ -72,7 +73,7 @@ class CustomXMLBuilder(object): def add_lyrics_to_song(self): """ Set up and add a ```` tag which contains the lyrics of the - song. + custom item. """ # Create the main element self.lyrics = self.custom_xml.createElement(u'lyrics') @@ -93,7 +94,6 @@ class CustomXMLBuilder(object): ``content`` The actual text of the verse to be stored. """ - #log.debug(u'add_verse_to_lyrics %s, %s\n%s' % (type, number, content)) verse = self.custom_xml.createElement(u'verse') verse.setAttribute(u'type', type) verse.setAttribute(u'label', number) @@ -102,7 +102,7 @@ class CustomXMLBuilder(object): cds = self.custom_xml.createCDATASection(content) verse.appendChild(cds) - def dump_xml(self): + def _dump_xml(self): """ Debugging aid to dump XML so that we can see what we have. """ @@ -110,29 +110,30 @@ class CustomXMLBuilder(object): def extract_xml(self): """ - Extract our newly created XML song. + Extract our newly created XML custom. """ return self.custom_xml.toxml(u'utf-8') class CustomXMLParser(object): """ - A class to read in and parse a song's XML. + A class to read in and parse a custom's XML. """ log.info(u'CustomXMLParser Loaded') def __init__(self, xml): """ - Set up our song XML parser. + Set up our custom XML parser. ``xml`` - The XML of the song to be parsed. + The XML of the custom to be parsed. """ self.custom_xml = None + if xml[:5] == u' 1: self.DisplayTypeComboBox.insertItem(0, self.Automatic) self.DisplayTypeComboBox.setCurrentIndex(0) - if QtCore.QSettings().value(self.settingsSection + u'/override app', + if QtCore.QSettings().value(self.settingsSection + u'/override app', QtCore.QVariant(QtCore.Qt.Unchecked)) == QtCore.Qt.Checked: self.PresentationWidget.show() else: @@ -238,7 +238,7 @@ class PresentationMediaItem(MediaManagerItem): SettingsManager.set_list(self.settingsSection, self.settingsSection, self.getFileList()) - def generateSlideData(self, service_item, item=None): + def generateSlideData(self, service_item, item=None, xmlVersion=False): """ Load the relevant information for displaying the presentation in the slidecontroller. In the case of powerpoints, an image @@ -277,7 +277,7 @@ class PresentationMediaItem(MediaManagerItem): def findControllerByType(self, filename): """ Determine the default application controller to use for the selected - file type. This is used if "Automatic" is set as the preferred + file type. This is used if "Automatic" is set as the preferred controller. Find the first (alphabetic) enabled controller which "supports" the extension. If none found, then look for a controller which "alsosupports" it instead. diff --git a/openlp/plugins/songs/forms/editsongform.py b/openlp/plugins/songs/forms/editsongform.py index 241c6fd95..e421e63a0 100644 --- a/openlp/plugins/songs/forms/editsongform.py +++ b/openlp/plugins/songs/forms/editsongform.py @@ -152,6 +152,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): def newSong(self): log.debug(u'New Song') + self.initialise() self.SongTabWidget.setCurrentIndex(0) self.TitleEditItem.setText(u'') self.AlternativeEdit.setText(u'') @@ -170,8 +171,18 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): # it's a new song to preview is not possible self.previewButton.setVisible(False) - def loadSong(self, id, preview): + def loadSong(self, id, preview=False): + """ + Loads a song. + + ``id`` + The song id (int). + + ``preview`` + Should be ``True`` if the song is also previewed (boolean). + """ log.debug(u'Load Song') + self.initialise() self.SongTabWidget.setCurrentIndex(0) self.loadAuthors() self.loadTopics() @@ -594,6 +605,9 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): """ Save and Preview button pressed. The Song is valid so as the plugin to add it to preview to see. + + ``button`` + A button (QPushButton). """ log.debug(u'onPreview') if unicode(button.objectName()) == u'previewButton' and \ @@ -631,13 +645,16 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): database. ``preview`` - Should be True if song is also previewed. + Should be ``True`` if the song is also previewed (boolean). """ self.song.title = unicode(self.TitleEditItem.text()) self.song.alternate_title = unicode(self.AlternativeEdit.text()) self.song.copyright = unicode(self.CopyrightEditItem.text()) - self.song.search_title = self.song.title + u'@' + \ - self.song.alternate_title + if self.song.alternate_title: + self.song.search_title = self.song.title + u'@' + \ + self.song.alternate_title + else: + self.song.search_title = self.song.title self.song.comments = unicode(self.CommentsEdit.toPlainText()) self.song.verse_order = unicode(self.VerseOrderEdit.text()) self.song.ccli_number = unicode(self.CCLNumberEdit.text()) @@ -648,6 +665,11 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): Book.name == book_name) else: self.song.book = None + theme_name = unicode(self.ThemeSelectionComboItem.currentText()) + if theme_name: + self.song.theme_name = theme_name + else: + self.song.theme_name = None if self._validate_song(): self.processLyrics() self.processTitle() diff --git a/openlp/plugins/songs/forms/songimportform.py b/openlp/plugins/songs/forms/songimportform.py index 014c66a51..67ef4d8c1 100644 --- a/openlp/plugins/songs/forms/songimportform.py +++ b/openlp/plugins/songs/forms/songimportform.py @@ -507,8 +507,7 @@ class SongImportForm(QtGui.QWizard, Ui_SongImportWizard): filenames=self.getListOfFiles( self.songBeamerFileListWidget) ) - success = importer.do_import() - if success: + if importer.do_import(): # reload songs self.importProgressLabel.setText( translate('SongsPlugin.SongImportForm', 'Finished import.')) diff --git a/openlp/plugins/songs/forms/songmaintenanceform.py b/openlp/plugins/songs/forms/songmaintenanceform.py index 7c13e0989..e057bb516 100644 --- a/openlp/plugins/songs/forms/songmaintenanceform.py +++ b/openlp/plugins/songs/forms/songmaintenanceform.py @@ -405,7 +405,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): def mergeAuthors(self, old_author): """ Merges two authors into one author. - + ``old_author`` The author which will be deleted afterwards. """ @@ -427,7 +427,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): def mergeTopics(self, old_topic): """ Merges two topics into one topic. - + ``old_topic`` The topic which will be deleted afterwards. """ @@ -447,7 +447,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): def mergeBooks(self, old_book): """ Merges two books into one book. - + ``old_book`` The book which will be deleted afterwards. """ diff --git a/openlp/plugins/songs/lib/__init__.py b/openlp/plugins/songs/lib/__init__.py index 351d50071..795116b4e 100644 --- a/openlp/plugins/songs/lib/__init__.py +++ b/openlp/plugins/songs/lib/__init__.py @@ -62,6 +62,36 @@ class VerseType(object): elif verse_type == VerseType.Other: return translate('SongsPlugin.VerseType', 'Other') + @staticmethod + def expand_string(verse_type): + """ + Return the VerseType for a given string + + ``verse_type`` + The string to return a VerseType for + """ + verse_type = verse_type.lower() + if verse_type == unicode(VerseType.to_string(VerseType.Verse)).lower()[0]: + return translate('SongsPlugin.VerseType', 'Verse') + elif verse_type == \ + unicode(VerseType.to_string(VerseType.Chorus)).lower()[0]: + return translate('SongsPlugin.VerseType', 'Chorus') + elif verse_type == \ + unicode(VerseType.to_string(VerseType.Bridge)).lower()[0]: + return translate('SongsPlugin.VerseType', 'Bridge') + elif verse_type == \ + unicode(VerseType.to_string(VerseType.PreChorus)).lower()[0]: + return translate('SongsPlugin.VerseType', 'PreChorus') + elif verse_type == \ + unicode(VerseType.to_string(VerseType.Intro)).lower()[0]: + return translate('SongsPlugin.VerseType', 'Intro') + elif verse_type == \ + unicode(VerseType.to_string(VerseType.Ending)).lower()[0]: + return translate('SongsPlugin.VerseType', 'Ending') + elif verse_type == \ + unicode(VerseType.to_string(VerseType.Other)).lower()[0]: + return translate('SongsPlugin.VerseType', 'Other') + @staticmethod def from_string(verse_type): """ @@ -92,7 +122,6 @@ class VerseType(object): unicode(VerseType.to_string(VerseType.Other)).lower(): return VerseType.Other - -from xml import LyricsXML, SongXMLBuilder, SongXMLParser +from xml import LyricsXML, SongXMLBuilder, SongXMLParser, OpenLyricsParser from songstab import SongsTab from mediaitem import SongMediaItem diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py index 63496affd..432eee744 100644 --- a/openlp/plugins/songs/lib/mediaitem.py +++ b/openlp/plugins/songs/lib/mediaitem.py @@ -32,7 +32,7 @@ from openlp.core.lib import MediaManagerItem, BaseListWithDnD, Receiver, \ ItemCapabilities, translate, check_item_selected from openlp.plugins.songs.forms import EditSongForm, SongMaintenanceForm, \ SongImportForm -from openlp.plugins.songs.lib import SongXMLParser +from openlp.plugins.songs.lib import SongXMLParser, OpenLyricsParser from openlp.plugins.songs.lib.db import Author, Song log = logging.getLogger(__name__) @@ -53,8 +53,8 @@ class SongMediaItem(MediaManagerItem): self.ListViewWithDnD_class = SongListView MediaManagerItem.__init__(self, parent, self, icon) self.edit_song_form = EditSongForm(self, self.parent.manager) + self.openLyrics = OpenLyricsParser(self.parent.manager) self.singleServiceItem = False - #self.edit_song_form = EditSongForm(self.parent.manager, self) self.song_maintenance_form = SongMaintenanceForm( self.parent.manager, self) # Holds information about whether the edit is remotly triggered and @@ -114,6 +114,8 @@ class SongMediaItem(MediaManagerItem): self.SearchButtonLayout.addWidget(self.ClearTextButton) self.pageLayout.addLayout(self.SearchButtonLayout) # Signals and slots + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'plugin_list_refresh'), self.onSearchTextButtonClick) QtCore.QObject.connect(self.SearchTextEdit, QtCore.SIGNAL(u'returnPressed()'), self.onSearchTextButtonClick) QtCore.QObject.connect(self.SearchTextButton, @@ -141,7 +143,7 @@ class SongMediaItem(MediaManagerItem): self.updateServiceOnEdit = QtCore.QSettings().value( self.settingsSection + u'/update service on edit', QtCore.QVariant(u'False')).toBool() - self.AddSongFromServide = QtCore.QSettings().value( + self.addSongFromService = QtCore.QSettings().value( self.settingsSection + u'/add song from service', QtCore.QVariant(u'True')).toBool() @@ -192,6 +194,7 @@ class SongMediaItem(MediaManagerItem): Handle the exit from the edit dialog and trigger remote updates of songs """ + log.debug(u'onSongListLoad') # Called to redisplay the song list screen edit from a search # or from the exit of the Song edit dialog. If remote editing is active # Trigger it and clean up so it will not update again. @@ -259,6 +262,7 @@ class SongMediaItem(MediaManagerItem): Receiver.send_message(u'songs_load_list') def onNewClick(self): + log.debug(u'onNewClick') self.edit_song_form.newSong() self.edit_song_form.exec_() @@ -266,6 +270,7 @@ class SongMediaItem(MediaManagerItem): self.song_maintenance_form.exec_() def onRemoteEditClear(self): + log.debug(u'onRemoteEditClear') self.remoteTriggered = None self.remoteSong = -1 @@ -275,6 +280,7 @@ class SongMediaItem(MediaManagerItem): the Song Id in the payload along with an indicator to say which type of display is required. """ + log.debug(u'onRemoteEdit %s' % songid) fields = songid.split(u':') valid = self.parent.manager.get_object(Song, fields[1]) if valid: @@ -287,6 +293,7 @@ class SongMediaItem(MediaManagerItem): """ Edit a song """ + log.debug(u'onEditClick') if check_item_selected(self.listView, translate('SongsPlugin.MediaItem', 'You must select an item to edit.')): @@ -323,7 +330,8 @@ class SongMediaItem(MediaManagerItem): self.parent.manager.delete_object(Song, item_id) self.onSearchTextButtonClick() - def generateSlideData(self, service_item, item=None): + def generateSlideData(self, service_item, item=None, xmlVersion=False): + log.debug(u'generateSlideData (%s:%s)' % (service_item, item)) raw_footer = [] author_list = u'' author_audit = [] @@ -345,11 +353,11 @@ class SongMediaItem(MediaManagerItem): service_item.add_capability(ItemCapabilities.AddIfNewItem) song = self.parent.manager.get_object(Song, item_id) service_item.theme = song.theme_name - service_item.editId = item_id + service_item.edit_id = item_id if song.lyrics.startswith(u' 0: - self.add_verse(self.current_verse, + # Just make sure that the line is of the type 'Unicode'. + line = unicode(line).strip() + if line.startswith(u'#') and not read_verses: + self.parse_tags(line) + elif line.startswith(u'---'): + if self.current_verse: + self.replace_html_tags() + self.add_verse(self.current_verse, self.current_verse_type) self.current_verse = u'' self.current_verse_type = u'V' - self.read_verse = True - self.verse_start = True - elif self.read_verse: - if self.verse_start: - self.check_verse_marks(line) - self.verse_start = False + read_verses = True + verse_start = True + elif read_verses: + if verse_start: + verse_start = False + if not self.check_verse_marks(line): + self.current_verse = u'%s\n' % line else: self.current_verse += u'%s\n' % line - if len(self.current_verse) > 0: + if self.current_verse: + self.replace_html_tags() self.add_verse(self.current_verse, self.current_verse_type) self.finish() self.import_wizard.incrementProgressBar( "Importing %s" % (self.file_name)) return True + def replace_html_tags(self): + """ + This can be called to replace SongBeamer's specific (html) tags with + OpenLP's specific (html) tags. + """ + tag_pairs = [ + (u'', u'{st}'), + (u'', u'{/st}'), + (u'', u'{it}'), + (u'', u'{/it}'), + (u'', u'{u}'), + (u'', u'{/u}'), + (u'
', u'{st}'), + (u'
', u'{st}'), + (u'', u'{st}'), + (u'

', u'{p}'), + (u'

', u'{/p}'), + (u'', u'{su}'), + (u'', u'{/su}'), + (u'', u'{sb}'), + (u'', u'{/sb}'), + (u'', u''), + (u'', u''), + (u'', u''), + (u'', u'') + ] + for pair in tag_pairs: + self.current_verse = self.current_verse.replace(pair[0], pair[1]) + # TODO: check for unsupported tags (see wiki) and remove them as well. + def parse_tags(self, line): - tag_val = line.split('=') - if len(tag_val[0]) == 0 or len(tag_val[1]) == 0: - return True - if tag_val[0] == '#(c)': + """ + Parses a meta data line. + + ``line`` + The line in the file. It should consist of a tag and a value + for this tag (unicode):: + + u'#Title=Nearer my God to Thee' + """ + tag_val = line.split(u'=', 1) + if len(tag_val) == 1: + return + if not tag_val[0] or not tag_val[1]: + return + if tag_val[0] == u'#(c)': self.add_copyright(tag_val[1]) - elif tag_val[0] == '#AddCopyrightInfo': + elif tag_val[0] == u'#AddCopyrightInfo': pass - elif tag_val[0] == '#Author': - #TODO split Authors - self.add_author(tag_val[1]) - elif tag_val[0] == '#BackgroundImage': + elif tag_val[0] == u'#Author': + self.parse_author(tag_val[1]) + elif tag_val[0] == u'#BackgroundImage': pass - elif tag_val[0] == '#Bible': + elif tag_val[0] == u'#Bible': pass - elif tag_val[0] == '#Categories': + elif tag_val[0] == u'#Categories': self.topics = line.split(',') - elif tag_val[0] == '#CCLI': + elif tag_val[0] == u'#CCLI': self.ccli_number = tag_val[1] - elif tag_val[0] == '#Chords': + elif tag_val[0] == u'#Chords': pass - elif tag_val[0] == '#ChurchSongID': + elif tag_val[0] == u'#ChurchSongID': pass - elif tag_val[0] == '#ColorChords': + elif tag_val[0] == u'#ColorChords': pass - elif tag_val[0] == '#Comments': + elif tag_val[0] == u'#Comments': self.comments = tag_val[1] - elif tag_val[0] == '#Editor': + elif tag_val[0] == u'#Editor': pass - elif tag_val[0] == '#Font': + elif tag_val[0] == u'#Font': pass - elif tag_val[0] == '#FontLang2': + elif tag_val[0] == u'#FontLang2': pass - elif tag_val[0] == '#FontSize': + elif tag_val[0] == u'#FontSize': pass - elif tag_val[0] == '#Format': + elif tag_val[0] == u'#Format': pass - elif tag_val[0] == '#Format_PreLine': + elif tag_val[0] == u'#Format_PreLine': pass - elif tag_val[0] == '#Format_PrePage': + elif tag_val[0] == u'#Format_PrePage': pass - elif tag_val[0] == '#ID': + elif tag_val[0] == u'#ID': pass - elif tag_val[0] == '#Key': + elif tag_val[0] == u'#Key': pass - elif tag_val[0] == '#Keywords': + elif tag_val[0] == u'#Keywords': pass - elif tag_val[0] == '#LangCount': + elif tag_val[0] == u'#LangCount': pass - elif tag_val[0] == '#Melody': - #TODO split Authors - self.add_author(tag_val[1]) - elif tag_val[0] == '#NatCopyright': + elif tag_val[0] == u'#Melody': + self.parse_author(tag_val[1]) + elif tag_val[0] == u'#NatCopyright': pass - elif tag_val[0] == '#OTitle': + elif tag_val[0] == u'#OTitle': pass - elif tag_val[0] == '#OutlineColor': + elif tag_val[0] == u'#OutlineColor': pass - elif tag_val[0] == '#OutlinedFont': + elif tag_val[0] == u'#OutlinedFont': pass - elif tag_val[0] == '#QuickFind': + elif tag_val[0] == u'#QuickFind': pass - elif tag_val[0] == '#Rights': + elif tag_val[0] == u'#Rights': song_book_pub = tag_val[1] - elif tag_val[0] == '#Songbook': + elif tag_val[0] == u'#Songbook': book_num = tag_val[1].split(' / ') self.song_book_name = book_num[0] if len(book_num) == book_num[1]: self.song_number = u'' - elif tag_val[0] == '#Speed': + elif tag_val[0] == u'#Speed': pass - elif tag_val[0] == '#TextAlign': + elif tag_val[0] == u'#TextAlign': pass - elif tag_val[0] == '#Title': + elif tag_val[0] == u'#Title': self.title = u'%s' % tag_val[1] - elif tag_val[0] == '#TitleAlign': + elif tag_val[0] == u'#TitleAlign': pass - elif tag_val[0] == '#TitleFontSize': + elif tag_val[0] == u'#TitleFontSize': pass - elif tag_val[0] == '#TitleLang2': + elif tag_val[0] == u'#TitleLang2': pass - elif tag_val[0] == '#TitleLang3': + elif tag_val[0] == u'#TitleLang3': pass - elif tag_val[0] == '#TitleLang4': + elif tag_val[0] == u'#TitleLang4': pass - elif tag_val[0] == '#Translation': + elif tag_val[0] == u'#Translation': pass - elif tag_val[0] == '#Transpose': + elif tag_val[0] == u'#Transpose': pass - elif tag_val[0] == '#TransposeAccidental': + elif tag_val[0] == u'#TransposeAccidental': pass - elif tag_val[0] == '#Version': + elif tag_val[0] == u'#Version': pass - else: - pass - return True def check_verse_marks(self, line): - marks = line.split(' ') + """ + Check and add the verse's MarkType. Returns ``True`` if the given line + contains a correct verse mark otherwise ``False``. + + ``line`` + The line to check for marks (unicode). + """ + marks = line.split(u' ') if len(marks) <= 2 and marks[0] in SongBeamerTypes.MarkTypes: self.current_verse_type = SongBeamerTypes.MarkTypes[marks[0]] if len(marks) == 2: - #TODO: may check, because of only digits are allowed - self.current_verse_type += marks[1] + # If we have a digit, we append it to current_verse_type. + if marks[1].isdigit(): + self.current_verse_type += marks[1] + return True + else: + return False diff --git a/openlp/plugins/songs/lib/songimport.py b/openlp/plugins/songs/lib/songimport.py index 2c1de83a2..ea36d55b5 100644 --- a/openlp/plugins/songs/lib/songimport.py +++ b/openlp/plugins/songs/lib/songimport.py @@ -254,7 +254,8 @@ class SongImport(QtCore.QObject): All fields have been set to this song. Write it away """ if not self.authors: - self.authors.append(u'Author unknown') + self.authors.append(unicode(translate('SongsPlugin.SongImport', + 'Author unknown'))) self.commit_song() def commit_song(self): diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py index ff39a0efa..655ea1e3c 100644 --- a/openlp/plugins/songs/lib/xml.py +++ b/openlp/plugins/songs/lib/xml.py @@ -39,8 +39,11 @@ The basic XML is of the format:: """ import logging +import re from lxml import etree, objectify +from openlp.plugins.songs.lib import VerseType +from openlp.plugins.songs.lib.db import Author, Song log = logging.getLogger(__name__) @@ -77,7 +80,6 @@ class SongXMLBuilder(object): ``content`` The actual text of the verse to be stored. """ - # log.debug(u'add_verse_to_lyrics %s, %s\n%s' % (type, number, content)) verse = etree.Element(u'verse', type = unicode(type), label = unicode(number)) verse.text = etree.CDATA(content) @@ -239,3 +241,153 @@ class LyricsXML(object): song_output = u'' + \ u'%s' % lyrics_output return song_output + + +class OpenLyricsParser(object): + """ + This class represents the converter for Song to/from OpenLyrics XML. + """ + def __init__(self, manager): + self.manager = manager + + def song_to_xml(self, song): + """ + Convert the song to OpenLyrics Format + """ + song_xml_parser = SongXMLParser(song.lyrics) + verse_list = song_xml_parser.get_verses() + song_xml = objectify.fromstring( + u'') + properties = etree.SubElement(song_xml, u'properties') + titles = etree.SubElement(properties, u'titles') + self._add_text_to_element(u'title', titles, song.title) + if song.alternate_title: + self._add_text_to_element(u'title', titles, song.alternate_title) + if song.theme_name: + themes = etree.SubElement(properties, u'themes') + self._add_text_to_element(u'theme', themes, song.theme_name) + self._add_text_to_element(u'copyright', properties, song.copyright) + self._add_text_to_element(u'verseOrder', properties, song.verse_order) + if song.ccli_number: + self._add_text_to_element(u'ccliNo', properties, song.ccli_number) + authors = etree.SubElement(properties, u'authors') + for author in song.authors: + self._add_text_to_element(u'author', authors, author.display_name) + lyrics = etree.SubElement(song_xml, u'lyrics') + for verse in verse_list: + verse_tag = u'%s%s' % ( + verse[0][u'type'][0].lower(), verse[0][u'label']) + element = \ + self._add_text_to_element(u'verse', lyrics, None, verse_tag) + element = self._add_text_to_element(u'lines', element) + for line in unicode(verse[1]).split(u'\n'): + self._add_text_to_element(u'line', element, line) + return self._extract_xml(song_xml) + + def xml_to_song(self, xml): + """ + Create a Song from OpenLyrics format xml + """ + # No xml get out of here + if not xml: + return 0 + song = Song() + if xml[:5] == u'