diff --git a/MANIFEST.in b/MANIFEST.in index e8a5822e6..efccdf79d 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,6 +3,7 @@ recursive-include openlp *.sqlite recursive-include openlp *.csv recursive-include openlp *.html recursive-include openlp *.js +recursive-include openlp *.qm recursive-include documentation * recursive-include resources/forms * recursive-include resources/i18n * diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index 1d753264e..52ebe99c8 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -81,6 +81,9 @@ 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}) +# Image image_cache to stop regualar image resizing +image_cache = {} + def translate(context, text, comment=None): """ A special shortcut method to wrap around the Qt4 translation functions. @@ -220,6 +223,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) @@ -249,6 +253,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 @@ -256,6 +261,9 @@ def resize_image(image, width, height, background=QtCore.Qt.black): return preview preview = preview.scaled(width, height, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) + image_cache_key = u'%s%s%s' % (image, unicode(width), unicode(height)) + if image_cache_key in image_cache: + return image_cache[image_cache_key] realw = preview.width() realh = preview.height() # and move it to the centre of the preview space @@ -264,6 +272,7 @@ def resize_image(image, width, height, background=QtCore.Qt.black): new_image.fill(background) painter = QtGui.QPainter(new_image) painter.drawImage((width - realw) / 2, (height - realh) / 2, preview) + image_cache[image_cache_key] = new_image return new_image def check_item_selected(list_widget, message): diff --git a/openlp/core/lib/htmlbuilder.py b/openlp/core/lib/htmlbuilder.py index 906ebb987..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 { @@ -82,54 +83,12 @@ body { - - + + + %s
@@ -301,16 +324,16 @@ 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), build_footer_css(item, height), build_lyrics_css(item, webkitvers), - u'true' if theme and theme.display_slideTransition and islive \ + u'true' if theme and theme.display_slide_transition and islive \ else u'false', image, build_lyrics_html(item, webkitvers)) @@ -347,18 +370,18 @@ def build_background_css(item, width, height): background = \ u'background: ' \ u'-webkit-gradient(linear, left top, left bottom, ' \ - 'from(%s), to(%s))' % (theme.background_startColor, - theme.background_endColor) + 'from(%s), to(%s))' % (theme.background_start_color, + theme.background_end_color) elif theme.background_direction == u'vertical': background = \ u'background: -webkit-gradient(linear, left top, ' \ u'right top, from(%s), to(%s))' % \ - (theme.background_startColor, theme.background_endColor) + (theme.background_start_color, theme.background_end_color) else: background = \ u'background: -webkit-gradient(radial, %s 50%%, 100, %s ' \ u'50%%, %s, from(%s), to(%s))' % (width, width, width, - theme.background_startColor, theme.background_endColor) + theme.background_start_color, theme.background_end_color) return background def build_lyrics_css(item, webkitvers): @@ -426,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;' % \ @@ -475,25 +500,29 @@ def build_lyrics_format_css(theme, width, height): Height of the lyrics block """ - if theme.display_horizontalAlign == 2: + if theme.display_horizontal_align == 2: align = u'center' - elif theme.display_horizontalAlign == 1: + elif theme.display_horizontal_align == 1: align = u'right' else: align = u'left' - if theme.display_verticalAlign == 2: + if theme.display_vertical_align == 2: valign = u'bottom' - elif theme.display_verticalAlign == 1: + elif theme.display_vertical_align == 1: 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;' @@ -547,7 +576,7 @@ def build_footer_css(item, height): font-size: %spt; color: %s; text-align: left; - white-space:nowrap; + white-space:nowrap; """ theme = item.themedata if not theme or not item.footer: 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..ec6a7f8cb 100644 --- a/openlp/core/lib/rendermanager.py +++ b/openlp/core/lib/rendermanager.py @@ -192,13 +192,15 @@ class RenderManager(object): log.debug(u'generate preview') # set the default image size for previews self.calculate_default(self.screens.preview[u'size']) - verse = u'Amazing Grace!\n'\ - 'How sweet the sound\n'\ - 'To save a wretch like me;\n'\ - 'I once was lost but now am found,\n'\ - 'Was blind, but now I see.' + verse = u'The Lord said to {r}Noah{/r}: \n' \ + 'There\'s gonna be a {su}floody{/su}, {sb}floody{/sb}\n' \ + 'The Lord said to {g}Noah{/g}:\n' \ + 'There\'s gonna be a {st}floody{/st}, {it}floody{/it}\n' \ + 'Get those children out of the muddy, muddy \n' \ + '{r}C{/r}{b}h{/b}{bl}i{/bl}{y}l{/y}{g}d{/g}{pk}' \ + 'r{/pk}{o}e{/o}{pp}n{/pp} of the Lord\n' footer = [] - footer.append(u'Amazing Grace (John Newton)' ) + footer.append(u'Arky Arky (Unknown)' ) footer.append(u'Public Domain') footer.append(u'CCLI 123456') # build a service item to generate preview @@ -223,7 +225,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/lib/theme.py b/openlp/core/lib/theme.py index ee3418dca..def6f01cc 100644 --- a/openlp/core/lib/theme.py +++ b/openlp/core/lib/theme.py @@ -27,6 +27,7 @@ Provide the theme XML and handling functions for OpenLP v2 themes. """ import os +import re from xml.dom.minidom import Document from xml.etree.ElementTree import ElementTree, XML @@ -86,6 +87,13 @@ class ThemeLevel(object): Service = 2 Song = 3 +boolean_list = [u'italics', u'override', u'outline', u'shadow', \ +u'slide_transition'] + +integer_list =[u'proportion', u'line_adjustment', u'x', u'height', u'y', \ +u'width', u'shadow_size', u'outline_size', u'horizontal_align', \ +u'vertical_align', u'wrap_style' ] + class ThemeXML(object): """ A class to encapsulate the Theme XML. @@ -326,16 +334,14 @@ class ThemeXML(object): def dump_xml(self): """ - Dump the XML to file. + Dump the XML to file used for debugging """ - # Debugging aid to see what we have return self.theme_xml.toprettyxml(indent=u' ') def extract_xml(self): """ - Pull out the XML string. + Print out the XML string. """ - # Print our newly created XML return self.theme_xml.toxml(u'utf-8').decode(u'utf-8') def extract_formatted_xml(self): @@ -372,33 +378,37 @@ class ThemeXML(object): if element.getchildren(): master = element.tag + u'_' else: - #background transparent tags have no children so special case + # background transparent tags have no children so special case if element.tag == u'background': for e in element.attrib.iteritems(): - setattr(self, element.tag + u'_' + e[0], e[1]) + self._create_attr(element.tag , e[0], e[1]) if element.attrib: for e in element.attrib.iteritems(): if master == u'font_' and e[0] == u'type': master += e[1] + u'_' elif master == u'display_' and (element.tag == u'shadow' \ or element.tag == u'outline' ): - et = str_to_bool(element.text) - setattr(self, master + element.tag, et) - setattr(self, master + element.tag + u'_'+ e[0], e[1]) + self._create_attr(master, element.tag, element.text) + self._create_attr(master, element.tag + u'_'+ e[0], e[1]) else: field = master + e[0] - if e[1] == u'True' or e[1] == u'False': - setattr(self, field, str_to_bool(e[1])) - else: - setattr(self, field, e[1]) + self._create_attr(master, e[0], e[1]) else: if element.tag: - field = master + element.tag element.text = element.text.strip().lstrip() - if element.text == u'True' or element.text == u'False': - setattr(self, field, str_to_bool(element.text)) - else: - setattr(self, field, element.text) + self._create_attr(master , element.tag, element.text) + + def _create_attr(self, master , element, value): + """ + Create the attributes with the correct data types and name format + """ + field = self._de_hump(element) + if field in boolean_list: + setattr(self, master + field, str_to_bool(value)) + elif field in integer_list: + setattr(self, master + field, int(value)) + else: + setattr(self, master + field, unicode(value)) def __str__(self): """ @@ -409,3 +419,11 @@ class ThemeXML(object): if key[0:1] != u'_': theme_strings.append(u'%30s: %s' % (key, getattr(self, key))) return u'\n'.join(theme_strings) + + def _de_hump(self, name): + """ + Change Camel Case string to python string + """ + s1 = re.sub(u'(.)([A-Z][a-z]+)', r'\1_\2', name) + return re.sub(u'([a-z0-9])([A-Z])', r'\1_\2', s1).lower() + diff --git a/openlp/core/ui/__init__.py b/openlp/core/ui/__init__.py index 33ba25046..80d677386 100644 --- a/openlp/core/ui/__init__.py +++ b/openlp/core/ui/__init__.py @@ -37,6 +37,7 @@ class HideMode(object): Theme = 2 Screen = 3 +from filerenameform import FileRenameForm from maindisplay import MainDisplay from slidecontroller import HideMode from servicenoteform import ServiceNoteForm diff --git a/openlp/core/ui/amendthemeform.py b/openlp/core/ui/amendthemeform.py index e23044a2b..1e0404f10 100644 --- a/openlp/core/ui/amendthemeform.py +++ b/openlp/core/ui/amendthemeform.py @@ -150,8 +150,8 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog): unicode(self.theme.background_color)) elif self.theme.background_type == u'gradient': new_theme.add_background_gradient( - unicode(self.theme.background_startColor), - unicode(self.theme.background_endColor), + unicode(self.theme.background_start_color), + unicode(self.theme.background_end_color), self.theme.background_direction) else: filename = \ @@ -185,10 +185,10 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog): unicode(self.theme.display_shadow_color), unicode(self.theme.display_outline), unicode(self.theme.display_outline_color), - unicode(self.theme.display_horizontalAlign), - unicode(self.theme.display_verticalAlign), - unicode(self.theme.display_wrapStyle), - unicode(self.theme.display_slideTransition), + unicode(self.theme.display_horizontal_align), + unicode(self.theme.display_vertical_align), + unicode(self.theme.display_wrap_style), + unicode(self.theme.display_slide_transition), unicode(self.theme.display_shadow_size), unicode(self.theme.display_outline_size)) theme = new_theme.extract_xml() @@ -217,7 +217,6 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog): self.imageLineEdit.setText(filename) self.theme.background_filename = filename self.previewTheme() - # # Main Font Tab # @@ -301,7 +300,6 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog): if self.theme.font_main_height != self.fontMainHeightSpinBox.value(): self.theme.font_main_height = self.fontMainHeightSpinBox.value() self.previewTheme() - # # Footer Font Tab # @@ -382,7 +380,6 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog): self.theme.font_footer_height = \ self.fontFooterHeightSpinBox.value() self.previewTheme() - # # Background Tab # @@ -407,10 +404,10 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog): self.theme.background_direction = u'vertical' else: self.theme.background_direction = u'circular' - if self.theme.background_startColor is None: - self.theme.background_startColor = u'#000000' - if self.theme.background_endColor is None: - self.theme.background_endColor = u'#ff0000' + if self.theme.background_start_color is None: + self.theme.background_start_color = u'#000000' + if self.theme.background_end_color is None: + self.theme.background_end_color = u'#ff0000' self.imageLineEdit.setText(u'') else: self.theme.background_type = u'image' @@ -427,22 +424,21 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog): unicode(self.theme.background_color)) else: new_color = QtGui.QColorDialog.getColor( - QtGui.QColor(self.theme.background_startColor), self) + QtGui.QColor(self.theme.background_start_color), self) if new_color.isValid(): - self.theme.background_startColor = new_color.name() + self.theme.background_start_color = new_color.name() self.color1PushButton.setStyleSheet(u'background-color: %s' % - unicode(self.theme.background_startColor)) + unicode(self.theme.background_start_color)) self.previewTheme() def onColor2PushButtonClicked(self): new_color = QtGui.QColorDialog.getColor( - QtGui.QColor(self.theme.background_endColor), self) + QtGui.QColor(self.theme.background_end_color), self) if new_color.isValid(): - self.theme.background_endColor = new_color.name() + self.theme.background_end_color = new_color.name() self.color2PushButton.setStyleSheet(u'background-color: %s' % - unicode(self.theme.background_endColor)) + unicode(self.theme.background_end_color)) self.previewTheme() - # # Other Tab # @@ -483,9 +479,9 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog): def onSlideTransitionCheckBoxChanged(self, value): if value == 2: # checked - self.theme.display_slideTransition = True + self.theme.display_slide_transition = True else: - self.theme.display_slideTransition = False + self.theme.display_slide_transition = False self.stateChanging(self.theme) self.previewTheme() @@ -499,15 +495,14 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog): self.previewTheme() def onHorizontalComboBoxSelected(self, currentIndex): - self.theme.display_horizontalAlign = currentIndex + self.theme.display_horizontal_align = currentIndex self.stateChanging(self.theme) self.previewTheme() def onVerticalComboBoxSelected(self, currentIndex): - self.theme.display_verticalAlign = currentIndex + self.theme.display_vertical_align = currentIndex self.stateChanging(self.theme) self.previewTheme() - # # Local Methods # @@ -598,13 +593,13 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog): self.shadowCheckBox.setChecked(False) self.shadowColorPushButton.setEnabled(False) self.shadowSpinBox.setValue(int(self.theme.display_shadow_size)) - if self.theme.display_slideTransition: + if self.theme.display_slide_transition: self.slideTransitionCheckBox.setCheckState(QtCore.Qt.Checked) else: self.slideTransitionCheckBox.setCheckState(QtCore.Qt.Unchecked) self.horizontalComboBox.setCurrentIndex( - self.theme.display_horizontalAlign) - self.verticalComboBox.setCurrentIndex(self.theme.display_verticalAlign) + self.theme.display_horizontal_align) + self.verticalComboBox.setCurrentIndex(self.theme.display_vertical_align) def stateChanging(self, theme): self.backgroundTypeComboBox.setVisible(True) @@ -625,9 +620,9 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog): self.gradientComboBox.setVisible(False) elif theme.background_type == u'gradient': self.color1PushButton.setStyleSheet(u'background-color: %s' \ - % unicode(theme.background_startColor)) + % unicode(theme.background_start_color)) self.color2PushButton.setStyleSheet(u'background-color: %s' \ - % unicode(theme.background_endColor)) + % unicode(theme.background_end_color)) self.color1Label.setText( translate('OpenLP.AmendThemeForm', 'First color:')) self.color2Label.setText( diff --git a/openlp/core/ui/exceptionform.py b/openlp/core/ui/exceptionform.py index d181ad0d1..8c344e662 100644 --- a/openlp/core/ui/exceptionform.py +++ b/openlp/core/ui/exceptionform.py @@ -27,7 +27,6 @@ from PyQt4 import QtCore, QtGui from exceptiondialog import Ui_ExceptionDialog -from openlp.core.lib import translate class ExceptionForm(QtGui.QDialog, Ui_ExceptionDialog): """ diff --git a/openlp/core/ui/filerenamedialog.py b/openlp/core/ui/filerenamedialog.py new file mode 100644 index 000000000..897acf02f --- /dev/null +++ b/openlp/core/ui/filerenamedialog.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Meinert Jordan, Andreas Preikschat, Christian # +# Richter, Philip Ridout, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard, Frode Woldsund # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### + +from PyQt4 import QtCore, QtGui + +from openlp.core.lib import translate + +class Ui_FileRenameDialog(object): + def setupUi(self, FileRenameDialog): + FileRenameDialog.setObjectName("FileRenameDialog") + FileRenameDialog.resize(400, 87) + self.buttonBox = QtGui.QDialogButtonBox(FileRenameDialog) + self.buttonBox.setGeometry(QtCore.QRect(210, 50, 171, 25)) + self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.widget = QtGui.QWidget(FileRenameDialog) + self.widget.setGeometry(QtCore.QRect(10, 10, 381, 35)) + self.widget.setObjectName("widget") + self.horizontalLayout = QtGui.QHBoxLayout(self.widget) + self.horizontalLayout.setObjectName("horizontalLayout") + self.FileRenameLabel = QtGui.QLabel(self.widget) + self.FileRenameLabel.setObjectName("FileRenameLabel") + self.horizontalLayout.addWidget(self.FileRenameLabel) + self.FileNameEdit = QtGui.QLineEdit(self.widget) + self.FileNameEdit.setObjectName("FileNameEdit") + self.horizontalLayout.addWidget(self.FileNameEdit) + + self.retranslateUi(FileRenameDialog) + QtCore.QMetaObject.connectSlotsByName(FileRenameDialog) + + def retranslateUi(self, FileRenameDialog): + FileRenameDialog.setWindowTitle(translate('OpenLP.FileRenameForm', + 'File Rename')) + self.FileRenameLabel.setText(translate('OpenLP.FileRenameForm', + 'New File Name:')) + diff --git a/openlp/core/ui/filerenameform.py b/openlp/core/ui/filerenameform.py new file mode 100644 index 000000000..d1c339811 --- /dev/null +++ b/openlp/core/ui/filerenameform.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Meinert Jordan, Andreas Preikschat, Christian # +# Richter, Philip Ridout, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard, Frode Woldsund # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### + +from PyQt4 import QtCore, QtGui + +from filerenamedialog import Ui_FileRenameDialog +from openlp.core.lib import translate + +class FileRenameForm(QtGui.QDialog, Ui_FileRenameDialog): + """ + The exception dialog + """ + def __init__(self, parent): + QtGui.QDialog.__init__(self, parent) + self.setupUi(self) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(u'accepted()'), + self.accept) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(u'rejected()'), + self.reject) diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index a2b9dd81a..ad26bb2d4 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 @@ -176,6 +183,9 @@ class MainDisplay(DisplayWidget): The slide text to be displayed """ log.debug(u'text') + # Wait for the webview to update before displayiong text. + while not self.loaded: + Receiver.send_message(u'openlp_process_events') self.frame.evaluateJavaScript(u'show_text("%s")' % \ slide.replace(u'\\', u'\\\\').replace(u'\"', u'\\\"')) return self.preview() @@ -227,8 +237,11 @@ class MainDisplay(DisplayWidget): Display an image, as is. """ if image: - js = u'show_image("data:image/png;base64,%s");' % \ - image_to_byte(image) + if isinstance(image, QtGui.QImage): + js = u'show_image("data:image/png;base64,%s");' % \ + image_to_byte(image) + else: + js = u'show_image("data:image/png;base64,%s");' % image else: js = u'show_image("");' self.frame.evaluateJavaScript(js) @@ -239,7 +252,7 @@ class MainDisplay(DisplayWidget): Used after Image plugin has changed the background """ log.debug(u'resetImage') - self.displayImage(self.serviceItem.bg_frame) + self.displayImage(self.serviceItem.bg_image_bytes) def resetVideo(self): """ @@ -286,7 +299,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() @@ -306,11 +319,12 @@ class MainDisplay(DisplayWidget): # 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 ! if self.serviceItem.themedata and \ - self.serviceItem.themedata.display_slideTransition: + self.serviceItem.themedata.display_slide_transition: while self.frame.evaluateJavaScript(u'show_text_complete()') \ .toString() == u'false': Receiver.send_message(u'openlp_process_events') @@ -318,6 +332,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) @@ -325,9 +345,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): @@ -341,7 +358,9 @@ class MainDisplay(DisplayWidget): self.serviceItem = serviceItem 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 730bc9ca1..81487e4f8 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -337,7 +337,6 @@ class Ui_MainWindow(object): Set up the translation system """ MainWindow.mainTitle = translate('OpenLP.MainWindow', 'OpenLP 2.0') - MainWindow.language = translate('OpenLP.MainWindow', 'AddHereYourLanguageName') MainWindow.setWindowTitle(MainWindow.mainTitle) self.FileMenu.setTitle(translate('OpenLP.MainWindow', '&File')) self.FileImportMenu.setTitle(translate('OpenLP.MainWindow', '&Import')) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 8db14956d..319725790 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -236,7 +236,7 @@ class ServiceManager(QtGui.QWidget): self.addToAction = self.dndMenu.addAction( translate('OpenLP.ServiceManager', '&Add to Selected Item')) self.addToAction.setIcon(build_icon(u':/general/general_edit.png')) - #build the context menu + # build the context menu self.menu = QtGui.QMenu() self.editAction = self.menu.addAction( translate('OpenLP.ServiceManager', '&Edit Item')) @@ -383,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) @@ -406,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) @@ -557,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)) diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 9170bfb2b..31bf0e321 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -642,9 +642,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) @@ -657,9 +657,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) @@ -774,9 +774,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 d8324f977..ff61b05e8 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -32,7 +32,7 @@ import logging from xml.etree.ElementTree import ElementTree, XML from PyQt4 import QtCore, QtGui -from openlp.core.ui import AmendThemeForm +from openlp.core.ui import AmendThemeForm, FileRenameForm from openlp.core.theme import Theme from openlp.core.lib import OpenLPToolbar, context_menu_action, \ ThemeXML, str_to_bool, get_text_file_string, build_icon, Receiver, \ @@ -54,6 +54,7 @@ class ThemeManager(QtGui.QWidget): self.layout.setSpacing(0) self.layout.setMargin(0) self.amendThemeForm = AmendThemeForm(self) + self.fileRenameForm = FileRenameForm(self) self.toolbar = OpenLPToolbar(self) self.toolbar.addToolbarButton( translate('OpenLP.ThemeManager', 'New Theme'), @@ -87,31 +88,32 @@ class ThemeManager(QtGui.QWidget): self.themeListWidget.setAlternatingRowColors(True) self.themeListWidget.setIconSize(QtCore.QSize(88, 50)) self.layout.addWidget(self.themeListWidget) - self.themeListWidget.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) - self.themeListWidget.addAction( - context_menu_action(self.themeListWidget, - u':/themes/theme_edit.png', - translate('OpenLP.ThemeManager', '&Edit Theme'), - self.onEditTheme)) - self.themeListWidget.addAction( - context_menu_separator(self.themeListWidget)) - self.themeListWidget.addAction( - context_menu_action(self.themeListWidget, - u':/general/general_delete.png', - translate('OpenLP.ThemeManager', '&Delete Theme'), - self.onDeleteTheme)) - self.themeListWidget.addAction( - context_menu_action(self.themeListWidget, - u':/general/general_export.png', - translate('OpenLP.ThemeManager', 'Set As &Global Default'), - self.changeGlobalFromScreen)) - self.themeListWidget.addAction( - context_menu_action(self.themeListWidget, - u':/general/general_export.png', - translate('OpenLP.ThemeManager', 'E&xport Theme'), - self.onExportTheme)) - self.themeListWidget.addAction( - context_menu_separator(self.themeListWidget)) + self.themeListWidget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + QtCore.QObject.connect(self.themeListWidget, + QtCore.SIGNAL('customContextMenuRequested(QPoint)'), + self.contextMenu) + # build the context menu + self.menu = QtGui.QMenu() + self.editAction = self.menu.addAction( + translate('OpenLP.ThemeManager', '&Edit Theme')) + self.editAction.setIcon(build_icon(u':/themes/theme_edit.png')) + self.copyAction = self.menu.addAction( + translate('OpenLP.ThemeManager', '&Copy Theme')) + self.copyAction.setIcon(build_icon(u':/themes/theme_edit.png')) + self.renameAction = self.menu.addAction( + translate('OpenLP.ThemeManager', '&Rename Theme')) + self.renameAction.setIcon(build_icon(u':/themes/theme_edit.png')) + self.deleteAction = self.menu.addAction( + translate('OpenLP.ThemeManager', '&Delete Theme')) + self.deleteAction.setIcon(build_icon(u':/general/general_delete.png')) + self.sep1 = self.menu.addAction(u'') + self.sep1.setSeparator(True) + self.globalAction = self.menu.addAction( + translate('OpenLP.ThemeManager', 'Set As &Global Default')) + self.globalAction.setIcon(build_icon(u':/general/general_export.png')) + self.exportAction = self.menu.addAction( + translate('OpenLP.ThemeManager', '&Export Theme')) + self.exportAction.setIcon(build_icon(u':/general/general_export.png')) #Signals QtCore.QObject.connect(self.themeListWidget, QtCore.SIGNAL(u'doubleClicked(QModelIndex)'), @@ -132,6 +134,34 @@ class ThemeManager(QtGui.QWidget): self.settingsSection + u'/global theme', QtCore.QVariant(u'')).toString()) + def contextMenu(self, point): + item = self.themeListWidget.itemAt(point) + if item is None: + return + realThemeName = unicode(item.data(QtCore.Qt.UserRole).toString()) + themeName = unicode(item.text()) + self.deleteAction.setVisible(False) + self.renameAction.setVisible(False) + self.globalAction.setVisible(False) + # If default theme restrict actions + if realThemeName == themeName: + self.deleteAction.setVisible(True) + self.renameAction.setVisible(True) + self.globalAction.setVisible(True) + action = self.menu.exec_(self.themeListWidget.mapToGlobal(point)) + if action == self.editAction: + self.onEditTheme() + if action == self.copyAction: + self.onCopyTheme() + if action == self.renameAction: + self.onRenameTheme() + if action == self.deleteAction: + self.onDeleteTheme() + if action == self.globalAction: + self.changeGlobalFromScreen() + if action == self.exportAction: + self.onExportTheme() + def changeGlobalFromTab(self, themeName): """ Change the global theme when it is changed through the Themes settings @@ -189,6 +219,92 @@ class ThemeManager(QtGui.QWidget): self.saveThemeName = u'' self.amendThemeForm.exec_() + def onRenameTheme(self): + """ + Renames an existing theme to a new name + """ + item = self.themeListWidget.currentItem() + oldThemeName = unicode(item.data(QtCore.Qt.UserRole).toString()) + self.fileRenameForm.FileNameEdit.setText(oldThemeName) + self.saveThemeName = u'' + if self.fileRenameForm.exec_(): + newThemeName = unicode(self.fileRenameForm.FileNameEdit.text()) + oldThemeData = self.getThemeData(oldThemeName) + self.deleteTheme(oldThemeName) + self.cloneThemeData(oldThemeData, newThemeName) + + def onCopyTheme(self): + """ + Copies an existing theme to a new name + """ + item = self.themeListWidget.currentItem() + oldThemeName = unicode(item.data(QtCore.Qt.UserRole).toString()) + self.fileRenameForm.FileNameEdit.setText(oldThemeName) + self.saveThemeName = u'' + if self.fileRenameForm.exec_(): + newThemeName = unicode(self.fileRenameForm.FileNameEdit.text()) + oldThemeData = self.getThemeData(oldThemeName) + self.cloneThemeData(oldThemeData, newThemeName) + self.loadThemes() + + def cloneThemeData(self, oldThemeData, newThemeName): + """ + """ + log.debug(u'cloneThemeData') + new_theme = ThemeXML() + new_theme.new_document(newThemeName) + save_from = None + save_to = None + if oldThemeData.background_type == u'solid': + new_theme.add_background_solid( + unicode(oldThemeData.background_color)) + elif oldThemeData.background_type == u'gradient': + new_theme.add_background_gradient( + unicode(oldThemeData.background_start_color), + unicode(oldThemeData.background_end_color), + oldThemeData.background_direction) + else: + filename = \ + os.path.split(unicode(oldThemeData.background_filename))[1] + new_theme.add_background_image(filename) + save_to = os.path.join(self.path, theme_name, filename) + save_from = oldThemeData.background_filename + new_theme.add_font(unicode(oldThemeData.font_main_name), + unicode(oldThemeData.font_main_color), + unicode(oldThemeData.font_main_proportion), + unicode(oldThemeData.font_main_override), u'main', + unicode(oldThemeData.font_main_weight), + unicode(oldThemeData.font_main_italics), + unicode(oldThemeData.font_main_line_adjustment), + unicode(oldThemeData.font_main_x), + unicode(oldThemeData.font_main_y), + unicode(oldThemeData.font_main_width), + unicode(oldThemeData.font_main_height)) + new_theme.add_font(unicode(oldThemeData.font_footer_name), + unicode(oldThemeData.font_footer_color), + unicode(oldThemeData.font_footer_proportion), + unicode(oldThemeData.font_footer_override), u'footer', + unicode(oldThemeData.font_footer_weight), + unicode(oldThemeData.font_footer_italics), + 0, # line adjustment + unicode(oldThemeData.font_footer_x), + unicode(oldThemeData.font_footer_y), + unicode(oldThemeData.font_footer_width), + unicode(oldThemeData.font_footer_height)) + new_theme.add_display(unicode(oldThemeData.display_shadow), + unicode(oldThemeData.display_shadow_color), + unicode(oldThemeData.display_outline), + unicode(oldThemeData.display_outline_color), + unicode(oldThemeData.display_horizontal_align), + unicode(oldThemeData.display_vertical_align), + unicode(oldThemeData.display_wrap_style), + unicode(oldThemeData.display_slide_transition), + unicode(oldThemeData.display_shadow_size), + unicode(oldThemeData.display_outline_size)) + theme = new_theme.extract_xml() + pretty_theme = new_theme.extract_formatted_xml() + self.saveTheme(newThemeName, theme, pretty_theme, save_from, save_to) + def onEditTheme(self): """ Loads the settings for the theme that is to be edited and launches the @@ -462,11 +578,7 @@ class ThemeManager(QtGui.QWidget): log.exception(u'Theme XML is not UTF-8 ' u'encoded.') break - if self.checkVersion1(xml_data): - # upgrade theme xml - filexml = self.migrateVersion122(xml_data) - else: - filexml = xml_data + filexml = self.checkVersionAndConvert(xml_data) outfile = open(fullpath, u'w') outfile.write(filexml.encode(u'utf-8')) else: @@ -492,20 +604,21 @@ class ThemeManager(QtGui.QWidget): if outfile: outfile.close() - def checkVersion1(self, xmlfile): + def checkVersionAndConvert(self, xml_data): """ Check if a theme is from OpenLP version 1 - ``xmlfile`` + ``xml_data`` Theme XML to check the version of """ log.debug(u'checkVersion1 ') - theme = xmlfile.encode(u'ascii', u'xmlcharrefreplace') + theme = xml_data.encode(u'ascii', u'xmlcharrefreplace') tree = ElementTree(element=XML(theme)).getroot() + # look for old version 1 tags if tree.find(u'BackgroundType') is None: - return False + return xml_data else: - return True + return self.migrateVersion122(xml_data) def migrateVersion122(self, xml_data): """ @@ -699,61 +812,5 @@ class ThemeManager(QtGui.QWidget): """ theme = ThemeXML() theme.parse(theme_xml) - self.cleanTheme(theme) theme.extend_image_filename(path) return theme - - def cleanTheme(self, theme): - """ - Clean a theme loaded from an XML file by removing stray whitespace and - making sure parameters are the correct type for the theme object - attributes - """ - theme.background_color = theme.background_color.strip() - theme.background_direction = theme.background_direction.strip() - theme.background_endColor = theme.background_endColor.strip() - if theme.background_filename: - theme.background_filename = theme.background_filename.strip() - #theme.background_mode - theme.background_startColor = theme.background_startColor.strip() - #theme.background_type - if theme.display_display: - theme.display_display = theme.display_display.strip() - theme.display_horizontalAlign = \ - int(theme.display_horizontalAlign.strip()) - theme.display_outline = str_to_bool(theme.display_outline) - #theme.display_outline_color - theme.display_shadow = str_to_bool(theme.display_shadow) - #theme.display_shadow_color - theme.display_verticalAlign = int(theme.display_verticalAlign.strip()) - theme.display_wrapStyle = theme.display_wrapStyle.strip() - theme.display_slideTransition = theme.display_slideTransition - theme.font_footer_color = theme.font_footer_color.strip() - theme.font_footer_height = int(theme.font_footer_height.strip()) - theme.font_footer_italics = str_to_bool(theme.font_footer_italics) - theme.font_footer_name = theme.font_footer_name.strip() - #theme.font_footer_override - theme.font_footer_proportion = \ - int(theme.font_footer_proportion.strip()) - theme.font_footer_weight = theme.font_footer_weight.strip() - theme.font_footer_width = int(theme.font_footer_width.strip()) - theme.font_footer_x = int(theme.font_footer_x.strip()) - theme.font_footer_y = int(theme.font_footer_y.strip()) - theme.font_main_color = theme.font_main_color.strip() - theme.font_main_height = int(theme.font_main_height.strip()) - theme.font_main_italics = str_to_bool(theme.font_main_italics) - theme.font_main_name = theme.font_main_name.strip() - #theme.font_main_override - theme.font_main_proportion = int(theme.font_main_proportion.strip()) - theme.font_main_weight = theme.font_main_weight.strip() - theme.font_main_width = int(theme.font_main_width.strip()) - theme.font_main_x = int(theme.font_main_x.strip()) - theme.font_main_y = int(theme.font_main_y.strip()) - #theme.theme_mode - theme.theme_name = theme.theme_name.strip() - #theme.theme_version - # Remove the Transparent settings as they are not relevent - if theme.background_mode == u'transparent': - theme.background_mode = u'opaque' - theme.background_type = u'solid' - theme.background_startColor = u'#000000' diff --git a/openlp/plugins/alerts/alertsplugin.py b/openlp/plugins/alerts/alertsplugin.py index 2ae117aaa..44d919295 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 6d3406381..3f27f0736 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 84f0f41ee..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/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index 7ffb1d627..cc57d973e 100644 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -53,22 +53,18 @@ class BibleMediaItem(MediaManagerItem): """ log.info(u'Bible Media Item loaded') - def __init__(self, parent, plugin, icon): + def __init__(self, parent, icon, title): + self.PluginNameShort = u'Bible' + self.pluginNameVisible = translate('BiblesPlugin.MediaItem', 'Bible') self.IconPath = u'songs/song' self.ListViewWithDnD_class = BibleListView - MediaManagerItem.__init__(self, parent, self, icon) + MediaManagerItem.__init__(self, parent, icon, title) # place to store the search results for both bibles self.search_results = {} self.dual_search_results = {} 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 @@ -345,6 +341,31 @@ class BibleMediaItem(MediaManagerItem): # minor delay to get the events processed time.sleep(0.1) + def onListViewResize(self, width, height): + listViewGeometry = self.listView.geometry() + self.SearchProgress.setGeometry(listViewGeometry.x(), + (listViewGeometry.y() + listViewGeometry.height()) - 23, 81, 23) + + def onSearchProgressShow(self): + self.SearchProgress.setVisible(True) + Receiver.send_message(u'openlp_process_events') + + def onSearchProgressHide(self): + self.SearchProgress.setVisible(False) + + def onNoBookFound(self): + QtGui.QMessageBox.critical(self, + translate('BiblesPlugin.MediaItem', 'No Book Found'), + translate('BiblesPlugin.MediaItem', + 'No matching book could be found in this Bible.')) + + def onImportClick(self): + if not hasattr(self, u'import_wizard'): + self.import_wizard = ImportWizardForm(self, self.parent.manager, + self.parent) + self.import_wizard.exec_() + self.reloadBibles() + def loadBibles(self): log.debug(u'Loading Bibles') self.QuickVersionComboBox.clear() @@ -366,236 +387,6 @@ class BibleMediaItem(MediaManagerItem): first = False self.initialiseBible(bible) - def onListViewResize(self, width, height): - listViewGeometry = self.listView.geometry() - self.SearchProgress.setGeometry(listViewGeometry.x(), - (listViewGeometry.y() + listViewGeometry.height()) - 23, 81, 23) - - 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) - - def onNoBookFound(self): - QtGui.QMessageBox.critical(self, - translate('BiblesPlugin.MediaItem', 'No Book Found'), - translate('BiblesPlugin.MediaItem', - 'No matching book could be found in this Bible.')) - - def onAdvancedVersionComboBox(self): - self.initialiseBible( - unicode(self.AdvancedVersionComboBox.currentText())) - - def onAdvancedBookComboBox(self): - item = int(self.AdvancedBookComboBox.currentIndex()) - self.initialiseChapterVerse( - unicode(self.AdvancedVersionComboBox.currentText()), - unicode(self.AdvancedBookComboBox.currentText()), - self.AdvancedBookComboBox.itemData(item).toInt()[0]) - - def onImportClick(self): - if not hasattr(self, u'import_wizard'): - self.import_wizard = ImportWizardForm(self, self.parent.manager, - self.parent) - self.import_wizard.exec_() - 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: - 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)) - self.adjustComboBox(1, verses, self.AdvancedToVerse) - - def onAdvancedSearchButton(self): - log.debug(u'Advanced Search Button pressed') - bible = unicode(self.AdvancedVersionComboBox.currentText()) - dual_bible = unicode(self.AdvancedSecondBibleComboBox.currentText()) - book = unicode(self.AdvancedBookComboBox.currentText()) - chapter_from = int(self.AdvancedFromChapter.currentText()) - chapter_to = int(self.AdvancedToChapter.currentText()) - verse_from = int(self.AdvancedFromVerse.currentText()) - verse_to = int(self.AdvancedToVerse.currentText()) - versetext = u'%s %s:%s-%s:%s' % (book, chapter_from, verse_from, - chapter_to, verse_to) - self.search_results = self.parent.manager.get_verses(bible, versetext) - if dual_bible: - self.dual_search_results = self.parent.manager.get_verses( - dual_bible, versetext) - if self.ClearAdvancedSearchComboBox.currentIndex() == 0: - self.listView.clear() - 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) - - 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: - self.displayResults(bible, dual_bible) - - def generateSlideData(self, service_item, item=None): - """ - Generates and formats the slides for the service item as well as the - service item's title. - """ - log.debug(u'generating slide data') - items = self.listView.selectedIndexes() - if len(items) == 0: - return False - has_dual_bible = False - bible_text = u'' - old_chapter = u'' - raw_footer = [] - raw_slides = [] - 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') - 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 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) - raw_slides.append(bible_text) - bible_text = u'' - # If we are 'Verse Per Line' then force a new line. - elif self.parent.settings_tab.layout_style == 1: - bible_text = u'%s %s %s\n' % (bible_text, verse_text, text) - # We have to be 'Continuous'. - else: - bible_text = u'%s %s %s\n' % (bible_text, verse_text, text) - old_chapter = chapter - # 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 - 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) - 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: Theme - if len(self.parent.settings_tab.bible_theme) == 0: - service_item.theme = None - else: - service_item.theme = self.parent.settings_tab.bible_theme - for slide in raw_slides: - service_item.add_from_text(slide[:30], slide) - if service_item.raw_footer: - for footer in raw_footer: - service_item.raw_footer.append(footer) - else: - service_item.raw_footer = raw_footer - return True - - 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 - else: - verse_text = 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: - verse_text = u'{su}{' + verse_text + u'}{/su}' - elif self.parent.settings_tab.display_style == 3: - verse_text = u'{su}[' + verse_text + u']{/su}' - else: - verse_text = u'{su}' + verse_text + u'{/su}' - return verse_text - def reloadBibles(self): log.debug(u'Reloading Bibles') self.parent.manager.reload_bibles() @@ -632,12 +423,121 @@ class BibleMediaItem(MediaManagerItem): self.adjustComboBox(1, self.verses, self.AdvancedFromVerse) self.adjustComboBox(1, self.verses, self.AdvancedToVerse) + def onAdvancedVersionComboBox(self): + self.initialiseBible( + unicode(self.AdvancedVersionComboBox.currentText())) + + def onAdvancedBookComboBox(self): + item = int(self.AdvancedBookComboBox.currentIndex()) + self.initialiseChapterVerse( + unicode(self.AdvancedVersionComboBox.currentText()), + unicode(self.AdvancedBookComboBox.currentText()), + self.AdvancedBookComboBox.itemData(item).toInt()[0]) + + def onAdvancedFromVerse(self): + 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()) + 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 onAdvancedFromChapter(self): + bible = unicode(self.AdvancedVersionComboBox.currentText()) + book = unicode(self.AdvancedBookComboBox.currentText()) + 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 adjustComboBox(self, range_from, range_to, combo): log.debug(u'adjustComboBox %s, %s, %s', combo, range_from, range_to) combo.clear() for i in range(int(range_from), int(range_to) + 1): combo.addItem(unicode(i)) + def onAdvancedSearchButton(self): + log.debug(u'Advanced Search Button pressed') + bible = unicode(self.AdvancedVersionComboBox.currentText()) + dual_bible = unicode(self.AdvancedSecondBibleComboBox.currentText()) + book = unicode(self.AdvancedBookComboBox.currentText()) + chapter_from = int(self.AdvancedFromChapter.currentText()) + chapter_to = int(self.AdvancedToChapter.currentText()) + verse_from = int(self.AdvancedFromVerse.currentText()) + verse_to = int(self.AdvancedToVerse.currentText()) + versetext = u'%s %s:%s-%s:%s' % (book, chapter_from, verse_from, + chapter_to, verse_to) + self.search_results = self.parent.manager.get_verses(bible, versetext) + if dual_bible: + self.dual_search_results = self.parent.manager.get_verses( + dual_bible, versetext) + if self.ClearAdvancedSearchComboBox.currentIndex() == 0: + self.listView.clear() + 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 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()) + 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.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 displayResults(self, bible, dual_bible=u''): """ Displays the search results in the media manager. All data needed for @@ -688,18 +588,205 @@ 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) if row: row.setSelected(True) self.search_results = {} - self.dual_search_results = {} \ No newline at end of file + self.dual_search_results = {} + + 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 + service item's title. + """ + log.debug(u'generating slide data') + items = self.listView.selectedIndexes() + if len(items) == 0: + return 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()) + 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 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) + raw_slides.append(bible_text) + bible_text = u'' + # If we are 'Verse Per Line' then force a new line. + elif self.parent.settings_tab.layout_style == 1: + bible_text = u'%s %s %s\n' % (bible_text, verse_text, text) + # 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 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: Title + for title in raw_title: + if not service_item.title: + service_item.title = title + else: + service_item.title += u', ' + title + # Service Item: Theme + if len(self.parent.settings_tab.bible_theme) == 0: + service_item.theme = None + else: + service_item.theme = self.parent.settings_tab.bible_theme + for slide in raw_slides: + service_item.add_from_text(slide[:30], slide) + if service_item.raw_footer: + for footer in raw_footer: + service_item.raw_footer.append(footer) + else: + 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 = u'%s:%s' % (chapter, verse) + else: + 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: + verse_text = u'{su}{' + verse_text + u'}{/su}' + elif self.parent.settings_tab.display_style == 3: + verse_text = u'{su}[' + verse_text + u']{/su}' + else: + verse_text = u'{su}' + verse_text + u'{/su}' + return verse_text diff --git a/openlp/plugins/custom/customplugin.py b/openlp/plugins/custom/customplugin.py index 67644f749..91f928ad8 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 d7e6d1a08..5f4f5d146 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.4', 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/images/lib/mediaitem.py b/openlp/plugins/images/lib/mediaitem.py index 82fc434d0..5a3918dd9 100644 --- a/openlp/plugins/images/lib/mediaitem.py +++ b/openlp/plugins/images/lib/mediaitem.py @@ -185,8 +185,7 @@ class ImageMediaItem(MediaManagerItem): for item in items: bitem = self.listView.item(item.row()) filename = unicode(bitem.data(QtCore.Qt.UserRole).toString()) - frame = QtGui.QImage(unicode(filename)) - self.parent.liveController.display.image(frame) + self.parent.liveController.display.image(filename) self.resetButton.setVisible(True) def onPreviewClick(self): diff --git a/openlp/plugins/media/mediaplugin.py b/openlp/plugins/media/mediaplugin.py index d6ef87178..474c6d18d 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/presentationplugin.py b/openlp/plugins/presentations/presentationplugin.py index 2abb01138..f80079639 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/remoteplugin.py b/openlp/plugins/remotes/remoteplugin.py index ae52379ab..595cc14d6 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/editsongform.py b/openlp/plugins/songs/forms/editsongform.py index ae9938ab4..41eef0545 100644 --- a/openlp/plugins/songs/forms/editsongform.py +++ b/openlp/plugins/songs/forms/editsongform.py @@ -218,6 +218,11 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): self.CCLNumberEdit.setText(self.song.ccli_number) else: self.CCLNumberEdit.setText(u'') + if self.song.song_number: + self.songBookNumberEdit.setText(self.song.song_number) + else: + self.songBookNumberEdit.setText(u'') + # lazy xml migration for now self.VerseListWidget.clear() self.VerseListWidget.setRowCount(0) @@ -604,7 +609,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): self.song = Song() item = int(self.SongbookCombo.currentIndex()) text = unicode(self.SongbookCombo.currentText()) - if item == 0 and text: + if self.SongbookCombo.findText(text, QtCore.Qt.MatchExactly) < 0: if QtGui.QMessageBox.question(self, translate('SongsPlugin.EditSongForm', 'Add Book'), translate('SongsPlugin.EditSongForm', 'This song book does ' @@ -613,7 +618,6 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): QtGui.QMessageBox.Yes) == QtGui.QMessageBox.Yes: book = Book.populate(name=text, publisher=u'') self.songmanager.save_object(book) - self.song.book = book else: return if self.saveSong(): @@ -629,11 +633,17 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): self.song.alternate_title = unicode(self.AlternativeEdit.text()) self.song.copyright = unicode(self.CopyrightEditItem.text()) self.song.search_title = self.song.title + u'@' + \ - unicode(self.AlternativeEdit.text()) + self.song.alternate_title self.song.comments = unicode(self.CommentsEdit.toPlainText()) self.song.verse_order = unicode(self.VerseOrderEdit.text()) self.song.ccli_number = unicode(self.CCLNumberEdit.text()) self.song.song_number = unicode(self.songBookNumberEdit.text()) + book_name = unicode(self.SongbookCombo.currentText()) + if book_name: + self.song.book = self.songmanager.get_object_filtered(Book, + Book.name == book_name) + else: + self.song.book = None if self._validate_song(): self.processLyrics() self.processTitle() @@ -669,7 +679,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): unicode(self.VerseListWidget.item(i, 0).text())) + u' ' if (bits[1] > u'1') and (bits[0][0] not in multiple): multiple.append(bits[0][0]) - self.song.search_lyrics = text + self.song.search_lyrics = text.lower() self.song.lyrics = unicode(sxml.extract_xml(), u'utf-8') for verse in multiple: self.song.verse_order = re.sub(u'([' + verse.upper() + @@ -682,4 +692,4 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): def processTitle(self): log.debug(u'processTitle') self.song.search_title = re.sub(r'[\'"`,;:(){}?]+', u'', - unicode(self.song.search_title)) + unicode(self.song.search_title)).lower() diff --git a/openlp/plugins/songs/forms/songimportform.py b/openlp/plugins/songs/forms/songimportform.py index 29cc42ec5..c62fa058e 100644 --- a/openlp/plugins/songs/forms/songimportform.py +++ b/openlp/plugins/songs/forms/songimportform.py @@ -109,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) @@ -214,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 @@ -322,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. @@ -341,6 +361,8 @@ 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'') @@ -350,6 +372,7 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard): self.ccliFileListWidget.clear() self.songsOfFellowshipFileListWidget.clear() self.genericFileListWidget.clear() + self.ewFilenameEdit.setText(u'') #self.csvFilenameEdit.setText(u'') def incrementProgressBar(self, status_text, increment=1): @@ -420,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 37ee10612..bad85676f 100644 --- a/openlp/plugins/songs/forms/songimportwizard.py +++ b/openlp/plugins/songs/forms/songimportwizard.py @@ -30,8 +30,8 @@ from openlp.core.lib import build_icon, translate class Ui_SongImportWizard(object): def setupUi(self, songImportWizard): - openIcon = build_icon(u':/general/general_open.png') - deleteIcon = build_icon(u':/general/general_delete.png') + self.openIcon = build_icon(u':/general/general_open.png') + self.deleteIcon = build_icon(u':/general/general_delete.png') songImportWizard.setObjectName(u'songImportWizard') songImportWizard.resize(550, 386) songImportWizard.setModal(True) @@ -88,15 +88,6 @@ class Ui_SongImportWizard(object): self.formatComboBox.sizePolicy().hasHeightForWidth()) self.formatComboBox.setSizePolicy(sizePolicy) self.formatComboBox.setObjectName(u'formatComboBox') - self.formatComboBox.addItem(u'') - self.formatComboBox.addItem(u'') - self.formatComboBox.addItem(u'') - self.formatComboBox.addItem(u'') - 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, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) @@ -105,338 +96,28 @@ class Ui_SongImportWizard(object): self.formatStackedWidget = QtGui.QStackedWidget(self.sourcePage) self.formatStackedWidget.setObjectName(u'FormatStackedWidget') # OpenLP 2.0 - self.openLP2Page = QtGui.QWidget() - self.openLP2Page.setObjectName(u'openLP2Page') - self.openLP2Layout = QtGui.QFormLayout(self.openLP2Page) - self.openLP2Layout.setMargin(0) - self.openLP2Layout.setSpacing(8) - self.openLP2Layout.setObjectName(u'openLP2Layout') - self.openLP2FilenameLabel = QtGui.QLabel(self.openLP2Page) - self.openLP2FilenameLabel.setObjectName(u'openLP2FilenameLabel') - self.openLP2Layout.setWidget(0, QtGui.QFormLayout.LabelRole, - self.openLP2FilenameLabel) - self.openLP2FileLayout = QtGui.QHBoxLayout() - self.openLP2FileLayout.setSpacing(8) - self.openLP2FileLayout.setObjectName(u'openLP2FileLayout') - self.openLP2FilenameEdit = QtGui.QLineEdit(self.openLP2Page) - self.openLP2FilenameEdit.setObjectName(u'openLP2FilenameEdit') - self.openLP2FileLayout.addWidget(self.openLP2FilenameEdit) - self.openLP2BrowseButton = QtGui.QToolButton(self.openLP2Page) - self.openLP2BrowseButton.setIcon(openIcon) - self.openLP2BrowseButton.setObjectName(u'openLP2BrowseButton') - self.openLP2FileLayout.addWidget(self.openLP2BrowseButton) - self.openLP2Layout.setLayout(0, QtGui.QFormLayout.FieldRole, - self.openLP2FileLayout) - self.formatStackedWidget.addWidget(self.openLP2Page) + self.addSingleFileSelectItem(u'openLP2') # openlp.org 1.x - self.openLP1Page = QtGui.QWidget() - self.openLP1Page.setObjectName(u'openLP1Page') - self.openLP1Layout = QtGui.QVBoxLayout(self.openLP1Page) - self.openLP1Layout.setMargin(0) - self.openLP1Layout.setSpacing(0) - self.openLP1Layout.setObjectName(u'openLP1Layout') - 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.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.openLP1ImportWidget) - self.openLP1FilenameEdit.setObjectName(u'openLP1FilenameEdit') - self.openLP1FileLayout.addWidget(self.openLP1FilenameEdit) - self.openLP1BrowseButton = QtGui.QToolButton(self.openLP1ImportWidget) - self.openLP1BrowseButton.setIcon(openIcon) - self.openLP1BrowseButton.setObjectName(u'openLP1BrowseButton') - self.openLP1FileLayout.addWidget(self.openLP1BrowseButton) - self.openLP1ImportLayout.setLayout(0, QtGui.QFormLayout.FieldRole, - self.openLP1FileLayout) - self.openLP1Layout.addWidget(self.openLP1ImportWidget) - self.formatStackedWidget.addWidget(self.openLP1Page) + self.addSingleFileSelectItem(u'openLP1', None, True) # OpenLyrics - self.openLyricsPage = QtGui.QWidget() - self.openLyricsPage.setObjectName(u'OpenLyricsPage') - self.openLyricsLayout = QtGui.QVBoxLayout(self.openLyricsPage) - self.openLyricsLayout.setSpacing(8) - self.openLyricsLayout.setMargin(0) - self.openLyricsLayout.setObjectName(u'OpenLyricsLayout') - 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) + self.addMultiFileSelectItem(u'openLyrics', u'OpenLyrics', True) + # set OpenLyrics to disabled by default + self.openLyricsDisabledWidget.setVisible(True) + self.openLyricsImportWidget.setVisible(False) # Open Song - self.openSongPage = QtGui.QWidget() - self.openSongPage.setObjectName(u'OpenSongPage') - self.openSongLayout = QtGui.QVBoxLayout(self.openSongPage) - self.openSongLayout.setSpacing(8) - self.openSongLayout.setMargin(0) - self.openSongLayout.setObjectName(u'OpenSongLayout') - self.openSongFileListWidget = QtGui.QListWidget(self.openSongPage) - self.openSongFileListWidget.setSelectionMode( - QtGui.QAbstractItemView.ExtendedSelection) - self.openSongFileListWidget.setObjectName(u'OpenSongFileListWidget') - self.openSongLayout.addWidget(self.openSongFileListWidget) - self.openSongButtonLayout = QtGui.QHBoxLayout() - self.openSongButtonLayout.setSpacing(8) - self.openSongButtonLayout.setObjectName(u'OpenSongButtonLayout') - self.openSongAddButton = QtGui.QPushButton(self.openSongPage) - self.openSongAddButton.setIcon(openIcon) - self.openSongAddButton.setObjectName(u'OpenSongAddButton') - self.openSongButtonLayout.addWidget(self.openSongAddButton) - self.openSongButtonSpacer = QtGui.QSpacerItem(40, 20, - QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.openSongButtonLayout.addItem(self.openSongButtonSpacer) - self.openSongRemoveButton = QtGui.QPushButton(self.openSongPage) - self.openSongRemoveButton.setIcon(deleteIcon) - self.openSongRemoveButton.setObjectName(u'OpenSongRemoveButton') - self.openSongButtonLayout.addWidget(self.openSongRemoveButton) - self.openSongLayout.addLayout(self.openSongButtonLayout) - self.formatStackedWidget.addWidget(self.openSongPage) + self.addMultiFileSelectItem(u'openSong', u'OpenSong') # Words of Worship - self.wordsOfWorshipPage = QtGui.QWidget() - self.wordsOfWorshipPage.setObjectName(u'wordsOfWorshipPage') - self.wordsOfWorshipLayout = QtGui.QVBoxLayout(self.wordsOfWorshipPage) - self.wordsOfWorshipLayout.setSpacing(8) - self.wordsOfWorshipLayout.setMargin(0) - self.wordsOfWorshipLayout.setObjectName(u'wordsOfWorshipLayout') - self.wordsOfWorshipFileListWidget = QtGui.QListWidget( - self.wordsOfWorshipPage) - self.wordsOfWorshipFileListWidget.setSelectionMode( - QtGui.QAbstractItemView.ExtendedSelection) - self.wordsOfWorshipFileListWidget.setObjectName( - u'wordsOfWorshipFileListWidget') - self.wordsOfWorshipLayout.addWidget(self.wordsOfWorshipFileListWidget) - self.wordsOfWorshipButtonLayout = QtGui.QHBoxLayout() - self.wordsOfWorshipButtonLayout.setSpacing(8) - self.wordsOfWorshipButtonLayout.setObjectName( - u'wordsOfWorshipButtonLayout') - self.wordsOfWorshipAddButton = QtGui.QPushButton( - self.wordsOfWorshipPage) - self.wordsOfWorshipAddButton.setIcon(openIcon) - self.wordsOfWorshipAddButton.setObjectName(u'wordsOfWorshipAddButton') - self.wordsOfWorshipButtonLayout.addWidget(self.wordsOfWorshipAddButton) - self.wordsOfWorshipButtonSpacer = QtGui.QSpacerItem(40, 20, - QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.wordsOfWorshipButtonLayout.addItem(self.wordsOfWorshipButtonSpacer) - self.wordsOfWorshipRemoveButton = QtGui.QPushButton( - self.wordsOfWorshipPage) - self.wordsOfWorshipRemoveButton.setIcon(deleteIcon) - self.wordsOfWorshipRemoveButton.setObjectName( - u'wordsOfWorshipRemoveButton') - self.wordsOfWorshipButtonLayout.addWidget( - self.wordsOfWorshipRemoveButton) - self.wordsOfWorshipLayout.addLayout(self.wordsOfWorshipButtonLayout) - self.formatStackedWidget.addWidget(self.wordsOfWorshipPage) + self.addMultiFileSelectItem(u'wordsOfWorship') # CCLI File import - self.ccliPage = QtGui.QWidget() - self.ccliPage.setObjectName(u'ccliPage') - self.ccliLayout = QtGui.QVBoxLayout(self.ccliPage) - self.ccliLayout.setSpacing(8) - self.ccliLayout.setMargin(0) - self.ccliLayout.setObjectName(u'ccliLayout') - self.ccliFileListWidget = QtGui.QListWidget(self.ccliPage) - self.ccliFileListWidget.setSelectionMode( - QtGui.QAbstractItemView.ExtendedSelection) - self.ccliFileListWidget.setObjectName(u'ccliFileListWidget') - self.ccliLayout.addWidget(self.ccliFileListWidget) - self.ccliButtonLayout = QtGui.QHBoxLayout() - self.ccliButtonLayout.setSpacing(8) - self.ccliButtonLayout.setObjectName(u'ccliButtonLayout') - self.ccliAddButton = QtGui.QPushButton(self.ccliPage) - self.ccliAddButton.setIcon(openIcon) - self.ccliAddButton.setObjectName(u'ccliAddButton') - self.ccliButtonLayout.addWidget(self.ccliAddButton) - self.ccliButtonSpacer = QtGui.QSpacerItem(40, 20, - QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.ccliButtonLayout.addItem(self.ccliButtonSpacer) - self.ccliRemoveButton = QtGui.QPushButton(self.ccliPage) - self.ccliRemoveButton.setIcon(deleteIcon) - self.ccliRemoveButton.setObjectName(u'ccliRemoveButton') - self.ccliButtonLayout.addWidget(self.ccliRemoveButton) - self.ccliLayout.addLayout(self.ccliButtonLayout) - self.formatStackedWidget.addWidget(self.ccliPage) + self.addMultiFileSelectItem(u'ccli') # Songs of Fellowship - self.songsOfFellowshipPage = QtGui.QWidget() - self.songsOfFellowshipPage.setObjectName(u'songsOfFellowshipPage') - self.songsOfFellowshipLayout = QtGui.QVBoxLayout( - self.songsOfFellowshipPage) - self.songsOfFellowshipLayout.setMargin(0) - self.songsOfFellowshipLayout.setSpacing(0) - self.songsOfFellowshipLayout.setObjectName(u'songsOfFellowshipLayout') - 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.songsOfFellowshipImportLayout.addWidget( - self.songsOfFellowshipFileListWidget) - self.songsOfFellowshipButtonLayout = QtGui.QHBoxLayout() - self.songsOfFellowshipButtonLayout.setSpacing(8) - self.songsOfFellowshipButtonLayout.setObjectName( - u'songsOfFellowshipButtonLayout') - self.songsOfFellowshipAddButton = QtGui.QPushButton( - self.songsOfFellowshipImportWidget) - self.songsOfFellowshipAddButton.setIcon(openIcon) - self.songsOfFellowshipAddButton.setObjectName( - u'songsOfFellowshipAddButton') - self.songsOfFellowshipButtonLayout.addWidget( - self.songsOfFellowshipAddButton) - self.songsOfFellowshipButtonSpacer = QtGui.QSpacerItem(40, 20, - QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.songsOfFellowshipButtonLayout.addItem( - self.songsOfFellowshipButtonSpacer) - self.songsOfFellowshipRemoveButton = QtGui.QPushButton( - self.songsOfFellowshipImportWidget) - self.songsOfFellowshipRemoveButton.setIcon(deleteIcon) - self.songsOfFellowshipRemoveButton.setObjectName( - u'songsOfFellowshipRemoveButton') - self.songsOfFellowshipButtonLayout.addWidget( - self.songsOfFellowshipRemoveButton) - self.songsOfFellowshipImportLayout.addLayout( - self.songsOfFellowshipButtonLayout) - self.songsOfFellowshipLayout.addWidget( - self.songsOfFellowshipImportWidget) - self.formatStackedWidget.addWidget(self.songsOfFellowshipPage) + self.addMultiFileSelectItem(u'songsOfFellowship', None, True) # 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(0) - self.genericLayout.setObjectName(u'genericLayout') - 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.genericImportLayout.addWidget(self.genericFileListWidget) - self.genericButtonLayout = QtGui.QHBoxLayout() - self.genericButtonLayout.setSpacing(8) - self.genericButtonLayout.setObjectName(u'genericButtonLayout') - 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.genericImportWidget) - self.genericRemoveButton.setIcon(deleteIcon) - self.genericRemoveButton.setObjectName(u'genericRemoveButton') - self.genericButtonLayout.addWidget(self.genericRemoveButton) - self.genericImportLayout.addLayout(self.genericButtonLayout) - self.genericLayout.addWidget(self.genericImportWidget) - self.formatStackedWidget.addWidget(self.genericPage) + self.addMultiFileSelectItem(u'generic', None, True) + # EasyWorship + self.addSingleFileSelectItem(u'ew') # Commented out for future use. -# self.csvPage = QtGui.QWidget() -# self.csvPage.setObjectName(u'CSVPage') -# self.csvLayout = QtGui.QFormLayout(self.csvPage) -# self.csvLayout.setMargin(0) -# self.csvLayout.setSpacing(8) -# self.csvLayout.setObjectName(u'CSVLayout') -# self.csvFilenameLabel = QtGui.QLabel(self.csvPage) -# self.csvFilenameLabel.setObjectName(u'CSVFilenameLabel') -# self.csvLayout.setWidget(0, QtGui.QFormLayout.LabelRole, -# self.csvFilenameLabel) -# self.csvFileLayout = QtGui.QHBoxLayout() -# self.csvFileLayout.setSpacing(8) -# self.csvFileLayout.setObjectName(u'CSVFileLayout') -# self.csvFilenameEdit = QtGui.QLineEdit(self.csvPage) -# self.csvFilenameEdit.setObjectName(u'CSVFilenameEdit') -# self.csvFileLayout.addWidget(self.csvFilenameEdit) -# self.csvBrowseButton = QtGui.QToolButton(self.csvPage) -# self.csvBrowseButton.setIcon(openIcon) -# self.csvBrowseButton.setObjectName(u'CSVBrowseButton') -# self.csvFileLayout.addWidget(self.csvBrowseButton) -# self.csvLayout.setLayout(0, QtGui.QFormLayout.FieldRole, -# self.csvFileLayout) -# self.formatStackedWidget.addWidget(self.csvPage) +# self.addSingleFileSelectItem(u'csv', u'CSV') self.sourceLayout.addWidget(self.formatStackedWidget) songImportWizard.addPage(self.sourcePage) self.importPage = QtGui.QWizardPage() @@ -497,7 +178,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:')) @@ -549,6 +232,10 @@ class Ui_SongImportWizard(object): 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( @@ -562,3 +249,123 @@ class Ui_SongImportWizard(object): translate('SongsPlugin.ImportWizardForm', 'Ready.')) self.importProgressBar.setFormat( translate('SongsPlugin.ImportWizardForm', '%p%')) + + def addSingleFileSelectItem(self, prefix, obj_prefix=None, + can_disable=False): + if not obj_prefix: + obj_prefix = prefix + page = QtGui.QWidget() + page.setObjectName(obj_prefix + u'Page') + if can_disable: + importWidget = self.disablableWidget(page, prefix, obj_prefix) + else: + importWidget = page + importLayout = QtGui.QFormLayout(importWidget) + importLayout.setMargin(0) + importLayout.setSpacing(8) + if can_disable: + importLayout.setObjectName(obj_prefix + u'ImportLayout') + else: + importLayout.setObjectName(obj_prefix + u'Layout') + filenameLabel = QtGui.QLabel(importWidget) + filenameLabel.setObjectName(obj_prefix + u'FilenameLabel') + importLayout.setWidget(0, QtGui.QFormLayout.LabelRole, filenameLabel) + fileLayout = QtGui.QHBoxLayout() + fileLayout.setSpacing(8) + fileLayout.setObjectName(obj_prefix + u'FileLayout') + filenameEdit = QtGui.QLineEdit(importWidget) + filenameEdit.setObjectName(obj_prefix + u'FilenameEdit') + fileLayout.addWidget(filenameEdit) + browseButton = QtGui.QToolButton(importWidget) + browseButton.setIcon(self.openIcon) + browseButton.setObjectName(obj_prefix + u'BrowseButton') + fileLayout.addWidget(browseButton) + importLayout.setLayout(0, QtGui.QFormLayout.FieldRole, fileLayout) + self.formatStackedWidget.addWidget(page) + setattr(self, prefix + u'Page', page) + setattr(self, prefix + u'FilenameLabel', filenameLabel) + setattr(self, prefix + u'FileLayout', fileLayout) + setattr(self, prefix + u'FilenameEdit', filenameEdit) + setattr(self, prefix + u'BrowseButton', browseButton) + if can_disable: + setattr(self, prefix + u'ImportLayout', importLayout) + else: + setattr(self, prefix + u'Layout', importLayout) + self.formatComboBox.addItem(u'') + + def addMultiFileSelectItem(self, prefix, obj_prefix=None, + can_disable=False): + if not obj_prefix: + obj_prefix = prefix + page = QtGui.QWidget() + page.setObjectName(obj_prefix + u'Page') + if can_disable: + importWidget = self.disablableWidget(page, prefix, obj_prefix) + else: + importWidget = page + importLayout = QtGui.QVBoxLayout(importWidget) + importLayout.setMargin(0) + importLayout.setSpacing(8) + if can_disable: + importLayout.setObjectName(obj_prefix + u'ImportLayout') + else: + importLayout.setObjectName(obj_prefix + u'Layout') + fileListWidget = QtGui.QListWidget(importWidget) + fileListWidget.setSelectionMode( + QtGui.QAbstractItemView.ExtendedSelection) + fileListWidget.setObjectName(obj_prefix + u'FileListWidget') + importLayout.addWidget(fileListWidget) + buttonLayout = QtGui.QHBoxLayout() + buttonLayout.setSpacing(8) + buttonLayout.setObjectName(obj_prefix + u'ButtonLayout') + addButton = QtGui.QPushButton(importWidget) + addButton.setIcon(self.openIcon) + addButton.setObjectName(obj_prefix + u'AddButton') + buttonLayout.addWidget(addButton) + buttonSpacer = QtGui.QSpacerItem(40, 20, + QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + buttonLayout.addItem(buttonSpacer) + removeButton = QtGui.QPushButton(importWidget) + removeButton.setIcon(self.deleteIcon) + removeButton.setObjectName(obj_prefix + u'RemoveButton') + buttonLayout.addWidget(removeButton) + importLayout.addLayout(buttonLayout) + self.formatStackedWidget.addWidget(page) + setattr(self, prefix + u'Page', page) + setattr(self, prefix + u'FileListWidget', fileListWidget) + setattr(self, prefix + u'ButtonLayout', buttonLayout) + setattr(self, prefix + u'ButtonSpacer', buttonSpacer) + setattr(self, prefix + u'AddButton', addButton) + setattr(self, prefix + u'RemoveButton', removeButton) + if can_disable: + setattr(self, prefix + u'ImportLayout', importLayout) + else: + setattr(self, prefix + u'Layout', importLayout) + self.formatComboBox.addItem(u'') + + def disablableWidget(self, page, prefix, obj_prefix): + layout = QtGui.QVBoxLayout(page) + layout.setMargin(0) + layout.setSpacing(0) + layout.setObjectName(obj_prefix + u'Layout') + disabledWidget = QtGui.QWidget(page) + disabledWidget.setVisible(False) + disabledWidget.setObjectName(obj_prefix + u'DisabledWidget') + disabledLayout = QtGui.QVBoxLayout(disabledWidget) + disabledLayout.setMargin(0) + disabledLayout.setSpacing(8) + disabledLayout.setObjectName(obj_prefix + u'DisabledLayout') + disabledLabel = QtGui.QLabel(disabledWidget) + disabledLabel.setWordWrap(True) + disabledLabel.setObjectName(obj_prefix + u'DisabledLabel') + disabledLayout.addWidget(disabledLabel) + layout.addWidget(disabledWidget) + importWidget = QtGui.QWidget(page) + importWidget.setObjectName(obj_prefix + u'ImportWidget') + layout.addWidget(importWidget) + setattr(self, prefix + u'Layout', layout) + setattr(self, prefix + u'DisabledWidget', disabledWidget) + setattr(self, prefix + u'DisabledLayout', disabledLayout) + setattr(self, prefix + u'DisabledLabel', disabledLabel) + setattr(self, prefix + u'ImportWidget', importWidget) + return importWidget diff --git a/openlp/plugins/songs/lib/ewimport.py b/openlp/plugins/songs/lib/ewimport.py new file mode 100644 index 000000000..2db1df375 --- /dev/null +++ b/openlp/plugins/songs/lib/ewimport.py @@ -0,0 +1,296 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Meinert Jordan, Andreas Preikschat, Christian # +# Richter, Philip Ridout, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard, Frode Woldsund, Jeffrey Smith # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### +""" +The :mod:`ewimport` module provides the functionality for importing +EasyWorship song databases into the current installation database. +""" + +import sys +import os +import struct + +from openlp.core.lib import translate +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] + # Return None in case of 'blank' entries + if isinstance(field, str): + if len(field.rstrip('\0')) == 0: + return None + elif field == 0: + return None + # 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 + 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 + (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 2d00c6523..d8028db24 100644 --- a/openlp/plugins/songs/lib/importer.py +++ b/openlp/plugins/songs/lib/importer.py @@ -28,6 +28,7 @@ from opensongimport import OpenSongImport from olpimport import OpenLPSongImport from wowimport import WowImport from cclifileimport import CCLIFileImport +from ewimport import EasyWorshipSongImport # Imports that might fail try: from olp1import import OpenLP1SongImport @@ -61,7 +62,8 @@ class SongFormat(object): CCLI = 5 SongsOfFellowship = 6 Generic = 7 - CSV = 8 + #CSV = 8 + EasyWorship = 8 @staticmethod def get_class(format): @@ -85,6 +87,8 @@ class SongFormat(object): return OooImport elif format == SongFormat.CCLI: return CCLIFileImport + elif format == SongFormat.EasyWorship: + return EasyWorshipSongImport # else: return None @@ -101,7 +105,8 @@ class SongFormat(object): SongFormat.WordsOfWorship, SongFormat.CCLI, SongFormat.SongsOfFellowship, - SongFormat.Generic + SongFormat.Generic, + SongFormat.EasyWorship ] @staticmethod diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py index a794ea1f5..b9c4526cf 100644 --- a/openlp/plugins/songs/lib/mediaitem.py +++ b/openlp/plugins/songs/lib/mediaitem.py @@ -164,13 +164,13 @@ class SongMediaItem(MediaManagerItem): if search_type == 0: log.debug(u'Titles Search') search_results = self.parent.manager.get_all_objects(Song, - Song.search_title.like(u'%' + search_keywords + u'%'), + Song.search_title.like(u'%' + search_keywords.lower() + u'%'), Song.search_title.asc()) self.displayResultsSong(search_results) elif search_type == 1: log.debug(u'Lyrics Search') search_results = self.parent.manager.get_all_objects(Song, - Song.search_lyrics.like(u'%' + search_keywords + u'%'), + Song.search_lyrics.like(u'%' + search_keywords.lower() + u'%'), Song.search_lyrics.asc()) self.displayResultsSong(search_results) elif search_type == 2: diff --git a/openlp/plugins/songs/lib/songimport.py b/openlp/plugins/songs/lib/songimport.py index dcf4ed8d8..d2e0698b3 100644 --- a/openlp/plugins/songs/lib/songimport.py +++ b/openlp/plugins/songs/lib/songimport.py @@ -55,6 +55,7 @@ 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 @@ -262,8 +263,8 @@ class SongImport(QtCore.QObject): 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.search_title = self.remove_punctuation(self.title).lower() \ + + '@' + self.remove_punctuation(self.alternate_title).lower() song.song_number = self.song_number song.search_lyrics = u'' verses_changed_to_other = {} @@ -291,6 +292,7 @@ class SongImport(QtCore.QObject): versetag = newversetag sxml.add_verse_to_lyrics(versetype, versetag[1:], versetext) song.search_lyrics += u' ' + self.remove_punctuation(versetext) + song.search_lyrics = song.search_lyrics.lower() 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): diff --git a/openlp/plugins/songs/songsplugin.py b/openlp/plugins/songs/songsplugin.py index bd5c3fb45..297be7ed8 100644 --- a/openlp/plugins/songs/songsplugin.py +++ b/openlp/plugins/songs/songsplugin.py @@ -50,7 +50,7 @@ class SongsPlugin(Plugin): """ Create and set up the Songs plugin. """ - Plugin.__init__(self, u'Songs', u'1.9.2', plugin_helpers) + Plugin.__init__(self, u'Songs', u'1.9.3', plugin_helpers) self.weight = -10 self.manager = Manager(u'songs', init_schema) self.icon_path = u':/plugins/plugin_songs.png' @@ -64,7 +64,7 @@ class SongsPlugin(Plugin): log.info(u'Songs Initialising') Plugin.initialise(self) self.mediaItem.displayResultsSong( - self.manager.get_all_objects(Song, order_by_ref=Song.title)) + self.manager.get_all_objects(Song, order_by_ref=Song.search_title)) def getMediaManagerItem(self): """ @@ -141,7 +141,7 @@ class SongsPlugin(Plugin): Song.theme_name == oldTheme) for song in songsUsingTheme: song.theme_name = newTheme - self.custommanager.save_object(song) + self.manager.save_object(song) def importSongs(self, format, **kwargs): class_ = SongFormat.get_class(format) diff --git a/openlp/plugins/songusage/songusageplugin.py b/openlp/plugins/songusage/songusageplugin.py index cd3689095..167a00029 100644 --- a/openlp/plugins/songusage/songusageplugin.py +++ b/openlp/plugins/songusage/songusageplugin.py @@ -41,7 +41,7 @@ class SongUsagePlugin(Plugin): log.info(u'SongUsage Plugin loaded') def __init__(self, plugin_helpers): - Plugin.__init__(self, u'SongUsage', u'1.9.2', plugin_helpers) + Plugin.__init__(self, u'SongUsage', u'1.9.3', plugin_helpers) self.weight = -4 self.icon = build_icon(u':/plugins/plugin_songusage.png') self.songusagemanager = None diff --git a/resources/forms/filerenamedialog.ui b/resources/forms/filerenamedialog.ui new file mode 100644 index 000000000..fb2d8780d --- /dev/null +++ b/resources/forms/filerenamedialog.ui @@ -0,0 +1,54 @@ + + + FileRenameDialog + + + + 0 + 0 + 400 + 87 + + + + File Rename + + + + + 210 + 50 + 171 + 25 + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + 10 + 10 + 381 + 27 + + + + + + + New File Name: + + + + + + + + + + + + diff --git a/resources/forms/themewizard.ui b/resources/forms/themewizard.ui index 54f9e43aa..a87052322 100644 --- a/resources/forms/themewizard.ui +++ b/resources/forms/themewizard.ui @@ -1,95 +1,893 @@ - - ThemeWizard - - - Qt::ApplicationModal - - + + + Wizard + + 0 0 - 576 - 397 + 559 + 487 - + Theme Wizard - + true - + QWizard::ModernStyle - - QWizard::DisabledBackButtonOnLastPage|QWizard::IndependentPages|QWizard::NoBackButtonOnStartPage|QWizard::NoCancelButton + + QWizard::HaveCustomButton1|QWizard::NoBackButtonOnStartPage - - - Welcome - - + + - - - - 20 - 100 - 341 - 31 - + + + + + + 8 - - Welcome to the Theme Wizard. This wizard will guide you through the process of creating a new theme. + + 0 - - true - - + + + + + 163 + 0 + + + + + 163 + 16777215 + + + + 0 + + + + + + :/wizards/wizard_importbible.bmp + + + 0 + + + + + + + 8 + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:14pt; font-weight:600;">Welcome to the Theme Wizard</span></p></body></html> + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 40 + + + + + + + + This wizard will help you to maintain Themes . Click the next button below to start the process.. + + + true + + + 10 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + - - - Theme Name + + + Select Import Source - - Choose a name for your theme + + Select the import format, and where to import from. - - - - 100 - 130 - 91 - 17 - + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - Theme Name: - - - - - - 200 - 127 - 261 - 22 - - - + + + + + + Background: + + + + + + + + Opaque + + + + + Transparent + + + + + + + + + + + + Background Type: + + + + + + + + Solid Color + + + + + Gradient + + + + + Image + + + + + + + + + + + + <Color1> + + + + + + + + + + + + + + + + + + <Color2> + + + + + + + + + + + + + + + + + + Gradient : + + + + + + + + Horizontal + + + + + Vertical + + + + + Circular + + + + + + + + + + + + Image: + + + + + + + + + + ... + + + + + + - - - Select Background + + + Main Area Font Details - - Select a background type and configure your background + + Define the font and display charaistics for the Display text + + + + + Font: + + + + + + + + + + Font Color: + + + + + + + + + + + + + + Size: + + + + + + + + 0 + 0 + + + + + 70 + 0 + + + + pt + + + 999 + + + 16 + + + + + + + Wrap Indentation + + + + + + + + + + TextLabel + + + + + + + Show Outline: + + + + + + + + + + + + + + Outline Color: + + + + + + + + + + + + + + Show Shadow: + + + + + + + + + + + + + + Shadow Color: + + + + + + + + + + + + + + + Footer Area Font Details + + + Define the font and display charaistics for the Footer text + + + + + + Font: + + + + + + + + + + Font Color: + + + + + + + + + + + + + + Size: + + + + + + + + 0 + 0 + + + + + 70 + 0 + + + + pt + + + 999 + + + 10 + + + + + + + Show Outline: + + + + + + + + + + + + + + Shadow Color: + + + + + + + + + + + + + + Show Shadow: + + + + + + + + + + + + + + Outline Color: + + + + + + + + + + + + + + + Text Display Layout + + + Allows you to change and move the Main and Footer areas. + + + + + + + + + false + + + + + + + + + Main Area + + + + + + + Footer Area + + + + + + + X Position: + + + + + + + + 0 + 0 + + + + + 78 + 0 + + + + px + + + 9999 + + + 0 + + + + + + + X Position: + + + + + + + + 0 + 0 + + + + + 78 + 0 + + + + px + + + 9999 + + + 0 + + + + + + + Y Position: + + + + + + + + 0 + 0 + + + + + 78 + 0 + + + + px + + + 9999 + + + + + + + Y Position: + + + + + + + + 0 + 0 + + + + + 78 + 0 + + + + px + + + 9999 + + + 0 + + + + + + + Width: + + + + + + + + 0 + 0 + + + + + 78 + 0 + + + + px + + + 9999 + + + + + + + Width: + + + + + + + + 78 + 0 + + + + px + + + 9999 + + + + + + + Height: + + + + + + + + 0 + 0 + + + + + 78 + 0 + + + + px + + + 9999 + + + + + + + Height: + + + + + + + + 78 + 0 + + + + px + + + 9999 + + + + + + + + + Use Default Location: + + + + + + + + Save and Preview + + + View the theme and save it replacing the current one or change the name to create a new theme + + + + + + + + Theme Name: + + + + + + + + + + + + + + Preview + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + 300 + 225 + + + + QFrame::WinPanel + + + QFrame::Sunken + + + 1 + + + + + + true + + + + + + - + diff --git a/resources/i18n/ja.ts b/resources/i18n/ja.ts new file mode 100644 index 000000000..22fe3f242 --- /dev/null +++ b/resources/i18n/ja.ts @@ -0,0 +1,3992 @@ + + + + AlertsPlugin + + + &Alert + 警告(&A) + + + + Show an alert message. + 警告メッセージを表示する。 + + + + <strong>Alerts Plugin</strong><br />The alert plugin controls the displaying of nursery alerts on the display screen + + + + + AlertsPlugin.AlertForm + + + Alert Message + 警告メッセージ + + + + Alert &text: + 警告文(&T) + + + + &Parameter(s): + + + + + &New + 新規作成(&N) + + + + &Save + 保存(&S) + + + + &Delete + 削除(&D) + + + + Displ&ay + 表示(&A) + + + + Display && Cl&ose + 表示して閉じる(&O) + + + + &Close + 閉じる(&C) + + + + New Alert + 新しい警告 + + + + You haven't specified any text for your alert. Please type in some text before clicking New. + 警告文が何も設定されていません。新規作成をクリックする前にメッセージを入力してください。 + + + + AlertsPlugin.AlertsManager + + + Alert message created and displayed. + 警告文を作成し表示しました。 + + + + AlertsPlugin.AlertsTab + + + Alerts + 警告 + + + + Font + フォント + + + + Font name: + フォント名: + + + + Font color: + 文字色: + + + + Background color: + 背景色: + + + + Font size: + 大きさ: + + + + pt + + + + + Alert timeout: + 警告タイムアウト: + + + + s + + + + + Location: + 表示位置: + + + + Preview + + + + + OpenLP 2.0 + OpenLP 2.0 + + + + Top + + + + + Middle + + + + + Bottom + + + + + BiblePlugin.MediaItem + + + Error + + + + + You cannot combine single and dual bible verses. Do you want to delete your search results and start a new search? + + + + + BiblesPlugin + + + &Bible + + + + + <strong>Bible Plugin</strong><br />The Bible plugin provides the ability to display bible verses from different sources during the service. + + + + + BiblesPlugin.BibleDB + + + Book not found + + + + + The book you requested could not be found in this bible. Please check your spelling and that this is a complete bible not just one testament. + + + + + BiblesPlugin.BibleManager + + + Scripture Reference Error + + + + + Your scripture reference is either not supported by OpenLP or is invalid. Please make sure your reference conforms to one of the following patterns: + +Book Chapter +Book Chapter-Chapter +Book Chapter:Verse-Verse +Book Chapter:Verse-Verse,Verse-Verse +Book Chapter:Verse-Verse,Chapter:Verse-Verse +Book Chapter:Verse-Chapter:Verse + + + + + + BiblesPlugin.BiblesTab + + + Bibles + + + + + Verse Display + + + + + Only show new chapter numbers + + + + + Layout style: + + + + + Display style: + + + + + Bible theme: + + + + + Verse Per Slide + + + + + Verse Per Line + + + + + Continuous + + + + + No Brackets + + + + + ( And ) + + + + + { And } + + + + + [ And ] + + + + + Note: +Changes do not affect verses already in the service. + + + + + Display dual Bible verses + + + + + BiblesPlugin.ImportWizardForm + + + Bible Import Wizard + + + + + Welcome to the Bible Import Wizard + + + + + This wizard will help you to import Bibles from a variety of formats. Click the next button below to start the process by selecting a format to import from. + + + + + Select Import Source + + + + + Select the import format, and where to import from. + + + + + Format: + + + + + OSIS + + + + + CSV + + + + + OpenSong + + + + + Web Download + + + + + File location: + + + + + Books location: + + + + + Verse location: + + + + + Bible filename: + + + + + Location: + 表示位置: + + + + Crosswalk + + + + + BibleGateway + + + + + Bible: + + + + + Download Options + + + + + Server: + + + + + Username: + + + + + Password: + + + + + Proxy Server (Optional) + + + + + License Details + + + + + Set up the Bible's license details. + + + + + Version name: + + + + + Copyright: + + + + + Permission: + + + + + Importing + + + + + Please wait while your Bible is imported. + + + + + Ready. + + + + + Invalid Bible Location + + + + + You need to specify a file to import your Bible from. + + + + + Invalid Books File + + + + + You need to specify a file with books of the Bible to use in the import. + + + + + Invalid Verse File + + + + + You need to specify a file of Bible verses to import. + + + + + Invalid OpenSong Bible + + + + + You need to specify an OpenSong Bible file to import. + + + + + Empty Version Name + + + + + You need to specify a version name for your Bible. + + + + + Empty Copyright + + + + + You need to set a copyright for your Bible. Bibles in the Public Domain need to be marked as such. + + + + + Bible Exists + + + + + This Bible already exists. Please import a different Bible or first delete the existing one. + + + + + Open OSIS File + + + + + Open Books CSV File + + + + + Open Verses CSV File + + + + + Open OpenSong Bible + + + + + Starting import... + + + + + Finished import. + + + + + Your Bible import failed. + + + + + BiblesPlugin.MediaItem + + + Bible + + + + + Quick + + + + + Advanced + + + + + Version: + + + + + Dual: + + + + + Search type: + + + + + Find: + + + + + Search + + + + + Results: + + + + + Book: + + + + + Chapter: + + + + + Verse: + + + + + From: + + + + + To: + + + + + Verse Search + + + + + Text Search + + + + + Clear + + + + + Keep + + + + + No Book Found + + + + + No matching book could be found in this Bible. + + + + + Bible not fully loaded. + + + + + BiblesPlugin.Opensong + + + Importing + + + + + CustomPlugin + + + <strong>Custom Plugin</strong><br />The custom plugin provides the ability to set up custom text slides that can be displayed on the screen the same way songs are. This plugin provides greater freedom over the songs plugin. + + + + + CustomPlugin.CustomTab + + + Custom + + + + + Custom Display + + + + + Display footer + + + + + CustomPlugin.EditCustomForm + + + Edit Custom Slides + + + + + Move slide up one position. + + + + + Move slide down one position. + + + + + &Title: + + + + + Add New + + + + + Add a new slide at bottom. + + + + + Edit + + + + + Edit the selected slide. + + + + + Edit All + + + + + Edit all the slides at once. + + + + + Save + 保存 + + + + Save the slide currently being edited. + + + + + Delete + + + + + Delete the selected slide. + + + + + Clear + 消去 + + + + Clear edit area + + + + + Split Slide + + + + + Split a slide into two by inserting a slide splitter. + + + + + The&me: + + + + + &Credits: + + + + + Save && Preview + + + + + Error + + + + + You need to type in a title. + + + + + You need to add at least one slide + + + + + You have one or more unsaved slides, please either save your slide(s) or clear your changes. + + + + + CustomPlugin.MediaItem + + + Custom + + + + + You haven't selected an item to edit. + + + + + You haven't selected an item to delete. + + + + + ImagePlugin + + + <strong>Image Plugin</strong><br />The image plugin provides displaying of images.<br />One of the distinguishing features of this plugin is the ability to group a number of images together in the service manager, making the displaying of multiple images easier. This plugin can also make use of OpenLP's "timed looping" feature to create a slide show that runs automatically. In addition to this, images from the plugin can be used to override the current theme's background, which renders text-based items like songs with the selected image as a background instead of the background provided by the theme. + + + + + ImagePlugin.MediaItem + + + Image + + + + + Select Image(s) + + + + + All Files + + + + + Replace Live Background + + + + + Replace Background + + + + + Reset Live Background + + + + + You must select an image to delete. + + + + + Image(s) + + + + + You must select an image to replace the background with. + + + + + You must select a media file to replace the background with. + + + + + MediaPlugin + + + <strong>Media Plugin</strong><br />The media plugin provides playback of audio and video. + + + + + MediaPlugin.MediaItem + + + Media + + + + + Select Media + + + + + Replace Live Background + + + + + Replace Background + + + + + You must select a media file to delete. + + + + + OpenLP + + + Image Files + + + + + OpenLP.AboutForm + + + About OpenLP + + + + + OpenLP <version><revision> - Open Source Lyrics Projection + +OpenLP is free church presentation software, or lyrics projection software, used to display slides of songs, Bible verses, videos, images, and even presentations (if OpenOffice.org, PowerPoint or PowerPoint Viewer is installed) for church worship using a computer and a data projector. + +Find out more about OpenLP: http://openlp.org/ + +OpenLP is written and maintained by volunteers. If you would like to see more free Christian software being written, please consider contributing by using the button below. + + + + + About + + + + + Project Lead + Raoul "superfly" Snyman + +Developers + Tim "TRB143" Bentley + Jonathan "gushie" Corwin + Michael "cocooncrash" Gorven + Scott "sguerrieri" Guerrieri + Raoul "superfly" Snyman + Martin "mijiti" Thompson + Jon "Meths" Tibble + +Contributors + Meinert "m2j" Jordan + Andreas "googol" Preikschat + Christian "crichter" Richter + Philip "Phill" Ridout + Maikel Stuivenberg + Carsten "catini" Tingaard + Frode "frodus" Woldsund + +Testers + Philip "Phill" Ridout + Wesley "wrst" Stout (lead) + +Packagers + Thomas "tabthorpe" Abthorpe (FreeBSD) + Tim "TRB143" Bentley (Fedora) + Michael "cocooncrash" Gorven (Ubuntu) + Matthias "matthub" Hub (Mac OS X) + Raoul "superfly" Snyman (Windows, Ubuntu) + +Built With + Python: http://www.python.org/ + Qt4: http://qt.nokia.com/ + PyQt4: http://www.riverbankcomputing.co.uk/software/pyqt/intro + Oxygen Icons: http://oxygen-icons.org/ + + + + + + Credits + + + + + Copyright © 2004-2010 Raoul Snyman +Portions copyright © 2004-2010 Tim Bentley, Jonathan Corwin, Michael Gorven, Scott Guerrieri, Christian Richter, Maikel Stuivenberg, Martin Thompson, Jon Tibble, Carsten Tinggaard + +This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See below for more details. + + +GNU GENERAL PUBLIC LICENSE +Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + +Preamble + +The licenses for most software are designed to take away your 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 the Free Software Foundation's software and to any other 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 programs, too. + +When we speak of free software, we are referring to freedom, not 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 the software or use pieces of it in new free programs; and that you know you can do these things. + +To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. + +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 they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. + +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 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. + +Finally, any free program is threatened constantly by software 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 it clear that any patent must be licensed for everyone's free use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification follow. + +GNU GENERAL PUBLIC LICENSE +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +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. 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. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are 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 running the Program). Whether that is true depends on what the Program does. + +1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: + +a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. + +b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. + +c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a 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 interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) + +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 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 whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. + +3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: + +a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, + +b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, + +c) Accompany it with the information you received as to the 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.) + +The source code for a work means the preferred form of the 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 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 on which the executable runs, unless that component itself accompanies the executable. + +If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. + +4. You may not copy, modify, sublicense, or distribute the 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, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. + +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 actions are prohibited by law if you do not accept this 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 it. + +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 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. + +7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to 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 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 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 would be to refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe 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 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 author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. + +This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted 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 case, this License incorporates the limitation as if written in the body of this License. + +9. The Free Software Foundation may publish revised and/or new 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. + +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 following the terms and conditions either of that version or of 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. + +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 copyrighted by the Free Software Foundation, write to the Free 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. + +NO WARRANTY + +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 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 PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. + +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 the full notice is found. + +<one line to give the program's name and a brief idea of what it does.> +Copyright (C) <year> <name of author> + +This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this when it starts in an interactive mode: + +Gnomovision version 69, Copyright (C) year name of author +Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type "show w". +This is free software, and you are welcome to redistribute it under certain conditions; type "show c" for details. + +The hypothetical commands "show w" and "show c" should show 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. + +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: + +Yoyodyne, Inc., hereby disclaims all copyright interest in the program "Gnomovision" (which makes passes at compilers) written by James Hacker. + +<signature of Ty Coon>, 1 April 1989 +Ty Coon, President of Vice + +This General Public License does not permit incorporating your 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 what you want to do, use the GNU Lesser General Public License instead of this License. + + + + + License + + + + + Contribute + + + + + Close + + + + + build %s + + + + + OpenLP.AdvancedTab + + + Advanced + + + + + UI Settings + + + + + Number of recent files to display: + + + + + Remember active media manager tab on startup + + + + + Double-click to send items straight to live (requires restart) + + + + + OpenLP.AmendThemeForm + + + Theme Maintenance + + + + + Theme &name: + + + + + Type: + + + + + Solid Color + + + + + Gradient + + + + + Image + + + + + Image: + + + + + Gradient: + + + + + Horizontal + + + + + Vertical + + + + + Circular + + + + + &Background + + + + + Main Font + + + + + Font: + + + + + Color: + + + + + Size: + + + + + pt + + + + + Adjust line spacing: + + + + + Normal + + + + + Bold + + + + + Italics + + + + + Bold/Italics + + + + + Style: + + + + + Display Location + + + + + Use default location + + + + + X position: + + + + + Y position: + + + + + Width: + + + + + Height: + + + + + px + + + + + &Main Font + + + + + Footer Font + + + + + &Footer Font + + + + + Outline + + + + + Outline size: + + + + + Outline color: + + + + + Show outline: + + + + + Shadow + + + + + Shadow size: + + + + + Shadow color: + + + + + Show shadow: + + + + + Alignment + + + + + Horizontal align: + + + + + Left + + + + + Right + + + + + Center + + + + + Vertical align: + + + + + Top + + + + + Middle + + + + + Bottom + + + + + Slide Transition + + + + + Transition active + + + + + &Other Options + + + + + Preview + + + + + All Files + + + + + Select Image + + + + + First color: + + + + + Second color: + + + + + Slide height is %s rows. + + + + + OpenLP.ExceptionDialog + + + Error Occurred + + + + + Oops! OpenLP hit a problem, and couldn't recover. The text in the box below contains information that might be helpful to the OpenLP developers, so please e-mail it to bugs@openlp.org, along with a detailed description of what you were doing when the problem occurred. + + + + + OpenLP.GeneralTab + + + General + + + + + Monitors + + + + + Select monitor for output display: + + + + + Display if a single screen + + + + + Application Startup + + + + + Show blank screen warning + + + + + Automatically open the last service + + + + + Show the splash screen + + + + + Application Settings + + + + + Prompt to save before starting a new service + + + + + Automatically preview next item in service + + + + + Slide loop delay: + + + + + sec + + + + + CCLI Details + + + + + CCLI number: + + + + + SongSelect username: + + + + + SongSelect password: + + + + + Display Position + + + + + X + + + + + Y + + + + + Height + + + + + Width + + + + + Override display position + + + + + Screen + + + + + primary + + + + + OpenLP.LanguageManager + + + Language + 言語 + + + + Please restart OpenLP to use your new language setting. + + + + + OpenLP.MainWindow + + + OpenLP 2.0 + OpenLP 2.0 + + + + &File + ファイル(&F) + + + + &Import + インポート(&I) + + + + &Export + エクスポート(&E) + + + + &View + 表示(&V) + + + + M&ode + モード(&O) + + + + &Tools + ツール(&T) + + + + &Settings + 設定(&S) + + + + &Language + 言語(&L) + + + + &Help + ヘルプ(&H) + + + + Media Manager + + + + + Service Manager + + + + + Theme Manager + + + + + &New + 新規作成(&N) + + + + New Service + + + + + Create a new service. + + + + + Ctrl+N + + + + + &Open + 開く(&O) + + + + Open Service + + + + + Open an existing service. + + + + + Ctrl+O + + + + + &Save + 保存(&S) + + + + Save Service + + + + + Save the current service to disk. + + + + + Ctrl+S + + + + + Save &As... + 名前を付けて保存(&A)... + + + + Save Service As + + + + + Save the current service under a new name. + + + + + Ctrl+Shift+S + + + + + E&xit + 終了(&X) + + + + Quit OpenLP + + + + + Alt+F4 + + + + + &Theme + テーマ(&T) + + + + &Configure OpenLP... + OpenLPの設定(&C)... + + + + &Media Manager + + + + + Toggle Media Manager + + + + + Toggle the visibility of the media manager. + + + + + F8 + + + + + &Theme Manager + + + + + Toggle Theme Manager + + + + + Toggle the visibility of the theme manager. + + + + + F10 + + + + + &Service Manager + + + + + Toggle Service Manager + + + + + Toggle the visibility of the service manager. + + + + + F9 + + + + + &Preview Panel + + + + + Toggle Preview Panel + + + + + Toggle the visibility of the preview panel. + + + + + F11 + + + + + &Live Panel + + + + + Toggle Live Panel + + + + + Toggle the visibility of the live panel. + + + + + F12 + + + + + &Plugin List + プラグイン一覧(&P) + + + + List the Plugins + + + + + Alt+F7 + + + + + &User Guide + ユーザガイド(&U) + + + + &About + + + + + More information about OpenLP + + + + + Ctrl+F1 + + + + + &Online Help + オンラインヘルプ(&O) + + + + &Web Site + ウェブサイト(&W) + + + + &Auto Detect + 自動検出(&A) + + + + Use the system language, if available. + + + + + Set the interface language to %s + + + + + Add &Tool... + ツールの追加(&T)... + + + + Add an application to the list of tools. + + + + + &Default + デフォルト(&D) + + + + Set the view mode back to the default. + + + + + &Setup + 設定(&S) + + + + Set the view mode to Setup. + + + + + &Live + + + + + Set the view mode to Live. + + + + + Version %s of OpenLP is now available for download (you are currently running version %s). + +You can download the latest version from http://openlp.org/. + + + + + OpenLP Version Updated + + + + + OpenLP Main Display Blanked + + + + + The Main Display has been blanked out + + + + + Save Changes to Service? + + + + + Your service has changed. Do you want to save those changes? + + + + + Default Theme: %s + + + + + English + Please add the name of your language here + 日本語 + + + + OpenLP.MediaManagerItem + + + No Items Selected + + + + + Import %s + + + + + Import a %s + + + + + Load %s + + + + + Load a new %s + + + + + New %s + + + + + Add a new %s + + + + + Edit %s + + + + + Edit the selected %s + + + + + Delete %s + + + + + Delete the selected item + + + + + Preview %s + + + + + Preview the selected item + + + + + Send the selected item live + + + + + Add %s to Service + + + + + Add the selected item(s) to the service + + + + + &Edit %s + + + + + &Delete %s + + + + + &Preview %s + + + + + &Show Live + + + + + &Add to Service + + + + + &Add to selected Service Item + + + + + You must select one or more items to preview. + + + + + You must select one or more items to send live. + + + + + You must select one or more items. + + + + + No items selected + + + + + You must select one or more items + + + + + No Service Item Selected + + + + + You must select an existing service item to add to. + + + + + Invalid Service Item + + + + + You must select a %s service item. + + + + + OpenLP.PluginForm + + + Plugin List + プラグイン一覧 + + + + Plugin Details + プラグイン詳細 + + + + Version: + バージョン: + + + + About: + + + + + Status: + 状況: + + + + Active + 有効 + + + + Inactive + 無効 + + + + %s (Inactive) + %s (無効) + + + + %s (Active) + %s (有効) + + + + %s (Disabled) + + + + + OpenLP.ServiceItemEditForm + + + Reorder Service Item + + + + + Up + + + + + Delete + + + + + Down + + + + + OpenLP.ServiceManager + + + New Service + + + + + Create a new service + + + + + Open Service + + + + + Load an existing service + + + + + Save Service + + + + + Save this service + + + + + Theme: + + + + + Select a theme for the service + + + + + Move to &top + + + + + Move item to the top of the service. + + + + + Move &up + + + + + Move item up one position in the service. + + + + + Move &down + + + + + Move item down one position in the service. + + + + + Move to &bottom + + + + + Move item to the end of the service. + + + + + &Delete From Service + + + + + Delete the selected item from the service. + + + + + &Add New Item + + + + + &Add to Selected Item + + + + + &Edit Item + + + + + &Reorder Item + + + + + &Notes + + + + + &Preview Verse + + + + + &Live Verse + + + + + &Change Item Theme + + + + + Save Changes to Service? + + + + + Your service is unsaved, do you want to save those changes before creating a new one? + + + + + OpenLP Service Files (*.osz) + + + + + Your current service is unsaved, do you want to save the changes before opening a new one? + + + + + Error + + + + + File is not a valid service. +The content encoding is not UTF-8. + + + + + File is not a valid service. + + + + + Missing Display Handler + + + + + Your item cannot be displayed as there is no handler to display it + + + + + Your item cannot be displayed as the plugin required to display it is missing or inactive + + + + + OpenLP.ServiceNoteForm + + + Service Item Notes + + + + + OpenLP.SettingsForm + + + Configure OpenLP + OpenLPの設定 + + + + OpenLP.SlideController + + + Live + + + + + Preview + + + + + Move to previous + + + + + Move to next + + + + + Hide + + + + + Move to live + + + + + Edit and reload song preview + + + + + Start continuous loop + + + + + Stop continuous loop + + + + + s + + + + + Delay between slides in seconds + + + + + Start playing media + + + + + Go To + + + + + OpenLP.SpellTextEdit + + + Spelling Suggestions + + + + + Formatting Tags + + + + + OpenLP.ThemeManager + + + New Theme + + + + + Create a new theme. + + + + + Edit Theme + + + + + Edit a theme. + + + + + Delete Theme + + + + + Delete a theme. + + + + + Import Theme + + + + + Import a theme. + + + + + Export Theme + + + + + Export a theme. + + + + + &Edit Theme + + + + + &Delete Theme + + + + + Set As &Global Default + + + + + E&xport Theme + + + + + %s (default) + + + + + You must select a theme to edit. + + + + + You must select a theme to delete. + + + + + Delete Confirmation + + + + + Delete theme? + + + + + Error + + + + + You are unable to delete the default theme. + + + + + Theme %s is used in the %s plugin. + + + + + Theme %s is used by the service manager. + + + + + You have not selected a theme. + + + + + Save Theme - (%s) + + + + + Theme Exported + + + + + Your theme has been successfully exported. + + + + + Theme Export Failed + + + + + Your theme could not be exported due to an error. + + + + + Select Theme Import File + + + + + Theme (*.*) + + + + + File is not a valid theme. +The content encoding is not UTF-8. + + + + + File is not a valid theme. + + + + + Theme Exists + + + + + A theme with this name already exists. Would you like to overwrite it? + + + + + OpenLP.ThemesTab + + + Themes + + + + + Global Theme + + + + + Theme Level + + + + + S&ong Level + + + + + Use the theme from each song in the database. If a song doesn't have a theme associated with it, then use the service's theme. If the service doesn't have a theme, then use the global theme. + + + + + &Service Level + + + + + Use the theme from the service, overriding any of the individual songs' themes. If the service doesn't have a theme, then use the global theme. + + + + + &Global Level + + + + + Use the global theme, overriding any themes associated with either the service or the songs. + + + + + PresentationPlugin + + + <strong>Presentation Plugin</strong><br />The presentation plugin provides the ability to show presentations using a number of different programs. The choice of available presentation programs is available to the user in a drop down box. + + + + + PresentationPlugin.MediaItem + + + Presentation + + + + + Select Presentation(s) + + + + + Automatic + + + + + Present using: + + + + + File Exists + + + + + A presentation with that filename already exists. + + + + + Unsupported File + + + + + This type of presentation is not supported. + + + + + You must select an item to delete. + + + + + PresentationPlugin.PresentationTab + + + Presentations + + + + + Available Controllers + + + + + Advanced + + + + + Allow presentation application to be overriden + + + + + RemotePlugin + + + <strong>Remote Plugin</strong><br />The remote plugin provides the ability to send messages to a running version of OpenLP on a different computer via a web browser or through the remote API. + + + + + RemotePlugin.RemoteTab + + + Remotes + + + + + Serve on IP address: + + + + + Port number: + + + + + Server Settings + + + + + SongUsagePlugin + + + &Song Usage Tracking + + + + + &Delete Tracking Data + + + + + Delete song usage data up to a specified date. + + + + + &Extract Tracking Data + + + + + Generate a report on song usage. + + + + + Toggle Tracking + + + + + Toggle the tracking of song usage. + + + + + <strong>SongUsage Plugin</strong><br />This plugin tracks the usage of songs in services. + + + + + SongUsagePlugin.SongUsageDeleteForm + + + Delete Song Usage Data + + + + + Delete Selected Song Usage Events? + + + + + Are you sure you want to delete selected Song Usage data? + + + + + SongUsagePlugin.SongUsageDetailForm + + + Song Usage Extraction + + + + + Select Date Range + + + + + to + + + + + Report Location + + + + + Output File Location + + + + + SongsPlugin + + + &Song + + + + + Import songs using the import wizard. + + + + + <strong>Songs Plugin</strong><br />The songs plugin provides the ability to display and manage songs. + + + + + SongsPlugin.AuthorsForm + + + Author Maintenance + + + + + Display name: + + + + + First name: + + + + + Last name: + + + + + Error + + + + + You need to type in the first name of the author. + + + + + You need to type in the last name of the author. + + + + + You have not set a display name for the author, combine the first and last names? + + + + + SongsPlugin.EditSongForm + + + Song Editor + + + + + &Title: + + + + + Alt&ernate title: + + + + + &Lyrics: + + + + + &Verse order: + + + + + &Add + + + + + &Edit + + + + + Ed&it All + + + + + &Delete + 削除(&D) + + + + Title && Lyrics + + + + + Authors + + + + + &Add to Song + + + + + &Remove + + + + + &Manage Authors, Topics, Song Books + + + + + Topic + + + + + A&dd to Song + + + + + R&emove + + + + + Song Book + + + + + Book: + + + + + Number: + + + + + Authors, Topics && Song Book + + + + + Theme + + + + + New &Theme + + + + + Copyright Information + + + + + © + + + + + CCLI number: + + + + + Comments + + + + + Theme, Copyright Info && Comments + + + + + Save && Preview + + + + + Add Author + + + + + This author does not exist, do you want to add them? + + + + + Error + + + + + This author is already in the list. + + + + + No Author Selected + + + + + You have not selected a valid author. Either select an author from the list, or type in a new author and click the "Add Author to Song" button to add the new author. + + + + + Add Topic + + + + + This topic does not exist, do you want to add it? + + + + + This topic is already in the list. + + + + + No Topic Selected + + + + + You have not selected a valid topic. Either select a topic from the list, or type in a new topic and click the "Add Topic to Song" button to add the new topic. + + + + + You need to type in a song title. + + + + + You need to type in at least one verse. + + + + + Warning + + + + + You have not added any authors for this song. Do you want to add an author now? + + + + + The verse order is invalid. There is no verse corresponding to %s. Valid entries are %s. + + + + + You have not used %s anywhere in the verse order. Are you sure you want to save the song like this? + + + + + Add Book + + + + + This song book does not exist, do you want to add it? + + + + + SongsPlugin.EditVerseForm + + + Edit Verse + + + + + &Verse type: + + + + + &Insert + + + + + SongsPlugin.ImportWizardForm + + + No OpenLP 2.0 Song Database Selected + + + + + You need to select an OpenLP 2.0 song database file to import from. + + + + + No openlp.org 1.x Song Database Selected + + + + + You need to select an openlp.org 1.x song database file to import from. + + + + + No OpenSong Files Selected + + + + + You need to add at least one OpenSong song file to import from. + + + + + No Words of Worship Files Selected + + + + + You need to add at least one Words of Worship file to import from. + + + + + No CCLI Files Selected + + + + + You need to add at least one CCLI file to import from. + + + + + No Songs of Fellowship File Selected + + + + + You need to add at least one Songs of Fellowship file to import from. + + + + + No Document/Presentation Selected + + + + + You need to add at least one document or presentation file to import from. + + + + + Select OpenLP 2.0 Database File + + + + + Select openlp.org 1.x Database File + + + + + Select Open Song Files + + + + + Select Words of Worship Files + + + + + Select CCLI Files + + + + + Select Songs of Fellowship Files + + + + + Select Document/Presentation Files + + + + + Starting import... + + + + + Song Import Wizard + + + + + Welcome to the Song Import Wizard + + + + + This wizard will help you to import songs from a variety of formats. Click the next button below to start the process by selecting a format to import from. + + + + + Select Import Source + + + + + Select the import format, and where to import from. + + + + + Format: + + + + + OpenLP 2.0 + OpenLP 2.0 + + + + openlp.org 1.x + + + + + OpenLyrics + + + + + OpenSong + + + + + Words of Worship + + + + + CCLI/SongSelect + + + + + Songs of Fellowship + + + + + Generic Document/Presentation + + + + + Filename: + + + + + Browse... + + + + + 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. + + + + + 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. + + + + + Add Files... + + + + + Remove File(s) + + + + + The Songs of Fellowship importer has been disabled because OpenLP cannot find OpenOffice.org on your computer. + + + + + The generic document/presentation importer has been disabled because OpenLP cannot find OpenOffice.org on your computer. + + + + + Importing + + + + + Please wait while your songs are imported. + + + + + Ready. + + + + + %p% + + + + + Importing "%s"... + + + + + Importing %s... + + + + + No EasyWorship Song Database Selected + + + + + You need to select an EasyWorship song database file to import from. + + + + + Select EasyWorship Database File + + + + + EasyWorship + + + + + Administered by %s + + + + + SongsPlugin.MediaItem + + + Song + + + + + Song Maintenance + + + + + Maintain the lists of authors, topics and books + + + + + Search: + + + + + Type: + + + + + Clear + + + + + Search + + + + + Titles + + + + + Lyrics + + + + + Authors + + + + + You must select an item to edit. + + + + + You must select an item to delete. + + + + + Are you sure you want to delete the selected song? + + + + + Are you sure you want to delete the %d selected songs? + + + + + Delete Song(s)? + + + + + CCLI Licence: + + + + + SongsPlugin.SongBookForm + + + Song Book Maintenance + + + + + &Name: + + + + + &Publisher: + + + + + Error + + + + + You need to type in a name for the book. + + + + + SongsPlugin.SongImport + + + copyright + + + + + © + + + + + SongsPlugin.SongImportForm + + + Finished import. + + + + + Your song import failed. + + + + + SongsPlugin.SongMaintenanceForm + + + Song Maintenance + + + + + Authors + + + + + Topics + + + + + Song Books + + + + + &Add + + + + + &Edit + + + + + &Delete + 削除(&D) + + + + Error + + + + + Could not add your author. + + + + + This author already exists. + + + + + Could not add your topic. + + + + + This topic already exists. + + + + + Could not add your book. + + + + + This book already exists. + + + + + Could not save your changes. + + + + + Could not save your modified author, because the author already exists. + + + + + Could not save your modified topic, because it already exists. + + + + + Delete Author + + + + + Are you sure you want to delete the selected author? + + + + + This author cannot be deleted, they are currently assigned to at least one song. + + + + + No author selected! + + + + + Delete Topic + + + + + Are you sure you want to delete the selected topic? + + + + + This topic cannot be deleted, it is currently assigned to at least one song. + + + + + No topic selected! + + + + + Delete Book + + + + + Are you sure you want to delete the selected book? + + + + + This book cannot be deleted, it is currently assigned to at least one song. + + + + + No book selected! + + + + + SongsPlugin.SongsTab + + + Songs + + + + + Songs Mode + + + + + Enable search as you type + + + + + Display verses on live tool bar + + + + + SongsPlugin.TopicsForm + + + Topic Maintenance + + + + + Topic name: + + + + + Error + + + + + You need to type in a topic name. + + + + + SongsPlugin.VerseType + + + Verse + + + + + Chorus + + + + + Bridge + + + + + Pre-Chorus + + + + + Intro + + + + + Ending + + + + + Other + その他 + + + diff --git a/scripts/translation_utils.py b/scripts/translation_utils.py index 6d8997677..424e977bd 100755 --- a/scripts/translation_utils.py +++ b/scripts/translation_utils.py @@ -86,7 +86,9 @@ class CommandStack(object): return len(self.data) def __getitem__(self, index): - if self.data[index].get(u'arguments'): + if not index in self.data: + return None + elif self.data[index].get(u'arguments'): return self.data[index][u'command'], self.data[index][u'arguments'] else: return self.data[index][u'command'] @@ -111,6 +113,21 @@ class CommandStack(object): def reset(self): self.current_index = 0 + def arguments(self): + if self.data[self.current_index - 1].get(u'arguments'): + return self.data[self.current_index - 1][u'arguments'] + else: + return [] + + def __repr__(self): + results = [] + for item in self.data: + if item.get(u'arguments'): + results.append(str((item[u'command'], item[u'arguments']))) + else: + results.append(str((item[u'command'], ))) + return u'[%s]' % u', '.join(results) + def print_verbose(text): """ @@ -140,6 +157,21 @@ def run(command): print_verbose(u'Output:\n%s' % process.readAllStandardOutput()) print u' Done.' +def update_export_at_pootle(source_filename): + """ + This is needed because of database and exported *.ts file can be out of sync + + ``source_filename`` + The file to sync. + + """ + language = source_filename[:-3] + REVIEW_URL = u'http://pootle.projecthq.biz/%s/openlp/review.html' % language + print_verbose(u'Accessing: %s' % (REVIEW_URL)) + page = urllib.urlopen(REVIEW_URL) + page.close() + + def download_file(source_filename, dest_filename): """ Download a file and save it to disk. @@ -166,11 +198,13 @@ def download_translations(): page = urllib.urlopen(SERVER_URL) soup = BeautifulSoup(page) languages = soup.findAll(text=re.compile(r'.*\.ts')) - for language in languages: + for language_file in languages: + update_export_at_pootle(language_file) + for language_file in languages: filename = os.path.join(os.path.abspath(u'..'), u'resources', u'i18n', - language) + language_file) print_verbose(u'Get Translation File: %s' % filename) - download_file(language, filename) + download_file(language_file, filename) print u' Done.' def prepare_project(): @@ -233,7 +267,7 @@ def update_translations(): def generate_binaries(): print u'Generate the related *.qm files' if not os.path.exists(os.path.join(os.path.abspath(u'..'), u'openlp.pro')): - print u'You have no generated a project file yet, please run this ' + \ + print u'You have not generated a project file yet, please run this ' + \ u'script with the -p option. It is also recommended that you ' + \ u'this script with the -u option to update the translation ' + \ u'files as well.' @@ -261,13 +295,15 @@ def create_translation(language): The language file to create. """ print "Create new Translation File" + if not language.endswith(u'.ts'): + language += u'.ts' filename = os.path.join(os.path.abspath(u'..'), u'resources', u'i18n', language) download_file(u'en.ts', filename) - print u'\n** Please Note **\n' - print u'In order to get this file into OpenLP and onto the Pootle ' + \ + print u' ** Please Note **' + print u' In order to get this file into OpenLP and onto the Pootle ' + \ u'translation server you will need to subscribe to the OpenLP' + \ u'Translators mailing list, and request that your language file ' + \ - u'be added to the project.\n' + u'be added to the project.' print u' Done' def process_stack(command_stack): @@ -291,7 +327,7 @@ def process_stack(command_stack): elif command == Command.Generate: generate_binaries() elif command == Command.Create: - command, arguments = command_stack[command_stack.current_index] + arguments = command_stack.arguments() create_translation(*arguments) print u'Finished processing commands.' else: @@ -306,7 +342,7 @@ def main(): parser = OptionParser(usage=usage) parser.add_option('-d', '--download-ts', dest='download', action='store_true', help='download language files from Pootle') - parser.add_option('-c', '--create', dest=u'create', metavar='LANG', + parser.add_option('-c', '--create', dest='create', metavar='LANG', help='create a new translation file for language LANG, e.g. "en_GB"') parser.add_option('-p', '--prepare', dest='prepare', action='store_true', help='generate a project file, used to update the translations') @@ -343,4 +379,3 @@ if __name__ == u'__main__': print u'You need to run this script from the scripts directory.' else: main() - diff --git a/scripts/windows-builder.py b/scripts/windows-builder.py index a100dfd5a..d34b77249 100644 --- a/scripts/windows-builder.py +++ b/scripts/windows-builder.py @@ -179,7 +179,16 @@ def copy_windows_files(): copy(os.path.join(iss_path, u'OpenLP.ico'), os.path.join(dist_path, u'OpenLP.ico')) copy(os.path.join(iss_path, u'LICENSE.txt'), os.path.join(dist_path, u'LICENSE.txt')) +def update_translations(): + print u'Updating translations...' + os.chdir(script_path) + translation_utils = Popen(u'python translation_utils.py -dpu') + code = translation_utils.wait() + if code != 0: + print u'Error running translation_utils.py' + def compile_translations(): + print u'Compiling translations...' files = os.listdir(i18n_path) if not os.path.exists(os.path.join(dist_path, u'i18n')): os.makedirs(os.path.join(dist_path, u'i18n')) @@ -221,6 +230,7 @@ def main(): copy_enchant() copy_plugins() copy_windows_files() + update_translations() compile_translations() run_innosetup() print "Done."