diff --git a/openlp.pyw b/openlp.pyw index 805181c11..80b49321e 100755 --- a/openlp.pyw +++ b/openlp.pyw @@ -147,6 +147,9 @@ class OpenLP(QtGui.QApplication): return self.exec_() def hookException(self, exctype, value, traceback): + if not hasattr(self, u'mainWindow'): + log.exception(''.join(format_exception(exctype, value, traceback))) + return if not hasattr(self, u'exceptionForm'): self.exceptionForm = ExceptionForm(self.mainWindow) self.exceptionForm.exceptionTextEdit.setPlainText( @@ -159,18 +162,18 @@ def main(): the PyQt4 Application. """ # Set up command line options. - usage = u'Usage: %prog [options] [qt-options]' + usage = 'Usage: %prog [options] [qt-options]' parser = OptionParser(usage=usage) - parser.add_option("-l", "--log-level", dest="loglevel", - default="warning", metavar="LEVEL", - help="Set logging to LEVEL level. Valid values are " - "\"debug\", \"info\", \"warning\".") - parser.add_option("-p", "--portable", dest="portable", - action="store_true", - help="Specify if this should be run as a portable app, " - "off a USB flash drive.") - parser.add_option("-s", "--style", dest="style", - help="Set the Qt4 style (passed directly to Qt4).") + parser.add_option('-e', '--no-error-form', dest='no_error_form', + action='store_true', help='Disable the error notification form.') + parser.add_option('-l', '--log-level', dest='loglevel', + default='warning', metavar='LEVEL', help='Set logging to LEVEL ' + 'level. Valid values are "debug", "info", "warning".') + parser.add_option('-p', '--portable', dest='portable', + action='store_true', help='Specify if this should be run as a ' + 'portable app, off a USB flash drive (not implemented).') + parser.add_option('-s', '--style', dest='style', + help='Set the Qt4 style (passed directly to Qt4).') # Set up logging log_path = AppLocation.get_directory(AppLocation.CacheDir) if not os.path.exists(log_path): @@ -203,7 +206,8 @@ def main(): language = LanguageManager.get_language() appTranslator = LanguageManager.get_translator(language) app.installTranslator(appTranslator) - sys.excepthook = app.hookException + if not options.no_error_form: + sys.excepthook = app.hookException sys.exit(app.run()) if __name__ == u'__main__': diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index 1f911491f..ade4bc019 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -38,63 +38,48 @@ 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}) def translate(context, text, comment=None): """ @@ -235,6 +220,7 @@ def image_to_byte(image): ``image`` The image to converted. """ + log.debug(u'image_to_byte') byte_array = QtCore.QByteArray() # use buffer to store pixmap into byteArray buffie = QtCore.QBuffer(byte_array) @@ -264,6 +250,7 @@ def resize_image(image, width, height, background=QtCore.Qt.black): The background colour defaults to black. """ + log.debug(u'resize_image') preview = QtGui.QImage(image) if not preview.isNull(): # Only resize if different size diff --git a/openlp/core/lib/htmlbuilder.py b/openlp/core/lib/htmlbuilder.py index f0194da02..765d0f9fa 100644 --- a/openlp/core/lib/htmlbuilder.py +++ b/openlp/core/lib/htmlbuilder.py @@ -27,8 +27,6 @@ import logging from PyQt4 import QtWebKit -from openlp.core.lib import image_to_byte - log = logging.getLogger(__name__) HTMLSRC = u""" @@ -60,7 +58,10 @@ body { #image { z-index:1; } -#video { +#video1 { + z-index:2; +} +#video2 { z-index:2; } #alert { @@ -81,38 +82,86 @@ body { - - + + + %s
@@ -272,10 +324,10 @@ def build_html(item, screen, alert, islive): height = screen[u'size'].height() theme = item.themedata webkitvers = webkit_version() - if item.bg_frame: - image = u'data:image/png;base64,%s' % image_to_byte(item.bg_frame) + if item.bg_image_bytes: + image = u'src="data:image/png;base64,%s"' % item.bg_image_bytes else: - image = u'' + image = u'style="display:none;"' html = HTMLSRC % (build_background_css(item, width, height), width, height, build_alert_css(alert, width), @@ -397,8 +449,10 @@ def build_lyrics_css(item, webkitvers): outline = build_lyrics_outline_css(theme) if theme.display_shadow: if theme.display_outline and webkitvers < 534.3: - shadow = u'padding-left: %spx; padding-top: %spx ' % \ - (theme.display_shadow_size, theme.display_shadow_size) + shadow = u'padding-left: %spx; padding-top: %spx;' % \ + (int(theme.display_shadow_size) + + (int(theme.display_outline_size) * 2), + theme.display_shadow_size) shadow += build_lyrics_outline_css(theme, True) else: lyricsmain += u' text-shadow: %s %spx %spx;' % \ @@ -458,13 +512,17 @@ def build_lyrics_format_css(theme, width, height): valign = u'middle' else: valign = u'top' + if theme.display_outline: + left_margin = int(theme.display_outline_size) * 2 + else: + left_margin = 0 lyrics = u'white-space:pre-wrap; word-wrap: break-word; ' \ 'text-align: %s; vertical-align: %s; font-family: %s; ' \ - 'font-size: %spt; color: %s; line-height: %d%%; ' \ - 'margin:0; padding:0; width: %spx; height: %spx; ' % \ + 'font-size: %spt; color: %s; line-height: %d%%; margin:0;' \ + 'padding:0; padding-left:%spx; width: %spx; height: %spx; ' % \ (align, valign, theme.font_main_name, theme.font_main_proportion, theme.font_main_color, 100 + int(theme.font_main_line_adjustment), - width, height) + left_margin, width, height) if theme.display_outline: if webkit_version() < 534.3: lyrics += u' letter-spacing: 1px;' diff --git a/openlp/core/lib/mediamanageritem.py b/openlp/core/lib/mediamanageritem.py index a6d62f618..625a74842 100644 --- a/openlp/core/lib/mediamanageritem.py +++ b/openlp/core/lib/mediamanageritem.py @@ -432,7 +432,7 @@ class MediaManagerItem(QtGui.QWidget): raise NotImplementedError(u'MediaManagerItem.onDeleteClick needs to ' u'be defined by the plugin') - def generateSlideData(self, service_item, item): + def generateSlideData(self, service_item, item=None): raise NotImplementedError(u'MediaManagerItem.generateSlideData needs ' u'to be defined by the plugin') diff --git a/openlp/core/lib/renderer.py b/openlp/core/lib/renderer.py index 0cb92ad39..f97575c5e 100644 --- a/openlp/core/lib/renderer.py +++ b/openlp/core/lib/renderer.py @@ -32,7 +32,8 @@ import logging from PyQt4 import QtGui, QtCore, QtWebKit from openlp.core.lib import resize_image, expand_tags, \ - build_lyrics_format_css, build_lyrics_outline_css + build_lyrics_format_css, build_lyrics_outline_css, image_to_byte + log = logging.getLogger(__name__) @@ -54,6 +55,7 @@ class Renderer(object): self.frame = None self.bg_frame = None self.bg_image = None + self.bg_image_bytes = None def set_theme(self, theme): """ @@ -66,15 +68,12 @@ class Renderer(object): self._theme = theme self.bg_frame = None self.bg_image = None + self.bg_image_bytes = None self._bg_image_filename = None self.theme_name = theme.theme_name if theme.background_type == u'image': if theme.background_filename: self._bg_image_filename = unicode(theme.background_filename) - if self.frame: - self.bg_image = resize_image(self._bg_image_filename, - self.frame.width(), - self.frame.height()) def set_text_rectangle(self, rect_main, rect_footer): """ @@ -89,6 +88,22 @@ class Renderer(object): log.debug(u'set_text_rectangle %s , %s' % (rect_main, rect_footer)) self._rect = rect_main self._rect_footer = rect_footer + self.page_width = self._rect.width() + self.page_height = self._rect.height() + if self._theme.display_shadow: + self.page_width -= int(self._theme.display_shadow_size) + self.page_height -= int(self._theme.display_shadow_size) + self.web = QtWebKit.QWebView() + self.web.setVisible(False) + self.web.resize(self.page_width, self.page_height) + self.web_frame = self.web.page().mainFrame() + # Adjust width and height to account for shadow. outline done in css + self.page_shell = u'' \ + u'
' % \ + (build_lyrics_format_css(self._theme, self.page_width, + self.page_height), build_lyrics_outline_css(self._theme)) def set_frame_dest(self, frame_width, frame_height): """ @@ -110,15 +125,18 @@ class Renderer(object): self.frame.width(), self.frame.height()) if self._theme.background_type == u'image': self.bg_frame = QtGui.QImage(self.frame.width(), - self.frame.height(), QtGui.QImage.Format_ARGB32_Premultiplied) + self.frame.height(), + QtGui.QImage.Format_ARGB32_Premultiplied) painter = QtGui.QPainter() painter.begin(self.bg_frame) painter.fillRect(self.frame.rect(), QtCore.Qt.black) if self.bg_image: painter.drawImage(0, 0, self.bg_image) painter.end() + self.bg_image_bytes = image_to_byte(self.bg_frame) else: self.bg_frame = None + self.bg_image_bytes = None def format_slide(self, words, line_break): """ @@ -139,29 +157,16 @@ class Renderer(object): lines = verse.split(u'\n') for line in lines: text.append(line) - web = QtWebKit.QWebView() - web.resize(self._rect.width(), self._rect.height()) - web.setVisible(False) - frame = web.page().mainFrame() - # Adjust width and height to account for shadow. outline done in css - width = self._rect.width() - int(self._theme.display_shadow_size) - height = self._rect.height() - int(self._theme.display_shadow_size) - shell = u'' \ - u'
' % \ - (build_lyrics_format_css(self._theme, width, height), - build_lyrics_outline_css(self._theme)) formatted = [] html_text = u'' styled_text = u'' - js_height = 'document.getElementById("main").scrollHeight' for line in text: styled_line = expand_tags(line) + line_end styled_text += styled_line - html = shell + styled_text + u'
' - web.setHtml(html) + html = self.page_shell + styled_text + u'
' + self.web.setHtml(html) # Text too long so go to next page - text_height = int(frame.evaluateJavaScript(js_height).toString()) - if text_height > height: + if self.web_frame.contentsSize().height() > self.page_height: formatted.append(html_text) html_text = u'' styled_text = styled_line diff --git a/openlp/core/lib/rendermanager.py b/openlp/core/lib/rendermanager.py index a6e494b01..e98ab4f01 100644 --- a/openlp/core/lib/rendermanager.py +++ b/openlp/core/lib/rendermanager.py @@ -223,7 +223,6 @@ class RenderManager(object): The words to go on the slides. """ log.debug(u'format slide') - self.build_text_rectangle(self.themedata) return self.renderer.format_slide(words, line_break) def calculate_default(self, screen): diff --git a/openlp/core/lib/serviceitem.py b/openlp/core/lib/serviceitem.py index b0d453af5..e7ec9c2af 100644 --- a/openlp/core/lib/serviceitem.py +++ b/openlp/core/lib/serviceitem.py @@ -97,7 +97,7 @@ class ServiceItem(object): self.themedata = None self.main = None self.footer = None - self.bg_frame = None + self.bg_image_bytes = None def _new_item(self): """ @@ -145,7 +145,7 @@ class ServiceItem(object): """ log.debug(u'Render called') self._display_frames = [] - self.bg_frame = None + self.bg_image_bytes = None line_break = True if self.is_capable(ItemCapabilities.NoLineBreaks): line_break = False @@ -156,7 +156,7 @@ class ServiceItem(object): theme = self.theme self.main, self.footer = \ self.render_manager.set_override_theme(theme, useOverride) - self.bg_frame = self.render_manager.renderer.bg_frame + self.bg_image_bytes = self.render_manager.renderer.bg_image_bytes self.themedata = self.render_manager.renderer._theme for slide in self._raw_frames: before = time.time() diff --git a/openlp/core/lib/spelltextedit.py b/openlp/core/lib/spelltextedit.py index 7d227079b..07e8ad728 100644 --- a/openlp/core/lib/spelltextedit.py +++ b/openlp/core/lib/spelltextedit.py @@ -28,6 +28,7 @@ import re import sys try: import enchant + from enchant import DictNotFoundError enchant_available = True except ImportError: enchant_available = False @@ -43,7 +44,10 @@ class SpellTextEdit(QtGui.QPlainTextEdit): QtGui.QPlainTextEdit.__init__(self, *args) # Default dictionary based on the current locale. if enchant_available: - self.dict = enchant.Dict() + try: + self.dict = enchant.Dict() + except DictNotFoundError: + self.dict = enchant.Dict(u'en_US') self.highlighter = Highlighter(self.document()) self.highlighter.setDict(self.dict) diff --git a/openlp/core/ui/aboutdialog.py b/openlp/core/ui/aboutdialog.py index 3965b4ffc..3b9190156 100644 --- a/openlp/core/ui/aboutdialog.py +++ b/openlp/core/ui/aboutdialog.py @@ -199,18 +199,18 @@ class Ui_AboutDialog(object): 'Preamble\n' '\n' 'The licenses for most software are designed to take away your ' - 'freedom to share and change it. By contrast, the GNU General ' + 'freedom to share and change it. By contrast, the GNU General ' 'Public License is intended to guarantee your freedom to share ' 'and change free software--to make sure the software is free for ' - 'all its users. This General Public License applies to most of ' + 'all its users. This General Public License applies to most of ' 'the Free Software Foundation\'s software and to any other ' - 'program whose authors commit to using it. (Some other Free ' + 'program whose authors commit to using it. (Some other Free ' 'Software Foundation software is covered by the GNU Lesser ' - 'General Public License instead.) You can apply it to your ' + 'General Public License instead.) You can apply it to your ' 'programs, too.\n' '\n' 'When we speak of free software, we are referring to freedom, not ' - 'price. Our General Public Licenses are designed to make sure ' + 'price. Our General Public Licenses are designed to make sure ' 'that you have the freedom to distribute copies of free software ' '(and charge for this service if you wish), that you receive ' 'source code or can get it if you want it, that you can change ' @@ -225,8 +225,8 @@ class Ui_AboutDialog(object): '\n' 'For example, if you distribute copies of such a program, whether ' 'gratis or for a fee, you must give the recipients all the rights ' - 'that you have. You must make sure that they, too, receive or ' - 'can get the source code. And you must show them these terms so ' + 'that you have. You must make sure that they, too, receive or ' + 'can get the source code. And you must show them these terms so ' 'they know their rights.\n' '\n' 'We protect your rights with two steps: (1) copyright the ' @@ -235,15 +235,15 @@ class Ui_AboutDialog(object): '\n' 'Also, for each author\'s protection and ours, we want to make ' 'certain that everyone understands that there is no warranty for ' - 'this free software. If the software is modified by someone else ' + 'this free software. If the software is modified by someone else ' 'and passed on, we want its recipients to know that what they ' 'have is not the original, so that any problems introduced by ' 'others will not reflect on the original authors\' reputations.\n' '\n' 'Finally, any free program is threatened constantly by software ' - 'patents. We wish to avoid the danger that redistributors of a ' + 'patents. We wish to avoid the danger that redistributors of a ' 'free program will individually obtain patent licenses, in effect ' - 'making the program proprietary. To prevent this, we have made ' + 'making the program proprietary. To prevent this, we have made ' 'it clear that any patent must be licensed for everyone\'s free ' 'use or not licensed at all.\n' '\n' @@ -255,17 +255,17 @@ class Ui_AboutDialog(object): '\n' '0. This License applies to any program or other work which ' 'contains a notice placed by the copyright holder saying it may ' - 'be distributed under the terms of this General Public License. ' + 'be distributed under the terms of this General Public License. ' 'The "Program", below, refers to any such program or work, and a ' '"work based on the Program" means either the Program or any ' 'derivative work under copyright law: that is to say, a work ' 'containing the Program or a portion of it, either verbatim or ' - 'with modifications and/or translated into another language. ' + 'with modifications and/or translated into another language. ' '(Hereinafter, translation is included without limitation in the ' - 'term "modification".) Each licensee is addressed as "you".\n' + 'term "modification".) Each licensee is addressed as "you".\n' '\n' 'Activities other than copying, distribution and modification are ' - 'not covered by this License; they are outside its scope. The ' + 'not covered by this License; they are outside its scope. The ' 'act of running the Program is not restricted, and the output ' 'from the Program is covered only if its contents constitute a ' 'work based on the Program (independent of having been made by ' @@ -305,17 +305,17 @@ class Ui_AboutDialog(object): 'notice that there is no warranty (or else, saying that you ' 'provide a warranty) and that users may redistribute the program ' 'under these conditions, and telling the user how to view a copy ' - 'of this License. (Exception: if the Program itself is ' + 'of this License. (Exception: if the Program itself is ' 'interactive but does not normally print such an announcement, ' 'your work based on the Program is not required to print an ' 'announcement.)\n' '\n' - 'These requirements apply to the modified work as a whole. If ' + 'These requirements apply to the modified work as a whole. If ' 'identifiable sections of that work are not derived from the ' 'Program, and can be reasonably considered independent and ' 'separate works in themselves, then this License, and its terms, ' 'do not apply to those sections when you distribute them as ' - 'separate works. But when you distribute the same sections as ' + 'separate works. But when you distribute the same sections as ' 'part of a whole which is a work based on the Program, the ' 'distribution of the whole must be on the terms of this License, ' 'whose permissions for other licensees extend to the entire ' @@ -350,17 +350,17 @@ class Ui_AboutDialog(object): 'medium customarily used for software interchange; or,\n' '\n' 'c) Accompany it with the information you received as to the ' - 'offer to distribute corresponding source code. (This ' + 'offer to distribute corresponding source code. (This ' 'alternative is allowed only for noncommercial distribution and ' 'only if you received the program in object code or executable ' 'form with such an offer, in accord with Subsection b above.)\n' '\n' 'The source code for a work means the preferred form of the work ' - 'for making modifications to it. For an executable work, ' + 'for making modifications to it. For an executable work, ' 'complete source code means all the source code for all modules ' 'it contains, plus any associated interface definition files, ' 'plus the scripts used to control compilation and installation of ' - 'the executable. However, as a special exception, the source ' + 'the executable. However, as a special exception, the source ' 'code distributed need not include anything that is normally ' 'distributed (in either source or binary form) with the major ' 'components (compiler, kernel, and so on) of the operating system ' @@ -374,7 +374,7 @@ class Ui_AboutDialog(object): 'not compelled to copy the source along with the object code.\n' '\n' '4. You may not copy, modify, sublicense, or distribute the ' - 'Program except as expressly provided under this License. Any ' + 'Program except as expressly provided under this License. Any ' 'attempt otherwise to copy, modify, sublicense or distribute the ' 'Program is void, and will automatically terminate your rights ' 'under this License. However, parties who have received copies, ' @@ -383,10 +383,10 @@ class Ui_AboutDialog(object): 'compliance.\n' '\n' '5. You are not required to accept this License, since you have ' - 'not signed it. However, nothing else grants you permission to ' - 'modify or distribute the Program or its derivative works. These ' + 'not signed it. However, nothing else grants you permission to ' + 'modify or distribute the Program or its derivative works. These ' 'actions are prohibited by law if you do not accept this ' - 'License. Therefore, by modifying or distributing the Program ' + 'License. Therefore, by modifying or distributing the Program ' '(or any work based on the Program), you indicate your acceptance ' 'of this License to do so, and all its terms and conditions for ' 'copying, distributing or modifying the Program or works based on ' @@ -395,7 +395,7 @@ class Ui_AboutDialog(object): '6. Each time you redistribute the Program (or any work based on ' 'the Program), the recipient automatically receives a license ' 'from the original licensor to copy, distribute or modify the ' - 'Program subject to these terms and conditions. You may not ' + 'Program subject to these terms and conditions. You may not ' 'impose any further restrictions on the recipients\' exercise of ' 'the rights granted herein. You are not responsible for enforcing ' 'compliance by third parties to this License.\n' @@ -405,10 +405,10 @@ class Ui_AboutDialog(object): 'patent issues), conditions are imposed on you (whether by court ' 'order, agreement or otherwise) that contradict the conditions of ' 'this License, they do not excuse you from the conditions of this ' - 'License. If you cannot distribute so as to satisfy ' + 'License. If you cannot distribute so as to satisfy ' 'simultaneously your obligations under this License and any other ' 'pertinent obligations, then as a consequence you may not ' - 'distribute the Program at all. For example, if a patent license ' + 'distribute the Program at all. For example, if a patent license ' 'would not permit royalty-free redistribution of the Program by ' 'all those who receive copies directly or indirectly through you, ' 'then the only way you could satisfy both it and this License ' @@ -423,7 +423,7 @@ class Ui_AboutDialog(object): 'any patents or other property right claims or to contest ' 'validity of any such claims; this section has the sole purpose ' 'of protecting the integrity of the free software distribution ' - 'system, which is implemented by public license practices. Many ' + 'system, which is implemented by public license practices. Many ' 'people have made generous contributions to the wide range of ' 'software distributed through that system in reliance on ' 'consistent application of that system; it is up to the ' @@ -439,29 +439,29 @@ class Ui_AboutDialog(object): 'interfaces, the original copyright holder who places the Program ' 'under this License may add an explicit geographical distribution ' 'limitation excluding those countries, so that distribution is ' - 'permitted only in or among countries not thus excluded. In such ' + 'permitted only in or among countries not thus excluded. In such ' 'case, this License incorporates the limitation as if written in ' 'the body of this License.\n' '\n' '9. The Free Software Foundation may publish revised and/or new ' - 'versions of the General Public License from time to time. Such ' + 'versions of the General Public License from time to time. Such ' 'new versions will be similar in spirit to the present version, ' 'but may differ in detail to address new problems or concerns.\n' '\n' - 'Each version is given a distinguishing version number. If the ' + 'Each version is given a distinguishing version number. If the ' 'Program specifies a version number of this License which applies ' - 'to it and \"any later version\', you have the option of ' + 'to it and "any later version", you have the option of ' 'following the terms and conditions either of that version or of ' - 'any later version published by the Free Software Foundation. If ' + 'any later version published by the Free Software Foundation. If ' 'the Program does not specify a version number of this License, ' 'you may choose any version ever published by the Free Software ' 'Foundation.\n' '\n' '10. If you wish to incorporate parts of the Program into other ' 'free programs whose distribution conditions are different, write ' - 'to the author to ask for permission. For software which is ' + 'to the author to ask for permission. For software which is ' 'copyrighted by the Free Software Foundation, write to the Free ' - 'Software Foundation; we sometimes make exceptions for this. Our ' + 'Software Foundation; we sometimes make exceptions for this. Our ' 'decision will be guided by the two goals of preserving the free ' 'status of all derivatives of our free software and of promoting ' 'the sharing and reuse of software generally.\n' @@ -470,12 +470,12 @@ class Ui_AboutDialog(object): '\n' '11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO ' 'WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE ' - 'LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT ' + 'LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT ' 'HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT ' 'WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, ' 'BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY ' - 'AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE ' - 'QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE ' + 'AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE ' + 'QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE ' 'PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY ' 'SERVICING, REPAIR OR CORRECTION.\n' '\n' @@ -499,7 +499,7 @@ class Ui_AboutDialog(object): 'this is to make it free software which everyone can redistribute ' 'and change under these terms.\n' '\n' - 'To do so, attach the following notices to the program. It is ' + 'To do so, attach the following notices to the program. It is ' 'safest to attach them to the start of each source file to most ' 'effectively convey the exclusion of warranty; and each file ' 'should have at least the "copyright" line and a pointer to where ' @@ -507,7 +507,7 @@ class Ui_AboutDialog(object): '\n' '\n' - 'Copyright (C) \n' + 'Copyright (C) \n' '\n' 'This program is free software; you can redistribute it and/or ' 'modify it under the terms of the GNU General Public License as ' @@ -516,7 +516,7 @@ class Ui_AboutDialog(object): '\n' 'This program is distributed in the hope that it will be useful, ' 'but WITHOUT ANY WARRANTY; without even the implied warranty of ' - 'MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ' + 'MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ' 'GNU General Public License for more details.\n' '\n' 'You should have received a copy of the GNU General Public ' @@ -537,14 +537,14 @@ class Ui_AboutDialog(object): 'under certain conditions; type "show c" for details.\n' '\n' 'The hypothetical commands "show w" and "show c" should show ' - 'the appropriate parts of the General Public License. Of course, ' + 'the appropriate parts of the General Public License. Of course, ' 'the commands you use may be called something other than "show ' 'w" and "show c"; they could even be mouse-clicks or menu items--' 'whatever suits your program.\n' '\n' 'You should also get your employer (if you work as a programmer) ' 'or your school, if any, to sign a "copyright disclaimer" for the ' - 'program, if necessary. Here is a sample; alter the names:\n' + 'program, if necessary. Here is a sample; alter the names:\n' '\n' 'Yoyodyne, Inc., hereby disclaims all copyright interest in the ' 'program "Gnomovision" (which makes passes at compilers) written ' @@ -554,9 +554,9 @@ class Ui_AboutDialog(object): 'Ty Coon, President of Vice\n' '\n' 'This General Public License does not permit incorporating your ' - 'program into proprietary programs. If your program is a ' + 'program into proprietary programs. If your program is a ' 'subroutine library, you may consider it more useful to permit ' - 'linking proprietary applications with the library. If this is ' + 'linking proprietary applications with the library. If this is ' 'what you want to do, use the GNU Lesser General Public License ' 'instead of this License.')) self.aboutNotebook.setTabText( @@ -565,3 +565,4 @@ class Ui_AboutDialog(object): self.contributeButton.setText(translate('OpenLP.AboutForm', 'Contribute')) self.closeButton.setText(translate('OpenLP.AboutForm', 'Close')) + diff --git a/openlp/core/ui/exceptiondialog.py b/openlp/core/ui/exceptiondialog.py index 28097e938..49e2c8151 100644 --- a/openlp/core/ui/exceptiondialog.py +++ b/openlp/core/ui/exceptiondialog.py @@ -73,7 +73,7 @@ class Ui_ExceptionDialog(object): def retranslateUi(self, exceptionDialog): exceptionDialog.setWindowTitle( - translate('OpenLP.ExceptionDialog', 'Error Occured')) + translate('OpenLP.ExceptionDialog', 'Error Occurred')) self.messageLabel.setText(translate('OpenLP.ExceptionDialog', 'Oops! ' 'OpenLP hit a problem, and couldn\'t recover. The text in the box ' 'below contains information that might be helpful to the OpenLP ' diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index 6fbd7995c..d5fd1e861 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -99,6 +99,7 @@ class MainDisplay(DisplayWidget): self.alertTab = None self.hide_mode = None self.setWindowTitle(u'OpenLP Display') + self.setStyleSheet(u'border: 0px; margin: 0px; padding: 0px;') self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint) if self.isLive: @@ -116,12 +117,18 @@ class MainDisplay(DisplayWidget): self.screen = self.screens.current self.setVisible(False) self.setGeometry(self.screen[u'size']) - self.scene = QtGui.QGraphicsScene() - self.setScene(self.scene) - self.webView = QtWebKit.QGraphicsWebView() - self.scene.addItem(self.webView) - self.webView.resize(self.screen[u'size'].width(), \ - self.screen[u'size'].height()) + try: + self.webView = QtWebKit.QGraphicsWebView() + self.scene = QtGui.QGraphicsScene(self) + self.setScene(self.scene) + self.scene.addItem(self.webView) + self.webView.setGeometry(QtCore.QRectF(0, 0, + self.screen[u'size'].width(), self.screen[u'size'].height())) + except AttributeError: + # QGraphicsWebView a recent addition, so fall back to QWebView + self.webView = QtWebKit.QWebView(self) + self.webView.setGeometry(0, 0, + self.screen[u'size'].width(), self.screen[u'size'].height()) self.page = self.webView.page() self.frame = self.page.mainFrame() QtCore.QObject.connect(self.webView, @@ -157,7 +164,7 @@ class MainDisplay(DisplayWidget): - splash_image.height()) / 2, splash_image) serviceItem = ServiceItem() - serviceItem.bg_frame = initialFrame + serviceItem.bg_image_bytes = image_to_byte(initialFrame) self.webView.setHtml(build_html(serviceItem, self.screen, self.parent.alertTab, self.isLive)) self.initialFrame = True @@ -286,7 +293,7 @@ class MainDisplay(DisplayWidget): """ log.debug(u'video') self.loaded = True - js = u'show_video("play", "%s", %s, true);' % \ + js = u'show_video("init", "%s", %s, true); show_video("play");' % \ (videoPath.replace(u'\\', u'\\\\'), str(float(volume)/float(10))) self.frame.evaluateJavaScript(js) return self.preview() @@ -303,6 +310,10 @@ class MainDisplay(DisplayWidget): Generates a preview of the image displayed. """ log.debug(u'preview for %s', self.isLive) + # We must have a service item to preview + if not hasattr(self, u'serviceItem'): + return + Receiver.send_message(u'openlp_process_events') if self.isLive: # Wait for the fade to finish before geting the preview. # Important otherwise preview will have incorrect text if at all ! @@ -315,6 +326,12 @@ class MainDisplay(DisplayWidget): # Important otherwise first preview will miss the background ! while not self.loaded: Receiver.send_message(u'openlp_process_events') + # if was hidden keep it hidden + if self.isLive: + self.setVisible(True) + # if was hidden keep it hidden + if self.hide_mode and self.isLive: + self.hideDisplay(self.hide_mode) preview = QtGui.QImage(self.screen[u'size'].width(), self.screen[u'size'].height(), QtGui.QImage.Format_ARGB32_Premultiplied) @@ -322,9 +339,6 @@ class MainDisplay(DisplayWidget): painter.setRenderHint(QtGui.QPainter.Antialiasing) self.frame.render(painter) painter.end() - # Make display show up if in single screen mode - if self.isLive: - self.setVisible(True) return preview def buildHtml(self, serviceItem): @@ -336,9 +350,11 @@ class MainDisplay(DisplayWidget): self.loaded = False self.initialFrame = False self.serviceItem = serviceItem - html = build_html(self.serviceItem, self.screen, self.parent.alertTab,\ + html = build_html(self.serviceItem, self.screen, self.parent.alertTab, self.isLive) + log.debug(u'buildHtml - pre setHtml') self.webView.setHtml(html) + log.debug(u'buildHtml - post setHtml') if serviceItem.foot_text and serviceItem.foot_text: self.footer(serviceItem.foot_text) # if was hidden keep it hidden diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index abf035d65..0d158d042 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -250,7 +250,7 @@ class Ui_MainWindow(object): self.LanguageGroup = QtGui.QActionGroup(MainWindow) qmList = LanguageManager.get_qm_list() savedLanguage = LanguageManager.get_language() - self.AutoLanguageItem.setChecked(LanguageManager.AutoLanguage) + self.AutoLanguageItem.setChecked(LanguageManager.auto_language) for key in sorted(qmList.keys()): languageItem = QtGui.QAction(MainWindow) languageItem.setObjectName(key) @@ -258,7 +258,7 @@ class Ui_MainWindow(object): if qmList[key] == savedLanguage: languageItem.setChecked(True) add_actions(self.LanguageGroup, [languageItem]) - self.LanguageGroup.setDisabled(LanguageManager.AutoLanguage) + self.LanguageGroup.setDisabled(LanguageManager.auto_language) self.ToolsAddToolItem = QtGui.QAction(MainWindow) self.ToolsAddToolItem.setIcon(build_icon(u':/tools/tools_add.png')) self.ToolsAddToolItem.setObjectName(u'ToolsAddToolItem') @@ -575,7 +575,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): QtCore.SIGNAL(u'toggled(bool)'), self.setAutoLanguage) self.LanguageGroup.triggered.connect(LanguageManager.set_language) QtCore.QObject.connect(self.ModeDefaultItem, - QtCore.SIGNAL(u'triggered()'), self.setViewMode) + QtCore.SIGNAL(u'triggered()'), self.onModeDefaultItemClicked) QtCore.QObject.connect(self.ModeSetupItem, QtCore.SIGNAL(u'triggered()'), self.onModeSetupItemClicked) QtCore.QObject.connect(self.ModeLiveItem, @@ -640,7 +640,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): def setAutoLanguage(self, value): self.LanguageGroup.setDisabled(value) - LanguageManager.AutoLanguage = value + LanguageManager.auto_language = value LanguageManager.set_language(self.LanguageGroup.checkedAction()) def versionNotice(self, version): @@ -670,6 +670,16 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): self.generalSettingsSection + u'/auto open', QtCore.QVariant(False)).toBool(): self.ServiceManagerContents.onLoadService(True) + view_mode = QtCore.QSettings().value(u'%s/view mode' % \ + self.generalSettingsSection, u'default') + if view_mode == u'default': + self.ModeDefaultItem.setChecked(True) + elif view_mode == u'setup': + self.setViewMode(True, True, False, True, False) + self.ModeSetupItem.setChecked(True) + elif view_mode == u'live': + self.setViewMode(False, True, False, False, True) + self.ModeLiveItem.setChecked(True) def blankCheck(self): """ @@ -677,8 +687,8 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): Triggered by delay thread. """ settings = QtCore.QSettings() - settings.beginGroup(self.generalSettingsSection) - if settings.value(u'screen blank', QtCore.QVariant(False)).toBool(): + if settings.value(u'%s/screen blank' % self.generalSettingsSection, + QtCore.QVariant(False)).toBool(): self.LiveController.mainDisplaySetBackground() if settings.value(u'blank warning', QtCore.QVariant(False)).toBool(): @@ -687,7 +697,6 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): 'OpenLP Main Display Blanked'), translate('OpenLP.MainWindow', 'The Main Display has been blanked out')) - settings.endGroup() def onHelpWebSiteClicked(self): """ @@ -716,16 +725,31 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): """ self.settingsForm.exec_() + def onModeDefaultItemClicked(self): + """ + Put OpenLP into "Default" view mode. + """ + settings = QtCore.QSettings() + settings.setValue(u'%s/view mode' % self.generalSettingsSection, + u'default') + self.setViewMode(True, True, True, True, True) + def onModeSetupItemClicked(self): """ Put OpenLP into "Setup" view mode. """ + settings = QtCore.QSettings() + settings.setValue(u'%s/view mode' % self.generalSettingsSection, + u'setup') self.setViewMode(True, True, False, True, False) def onModeLiveItemClicked(self): """ Put OpenLP into "Live" view mode. """ + settings = QtCore.QSettings() + settings.setValue(u'%s/view mode' % self.generalSettingsSection, + u'live') self.setViewMode(False, True, False, False, True) def setViewMode(self, media=True, service=True, theme=True, preview=True, diff --git a/openlp/core/ui/plugindialog.py b/openlp/core/ui/plugindialog.py index 9d12651c9..45305d75c 100644 --- a/openlp/core/ui/plugindialog.py +++ b/openlp/core/ui/plugindialog.py @@ -93,6 +93,7 @@ class Ui_PluginViewDialog(object): self.pluginListButtonBox.setStandardButtons(QtGui.QDialogButtonBox.Ok) self.pluginListButtonBox.setObjectName(u'pluginListButtonBox') self.pluginLayout.addWidget(self.pluginListButtonBox) + self.versionNumberLabel.setText(u'') self.retranslateUi(pluginViewDialog) QtCore.QObject.connect(self.pluginListButtonBox, QtCore.SIGNAL(u'accepted()'), pluginViewDialog.close) @@ -105,8 +106,6 @@ class Ui_PluginViewDialog(object): translate('OpenLP.PluginForm', 'Plugin Details')) self.versionLabel.setText( translate('OpenLP.PluginForm', 'Version:')) - self.versionNumberLabel.setText( - translate('OpenLP.PluginForm', 'TextLabel')) self.aboutLabel.setText( translate('OpenLP.PluginForm', 'About:')) self.statusLabel.setText( @@ -115,3 +114,4 @@ class Ui_PluginViewDialog(object): translate('OpenLP.PluginForm', 'Active')) self.statusComboBox.setItemText(1, translate('OpenLP.PluginForm', 'Inactive')) + diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index c7d3c497f..5abb8d576 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -279,7 +279,8 @@ class ServiceManager(QtGui.QWidget): self.editAction.setVisible(False) self.maintainAction.setVisible(False) self.notesAction.setVisible(False) - if serviceItem[u'service_item'].is_capable(ItemCapabilities.AllowsEdit): + if serviceItem[u'service_item'].is_capable(ItemCapabilities.AllowsEdit) \ + and hasattr(serviceItem[u'service_item'], u'editId'): self.editAction.setVisible(True) if serviceItem[u'service_item']\ .is_capable(ItemCapabilities.AllowsMaintain): @@ -382,20 +383,20 @@ class ServiceManager(QtGui.QWidget): serviceIterator = QtGui.QTreeWidgetItemIterator(self.serviceManagerList) tempItem = None setLastItem = False - while serviceIterator: - if serviceIterator.isSelected() and tempItem is None: + while serviceIterator.value(): + if serviceIterator.value().isSelected() and tempItem is None: setLastItem = True - serviceIterator.setSelected(False) - if serviceIterator.isSelected(): - #We are on the first record + serviceIterator.value().setSelected(False) + if serviceIterator.value().isSelected(): + # We are on the first record if tempItem: tempItem.setSelected(True) - serviceIterator.setSelected(False) + serviceIterator.value().setSelected(False) else: - tempItem = serviceIterator - lastItem = serviceIterator - ++serviceIterator - #Top Item was selected so set the last one + tempItem = serviceIterator.value() + lastItem = serviceIterator.value() + serviceIterator += 1 + # Top Item was selected so set the last one if setLastItem: lastItem.setSelected(True) @@ -405,16 +406,18 @@ class ServiceManager(QtGui.QWidget): Called by the down arrow """ serviceIterator = QtGui.QTreeWidgetItemIterator(self.serviceManagerList) - firstItem = serviceIterator + firstItem = None setSelected = False - while serviceIterator: + while serviceIterator.value(): + if not firstItem: + firstItem = serviceIterator.value() if setSelected: setSelected = False - serviceIterator.setSelected(True) - elif serviceIterator.isSelected(): - serviceIterator.setSelected(False) + serviceIterator.value().setSelected(True) + elif serviceIterator.value() and serviceIterator.value().isSelected(): + serviceIterator.value().setSelected(False) setSelected = True - ++serviceIterator + serviceIterator += 1 if setSelected: firstItem.setSelected(True) @@ -556,7 +559,7 @@ class ServiceManager(QtGui.QWidget): QtCore.QVariant(item[u'order'])) for count, frame in enumerate(serviceitem.get_frames()): treewidgetitem1 = QtGui.QTreeWidgetItem(treewidgetitem) - text = frame[u'title'] + text = frame[u'title'].replace(u'\n', u' ') treewidgetitem1.setText(0, text[:40]) treewidgetitem1.setData(0, QtCore.Qt.UserRole, QtCore.QVariant(count)) @@ -632,6 +635,8 @@ class ServiceManager(QtGui.QWidget): def onLoadService(self, lastService=False): if lastService: + if not self.parent.recentFiles: + return filename = self.parent.recentFiles[0] else: filename = QtGui.QFileDialog.getOpenFileName( @@ -881,7 +886,8 @@ class ServiceManager(QtGui.QWidget): QtGui.QMessageBox.critical(self, translate('OpenLP.ServiceManager', 'Missing Display Handler'), translate('OpenLP.ServiceManager', 'Your item cannot be ' - 'displayed as there is no handler to display it')) + 'displayed as the plugin required to display it is missing ' + 'or inactive')) def remoteEdit(self): """ diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 2ea985598..0a3e0c91b 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -209,7 +209,8 @@ class SlideController(QtGui.QWidget): self.Toolbar.addToolbarSeparator(u'Close Separator') self.Toolbar.addToolbarButton( u'Edit Song', u':/general/general_edit.png', - translate('OpenLP.SlideController', 'Edit and re-preview Song'), + translate('OpenLP.SlideController', + 'Edit and reload song preview'), self.onEditSong) if isLive: self.Toolbar.addToolbarSeparator(u'Loop Separator') @@ -269,11 +270,11 @@ class SlideController(QtGui.QWidget): if isLive: self.SongMenu = QtGui.QToolButton(self.Toolbar) self.SongMenu.setText(translate('OpenLP.SlideController', - 'Go to')) + 'Go To')) self.SongMenu.setPopupMode(QtGui.QToolButton.InstantPopup) self.Toolbar.addToolbarWidget(u'Song Menu', self.SongMenu) self.SongMenu.setMenu(QtGui.QMenu( - translate('OpenLP.SlideController', 'Go to'), + translate('OpenLP.SlideController', 'Go To'), self.Toolbar)) self.Toolbar.makeWidgetsInvisible([u'Song Menu']) # Screen preview area @@ -636,9 +637,9 @@ class SlideController(QtGui.QWidget): """ if not self.serviceItem: return - Receiver.send_message(u'%s_first' % self.serviceItem.name.lower(), - [self.serviceItem, self.isLive]) if self.serviceItem.is_command(): + Receiver.send_message(u'%s_first' % self.serviceItem.name.lower(), + [self.serviceItem, self.isLive]) self.updatePreview() else: self.PreviewListWidget.selectRow(0) @@ -651,9 +652,9 @@ class SlideController(QtGui.QWidget): index = int(message[0]) if not self.serviceItem: return - Receiver.send_message(u'%s_slide' % self.serviceItem.name.lower(), - [self.serviceItem, self.isLive, index]) if self.serviceItem.is_command(): + Receiver.send_message(u'%s_slide' % self.serviceItem.name.lower(), + [self.serviceItem, self.isLive, index]) self.updatePreview() else: self.PreviewListWidget.selectRow(index) @@ -768,9 +769,9 @@ class SlideController(QtGui.QWidget): row = self.PreviewListWidget.currentRow() self.selectedRow = 0 if row > -1 and row < self.PreviewListWidget.rowCount(): - Receiver.send_message(u'%s_slide' % self.serviceItem.name.lower(), - [self.serviceItem, self.isLive, row]) if self.serviceItem.is_command() and self.isLive: + Receiver.send_message(u'%s_slide' % self.serviceItem.name.lower(), + [self.serviceItem, self.isLive, row]) self.updatePreview() else: frame, raw_html = self.serviceItem.get_rendered_frame(row) diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index 692bfe041..cd2cfefad 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -242,14 +242,14 @@ class ThemeManager(QtGui.QWidget): QtGui.QMessageBox.critical(self, translate('OpenLP.ThemeManager', 'Error'), unicode(translate('OpenLP.ThemeManager', - 'Theme %s is use in %s plugin.')) % \ + 'Theme %s is used in the %s plugin.')) % \ (theme, plugin.name)) return if unicode(self.serviceComboBox.currentText()) == theme: QtGui.QMessageBox.critical(self, translate('OpenLP.ThemeManager', 'Error'), unicode(translate('OpenLP.ThemeManager', - 'Theme %s is use by the service manager.')) % theme) + 'Theme %s is used by the service manager.')) % theme) return row = self.themeListWidget.row(item) self.themeListWidget.takeItem(row) @@ -573,7 +573,7 @@ class ThemeManager(QtGui.QWidget): translate('OpenLP.ThemeManager', 'Theme Exists'), translate('OpenLP.ThemeManager', 'A theme with this name already ' - 'exists. Would you like to overwrite it?'), + 'exists. Would you like to overwrite it?'), (QtGui.QMessageBox.Yes | QtGui.QMessageBox.No), QtGui.QMessageBox.No) if self.saveThemeName != u'': diff --git a/openlp/core/utils/__init__.py b/openlp/core/utils/__init__.py index e728fb544..119bf6b55 100644 --- a/openlp/core/utils/__init__.py +++ b/openlp/core/utils/__init__.py @@ -102,6 +102,7 @@ class AppLocation(object): PluginsDir = 4 VersionDir = 5 CacheDir = 6 + LanguageDir = 7 @staticmethod def get_directory(dir_type=1): @@ -112,7 +113,11 @@ class AppLocation(object): The directory type you want, for instance the data directory. """ if dir_type == AppLocation.AppDir: - return os.path.abspath(os.path.split(sys.argv[0])[0]) + if hasattr(sys, u'frozen') and sys.frozen == 1: + app_path = os.path.abspath(os.path.split(sys.argv[0])[0]) + else: + app_path = os.path.split(openlp.__file__)[0] + return app_path elif dir_type == AppLocation.ConfigDir: if sys.platform == u'win32': path = os.path.join(os.getenv(u'APPDATA'), u'openlp') @@ -169,6 +174,13 @@ class AppLocation(object): except ImportError: path = os.path.join(os.getenv(u'HOME'), u'.openlp') return path + if dir_type == AppLocation.LanguageDir: + if hasattr(sys, u'frozen') and sys.frozen == 1: + app_path = os.path.abspath(os.path.split(sys.argv[0])[0]) + else: + app_path = os.path.split(openlp.__file__)[0] + return os.path.join(app_path, u'i18n') + @staticmethod def get_data_path(): diff --git a/openlp/core/utils/languagemanager.py b/openlp/core/utils/languagemanager.py index 275d6985b..f118c64b0 100644 --- a/openlp/core/utils/languagemanager.py +++ b/openlp/core/utils/languagemanager.py @@ -35,14 +35,14 @@ from PyQt4 import QtCore, QtGui from openlp.core.utils import AppLocation from openlp.core.lib import translate -log = logging.getLogger() +log = logging.getLogger(__name__) class LanguageManager(object): """ Helper for Language selection """ - __qmList__ = None - AutoLanguage = False + __qm_list__ = {} + auto_language = False @staticmethod def get_translator(language): @@ -52,12 +52,11 @@ class LanguageManager(object): ``language`` The language to load into the translator """ - if LanguageManager.AutoLanguage: + if LanguageManager.auto_language: language = QtCore.QLocale.system().name() - lang_path = AppLocation.get_directory(AppLocation.AppDir) - lang_path = os.path.join(lang_path, u'resources', u'i18n') + lang_path = AppLocation.get_directory(AppLocation.LanguageDir) app_translator = QtCore.QTranslator() - if app_translator.load("openlp_" + language, lang_path): + if app_translator.load(language, lang_path): return app_translator @staticmethod @@ -65,8 +64,8 @@ class LanguageManager(object): """ Find all available language files in this OpenLP install """ - trans_dir = AppLocation.get_directory(AppLocation.AppDir) - trans_dir = QtCore.QDir(os.path.join(trans_dir, u'resources', u'i18n')) + trans_dir = QtCore.QDir(AppLocation.get_directory( + AppLocation.LanguageDir)) file_names = trans_dir.entryList(QtCore.QStringList("*.qm"), QtCore.QDir.Files, QtCore.QDir.Name) for name in file_names: @@ -96,7 +95,7 @@ class LanguageManager(object): log.info(u'Language file: \'%s\' Loaded from conf file' % language) reg_ex = QtCore.QRegExp("^\[(.*)\]") if reg_ex.exactMatch(language): - LanguageManager.AutoLanguage = True + LanguageManager.auto_language = True language = reg_ex.cap(1) return language @@ -108,9 +107,12 @@ class LanguageManager(object): ``action`` The language menu option """ - action_name = u'%s' % action.objectName() + if action is None: + action_name = u'en' + else: + action_name = u'%s' % action.objectName() qm_list = LanguageManager.get_qm_list() - if LanguageManager.AutoLanguage: + if LanguageManager.auto_language: language = u'[%s]' % qm_list[action_name] else: language = u'%s' % qm_list[action_name] @@ -127,20 +129,18 @@ class LanguageManager(object): """ Initialise the list of available translations """ - LanguageManager.__qmList__ = {} + LanguageManager.__qm_list__ = {} qm_files = LanguageManager.find_qm_files() - for i, qmf in enumerate(qm_files): - reg_ex = QtCore.QRegExp("^.*openlp_(.*).qm") - if reg_ex.exactMatch(qmf): - lang_name = reg_ex.cap(1) - LanguageManager.__qmList__[u'%#2i %s' % (i+1, - LanguageManager.language_name(qmf))] = lang_name + for counter, qmf in enumerate(qm_files): + name = unicode(qmf).split(u'.')[0] + LanguageManager.__qm_list__[u'%#2i %s' % (counter + 1, + LanguageManager.language_name(qmf))] = name @staticmethod def get_qm_list(): """ Return the list of available translations """ - if LanguageManager.__qmList__ is None: + if not LanguageManager.__qm_list__: LanguageManager.init_qm_list() - return LanguageManager.__qmList__ + return LanguageManager.__qm_list__ diff --git a/openlp/plugins/alerts/alertsplugin.py b/openlp/plugins/alerts/alertsplugin.py index 9e1da2267..b8a829b37 100644 --- a/openlp/plugins/alerts/alertsplugin.py +++ b/openlp/plugins/alerts/alertsplugin.py @@ -40,7 +40,7 @@ class AlertsPlugin(Plugin): log.info(u'Alerts Plugin loaded') def __init__(self, plugin_helpers): - Plugin.__init__(self, u'Alerts', u'1.9.2', plugin_helpers) + Plugin.__init__(self, u'Alerts', u'1.9.3', plugin_helpers) self.weight = -3 self.icon = build_icon(u':/plugins/plugin_alerts.png') self.alertsmanager = AlertsManager(self) diff --git a/openlp/plugins/bibles/bibleplugin.py b/openlp/plugins/bibles/bibleplugin.py index da542b23b..7f69c6ff0 100644 --- a/openlp/plugins/bibles/bibleplugin.py +++ b/openlp/plugins/bibles/bibleplugin.py @@ -37,7 +37,7 @@ class BiblePlugin(Plugin): log.info(u'Bible Plugin loaded') def __init__(self, plugin_helpers): - Plugin.__init__(self, u'Bibles', u'1.9.2', plugin_helpers) + Plugin.__init__(self, u'Bibles', u'1.9.3', plugin_helpers) self.weight = -9 self.icon_path = u':/plugins/plugin_bibles.png' self.icon = build_icon(self.icon_path) diff --git a/openlp/plugins/bibles/forms/importwizardform.py b/openlp/plugins/bibles/forms/importwizardform.py index 67f3756dc..3b2611e8f 100644 --- a/openlp/plugins/bibles/forms/importwizardform.py +++ b/openlp/plugins/bibles/forms/importwizardform.py @@ -126,29 +126,29 @@ class ImportWizardForm(QtGui.QWizard, Ui_BibleImportWizard): if self.field(u'osis_location').toString() == u'': QtGui.QMessageBox.critical(self, translate('BiblesPlugin.ImportWizardForm', - 'Invalid Bible Location'), + 'Invalid Bible Location'), translate('BiblesPlugin.ImportWizardForm', - 'You need to specify a file to import your ' - 'Bible from.')) + 'You need to specify a file to import your ' + 'Bible from.')) self.OSISLocationEdit.setFocus() return False elif self.field(u'source_format').toInt()[0] == BibleFormat.CSV: if self.field(u'csv_booksfile').toString() == u'': QtGui.QMessageBox.critical(self, translate('BiblesPlugin.ImportWizardForm', - 'Invalid Books File'), + 'Invalid Books File'), translate('BiblesPlugin.ImportWizardForm', - 'You need to specify a file with books of ' - 'the Bible to use in the import.')) + 'You need to specify a file with books of ' + 'the Bible to use in the import.')) self.BooksLocationEdit.setFocus() return False elif self.field(u'csv_versefile').toString() == u'': QtGui.QMessageBox.critical(self, translate('BiblesPlugin.ImportWizardForm', - 'Invalid Verse File'), + 'Invalid Verse File'), translate('BiblesPlugin.ImportWizardForm', - 'You need to specify a file of Bible ' - 'verses to import.')) + 'You need to specify a file of Bible ' + 'verses to import.')) self.CsvVerseLocationEdit.setFocus() return False elif self.field(u'source_format').toInt()[0] == \ @@ -156,10 +156,10 @@ class ImportWizardForm(QtGui.QWizard, Ui_BibleImportWizard): if self.field(u'opensong_file').toString() == u'': QtGui.QMessageBox.critical(self, translate('BiblesPlugin.ImportWizardForm', - 'Invalid OpenSong Bible'), + 'Invalid OpenSong Bible'), translate('BiblesPlugin.ImportWizardForm', - 'You need to specify an OpenSong Bible ' - 'file to import.')) + 'You need to specify an OpenSong Bible ' + 'file to import.')) self.OpenSongFileEdit.setFocus() return False return True @@ -171,29 +171,26 @@ class ImportWizardForm(QtGui.QWizard, Ui_BibleImportWizard): if license_version == u'': QtGui.QMessageBox.critical(self, translate('BiblesPlugin.ImportWizardForm', - 'Empty Version Name'), + 'Empty Version Name'), translate('BiblesPlugin.ImportWizardForm', - 'You need to specify a version name for your ' - 'Bible.')) + 'You need to specify a version name for your Bible.')) self.VersionNameEdit.setFocus() return False elif license_copyright == u'': QtGui.QMessageBox.critical(self, translate('BiblesPlugin.ImportWizardForm', - 'Empty Copyright'), + 'Empty Copyright'), translate('BiblesPlugin.ImportWizardForm', - 'You need to set a copyright for your Bible! ' - 'Bibles in the Public Domain need to be marked as ' - 'such.')) + 'You need to set a copyright for your Bible. ' + 'Bibles in the Public Domain need to be marked as such.')) self.CopyrightEdit.setFocus() return False elif self.manager.exists(license_version): QtGui.QMessageBox.critical(self, + translate('BiblesPlugin.ImportWizardForm', 'Bible Exists'), translate('BiblesPlugin.ImportWizardForm', - 'Bible Exists'), - translate('BiblesPlugin.ImportWizardForm', - 'This Bible already exists! Please import ' - 'a different Bible or first delete the existing one.')) + 'This Bible already exists. Please import ' + 'a different Bible or first delete the existing one.')) self.VersionNameEdit.setFocus() return False return True @@ -290,6 +287,9 @@ class ImportWizardForm(QtGui.QWizard, Ui_BibleImportWizard): def setDefaults(self): settings = QtCore.QSettings() settings.beginGroup(self.bibleplugin.settingsSection) + self.restart() + self.finishButton.setVisible(False) + self.cancelButton.setVisible(True) self.setField(u'source_format', QtCore.QVariant(0)) self.setField(u'osis_location', QtCore.QVariant('')) self.setField(u'csv_booksfile', QtCore.QVariant('')) @@ -434,18 +434,16 @@ class ImportWizardForm(QtGui.QWizard, Ui_BibleImportWizard): unicode(self.field(u'proxy_username').toString()), proxy_password=unicode(self.field(u'proxy_password').toString()) ) - success = importer.do_import() - if success: + if importer.do_import(): self.manager.save_meta_data(license_version, license_version, license_copyright, license_permission) self.manager.reload_bibles() self.ImportProgressLabel.setText( - translate('BiblesPlugin.ImportWizardForm', - 'Finished import.')) + translate('BiblesPlugin.ImportWizardForm', 'Finished import.')) else: self.ImportProgressLabel.setText( translate('BiblesPlugin.ImportWizardForm', - 'Your Bible import failed.')) + 'Your Bible import failed.')) delete_database(self.bibleplugin.settingsSection, importer.file) def postImport(self): diff --git a/openlp/plugins/bibles/lib/db.py b/openlp/plugins/bibles/lib/db.py index 51b6bb5fa..49bc82102 100644 --- a/openlp/plugins/bibles/lib/db.py +++ b/openlp/plugins/bibles/lib/db.py @@ -353,7 +353,7 @@ class BibleDB(QtCore.QObject, Manager): QtGui.QMessageBox.information(self.bible_plugin.mediaItem, translate('BiblesPlugin.BibleDB', 'Book not found'), translate('BiblesPlugin.BibleDB', 'The book you requested ' - 'could not be found in this bible. Please check your ' + 'could not be found in this bible. Please check your ' 'spelling and that this is a complete bible not just ' 'one testament.')) return verse_list diff --git a/openlp/plugins/bibles/lib/manager.py b/openlp/plugins/bibles/lib/manager.py index a0734aa98..fd2c2adff 100644 --- a/openlp/plugins/bibles/lib/manager.py +++ b/openlp/plugins/bibles/lib/manager.py @@ -246,7 +246,7 @@ class BibleManager(object): translate('BiblesPlugin.BibleManager', 'Scripture Reference Error'), translate('BiblesPlugin.BibleManager', 'Your scripture ' - 'reference is either not supported by OpenLP or invalid. ' + 'reference is either not supported by OpenLP or is invalid. ' 'Please make sure your reference conforms to one of the ' 'following patterns:\n\n' 'Book Chapter\n' diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index 57e70617a..037951954 100644 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -65,12 +65,6 @@ class BibleMediaItem(MediaManagerItem): QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'bibles_load_list'), self.reloadBibles) - def _decodeQtObject(self, listobj, key): - obj = listobj[QtCore.QString(key)] - if isinstance(obj, QtCore.QVariant): - obj = obj.toPyObject() - return unicode(obj) - def requiredIcons(self): MediaManagerItem.requiredIcons(self) self.hasImportIcon = True @@ -376,9 +370,6 @@ class BibleMediaItem(MediaManagerItem): def onSearchProgressShow(self): self.SearchProgress.setVisible(True) Receiver.send_message(u'openlp_process_events') - #self.SearchProgress.setMinimum(0) - #self.SearchProgress.setMaximum(2) - #self.SearchProgress.setValue(1) def onSearchProgressHide(self): self.SearchProgress.setVisible(False) @@ -408,18 +399,28 @@ class BibleMediaItem(MediaManagerItem): self.reloadBibles() def onAdvancedFromVerse(self): - frm = self.AdvancedFromVerse.currentText() - self.adjustComboBox(frm, self.verses, self.AdvancedToVerse) - - def onAdvancedToChapter(self): - frm = unicode(self.AdvancedFromChapter.currentText()) - to = unicode(self.AdvancedToChapter.currentText()) - if frm != to: + frm = int(self.AdvancedFromVerse.currentText()) + chapter_frm = int(self.AdvancedFromChapter.currentText()) + chapter_to = int(self.AdvancedToChapter.currentText()) + if chapter_frm == chapter_to: bible = unicode(self.AdvancedVersionComboBox.currentText()) book = unicode(self.AdvancedBookComboBox.currentText()) - # get the verse count for new chapter - verses = self.parent.manager.get_verse_count(bible, book, int(to)) + verses = self.parent.manager.get_verse_count(bible, book, chapter_to) + self.adjustComboBox(frm, verses, self.AdvancedToVerse) + + def onAdvancedToChapter(self): + chapter_frm = int(self.AdvancedFromChapter.currentText()) + chapter_to = int(self.AdvancedToChapter.currentText()) + bible = unicode(self.AdvancedVersionComboBox.currentText()) + book = unicode(self.AdvancedBookComboBox.currentText()) + verses = self.parent.manager.get_verse_count(bible, book, chapter_to) + if chapter_frm != chapter_to: self.adjustComboBox(1, verses, self.AdvancedToVerse) + else: + frm = int(self.AdvancedFromVerse.currentText()) + to = int(self.AdvancedToVerse.currentText()) + if to < frm: + self.adjustComboBox(frm, verses, self.AdvancedToVerse) def onAdvancedSearchButton(self): log.debug(u'Advanced Search Button pressed') @@ -438,32 +439,72 @@ class BibleMediaItem(MediaManagerItem): dual_bible, versetext) if self.ClearAdvancedSearchComboBox.currentIndex() == 0: self.listView.clear() - self.displayResults(bible, dual_bible) + if self.listView.count() != 0: + 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) + 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 ' + '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) + else: + self.displayResults(bible, dual_bible) def onAdvancedFromChapter(self): bible = unicode(self.AdvancedVersionComboBox.currentText()) book = unicode(self.AdvancedBookComboBox.currentText()) - cf = int(self.AdvancedFromChapter.currentText()) - self.adjustComboBox(cf, self.chapters_from, self.AdvancedToChapter) - # get the verse count for new chapter - vse = self.parent.manager.get_verse_count(bible, book, cf) - self.adjustComboBox(1, vse, self.AdvancedFromVerse) - self.adjustComboBox(1, vse, self.AdvancedToVerse) + chapter_frm = int(self.AdvancedFromChapter.currentText()) + self.adjustComboBox(chapter_frm, self.chapters_from, + self.AdvancedToChapter) + verse = self.parent.manager.get_verse_count(bible, book, chapter_frm) + self.adjustComboBox(1, verse, self.AdvancedToVerse) + self.adjustComboBox(1, verse, self.AdvancedFromVerse) def onQuickSearchButton(self): log.debug(u'Quick Search Button pressed') bible = unicode(self.QuickVersionComboBox.currentText()) dual_bible = unicode(self.QuickSecondBibleComboBox.currentText()) text = unicode(self.QuickSearchEdit.text()) - if self.ClearQuickSearchComboBox.currentIndex() == 0: - self.listView.clear() self.search_results = self.parent.manager.get_verses(bible, text) if dual_bible: self.dual_search_results = self.parent.manager.get_verses( dual_bible, text) - if self.search_results: + 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) + 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 ' + '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) + elif self.search_results: self.displayResults(bible, dual_bible) + def _decodeQtObject(self, bitem, key): + reference = bitem.data(QtCore.Qt.UserRole) + if isinstance(reference, QtCore.QVariant): + reference = reference.toPyObject() + obj = reference[QtCore.QString(key)] + if isinstance(obj, QtCore.QVariant): + obj = obj.toPyObject() + return unicode(obj) + def generateSlideData(self, service_item, item=None): """ Generates and formats the slides for the service item as well as the @@ -473,67 +514,40 @@ class BibleMediaItem(MediaManagerItem): items = self.listView.selectedIndexes() if len(items) == 0: return False - has_dual_bible = False bible_text = u'' old_chapter = u'' raw_footer = [] raw_slides = [] + raw_title = [] + first_item = True for item in items: bitem = self.listView.item(item.row()) - reference = bitem.data(QtCore.Qt.UserRole) - if isinstance(reference, QtCore.QVariant): - reference = reference.toPyObject() - dual_bible = self._decodeQtObject(reference, 'dual_bible') - if dual_bible: - has_dual_bible = True - break - # Let's loop through the main lot, and assemble our verses. - for item in items: - bitem = self.listView.item(item.row()) - reference = bitem.data(QtCore.Qt.UserRole) - if isinstance(reference, QtCore.QVariant): - reference = reference.toPyObject() - book = self._decodeQtObject(reference, 'book') - chapter = self._decodeQtObject(reference, 'chapter') - verse = self._decodeQtObject(reference, 'verse') - bible = self._decodeQtObject(reference, 'bible') - version = self._decodeQtObject(reference, 'version') - copyright = self._decodeQtObject(reference, 'copyright') - permission = self._decodeQtObject(reference, 'permission') - text = self._decodeQtObject(reference, 'text') - dual_bible = self._decodeQtObject(reference, 'dual_bible') - if dual_bible: - dual_version = self._decodeQtObject(reference, - 'dual_version') - dual_copyright = self._decodeQtObject(reference, - 'dual_copyright') - dual_permission = self._decodeQtObject(reference, - 'dual_permission') - dual_text = self._decodeQtObject(reference, 'dual_text') + book = self._decodeQtObject(bitem, 'book') + chapter = int(self._decodeQtObject(bitem, 'chapter')) + verse = int(self._decodeQtObject(bitem, 'verse')) + bible = self._decodeQtObject(bitem, 'bible') + version = self._decodeQtObject(bitem, 'version') + copyright = self._decodeQtObject(bitem, 'copyright') + permission = self._decodeQtObject(bitem, 'permission') + 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_permission = self._decodeQtObject(bitem, 'dual_permission') + dual_text = self._decodeQtObject(bitem, 'dual_text') verse_text = self.formatVerse(old_chapter, chapter, verse) footer = u'%s (%s %s %s)' % (book, version, copyright, permission) if footer not in raw_footer: raw_footer.append(footer) - if has_dual_bible: - if dual_bible: - footer = u'%s (%s %s %s)' % (book, dual_version, - dual_copyright, dual_permission) - if footer not in raw_footer: - raw_footer.append(footer) - # If there is an old bible_text we have to add it. - if bible_text: - raw_slides.append(bible_text) - bible_text = u'' - bible_text = u'%s %s\n\n%s %s' % (verse_text, text, - verse_text, dual_text) - raw_slides.append(bible_text) - bible_text = u'' - elif self.parent.settings_tab.layout_style == 0: - bible_text = u'%s %s' % (verse_text, text) - raw_slides.append(bible_text) - bible_text = u'' - else: - bible_text = u'%s %s %s\n' % (bible_text, verse_text, text) + if dual_bible: + footer = u'%s (%s %s %s)' % (book, dual_version, dual_copyright, + dual_permission) + 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) + raw_slides.append(bible_text) + bible_text = u'' # If we are 'Verse Per Slide' then create a new slide. elif self.parent.settings_tab.layout_style == 0: bible_text = u'%s %s' % (verse_text, text) @@ -545,29 +559,31 @@ class BibleMediaItem(MediaManagerItem): # We have to be 'Continuous'. else: bible_text = u'%s %s %s\n' % (bible_text, verse_text, text) + if first_item: + start_item = item + first_item = False + elif self.checkTitle(item, old_item): + raw_title.append(self.formatTitle(start_item, old_item)) + start_item = item + old_item = item old_chapter = chapter + raw_title.append(self.formatTitle(start_item, item)) # If there are no more items we check whether we have to add bible_text. if bible_text: raw_slides.append(bible_text) bible_text = u'' # Service Item: Capabilities - if self.parent.settings_tab.layout_style == 2 and not has_dual_bible: - # split the line but do not replace line breaks in renderer + if self.parent.settings_tab.layout_style == 2 and not dual_bible: + # Split the line but do not replace line breaks in renderer. service_item.add_capability(ItemCapabilities.NoLineBreaks) service_item.add_capability(ItemCapabilities.AllowsPreview) service_item.add_capability(ItemCapabilities.AllowsLoop) - service_item.add_capability(ItemCapabilities.AllowsAdditions) # Service Item: Title - if not service_item.title: - if dual_bible: - service_item.title = u'%s (%s, %s) %s' % (book, version, - dual_version, verse_text) + for title in raw_title: + if not service_item.title: + service_item.title = title else: - service_item.title = u'%s (%s) %s' % (book, version, verse_text) - elif service_item.title.find( - translate('BiblesPlugin.MediaItem', 'etc')) == -1: - service_item.title = u'%s, %s' % (service_item.title, - translate('BiblesPlugin.MediaItem', 'etc')) + service_item.title += u', ' + title # Service Item: Theme if len(self.parent.settings_tab.bible_theme) == 0: service_item.theme = None @@ -582,12 +598,80 @@ class BibleMediaItem(MediaManagerItem): service_item.raw_footer = raw_footer return True + def formatTitle(self, start_item, old_item): + """ + 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. + """ + old_bitem = self.listView.item(old_item.row()) + old_chapter = int(self._decodeQtObject(old_bitem, 'chapter')) + old_verse = int(self._decodeQtObject(old_bitem, 'verse')) + start_bitem = self.listView.item(start_item.row()) + start_book = self._decodeQtObject(start_bitem, 'book') + 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: + 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) + 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) + 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) + else: + if start_verse == old_verse and start_chapter == old_chapter: + title = u'%s %s:%s (%s)' % (start_book, start_chapter, + start_verse, start_bible) + elif start_chapter == old_chapter: + title = u'%s %s:%s-%s (%s)' % (start_book, start_chapter, + start_verse, old_verse, start_bible) + else: + title = u'%s %s:%s-%s:%s (%s)' % (start_book, start_chapter, + start_verse, old_chapter, old_verse, start_bible) + return title + + 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. + """ + 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') + 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_book != book: + return True + elif old_verse + 1 != verse and old_chapter == chapter: + 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)): + return True + else: + return False + def formatVerse(self, old_chapter, chapter, verse): if not self.parent.settings_tab.show_new_chapters or \ old_chapter != chapter: - verse_text = chapter + u':' + verse + verse_text = u'%s:%s' % (chapter, verse) else: - verse_text = verse + verse_text = u'%s' % verse if self.parent.settings_tab.display_style == 1: verse_text = u'{su}(' + verse_text + u'){/su}' elif self.parent.settings_tab.display_style == 2: @@ -678,7 +762,8 @@ class BibleMediaItem(MediaManagerItem): self.dual_search_results[count].text) } bible_text = u' %s %d:%d (%s, %s)' % (verse.book.name, - verse.chapter, verse.verse, version.value, dual_version.value) + verse.chapter, verse.verse, version.value, + dual_version.value) else: vdict = { 'book': QtCore.QVariant(verse.book.name), @@ -689,14 +774,15 @@ class BibleMediaItem(MediaManagerItem): 'copyright': QtCore.QVariant(copyright.value), 'permission': QtCore.QVariant(permission.value), 'text': QtCore.QVariant(verse.text), - 'dual_bible': QtCore.QVariant(dual_bible) + 'dual_bible': QtCore.QVariant(u''), + 'dual_version': QtCore.QVariant(u''), + 'dual_copyright': QtCore.QVariant(u''), + 'dual_permission': QtCore.QVariant(u''), + 'dual_text': QtCore.QVariant(u'') } bible_text = u' %s %d:%d (%s)' % (verse.book.name, verse.chapter, verse.verse, version.value) - # set the row title bible_verse = QtGui.QListWidgetItem(bible_text) - #bible_verse.setData(QtCore.Qt.UserRole, - # QtCore.QVariant(bible_text)) bible_verse.setData(QtCore.Qt.UserRole, QtCore.QVariant(vdict)) self.listView.addItem(bible_verse) row = self.listView.setCurrentRow(count + start_count) diff --git a/openlp/plugins/bibles/lib/opensong.py b/openlp/plugins/bibles/lib/opensong.py index 7acb7e2f2..f1d3efd74 100644 --- a/openlp/plugins/bibles/lib/opensong.py +++ b/openlp/plugins/bibles/lib/opensong.py @@ -89,7 +89,7 @@ class OpenSongBible(BibleDB): Receiver.send_message(u'openlp_process_events') self.wizard.incrementProgressBar( QtCore.QString('%s %s %s' % ( - translate('BiblesPlugin.Opensong', 'Importing'), \ + translate('BiblesPlugin.Opensong', 'Importing'), db_book.name, chapter.attrib[u'n']))) self.session.commit() except IOError: diff --git a/openlp/plugins/custom/customplugin.py b/openlp/plugins/custom/customplugin.py index 4e3819961..523ccb061 100644 --- a/openlp/plugins/custom/customplugin.py +++ b/openlp/plugins/custom/customplugin.py @@ -47,7 +47,7 @@ class CustomPlugin(Plugin): log.info(u'Custom Plugin loaded') def __init__(self, plugin_helpers): - Plugin.__init__(self, u'Custom', u'1.9.2', plugin_helpers) + Plugin.__init__(self, u'Custom', u'1.9.3', plugin_helpers) self.weight = -5 self.custommanager = Manager(u'custom', init_schema) self.edit_custom_form = EditCustomForm(self.custommanager) diff --git a/openlp/plugins/images/imageplugin.py b/openlp/plugins/images/imageplugin.py index d34cd6a3c..7f910f395 100644 --- a/openlp/plugins/images/imageplugin.py +++ b/openlp/plugins/images/imageplugin.py @@ -35,7 +35,7 @@ class ImagePlugin(Plugin): log.info(u'Image Plugin loaded') def __init__(self, plugin_helpers): - Plugin.__init__(self, u'Images', u'1.9.2', plugin_helpers) + Plugin.__init__(self, u'Images', u'1.9.3', plugin_helpers) self.weight = -7 self.icon_path = u':/plugins/plugin_images.png' self.icon = build_icon(self.icon_path) diff --git a/openlp/plugins/media/mediaplugin.py b/openlp/plugins/media/mediaplugin.py index db326f843..e50333eb2 100644 --- a/openlp/plugins/media/mediaplugin.py +++ b/openlp/plugins/media/mediaplugin.py @@ -37,7 +37,7 @@ class MediaPlugin(Plugin): log.info(u'%s MediaPlugin loaded', __name__) def __init__(self, plugin_helpers): - Plugin.__init__(self, u'Media', u'1.9.2', plugin_helpers) + Plugin.__init__(self, u'Media', u'1.9.3', plugin_helpers) self.weight = -6 self.icon_path = u':/plugins/plugin_media.png' self.icon = build_icon(self.icon_path) diff --git a/openlp/plugins/presentations/lib/impresscontroller.py b/openlp/plugins/presentations/lib/impresscontroller.py index 9ada43a5a..d054c3e9c 100644 --- a/openlp/plugins/presentations/lib/impresscontroller.py +++ b/openlp/plugins/presentations/lib/impresscontroller.py @@ -74,6 +74,7 @@ class ImpressController(PresentationController): self.process = None self.desktop = None self.manager = None + self.uno_connection_type = u'pipe' #u'socket' def check_available(self): """ @@ -98,7 +99,14 @@ class ImpressController(PresentationController): self.manager._FlagAsMethod(u'Bridge_GetValueObject') else: # -headless - cmd = u'openoffice.org -nologo -norestore -minimized -invisible -nofirststartwizard -accept="socket,host=localhost,port=2002;urp;"' + if self.uno_connection_type == u'pipe': + cmd = u'openoffice.org -nologo -norestore -minimized ' \ + + u'-invisible -nofirststartwizard ' \ + + u'-accept=pipe,name=openlp_pipe;urp;' + else: + cmd = u'openoffice.org -nologo -norestore -minimized ' \ + + u'-invisible -nofirststartwizard ' \ + + u'-accept=socket,host=localhost,port=2002;urp;' self.process = QtCore.QProcess() self.process.startDetached(cmd) self.process.waitForStarted() @@ -120,8 +128,14 @@ class ImpressController(PresentationController): while ctx is None and loop < 3: try: log.debug(u'get UNO Desktop Openoffice - resolve') - ctx = resolver.resolve(u'uno:socket,host=localhost,port=2002;' - u'urp;StarOffice.ComponentContext') + if self.uno_connection_type == u'pipe': + ctx = resolver.resolve(u'uno:' \ + + u'pipe,name=openlp_pipe;' \ + + u'urp;StarOffice.ComponentContext') + else: + ctx = resolver.resolve(u'uno:' \ + + u'socket,host=localhost,port=2002;' \ + + u'urp;StarOffice.ComponentContext') except: log.exception(u'Unable to find running instance ') self.start_process() diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index 001aeac4d..60b455368 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -212,7 +212,7 @@ class PresentationMediaItem(MediaManagerItem): self, translate('PresentationPlugin.MediaItem', 'Unsupported File'), translate('PresentationPlugin.MediaItem', - 'This type of presentation is not supported')) + 'This type of presentation is not supported.')) continue item_name = QtGui.QListWidgetItem(filename) item_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(file)) diff --git a/openlp/plugins/presentations/presentationplugin.py b/openlp/plugins/presentations/presentationplugin.py index e63063ffd..ada695625 100644 --- a/openlp/plugins/presentations/presentationplugin.py +++ b/openlp/plugins/presentations/presentationplugin.py @@ -51,7 +51,7 @@ class PresentationPlugin(Plugin): """ log.debug(u'Initialised') self.controllers = {} - Plugin.__init__(self, u'Presentations', u'1.9.2', plugin_helpers) + Plugin.__init__(self, u'Presentations', u'1.9.3', plugin_helpers) self.weight = -8 self.icon_path = u':/plugins/plugin_presentations.png' self.icon = build_icon(self.icon_path) diff --git a/openlp/plugins/remotes/html/jquery.js b/openlp/plugins/remotes/html/jquery.js old mode 100755 new mode 100644 diff --git a/openlp/plugins/remotes/remoteplugin.py b/openlp/plugins/remotes/remoteplugin.py index 59ad9a99c..927a706a3 100644 --- a/openlp/plugins/remotes/remoteplugin.py +++ b/openlp/plugins/remotes/remoteplugin.py @@ -38,7 +38,7 @@ class RemotesPlugin(Plugin): """ remotes constructor """ - Plugin.__init__(self, u'Remotes', u'1.9.2', plugin_helpers) + Plugin.__init__(self, u'Remotes', u'1.9.3', plugin_helpers) self.icon = build_icon(u':/plugins/plugin_remote.png') self.weight = -1 self.server = None diff --git a/openlp/plugins/songs/forms/authorsform.py b/openlp/plugins/songs/forms/authorsform.py index c7d1b0396..1dacd82cc 100644 --- a/openlp/plugins/songs/forms/authorsform.py +++ b/openlp/plugins/songs/forms/authorsform.py @@ -97,8 +97,7 @@ class AuthorsForm(QtGui.QDialog, Ui_AuthorsDialog): self, translate('SongsPlugin.AuthorsForm', 'Error'), translate('SongsPlugin.AuthorsForm', 'You have not set a display name for the ' - 'author, would you like me to combine the first and ' - 'last names for you?'), + 'author, combine the first and last names?'), QtGui.QMessageBox.StandardButtons( QtGui.QMessageBox.Yes | QtGui.QMessageBox.No) ) == QtGui.QMessageBox.Yes: diff --git a/openlp/plugins/songs/forms/editsongdialog.py b/openlp/plugins/songs/forms/editsongdialog.py index 870a68bca..0d2e65f95 100644 --- a/openlp/plugins/songs/forms/editsongdialog.py +++ b/openlp/plugins/songs/forms/editsongdialog.py @@ -258,10 +258,11 @@ class Ui_EditSongDialog(object): self.TopicBookLayout.addWidget(self.TopicGroupBox) self.SongBookGroup = QtGui.QGroupBox(self.TopicBookWidget) self.SongBookGroup.setObjectName(u'SongBookGroup') - self.SongbookLayout = QtGui.QGridLayout(self.SongBookGroup) + self.SongbookLayout = QtGui.QFormLayout(self.SongBookGroup) self.SongbookLayout.setMargin(8) self.SongbookLayout.setSpacing(8) self.SongbookLayout.setObjectName(u'SongbookLayout') + self.SongbookNameLabel = QtGui.QLabel(self.SongBookGroup) self.SongbookCombo = QtGui.QComboBox(self.SongBookGroup) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Fixed) @@ -272,13 +273,11 @@ class Ui_EditSongDialog(object): self.SongbookCombo.setEditable(True) self.SongbookCombo.setSizePolicy(sizePolicy) self.SongbookCombo.setObjectName(u'SongbookCombo') - self.SongbookLayout.addWidget(self.SongbookCombo, 0, 0, 1, 1) + self.SongbookLayout.addRow(self.SongbookNameLabel, self.SongbookCombo) self.songBookNumberLabel = QtGui.QLabel(self.SongBookGroup) - self.SongbookLayout.addWidget(self.songBookNumberLabel, 0, 1, 1, 1) self.songBookNumberEdit = QtGui.QLineEdit(self.SongBookGroup) - self.songBookNumberLabel.setBuddy(self.songBookNumberEdit) - self.songBookNumberEdit.setMaximumWidth(35) - self.SongbookLayout.addWidget(self.songBookNumberEdit, 0, 2, 1, 1) + self.SongbookLayout.addRow(self.songBookNumberLabel, + self.songBookNumberEdit) self.TopicBookLayout.addWidget(self.SongBookGroup) self.AuthorsTabLayout.addWidget(self.TopicBookWidget) self.SongTabWidget.addTab(self.AuthorsTab, u'') @@ -446,8 +445,10 @@ class Ui_EditSongDialog(object): translate('SongsPlugin.EditSongForm', 'R&emove')) self.SongBookGroup.setTitle( translate('SongsPlugin.EditSongForm', 'Song Book')) + self.SongbookNameLabel.setText(translate('SongsPlugin.EditSongForm', + 'Book:')) self.songBookNumberLabel.setText(translate('SongsPlugin.EditSongForm', - 'Song No.:')) + 'Number:')) self.SongTabWidget.setTabText( self.SongTabWidget.indexOf(self.AuthorsTab), translate('SongsPlugin.EditSongForm', diff --git a/openlp/plugins/songs/forms/editsongform.py b/openlp/plugins/songs/forms/editsongform.py index 458e7200c..ae9938ab4 100644 --- a/openlp/plugins/songs/forms/editsongform.py +++ b/openlp/plugins/songs/forms/editsongform.py @@ -621,6 +621,10 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): self.close() def saveSong(self): + """ + Get all the data from the widgets on the form, and then save it to the + database. + """ self.song.title = unicode(self.TitleEditItem.text()) self.song.alternate_title = unicode(self.AlternativeEdit.text()) self.song.copyright = unicode(self.CopyrightEditItem.text()) @@ -646,6 +650,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): self.song.topics.append(self.songmanager.get_object(Topic, topicId)) self.songmanager.save_object(self.song) + self.song = None return True return False diff --git a/openlp/plugins/songs/forms/songimportform.py b/openlp/plugins/songs/forms/songimportform.py index 0c58b8650..c62fa058e 100644 --- a/openlp/plugins/songs/forms/songimportform.py +++ b/openlp/plugins/songs/forms/songimportform.py @@ -57,6 +57,15 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard): self.registerFields() self.finishButton = self.button(QtGui.QWizard.FinishButton) self.cancelButton = self.button(QtGui.QWizard.CancelButton) + if not SongFormat.get_availability(SongFormat.OpenLP1): + self.openLP1DisabledWidget.setVisible(True) + self.openLP1ImportWidget.setVisible(False) + if not SongFormat.get_availability(SongFormat.SongsOfFellowship): + self.songsOfFellowshipDisabledWidget.setVisible(True) + self.songsOfFellowshipImportWidget.setVisible(False) + if not SongFormat.get_availability(SongFormat.Generic): + self.genericDisabledWidget.setVisible(True) + self.genericImportWidget.setVisible(False) self.plugin = plugin QtCore.QObject.connect(self.openLP2BrowseButton, QtCore.SIGNAL(u'clicked()'), @@ -64,12 +73,12 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard): QtCore.QObject.connect(self.openLP1BrowseButton, QtCore.SIGNAL(u'clicked()'), self.onOpenLP1BrowseButtonClicked) - QtCore.QObject.connect(self.openLyricsAddButton, - QtCore.SIGNAL(u'clicked()'), - self.onOpenLyricsAddButtonClicked) - QtCore.QObject.connect(self.openLyricsRemoveButton, - QtCore.SIGNAL(u'clicked()'), - self.onOpenLyricsRemoveButtonClicked) + #QtCore.QObject.connect(self.openLyricsAddButton, + # QtCore.SIGNAL(u'clicked()'), + # self.onOpenLyricsAddButtonClicked) + #QtCore.QObject.connect(self.openLyricsRemoveButton, + # QtCore.SIGNAL(u'clicked()'), + # self.onOpenLyricsRemoveButtonClicked) QtCore.QObject.connect(self.openSongAddButton, QtCore.SIGNAL(u'clicked()'), self.onOpenSongAddButtonClicked) @@ -100,6 +109,9 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard): QtCore.QObject.connect(self.genericRemoveButton, QtCore.SIGNAL(u'clicked()'), self.onGenericRemoveButtonClicked) + QtCore.QObject.connect(self.ewBrowseButton, + QtCore.SIGNAL(u'clicked()'), + self.onEWBrowseButtonClicked) QtCore.QObject.connect(self.cancelButton, QtCore.SIGNAL(u'clicked(bool)'), self.onCancelButtonClicked) @@ -145,15 +157,16 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard): self.openLP1BrowseButton.setFocus() return False elif source_format == SongFormat.OpenLyrics: - if self.openLyricsFileListWidget.count() == 0: - QtGui.QMessageBox.critical(self, - translate('SongsPlugin.ImportWizardForm', - 'No OpenLyrics Files Selected'), - translate('SongsPlugin.ImportWizardForm', - 'You need to add at least one OpenLyrics ' - 'song file to import from.')) - self.openLyricsAddButton.setFocus() - return False + #if self.openLyricsFileListWidget.count() == 0: + # QtGui.QMessageBox.critical(self, + # translate('SongsPlugin.ImportWizardForm', + # 'No OpenLyrics Files Selected'), + # translate('SongsPlugin.ImportWizardForm', + # 'You need to add at least one OpenLyrics ' + # 'song file to import from.')) + # self.openLyricsAddButton.setFocus() + # return False + return False elif source_format == SongFormat.OpenSong: if self.openSongFileListWidget.count() == 0: QtGui.QMessageBox.critical(self, @@ -204,6 +217,16 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard): 'presentation file to import from.')) self.genericAddButton.setFocus() return False + elif source_format == SongFormat.EasyWorship: + if self.ewFilenameEdit.text().isEmpty(): + QtGui.QMessageBox.critical(self, + translate('SongsPlugin.ImportWizardForm', + 'No EasyWorship Song Database Selected'), + translate('SongsPlugin.ImportWizardForm', + 'You need to select an EasyWorship song database ' + 'file to import from.')) + self.ewBrowseButton.setFocus() + return False return True elif self.currentId() == 2: # Progress page @@ -252,15 +275,15 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard): self.openLP1FilenameEdit ) - def onOpenLyricsAddButtonClicked(self): - self.getFiles( - translate('SongsPlugin.ImportWizardForm', - 'Select OpenLyrics Files'), - self.openLyricsFileListWidget - ) + #def onOpenLyricsAddButtonClicked(self): + # self.getFiles( + # translate('SongsPlugin.ImportWizardForm', + # 'Select OpenLyrics Files'), + # self.openLyricsFileListWidget + # ) - def onOpenLyricsRemoveButtonClicked(self): - self.removeSelectedItems(self.openLyricsFileListWidget) + #def onOpenLyricsRemoveButtonClicked(self): + # self.removeSelectedItems(self.openLyricsFileListWidget) def onOpenSongAddButtonClicked(self): self.getFiles( @@ -312,6 +335,13 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard): def onGenericRemoveButtonClicked(self): self.removeSelectedItems(self.genericFileListWidget) + def onEWBrowseButtonClicked(self): + self.getFileName( + translate('SongsPlugin.ImportWizardForm', + 'Select EasyWorship Database File'), + self.ewFilenameEdit + ) + def onCancelButtonClicked(self, checked): """ Stop the import on pressing the cancel button. @@ -331,15 +361,18 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard): def setDefaults(self): self.restart() + self.finishButton.setVisible(False) + self.cancelButton.setVisible(True) self.formatComboBox.setCurrentIndex(0) self.openLP2FilenameEdit.setText(u'') self.openLP1FilenameEdit.setText(u'') - self.openLyricsFileListWidget.clear() + #self.openLyricsFileListWidget.clear() self.openSongFileListWidget.clear() self.wordsOfWorshipFileListWidget.clear() self.ccliFileListWidget.clear() self.songsOfFellowshipFileListWidget.clear() self.genericFileListWidget.clear() + self.ewFilenameEdit.setText(u'') #self.csvFilenameEdit.setText(u'') def incrementProgressBar(self, status_text, increment=1): @@ -410,6 +443,11 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard): importer = self.plugin.importSongs(SongFormat.Generic, filenames=self.getListOfFiles(self.genericFileListWidget) ) + elif source_format == SongFormat.EasyWorship: + # Import an OpenLP 2.0 database + importer = self.plugin.importSongs(SongFormat.EasyWorship, + filename=unicode(self.ewFilenameEdit.text()) + ) success = importer.do_import() if success: # reload songs diff --git a/openlp/plugins/songs/forms/songimportwizard.py b/openlp/plugins/songs/forms/songimportwizard.py index f174d5ce3..0fb36cfe7 100644 --- a/openlp/plugins/songs/forms/songimportwizard.py +++ b/openlp/plugins/songs/forms/songimportwizard.py @@ -96,6 +96,7 @@ class Ui_SongImportWizard(object): self.formatComboBox.addItem(u'') self.formatComboBox.addItem(u'') self.formatComboBox.addItem(u'') + self.formatComboBox.addItem(u'') # self.formatComboBox.addItem(u'') self.formatLayout.addWidget(self.formatComboBox) self.formatSpacer = QtGui.QSpacerItem(40, 20, @@ -131,26 +132,43 @@ class Ui_SongImportWizard(object): # openlp.org 1.x self.openLP1Page = QtGui.QWidget() self.openLP1Page.setObjectName(u'openLP1Page') - self.openLP1Layout = QtGui.QFormLayout(self.openLP1Page) + self.openLP1Layout = QtGui.QVBoxLayout(self.openLP1Page) self.openLP1Layout.setMargin(0) - self.openLP1Layout.setSpacing(8) + self.openLP1Layout.setSpacing(0) self.openLP1Layout.setObjectName(u'openLP1Layout') - self.openLP1FilenameLabel = QtGui.QLabel(self.openLP1Page) + self.openLP1DisabledWidget = QtGui.QWidget(self.openLP1Page) + self.openLP1DisabledLayout = QtGui.QVBoxLayout(self.openLP1DisabledWidget) + self.openLP1DisabledLayout.setMargin(0) + self.openLP1DisabledLayout.setSpacing(8) + self.openLP1DisabledLayout.setObjectName(u'openLP1DisabledLayout') + self.openLP1DisabledLabel = QtGui.QLabel(self.openLP1DisabledWidget) + self.openLP1DisabledLabel.setWordWrap(True) + self.openLP1DisabledLabel.setObjectName(u'openLP1DisabledLabel') + self.openLP1DisabledLayout.addWidget(self.openLP1DisabledLabel) + self.openLP1DisabledWidget.setVisible(False) + self.openLP1Layout.addWidget(self.openLP1DisabledWidget) + self.openLP1ImportWidget = QtGui.QWidget(self.openLP1Page) + self.openLP1ImportLayout = QtGui.QFormLayout(self.openLP1ImportWidget) + self.openLP1ImportLayout.setMargin(0) + self.openLP1ImportLayout.setSpacing(8) + self.openLP1ImportLayout.setObjectName(u'openLP1ImportLayout') + self.openLP1FilenameLabel = QtGui.QLabel(self.openLP1ImportWidget) self.openLP1FilenameLabel.setObjectName(u'openLP1FilenameLabel') - self.openLP1Layout.setWidget(0, QtGui.QFormLayout.LabelRole, + self.openLP1ImportLayout.setWidget(0, QtGui.QFormLayout.LabelRole, self.openLP1FilenameLabel) self.openLP1FileLayout = QtGui.QHBoxLayout() self.openLP1FileLayout.setSpacing(8) self.openLP1FileLayout.setObjectName(u'openLP1FileLayout') - self.openLP1FilenameEdit = QtGui.QLineEdit(self.openLP1Page) + self.openLP1FilenameEdit = QtGui.QLineEdit(self.openLP1ImportWidget) self.openLP1FilenameEdit.setObjectName(u'openLP1FilenameEdit') self.openLP1FileLayout.addWidget(self.openLP1FilenameEdit) - self.openLP1BrowseButton = QtGui.QToolButton(self.openLP1Page) + self.openLP1BrowseButton = QtGui.QToolButton(self.openLP1ImportWidget) self.openLP1BrowseButton.setIcon(openIcon) self.openLP1BrowseButton.setObjectName(u'openLP1BrowseButton') self.openLP1FileLayout.addWidget(self.openLP1BrowseButton) - self.openLP1Layout.setLayout(0, QtGui.QFormLayout.FieldRole, + self.openLP1ImportLayout.setLayout(0, QtGui.QFormLayout.FieldRole, self.openLP1FileLayout) + self.openLP1Layout.addWidget(self.openLP1ImportWidget) self.formatStackedWidget.addWidget(self.openLP1Page) # OpenLyrics self.openLyricsPage = QtGui.QWidget() @@ -159,26 +177,31 @@ class Ui_SongImportWizard(object): self.openLyricsLayout.setSpacing(8) self.openLyricsLayout.setMargin(0) self.openLyricsLayout.setObjectName(u'OpenLyricsLayout') - self.openLyricsFileListWidget = QtGui.QListWidget(self.openLyricsPage) - self.openLyricsFileListWidget.setSelectionMode( - QtGui.QAbstractItemView.ExtendedSelection) - self.openLyricsFileListWidget.setObjectName(u'OpenLyricsFileListWidget') - self.openLyricsLayout.addWidget(self.openLyricsFileListWidget) - self.openLyricsButtonLayout = QtGui.QHBoxLayout() - self.openLyricsButtonLayout.setSpacing(8) - self.openLyricsButtonLayout.setObjectName(u'OpenLyricsButtonLayout') - self.openLyricsAddButton = QtGui.QPushButton(self.openLyricsPage) - self.openLyricsAddButton.setIcon(openIcon) - self.openLyricsAddButton.setObjectName(u'OpenLyricsAddButton') - self.openLyricsButtonLayout.addWidget(self.openLyricsAddButton) - self.openLyricsButtonSpacer = QtGui.QSpacerItem(40, 20, - QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.openLyricsButtonLayout.addItem(self.openLyricsButtonSpacer) - self.openLyricsRemoveButton = QtGui.QPushButton(self.openLyricsPage) - self.openLyricsRemoveButton.setIcon(deleteIcon) - self.openLyricsRemoveButton.setObjectName(u'OpenLyricsRemoveButton') - self.openLyricsButtonLayout.addWidget(self.openLyricsRemoveButton) - self.openLyricsLayout.addLayout(self.openLyricsButtonLayout) + self.openLyricsDisabledLabel = QtGui.QLabel(self.openLyricsPage) + self.openLyricsDisabledLabel.setWordWrap(True) + self.openLyricsDisabledLabel.setObjectName(u'openLyricsDisabledLabel') + self.openLyricsLayout.addWidget(self.openLyricsDisabledLabel) + # Commented out for future use. + #self.openLyricsFileListWidget = QtGui.QListWidget(self.openLyricsPage) + #self.openLyricsFileListWidget.setSelectionMode( + # QtGui.QAbstractItemView.ExtendedSelection) + #self.openLyricsFileListWidget.setObjectName(u'OpenLyricsFileListWidget') + #self.openLyricsLayout.addWidget(self.openLyricsFileListWidget) + #self.openLyricsButtonLayout = QtGui.QHBoxLayout() + #self.openLyricsButtonLayout.setSpacing(8) + #self.openLyricsButtonLayout.setObjectName(u'OpenLyricsButtonLayout') + #self.openLyricsAddButton = QtGui.QPushButton(self.openLyricsPage) + #self.openLyricsAddButton.setIcon(openIcon) + #self.openLyricsAddButton.setObjectName(u'OpenLyricsAddButton') + #self.openLyricsButtonLayout.addWidget(self.openLyricsAddButton) + #self.openLyricsButtonSpacer = QtGui.QSpacerItem(40, 20, + # QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + #self.openLyricsButtonLayout.addItem(self.openLyricsButtonSpacer) + #self.openLyricsRemoveButton = QtGui.QPushButton(self.openLyricsPage) + #self.openLyricsRemoveButton.setIcon(deleteIcon) + #self.openLyricsRemoveButton.setObjectName(u'OpenLyricsRemoveButton') + #self.openLyricsButtonLayout.addWidget(self.openLyricsRemoveButton) + #self.openLyricsLayout.addLayout(self.openLyricsButtonLayout) self.formatStackedWidget.addWidget(self.openLyricsPage) # Open Song self.openSongPage = QtGui.QWidget() @@ -277,22 +300,52 @@ class Ui_SongImportWizard(object): self.songsOfFellowshipLayout = QtGui.QVBoxLayout( self.songsOfFellowshipPage) self.songsOfFellowshipLayout.setMargin(0) - self.songsOfFellowshipLayout.setSpacing(8) + self.songsOfFellowshipLayout.setSpacing(0) self.songsOfFellowshipLayout.setObjectName(u'songsOfFellowshipLayout') - self.songsOfFellowshipFileListWidget = QtGui.QListWidget( + self.songsOfFellowshipDisabledWidget = QtGui.QWidget( self.songsOfFellowshipPage) + self.songsOfFellowshipDisabledWidget.setVisible(False) + self.songsOfFellowshipDisabledWidget.setObjectName( + u'songsOfFellowshipDisabledWidget') + self.songsOfFellowshipDisabledLayout = QtGui.QVBoxLayout( + self.songsOfFellowshipDisabledWidget) + self.songsOfFellowshipDisabledLayout.setMargin(0) + self.songsOfFellowshipDisabledLayout.setSpacing(8) + self.songsOfFellowshipDisabledLayout.setObjectName( + u'songsOfFellowshipDisabledLayout') + self.songsOfFellowshipDisabledLabel = QtGui.QLabel( + self.songsOfFellowshipDisabledWidget) + self.songsOfFellowshipDisabledLabel.setWordWrap(True) + self.songsOfFellowshipDisabledLabel.setObjectName( + u'songsOfFellowshipDisabledLabel') + self.songsOfFellowshipDisabledLayout.addWidget( + self.songsOfFellowshipDisabledLabel) + self.songsOfFellowshipLayout.addWidget( + self.songsOfFellowshipDisabledWidget) + self.songsOfFellowshipImportWidget = QtGui.QWidget( + self.songsOfFellowshipPage) + self.songsOfFellowshipImportWidget.setObjectName( + u'songsOfFellowshipImportWidget') + self.songsOfFellowshipImportLayout = QtGui.QVBoxLayout( + self.songsOfFellowshipImportWidget) + self.songsOfFellowshipImportLayout.setMargin(0) + self.songsOfFellowshipImportLayout.setSpacing(8) + self.songsOfFellowshipImportLayout.setObjectName( + u'songsOfFellowshipImportLayout') + self.songsOfFellowshipFileListWidget = QtGui.QListWidget( + self.songsOfFellowshipImportWidget) self.songsOfFellowshipFileListWidget.setSelectionMode( QtGui.QAbstractItemView.ExtendedSelection) self.songsOfFellowshipFileListWidget.setObjectName( u'songsOfFellowshipFileListWidget') - self.songsOfFellowshipLayout.addWidget( + self.songsOfFellowshipImportLayout.addWidget( self.songsOfFellowshipFileListWidget) self.songsOfFellowshipButtonLayout = QtGui.QHBoxLayout() self.songsOfFellowshipButtonLayout.setSpacing(8) self.songsOfFellowshipButtonLayout.setObjectName( u'songsOfFellowshipButtonLayout') self.songsOfFellowshipAddButton = QtGui.QPushButton( - self.songsOfFellowshipPage) + self.songsOfFellowshipImportWidget) self.songsOfFellowshipAddButton.setIcon(openIcon) self.songsOfFellowshipAddButton.setObjectName( u'songsOfFellowshipAddButton') @@ -303,43 +356,88 @@ class Ui_SongImportWizard(object): self.songsOfFellowshipButtonLayout.addItem( self.songsOfFellowshipButtonSpacer) self.songsOfFellowshipRemoveButton = QtGui.QPushButton( - self.songsOfFellowshipPage) + self.songsOfFellowshipImportWidget) self.songsOfFellowshipRemoveButton.setIcon(deleteIcon) self.songsOfFellowshipRemoveButton.setObjectName( u'songsOfFellowshipRemoveButton') self.songsOfFellowshipButtonLayout.addWidget( self.songsOfFellowshipRemoveButton) - self.songsOfFellowshipLayout.addLayout( + self.songsOfFellowshipImportLayout.addLayout( self.songsOfFellowshipButtonLayout) + self.songsOfFellowshipLayout.addWidget( + self.songsOfFellowshipImportWidget) self.formatStackedWidget.addWidget(self.songsOfFellowshipPage) # Generic Document/Presentation import self.genericPage = QtGui.QWidget() self.genericPage.setObjectName(u'genericPage') self.genericLayout = QtGui.QVBoxLayout(self.genericPage) self.genericLayout.setMargin(0) - self.genericLayout.setSpacing(8) + self.genericLayout.setSpacing(0) self.genericLayout.setObjectName(u'genericLayout') - self.genericFileListWidget = QtGui.QListWidget(self.genericPage) + self.genericDisabledWidget = QtGui.QWidget(self.genericPage) + self.genericDisabledWidget.setObjectName(u'genericDisabledWidget') + self.genericDisabledLayout = QtGui.QVBoxLayout(self.genericDisabledWidget) + self.genericDisabledLayout.setMargin(0) + self.genericDisabledLayout.setSpacing(8) + self.genericDisabledLayout.setObjectName(u'genericDisabledLayout') + self.genericDisabledLabel = QtGui.QLabel(self.genericDisabledWidget) + self.genericDisabledLabel.setWordWrap(True) + self.genericDisabledLabel.setObjectName(u'genericDisabledLabel') + self.genericDisabledWidget.setVisible(False) + self.genericDisabledLayout.addWidget(self.genericDisabledLabel) + self.genericLayout.addWidget(self.genericDisabledWidget) + self.genericImportWidget = QtGui.QWidget(self.genericPage) + self.genericImportWidget.setObjectName(u'genericImportWidget') + self.genericImportLayout = QtGui.QVBoxLayout(self.genericImportWidget) + self.genericImportLayout.setMargin(0) + self.genericImportLayout.setSpacing(8) + self.genericImportLayout.setObjectName(u'genericImportLayout') + self.genericFileListWidget = QtGui.QListWidget(self.genericImportWidget) self.genericFileListWidget.setSelectionMode( QtGui.QAbstractItemView.ExtendedSelection) self.genericFileListWidget.setObjectName(u'genericFileListWidget') - self.genericLayout.addWidget(self.genericFileListWidget) + self.genericImportLayout.addWidget(self.genericFileListWidget) self.genericButtonLayout = QtGui.QHBoxLayout() self.genericButtonLayout.setSpacing(8) self.genericButtonLayout.setObjectName(u'genericButtonLayout') - self.genericAddButton = QtGui.QPushButton(self.genericPage) + self.genericAddButton = QtGui.QPushButton(self.genericImportWidget) self.genericAddButton.setIcon(openIcon) self.genericAddButton.setObjectName(u'genericAddButton') self.genericButtonLayout.addWidget(self.genericAddButton) self.genericButtonSpacer = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) self.genericButtonLayout.addItem(self.genericButtonSpacer) - self.genericRemoveButton = QtGui.QPushButton(self.genericPage) + self.genericRemoveButton = QtGui.QPushButton(self.genericImportWidget) self.genericRemoveButton.setIcon(deleteIcon) self.genericRemoveButton.setObjectName(u'genericRemoveButton') self.genericButtonLayout.addWidget(self.genericRemoveButton) - self.genericLayout.addLayout(self.genericButtonLayout) + self.genericImportLayout.addLayout(self.genericButtonLayout) + self.genericLayout.addWidget(self.genericImportWidget) self.formatStackedWidget.addWidget(self.genericPage) + # EasyWorship + self.ewPage = QtGui.QWidget() + self.ewPage.setObjectName(u'ewPage') + self.ewLayout = QtGui.QFormLayout(self.ewPage) + self.ewLayout.setMargin(0) + self.ewLayout.setSpacing(8) + self.ewLayout.setObjectName(u'ewLayout') + self.ewFilenameLabel = QtGui.QLabel(self.ewPage) + self.ewFilenameLabel.setObjectName(u'ewFilenameLabel') + self.ewLayout.setWidget(0, QtGui.QFormLayout.LabelRole, + self.ewFilenameLabel) + self.ewFileLayout = QtGui.QHBoxLayout() + self.ewFileLayout.setSpacing(8) + self.ewFileLayout.setObjectName(u'ewFileLayout') + self.ewFilenameEdit = QtGui.QLineEdit(self.ewPage) + self.ewFilenameEdit.setObjectName(u'ewFilenameEdit') + self.ewFileLayout.addWidget(self.ewFilenameEdit) + self.ewBrowseButton = QtGui.QToolButton(self.ewPage) + self.ewBrowseButton.setIcon(openIcon) + self.ewBrowseButton.setObjectName(u'ewBrowseButton') + self.ewFileLayout.addWidget(self.ewBrowseButton) + self.ewLayout.setLayout(0, QtGui.QFormLayout.FieldRole, + self.ewFileLayout) + self.formatStackedWidget.addWidget(self.ewPage) # Commented out for future use. # self.csvPage = QtGui.QWidget() # self.csvPage.setObjectName(u'CSVPage') @@ -424,7 +522,9 @@ class Ui_SongImportWizard(object): self.formatComboBox.setItemText(7, translate('SongsPlugin.ImportWizardForm', 'Generic Document/Presentation')) -# self.formatComboBox.setItemText(8, + self.formatComboBox.setItemText(8, + translate('SongsPlugin.ImportWizardForm', 'EasyWorship')) +# self.formatComboBox.setItemText(9, # translate('SongsPlugin.ImportWizardForm', 'CSV')) self.openLP2FilenameLabel.setText( translate('SongsPlugin.ImportWizardForm', 'Filename:')) @@ -434,10 +534,20 @@ class Ui_SongImportWizard(object): translate('SongsPlugin.ImportWizardForm', 'Filename:')) self.openLP1BrowseButton.setText( translate('SongsPlugin.ImportWizardForm', 'Browse...')) - self.openLyricsAddButton.setText( - translate('SongsPlugin.ImportWizardForm', 'Add Files...')) - self.openLyricsRemoveButton.setText( - translate('SongsPlugin.ImportWizardForm', 'Remove File(s)')) + self.openLP1DisabledLabel.setText( + translate('SongsPlugin.ImportWizardForm', 'The openlp.org 1.x ' + 'importer has been disabled due to a missing Python module. If ' + 'you want to use this importer, you will need to install the ' + '"python-sqlite" module.')) + #self.openLyricsAddButton.setText( + # translate('SongsPlugin.ImportWizardForm', 'Add Files...')) + #self.openLyricsRemoveButton.setText( + # translate('SongsPlugin.ImportWizardForm', 'Remove File(s)')) + self.openLyricsDisabledLabel.setText( + translate('SongsPlugin.ImportWizardForm', 'The OpenLyrics ' + 'importer has not yet been developed, but as you can see, we are ' + 'still intending to do so. Hopefully it will be in the next ' + 'release.')) self.openSongAddButton.setText( translate('SongsPlugin.ImportWizardForm', 'Add Files...')) self.openSongRemoveButton.setText( @@ -454,10 +564,22 @@ class Ui_SongImportWizard(object): translate('SongsPlugin.ImportWizardForm', 'Add Files...')) self.songsOfFellowshipRemoveButton.setText( translate('SongsPlugin.ImportWizardForm', 'Remove File(s)')) + self.songsOfFellowshipDisabledLabel.setText( + translate('SongsPlugin.ImportWizardForm', 'The Songs of ' + 'Fellowship importer has been disabled because OpenLP cannot ' + 'find OpenOffice.org on your computer.')) self.genericAddButton.setText( translate('SongsPlugin.ImportWizardForm', 'Add Files...')) self.genericRemoveButton.setText( translate('SongsPlugin.ImportWizardForm', 'Remove File(s)')) + self.genericDisabledLabel.setText( + translate('SongsPlugin.ImportWizardForm', 'The generic document/' + 'presentation importer has been disabled because OpenLP cannot ' + 'find OpenOffice.org on your computer.')) + self.ewFilenameLabel.setText( + translate('SongsPlugin.ImportWizardForm', 'Filename:')) + self.ewBrowseButton.setText( + translate('SongsPlugin.ImportWizardForm', 'Browse...')) # self.csvFilenameLabel.setText( # translate('SongsPlugin.ImportWizardForm', 'Filename:')) # self.csvBrowseButton.setText( diff --git a/openlp/plugins/songs/forms/songmaintenanceform.py b/openlp/plugins/songs/forms/songmaintenanceform.py index a2c12a6d6..48f9a5a55 100644 --- a/openlp/plugins/songs/forms/songmaintenanceform.py +++ b/openlp/plugins/songs/forms/songmaintenanceform.py @@ -324,8 +324,8 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): QtGui.QMessageBox.critical(self, translate('SongsPlugin.SongMaintenanceForm', 'Error'), translate('SongsPlugin.SongMaintenanceForm', - 'Could not save your modified author, because he ' - 'already exists.')) + 'Could not save your modified author, because the ' + 'author already exists.')) def onTopicEditButtonClick(self): topic_id = self._getCurrentItemId(self.TopicsListWidget) diff --git a/openlp/plugins/songs/forms/topicsform.py b/openlp/plugins/songs/forms/topicsform.py index a618dd9db..c45228527 100644 --- a/openlp/plugins/songs/forms/topicsform.py +++ b/openlp/plugins/songs/forms/topicsform.py @@ -51,7 +51,7 @@ class TopicsForm(QtGui.QDialog, Ui_TopicsDialog): QtGui.QMessageBox.critical( self, translate('SongsPlugin.TopicsForm', 'Error'), translate('SongsPlugin.TopicsForm', - 'You need to type in a topic name!')) + 'You need to type in a topic name.')) self.NameEdit.setFocus() return False else: diff --git a/openlp/plugins/songs/lib/cclifileimport.py b/openlp/plugins/songs/lib/cclifileimport.py index 08bccef79..9d0da5cfa 100755 --- a/openlp/plugins/songs/lib/cclifileimport.py +++ b/openlp/plugins/songs/lib/cclifileimport.py @@ -57,7 +57,7 @@ class CCLIFileImport(SongImport): self.filenames = kwargs[u'filenames'] log.debug(self.filenames) else: - raise KeyError(u'Keyword argument "filenames" not supplied.') + raise KeyError(u'Keyword argument "filenames" not supplied.') def do_import(self): """ @@ -66,11 +66,11 @@ class CCLIFileImport(SongImport): log.debug(u'Starting CCLI File Import') song_total = len(self.filenames) self.import_wizard.importProgressBar.setMaximum(song_total) - song_count = 1 + song_count = 1 for filename in self.filenames: self.import_wizard.incrementProgressBar( - u'Importing song %s of %s' % (song_count, song_total)) - filename = unicode(filename) + u'Importing song %s of %s' % (song_count, song_total)) + filename = unicode(filename) log.debug(u'Importing CCLI File: %s', filename) lines = [] if os.path.isfile(filename): @@ -81,33 +81,34 @@ class CCLIFileImport(SongImport): lines = infile.readlines() ext = os.path.splitext(filename)[1] if ext.lower() == ".usr": - log.info(u'SongSelect .usr format file found %s: ' , filename) + log.info(u'SongSelect .usr format file found %s: ', + filename) self.do_import_usr_file(lines) elif ext.lower() == ".txt": - log.info(u'SongSelect .txt format file found %s: ', filename) + log.info(u'SongSelect .txt format file found %s: ', + filename) self.do_import_txt_file(lines) else: log.info(u'Extension %s is not valid', filename) - pass song_count += 1 if self.stop_import_flag: - return False + return False return True def do_import_usr_file(self, textList): """ The :method:`do_import_usr_file` method provides OpenLP with the ability to import CCLI SongSelect songs in - *USR* file format - + *USR* file format + ``textList`` An array of strings containing the usr file content. - + **SongSelect .usr file format** ``[File]`` USR file format first line ``Type=`` - Indicates the file type + Indicates the file type e.g. *Type=SongSelect Import File* ``Version=3.0`` File format version @@ -116,7 +117,7 @@ class CCLIFileImport(SongImport): ``Title=`` Contains the song title (e.g. *Title=Above All*) ``Author=`` - Contains a | delimited list of the song authors + Contains a | delimited list of the song authors e.g. *Author=LeBlanc, Lenny | Baloche, Paul* ``Copyright=`` Contains a | delimited list of the song copyrights @@ -136,7 +137,7 @@ class CCLIFileImport(SongImport): Contains a list of the songs fields in order /t delimited e.g. *Fields=Vers 1/tVers 2/tChorus 1/tAndere 1* ``Words=`` - Contains the songs various lyrics in order as shown by the + Contains the songs various lyrics in order as shown by the *Fields* description e.g. *Words=Above all powers....* [/n = CR, /n/t = CRLF] """ @@ -174,8 +175,8 @@ class CCLIFileImport(SongImport): verse_type = u'O' verse_text = unicode(words_list[counter]) verse_text = verse_text.replace("/n", "\n") - if len(verse_text) > 0: - self.add_verse(verse_text, verse_type); + if len(verse_text) > 0: + self.add_verse(verse_text, verse_type) #Handle multiple authors author_list = song_author.split(u'/') if len(author_list) < 2: @@ -192,10 +193,10 @@ class CCLIFileImport(SongImport): """ The :method:`do_import_txt_file` method provides OpenLP with the ability to import CCLI SongSelect songs in - *TXT* file format - + *TXT* file format + ``textList`` - An array of strings containing the txt file content. + An array of strings containing the txt file content. **SongSelect .txt file format** @@ -225,38 +226,38 @@ class CCLIFileImport(SongImport): e.g. CCLI Number (e.g.CCLI-Liednummer: 2672885) ``Song Copyright`` e.g. © 1999 Integrity's Hosanna! Music | LenSongs Publishing - ``Song Authors`` + ``Song Authors`` e.g. Lenny LeBlanc | Paul Baloche ``Licencing info`` - e.g. For use solely with the SongSelect Terms of Use. + e.g. For use solely with the SongSelect Terms of Use. All rights Reserved. www.ccli.com - ``CCLI Licence number of user`` - e.g. CCL-Liedlizenznummer: 14 / CCLI License No. 14 + ``CCLI Licence number of user`` + e.g. CCL-Liedlizenznummer: 14 / CCLI License No. 14 """ log.debug(u'TXT file text: %s', textList) self.set_defaults() line_number = 0 verse_text = u'' song_comments = u'' - song_copyright = u''; + song_copyright = u'' verse_start = False for line in textList: clean_line = line.strip() if not clean_line: - if line_number==0: + if line_number == 0: continue elif verse_start: - if verse_text: + if verse_text: self.add_verse(verse_text, verse_type) verse_text = '' verse_start = False else: #line_number=0, song title - if line_number==0: + if line_number == 0: song_name = clean_line line_number += 1 - #line_number=1, verses - elif line_number==1: + #line_number=1, verses + elif line_number == 1: #line_number=1, ccli number, first line after verses if clean_line.startswith(u'CCLI'): line_number += 1 @@ -285,15 +286,16 @@ class CCLIFileImport(SongImport): verse_text = verse_text + line else: #line_number=2, copyright - if line_number==2: + if line_number == 2: line_number += 1 song_copyright = clean_line - #n=3, authors - elif line_number==3: + #n=3, authors + elif line_number == 3: line_number += 1 song_author = clean_line - #line_number=4, comments lines before last line - elif (line_number==4) and (not clean_line.startswith(u'CCL')): + #line_number=4, comments lines before last line + elif (line_number == 4) and \ + (not clean_line.startswith(u'CCL')): song_comments = song_comments + clean_line # split on known separators author_list = song_author.split(u'/') @@ -307,4 +309,3 @@ class CCLIFileImport(SongImport): self.ccli_number = song_ccli self.comments = song_comments self.finish() - diff --git a/openlp/plugins/songs/lib/ewimport.py b/openlp/plugins/songs/lib/ewimport.py new file mode 100644 index 000000000..16db36b20 --- /dev/null +++ b/openlp/plugins/songs/lib/ewimport.py @@ -0,0 +1,255 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +""" +The :mod:`ewimport` module provides the functionality for importing +EasyWorship song databases into the current installation database. +""" + +import sys +import os +import struct + +from songimport import SongImport + +def strip_rtf(blob): + depth = 0 + control = False + clear_text = [] + control_word = [] + for c in blob: + if control: + # for delimiters, set control to False + if c == '{': + if len(control_word) > 0: + depth += 1 + control = False + elif c == '}': + if len(control_word) > 0: + depth -= 1 + control = False + elif c == '\\': + new_control = (len(control_word) > 0) + control = False + elif c.isspace(): + control = False + else: + control_word.append(c) + if len(control_word) == 3 and control_word[0] == '\'': + control = False + if not control: + if len(control_word) == 0: + if c == '{' or c == '}' or c == '\\': + clear_text.append(c) + else: + control_str = ''.join(control_word) + if control_str == 'par' or control_str == 'line': + clear_text.append(u'\n') + elif control_str == 'tab': + clear_text.append(u'\n') + elif control_str[0] == '\'': + # Really should take RTF character set into account but + # for now assume ANSI (Windows-1252) and call it good + s = chr(int(control_str[1:3], 16)) + clear_text.append(s.decode(u'windows-1252')) + del control_word[:] + if c == '\\' and new_control: + control = True + elif c == '{': + depth += 1 + elif c == '}': + depth -= 1 + elif depth > 2: + continue + elif c == '\n' or c == '\r': + continue + elif c == '\\': + control = True + else: + clear_text.append(c) + return u''.join(clear_text) + +class FieldDescEntry: + def __init__(self, name, type, size): + self.name = name + self.type = type + self.size = size + +class EasyWorshipSongImport(SongImport): + """ + The :class:`EasyWorshipSongImport` class provides OpenLP with the + ability to import EasyWorship song files. + """ + def __init__(self, manager, **kwargs): + self.import_source = kwargs[u'filename'] + SongImport.__init__(self, manager) + + def do_import(self): + # Open the DB and MB files if they exist + import_source_mb = self.import_source.replace('.DB', '.MB') + if not os.path.isfile(self.import_source): + return False + if not os.path.isfile(import_source_mb): + return False + db_size = os.path.getsize(self.import_source) + if db_size < 0x800: + return False + db_file = open(self.import_source, 'rb') + self.memo_file = open(import_source_mb, 'rb') + # Don't accept files that are clearly not paradox files + record_size, header_size, block_size, first_block, num_fields \ + = struct.unpack(' 4: + db_file.close() + self.memo_file.close() + return False + # There does not appear to be a _reliable_ way of getting the number + # of songs/records, so let's use file blocks for measuring progress. + total_blocks = (db_size - header_size) / (block_size * 1024) + self.import_wizard.importProgressBar.setMaximum(total_blocks) + # Read the field description information + db_file.seek(120) + field_info = db_file.read(num_fields * 2) + db_file.seek(4 + (num_fields * 4) + 261, os.SEEK_CUR) + field_names = db_file.read(header_size - db_file.tell()).split('\0', + num_fields) + field_names.pop() + field_descs = [] + for i,field_name in enumerate(field_names): + field_type, field_size = struct.unpack_from('BB', field_info, i * 2) + field_descs.append(FieldDescEntry(field_name, field_type, + field_size)) + self.set_record_struct(field_descs) + # Pick out the field description indexes we will need + success = True + try: + fi_title = self.find_field(u'Title') + fi_author = self.find_field(u'Author') + fi_copy = self.find_field(u'Copyright') + fi_admin = self.find_field(u'Administrator') + fi_words = self.find_field(u'Words') + fi_ccli = self.find_field(u'Song Number') + except IndexError: + # This is the wrong table + success = False + # Loop through each block of the file + cur_block = first_block + while cur_block != 0 and success: + db_file.seek(header_size + ((cur_block - 1) * 1024 * block_size)) + cur_block, rec_count = struct.unpack(''] + for field_desc in field_descs: + if field_desc.type == 1: + # string + fsl.append('%ds' % field_desc.size) + elif field_desc.type == 3: + # 16-bit int + fsl.append('H') + elif field_desc.type == 4: + # 32-bit int + fsl.append('I') + elif field_desc.type == 9: + # Logical + fsl.append('B') + elif field_desc.type == 0x0c: + # Memo + fsl.append('%ds' % field_desc.size) + elif field_desc.type == 0x0d: + # Blob + fsl.append('%ds' % field_desc.size) + elif field_desc.type == 0x15: + # Timestamp + fsl.append('Q') + else: + fsl.append('%ds' % field_desc.size) + self.record_struct = struct.Struct(''.join(fsl)) + self.field_descs = field_descs + + def get_field(self, field_desc_index): + field = self.fields[field_desc_index] + field_desc = self.field_descs[field_desc_index] + # Check for 'blank' entries + if isinstance(field, str): + if len(field.rstrip('\0')) == 0: + return u'' + elif field == 0: + return 0 + # Format the field depending on the field type + if field_desc.type == 1: + # string + return field.rstrip('\0').decode(u'windows-1252') + elif field_desc.type == 3: + # 16-bit int + return field ^ 0x8000 + elif field_desc.type == 4: + # 32-bit int + return field ^ 0x80000000 + elif field_desc.type == 9: + # Logical + return (field ^ 0x80 == 1) + elif field_desc.type == 0x0c or field_desc.type == 0x0d: + # Memo or Blob + sub_block, block_start, blob_size = \ + struct.unpack_from(' 63: + return u''; + self.memo_file.seek(11 + (5 * sub_block), os.SEEK_CUR) + sub_block_start, = struct.unpack('B', self.memo_file.read(1)) + self.memo_file.seek((block_start * 256) + + (sub_block_start * 16)) + else: + return u''; + return self.memo_file.read(blob_size) + else: + return 0 diff --git a/openlp/plugins/songs/lib/importer.py b/openlp/plugins/songs/lib/importer.py index da9618ef2..d8028db24 100644 --- a/openlp/plugins/songs/lib/importer.py +++ b/openlp/plugins/songs/lib/importer.py @@ -26,14 +26,25 @@ from opensongimport import OpenSongImport from olpimport import OpenLPSongImport -from olp1import import OpenLP1SongImport +from wowimport import WowImport +from cclifileimport import CCLIFileImport +from ewimport import EasyWorshipSongImport +# Imports that might fail +try: + from olp1import import OpenLP1SongImport + has_openlp1 = True +except ImportError: + has_openlp1 = False try: from sofimport import SofImport - from oooimport import OooImport - from cclifileimport import CCLIFileImport - from wowimport import WowImport + has_sof = True except ImportError: - pass + has_sof = False +try: + from oooimport import OooImport + has_ooo = True +except ImportError: + has_ooo = False class SongFormat(object): """ @@ -41,6 +52,7 @@ class SongFormat(object): plus a few helper functions to facilitate generic handling of song types for importing. """ + _format_availability = {} Unknown = -1 OpenLP2 = 0 OpenLP1 = 1 @@ -50,7 +62,8 @@ class SongFormat(object): CCLI = 5 SongsOfFellowship = 6 Generic = 7 - CSV = 8 + #CSV = 8 + EasyWorship = 8 @staticmethod def get_class(format): @@ -74,6 +87,8 @@ class SongFormat(object): return OooImport elif format == SongFormat.CCLI: return CCLIFileImport + elif format == SongFormat.EasyWorship: + return EasyWorshipSongImport # else: return None @@ -90,7 +105,20 @@ class SongFormat(object): SongFormat.WordsOfWorship, SongFormat.CCLI, SongFormat.SongsOfFellowship, - SongFormat.Generic + SongFormat.Generic, + SongFormat.EasyWorship ] + @staticmethod + def set_availability(format, available): + SongFormat._format_availability[format] = available + + @staticmethod + def get_availability(format): + return SongFormat._format_availability.get(format, True) + +SongFormat.set_availability(SongFormat.OpenLP1, has_openlp1) +SongFormat.set_availability(SongFormat.SongsOfFellowship, has_sof) +SongFormat.set_availability(SongFormat.Generic, has_ooo) + __all__ = [u'SongFormat'] diff --git a/openlp/plugins/songs/lib/olp1import.py b/openlp/plugins/songs/lib/olp1import.py index 51e3e1fbf..de750cb24 100644 --- a/openlp/plugins/songs/lib/olp1import.py +++ b/openlp/plugins/songs/lib/olp1import.py @@ -28,10 +28,8 @@ The :mod:`olp1import` module provides the functionality for importing openlp.org 1.x song databases into the current installation database. """ import logging -try: - import sqlite -except: - pass +import chardet +import sqlite from openlp.core.lib import translate from songimport import SongImport @@ -43,6 +41,8 @@ class OpenLP1SongImport(SongImport): The :class:`OpenLP1SongImport` class provides OpenLP with the ability to import song databases from installations of openlp.org 1.x. """ + last_encoding = u'windows-1252' + def __init__(self, manager, **kwargs): """ Initialise the import. @@ -56,6 +56,34 @@ class OpenLP1SongImport(SongImport): SongImport.__init__(self, manager) self.import_source = kwargs[u'filename'] + def decode_string(self, raw, guess): + """ + Use chardet to detect the encoding of the raw string, and convert it + to unicode. + + ``raw`` + The raw bytestring to decode. + ``guess`` + What chardet guessed the encoding to be. + """ + if guess[u'confidence'] < 0.8: + codec = u'windows-1252' + else: + codec = guess[u'encoding'] + try: + decoded = unicode(raw, codec) + self.last_encoding = codec + except UnicodeDecodeError: + log.exception(u'Error in detecting openlp.org 1.x database encoding.') + try: + decoded = unicode(raw, self.last_encoding) + except UnicodeDecodeError: + # possibly show an error form + #self.import_wizard.showError(u'There was a problem ' + # u'detecting the encoding of a string') + decoded = raw + return decoded + def do_import(self): """ Run the import for an openlp.org 1.x song database. @@ -63,6 +91,11 @@ class OpenLP1SongImport(SongImport): # Connect to the database connection = sqlite.connect(self.import_source) cursor = connection.cursor() + # Determine if we're using a new or an old DB + cursor.execute(u'SELECT name FROM sqlite_master ' + u'WHERE type = \'table\' AND name = \'tracks\'') + table_list = cursor.fetchall() + new_db = len(table_list) > 0 # Count the number of records we need to import, for the progress bar cursor.execute(u'SELECT COUNT(songid) FROM songs') count = int(cursor.fetchone()[0]) @@ -71,9 +104,10 @@ class OpenLP1SongImport(SongImport): # "cache" our list of authors cursor.execute(u'SELECT authorid, authorname FROM authors') authors = cursor.fetchall() - # "cache" our list of tracks - cursor.execute(u'SELECT trackid, fulltrackname FROM tracks') - tracks = cursor.fetchall() + if new_db: + # "cache" our list of tracks + cursor.execute(u'SELECT trackid, fulltrackname FROM tracks') + tracks = cursor.fetchall() # Import the songs cursor.execute(u'SELECT songid, songtitle, lyrics || \'\' AS lyrics, ' u'copyrightinfo FROM songs') @@ -84,9 +118,10 @@ class OpenLP1SongImport(SongImport): success = False break song_id = song[0] - title = unicode(song[1], u'cp1252') - lyrics = unicode(song[2], u'cp1252').replace(u'\r', u'') - copyright = unicode(song[3], u'cp1252') + guess = chardet.detect(song[2]) + title = self.decode_string(song[1], guess) + lyrics = self.decode_string(song[2], guess).replace(u'\r', u'') + copyright = self.decode_string(song[3], guess) self.import_wizard.incrementProgressBar( unicode(translate('SongsPlugin.ImportWizardForm', 'Importing "%s"...')) % title) @@ -102,15 +137,12 @@ class OpenLP1SongImport(SongImport): break for author in authors: if author[0] == author_id[0]: - self.parse_author(unicode(author[1], u'cp1252')) + self.parse_author(self.decode_string(author[1], guess)) break if self.stop_import_flag: success = False break - cursor.execute(u'SELECT name FROM sqlite_master ' - u'WHERE type = \'table\' AND name = \'tracks\'') - table_list = cursor.fetchall() - if len(table_list) > 0: + if new_db: cursor.execute(u'SELECT trackid FROM songtracks ' u'WHERE songid = %s ORDER BY listindex' % song_id) track_ids = cursor.fetchall() @@ -120,10 +152,11 @@ class OpenLP1SongImport(SongImport): break for track in tracks: if track[0] == track_id[0]: - self.add_media_file(unicode(track[1], u'cp1252')) + self.add_media_file(self.decode_string(track[1], guess)) break if self.stop_import_flag: success = False break self.finish() return success + diff --git a/openlp/plugins/songs/lib/oooimport.py b/openlp/plugins/songs/lib/oooimport.py index e8c723c0e..26a0abfcc 100644 --- a/openlp/plugins/songs/lib/oooimport.py +++ b/openlp/plugins/songs/lib/oooimport.py @@ -59,6 +59,7 @@ class OooImport(SongImport): self.document = None self.process_started = False self.filenames = kwargs[u'filenames'] + self.uno_connection_type = u'pipe' #u'socket' QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'song_stop_import'), self.stop_import) @@ -106,8 +107,14 @@ class OooImport(SongImport): loop = 0 while ctx is None and loop < 5: try: - ctx = resolver.resolve(u'uno:socket,host=localhost,' \ - + 'port=2002;urp;StarOffice.ComponentContext') + if self.uno_connection_type == u'pipe': + ctx = resolver.resolve(u'uno:' \ + + u'pipe,name=openlp_pipe;' \ + + u'urp;StarOffice.ComponentContext') + else: + ctx = resolver.resolve(u'uno:' \ + + u'socket,host=localhost,port=2002;' \ + + u'urp;StarOffice.ComponentContext') except: pass self.start_ooo_process() @@ -123,9 +130,14 @@ class OooImport(SongImport): self.manager._FlagAsMethod(u'Bridge_GetStruct') self.manager._FlagAsMethod(u'Bridge_GetValueObject') else: - cmd = u'openoffice.org -nologo -norestore -minimized ' \ - + u'-invisible -nofirststartwizard ' \ - + '-accept="socket,host=localhost,port=2002;urp;"' + if self.uno_connection_type == u'pipe': + cmd = u'openoffice.org -nologo -norestore -minimized ' \ + + u'-invisible -nofirststartwizard ' \ + + u'-accept=pipe,name=openlp_pipe;urp;' + else: + cmd = u'openoffice.org -nologo -norestore -minimized ' \ + + u'-invisible -nofirststartwizard ' \ + + u'-accept=socket,host=localhost,port=2002;urp;' process = QtCore.QProcess() process.startDetached(cmd) process.waitForStarted() diff --git a/openlp/plugins/songs/lib/opensongimport.py b/openlp/plugins/songs/lib/opensongimport.py index f6048d566..f4be0dc87 100644 --- a/openlp/plugins/songs/lib/opensongimport.py +++ b/openlp/plugins/songs/lib/opensongimport.py @@ -29,7 +29,9 @@ import os from zipfile import ZipFile from lxml import objectify from lxml.etree import Error, LxmlError +import re +from openlp.core.lib import translate from openlp.plugins.songs.lib.songimport import SongImport log = logging.getLogger(__name__) @@ -112,12 +114,22 @@ class OpenSongImport(SongImport): def do_import(self): """ - Import either a single opensong file, or a zipfile containing multiple - opensong files. If `self.commit` is set False, the import will not be - committed to the database (useful for test scripts). + Import either each of the files in self.filenames - each element of + which can be either a single opensong file, or a zipfile containing + multiple opensong files. If `self.commit` is set False, the + import will not be committed to the database (useful for test scripts). """ success = True - self.import_wizard.importProgressBar.setMaximum(len(self.filenames)) + numfiles = 0 + for filename in self.filenames: + ext = os.path.splitext(filename)[1] + if ext.lower() == u'.zip': + z = ZipFile(filename, u'r') + numfiles += len(z.infolist()) + else: + numfiles += 1 + log.debug("Total number of files: %d", numfiles) + self.import_wizard.importProgressBar.setMaximum(numfiles) for filename in self.filenames: if self.stop_import_flag: success = False @@ -126,9 +138,6 @@ class OpenSongImport(SongImport): if ext.lower() == u'.zip': log.debug(u'Zipfile found %s', filename) z = ZipFile(filename, u'r') - self.import_wizard.importProgressBar.setMaximum( - self.import_wizard.importProgressBar.maximum() + - len(z.infolist())) for song in z.infolist(): if self.stop_import_flag: success = False @@ -137,6 +146,7 @@ class OpenSongImport(SongImport): if parts[-1] == u'': #No final part => directory continue + log.info(u'Zip importing %s', parts[-1]) self.import_wizard.incrementProgressBar( unicode(translate('SongsPlugin.ImportWizardForm', 'Importing %s...')) % parts[-1]) @@ -144,11 +154,11 @@ class OpenSongImport(SongImport): self.do_import_file(songfile) if self.commit: self.finish() - self.set_defaults() if self.stop_import_flag: success = False break else: + # not a zipfile log.info('Direct import %s', filename) self.import_wizard.incrementProgressBar( unicode(translate('SongsPlugin.ImportWizardForm', @@ -157,9 +167,7 @@ class OpenSongImport(SongImport): self.do_import_file(file) if self.commit: self.finish() - self.set_defaults() - if not self.commit: - self.finish() + return success def do_import_file(self, file): @@ -167,10 +175,10 @@ class OpenSongImport(SongImport): Process the OpenSong file - pass in a file-like object, not a filename """ - self.authors = [] + self.set_defaults() try: tree = objectify.parse(file) - except Error, LxmlError: + except (Error, LxmlError): log.exception(u'Error parsing XML') return root = tree.getroot() @@ -196,7 +204,6 @@ class OpenSongImport(SongImport): self.topics.append(unicode(root.alttheme)) # data storage while importing verses = {} - lyrics = unicode(root.lyrics) # keep track of a "default" verse order, in case none is specified our_verse_order = [] verses_seen = {} @@ -204,6 +211,7 @@ class OpenSongImport(SongImport): # erm, versetype! versetype = u'V' versenum = None + lyrics = unicode(root.lyrics) for thisline in lyrics.split(u'\n'): # remove comments semicolon = thisline.find(u';') @@ -218,16 +226,18 @@ class OpenSongImport(SongImport): continue # verse/chorus/etc. marker if thisline[0] == u'[': - versetype = thisline[1].upper() - if versetype.isdigit(): - versenum = versetype - versetype = u'V' - elif thisline[2] != u']': - # there's a number to go with it - extract that as well - right_bracket = thisline.find(u']') - versenum = thisline[2:right_bracket] + # drop the square brackets + right_bracket = thisline.find(u']') + content = thisline[1:right_bracket].upper() + # have we got any digits? If so, versenumber is everything from the digits + # to the end (even if there are some alpha chars on the end) + match = re.match(u'(.*)(\d+.*)', content) + if match is not None: + versetype = match.group(1) + versenum = match.group(2) else: - # if there's no number, assume it's no.1 + # otherwise we assume number 1 and take the whole prefix as versetype + versetype = content versenum = u'1' continue words = None @@ -235,10 +245,10 @@ class OpenSongImport(SongImport): if thisline[0].isdigit(): versenum = thisline[0] words = thisline[1:].strip() - if words is None and \ - versenum is not None and \ - versetype is not None: + if words is None: words = thisline + if not versenum: + versenum = u'1' if versenum is not None: versetag = u'%s%s' % (versetype, versenum) if not verses.has_key(versetype): @@ -259,10 +269,13 @@ class OpenSongImport(SongImport): versetypes.sort() versetags = {} for versetype in versetypes: + our_verse_type = versetype + if our_verse_type == u'': + our_verse_type = u'V' versenums = verses[versetype].keys() versenums.sort() for num in versenums: - versetag = u'%s%s' % (versetype, num) + versetag = u'%s%s' % (our_verse_type, num) lines = u'\n'.join(verses[versetype][num]) self.verses.append([versetag, lines]) # Keep track of what we have for error checking later @@ -271,16 +284,23 @@ class OpenSongImport(SongImport): order = [] if u'presentation' in fields and root.presentation != u'': order = unicode(root.presentation) - order = order.split() + # We make all the tags in the lyrics upper case, so match that here + # and then split into a list on the whitespace + order = order.upper().split() else: if len(our_verse_order) > 0: order = our_verse_order else: - log.warn(u'No verse order available for %s, skipping.', self.title) + log.warn(u'No verse order available for %s, skipping.', + self.title) for tag in order: - if len(tag) == 1: - tag = tag + u'1' # Assume it's no.1 if it's not there + if tag[0].isdigit(): + # Assume it's a verse if it has no prefix + tag = u'V' + tag + elif not re.search('\d+', tag): + # Assume it's no.1 if there's no digits + tag = tag + u'1' if not versetags.has_key(tag): - log.warn(u'Got order %s but not in versetags, skipping', tag) + log.info(u'Got order %s but not in versetags, dropping this item from presentation order', tag) else: self.verse_order_list.append(tag) diff --git a/openlp/plugins/songs/lib/songimport.py b/openlp/plugins/songs/lib/songimport.py index 8d29a73a2..dcf4ed8d8 100644 --- a/openlp/plugins/songs/lib/songimport.py +++ b/openlp/plugins/songs/lib/songimport.py @@ -55,8 +55,12 @@ class SongImport(QtCore.QObject): self.set_defaults() QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'songs_stop_import'), self.stop_import) - def set_defaults(self): + """ + Create defaults for properties - call this before each song + if importing many songs at once to ensure a clean beginning + """ + self.authors = [] self.title = u'' self.song_number = u'' self.alternate_title = u'' @@ -71,13 +75,7 @@ class SongImport(QtCore.QObject): self.song_book_pub = u'' self.verse_order_list = [] self.verses = [] - self.versecount = 0 - self.choruscount = 0 - self.bridgecount = 0 - self.introcount = 0 - self.prechoruscount = 0 - self.endingcount = 0 - self.othercount = 0 + self.versecounts = {} self.copyright_string = unicode(translate( 'SongsPlugin.SongImport', 'copyright')) self.copyright_symbol = unicode(translate( @@ -198,7 +196,7 @@ class SongImport(QtCore.QObject): return self.media_files.append(filename) - def add_verse(self, verse, versetag=None): + def add_verse(self, verse, versetag=u'V'): """ Add a verse. This is the whole verse, lines split by \n Verse tag can be V1/C1/B etc, or 'V' and 'C' (will count the verses/ @@ -210,33 +208,14 @@ class SongImport(QtCore.QObject): if oldverse.strip() == verse.strip(): self.verse_order_list.append(oldversetag) return - if versetag == u'V' or not versetag: - self.versecount += 1 - versetag = u'V' + unicode(self.versecount) - if versetag.startswith(u'C'): - self.choruscount += 1 - if versetag == u'C': - versetag += unicode(self.choruscount) - if versetag.startswith(u'B'): - self.bridgecount += 1 - if versetag == u'B': - versetag += unicode(self.bridgecount) - if versetag.startswith(u'I'): - self.introcount += 1 - if versetag == u'I': - versetag += unicode(self.introcount) - if versetag.startswith(u'P'): - self.prechoruscount += 1 - if versetag == u'P': - versetag += unicode(self.prechoruscount) - if versetag.startswith(u'E'): - self.endingcount += 1 - if versetag == u'E': - versetag += unicode(self.endingcount) - if versetag.startswith(u'O'): - self.othercount += 1 - if versetag == u'O': - versetag += unicode(self.othercount) + if versetag[0] in self.versecounts: + self.versecounts[versetag[0]] += 1 + else: + self.versecounts[versetag[0]] = 1 + if len(versetag) == 1: + versetag += unicode(self.versecounts[versetag[0]]) + elif int(versetag[1:]) > self.versecounts[versetag[0]]: + self.versecounts[versetag[0]] = int(versetag[1:]) self.verses.append([versetag, verse.rstrip()]) self.verse_order_list.append(versetag) if versetag.startswith(u'V') and self.contains_verse(u'C1'): @@ -280,13 +259,16 @@ class SongImport(QtCore.QObject): """ Write the song and its fields to disk """ + log.info(u'commiting song %s to database', self.title) song = Song() song.title = self.title song.search_title = self.remove_punctuation(self.title) \ + '@' + self.alternate_title song.song_number = self.song_number song.search_lyrics = u'' + verses_changed_to_other = {} sxml = SongXMLBuilder() + other_count = 1 for (versetag, versetext) in self.verses: if versetag[0] == u'C': versetype = VerseType.to_string(VerseType.Chorus) @@ -301,10 +283,18 @@ class SongImport(QtCore.QObject): elif versetag[0] == u'E': versetype = VerseType.to_string(VerseType.Ending) else: + newversetag = u'O%d' % other_count + verses_changed_to_other[versetag] = newversetag + other_count += 1 versetype = VerseType.to_string(VerseType.Other) + log.info(u'Versetype %s changing to %s' , versetag, newversetag) + versetag = newversetag sxml.add_verse_to_lyrics(versetype, versetag[1:], versetext) song.search_lyrics += u' ' + self.remove_punctuation(versetext) song.lyrics = unicode(sxml.extract_xml(), u'utf-8') + for i, current_verse_tag in enumerate(self.verse_order_list): + if verses_changed_to_other.has_key(current_verse_tag): + self.verse_order_list[i] = verses_changed_to_other[current_verse_tag] song.verse_order = u' '.join(self.verse_order_list) song.copyright = self.copyright song.comments = self.comments diff --git a/openlp/plugins/songs/lib/test/test.opensong b/openlp/plugins/songs/lib/test/test.opensong index 1ce60cf3b..c75951492 100644 --- a/openlp/plugins/songs/lib/test/test.opensong +++ b/openlp/plugins/songs/lib/test/test.opensong @@ -1,10 +1,10 @@ Martins Test - MartiÑ Thómpson + MartiÑ & Martin2 Thómpson 2010 Martin Thompson 1 - V1 C V2 C2 V3 B1 V1 + V1 C V2 C2 3a B1 V1 T U Rap1 Rap2 Rap3 Blah @@ -17,7 +17,12 @@ TestAltTheme - ;Comment + [3a] +. G A B + V3 Line 1 +. G A B + V3 Line 2 + . A B C 1 v1 Line 1___ 2 v2 Line 1___ @@ -25,10 +30,6 @@ 1 V1 Line 2 2 V2 Line 2 -[3] - V3 Line 1 - V3 Line 2 - [b1] Bridge 1 --- @@ -36,12 +37,29 @@ Bridge 1 line 2 [C] -. A B + . A B Chorus 1 [C2] . A B Chorus 2 + +[T] + T Line 1 + +[Rap] +1 Rap 1 Line 1 +2 Rap 2 Line 1 +1 Rap 1 Line 2 +2 Rap 2 Line 2 + +[rap3] + Rap 3 Line 1 + Rap 3 Line 2 + + +[X] + Unreferenced verse line 1