diff --git a/copyright.txt b/copyright.txt index c99a64287..6882a7282 100644 --- a/copyright.txt +++ b/copyright.txt @@ -4,8 +4,8 @@ ############################################################################### # OpenLP - Open Source Lyrics Projection # # --------------------------------------------------------------------------- # -# Copyright (c) 2008-2010 Raoul Snyman # -# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Copyright (c) 2008-2011 Raoul Snyman # +# Portions copyright (c) 2008-2011 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 # diff --git a/documentation/api/source/core/theme.rst b/documentation/api/source/core/theme.rst index 3dbc7a6ec..3621c6581 100644 --- a/documentation/api/source/core/theme.rst +++ b/documentation/api/source/core/theme.rst @@ -1,8 +1,10 @@ .. _core-theme: -:mod:`theme` Module -=================== +Theme Function Library +====================== .. automodule:: openlp.core.theme :members: +.. autoclass:: openlp.core.theme.theme.Theme + :members: diff --git a/documentation/api/source/openlp.rst b/documentation/api/source/openlp.rst deleted file mode 100644 index 76a1a2098..000000000 --- a/documentation/api/source/openlp.rst +++ /dev/null @@ -1,7 +0,0 @@ -.. _openlp: - -:mod:`openlp` Module -==================== - -.. automodule:: openlp - :members: diff --git a/documentation/api/source/plugins/bibles.rst b/documentation/api/source/plugins/bibles.rst index 67162d414..c89f9c6ae 100644 --- a/documentation/api/source/plugins/bibles.rst +++ b/documentation/api/source/plugins/bibles.rst @@ -18,7 +18,7 @@ Forms .. automodule:: openlp.plugins.bibles.forms :members: -.. autoclass:: openlp.plugins.bibles.forms.importwizardform.ImportWizardForm +.. autoclass:: openlp.plugins.bibles.forms.bibleimportform.BibleImportForm :members: Helper Classes & Functions diff --git a/documentation/manual/source/conf.py b/documentation/manual/source/conf.py index 517fc2f44..f0d918c11 100644 --- a/documentation/manual/source/conf.py +++ b/documentation/manual/source/conf.py @@ -48,7 +48,7 @@ copyright = u'2004-2010 Raoul Snyman' # The short X.Y version. version = '2.0' # The full version, including alpha/beta/rc tags. -release = '1.9.3' +release = '1.9.5' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -92,22 +92,24 @@ pygments_style = 'sphinx' # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. +#html_theme = 'openlp_qthelp' html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -html_theme_options = { - 'sidebarbgcolor': '#3a60a9', - 'relbarbgcolor': '#203b6f', - 'footerbgcolor': '#26437c', - 'headtextcolor': '#203b6f', - 'linkcolor': '#26437c', - 'sidebarlinkcolor': '#ceceff' -} +if html_theme == 'default': + html_theme_options = { + 'sidebarbgcolor': '#3a60a9', + 'relbarbgcolor': '#203b6f', + 'footerbgcolor': '#26437c', + 'headtextcolor': '#203b6f', + 'linkcolor': '#26437c', + 'sidebarlinkcolor': '#ceceff' + } # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +html_theme_path = [u'../themes'] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". diff --git a/documentation/manual/source/dualmonitors.rst b/documentation/manual/source/dualmonitors.rst index 7e5fdc19b..ee4bc90a2 100644 --- a/documentation/manual/source/dualmonitors.rst +++ b/documentation/manual/source/dualmonitors.rst @@ -149,15 +149,15 @@ Or, as root:: root@linux: # nividia-settings If you do not want to write the changes to your ``xorg.conf`` file simply run -the nVidia Settings program (``nvidia-settings``) from your desktop's menu, -usually in an administration or system menu, or from the terminal as a normal -user run:: +the nVidia Settings program (:command:`nvidia-settings`) from your desktop's +menu, usually in an administration or system menu, or from the terminal as a +normal user run:: user@linux:~ $ nvidia-settings -Once you have opened nVidia Settings, click on -:guilabel:`X Server Display Configuration`. Then select the monitor you are -wanting to use as your second monitor and click :guilabel:`Configure`. +Once you have opened nVidia Settings, click on :guilabel:`X Server Display +Configuration`. Then select the monitor you are wanting to use as your second +monitor and click :guilabel:`Configure`. .. image:: pics/nvlinux1.png diff --git a/documentation/manual/source/index.rst b/documentation/manual/source/index.rst index 5786af1ae..378cfe2a0 100644 --- a/documentation/manual/source/index.rst +++ b/documentation/manual/source/index.rst @@ -17,10 +17,3 @@ Contents: mediamanager songs -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - diff --git a/documentation/manual/themes/openlp_qthelp/layout.html b/documentation/manual/themes/openlp_qthelp/layout.html new file mode 100644 index 000000000..d16116773 --- /dev/null +++ b/documentation/manual/themes/openlp_qthelp/layout.html @@ -0,0 +1,68 @@ +{# + openlp_qthelp/layout.html + ~~~~~~~~~~~~~~~~~ + + Sphinx layout template for the openlp_qthelp theme. + + :copyright: Copyright 2004-2010 Raoul Snyman. + :license: GPL +#} +{% extends "basic/layout.html" %} +{% set script_files = script_files + ['_static/theme_extras.js'] %} +{% set css_files = css_files + ['_static/print.css'] %} + +{# do not display relbars #} +{% block relbar1 %}{% endblock %} +{% block relbar2 %}{% endblock %} + +{% macro nav() %} +

+ {%- block openlp_qthelprel1 %} + {%- endblock %} + {%- if prev %} + «  {{ prev.title }} +   ::   + {%- endif %} + {{ _('Contents') }} + {%- if next %} +   ::   + {{ next.title }}  » + {%- endif %} + {%- block openlp_qthelprel2 %} + {%- endblock %} +

+{% endmacro %} + +{% block content %} + +
+ {{ nav() }} +
+
+ {#{%- if display_toc %} +
+

Table Of Contents

+ {{ toc }} +
+ {%- endif %}#} + {% block body %}{% endblock %} +
+
+ {{ nav() }} +
+{% endblock %} diff --git a/documentation/manual/themes/openlp_qthelp/static/alert_info_32.png b/documentation/manual/themes/openlp_qthelp/static/alert_info_32.png new file mode 100644 index 000000000..05b4fe898 Binary files /dev/null and b/documentation/manual/themes/openlp_qthelp/static/alert_info_32.png differ diff --git a/documentation/manual/themes/openlp_qthelp/static/alert_warning_32.png b/documentation/manual/themes/openlp_qthelp/static/alert_warning_32.png new file mode 100644 index 000000000..f13611cde Binary files /dev/null and b/documentation/manual/themes/openlp_qthelp/static/alert_warning_32.png differ diff --git a/documentation/manual/themes/openlp_qthelp/static/bg-page.png b/documentation/manual/themes/openlp_qthelp/static/bg-page.png new file mode 100644 index 000000000..c6f3bc477 Binary files /dev/null and b/documentation/manual/themes/openlp_qthelp/static/bg-page.png differ diff --git a/documentation/manual/themes/openlp_qthelp/static/bullet_orange.png b/documentation/manual/themes/openlp_qthelp/static/bullet_orange.png new file mode 100644 index 000000000..ad5d02f34 Binary files /dev/null and b/documentation/manual/themes/openlp_qthelp/static/bullet_orange.png differ diff --git a/documentation/manual/themes/openlp_qthelp/static/openlp_qthelp.css_t b/documentation/manual/themes/openlp_qthelp/static/openlp_qthelp.css_t new file mode 100644 index 000000000..918ed661b --- /dev/null +++ b/documentation/manual/themes/openlp_qthelp/static/openlp_qthelp.css_t @@ -0,0 +1,372 @@ +/* + * openlp_qthelp.css_t + * ~~~~~~~~~~~ + * + * Sphinx stylesheet -- openlp_qthelp theme. + * + * Adapted from http://openlp_qthelp-os.org/docs/Haiku-doc.css. + * Original copyright message: + * + * Copyright 2008-2009, Haiku. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Francois Revol + * Stephan Assmus + * Braden Ewing + * Humdinger + * + * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +@import url("basic.css"); + +html { + margin: 0px; + padding: 0px; + background-color: #fff; + background-image: none; +} + +body { + line-height: 1.5; + margin: auto; + padding: 0px; + font-family: "DejaVu Sans", Arial, Helvetica, sans-serif; + min-width: 59em; + max-width: 70em; + color: {{ theme_textcolor }}; +} + +div.footer { + padding: 8px; + font-size: 11px; + text-align: center; + letter-spacing: 0.5px; +} + +/* link colors and text decoration */ + +a:link { + font-weight: bold; + text-decoration: none; + color: {{ theme_linkcolor }}; +} + +a:visited { + font-weight: bold; + text-decoration: none; + color: {{ theme_visitedlinkcolor }}; +} + +a:hover, a:active { + text-decoration: underline; + color: {{ theme_hoverlinkcolor }}; +} + +/* Some headers act as anchors, don't give them a hover effect */ + +h1 a:hover, a:active { + text-decoration: none; + color: {{ theme_headingcolor }}; +} + +h2 a:hover, a:active { + text-decoration: none; + color: {{ theme_headingcolor }}; +} + +h3 a:hover, a:active { + text-decoration: none; + color: {{ theme_headingcolor }}; +} + +h4 a:hover, a:active { + text-decoration: none; + color: {{ theme_headingcolor }}; +} + +a.headerlink { + color: #a7ce38; + padding-left: 5px; +} + +a.headerlink:hover { + color: #a7ce38; +} + +/* basic text elements */ + +div.content { + margin-top: 20px; + margin-left: 40px; + margin-right: 40px; + margin-bottom: 50px; + font-size: 0.9em; +} + +/* heading and navigation */ + +div.header { + position: relative; + left: 0px; + top: 0px; + height: 85px; + /* background: #eeeeee; */ + padding: 0 40px; +} +div.header h1 { + font-size: 1.6em; + font-weight: normal; + letter-spacing: 1px; + color: {{ theme_headingcolor }}; + border: 0; + margin: 0; + padding-top: 15px; +} +div.header h1 a { + font-weight: normal; + color: {{ theme_headingcolor }}; +} +div.header h2 { + font-size: 1.3em; + font-weight: normal; + letter-spacing: 1px; + text-transform: uppercase; + color: #aaa; + border: 0; + margin-top: -3px; + padding: 0; +} + +div.header img.rightlogo { + float: right; +} + + +div.title { + font-size: 1.3em; + font-weight: bold; + color: {{ theme_headingcolor }}; + border-bottom: dotted thin #e0e0e0; + margin-bottom: 25px; +} +div.topnav { + /* background: #e0e0e0; */ +} +div.topnav p { + margin-top: 0; + margin-left: 40px; + margin-right: 40px; + margin-bottom: 0px; + text-align: right; + font-size: 0.8em; +} +div.bottomnav { + background: #eeeeee; +} +div.bottomnav p { + margin-right: 40px; + text-align: right; + font-size: 0.8em; +} + +a.uplink { + font-weight: normal; +} + + +/* contents box */ + +table.index { + margin: 0px 0px 30px 30px; + padding: 1px; + border-width: 1px; + border-style: dotted; + border-color: #e0e0e0; +} +table.index tr.heading { + background-color: #e0e0e0; + text-align: center; + font-weight: bold; + font-size: 1.1em; +} +table.index tr.index { + background-color: #eeeeee; +} +table.index td { + padding: 5px 20px; +} + +table.index a:link, table.index a:visited { + font-weight: normal; + text-decoration: none; + color: {{ theme_linkcolor }}; +} +table.index a:hover, table.index a:active { + text-decoration: underline; + color: {{ theme_hoverlinkcolor }}; +} + + +/* Haiku User Guide styles and layout */ + +/* Rounded corner boxes */ +/* Common declarations */ +div.admonition { + -webkit-border-radius: 10px; + -khtml-border-radius: 10px; + -moz-border-radius: 10px; + border-radius: 10px; + border-style: dotted; + border-width: thin; + border-color: #dcdcdc; + padding: 10px 15px 10px 15px; + margin-bottom: 15px; + margin-top: 15px; +} +div.note { + padding: 10px 15px 10px 80px; + background: #e4ffde url(alert_info_32.png) 15px 15px no-repeat; + min-height: 42px; +} +div.warning { + padding: 10px 15px 10px 80px; + background: #fffbc6 url(alert_warning_32.png) 15px 15px no-repeat; + min-height: 42px; +} +div.seealso { + background: #e4ffde; +} + +/* More layout and styles */ +h1 { + font-size: 1.3em; + font-weight: bold; + color: {{ theme_headingcolor }}; + border-bottom: dotted thin #e0e0e0; + margin-top: 30px; +} + +h2 { + font-size: 1.2em; + font-weight: normal; + color: {{ theme_headingcolor }}; + border-bottom: dotted thin #e0e0e0; + margin-top: 30px; +} + +h3 { + font-size: 1.1em; + font-weight: normal; + color: {{ theme_headingcolor }}; + margin-top: 30px; +} + +h4 { + font-size: 1.0em; + font-weight: normal; + color: {{ theme_headingcolor }}; + margin-top: 30px; +} + +p { + text-align: justify; +} + +p.last { + margin-bottom: 0; +} + +ol { + padding-left: 20px; +} + +ul { + padding-left: 5px; + margin-top: 3px; +} + +li { + line-height: 1.3; +} + +div.content ul > li { + -moz-background-clip:border; + -moz-background-inline-policy:continuous; + -moz-background-origin:padding; + background: transparent url(bullet_orange.png) no-repeat scroll left 0.45em; + list-style-image: none; + list-style-type: none; + padding: 0 0 0 1.666em; + margin-bottom: 3px; +} + +td { + vertical-align: top; +} + +tt { + background-color: #e2e2e2; + font-size: 1.0em; + font-family: monospace; +} + +pre { + border-color: #0c3762; + border-style: dotted; + border-width: thin; + margin: 0 0 12px 0; + padding: 0.8em; + background-color: #f0f0f0; +} + +hr { + border-top: 1px solid #ccc; + border-bottom: 0; + border-right: 0; + border-left: 0; + margin-bottom: 10px; + margin-top: 20px; +} + +/* printer only pretty stuff */ +@media print { + .noprint { + display: none; + } + /* for acronyms we want their definitions inlined at print time */ + acronym[title]:after { + font-size: small; + content: " (" attr(title) ")"; + font-style: italic; + } + /* and not have mozilla dotted underline */ + acronym { + border: none; + } + div.topnav, div.bottomnav, div.header, table.index { + display: none; + } + div.content { + margin: 0px; + padding: 0px; + } + html { + background: #FFF; + } +} + +.viewcode-back { + font-family: "DejaVu Sans", Arial, Helvetica, sans-serif; +} + +div.viewcode-block:target { + background-color: #f4debf; + border-top: 1px solid #ac9; + border-bottom: 1px solid #ac9; + margin: -1px -12px; + padding: 0 12px; +} diff --git a/documentation/manual/themes/openlp_qthelp/theme.conf b/documentation/manual/themes/openlp_qthelp/theme.conf new file mode 100644 index 000000000..0d44b0faa --- /dev/null +++ b/documentation/manual/themes/openlp_qthelp/theme.conf @@ -0,0 +1,12 @@ +[theme] +inherit = basic +stylesheet = openlp_qthelp.css +pygments_style = autumn + +[options] +full_logo = false +textcolor = #333333 +headingcolor = #203b6f +linkcolor = #26437c +visitedlinkcolor = #26437c +hoverlinkcolor = #26437c diff --git a/openlp/core/theme/theme.py b/openlp/core/theme/theme.py index 8a57c3ae7..c5d60e299 100644 --- a/openlp/core/theme/theme.py +++ b/openlp/core/theme/theme.py @@ -71,38 +71,45 @@ class Theme(object): Theme name ``BackgroundMode`` - The behaviour of the background. Valid modes are: - - 0 - Transparent - - 1 - Opaque + The behaviour of the background. Valid modes are: + + * ``0`` - Transparent + * ``1`` - Opaque ``BackgroundType`` - The content of the background. Valid types are: - - 0 - solid color - - 1 - gradient color - - 2 - image + The content of the background. Valid types are: + + * ``0`` - solid color + * ``1`` - gradient color + * ``2`` - image ``BackgroundParameter1`` - Extra information about the background. The contents of this attribute + Extra information about the background. The contents of this attribute depend on the BackgroundType: - - image: image filename - - gradient: start color - - solid: color + + * ``image`` - image filename + * ``gradient`` - start color + * ``solid`` - color ``BackgroundParameter2`` Extra information about the background. The contents of this attribute depend on the BackgroundType: - - image: border color - - gradient: end color - - solid: N/A + + * ``image`` - border color + * ``gradient`` - end color + * ``solid`` - N/A ``BackgroundParameter3`` Extra information about the background. The contents of this attribute depend on the BackgroundType: - - image: N/A - - gradient: The direction of the gradient. Valid entries are: - - 0 -> vertical - - 1 -> horizontal - - solid: N/A + + * ``image`` - N/A + * ``gradient`` - The direction of the gradient. Valid entries are: + + * ``0`` - vertical + * ``1`` - horizontal + + * ``solid`` - N/A ``FontName`` Name of the font to use for the main font. @@ -118,36 +125,41 @@ class Theme(object): ``Shadow`` The shadow type to apply to the main font. - - 0 - no shadow - - non-zero - use shadow + + * ``0`` - no shadow + * non-zero - use shadow ``ShadowColor`` Color for the shadow ``Outline`` The outline to apply to the main font - - 0 - no outline - - non-zero - use outline + + * ``0`` - no outline + * non-zero - use outline ``OutlineColor`` Color for the outline (or None if Outline is 0) ``HorizontalAlign`` The horizontal alignment to apply to text. Valid alignments are: - - 0 - left align - - 1 - right align - - 2 - centre align + + * ``0`` - left align + * ``1`` - right align + * ``2`` - centre align ``VerticalAlign`` The vertical alignment to apply to the text. Valid alignments are: - - 0 - top align - - 1 - bottom align - - 2 - centre align + + * ``0`` - top align + * ``1`` - bottom align + * ``2`` - centre align ``WrapStyle`` The wrap style to apply to the text. Valid styles are: - - 0 - normal - - 1 - lyrics + + * ``0`` - normal + * ``1`` - lyrics """ def __init__(self, xml): """ diff --git a/openlp/core/ui/__init__.py b/openlp/core/ui/__init__.py index eb0e89775..d820c9a5b 100644 --- a/openlp/core/ui/__init__.py +++ b/openlp/core/ui/__init__.py @@ -28,7 +28,7 @@ The :mod:`ui` module provides the core user interface for OpenLP """ from PyQt4 import QtGui -from openlp.core.lib import translate, Receiver +from openlp.core.lib import translate class HideMode(object): """ diff --git a/openlp/core/ui/aboutdialog.py b/openlp/core/ui/aboutdialog.py index 597d3fb24..bb09ab91f 100644 --- a/openlp/core/ui/aboutdialog.py +++ b/openlp/core/ui/aboutdialog.py @@ -164,9 +164,10 @@ class Ui_AboutDialog(object): self.licenseTextEdit.setPlainText(translate('OpenLP.AboutForm', 'Copyright \xa9 2004-2011 Raoul Snyman\n' 'Portions copyright \xa9 2004-2011 ' - 'Tim Bentley, Jonathan Corwin, Michael Gorven, Scott Guerrieri, ' - 'Christian Richter, Maikel Stuivenberg, Martin Thompson, Jon ' - 'Tibble, Carsten Tinggaard\n' + 'Tim Bentley, Jonathan Corwin, Michael Gorven, Scott Guerrieri,\n' + 'Meinert Jordan, Andreas Preikschat, Christian Richter, Philip\n' + 'Ridout, Maikel Stuivenberg, Martin Thompson, Jon Tibble, Carstenn' + 'Tinggaard, Frode Woldsund\n' '\n' 'This program is free software; you can redistribute it and/or ' 'modify it under the terms of the GNU General Public License as ' diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index c329abb7d..4b99cc32b 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -216,6 +216,9 @@ class Ui_MainWindow(object): self.ToolsAddToolItem = icon_action(mainWindow, u'ToolsAddToolItem', u':/tools/tools_add.png') mainWindow.actionList.add_action(self.ToolsAddToolItem, u'Tools') + self.ToolsOpenDataFolder = icon_action(mainWindow, u'ToolsOpenDataFolder', + u':/general/general_open.png') + mainWindow.actionList.add_action(self.ToolsOpenDataFolder, u'Tools') self.settingsPluginListItem = icon_action(mainWindow, u'settingsPluginListItem', u':/system/settings_plugin_list.png') mainWindow.actionList.add_action(self.settingsPluginListItem, @@ -276,6 +279,7 @@ class Ui_MainWindow(object): self.SettingsLanguageMenu.menuAction(), None, self.SettingsShortcutsItem, self.SettingsConfigureItem)) add_actions(self.ToolsMenu, (self.ToolsAddToolItem, None)) + add_actions(self.ToolsMenu, (self.ToolsOpenDataFolder, None)) add_actions(self.HelpMenu, (self.HelpDocumentationItem, self.HelpOnlineHelpItem, None, self.HelpWebSiteItem, self.HelpAboutItem)) @@ -432,6 +436,10 @@ class Ui_MainWindow(object): translate('OpenLP.MainWindow', 'Add &Tool...')) self.ToolsAddToolItem.setStatusTip(translate('OpenLP.MainWindow', 'Add an application to the list of tools.')) + self.ToolsOpenDataFolder.setText( + translate('OpenLP.MainWindow', 'Open &Data Folder...')) + self.ToolsOpenDataFolder.setStatusTip(translate('OpenLP.MainWindow', + 'Open the folder where songs, bibles and other data resides.')) self.ModeDefaultItem.setText( translate('OpenLP.MainWindow', '&Default')) self.ModeDefaultItem.setStatusTip(translate('OpenLP.MainWindow', @@ -514,6 +522,8 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): QtCore.SIGNAL(u'triggered()'), self.onHelpWebSiteClicked) QtCore.QObject.connect(self.HelpAboutItem, QtCore.SIGNAL(u'triggered()'), self.onHelpAboutItemClicked) + QtCore.QObject.connect(self.ToolsOpenDataFolder, + QtCore.SIGNAL(u'triggered()'), self.onToolsOpenDataFolderClicked) QtCore.QObject.connect(self.settingsPluginListItem, QtCore.SIGNAL(u'triggered()'), self.onPluginItemClicked) QtCore.QObject.connect(self.SettingsConfigureItem, @@ -702,6 +712,13 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): self.pluginForm.load() self.pluginForm.exec_() + def onToolsOpenDataFolderClicked(self): + """ + Open data folder + """ + path = AppLocation.get_data_path() + QtGui.QDesktopServices.openUrl(QtCore.QUrl("file:///" + path)) + def onSettingsConfigureItemClicked(self): """ Show the Settings dialog diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index cebb119ed..5fc80b175 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -461,7 +461,7 @@ class ServiceManager(QtGui.QWidget): def saveFileAs(self): """ - Get a file name and then call :function:`ServiceManager.saveFile` to + Get a file name and then call :func:`ServiceManager.saveFile` to save the file. """ fileName = unicode(QtGui.QFileDialog.getSaveFileName(self.mainwindow, diff --git a/openlp/core/utils/__init__.py b/openlp/core/utils/__init__.py index 37cbd7a63..2862afef4 100644 --- a/openlp/core/utils/__init__.py +++ b/openlp/core/utils/__init__.py @@ -24,7 +24,7 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ -The :mod:`utils` module provides the utility libraries for OpenLP +The :mod:`openlp.core.utils` module provides the utility libraries for OpenLP. """ import logging import os diff --git a/openlp/plugins/bibles/lib/__init__.py b/openlp/plugins/bibles/lib/__init__.py index 7de4e82c3..314651ced 100644 --- a/openlp/plugins/bibles/lib/__init__.py +++ b/openlp/plugins/bibles/lib/__init__.py @@ -69,69 +69,90 @@ def get_reference_match(match_type): def parse_reference(reference): """ This is the next generation über-awesome function that takes a person's - typed in string and converts it to a reference list, a list of references to - be queried from the Bible database files. + typed in string and converts it to a list of references to be queried from + the Bible database files. - This is a user manual like description, how the references are working. + ``reference`` + A string. The Bible reference to parse. - - Each reference starts with the book name. A chapter name is manditory. - ``John 3`` refers to Gospel of John chapter 3 - - A reference range can be given after a range separator. - ``John 3-5`` refers to John chapters 3 to 5 - - Single verses can be addressed after a verse separator - ``John 3:16`` refers to John chapter 3 verse 16 - ``John 3:16-4:3`` refers to John chapter 3 verse 16 to chapter 4 verse 3 - - After a verse reference all further single values are treat as verse in - the last selected chapter. - ``John 3:16-18`` refers to John chapter 3 verses 16 to 18 - - After a list separator it is possible to refer to additional verses. They - are build analog to the first ones. This way it is possible to define each - number of verse references. It is not possible to refer to verses in - additional books. - ``John 3:16,18`` refers to John chapter 3 verses 16 and 18 - ``John 3:16-18,20`` refers to John chapter 3 verses 16 to 18 and 20 - ``John 3:16-18,4:1`` refers to John chapter 3 verses 16 to 18 and - chapter 3 verse 1 - - If there is a range separator without further verse declaration the last - refered chapter is addressed until the end. - - ``range_string`` is a regular expression which matches for verse range - declarations: - - 1. ``(?:(?P[0-9]+)%(sep_v)s)?`` - It starts with a optional chapter reference ``from_chapter`` followed by - a verse separator. - 2. ``(?P[0-9]+)`` - The verse reference ``from_verse`` is manditory - 3. ``(?P%(sep_r)s(?:`` ... ``|%(sep_e)s)?)?`` - A ``range_to`` declaration is optional. It starts with a range separator - and contains optional a chapter and verse declaration or a end - separator. - 4. ``(?:(?P[0-9]+)%(sep_v)s)?`` - The ``to_chapter`` reference with separator is equivalent to group 1. - 5. ``(?P[0-9]+)`` - The ``to_verse`` reference is equivalent to group 2. - - The full reference is matched against get_reference_match(u'full'). This - regular expression looks like this: - - 1. ``^\s*(?!\s)(?P[\d]*[^\d]+)(?(?:`` + range_string + ``(?:%(sep_l)s|(?=\s*$)))+)\s*$`` - The second group contains all ``ranges``. This can be multiple - declarations of a range_string separated by a list separator. + Returns ``None`` or a reference list. The reference list is a list of tuples, with each tuple structured like this:: (book, chapter, from_verse, to_verse) + + For example:: + + [(u'John', 3, 16, 18), (u'John', 4, 1, 1)] - ``reference`` - The bible reference to parse. + **Reference string details:** + + Each reference starts with the book name and a chapter number. These are + both mandatory. + + * ``John 3`` refers to Gospel of John chapter 3 + + A reference range can be given after a range separator. + + * ``John 3-5`` refers to John chapters 3 to 5 + + Single verses can be addressed after a verse separator. + + * ``John 3:16`` refers to John chapter 3 verse 16 + * ``John 3:16-4:3`` refers to John chapter 3 verse 16 to chapter 4 verse 3 + + After a verse reference all further single values are treat as verse in + the last selected chapter. + + * ``John 3:16-18`` refers to John chapter 3 verses 16 to 18 + + After a list separator it is possible to refer to additional verses. They + are build analog to the first ones. This way it is possible to define each + number of verse references. It is not possible to refer to verses in + additional books. + + * ``John 3:16,18`` refers to John chapter 3 verses 16 and 18 + * ``John 3:16-18,20`` refers to John chapter 3 verses 16 to 18 and 20 + * ``John 3:16-18,4:1`` refers to John chapter 3 verses 16 to 18 and + chapter 4 verse 1 + + If there is a range separator without further verse declaration the last + refered chapter is addressed until the end. + + ``range_string`` is a regular expression which matches for verse range + declarations: + + ``(?:(?P[0-9]+)%(sep_v)s)?`` + It starts with a optional chapter reference ``from_chapter`` followed by + a verse separator. + + ``(?P[0-9]+)`` + The verse reference ``from_verse`` is manditory + + ``(?P%(sep_r)s(?:`` ... ``|%(sep_e)s)?)?`` + A ``range_to`` declaration is optional. It starts with a range separator + and contains optional a chapter and verse declaration or a end + separator. + + ``(?:(?P[0-9]+)%(sep_v)s)?`` + The ``to_chapter`` reference with separator is equivalent to group 1. + + ``(?P[0-9]+)`` + The ``to_verse`` reference is equivalent to group 2. + + The full reference is matched against get_reference_match(u'full'). This + regular expression looks like this: + + ``^\s*(?!\s)(?P[\d]*[^\d]+)(?(?:`` + range_string + ``(?:%(sep_l)s|(?=\s*$)))+)\s*$`` + The second group contains all ``ranges``. This can be multiple + declarations of a range_string separated by a list separator. - Returns None or a reference list. """ log.debug(u'parse_reference("%s")', reference) match = get_reference_match(u'full').match(reference) @@ -201,7 +222,7 @@ def parse_reference(reference): class SearchResults(object): """ - Encapsulate a set of search results. This is Bible-type independent. + Encapsulate a set of search results. This is Bible-type independent. """ def __init__(self, book, chapter, verselist): """ @@ -214,7 +235,8 @@ class SearchResults(object): The chapter of the book. ``verselist`` - The list of verses for this reading + The list of verses for this reading. + """ self.book = book self.chapter = chapter diff --git a/openlp/plugins/songs/forms/__init__.py b/openlp/plugins/songs/forms/__init__.py index 160a014ac..e75f9fe04 100644 --- a/openlp/plugins/songs/forms/__init__.py +++ b/openlp/plugins/songs/forms/__init__.py @@ -57,4 +57,6 @@ from songbookform import SongBookForm from editverseform import EditVerseForm from editsongform import EditSongForm from songmaintenanceform import SongMaintenanceForm -from songimportform import SongImportForm \ No newline at end of file +from songimportform import SongImportForm +from songexportform import SongExportForm + diff --git a/openlp/plugins/songs/forms/songexportform.py b/openlp/plugins/songs/forms/songexportform.py new file mode 100644 index 000000000..5d5b9a8b1 --- /dev/null +++ b/openlp/plugins/songs/forms/songexportform.py @@ -0,0 +1,372 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2011 Raoul Snyman # +# Portions copyright (c) 2008-2011 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 # +############################################################################### +""" +The :mod:`songexportform` module provides the wizard for exporting songs to the +OpenLyrics format. +""" +import logging + +from PyQt4 import QtCore, QtGui + +from openlp.core.lib import build_icon, Receiver, SettingsManager, translate +from openlp.core.lib.ui import critical_error_message_box +from openlp.core.ui.wizard import OpenLPWizard +from openlp.plugins.songs.lib.db import Song +from openlp.plugins.songs.lib.openlyricsexport import OpenLyricsExport + +log = logging.getLogger(__name__) + +class SongExportForm(OpenLPWizard): + """ + This is the Song Export Wizard, which allows easy exporting of Songs to the + OpenLyrics format. + """ + log.info(u'SongExportForm loaded') + + def __init__(self, parent, plugin): + """ + Instantiate the wizard, and run any extra setup we need to. + + ``parent`` + The QWidget-derived parent of the wizard. + + ``plugin`` + The songs plugin. + """ + self.plugin = plugin + OpenLPWizard.__init__(self, parent, plugin, u'songExportWizard', + u':/wizards/wizard_exportsong.bmp') + self.stop_export_flag = False + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'openlp_stop_wizard'), self.stop_export) + + def stop_export(self): + """ + Sets the flag for the exporter to stop the export. + """ + log.debug(u'Stopping songs export') + self.stop_export_flag = True + + def setupUi(self, image): + """ + Set up the song wizard UI. + """ + OpenLPWizard.setupUi(self, image) + + def customInit(self): + """ + Song wizard specific initialisation. + """ + pass + + def customSignals(self): + """ + Song wizard specific signals. + """ + QtCore.QObject.connect(self.availableListWidget, + QtCore.SIGNAL(u'itemActivated(QListWidgetItem*)'), self.onItemPressed) + QtCore.QObject.connect(self.searchLineEdit, + QtCore.SIGNAL(u'textEdited(const QString&)'), + self.onSearchLineEditChanged) + QtCore.QObject.connect(self.uncheckButton, + QtCore.SIGNAL(u'clicked()'), self.onUncheckButtonClicked) + QtCore.QObject.connect(self.checkButton, + QtCore.SIGNAL(u'clicked()'), self.onCheckButtonClicked) + QtCore.QObject.connect(self.directoryButton, + QtCore.SIGNAL(u'clicked()'), self.onDirectoryButtonClicked) + + def addCustomPages(self): + """ + Add song wizard specific pages. + """ + # The page with all available songs. + self.availableSongsPage = QtGui.QWizardPage() + self.availableSongsPage.setObjectName(u'availableSongsPage') + self.availableSongsLayout = QtGui.QHBoxLayout(self.availableSongsPage) + self.availableSongsLayout.setObjectName(u'availableSongsLayout') + self.verticalLayout = QtGui.QVBoxLayout() + self.verticalLayout.setObjectName(u'verticalLayout') + self.availableListWidget = QtGui.QListWidget(self.availableSongsPage) + self.availableListWidget.setObjectName(u'availableListWidget') + self.verticalLayout.addWidget(self.availableListWidget) + self.horizontalLayout = QtGui.QHBoxLayout() + self.horizontalLayout.setObjectName(u'horizontalLayout') + self.searchLabel = QtGui.QLabel(self.availableSongsPage) + self.searchLabel.setObjectName(u'searchLabel') + self.horizontalLayout.addWidget(self.searchLabel) + self.searchLineEdit = QtGui.QLineEdit(self.availableSongsPage) + self.searchLineEdit.setObjectName(u'searchLineEdit') + self.horizontalLayout.addWidget(self.searchLineEdit) + spacerItem = QtGui.QSpacerItem(40, 20, + QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem) + self.uncheckButton = QtGui.QPushButton(self.availableSongsPage) + self.uncheckButton.setObjectName(u'uncheckButton') + self.horizontalLayout.addWidget(self.uncheckButton) + self.checkButton = QtGui.QPushButton(self.availableSongsPage) + self.checkButton.setObjectName(u'selectButton') + self.horizontalLayout.addWidget(self.checkButton) + self.verticalLayout.addLayout(self.horizontalLayout) + self.availableSongsLayout.addLayout(self.verticalLayout) + self.addPage(self.availableSongsPage) + # The page with the selected songs. + self.exportSongPage = QtGui.QWizardPage() + self.exportSongPage.setObjectName(u'availableSongsPage') + self.exportSongLayout = QtGui.QHBoxLayout(self.exportSongPage) + self.exportSongLayout.setObjectName(u'exportSongLayout') + self.gridLayout = QtGui.QGridLayout() + self.gridLayout.setObjectName(u'gridLayout') + self.selectedListWidget = QtGui.QListWidget(self.exportSongPage) + self.selectedListWidget.setObjectName(u'selectedListWidget') + self.gridLayout.addWidget(self.selectedListWidget, 1, 0, 1, 1) + self.horizontalLayout = QtGui.QHBoxLayout() + self.horizontalLayout.setObjectName(u'horizontalLayout') + self.directoryLabel = QtGui.QLabel(self.exportSongPage) + self.directoryLabel.setObjectName(u'directoryLabel') + self.horizontalLayout.addWidget(self.directoryLabel) + self.directoryLineEdit = QtGui.QLineEdit(self.exportSongPage) + self.directoryLineEdit.setObjectName(u'directoryLineEdit') + self.horizontalLayout.addWidget(self.directoryLineEdit) + self.directoryButton = QtGui.QToolButton(self.exportSongPage) + self.directoryButton.setIcon(build_icon(u':/exports/export_load.png')) + self.directoryButton.setObjectName(u'directoryButton') + self.horizontalLayout.addWidget(self.directoryButton) + self.gridLayout.addLayout(self.horizontalLayout, 0, 0, 1, 1) + self.exportSongLayout.addLayout(self.gridLayout) + self.addPage(self.exportSongPage) + + def retranslateUi(self): + """ + Song wizard localisation. + """ + self.setWindowTitle( + translate('SongsPlugin.ExportWizardForm', 'Song Export Wizard')) + self.titleLabel.setText( + u'%s' % + translate('SongsPlugin.ExportWizardForm', + 'Welcome to the Song Export Wizard')) + self.informationLabel.setText( + translate('SongsPlugin.ExportWizardForm', 'This wizard will help to' + ' export your songs to the open and free OpenLyrics worship song ' + 'format.')) + self.availableSongsPage.setTitle( + translate('SongsPlugin.ExportWizardForm', 'Select Songs')) + self.availableSongsPage.setSubTitle( + translate('SongsPlugin.ExportWizardForm', + 'Check the songs, you want to export.')) + self.searchLabel.setText( + translate('SongsPlugin.ExportWizardForm', 'Search:')) + self.uncheckButton.setText( + translate('SongsPlugin.ExportWizardForm', 'Uncheck All')) + self.checkButton.setText( + translate('SongsPlugin.ExportWizardForm', 'Check All')) + self.exportSongPage.setTitle( + translate('SongsPlugin.ExportWizardForm', 'Select Directory')) + self.exportSongPage.setSubTitle( + translate('SongsPlugin.ExportWizardForm', + 'Select the directory you want the songs to be saved.')) + self.directoryLabel.setText( + translate('SongsPlugin.ExportWizardForm', 'Directory:')) + self.progressPage.setTitle( + translate('SongsPlugin.ExportWizardForm', 'Exporting')) + self.progressPage.setSubTitle( + translate('SongsPlugin.ExportWizardForm', + 'Please wait while your songs are exported.')) + self.progressLabel.setText( + translate('SongsPlugin.ExportWizardForm', 'Ready.')) + self.progressBar.setFormat( + translate('SongsPlugin.ExportWizardForm', '%p%')) + + def validateCurrentPage(self): + """ + Validate the current page before moving on to the next page. + """ + if self.currentPage() == self.welcomePage: + return True + elif self.currentPage() == self.availableSongsPage: + items = [ + item for item in self._findListWidgetItems( + self.availableListWidget) if item.checkState() + ] + if not items: + critical_error_message_box( + translate('SongsPlugin.ExportWizardForm', + 'No Song Selected'), + translate('SongsPlugin.ExportWizardForm', + 'You need to add at least one Song to export.')) + return False + self.selectedListWidget.clear() + # Add the songs to the list of selected songs. + for item in items: + song = QtGui.QListWidgetItem(item.text()) + song.setData(QtCore.Qt.UserRole, + QtCore.QVariant(item.data(QtCore.Qt.UserRole).toPyObject())) + song.setFlags(QtCore.Qt.ItemIsEnabled) + self.selectedListWidget.addItem(song) + return True + elif self.currentPage() == self.exportSongPage: + if not self.directoryLineEdit.text(): + critical_error_message_box( + translate('SongsPlugin.ExportWizardForm', + 'No Save Location specified'), + translate('SongsPlugin.ExportWizardForm', + 'You need to specified a directory to save the songs in.')) + return False + return True + elif self.currentPage() == self.progressPage: + self.availableListWidget.clear() + self.selectedListWidget.clear() + return True + + def registerFields(self): + """ + Register song export wizard fields. + """ + pass + + def setDefaults(self): + """ + Set default form values for the song export wizard. + """ + self.restart() + self.finishButton.setVisible(False) + self.cancelButton.setVisible(True) + self.availableListWidget.clear() + self.selectedListWidget.clear() + self.directoryLineEdit.clear() + # Load the list of songs. + Receiver.send_message(u'cursor_busy') + songs = self.plugin.manager.get_all_objects(Song) + for song in songs: + authors = u', '.join([author.display_name + for author in song.authors]) + title = u'%s (%s)' % (unicode(song.title), authors) + item = QtGui.QListWidgetItem(title) + item.setData(QtCore.Qt.UserRole, QtCore.QVariant(song)) + item.setFlags(QtCore.Qt.ItemIsSelectable| + QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled) + item.setCheckState(QtCore.Qt.Unchecked) + self.availableListWidget.addItem(item) + Receiver.send_message(u'cursor_normal') + + def preWizard(self): + """ + Perform pre export tasks. + """ + OpenLPWizard.preWizard(self) + self.progressLabel.setText( + translate('SongsPlugin.ExportWizardForm', 'Starting export...')) + Receiver.send_message(u'openlp_process_events') + + def performWizard(self): + """ + Perform the actual export. This creates an *openlyricsexport* instance + and calls the *do_export* method. + """ + songs = [ + song.data(QtCore.Qt.UserRole).toPyObject() + for song in self._findListWidgetItems(self.selectedListWidget) + ] + exporter = OpenLyricsExport( + self, songs, unicode(self.directoryLineEdit.text())) + if exporter.do_export(): + self.progressLabel.setText( + translate('SongsPlugin.SongExportForm', 'Finished export.')) + else: + self.progressLabel.setText( + translate('SongsPlugin.SongExportForm', + 'Your song export failed.')) + + def _findListWidgetItems(self, listWidget, text=u''): + """ + Returns a list of *QListWidgetItem*s of the ``listWidget``. Note, that + hidden items are included. + + ``listWidget`` + The widget to get all items from. (QListWidget) + + ``text`` + The text to search for. (unicode string) + """ + return [item for item in listWidget.findItems( + QtCore.QString(unicode(text)), QtCore.Qt.MatchContains) + ] + + def onItemPressed(self, item): + """ + Called, when an item in the *availableListWidget* has been pressed. Thes + item is check if it was not checked, whereas it is unchecked when it was + checked. + + ``item`` + The *QListWidgetItem* which was pressed. + """ + item.setCheckState( + QtCore.Qt.Unchecked if item.checkState() else QtCore.Qt.Checked) + + def onSearchLineEditChanged(self, text): + """ + The *searchLineEdit*'s text has been changed. Update the list of + available songs. Note that any song, which does not match the ``text`` + will be hidden, but not unchecked! + + ``text`` + The text of the *searchLineEdit*. (QString) + """ + search_result = [ + song for song in self._findListWidgetItems( + self.availableListWidget, unicode(text)) + ] + for item in self._findListWidgetItems(self.availableListWidget): + item.setHidden(False if item in search_result else True) + + def onUncheckButtonClicked(self): + """ + The *uncheckButton* has been clicked. Set all songs unchecked. + """ + for row in range(self.availableListWidget.count()): + item = self.availableListWidget.item(row) + item.setCheckState(QtCore.Qt.Unchecked) + + def onCheckButtonClicked(self): + """ + The *checkButton* has been clicked. Set all songs checked. + """ + for row in range(self.availableListWidget.count()): + item = self.availableListWidget.item(row) + item.setCheckState(QtCore.Qt.Checked) + + def onDirectoryButtonClicked(self): + """ + Called when the *directoryButton* was clicked. Opens a dialog and writes + the path to *directoryLineEdit*. + """ + path = unicode(QtGui.QFileDialog.getExistingDirectory(self, + translate('SongsPlugin.ExportWizardForm', 'Selecte to Folder'), + SettingsManager.get_last_dir(self.plugin.settingsSection, 1), + options=QtGui.QFileDialog.ShowDirsOnly)) + SettingsManager.set_last_dir(self.plugin.settingsSection, path, 1) + self.directoryLineEdit.setText(path) diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py index cabb181fb..283aa6c03 100644 --- a/openlp/plugins/songs/lib/mediaitem.py +++ b/openlp/plugins/songs/lib/mediaitem.py @@ -35,7 +35,7 @@ from openlp.core.lib import MediaManagerItem, Receiver, ItemCapabilities, \ translate, check_item_selected, PluginStatus from openlp.core.lib.ui import UiStrings from openlp.plugins.songs.forms import EditSongForm, SongMaintenanceForm, \ - SongImportForm + SongImportForm, SongExportForm from openlp.plugins.songs.lib import OpenLyrics, SongXML from openlp.plugins.songs.lib.db import Author, Song from openlp.core.lib.searchedit import SearchEdit @@ -258,6 +258,11 @@ class SongMediaItem(MediaManagerItem): if self.import_wizard.exec_() == QtGui.QDialog.Accepted: Receiver.send_message(u'songs_load_list') + def onExportClick(self): + if not hasattr(self, u'export_wizard'): + self.export_wizard = SongExportForm(self, self.parent) + self.export_wizard.exec_() + def onNewClick(self): log.debug(u'onNewClick') self.edit_song_form.newSong() diff --git a/openlp/plugins/songs/lib/openlyricsexport.py b/openlp/plugins/songs/lib/openlyricsexport.py new file mode 100755 index 000000000..ffb1a2d6f --- /dev/null +++ b/openlp/plugins/songs/lib/openlyricsexport.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2011 Raoul Snyman # +# Portions copyright (c) 2008-2011 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 # +############################################################################### +""" +The :mod:`openlyricsexport` module provides the functionality for exporting +songs from the database to the OpenLyrics format. +""" +import logging +import os + +from lxml import etree + +from openlp.core.lib import Receiver, translate +from openlp.plugins.songs.lib import OpenLyrics + +log = logging.getLogger(__name__) + +class OpenLyricsExport(object): + """ + This provides the Openlyrics export. + """ + def __init__(self, parent, songs, save_path): + """ + Initialise the export. + """ + log.debug(u'initialise OpenLyricsExport') + self.parent = parent + self.manager = parent.plugin.manager + self.songs = songs + self.save_path = save_path + if not os.path.exists(self.save_path): + os.mkdir(self.save_path) + + def do_export(self): + """ + Export the songs. + """ + log.debug(u'started OpenLyricsExport') + openLyrics = OpenLyrics(self.manager) + self.parent.progressBar.setMaximum(len(self.songs)) + for song in self.songs: + Receiver.send_message(u'openlp_process_events') + if self.parent.stop_export_flag: + return False + self.parent.incrementProgressBar(unicode(translate( + 'SongsPlugin.OpenLyricsExport', 'Exporting "%s"...')) % + song.title) + xml = openLyrics.song_to_xml(song) + tree = etree.ElementTree(etree.fromstring(xml)) + tree.write(os.path.join(self.save_path, song.title + u'.xml'), + encoding=u'utf-8', xml_declaration=True, pretty_print=True) + return True diff --git a/openlp/plugins/songs/lib/opensongimport.py b/openlp/plugins/songs/lib/opensongimport.py index e7557f952..8c6d283e3 100644 --- a/openlp/plugins/songs/lib/opensongimport.py +++ b/openlp/plugins/songs/lib/opensongimport.py @@ -36,6 +36,7 @@ from openlp.plugins.songs.lib.songimport import SongImport log = logging.getLogger(__name__) +#TODO: Use lxml for parsing and make sure we use methods of "SongImport" . class OpenSongImport(SongImport): """ Import songs exported from OpenSong @@ -146,7 +147,7 @@ class OpenSongImport(SongImport): log.info(u'Zip importing %s', parts[-1]) self.import_wizard.incrementProgressBar( unicode(translate('SongsPlugin.ImportWizardForm', - 'Importing %s...')) % parts[-1]) + 'Importing %s...')) % parts[-1]) songfile = z.open(song) self.do_import_file(songfile) if self.commit: @@ -276,7 +277,7 @@ class OpenSongImport(SongImport): for num in versenums: versetag = u'%s%s' % (our_verse_type, num) lines = u'\n'.join(verses[versetype][num]) - self.verses.append([versetag, lines]) + self.add_verse(lines, versetag) # Keep track of what we have for error checking later versetags[versetag] = 1 # now figure out the presentation order @@ -292,6 +293,8 @@ class OpenSongImport(SongImport): else: log.warn(u'No verse order available for %s, skipping.', self.title) + # TODO: make sure that the default order list will be overwritten, if + # the songs provides its own order list. for tag in order: if tag[0].isdigit(): # Assume it's a verse if it has no prefix diff --git a/openlp/plugins/songs/lib/songimport.py b/openlp/plugins/songs/lib/songimport.py index 592aa7ac1..da017d4f5 100644 --- a/openlp/plugins/songs/lib/songimport.py +++ b/openlp/plugins/songs/lib/songimport.py @@ -50,6 +50,7 @@ class SongImport(QtCore.QObject): ``manager`` An instance of a SongManager, through which all database access is performed. + """ self.manager = manager self.stop_import_flag = False @@ -199,7 +200,7 @@ class SongImport(QtCore.QObject): def add_verse(self, versetext, versetag=u'V', lang=None): """ - Add a verse. This is the whole verse, lines split by \n. It will also + Add a verse. This is the whole verse, lines split by \\n. It will also attempt to detect duplicates. In this case it will just add to the verse order. @@ -212,6 +213,7 @@ class SongImport(QtCore.QObject): ``lang`` The language code (ISO-639) of the verse, for example *en* or *de*. + """ for (oldversetag, oldverse, oldlang) in self.verses: if oldverse.strip() == versetext.strip(): diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py index 91bfdb025..73ac8120c 100644 --- a/openlp/plugins/songs/lib/xml.py +++ b/openlp/plugins/songs/lib/xml.py @@ -26,7 +26,7 @@ """ The :mod:`xml` module provides the XML functionality. -The basic XML for storing the lyrics in the song database is of the format:: +The basic XML for storing the lyrics in the song database looks like this:: @@ -38,7 +38,7 @@ The basic XML for storing the lyrics in the song database is of the format:: -The XML of `OpenLyrics `_ songs is of the format:: +The XML of an `OpenLyrics `_ song looks like this:: `_ songs is of the format:: """ +import datetime import logging import re @@ -86,11 +87,13 @@ class SongXML(object): def add_verse_to_lyrics(self, type, number, content, lang=None): """ - Add a verse to the ** tag. + Add a verse to the ```` tag. ``type`` - A string denoting the type of verse. Possible values are "V", - "C", "B", "P", "I", "E" and "O". + A string denoting the type of verse. Possible values are *Verse*, + *Chorus*, *Bridge*, *Pre-Chorus*, *Intro*, *Ending* and *Other*. + Any other type is **not** allowed, this also includes translated + types. ``number`` An integer denoting the number of the item, for example: verse 1. @@ -126,8 +129,8 @@ class SongXML(object): The returned list has the following format:: - [[{'lang': 'en', 'type': 'V', 'label': '1'}, u"The English verse."], - [{'lang': 'en', 'type': 'C', 'label': '1'}, u"The English chorus."]] + [[{'lang': 'en', 'type': 'Verse', 'label': '1'}, u"English verse"], + [{'lang': 'en', 'type': 'Chorus', 'label': '1'}, u"English chorus"]] """ self.song_xml = None if xml[:5] == u'* + ```` OpenLP does not support the attribute *type* and *lang*. - ** + ```` This property is not supported. - ** - The ** property is fully supported. But comments in lyrics + ```` + The ```` property is fully supported. But comments in lyrics are not supported. - ** + ```` This property is fully supported. - ** + ```` This property is not supported. - ** + ```` This property is not supported. - ** + ```` This property is not supported. - ** + ```` The attribute *part* is not supported. - ** + ```` This property is not supported. - ** + ```` As OpenLP does only support one songbook, we cannot consider more than one songbook. - ** + ```` This property is not supported. - ** + ```` Topics, as they are called in OpenLP, are fully supported, whereby only the topic text (e. g. Grace) is considered, but neither the *id* nor *lang*. - ** + ```` This property is not supported. - ** + ```` This property is not supported. - ** - The attribute *translit* is not supported. + ```` + The attribute *translit* is not supported. Note, the attribute *lang* is + considered, but there is not further functionality implemented yet. - ** + ```` OpenLP supports this property. + """ + IMPLEMENTED_VERSION = u'0.7' def __init__(self, manager): self.manager = manager @@ -221,8 +227,14 @@ class OpenLyrics(object): """ sxml = SongXML() verse_list = sxml.get_verses(song.lyrics) - song_xml = objectify.fromstring( - u'') + song_xml = objectify.fromstring(u'') + # Append the necessary meta data to the song. + song_xml.set(u'xmlns', u'http://openlyrics.info/namespace/2009/song') + song_xml.set(u'version', OpenLyrics.IMPLEMENTED_VERSION) + song_xml.set(u'createdIn', u'OpenLP 1.9.4') # Use variable + song_xml.set(u'modifiedIn', u'OpenLP 1.9.4') # Use variable + song_xml.set(u'modifiedDate', + datetime.datetime.now().strftime(u'%Y-%m-%dT%H:%M:%S')) properties = etree.SubElement(song_xml, u'properties') titles = etree.SubElement(properties, u'titles') self._add_text_to_element(u'title', titles, song.title.strip()) @@ -236,7 +248,7 @@ class OpenLyrics(object): self._add_text_to_element(u'copyright', properties, song.copyright) if song.verse_order: self._add_text_to_element( - u'verseOrder', properties, song.verse_order) + u'verseOrder', properties, song.verse_order.lower()) if song.ccli_number: self._add_text_to_element(u'ccliNo', properties, song.ccli_number) if song.authors: @@ -262,6 +274,8 @@ class OpenLyrics(object): verse[0][u'type'][0].lower(), verse[0][u'label']) element = \ self._add_text_to_element(u'verse', lyrics, None, verse_tag) + if verse[0].has_key(u'lang'): + element.set(u'lang', verse[0][u'lang']) element = self._add_text_to_element(u'lines', element) for line in unicode(verse[1]).split(u'\n'): self._add_text_to_element(u'line', element, line) @@ -449,7 +463,7 @@ class OpenLyrics(object): text += u'\n' text += u'\n'.join([unicode(line) for line in lines.line]) verse_name = self._get(verse, u'name') - verse_type = unicode(VerseType.to_string(verse_name[0]))[0] + verse_type = unicode(VerseType.to_string(verse_name[0])) verse_number = re.compile(u'[a-zA-Z]*').sub(u'', verse_name) verse_part = re.compile(u'[0-9]*').sub(u'', verse_name[1:]) # OpenLyrics allows e. g. "c", but we need "c1". @@ -477,9 +491,9 @@ class OpenLyrics(object): for name in temp_verse_order: if name[0] == previous_type: if name[1] != previous_number: - verse_order.append(u''.join((name[0], name[1]))) + verse_order.append(u''.join((name[0][0], name[1]))) else: - verse_order.append(u''.join((name[0], name[1]))) + verse_order.append(u''.join((name[0][0], name[1]))) previous_type = name[0] previous_number = name[1] previous_part = name[2] diff --git a/openlp/plugins/songs/songsplugin.py b/openlp/plugins/songs/songsplugin.py index b6676140d..ef20d15ae 100644 --- a/openlp/plugins/songs/songsplugin.py +++ b/openlp/plugins/songs/songsplugin.py @@ -106,8 +106,17 @@ class SongsPlugin(Plugin): The actual **Export** menu item, so that your actions can use it as their parent. """ - # No menu items for now. - pass + # Main song import menu item - will eventually be the only one + self.SongExportItem = QtGui.QAction(export_menu) + self.SongExportItem.setObjectName(u'SongExportItem') + self.SongExportItem.setText(translate( + 'SongsPlugin', '&Song')) + self.SongExportItem.setToolTip(translate('SongsPlugin', + 'Exports songs using the export wizard.')) + export_menu.addAction(self.SongExportItem) + # Signals and slots + QtCore.QObject.connect(self.SongExportItem, + QtCore.SIGNAL(u'triggered()'), self.onSongExportItemClicked) def addToolsMenuItem(self, tools_menu): """ @@ -172,6 +181,10 @@ class SongsPlugin(Plugin): if self.mediaItem: self.mediaItem.onImportClick() + def onSongExportItemClicked(self): + if self.mediaItem: + self.mediaItem.onExportClick() + def about(self): about_text = translate('SongsPlugin', 'Songs Plugin' '
The songs plugin provides the ability to display and ' diff --git a/resources/forms/songexport.ui b/resources/forms/songexport.ui deleted file mode 100644 index 9830db3ef..000000000 --- a/resources/forms/songexport.ui +++ /dev/null @@ -1,241 +0,0 @@ - - SongExportDialog - - - - 0 - 0 - 641 - 607 - - - - Dialog - - - - 8 - - - 8 - - - - - - 8 - - - 0 - - - - - Available Songs - - - - - - - - - Select All - - - - - - - - - - - 0 - 0 - - - - - 30 - 16777215 - - - - - 8 - - - QLayout::SetMinimumSize - - - 0 - - - - - Qt::Vertical - - - QSizePolicy::MinimumExpanding - - - - 20 - 132 - - - - - - - - Select Songs - - - - :/exports/export_move_to_list.png:/exports/export_move_to_list.png - - - - 20 - 20 - - - - - - - - Deselect Songs - - - - :/exports/export_remove.png:/exports/export_remove.png - - - - 20 - 20 - - - - - - - - Qt::Vertical - - - QSizePolicy::MinimumExpanding - - - - 20 - 131 - - - - - - - - - - - Selected Songs - - - - - - - - - Select All - - - - - - - - - - - - - 0 - - - - OpenLyric Format - - - - - Text File - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - SongExportButtonBox - AvailableListWidget - AvailableAllToolButton - SelectToolButton - DeselectToolButton - SelectedListWidget - SelectedAllToolButton - ExportTabWidget - - - - - - - SongExportButtonBox - accepted() - SongExportDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - SongExportButtonBox - rejected() - SongExportDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff --git a/resources/images/export_move_to_list.png b/resources/images/export_move_to_list.png deleted file mode 100644 index 5c0005856..000000000 Binary files a/resources/images/export_move_to_list.png and /dev/null differ diff --git a/resources/images/export_remove.png b/resources/images/export_remove.png deleted file mode 100644 index ef8e685e2..000000000 Binary files a/resources/images/export_remove.png and /dev/null differ diff --git a/resources/images/export_selectall.png b/resources/images/export_selectall.png deleted file mode 100644 index 0f0d9f152..000000000 Binary files a/resources/images/export_selectall.png and /dev/null differ diff --git a/resources/images/openlp-2.qrc b/resources/images/openlp-2.qrc index 2db924363..dd5abd861 100644 --- a/resources/images/openlp-2.qrc +++ b/resources/images/openlp-2.qrc @@ -80,12 +80,10 @@ import_load.png - export_selectall.png - export_remove.png export_load.png - export_move_to_list.png + wizard_exportsong.bmp wizard_importsong.bmp wizard_importbible.bmp wizard_createtheme.bmp diff --git a/resources/images/wizard_exportsong.bmp b/resources/images/wizard_exportsong.bmp new file mode 100644 index 000000000..948422dcc Binary files /dev/null and b/resources/images/wizard_exportsong.bmp differ