From c2b822872993a6af3e652063c2b3ef26141eda3c Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Fri, 10 Dec 2010 07:09:03 +0200 Subject: [PATCH 01/28] Updates to documentation. --- documentation/api/source/core/theme.rst | 6 +- documentation/api/source/plugins/bibles.rst | 2 +- documentation/manual/source/dualmonitors.rst | 12 ++-- openlp/core/theme/theme.py | 74 ++++++++++++-------- openlp/core/utils/__init__.py | 2 +- 5 files changed, 55 insertions(+), 41 deletions(-) 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/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/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/openlp/core/theme/theme.py b/openlp/core/theme/theme.py index 52dda1631..6b6740b07 100644 --- a/openlp/core/theme/theme.py +++ b/openlp/core/theme/theme.py @@ -68,38 +68,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. @@ -115,36 +122,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/utils/__init__.py b/openlp/core/utils/__init__.py index 119bf6b55..f33d7ca04 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 From 40ddcd1010e21638a7b057476bf514251bec6e3c Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Fri, 10 Dec 2010 14:52:43 +0200 Subject: [PATCH 02/28] Added a qthelp theme, updated the manual to work better as Qt help. --- documentation/manual/source/conf.py | 20 +- documentation/manual/source/index.rst | 7 - .../manual/themes/openlp_qthelp/layout.html | 68 ++++ .../openlp_qthelp/static/alert_info_32.png | Bin 0 -> 1168 bytes .../openlp_qthelp/static/alert_warning_32.png | Bin 0 -> 1060 bytes .../themes/openlp_qthelp/static/bg-page.png | Bin 0 -> 164 bytes .../openlp_qthelp/static/bullet_orange.png | Bin 0 -> 365 bytes .../openlp_qthelp/static/openlp_qthelp.css_t | 372 ++++++++++++++++++ .../manual/themes/openlp_qthelp/theme.conf | 12 + 9 files changed, 462 insertions(+), 17 deletions(-) create mode 100644 documentation/manual/themes/openlp_qthelp/layout.html create mode 100644 documentation/manual/themes/openlp_qthelp/static/alert_info_32.png create mode 100644 documentation/manual/themes/openlp_qthelp/static/alert_warning_32.png create mode 100644 documentation/manual/themes/openlp_qthelp/static/bg-page.png create mode 100644 documentation/manual/themes/openlp_qthelp/static/bullet_orange.png create mode 100644 documentation/manual/themes/openlp_qthelp/static/openlp_qthelp.css_t create mode 100644 documentation/manual/themes/openlp_qthelp/theme.conf diff --git a/documentation/manual/source/conf.py b/documentation/manual/source/conf.py index 517fc2f44..70b4fb2f6 100644 --- a/documentation/manual/source/conf.py +++ b/documentation/manual/source/conf.py @@ -92,22 +92,22 @@ 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 = 'default' +html_theme = 'openlp_qthelp' # 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' -} +#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/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 0000000000000000000000000000000000000000..05b4fe898c39c93ca6041650c49dcb67e6595432 GIT binary patch literal 1168 zcmV;B1aJF^P)KB1(GEo2OU5dU1MjixU?b4HlPyU1Z#btg*bwF1uutH8k9WJ2h0&wD~{( zq-o+GmX3s=tgtkx6sd&#N1|j3;?zy1&gP$OUftcd7w1f`OJdNyJ@D}3at=J7^L(D) z@0<(&9%FzO;23Zx%%eaiyjIkB8aP38t}OABtn!#B-zOp)1W5rW!h4DmV}O&y+vS@2 z<;@zBYwDM!&dcJz3U7yC(lA6(I*}ndPi}fv)oZ0Cd!*`hlAE5D)tnL~-wInJs8Xn- z;BnNdevvmjC_bcM^=3JVi=2-_f-A>c?c0J1jWPq`Vvli%yW49O;*6{X(`TPLhl zK(;sl6^CREJ#sUp$Q;Sdl&qmgmN+K-F9pkmEh-wo5u)2R>B4+r5#coof14;hDk8hWt`r!+Yq}!Wl9NC4uHs9xs$6|j@dZ69X}Y9HxwF+k)?t)1qT3l0i4`B zS-FO+EZr|l-ja>KE_go}L|TKhfRBm-03bItE7x#aR@X0^a8o3DLx6>XMS>NAkBbN} zB`d4HEvxO5Ro@U5f8_xz5v&w!C?Y`PZP}y&*~DA2s_SChwGd#~|7yX;q5wPs1no}; z?YGKa5x_)xH3&{i;jQ?V)QRoC9D^p2cr+XiLnf&1Gpeoe%#hW>{S zpiczbhXK++azwrcyb2_M*TYNzF9A>fW9VXwb4q@i)0>~?lxiQmxBc$E&ShZI7@+!u>A1Q^G$hqUVNi2L-!F3-B`V z_``@V^jc7ez_8@Gr@$>Q-6v4VBO7S6NH1T<#9jNX7HxMPN6U7T%8{a#2J&1z&|{Lvf4eM;!PKn6vuz>4R0vJ5GZ9JCSY|{n-F$v?83&>Rb%7EO*d_5V&cw3mu}oBb)lFvibm5k zsqv%1bYY?~*iemz27y8oC@oZ47>5C-;LLQs-n;j>n0Yg=wt2%$J14n|$$fMGzjN;Y z-gBXPSimV@8HfYl0dE3_0do)kuRC1f+rz^j$m;5{?C!*5X8Jd|c;TF!8G2oS4dB~G z0o@)R9+uVBRS|JSbWTL4MAVjub5F!xl-6+k&44rO(tZvAQiZUvlT_R22#!7l#xhSA&zfKv9# z1E8JCz%75IQCpyj*^A?W$d>?g`7;AB8UmoTC-c(&yJi&aQKwNAa6IlWl!m(n>Y+~? zW5S1yUcMskn$xRp1RDY1alZ1J&@TF;ByJcD^MISZ?b&+=gfc)`w{ZYY63n1V#_vyssn*G@~3ork@o?(9& zdfH7a!@V1X>~;*pgd6Lvyzym}^#nt}^``}J`p97K$k^x!UOm#y)L$FCeeN1<`$F_| zG!Y6yDjs8O8ou}`!^9E^U>umO1;F<~vbpdMUES@d>;qJChuvtLKYm~2{Lf1q8|-E2 z<~oPMaQp>`<|7>aWUJbmwE@V&X%L09Mo__4bS8zflOVehV{YaSDwX1J`xd4N`=1{m zbaK9C0RQ)Za-fjLNX2OqjkF)c?CQqc*TP_1m_j^?y5CGF1g8H=bpmKl_fvU{&3T$M znC%A%bRB?n2Ay2RFx%==uL__%jmLBR%-vg1BV{7VjP$U7J+FdgVlS5p9OGr@rrw~xc*we)^#N+te3mbVE6nI!RHr%Mc z8g7=rvO+oj?W?d!Ra;*xb2KS{k=laGckLV}Uu4bBVJnhcB7PmHox#)9&t;ucLK6V3 Ci7X5N literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ad5d02f341dfcd8fec482e500170bb711257f0b9 GIT binary patch literal 365 zcmeAS@N?(olHy`uVBq!ia0vp^Y#_|R3?#1$$yWiXgaDrq*9Uc4|IbYRzdQHA%KGnb zp8lU>{{P{#za{Vg>DGTApT26<`oGZT|IxPp5BL1v zU-JLUi^r?#|Ns9FG!HA7@@RewP=YbZ+ucQ;l~-Udki%Kv5m^kRJ;2!QWVRhhu&lr_ z9Y}+n$uRZe&q+XzzNd?02*-8SgPcqZ0vs#{O4}y1{I|c#qqlle!_6Z7fZ4}QmsI^> zTd~_#Zi!CI4@OlM##wA?*5`m)RZCnWN>UO_QmvAUQh^kMk%6Isu7RPhfq96bk(G(1 rm4TVIfuWUwfz`yy6DS&T^HVa@DsgM@K69`HsDZ)L)z4*}Q$iB}`ILMr literal 0 HcmV?d00001 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 From b5982dec403d87c8efc7775813a08246966ee4ce Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Sun, 23 Jan 2011 17:05:46 +0100 Subject: [PATCH 03/28] --- openlp/plugins/songs/forms/__init__.py | 4 +- openlp/plugins/songs/forms/songexportform.py | 229 +++++++++++++++++++ openlp/plugins/songs/lib/mediaitem.py | 8 +- openlp/plugins/songs/lib/openlyricsexport.py | 77 +++++++ openlp/plugins/songs/songsplugin.py | 17 +- 5 files changed, 331 insertions(+), 4 deletions(-) create mode 100644 openlp/plugins/songs/forms/songexportform.py create mode 100755 openlp/plugins/songs/lib/openlyricsexport.py 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..6868da4aa --- /dev/null +++ b/openlp/plugins/songs/forms/songexportform.py @@ -0,0 +1,229 @@ +# -*- 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 song export function for OpenLP. +""" +import logging +import os + +from PyQt4 import QtCore, QtGui + +from openlp.core.lib import Receiver, SettingsManager, translate +from openlp.core.ui import criticalErrorMessageBox +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 + OpenLyrics. + """ + 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_importsong.bmp') + + def setupUi(self, image): + """ + Set up the song wizard UI. + """ + OpenLPWizard.setupUi(self, image) + + def customInit(self): + """ + Song wizard specific initialisation. + """ + songs = self.plugin.manager.get_all_objects(Song) + for song in songs: + self.availableListWidget.addItem(unicode(song.id)) + + def customSignals(self): + """ + Song wizard specific signals. + """ + pass + + def addCustomPages(self): + """ + Add song wizard specific pages. + """ + # Source Page + self.sourcePage = QtGui.QWizardPage() + self.sourcePage.setObjectName(u'SourcePage') + self.sourceLayout = QtGui.QVBoxLayout(self.sourcePage) + self.sourceLayout.setObjectName(u'SourceLayout') + + self.songListsWidget = QtGui.QWidget(self.sourcePage) + self.songListsWidget.setGeometry(QtCore.QRect(8, 10, 541, 291)) + self.songListsWidget.setObjectName("songListsWidget") + self.SongListsLayout = QtGui.QHBoxLayout(self.songListsWidget) + self.SongListsLayout.setSpacing(0) + self.SongListsLayout.setMargin(0) + self.SongListsLayout.setObjectName("SongListsLayout") + self.availableGroupBox = QtGui.QGroupBox(self.songListsWidget) + self.availableGroupBox.setObjectName("availableGroupBox") + self.verticalLayout_5 = QtGui.QVBoxLayout(self.availableGroupBox) + self.verticalLayout_5.setObjectName("verticalLayout_5") + self.availableListWidget = QtGui.QListWidget(self.availableGroupBox) + self.availableListWidget.setObjectName("availableListWidget") + self.verticalLayout_5.addWidget(self.availableListWidget) + self.SongListsLayout.addWidget(self.availableGroupBox) + self.selectionWidget = QtGui.QWidget(self.songListsWidget) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Minimum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.selectionWidget.sizePolicy().hasHeightForWidth()) + self.selectionWidget.setSizePolicy(sizePolicy) + self.selectionWidget.setMaximumSize(QtCore.QSize(30, 16777215)) + self.selectionWidget.setObjectName("selectionWidget") + self.SelectionLayout = QtGui.QVBoxLayout(self.selectionWidget) + self.SelectionLayout.setSpacing(0) + self.SelectionLayout.setSizeConstraint(QtGui.QLayout.SetMinimumSize) + self.SelectionLayout.setMargin(0) + self.SelectionLayout.setObjectName("SelectionLayout") + self.addSelected = QtGui.QToolButton(self.selectionWidget) + icon = QtGui.QIcon() + icon.addPixmap(QtGui.QPixmap(":/exports/export_move_to_list.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.addSelected.setIcon(icon) + self.addSelected.setIconSize(QtCore.QSize(20, 20)) + self.addSelected.setObjectName("addSelected") + self.SelectionLayout.addWidget(self.addSelected) + self.removeSelected = QtGui.QToolButton(self.selectionWidget) + icon1 = QtGui.QIcon() + icon1.addPixmap(QtGui.QPixmap(":/imports/import_remove.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.removeSelected.setIcon(icon1) + self.removeSelected.setIconSize(QtCore.QSize(20, 20)) + self.removeSelected.setObjectName("removeSelected") + self.SelectionLayout.addWidget(self.removeSelected) + self.SongListsLayout.addWidget(self.selectionWidget) + self.selectedGroupBox = QtGui.QGroupBox(self.songListsWidget) + self.selectedGroupBox.setObjectName("selectedGroupBox") + self.verticalLayout_6 = QtGui.QVBoxLayout(self.selectedGroupBox) + self.verticalLayout_6.setObjectName("verticalLayout_6") + self.selectedListWidget = QtGui.QListWidget(self.selectedGroupBox) + self.selectedListWidget.setObjectName("selectedListWidget") + self.verticalLayout_6.addWidget(self.selectedListWidget) + self.SongListsLayout.addWidget(self.selectedGroupBox) + + + self.formatStack = QtGui.QStackedLayout() + self.formatStack.setObjectName(u'FormatStack') + self.sourceLayout.addLayout(self.formatStack) + self.addPage(self.sourcePage) + + def retranslateUi(self): + """ + Song wizard localisation. + """ + self.setWindowTitle( + translate('SongsPlugin.ImportWizardForm', 'Song Import Wizard')) + self.titleLabel.setText( + u'%s' % \ + translate('SongsPlugin.ImportWizardForm', + 'Welcome to the Song Import Wizard')) + self.informationLabel.setText( + translate('SongsPlugin.ImportWizardForm', + 'This wizard will help you to export songs from a variety of ' + 'formats. Click the next button below to start the process by ' + 'selecting a format to export from.')) + self.sourcePage.setTitle( + translate('SongsPlugin.ImportWizardForm', 'Select Import Source')) + self.sourcePage.setSubTitle( + translate('SongsPlugin.ImportWizardForm', + 'Select the export format, and where to export from.')) + + self.progressPage.setTitle( + translate('SongsPlugin.ImportWizardForm', 'Importing')) + self.progressPage.setSubTitle( + translate('SongsPlugin.ImportWizardForm', + 'Please wait while your songs are exported.')) + self.progressLabel.setText( + translate('SongsPlugin.ImportWizardForm', 'Ready.')) + self.progressBar.setFormat( + translate('SongsPlugin.ImportWizardForm', '%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.sourcePage: + return True + elif self.currentPage() == self.progressPage: + 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) + + def preWizard(self): + """ + Perform pre export tasks + """ + OpenLPWizard.preWizard(self) + self.progressLabel.setText( + translate('SongsPlugin.ImportWizardForm', 'Starting export...')) + Receiver.send_message(u'openlp_process_events') + + def performWizard(self): + """ + Perform the actual export. This method pulls in the correct exporter + class, and then runs the ``do_export`` method of the exporter to do + the actual exporting. + """ + exporter = OpenLyricsExport() + if exporter.do_export(): + self.progressLabel.setText( + translate('SongsPlugin.SongImportForm', 'Finished export.')) + else: + self.progressLabel.setText( + translate('SongsPlugin.SongImportForm', + 'Your song export failed.')) diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py index 625f99f18..0dd0df882 100644 --- a/openlp/plugins/songs/lib/mediaitem.py +++ b/openlp/plugins/songs/lib/mediaitem.py @@ -34,7 +34,7 @@ from sqlalchemy.sql import or_ from openlp.core.lib import MediaManagerItem, BaseListWithDnD, Receiver, \ ItemCapabilities, translate, check_item_selected 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 @@ -271,6 +271,12 @@ 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) + if self.export_wizard.exec_() == QtGui.QDialog.Accepted: + Receiver.send_message(u'songs_load_list') + 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..2615a7448 --- /dev/null +++ b/openlp/plugins/songs/lib/openlyricsexport.py @@ -0,0 +1,77 @@ +# -*- 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-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 # +############################################################################### +""" +The :mod:`openlyricsexport` module provides the functionality for exporting +songs from the database. +""" +import datetime +import logging +import os + +from lxml import etree + +from openlp.core.lib import translate +from openlp.plugins.songs.lib import OpenLyrics + +log = logging.getLogger(__name__) + +class OpenLyricsExport(object): + """ + This provides the Openlyrics export. + """ + def __init__(self, master_manager, song_ids, save_path): + """ + Initialise the export. + """ + log.debug(u'initialise OpenLyricsExport') + self.master_manager = master_manager + self.song_ids = song_ids + self.save_path = save_path + + def do_export(self): + """ + Export the songs. + """ + openLyrics = OpenLyrics(self.master_manager) + self.export_wizard.exportProgressBar.setMaximum(len(songs)) + for song in self.songs: + if self.stop_export_flag: + return False + self.export_wizard.incrementProgressBar(unicode(translate( + 'SongsPlugin.OpenLyricsExport', 'Exporting %s...')) % + song.title) + path = os.path.join(self.save_path, song.title + u'.xml') + xml = openLyrics.song_to_xml(song) + # Add "IMPLEMENTED_VERSION = u'0.7" to xml.py/OpenLyrics class! + xml.set(u'version', OpenLyrics.IMPLEMENTED_VERSION) + xml.set(u'createdIn', u'OpenLP 1.9.4') # Use variable + xml.set(u'modifiedIn', u'OpenLP 1.9.4') # Use variable + xml.set(u'modifiedDate', + datetime.datetime.now().strftime(u'%Y-%m-%dT%H:%M:%S')) + tree = etree.ElementTree(etree.fromstring(xml)) + tree.write(path, encoding=u'utf-8', xml_declaration=True, + pretty_print=True) + return True diff --git a/openlp/plugins/songs/songsplugin.py b/openlp/plugins/songs/songsplugin.py index 7efe73db2..02ae6f2e7 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 ' From 6d1b2973108ca8d603ce972f414f62440b714f3b Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Sun, 23 Jan 2011 17:15:05 +0100 Subject: [PATCH 04/28] --- openlp/plugins/songs/forms/songexportform.py | 75 +++++++++----------- 1 file changed, 33 insertions(+), 42 deletions(-) diff --git a/openlp/plugins/songs/forms/songexportform.py b/openlp/plugins/songs/forms/songexportform.py index 6868da4aa..9058b6a2c 100644 --- a/openlp/plugins/songs/forms/songexportform.py +++ b/openlp/plugins/songs/forms/songexportform.py @@ -87,65 +87,56 @@ class SongExportForm(OpenLPWizard): # Source Page self.sourcePage = QtGui.QWizardPage() self.sourcePage.setObjectName(u'SourcePage') - self.sourceLayout = QtGui.QVBoxLayout(self.sourcePage) + self.sourceLayout = QtGui.QHBoxLayout(self.sourcePage) self.sourceLayout.setObjectName(u'SourceLayout') - self.songListsWidget = QtGui.QWidget(self.sourcePage) - self.songListsWidget.setGeometry(QtCore.QRect(8, 10, 541, 291)) - self.songListsWidget.setObjectName("songListsWidget") - self.SongListsLayout = QtGui.QHBoxLayout(self.songListsWidget) - self.SongListsLayout.setSpacing(0) - self.SongListsLayout.setMargin(0) - self.SongListsLayout.setObjectName("SongListsLayout") - self.availableGroupBox = QtGui.QGroupBox(self.songListsWidget) - self.availableGroupBox.setObjectName("availableGroupBox") + self.availableGroupBox = QtGui.QGroupBox(self.sourcePage) + self.availableGroupBox.setObjectName(u'availableGroupBox') self.verticalLayout_5 = QtGui.QVBoxLayout(self.availableGroupBox) - self.verticalLayout_5.setObjectName("verticalLayout_5") + self.verticalLayout_5.setObjectName(u'verticalLayout_5') self.availableListWidget = QtGui.QListWidget(self.availableGroupBox) - self.availableListWidget.setObjectName("availableListWidget") + self.availableListWidget.setObjectName(u'availableListWidget') self.verticalLayout_5.addWidget(self.availableListWidget) - self.SongListsLayout.addWidget(self.availableGroupBox) - self.selectionWidget = QtGui.QWidget(self.songListsWidget) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Minimum) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.selectionWidget.sizePolicy().hasHeightForWidth()) - self.selectionWidget.setSizePolicy(sizePolicy) + self.sourceLayout.addWidget(self.availableGroupBox) + self.selectionWidget = QtGui.QWidget(self.sourcePage) self.selectionWidget.setMaximumSize(QtCore.QSize(30, 16777215)) - self.selectionWidget.setObjectName("selectionWidget") - self.SelectionLayout = QtGui.QVBoxLayout(self.selectionWidget) - self.SelectionLayout.setSpacing(0) - self.SelectionLayout.setSizeConstraint(QtGui.QLayout.SetMinimumSize) - self.SelectionLayout.setMargin(0) - self.SelectionLayout.setObjectName("SelectionLayout") + self.selectionWidget.setObjectName(u'selectionWidget') + self.selectionLayout = QtGui.QVBoxLayout(self.selectionWidget) + self.selectionLayout.setSpacing(0) + self.selectionLayout.setSizeConstraint(QtGui.QLayout.SetMinimumSize) + self.selectionLayout.setMargin(0) + self.selectionLayout.setObjectName(u'SelectionLayout') self.addSelected = QtGui.QToolButton(self.selectionWidget) icon = QtGui.QIcon() - icon.addPixmap(QtGui.QPixmap(":/exports/export_move_to_list.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) + icon.addPixmap(QtGui.QPixmap( + u':/exports/export_move_to_list.png'), + QtGui.QIcon.Normal, QtGui.QIcon.Off) self.addSelected.setIcon(icon) self.addSelected.setIconSize(QtCore.QSize(20, 20)) - self.addSelected.setObjectName("addSelected") - self.SelectionLayout.addWidget(self.addSelected) + self.addSelected.setObjectName(u'addSelected') + self.selectionLayout.addWidget(self.addSelected) self.removeSelected = QtGui.QToolButton(self.selectionWidget) - icon1 = QtGui.QIcon() - icon1.addPixmap(QtGui.QPixmap(":/imports/import_remove.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) - self.removeSelected.setIcon(icon1) + icon = QtGui.QIcon() + icon.addPixmap(QtGui.QPixmap( + u':/imports/import_remove.png'), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.removeSelected.setIcon(icon) self.removeSelected.setIconSize(QtCore.QSize(20, 20)) - self.removeSelected.setObjectName("removeSelected") - self.SelectionLayout.addWidget(self.removeSelected) - self.SongListsLayout.addWidget(self.selectionWidget) - self.selectedGroupBox = QtGui.QGroupBox(self.songListsWidget) - self.selectedGroupBox.setObjectName("selectedGroupBox") + self.removeSelected.setObjectName(u'removeSelected') + self.selectionLayout.addWidget(self.removeSelected) + self.sourceLayout.addWidget(self.selectionWidget) + self.selectedGroupBox = QtGui.QGroupBox(self.sourcePage) + self.selectedGroupBox.setObjectName(u'selectedGroupBox') self.verticalLayout_6 = QtGui.QVBoxLayout(self.selectedGroupBox) - self.verticalLayout_6.setObjectName("verticalLayout_6") + self.verticalLayout_6.setObjectName(u'verticalLayout_6') self.selectedListWidget = QtGui.QListWidget(self.selectedGroupBox) - self.selectedListWidget.setObjectName("selectedListWidget") + self.selectedListWidget.setObjectName(u'selectedListWidget') self.verticalLayout_6.addWidget(self.selectedListWidget) - self.SongListsLayout.addWidget(self.selectedGroupBox) + self.sourceLayout.addWidget(self.selectedGroupBox) - self.formatStack = QtGui.QStackedLayout() - self.formatStack.setObjectName(u'FormatStack') - self.sourceLayout.addLayout(self.formatStack) + #self.formatStack = QtGui.QStackedLayout() + #self.formatStack.setObjectName(u'FormatStack') + #self.sourceLayout.addLayout(self.formatStack) self.addPage(self.sourcePage) def retranslateUi(self): From 1e0b9cbdd4f517e32296f67b06362c01feb109e9 Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Mon, 24 Jan 2011 20:27:59 +0100 Subject: [PATCH 05/28] --- openlp/plugins/songs/forms/songexportform.py | 131 +++++++++++++++---- openlp/plugins/songs/lib/openlyricsexport.py | 35 ++--- openlp/plugins/songs/lib/opensongimport.py | 7 +- openlp/plugins/songs/lib/xml.py | 18 ++- 4 files changed, 140 insertions(+), 51 deletions(-) diff --git a/openlp/plugins/songs/forms/songexportform.py b/openlp/plugins/songs/forms/songexportform.py index 18d1c85ba..6cf172087 100644 --- a/openlp/plugins/songs/forms/songexportform.py +++ b/openlp/plugins/songs/forms/songexportform.py @@ -27,11 +27,11 @@ The song export function for OpenLP. """ import logging -import os +#import os from PyQt4 import QtCore, QtGui -from openlp.core.lib import Receiver, SettingsManager, translate +from openlp.core.lib import Receiver, translate from openlp.core.ui import criticalErrorMessageBox from openlp.core.ui.wizard import OpenLPWizard from openlp.plugins.songs.lib.db import Song @@ -59,6 +59,16 @@ class SongExportForm(OpenLPWizard): self.plugin = plugin OpenLPWizard.__init__(self, parent, plugin, u'songExportWizard', u':/wizards/wizard_importsong.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 exporters to stop their export + """ + log.debug(u'Stopping songs export') + self.stop_export_flag = True def setupUi(self, image): """ @@ -70,25 +80,22 @@ class SongExportForm(OpenLPWizard): """ Song wizard specific initialisation. """ - songs = self.plugin.manager.get_all_objects(Song) - for song in songs: - author_list = u'' - for author in song.authors: - if author_list != u'': - author_list = author_list + u', ' - author_list = author_list + author.display_name - song_title = unicode(song.title) - song_detail = u'%s (%s)' % (song_title, author_list) - song_name = QtGui.QListWidgetItem(song_detail) - song_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(song)) - self.availableListWidget.addItem(song_name) - self.availableListWidget.selectAll() + pass def customSignals(self): """ Song wizard specific signals. """ - pass + QtCore.QObject.connect(self.addSelected, + QtCore.SIGNAL(u'clicked()'), self.onAddSelectedClicked) + QtCore.QObject.connect(self.removeSelected, + QtCore.SIGNAL(u'clicked()'), self.onRemoveSelectedClicked) + QtCore.QObject.connect(self.availableListWidget, + QtCore.SIGNAL(u'itemDoubleClicked(QListWidgetItem *)'), + self.onAvailableListItemDoubleClicked) + QtCore.QObject.connect(self.selectedListWidget, + QtCore.SIGNAL(u'itemDoubleClicked(QListWidgetItem *)'), + self.onSelectedListItemDoubleClicked) def addCustomPages(self): """ @@ -105,6 +112,11 @@ class SongExportForm(OpenLPWizard): self.verticalLayout.setObjectName(u'verticalLayout') self.availableListWidget = QtGui.QListWidget(self.availableGroupBox) self.availableListWidget.setObjectName(u'availableListWidget') + self.availableListWidget.setSelectionMode( + QtGui.QAbstractItemView.ExtendedSelection) + self.availableListWidget.setSizePolicy( + QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) + self.availableListWidget.setSortingEnabled(True) self.verticalLayout.addWidget(self.availableListWidget) self.sourceLayout.addWidget(self.availableGroupBox) self.selectionWidget = QtGui.QWidget(self.sourcePage) @@ -138,6 +150,11 @@ class SongExportForm(OpenLPWizard): self.verticalLayout.setObjectName(u'verticalLayout') self.selectedListWidget = QtGui.QListWidget(self.selectedGroupBox) self.selectedListWidget.setObjectName(u'selectedListWidget') + self.selectedListWidget.setSelectionMode( + QtGui.QAbstractItemView.ExtendedSelection) + self.selectedListWidget.setSizePolicy( + QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) + self.selectedListWidget.setSortingEnabled(True) self.verticalLayout.addWidget(self.selectedListWidget) self.sourceLayout.addWidget(self.selectedGroupBox) self.addPage(self.sourcePage) @@ -151,23 +168,23 @@ class SongExportForm(OpenLPWizard): self.titleLabel.setText( u'%s' % \ translate('SongsPlugin.ExportWizardForm', - 'Welcome to the Song Export Wizard')) + 'Welcome to the Song Export Wizard')) self.informationLabel.setText( translate('SongsPlugin.ExportWizardForm', 'This wizard will help to ' 'export your songs to the free and open OpenLyrics worship song ' 'format. You can import these songs in all lyrics projection ' 'software, which supports OpenLyrics.')) self.sourcePage.setTitle( - translate('SongsPlugin.ExportWizardForm', 'Select Emport Source')) + translate('SongsPlugin.ExportWizardForm', 'Select Songs')) self.sourcePage.setSubTitle( translate('SongsPlugin.ExportWizardForm', - 'Select the export format, and where to export from.')) + 'Select the songs, you want to export.')) self.progressPage.setTitle( translate('SongsPlugin.ExportWizardForm', 'Exporting')) self.progressPage.setSubTitle( translate('SongsPlugin.ExportWizardForm', - 'Please wait while your songs are exported.')) + 'Please wait while your songs are exported.')) self.progressLabel.setText( translate('SongsPlugin.ExportWizardForm', 'Ready.')) self.progressBar.setFormat( @@ -187,10 +204,35 @@ class SongExportForm(OpenLPWizard): Validate the current page before moving on to the next page. """ if self.currentPage() == self.welcomePage: + Receiver.send_message(u'cursor_busy') + songs = self.plugin.manager.get_all_objects(Song) + for song in songs: + author_list = u'' + for author in song.authors: + if author_list != u'': + author_list = author_list + u', ' + author_list = author_list + author.display_name + song_title = unicode(song.title) + song_detail = u'%s (%s)' % (song_title, author_list) + song_name = QtGui.QListWidgetItem(song_detail) + song_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(song)) + self.availableListWidget.addItem(song_name) + self.availableListWidget.selectAll() + Receiver.send_message(u'cursor_normal') return True elif self.currentPage() == self.sourcePage: + self.selectedListWidget.selectAll() + if not self.selectedListWidget.selectedItems(): + criticalErrorMessageBox( + translate('SongsPlugin.ExportWizardForm', + 'No Song Selected'), + translate('SongsPlugin.ImportWizardForm', + 'You need to add at least one Song to export.')) + return False return True elif self.currentPage() == self.progressPage: + self.availableListWidget.clear() + self.selectedListWidget.clear() return True def registerFields(self): @@ -222,8 +264,9 @@ class SongExportForm(OpenLPWizard): class, and then runs the ``do_export`` method of the exporter to do the actual exporting. """ - exporter = OpenLyricsExport(self.plugin.manager, - self.plugin.manager.get_all_objects(Song), u'/tmp/') + songs = [item.data(QtCore.Qt.UserRole).toPyObject() + for item in self.selectedListWidget.selectedItems()] + exporter = OpenLyricsExport(self, songs, u'/tmp/') if exporter.do_export(): self.progressLabel.setText( translate('SongsPlugin.SongExportForm', 'Finished export.')) @@ -231,3 +274,47 @@ class SongExportForm(OpenLPWizard): self.progressLabel.setText( translate('SongsPlugin.SongExportForm', 'Your song export failed.')) + + def onAddSelectedClicked(self): + """ + Removes the selected items from the list of available songs and add them + to the list of selected songs. + """ + items = self.availableListWidget.selectedItems() + # Save a list with tuples which consist of the item row, and the item. + items = [(self.availableListWidget.row(item), item) for item in items] + items.sort(reverse=True) + for item in items: + self.availableListWidget.takeItem(item[0]) + self.selectedListWidget.addItem(item[1]) + + def onRemoveSelectedClicked(self): + """ + Removes the selected items from the list of selected songs and add them + back to the list of available songs. + """ + items = self.selectedListWidget.selectedItems() + # Save a list with tuples which consist of the item row, and the item. + items = [(self.selectedListWidget.row(item), item) for item in items] + items.sort(reverse=True) + for item in items: + self.selectedListWidget.takeItem(item[0]) + self.availableListWidget.addItem(item[1]) + + def onAvailableListItemDoubleClicked(self, item): + """ + Adds the double clicked item to the list of selected songs and removes + it from the list of availables songs. + """ + row = self.availableListWidget.row(item) + self.availableListWidget.takeItem(row) + self.selectedListWidget.addItem(item) + + def onSelectedListItemDoubleClicked(self, item): + """ + Adds the double clicked item back to the list of available songs and + removes it from the list of selected songs. + """ + row = self.selectedListWidget.row(item) + self.selectedListWidget.takeItem(row) + self.availableListWidget.addItem(item) diff --git a/openlp/plugins/songs/lib/openlyricsexport.py b/openlp/plugins/songs/lib/openlyricsexport.py index 3a27b2e8a..0f8f679f5 100755 --- a/openlp/plugins/songs/lib/openlyricsexport.py +++ b/openlp/plugins/songs/lib/openlyricsexport.py @@ -27,13 +27,12 @@ The :mod:`openlyricsexport` module provides the functionality for exporting songs from the database. """ -import datetime import logging import os from lxml import etree, objectify -from openlp.core.lib import translate +from openlp.core.lib import Receiver, translate from openlp.plugins.songs.lib import OpenLyrics log = logging.getLogger(__name__) @@ -42,42 +41,34 @@ class OpenLyricsExport(object): """ This provides the Openlyrics export. """ - def __init__(self, master_manager, song_ids, save_path): + def __init__(self, parent, songs, save_path): """ Initialise the export. """ log.debug(u'initialise OpenLyricsExport') - self.master_manager = master_manager - self.songs = song_ids + self.parent = parent + self.manager = parent.plugin.manager + self.songs = songs self.save_path = save_path def do_export(self): """ Export the songs. """ - openLyrics = OpenLyrics(self.master_manager) -# self.export_wizard.exportProgressBar.setMaximum(len(songs)) + openLyrics = OpenLyrics(self.manager) + self.parent.progressBar.setMaximum(len(self.songs)) for song in self.songs: -# if self.stop_export_flag: -# return False -# self.export_wizard.incrementProgressBar(unicode(translate( -# 'SongsPlugin.OpenLyricsExport', 'Exporting %s...')) % -# song.title) + 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) # Check if path exists. If not, create the directories! # What do we do with songs with the same title? I do not want to # overwrite them! path = os.path.join(self.save_path, song.title + u'.xml') - # Convert the song object to an unicode string. xml = openLyrics.song_to_xml(song) - song_xml = objectify.fromstring(xml) - # Append the necessary meta data to the song. - # (Maybe move this to the xml module? - 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')) - xml = etree.tostring(song_xml) tree = etree.ElementTree(etree.fromstring(xml)) tree.write(path, encoding=u'utf-8', xml_declaration=True, pretty_print=True) diff --git a/openlp/plugins/songs/lib/opensongimport.py b/openlp/plugins/songs/lib/opensongimport.py index eb16f4ba4..b995b5def 100644 --- a/openlp/plugins/songs/lib/opensongimport.py +++ b/openlp/plugins/songs/lib/opensongimport.py @@ -39,6 +39,7 @@ log = logging.getLogger(__name__) class OpenSongImportError(Exception): pass +#TODO: Use lxml for parsing and make sure we use methods of "SongImport" . class OpenSongImport(SongImport): """ Import songs exported from OpenSong @@ -149,7 +150,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: @@ -279,7 +280,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 @@ -295,6 +296,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/xml.py b/openlp/plugins/songs/lib/xml.py index 9694e7ce7..78028f637 100644 --- a/openlp/plugins/songs/lib/xml.py +++ b/openlp/plugins/songs/lib/xml.py @@ -60,6 +60,7 @@ The XML of `OpenLyrics `_ songs is of the format:: """ +import datetime import logging import re @@ -207,7 +208,8 @@ class OpenLyrics(object): 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. @@ -222,8 +224,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()) @@ -237,7 +245,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: @@ -450,7 +458,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". From c6505d7e042f4a3ba3ca1cffb2ede441af5041ae Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Mon, 24 Jan 2011 20:31:31 +0100 Subject: [PATCH 06/28] --- openlp/plugins/songs/forms/songexportform.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openlp/plugins/songs/forms/songexportform.py b/openlp/plugins/songs/forms/songexportform.py index 6cf172087..e8e11d8b7 100644 --- a/openlp/plugins/songs/forms/songexportform.py +++ b/openlp/plugins/songs/forms/songexportform.py @@ -95,7 +95,7 @@ class SongExportForm(OpenLPWizard): self.onAvailableListItemDoubleClicked) QtCore.QObject.connect(self.selectedListWidget, QtCore.SIGNAL(u'itemDoubleClicked(QListWidgetItem *)'), - self.onSelectedListItemDoubleClicked) + self.onSelectedListItemDoubleClicked) def addCustomPages(self): """ @@ -189,7 +189,7 @@ class SongExportForm(OpenLPWizard): translate('SongsPlugin.ExportWizardForm', 'Ready.')) self.progressBar.setFormat( translate('SongsPlugin.ExportWizardForm', '%p%')) - + self.availableGroupBox.setTitle( translate('SongsPlugin.ExportWizardForm', 'Available Songs')) self.addSelected.setText( From d087bbb53ad4827be33c2927ad05993b5dcc0ea3 Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Mon, 24 Jan 2011 21:01:09 +0100 Subject: [PATCH 07/28] verseOrder fix --- openlp/plugins/songs/forms/songexportform.py | 12 ++++++++---- openlp/plugins/songs/lib/openlyricsexport.py | 3 ++- openlp/plugins/songs/lib/xml.py | 4 ++-- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/openlp/plugins/songs/forms/songexportform.py b/openlp/plugins/songs/forms/songexportform.py index f7cc0e616..b4bc2de42 100644 --- a/openlp/plugins/songs/forms/songexportform.py +++ b/openlp/plugins/songs/forms/songexportform.py @@ -305,16 +305,20 @@ class SongExportForm(OpenLPWizard): """ Adds the double clicked item to the list of selected songs and removes it from the list of availables songs. + + ``item`` + The *QListWidgetItem* which was double clicked. """ - row = self.availableListWidget.row(item) - self.availableListWidget.takeItem(row) + self.availableListWidget.takeItem(self.availableListWidget.row(item)) self.selectedListWidget.addItem(item) def onSelectedListItemDoubleClicked(self, item): """ Adds the double clicked item back to the list of available songs and removes it from the list of selected songs. + + ``ìtem`` + The *QListWidgetItem* which was double clicked. """ - row = self.selectedListWidget.row(item) - self.selectedListWidget.takeItem(row) + self.selectedListWidget.takeItem(self.selectedListWidget.row(item)) self.availableListWidget.addItem(item) diff --git a/openlp/plugins/songs/lib/openlyricsexport.py b/openlp/plugins/songs/lib/openlyricsexport.py index 0f8f679f5..b657f891f 100755 --- a/openlp/plugins/songs/lib/openlyricsexport.py +++ b/openlp/plugins/songs/lib/openlyricsexport.py @@ -30,7 +30,7 @@ songs from the database. import logging import os -from lxml import etree, objectify +from lxml import etree from openlp.core.lib import Receiver, translate from openlp.plugins.songs.lib import OpenLyrics @@ -55,6 +55,7 @@ class OpenLyricsExport(object): """ Export the songs. """ + log.debug(u'started OpenLyricsExport') openLyrics = OpenLyrics(self.manager) self.parent.progressBar.setMaximum(len(self.songs)) for song in self.songs: diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py index 78028f637..cc2660874 100644 --- a/openlp/plugins/songs/lib/xml.py +++ b/openlp/plugins/songs/lib/xml.py @@ -486,9 +486,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] From 5b680b32f652825f00279cf005bb050634001a28 Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Mon, 24 Jan 2011 22:00:36 +0100 Subject: [PATCH 08/28] --- openlp/plugins/songs/forms/songexportform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openlp/plugins/songs/forms/songexportform.py b/openlp/plugins/songs/forms/songexportform.py index b4bc2de42..4ec6acc7c 100644 --- a/openlp/plugins/songs/forms/songexportform.py +++ b/openlp/plugins/songs/forms/songexportform.py @@ -157,7 +157,7 @@ class SongExportForm(OpenLPWizard): self.verticalLayout.addWidget(self.selectedListWidget) self.sourceLayout.addWidget(self.selectedGroupBox) self.addPage(self.sourcePage) - #TODO: Add save dialog + #TODO: Add save dialog and search box. def retranslateUi(self): """ From ea83bf26d7f6347352b061261be3d7b985dd4054 Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Tue, 25 Jan 2011 19:34:17 +0100 Subject: [PATCH 09/28] --- openlp/plugins/songs/forms/songexportform.py | 45 +++++++++++++++++++- openlp/plugins/songs/lib/xml.py | 2 + 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/openlp/plugins/songs/forms/songexportform.py b/openlp/plugins/songs/forms/songexportform.py index 3b9bf3ffd..e9904e0bb 100644 --- a/openlp/plugins/songs/forms/songexportform.py +++ b/openlp/plugins/songs/forms/songexportform.py @@ -30,7 +30,7 @@ import logging from PyQt4 import QtCore, QtGui -from openlp.core.lib import Receiver, translate +from openlp.core.lib import Receiver, SettingsManager, translate from openlp.core.ui import criticalErrorMessageBox from openlp.core.ui.wizard import OpenLPWizard from openlp.plugins.songs.lib.db import Song @@ -158,7 +158,43 @@ class SongExportForm(OpenLPWizard): self.selectedListWidget.setSortingEnabled(True) self.verticalLayout.addWidget(self.selectedListWidget) self.sourceLayout.addWidget(self.selectedGroupBox) + + """ + importLayout.setObjectName(obj_prefix + u'Layout') + filenameLabel = QtGui.QLabel(importWidget) + filenameLabel.setObjectName(obj_prefix + u'FilenameLabel') + fileLayout = QtGui.QHBoxLayout() + 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) + """ + + + self.horizontalLayout = QtGui.QHBoxLayout() + self.horizontalLayout.setObjectName(u'horizontalLayout') + + self.pathLabel = QtGui.QLabel() + self.pathLabel.setObjectName(u'pathLabel') + self.horizontalLayout.addWidget(self.pathLabel) + + self.pathEdit = QtGui.QLineEdit(self.sourcePage) + self.pathEdit.setObjectName(u'pathEdit') + + self.horizontalLayout.addWidget(self.pathEdit) + + self.browseButton = QtGui.QPushButton(self.sourcePage) + self.browseButton.setObjectName(u'browseButton') + self.horizontalLayout.addWidget(self.browseButton) + + self.sourceLayout.addLayout(self.horizontalLayout) self.addPage(self.sourcePage) + + #TODO: Add save dialog and maybe a search box. def retranslateUi(self): @@ -266,9 +302,14 @@ class SongExportForm(OpenLPWizard): class, and then runs the ``do_export`` method of the exporter to do the actual exporting. """ + 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) songs = [item.data(QtCore.Qt.UserRole).toPyObject() for item in self.selectedListWidget.selectedItems()] - exporter = OpenLyricsExport(self, songs, u'/tmp/') + exporter = OpenLyricsExport(self, songs, path) if exporter.do_export(): self.progressLabel.setText( translate('SongsPlugin.SongExportForm', 'Finished export.')) diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py index cc2660874..d9446ec12 100644 --- a/openlp/plugins/songs/lib/xml.py +++ b/openlp/plugins/songs/lib/xml.py @@ -271,6 +271,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) From 98084cbe2f012f992ba519eb5601ab5a0e7ce77a Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Tue, 25 Jan 2011 19:35:00 +0100 Subject: [PATCH 10/28] --- openlp/plugins/songs/forms/songexportform.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openlp/plugins/songs/forms/songexportform.py b/openlp/plugins/songs/forms/songexportform.py index e9904e0bb..cbf84115b 100644 --- a/openlp/plugins/songs/forms/songexportform.py +++ b/openlp/plugins/songs/forms/songexportform.py @@ -173,12 +173,11 @@ class SongExportForm(OpenLPWizard): browseButton.setObjectName(obj_prefix + u'BrowseButton') fileLayout.addWidget(browseButton) """ - self.horizontalLayout = QtGui.QHBoxLayout() self.horizontalLayout.setObjectName(u'horizontalLayout') - self.pathLabel = QtGui.QLabel() + self.pathLabel = QtGui.QLabel(self.sourcePage) self.pathLabel.setObjectName(u'pathLabel') self.horizontalLayout.addWidget(self.pathLabel) From 54e1b8d9b73efba228eff39602ea71125c918db1 Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Wed, 26 Jan 2011 17:54:10 +0100 Subject: [PATCH 11/28] clean ups --- openlp/plugins/songs/forms/songexportform.py | 6 ++++++ openlp/plugins/songs/lib/mediaitem.py | 3 +-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/openlp/plugins/songs/forms/songexportform.py b/openlp/plugins/songs/forms/songexportform.py index 71ba83c73..512d7ec90 100644 --- a/openlp/plugins/songs/forms/songexportform.py +++ b/openlp/plugins/songs/forms/songexportform.py @@ -125,6 +125,9 @@ class SongExportForm(OpenLPWizard): self.selectionLayout.setSpacing(0) self.selectionLayout.setMargin(0) self.selectionLayout.setObjectName(u'selectionLayout') + spacerItem = QtGui.QSpacerItem(20, 0, + QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.MinimumExpanding) + self.selectionLayout.addItem(spacerItem) self.addSelected = QtGui.QToolButton(self.selectionWidget) icon = QtGui.QIcon() icon.addPixmap(QtGui.QPixmap( @@ -143,6 +146,9 @@ class SongExportForm(OpenLPWizard): self.removeSelected.setIconSize(QtCore.QSize(20, 20)) self.removeSelected.setObjectName(u'removeSelected') self.selectionLayout.addWidget(self.removeSelected) + spacerItem = QtGui.QSpacerItem(20, 0, + QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.MinimumExpanding) + self.selectionLayout.addItem(spacerItem) self.sourceLayout.addWidget(self.selectionWidget) self.selectedGroupBox = QtGui.QGroupBox(self.sourcePage) self.selectedGroupBox.setObjectName(u'selectedGroupBox') diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py index 0dd0df882..6067442c0 100644 --- a/openlp/plugins/songs/lib/mediaitem.py +++ b/openlp/plugins/songs/lib/mediaitem.py @@ -274,8 +274,7 @@ class SongMediaItem(MediaManagerItem): def onExportClick(self): if not hasattr(self, u'export_wizard'): self.export_wizard = SongExportForm(self, self.parent) - if self.export_wizard.exec_() == QtGui.QDialog.Accepted: - Receiver.send_message(u'songs_load_list') + self.export_wizard.exec_() def onNewClick(self): log.debug(u'onNewClick') From e0b6107dd5c2dd09e57d20813403b811b3cf8dc3 Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Wed, 26 Jan 2011 20:12:14 +0100 Subject: [PATCH 12/28] clean up --- openlp/plugins/songs/forms/songexportform.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/openlp/plugins/songs/forms/songexportform.py b/openlp/plugins/songs/forms/songexportform.py index 512d7ec90..21adcc93f 100644 --- a/openlp/plugins/songs/forms/songexportform.py +++ b/openlp/plugins/songs/forms/songexportform.py @@ -64,7 +64,7 @@ class SongExportForm(OpenLPWizard): def stop_export(self): """ - Sets the flag for exporters to stop their export + Sets the flag for the exporter to stop the export. """ log.debug(u'Stopping songs export') self.stop_export_flag = True @@ -140,7 +140,7 @@ class SongExportForm(OpenLPWizard): self.removeSelected = QtGui.QToolButton(self.selectionWidget) icon = QtGui.QIcon() icon.addPixmap(QtGui.QPixmap( - u':/imports/import_remove.png'), + u':/exports/export_remove.png'), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.removeSelected.setIcon(icon) self.removeSelected.setIconSize(QtCore.QSize(20, 20)) @@ -192,7 +192,7 @@ class SongExportForm(OpenLPWizard): 'Welcome to the Song Export Wizard')) self.informationLabel.setText( translate('SongsPlugin.ExportWizardForm', 'This wizard will help to ' - 'export your songs to the free and open OpenLyrics worship song ' + 'export your songs to the open and free OpenLyrics worship song ' 'format. You can import these songs in all lyrics projection ' 'software, which supports OpenLyrics.')) self.sourcePage.setTitle( @@ -213,10 +213,6 @@ class SongExportForm(OpenLPWizard): self.availableGroupBox.setTitle( translate('SongsPlugin.ExportWizardForm', 'Available Songs')) - self.addSelected.setText( - translate('SongsPlugin.ExportWizardForm', 'Select Songs')) - self.removeSelected.setText( - translate('SongsPlugin.ExportWizardForm', 'Select Songs')) self.selectedGroupBox.setTitle( translate('SongsPlugin.ExportWizardForm', 'Selected Songs')) From aa494c9ea32ded5b414cb5ceb4abd9cafc0b0210 Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Thu, 27 Jan 2011 16:13:11 +0100 Subject: [PATCH 13/28] --- openlp/plugins/songs/forms/songexportform.py | 225 +++++++++-------- openlp/plugins/songs/lib/openlyricsexport.py | 12 +- resources/forms/songexport.ui | 241 ------------------- resources/forms/songexportform.ui | 128 ++++++++++ resources/images/openlp-2.qrc | 1 + resources/images/wizard_exportsong.bmp | Bin 0 -> 172254 bytes 6 files changed, 252 insertions(+), 355 deletions(-) delete mode 100644 resources/forms/songexport.ui create mode 100644 resources/forms/songexportform.ui create mode 100644 resources/images/wizard_exportsong.bmp diff --git a/openlp/plugins/songs/forms/songexportform.py b/openlp/plugins/songs/forms/songexportform.py index 21adcc93f..f3311596e 100644 --- a/openlp/plugins/songs/forms/songexportform.py +++ b/openlp/plugins/songs/forms/songexportform.py @@ -24,7 +24,8 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ -The song export function for OpenLP. +The :mod:`songexportform` module provides the wizard for exporting songs to the +OpenLyrics format. """ import logging @@ -40,8 +41,8 @@ log = logging.getLogger(__name__) class SongExportForm(OpenLPWizard): """ - This is the Song Export Wizard, which allows easy exporting of Songs to - OpenLyrics. + This is the Song Export Wizard, which allows easy exporting of Songs to the + OpenLyrics format. """ log.info(u'SongExportForm loaded') @@ -57,7 +58,7 @@ class SongExportForm(OpenLPWizard): """ self.plugin = plugin OpenLPWizard.__init__(self, parent, plugin, u'songExportWizard', - u':/wizards/wizard_importsong.bmp') + 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) @@ -85,9 +86,9 @@ class SongExportForm(OpenLPWizard): """ Song wizard specific signals. """ - QtCore.QObject.connect(self.addSelected, + QtCore.QObject.connect(self.addButton, QtCore.SIGNAL(u'clicked()'), self.onAddSelectedClicked) - QtCore.QObject.connect(self.removeSelected, + QtCore.QObject.connect(self.removeButton, QtCore.SIGNAL(u'clicked()'), self.onRemoveSelectedClicked) QtCore.QObject.connect(self.availableListWidget, QtCore.SIGNAL(u'itemDoubleClicked(QListWidgetItem *)'), @@ -95,6 +96,8 @@ class SongExportForm(OpenLPWizard): QtCore.QObject.connect(self.selectedListWidget, QtCore.SIGNAL(u'itemDoubleClicked(QListWidgetItem *)'), self.onSelectedListItemDoubleClicked) + QtCore.QObject.connect(self.directoryButton, + QtCore.SIGNAL(u'clicked()'), self.onDirectoryButtonClicked) def addCustomPages(self): """ @@ -103,82 +106,70 @@ class SongExportForm(OpenLPWizard): # Source Page self.sourcePage = QtGui.QWizardPage() self.sourcePage.setObjectName(u'sourcePage') - self.sourceLayout = QtGui.QHBoxLayout(self.sourcePage) - self.sourceLayout.setObjectName(u'sourceLayout') - self.availableGroupBox = QtGui.QGroupBox(self.sourcePage) - self.availableGroupBox.setObjectName(u'availableGroupBox') - self.verticalLayout = QtGui.QVBoxLayout(self.availableGroupBox) + self.horizontalLayout = QtGui.QHBoxLayout(self.sourcePage) + self.horizontalLayout.setObjectName(u'horizontalLayout') + self.verticalLayout = QtGui.QVBoxLayout() self.verticalLayout.setObjectName(u'verticalLayout') - self.verticalLayout.setContentsMargins(0, -1, 0, 0) - self.availableListWidget = QtGui.QListWidget(self.availableGroupBox) - self.availableListWidget.setObjectName(u'availableListWidget') - self.availableListWidget.setSelectionMode( - QtGui.QAbstractItemView.ExtendedSelection) - self.availableListWidget.setSizePolicy( - QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) - self.availableListWidget.setSortingEnabled(True) - self.verticalLayout.addWidget(self.availableListWidget) - self.sourceLayout.addWidget(self.availableGroupBox) - self.selectionWidget = QtGui.QWidget(self.sourcePage) - self.selectionWidget.setObjectName(u'selectionWidget') - self.selectionLayout = QtGui.QVBoxLayout(self.selectionWidget) - self.selectionLayout.setSpacing(0) - self.selectionLayout.setMargin(0) - self.selectionLayout.setObjectName(u'selectionLayout') - spacerItem = QtGui.QSpacerItem(20, 0, - QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.MinimumExpanding) - self.selectionLayout.addItem(spacerItem) - self.addSelected = QtGui.QToolButton(self.selectionWidget) - icon = QtGui.QIcon() - icon.addPixmap(QtGui.QPixmap( - u':/exports/export_move_to_list.png'), - QtGui.QIcon.Normal, QtGui.QIcon.Off) - self.addSelected.setIcon(icon) - self.addSelected.setIconSize(QtCore.QSize(20, 20)) - self.addSelected.setObjectName(u'addSelected') - self.selectionLayout.addWidget(self.addSelected) - self.removeSelected = QtGui.QToolButton(self.selectionWidget) - icon = QtGui.QIcon() - icon.addPixmap(QtGui.QPixmap( - u':/exports/export_remove.png'), - QtGui.QIcon.Normal, QtGui.QIcon.Off) - self.removeSelected.setIcon(icon) - self.removeSelected.setIconSize(QtCore.QSize(20, 20)) - self.removeSelected.setObjectName(u'removeSelected') - self.selectionLayout.addWidget(self.removeSelected) - spacerItem = QtGui.QSpacerItem(20, 0, - QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.MinimumExpanding) - self.selectionLayout.addItem(spacerItem) - self.sourceLayout.addWidget(self.selectionWidget) - self.selectedGroupBox = QtGui.QGroupBox(self.sourcePage) - self.selectedGroupBox.setObjectName(u'selectedGroupBox') - self.verticalLayout = QtGui.QVBoxLayout(self.selectedGroupBox) - self.verticalLayout.setObjectName(u'verticalLayout') - self.verticalLayout.setContentsMargins(0, -1, 0, 0) - self.selectedListWidget = QtGui.QListWidget(self.selectedGroupBox) + self.gridLayout = QtGui.QGridLayout() + self.gridLayout.setObjectName(u'gridLayout') + self.selectedListWidget = QtGui.QListWidget(self.sourcePage) self.selectedListWidget.setObjectName(u'selectedListWidget') self.selectedListWidget.setSelectionMode( QtGui.QAbstractItemView.ExtendedSelection) - self.selectedListWidget.setSizePolicy( - QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) self.selectedListWidget.setSortingEnabled(True) - self.verticalLayout.addWidget(self.selectedListWidget) - self.sourceLayout.addWidget(self.selectedGroupBox) - # - self.horizontalLayout = QtGui.QHBoxLayout() - self.horizontalLayout.setObjectName(u'horizontalLayout') - self.pathLabel = QtGui.QLabel() - self.pathLabel.setObjectName(u'pathLabel') - self.horizontalLayout.addWidget(self.pathLabel) - self.pathEdit = QtGui.QLineEdit() - self.pathEdit.setObjectName(u'pathEdit') - self.horizontalLayout.addWidget(self.pathEdit) - self.browseButton = QtGui.QToolButton() - self.browseButton.setObjectName(u'browseButton') - self.horizontalLayout.addWidget(self.browseButton) - # + self.gridLayout.addWidget(self.selectedListWidget, 1, 2, 1, 1) + self.gridLayout2 = QtGui.QGridLayout() + self.gridLayout2.setObjectName(u'gridLayout2') + self.addButton = QtGui.QToolButton(self.sourcePage) + icon = QtGui.QIcon() + icon.addPixmap(QtGui.QPixmap(u':/exports/export_move_to_list.png'), + QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.addButton.setIcon(icon) + self.addButton.setObjectName(u'addButton') + self.gridLayout2.addWidget(self.addButton, 1, 0, 1, 1) + self.removeButton = QtGui.QToolButton(self.sourcePage) + icon = QtGui.QIcon() + icon.addPixmap(QtGui.QPixmap(u':/exports/export_remove.png'), + QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.removeButton.setIcon(icon) + self.removeButton.setObjectName(u'removeButton') + self.gridLayout2.addWidget(self.removeButton, 2, 0, 1, 1) + spacerItem = QtGui.QSpacerItem(20, 40, + QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) + self.gridLayout2.addItem(spacerItem, 0, 0, 1, 1) + self.gridLayout2.addItem(spacerItem, 3, 0, 1, 1) + self.gridLayout.addLayout(self.gridLayout2, 1, 1, 1, 1) + self.availableLabel = QtGui.QLabel(self.sourcePage) + self.availableLabel.setObjectName(u'availableLabel') + self.gridLayout.addWidget(self.availableLabel, 0, 0, 1, 1) + self.selectedLabel = QtGui.QLabel(self.sourcePage) + self.selectedLabel.setObjectName(u'selectedLabel') + self.gridLayout.addWidget(self.selectedLabel, 0, 2, 1, 1) + self.availableListWidget = QtGui.QListWidget(self.sourcePage) + self.availableListWidget.setObjectName(u'availableListWidget') + self.availableListWidget.setSelectionMode( + QtGui.QAbstractItemView.ExtendedSelection) + self.availableListWidget.setSortingEnabled(True) + self.gridLayout.addWidget(self.availableListWidget, 1, 0, 1, 1) + self.verticalLayout.addLayout(self.gridLayout) + self.gridLayout5 = QtGui.QGridLayout() + self.gridLayout5.setObjectName(u'gridLayout5') + self.directoryButton = QtGui.QToolButton(self.sourcePage) + icon = QtGui.QIcon() + icon.addPixmap(QtGui.QPixmap(u':/exports/export_load.png'), + QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.directoryButton.setIcon(icon) + self.directoryButton.setObjectName(u'directoryButton') + self.gridLayout5.addWidget(self.directoryButton, 0, 2, 1, 1) + self.directoryLineEdit = QtGui.QLineEdit(self.sourcePage) + self.directoryLineEdit.setObjectName(u'directoryLineEdit') + self.gridLayout5.addWidget(self.directoryLineEdit, 0, 1, 1, 1) + self.directoryLabel = QtGui.QLabel(self.sourcePage) + self.directoryLabel.setObjectName(u'directoryLabel') + self.gridLayout5.addWidget(self.directoryLabel, 0, 0, 1, 1) + self.verticalLayout.addLayout(self.gridLayout5) + self.horizontalLayout.addLayout(self.verticalLayout) self.addPage(self.sourcePage) - #TODO: Add save dialog and maybe a search box. def retranslateUi(self): """ @@ -193,13 +184,13 @@ class SongExportForm(OpenLPWizard): self.informationLabel.setText( translate('SongsPlugin.ExportWizardForm', 'This wizard will help to ' 'export your songs to the open and free OpenLyrics worship song ' - 'format. You can import these songs in all lyrics projection ' - 'software, which supports OpenLyrics.')) + 'format.')) self.sourcePage.setTitle( translate('SongsPlugin.ExportWizardForm', 'Select Songs')) self.sourcePage.setSubTitle( translate('SongsPlugin.ExportWizardForm', - 'Select the songs, you want to export.')) + 'Add the songs, you want to export to the list on the right hand ' + 'side. You can use the buttons below or double click them.')) self.progressPage.setTitle( translate('SongsPlugin.ExportWizardForm', 'Exporting')) @@ -211,37 +202,34 @@ class SongExportForm(OpenLPWizard): self.progressBar.setFormat( translate('SongsPlugin.ExportWizardForm', '%p%')) - self.availableGroupBox.setTitle( - translate('SongsPlugin.ExportWizardForm', 'Available Songs')) - self.selectedGroupBox.setTitle( - translate('SongsPlugin.ExportWizardForm', 'Selected Songs')) + self.directoryLabel.setText(translate('SongsPlugin.ExportWizardForm', + 'Directory:')) + self.availableLabel.setText( + translate('SongsPlugin.ExportWizardForm', 'Available Songs')) + self.selectedLabel.setText( + translate('SongsPlugin.ExportWizardForm', 'Selected Songs')) def validateCurrentPage(self): """ Validate the current page before moving on to the next page. """ if self.currentPage() == self.welcomePage: - 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]) - song_detail = u'%s (%s)' % (unicode(song.title), authors) - song_name = QtGui.QListWidgetItem(song_detail) - song_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(song)) - self.availableListWidget.addItem(song_name) - self.availableListWidget.selectAll() - Receiver.send_message(u'cursor_normal') return True elif self.currentPage() == self.sourcePage: - self.selectedListWidget.selectAll() - if not self.selectedListWidget.selectedItems(): + if not self.selectedListWidget.count(): criticalErrorMessageBox( translate('SongsPlugin.ExportWizardForm', 'No Song Selected'), - translate('SongsPlugin.ImportWizardForm', + translate('SongsPlugin.ExportWizardForm', 'You need to add at least one Song to export.')) return False + elif not self.directoryLineEdit.text(): + criticalErrorMessageBox( + 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() @@ -261,10 +249,25 @@ class SongExportForm(OpenLPWizard): 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]) + song_detail = u'%s (%s)' % (unicode(song.title), authors) + song_name = QtGui.QListWidgetItem(song_detail) + song_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(song)) + self.availableListWidget.addItem(song_name) + self.availableListWidget.selectAll() + Receiver.send_message(u'cursor_normal') def preWizard(self): """ - Perform pre export tasks + Perform pre export tasks. """ OpenLPWizard.preWizard(self) self.progressLabel.setText( @@ -273,18 +276,14 @@ class SongExportForm(OpenLPWizard): def performWizard(self): """ - Perform the actual export. This method pulls in the correct exporter - class, and then runs the ``do_export`` method of the exporter to do - the actual exporting. + Perform the actual export. This creates an *openlyricsexport* instance + and calls the *do_export* method. """ - 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.selectedListWidget.selectAll() songs = [item.data(QtCore.Qt.UserRole).toPyObject() for item in self.selectedListWidget.selectedItems()] - exporter = OpenLyricsExport(self, songs, path) + exporter = OpenLyricsExport( + self, songs, unicode(self.directoryLineEdit.text())) if exporter.do_export(): self.progressLabel.setText( translate('SongsPlugin.SongExportForm', 'Finished export.')) @@ -340,3 +339,15 @@ class SongExportForm(OpenLPWizard): """ self.selectedListWidget.takeItem(self.selectedListWidget.row(item)) self.availableListWidget.addItem(item) + + def onDirectoryButtonClicked(self): + """ + Called when click on the *directoryButton*. 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/openlyricsexport.py b/openlp/plugins/songs/lib/openlyricsexport.py index 68dfde93f..380db1932 100755 --- a/openlp/plugins/songs/lib/openlyricsexport.py +++ b/openlp/plugins/songs/lib/openlyricsexport.py @@ -25,7 +25,7 @@ ############################################################################### """ The :mod:`openlyricsexport` module provides the functionality for exporting -songs from the database. +songs from the database to the OpenLyrics format. """ import logging import os @@ -50,6 +50,8 @@ class OpenLyricsExport(object): 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): """ @@ -65,12 +67,8 @@ class OpenLyricsExport(object): self.parent.incrementProgressBar(unicode(translate( 'SongsPlugin.OpenLyricsExport', 'Exporting %s...')) % song.title) - # Check if path exists. If not, create the directories! - # What do we do with songs with the same title? I do not want to - # overwrite them! - path = os.path.join(self.save_path, song.title + u'.xml') xml = openLyrics.song_to_xml(song) tree = etree.ElementTree(etree.fromstring(xml)) - tree.write(path, encoding=u'utf-8', xml_declaration=True, - pretty_print=True) + 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/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/forms/songexportform.ui b/resources/forms/songexportform.ui new file mode 100644 index 000000000..f3d775a3a --- /dev/null +++ b/resources/forms/songexportform.ui @@ -0,0 +1,128 @@ + + + WizardPage + + + + 0 + 0 + 576 + 334 + + + + WizardPage + + + + + + + + + + + + + + + ... + + + + :/exports/export_move_to_list.png:/exports/export_move_to_list.png + + + + + + + ... + + + + :/exports/export_remove.png:/exports/export_remove.png + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Available Songs + + + + + + + Selected Songs + + + + + + + + + + + + + + ... + + + + :/exports/export_load.png:/exports/export_load.png + + + + + + + + + + Directory: + + + + + + + + + + + + + + diff --git a/resources/images/openlp-2.qrc b/resources/images/openlp-2.qrc index 6b9d6dd54..41424cf53 100644 --- a/resources/images/openlp-2.qrc +++ b/resources/images/openlp-2.qrc @@ -84,6 +84,7 @@ 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 0000000000000000000000000000000000000000..948422dcc4d845b05342bcd6dcf732a286aba486 GIT binary patch literal 172254 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%0D7|v^>o33f~x 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 literal 0 HcmV?d00001 From 3362fa9a84d32fd2443ac016b944e37059559924 Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Thu, 27 Jan 2011 16:42:11 +0100 Subject: [PATCH 14/28] removed not needed code --- openlp/plugins/songs/forms/songexportform.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/openlp/plugins/songs/forms/songexportform.py b/openlp/plugins/songs/forms/songexportform.py index 94abf54c8..908392b94 100644 --- a/openlp/plugins/songs/forms/songexportform.py +++ b/openlp/plugins/songs/forms/songexportform.py @@ -157,11 +157,6 @@ class SongExportForm(OpenLPWizard): self.gridLayout.addWidget(self.availableListWidget, 1, 0, 1, 1) # Button to select all songs in the "selectedListWidget". self.allSelectedButton = QtGui.QToolButton(self.sourcePage) -# sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) -# sizePolicy.setHorizontalStretch(0) -# sizePolicy.setVerticalStretch(0) -# sizePolicy.setHeightForWidth(self.allSelectedButton.sizePolicy().hasHeightForWidth()) -# self.allSelectedButton.setSizePolicy(sizePolicy) self.allSelectedButton.setObjectName(u'allSelectedButton') self.gridLayout.addWidget(self.allSelectedButton, 3, 2, 1, 1) # Button to select all songs in the "availableListWidget". From 74d20ae81b73c97718e4b49d769a561a03d1ffab Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Thu, 27 Jan 2011 19:14:55 +0100 Subject: [PATCH 15/28] removed blank lines --- openlp/plugins/songs/forms/songexportform.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openlp/plugins/songs/forms/songexportform.py b/openlp/plugins/songs/forms/songexportform.py index 908392b94..7e2ce7660 100644 --- a/openlp/plugins/songs/forms/songexportform.py +++ b/openlp/plugins/songs/forms/songexportform.py @@ -203,7 +203,6 @@ class SongExportForm(OpenLPWizard): translate('SongsPlugin.ExportWizardForm', 'Add the songs, you want to export to the list on the right hand ' 'side. You can use the buttons below or double click them.')) - self.progressPage.setTitle( translate('SongsPlugin.ExportWizardForm', 'Exporting')) self.progressPage.setSubTitle( @@ -213,7 +212,6 @@ class SongExportForm(OpenLPWizard): translate('SongsPlugin.ExportWizardForm', 'Ready.')) self.progressBar.setFormat( translate('SongsPlugin.ExportWizardForm', '%p%')) - self.directoryLabel.setText(translate('SongsPlugin.ExportWizardForm', 'Directory:')) self.availableLabel.setText( From eff7708e00dc805389014bb2154fde08a312a9d9 Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Thu, 3 Feb 2011 14:41:42 +0100 Subject: [PATCH 16/28] fixed doc --- openlp/plugins/songs/forms/songexportform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openlp/plugins/songs/forms/songexportform.py b/openlp/plugins/songs/forms/songexportform.py index 7e2ce7660..004fbdcf8 100644 --- a/openlp/plugins/songs/forms/songexportform.py +++ b/openlp/plugins/songs/forms/songexportform.py @@ -362,7 +362,7 @@ class SongExportForm(OpenLPWizard): def onDirectoryButtonClicked(self): """ - Called when click on the *directoryButton*. Opens a dialog and writes + Called when the *directoryButton* was clicked. Opens a dialog and writes the path to *directoryLineEdit*. """ path = unicode(QtGui.QFileDialog.getExistingDirectory(self, From aaba4c1b5e6c408a1e6a80efede63dbad9632de2 Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Thu, 3 Feb 2011 16:57:36 +0100 Subject: [PATCH 17/28] fixed Receiver, fixed errorBox --- openlp/core/lib/ui.py | 2 +- openlp/core/ui/__init__.py | 2 +- openlp/plugins/songs/forms/songexportform.py | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/openlp/core/lib/ui.py b/openlp/core/lib/ui.py index 8cfffef45..bb8238bf0 100644 --- a/openlp/core/lib/ui.py +++ b/openlp/core/lib/ui.py @@ -30,7 +30,7 @@ import logging from PyQt4 import QtCore, QtGui -from openlp.core.lib import translate +from openlp.core.lib import Receiver, translate log = logging.getLogger(__name__) 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/plugins/songs/forms/songexportform.py b/openlp/plugins/songs/forms/songexportform.py index 004fbdcf8..307d91570 100644 --- a/openlp/plugins/songs/forms/songexportform.py +++ b/openlp/plugins/songs/forms/songexportform.py @@ -32,7 +32,7 @@ import logging from PyQt4 import QtCore, QtGui from openlp.core.lib import Receiver, SettingsManager, translate -from openlp.core.ui import criticalErrorMessageBox +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 @@ -231,14 +231,14 @@ class SongExportForm(OpenLPWizard): return True elif self.currentPage() == self.sourcePage: if not self.selectedListWidget.count(): - criticalErrorMessageBox( + 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 elif not self.directoryLineEdit.text(): - criticalErrorMessageBox( + critical_error_message_box( translate('SongsPlugin.ExportWizardForm', 'No Save Location specified'), translate('SongsPlugin.ExportWizardForm', From 5d73211a9ff268f021bb012e91c2014928f147c4 Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Thu, 3 Feb 2011 18:37:49 +0100 Subject: [PATCH 18/28] fixed doc --- openlp/plugins/songs/lib/xml.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py index d9446ec12..da2a7a08d 100644 --- a/openlp/plugins/songs/lib/xml.py +++ b/openlp/plugins/songs/lib/xml.py @@ -159,7 +159,7 @@ class OpenLyrics(object): to/from a song. As OpenLyrics has a rich set of different features, we cannot support them - all. The following features are supported by the :class:`OpenLyrics`:: + all. The following features are supported by the :class:`OpenLyrics`: ** OpenLP does not support the attribute *type* and *lang*. From cdc188b2d5bd84d8df3948d7b7731dc97e0e48e7 Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Sun, 6 Feb 2011 18:21:32 +0100 Subject: [PATCH 19/28] --- openlp/plugins/songs/forms/songexportform.py | 245 ++++++------------- resources/forms/songexportform.ui | 148 ----------- resources/images/export_move_to_list.png | Bin 862 -> 0 bytes resources/images/export_remove.png | Bin 666 -> 0 bytes resources/images/openlp-2.qrc | 2 - 5 files changed, 79 insertions(+), 316 deletions(-) delete mode 100644 resources/forms/songexportform.ui delete mode 100644 resources/images/export_move_to_list.png delete mode 100644 resources/images/export_remove.png diff --git a/openlp/plugins/songs/forms/songexportform.py b/openlp/plugins/songs/forms/songexportform.py index 307d91570..e445db5f5 100644 --- a/openlp/plugins/songs/forms/songexportform.py +++ b/openlp/plugins/songs/forms/songexportform.py @@ -86,102 +86,61 @@ class SongExportForm(OpenLPWizard): """ Song wizard specific signals. """ - QtCore.QObject.connect(self.addButton, - QtCore.SIGNAL(u'clicked()'), self.onAddSelectedClicked) - QtCore.QObject.connect(self.removeButton, - QtCore.SIGNAL(u'clicked()'), self.onRemoveSelectedClicked) - QtCore.QObject.connect(self.availableListWidget, - QtCore.SIGNAL(u'itemDoubleClicked(QListWidgetItem *)'), - self.onAvailableListItemDoubleClicked) - QtCore.QObject.connect(self.selectedListWidget, - QtCore.SIGNAL(u'itemDoubleClicked(QListWidgetItem *)'), - self.onSelectedListItemDoubleClicked) QtCore.QObject.connect(self.directoryButton, QtCore.SIGNAL(u'clicked()'), self.onDirectoryButtonClicked) - QtCore.QObject.connect(self.allAvailableButton, - QtCore.SIGNAL(u'clicked()'), self.onAllAvailableButtonClicked) - QtCore.QObject.connect(self.allSelectedButton, - QtCore.SIGNAL(u'clicked()'), self.onAllSelectedButtonClicked) +# QtCore.QObject.connect(self.searchLineEdit, +# QtCore.SIGNAL(u'textEdited(const QString&)'), +# self.onSearchLineEditChanged) def addCustomPages(self): """ Add song wizard specific pages. """ - # Source Page - self.sourcePage = QtGui.QWizardPage() - self.sourcePage.setObjectName(u'sourcePage') - self.horizontalLayout = QtGui.QHBoxLayout(self.sourcePage) - self.horizontalLayout.setObjectName(u'horizontalLayout') - self.verticalLayout = QtGui.QVBoxLayout() - self.verticalLayout.setObjectName(u'verticalLayout') + # 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.gridLayout = QtGui.QGridLayout() +# self.gridLayout.setObjectName(u'gridLayout') + self.availableListWidget = QtGui.QListWidget(self.availableSongsPage) + self.availableListWidget.setObjectName(u'availableListWidget') +# self.gridLayout.addWidget(self.availableListWidget, 0, 0, 1, 1) +# self.searchLineEdit = QtGui.QLineEdit(self.availableSongsPage) +# self.searchLineEdit.setObjectName(u'searchLineEdit') +# self.gridLayout.addWidget(self.searchLineEdit, 1, 0, 1, 1) +# self.availableSongsLayout.addLayout(self.gridLayout) + self.availableSongsLayout.addWidget(self.availableListWidget) + 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.sourcePage) + self.selectedListWidget = QtGui.QListWidget(self.exportSongPage) self.selectedListWidget.setObjectName(u'selectedListWidget') - self.selectedListWidget.setSelectionMode( - QtGui.QAbstractItemView.ExtendedSelection) - self.selectedListWidget.setSortingEnabled(True) - self.gridLayout.addWidget(self.selectedListWidget, 1, 2, 1, 1) - self.gridLayout2 = QtGui.QGridLayout() - self.gridLayout2.setObjectName(u'gridLayout2') - self.addButton = QtGui.QToolButton(self.sourcePage) - icon = QtGui.QIcon() - icon.addPixmap(QtGui.QPixmap(u':/exports/export_move_to_list.png'), - QtGui.QIcon.Normal, QtGui.QIcon.Off) - self.addButton.setIcon(icon) - self.addButton.setObjectName(u'addButton') - self.gridLayout2.addWidget(self.addButton, 1, 0, 1, 1) - self.removeButton = QtGui.QToolButton(self.sourcePage) - icon = QtGui.QIcon() - icon.addPixmap(QtGui.QPixmap(u':/exports/export_remove.png'), - QtGui.QIcon.Normal, QtGui.QIcon.Off) - self.removeButton.setIcon(icon) - self.removeButton.setObjectName(u'removeButton') - self.gridLayout2.addWidget(self.removeButton, 2, 0, 1, 1) - spacerItem = QtGui.QSpacerItem(20, 40, - QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) - self.gridLayout2.addItem(spacerItem, 0, 0, 1, 1) - self.gridLayout2.addItem(spacerItem, 3, 0, 1, 1) - self.gridLayout.addLayout(self.gridLayout2, 1, 1, 1, 1) - self.availableLabel = QtGui.QLabel(self.sourcePage) - self.availableLabel.setObjectName(u'availableLabel') - self.gridLayout.addWidget(self.availableLabel, 0, 0, 1, 1) - self.selectedLabel = QtGui.QLabel(self.sourcePage) - self.selectedLabel.setObjectName(u'selectedLabel') - self.gridLayout.addWidget(self.selectedLabel, 0, 2, 1, 1) - self.availableListWidget = QtGui.QListWidget(self.sourcePage) - self.availableListWidget.setObjectName(u'availableListWidget') - self.availableListWidget.setSelectionMode( - QtGui.QAbstractItemView.ExtendedSelection) - self.availableListWidget.setSortingEnabled(True) - self.gridLayout.addWidget(self.availableListWidget, 1, 0, 1, 1) - # Button to select all songs in the "selectedListWidget". - self.allSelectedButton = QtGui.QToolButton(self.sourcePage) - self.allSelectedButton.setObjectName(u'allSelectedButton') - self.gridLayout.addWidget(self.allSelectedButton, 3, 2, 1, 1) - # Button to select all songs in the "availableListWidget". - self.allAvailableButton = QtGui.QToolButton(self.sourcePage) - self.allAvailableButton.setObjectName(u'allAvailableButton') - self.gridLayout.addWidget(self.allAvailableButton, 3, 0, 1, 1) - self.verticalLayout.addLayout(self.gridLayout) - self.gridLayout3 = QtGui.QGridLayout() - self.gridLayout3.setObjectName(u'gridLayout3') - self.directoryButton = QtGui.QToolButton(self.sourcePage) + 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) icon = QtGui.QIcon() icon.addPixmap(QtGui.QPixmap(u':/exports/export_load.png'), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.directoryButton.setIcon(icon) self.directoryButton.setObjectName(u'directoryButton') - self.gridLayout3.addWidget(self.directoryButton, 0, 2, 1, 1) - self.directoryLineEdit = QtGui.QLineEdit(self.sourcePage) - self.directoryLineEdit.setObjectName(u'directoryLineEdit') - self.gridLayout3.addWidget(self.directoryLineEdit, 0, 1, 1, 1) - self.directoryLabel = QtGui.QLabel(self.sourcePage) - self.directoryLabel.setObjectName(u'directoryLabel') - self.gridLayout3.addWidget(self.directoryLabel, 0, 0, 1, 1) - self.verticalLayout.addLayout(self.gridLayout3) - self.horizontalLayout.addLayout(self.verticalLayout) - self.addPage(self.sourcePage) + 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): """ @@ -197,12 +156,18 @@ class SongExportForm(OpenLPWizard): translate('SongsPlugin.ExportWizardForm', 'This wizard will help to ' 'export your songs to the open and free OpenLyrics worship song ' 'format.')) - self.sourcePage.setTitle( + self.availableSongsPage.setTitle( translate('SongsPlugin.ExportWizardForm', 'Select Songs')) - self.sourcePage.setSubTitle( + self.availableSongsPage.setSubTitle( translate('SongsPlugin.ExportWizardForm', - 'Add the songs, you want to export to the list on the right hand ' - 'side. You can use the buttons below or double click them.')) + 'Check the songs, you want to export.')) + 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( @@ -212,16 +177,6 @@ class SongExportForm(OpenLPWizard): translate('SongsPlugin.ExportWizardForm', 'Ready.')) self.progressBar.setFormat( translate('SongsPlugin.ExportWizardForm', '%p%')) - self.directoryLabel.setText(translate('SongsPlugin.ExportWizardForm', - 'Directory:')) - self.availableLabel.setText( - translate('SongsPlugin.ExportWizardForm', 'Available Songs')) - self.selectedLabel.setText( - translate('SongsPlugin.ExportWizardForm', 'Selected Songs')) - self.allSelectedButton.setText( - translate('SongsPlugin.ExportWizardForm', 'Select all')) - self.allAvailableButton.setText( - translate('SongsPlugin.ExportWizardForm', 'Select all')) def validateCurrentPage(self): """ @@ -229,15 +184,28 @@ class SongExportForm(OpenLPWizard): """ if self.currentPage() == self.welcomePage: return True - elif self.currentPage() == self.sourcePage: - if not self.selectedListWidget.count(): + elif self.currentPage() == self.availableSongsPage: + songs = [song for song in self.availableListWidget.findItems( + QtCore.QString(u''), QtCore.Qt.MatchContains) + if song.checkState() == QtCore.Qt.Checked] + if not songs: 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 - elif not self.directoryLineEdit.text(): + self.selectedListWidget.clear() + # Add the songs to the list of selectd songs. + for song in songs: + title = song.text() + new_song = QtGui.QListWidgetItem(title) + new_song.setData(QtCore.Qt.UserRole, QtCore.QVariant(song)) + new_song.setFlags(QtCore.Qt.ItemIsEnabled) + self.selectedListWidget.addItem(new_song) + return True + elif self.currentPage() == self.exportSongPage: + if not self.directoryLineEdit.text(): critical_error_message_box( translate('SongsPlugin.ExportWizardForm', 'No Save Location specified'), @@ -272,11 +240,13 @@ class SongExportForm(OpenLPWizard): for song in songs: authors = u', '.join([author.display_name for author in song.authors]) - song_detail = u'%s (%s)' % (unicode(song.title), authors) - song_name = QtGui.QListWidgetItem(song_detail) - song_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(song)) - self.availableListWidget.addItem(song_name) - self.availableListWidget.selectAll() + title = u'%s (%s)' % (unicode(song.title), authors) + song = QtGui.QListWidgetItem(title) + song.setData(QtCore.Qt.UserRole, QtCore.QVariant(song)) + song.setFlags(QtCore.Qt.ItemIsSelectable| + QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled) + song.setCheckState(QtCore.Qt.Checked) + self.availableListWidget.addItem(song) Receiver.send_message(u'cursor_normal') def preWizard(self): @@ -293,9 +263,9 @@ class SongExportForm(OpenLPWizard): Perform the actual export. This creates an *openlyricsexport* instance and calls the *do_export* method. """ - self.selectedListWidget.selectAll() - songs = [item.data(QtCore.Qt.UserRole).toPyObject() - for item in self.selectedListWidget.selectedItems()] + songs = [song.data(QtCore.Qt.UserRole).toPyObject() + for song in self.selectedListWidget.findItems( + QtCore.QString(u''), QtCore.Qt.MatchContains)] exporter = OpenLyricsExport( self, songs, unicode(self.directoryLineEdit.text())) if exporter.do_export(): @@ -305,60 +275,9 @@ class SongExportForm(OpenLPWizard): self.progressLabel.setText( translate('SongsPlugin.SongExportForm', 'Your song export failed.')) - - def onAddSelectedClicked(self): - """ - Removes the selected items from the list of available songs and add them - to the list of selected songs. - """ - items = self.availableListWidget.selectedItems() - # Save a list with tuples which consist of the item row, and the item. - items = [(self.availableListWidget.row(item), item) for item in items] - items.sort(reverse=True) - for item in items: - self.availableListWidget.takeItem(item[0]) - self.selectedListWidget.addItem(item[1]) - - def onRemoveSelectedClicked(self): - """ - Removes the selected items from the list of selected songs and add them - back to the list of available songs. - """ - items = self.selectedListWidget.selectedItems() - # Save a list with tuples which consist of the item row, and the item. - items = [(self.selectedListWidget.row(item), item) for item in items] - items.sort(reverse=True) - for item in items: - self.selectedListWidget.takeItem(item[0]) - self.availableListWidget.addItem(item[1]) - - def onAvailableListItemDoubleClicked(self, item): - """ - Adds the double clicked item to the list of selected songs and removes - it from the list of availables songs. - - ``item`` - The *QListWidgetItem* which was double clicked. - """ - self.availableListWidget.takeItem(self.availableListWidget.row(item)) - self.selectedListWidget.addItem(item) - - def onSelectedListItemDoubleClicked(self, item): - """ - Adds the double clicked item back to the list of available songs and - removes it from the list of selected songs. - - ``ìtem`` - The *QListWidgetItem* which was double clicked. - """ - self.selectedListWidget.takeItem(self.selectedListWidget.row(item)) - self.availableListWidget.addItem(item) - - def onAllAvailableButtonClicked(self): - """ - Selects all songs in the *availableListWidget*. - """ - self.availableListWidget.selectAll() + +# def onSearchLineEditChanged(self, text): +# pass def onDirectoryButtonClicked(self): """ @@ -371,9 +290,3 @@ class SongExportForm(OpenLPWizard): options=QtGui.QFileDialog.ShowDirsOnly)) SettingsManager.set_last_dir(self.plugin.settingsSection, path, 1) self.directoryLineEdit.setText(path) - - def onAllSelectedButtonClicked(self): - """ - Selects all songs in the *selectedListWidget*. - """ - self.selectedListWidget.selectAll() diff --git a/resources/forms/songexportform.ui b/resources/forms/songexportform.ui deleted file mode 100644 index ad3ada947..000000000 --- a/resources/forms/songexportform.ui +++ /dev/null @@ -1,148 +0,0 @@ - - - WizardPage - - - - 0 - 0 - 576 - 334 - - - - WizardPage - - - - - - - - - - - - - - - ... - - - - :/exports/export_move_to_list.png:/exports/export_move_to_list.png - - - - - - - ... - - - - :/exports/export_remove.png:/exports/export_remove.png - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - Available Songs - - - - - - - Selected Songs - - - - - - - - - - - 0 - 0 - - - - Select all - - - - - - - Select all - - - - - - - - - - - ... - - - - :/exports/export_load.png:/exports/export_load.png - - - - - - - - - - Directory: - - - - - - - - - - - - - - diff --git a/resources/images/export_move_to_list.png b/resources/images/export_move_to_list.png deleted file mode 100644 index 5c000585611b2a7eef251b16d157b7d3fbe343f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 862 zcmV-k1EKthP))=>P)60+m=mEULo}(6DE|o@Zn-E{+#%FBN6by>iN&WA))_C@EKH8?PsO`9y;M64pgk+G zo4(KY^=oW**AUeLtBE#EEYk$bC)G`2e1dcXlWsAycerd)^&Wu9d5P0&wb^L>f^vQ! zG8UuNGFlnXkR*z3tmp&*ekWwL9Qe_)0)eZnJ=$f>#Hl0v@F7v zuf#Z2=f%-o!IdkQ^!ztB5_xqAkpb_8=v?E}e|GukQ7l#SRydGZXRdfqM3|k$=$f~p oro44E|HtiCRwA;~*#1|)1JnysG2#JYp8x;=07*qoM6N<$f~BjP$^ZZW diff --git a/resources/images/export_remove.png b/resources/images/export_remove.png deleted file mode 100644 index ef8e685e2edc2bd322bc20b3930b3b8a4a956692..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 666 zcmV;L0%iS)P)EpEu1Bn~z?SOH%W3`5?j4Qac4I15gKU`L5t>eN996dfGWy$GdT78e~1IEWxDihex7vOw4eG#$fZhali~XI|dwy2)5q7?kev-7Va?i{8#`0ED~iS znPE8+jBfgBF#tb?!0r|!;|Uxhh~`JWR^bpU8u7y9}-^q*}IfB~Nq%ySBz{^kAz z3q)BlA;{zV9S&*>ZND1?U{GxVemB-UgWbmGokGcyqSwMaQBF)n)gu5F4?Uh4@^Ycw-*5-~sCaop*i?$X&F0Mt&l~Qb zfY1*WGoB%i7sW@t*o-kS#!v>NY9h9$NM~6&ozmxk>*nk(P@GSvbVZtA)kJKs3`pjz z2QVQIGtrHEzzyrW3909E+g3AU>}ZYus}ujmzk3qv7);hk;Q#;t07*qoM6N<$f=kC8 Ao&W#< diff --git a/resources/images/openlp-2.qrc b/resources/images/openlp-2.qrc index 3d953995e..f647eb39d 100644 --- a/resources/images/openlp-2.qrc +++ b/resources/images/openlp-2.qrc @@ -78,9 +78,7 @@ import_load.png - export_remove.png export_load.png - export_move_to_list.png wizard_exportsong.bmp From 9b1a19086965cfbf5bbbdd34f3921699b8df4955 Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Tue, 8 Feb 2011 18:05:09 +0100 Subject: [PATCH 20/28] use build_icon, bug fix --- openlp/plugins/songs/forms/songexportform.py | 38 +++++++++----------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/openlp/plugins/songs/forms/songexportform.py b/openlp/plugins/songs/forms/songexportform.py index a24aec3e2..4c918f66b 100644 --- a/openlp/plugins/songs/forms/songexportform.py +++ b/openlp/plugins/songs/forms/songexportform.py @@ -31,7 +31,7 @@ import logging from PyQt4 import QtCore, QtGui -from openlp.core.lib import Receiver, SettingsManager, translate +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 @@ -112,7 +112,6 @@ class SongExportForm(OpenLPWizard): # self.availableSongsLayout.addLayout(self.gridLayout) self.availableSongsLayout.addWidget(self.availableListWidget) self.addPage(self.availableSongsPage) - # The page with the selected songs. self.exportSongPage = QtGui.QWizardPage() self.exportSongPage.setObjectName(u'availableSongsPage') @@ -132,10 +131,7 @@ class SongExportForm(OpenLPWizard): self.directoryLineEdit.setObjectName(u'directoryLineEdit') self.horizontalLayout.addWidget(self.directoryLineEdit) self.directoryButton = QtGui.QToolButton(self.exportSongPage) - icon = QtGui.QIcon() - icon.addPixmap(QtGui.QPixmap(u':/exports/export_load.png'), - QtGui.QIcon.Normal, QtGui.QIcon.Off) - self.directoryButton.setIcon(icon) + 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) @@ -185,10 +181,10 @@ class SongExportForm(OpenLPWizard): if self.currentPage() == self.welcomePage: return True elif self.currentPage() == self.availableSongsPage: - songs = [song for song in self.availableListWidget.findItems( + items = [item for item in self.availableListWidget.findItems( QtCore.QString(u''), QtCore.Qt.MatchContains) - if song.checkState() == QtCore.Qt.Checked] - if not songs: + if item.checkState() == QtCore.Qt.Checked] + if not items: critical_error_message_box( translate('SongsPlugin.ExportWizardForm', 'No Song Selected'), @@ -196,13 +192,13 @@ class SongExportForm(OpenLPWizard): 'You need to add at least one Song to export.')) return False self.selectedListWidget.clear() - # Add the songs to the list of selectd songs. - for song in songs: - title = song.text() - new_song = QtGui.QListWidgetItem(title) - new_song.setData(QtCore.Qt.UserRole, QtCore.QVariant(song)) - new_song.setFlags(QtCore.Qt.ItemIsEnabled) - self.selectedListWidget.addItem(new_song) + # 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(): @@ -241,12 +237,12 @@ class SongExportForm(OpenLPWizard): authors = u', '.join([author.display_name for author in song.authors]) title = u'%s (%s)' % (unicode(song.title), authors) - song = QtGui.QListWidgetItem(title) - song.setData(QtCore.Qt.UserRole, QtCore.QVariant(song)) - song.setFlags(QtCore.Qt.ItemIsSelectable| + item = QtGui.QListWidgetItem(title) + item.setData(QtCore.Qt.UserRole, QtCore.QVariant(song)) + item.setFlags(QtCore.Qt.ItemIsSelectable| QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled) - song.setCheckState(QtCore.Qt.Checked) - self.availableListWidget.addItem(song) + item.setCheckState(QtCore.Qt.Checked) + self.availableListWidget.addItem(item) Receiver.send_message(u'cursor_normal') def preWizard(self): From 6a409e89575efcba8d0b2c5f3a2bc3781beee9a2 Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Wed, 9 Feb 2011 20:36:13 +0100 Subject: [PATCH 21/28] added search box, added buttons --- openlp/plugins/songs/forms/songexportform.py | 135 +++++++++++++++---- 1 file changed, 110 insertions(+), 25 deletions(-) diff --git a/openlp/plugins/songs/forms/songexportform.py b/openlp/plugins/songs/forms/songexportform.py index 4c918f66b..748bf3fdf 100644 --- a/openlp/plugins/songs/forms/songexportform.py +++ b/openlp/plugins/songs/forms/songexportform.py @@ -86,11 +86,17 @@ class SongExportForm(OpenLPWizard): """ Song wizard specific signals. """ + QtCore.QObject.connect(self.availableListWidget, + QtCore.SIGNAL(u'itemPressed(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) -# QtCore.QObject.connect(self.searchLineEdit, -# QtCore.SIGNAL(u'textEdited(const QString&)'), -# self.onSearchLineEditChanged) def addCustomPages(self): """ @@ -101,16 +107,30 @@ class SongExportForm(OpenLPWizard): self.availableSongsPage.setObjectName(u'availableSongsPage') self.availableSongsLayout = QtGui.QHBoxLayout(self.availableSongsPage) self.availableSongsLayout.setObjectName(u'availableSongsLayout') -# self.gridLayout = QtGui.QGridLayout() -# self.gridLayout.setObjectName(u'gridLayout') + self.verticalLayout = QtGui.QVBoxLayout() + self.verticalLayout.setObjectName(u'verticalLayout') self.availableListWidget = QtGui.QListWidget(self.availableSongsPage) self.availableListWidget.setObjectName(u'availableListWidget') -# self.gridLayout.addWidget(self.availableListWidget, 0, 0, 1, 1) -# self.searchLineEdit = QtGui.QLineEdit(self.availableSongsPage) -# self.searchLineEdit.setObjectName(u'searchLineEdit') -# self.gridLayout.addWidget(self.searchLineEdit, 1, 0, 1, 1) -# self.availableSongsLayout.addLayout(self.gridLayout) - self.availableSongsLayout.addWidget(self.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() @@ -145,23 +165,29 @@ class SongExportForm(OpenLPWizard): self.setWindowTitle( translate('SongsPlugin.ExportWizardForm', 'Song Export Wizard')) self.titleLabel.setText( - u'%s' % \ + 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 ' + 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', 'Deselect All')) + self.checkButton.setText( + translate('SongsPlugin.ExportWizardForm', 'Select 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.')) + translate('SongsPlugin.ExportWizardForm', + 'Select the directory you want the songs to be saved.')) self.directoryLabel.setText( translate('SongsPlugin.ExportWizardForm', 'Directory:')) self.progressPage.setTitle( @@ -181,9 +207,10 @@ class SongExportForm(OpenLPWizard): if self.currentPage() == self.welcomePage: return True elif self.currentPage() == self.availableSongsPage: - items = [item for item in self.availableListWidget.findItems( - QtCore.QString(u''), QtCore.Qt.MatchContains) - if item.checkState() == QtCore.Qt.Checked] + items = [ + item for item in self._findListWidgetItems( + self.availableListWidget) if item.checkState() + ] if not items: critical_error_message_box( translate('SongsPlugin.ExportWizardForm', @@ -241,7 +268,7 @@ class SongExportForm(OpenLPWizard): item.setData(QtCore.Qt.UserRole, QtCore.QVariant(song)) item.setFlags(QtCore.Qt.ItemIsSelectable| QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled) - item.setCheckState(QtCore.Qt.Checked) + item.setCheckState(QtCore.Qt.Unchecked) self.availableListWidget.addItem(item) Receiver.send_message(u'cursor_normal') @@ -259,9 +286,10 @@ class SongExportForm(OpenLPWizard): 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.selectedListWidget.findItems( - QtCore.QString(u''), QtCore.Qt.MatchContains)] + 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(): @@ -272,8 +300,65 @@ class SongExportForm(OpenLPWizard): translate('SongsPlugin.SongExportForm', 'Your song export failed.')) -# def onSearchLineEditChanged(self, text): -# pass + 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. + """ + return + 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): """ From 73cf235b49063ed9426d2a7ee73cd2606c556a5c Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Wed, 9 Feb 2011 20:55:53 +0100 Subject: [PATCH 22/28] doc fix --- openlp/plugins/songs/lib/xml.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py index da2a7a08d..75e03c855 100644 --- a/openlp/plugins/songs/lib/xml.py +++ b/openlp/plugins/songs/lib/xml.py @@ -90,8 +90,10 @@ class SongXML(object): 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. @@ -127,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' Date: Wed, 9 Feb 2011 21:05:44 +0100 Subject: [PATCH 23/28] enable checking the item when double clicking --- openlp/plugins/songs/forms/songexportform.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openlp/plugins/songs/forms/songexportform.py b/openlp/plugins/songs/forms/songexportform.py index 748bf3fdf..86d1b8681 100644 --- a/openlp/plugins/songs/forms/songexportform.py +++ b/openlp/plugins/songs/forms/songexportform.py @@ -87,7 +87,7 @@ class SongExportForm(OpenLPWizard): Song wizard specific signals. """ QtCore.QObject.connect(self.availableListWidget, - QtCore.SIGNAL(u'itemPressed(QListWidgetItem*)'), self.onItemPressed) + QtCore.SIGNAL(u'itemActivated(QListWidgetItem*)'), self.onItemPressed) QtCore.QObject.connect(self.searchLineEdit, QtCore.SIGNAL(u'textEdited(const QString&)'), self.onSearchLineEditChanged) @@ -324,7 +324,6 @@ class SongExportForm(OpenLPWizard): ``item`` The *QListWidgetItem* which was pressed. """ - return item.setCheckState( QtCore.Qt.Unchecked if item.checkState() else QtCore.Qt.Checked) From 000732db69151b40f5c14016815cf74aa860d7ab Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Wed, 9 Feb 2011 21:11:23 +0100 Subject: [PATCH 24/28] string fix --- openlp/plugins/songs/forms/songexportform.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openlp/plugins/songs/forms/songexportform.py b/openlp/plugins/songs/forms/songexportform.py index 86d1b8681..5d5b9a8b1 100644 --- a/openlp/plugins/songs/forms/songexportform.py +++ b/openlp/plugins/songs/forms/songexportform.py @@ -180,9 +180,9 @@ class SongExportForm(OpenLPWizard): self.searchLabel.setText( translate('SongsPlugin.ExportWizardForm', 'Search:')) self.uncheckButton.setText( - translate('SongsPlugin.ExportWizardForm', 'Deselect All')) + translate('SongsPlugin.ExportWizardForm', 'Uncheck All')) self.checkButton.setText( - translate('SongsPlugin.ExportWizardForm', 'Select All')) + translate('SongsPlugin.ExportWizardForm', 'Check All')) self.exportSongPage.setTitle( translate('SongsPlugin.ExportWizardForm', 'Select Directory')) self.exportSongPage.setSubTitle( From 5ecb3ce8b8b592bfeadab5d2a0cd71c9849f3f0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20P=C3=B5ldaru?= Date: Thu, 10 Feb 2011 00:53:35 +0200 Subject: [PATCH 25/28] works in gnome, menu icon missing --- openlp/core/ui/mainwindow.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index c87a9544a..c55787907 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':/tools/tools_add.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)) @@ -433,6 +437,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', @@ -515,6 +523,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, @@ -703,6 +713,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 From 2495aa82fcc9ccefde26d66345abde11f04fe71d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20P=C3=B5ldaru?= Date: Thu, 10 Feb 2011 02:11:05 +0200 Subject: [PATCH 26/28] better icon --- openlp/core/ui/mainwindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index c55787907..b6584f4fc 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -217,7 +217,7 @@ class Ui_MainWindow(object): u':/tools/tools_add.png') mainWindow.actionList.add_action(self.ToolsAddToolItem, u'Tools') self.ToolsOpenDataFolder = icon_action(mainWindow, u'ToolsOpenDataFolder', - u':/tools/tools_add.png') + 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') From 8abfd2ce14384a4f9e86112a26338f18fe897c80 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Thu, 10 Feb 2011 07:25:08 +0200 Subject: [PATCH 27/28] Fix docstrings and update documentation. --- documentation/api/source/openlp.rst | 7 - documentation/api/source/plugins/bibles.rst | 2 +- openlp/core/ui/servicemanager.py | 2 +- openlp/plugins/bibles/lib/__init__.py | 134 ++++++++++++-------- openlp/plugins/songs/lib/songimport.py | 4 +- openlp/plugins/songs/lib/xml.py | 43 ++++--- 6 files changed, 105 insertions(+), 87 deletions(-) delete mode 100644 documentation/api/source/openlp.rst 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/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 4d36f4aec..eb08be377 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/plugins/bibles/lib/__init__.py b/openlp/plugins/bibles/lib/__init__.py index e6fff1cc8..496535461 100644 --- a/openlp/plugins/bibles/lib/__init__.py +++ b/openlp/plugins/bibles/lib/__init__.py @@ -62,69 +62,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) @@ -194,7 +215,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): """ @@ -207,7 +228,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/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..4bf11887a 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:: * tag. + Add a verse to the ```` tag. ``type`` A string denoting the type of verse. Possible values are "V", @@ -158,59 +158,60 @@ class OpenLyrics(object): to/from a song. As OpenLyrics has a rich set of different features, we cannot support them - all. The following features are supported by the :class:`OpenLyrics`:: + all. The following features are supported by the :class:`OpenLyrics` class: - ** + ```` 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. - ** + ```` OpenLP supports this property. + """ def __init__(self, manager): self.manager = manager From e52cae13fd38e87a68dcfee357369a4c02761cd1 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Thu, 10 Feb 2011 08:36:41 +0200 Subject: [PATCH 28/28] Some conditional configuration. --- documentation/manual/source/conf.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/documentation/manual/source/conf.py b/documentation/manual/source/conf.py index 70b4fb2f6..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,19 +92,21 @@ 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 = '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 = [u'../themes']