diff --git a/.bzrignore b/.bzrignore index 2620fea42..1d2bb8267 100644 --- a/.bzrignore +++ b/.bzrignore @@ -20,3 +20,4 @@ _eric4project openlp/core/resources.py.old *.qm resources/windows/warnOpenLP.txt +openlp.cfg diff --git a/LICENSE b/LICENSE index d511905c1..fd94e166f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,12 +1,12 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. - Preamble + Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public @@ -56,7 +56,7 @@ patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. - GNU GENERAL PUBLIC LICENSE + GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains @@ -255,7 +255,7 @@ make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. - NO WARRANTY + NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN @@ -277,9 +277,9 @@ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - END OF TERMS AND CONDITIONS + END OF TERMS AND CONDITIONS - How to Apply These Terms to Your New Programs + How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it diff --git a/MANIFEST.in b/MANIFEST.in index 992685bcf..35e83e30f 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,13 +4,11 @@ recursive-include openlp *.csv recursive-include openlp *.html recursive-include openlp *.js recursive-include openlp *.css -recursive-include openlp *.qm +recursive-include openlp *.png recursive-include documentation * -recursive-include resources/forms * -recursive-include resources/i18n * -recursive-include resources/images * -recursive-include scripts *.py -include resources/*.desktop +recursive-include resources * +recursive-include scripts * include copyright.txt include LICENSE +include README.txt include openlp/.version diff --git a/README.txt b/README.txt new file mode 100644 index 000000000..b937e1d5f --- /dev/null +++ b/README.txt @@ -0,0 +1,16 @@ +OpenLP 2.0 +========== + +You're probably reading this because you've just downloaded the source code for +OpenLP 2.0. If you are looking for the installer file, please go to the download +page on the web site:: + + http://openlp.org/en/download.html + +If you're looking for how to contribute to OpenLP, then please look at the +OpenLP wiki:: + + http://wiki.openlp.org/ + +Thanks for downloading OpenLP 2.0! + diff --git a/copyright.txt b/copyright.txt index c99a64287..df5563844 100644 --- a/copyright.txt +++ b/copyright.txt @@ -4,11 +4,12 @@ ############################################################################### # OpenLP - Open Source Lyrics Projection # # --------------------------------------------------------------------------- # -# Copyright (c) 2008-2010 Raoul Snyman # -# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # -# Gorven, Scott Guerrieri, Meinert Jordan, Andreas Preikschat, Christian # -# Richter, Philip Ridout, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # -# Carsten Tinggaard, Frode Woldsund # +# Copyright (c) 2008-2011 Raoul Snyman # +# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # +# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias # +# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, 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 # diff --git a/demo_theme.xml b/demo_theme.xml deleted file mode 100644 index 118a1d7d4..000000000 --- a/demo_theme.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - openlp.org 2.0 Demo Theme - 2 - ./openlp/core/test/data_for_tests/treesbig.jpg - clBlack - - Tahoma - clWhite - 16 - -1 - $00000001 - -1 - clRed - 2 - 2 - diff --git a/documentation/PluginDevelopersGuide.txt b/documentation/PluginDevelopersGuide.txt deleted file mode 100644 index 79e8ef1b2..000000000 --- a/documentation/PluginDevelopersGuide.txt +++ /dev/null @@ -1,8 +0,0 @@ -openlp.org 2.x Plugin Developer's Guide -======================================================================== - -Introduction ------------- -This document will show you how to write your own module for openlp.org. -openlp.org has been written in plugins so that you can add your own -functionality to openlp.org. diff --git a/documentation/SongFormat.txt b/documentation/SongFormat.txt deleted file mode 100644 index 31b202dd6..000000000 --- a/documentation/SongFormat.txt +++ /dev/null @@ -1,124 +0,0 @@ -openlp.org 2.x Song Database Structure -======================================================================== - -Introduction ------------- -The song database in openlp.org 2.x is similar to the 1.x format. The -biggest differences are the addition of extra tables, and the use of -SQLite version 3. - -The song database contains the following tables: -- authors -- authors_songs -- song_books -- songs -- songs_topics -- topics - - -"authors" Table ---------------- -This table holds the names of all the authors. It has the following -columns: - -* id -* first_name -* last_name -* display_name - - -"authors_songs" Table ---------------------- -This is a bridging table between the "authors" and "songs" tables, which -serves to create a many-to-many relationship between the two tables. It -has the following columns: - -* author_id -* song_id - - -"song_books" Table ------------------- -The "song_books" table holds a list of books that a congregation gets -their songs from, or old hymnals now no longer used. This table has the -following columns: - -* id -* name -* publisher - - -"songs" Table -------------- -This table contains the songs, and each song has a list of attributes. -The "songs" table has the following columns: - -* id -* song_book_id -* title -* lyrics -* verse_order -* copyright -* comments -* ccli_number -* song_number -* theme_name -* search_title -* search_lyrics - - -"songs_topics" Table --------------------- -This is a bridging table between the "songs" and "topics" tables, which -serves to create a many-to-many relationship between the two tables. It -has the following columns: - -* song_id -* topic_id - - -"topics" Table --------------- -The topics table holds a selection of topics that songs can cover. This -is useful when a worship leader wants to select songs with a certain -theme. This table has the following columns: - -* id -* name - - -The lyrics definition (more or less similar to interformat to/from ChangingSong -The tags can also be used within the lyrics test. - -! Please note that this format has been checked at http://validator.w3.org/#validate_by_upload - - - Amazing Grace - - name of verse specific theme (optional) - any text (optional) - - Amazing grace, how ... - - - A b c - D e f - - ... - - - name of verse specific theme (optional) - any text (optional) - ... - - - - Erstaunliche Anmut - - Erstaunliche Anmut, wie - ... - - - ... - - diff --git a/documentation/api/Makefile b/documentation/api/Makefile deleted file mode 100644 index 70c821142..000000000 --- a/documentation/api/Makefile +++ /dev/null @@ -1,88 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source - -.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - -rm -rf build/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) build/html - @echo - @echo "Build finished. The HTML pages are in build/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) build/dirhtml - @echo - @echo "Build finished. The HTML pages are in build/dirhtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) build/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) build/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) build/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in build/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) build/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in build/qthelp, like this:" - @echo "# qcollectiongenerator build/qthelp/OpenLP.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile build/qthelp/OpenLP.qhc" - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) build/latex - @echo - @echo "Build finished; the LaTeX files are in build/latex." - @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ - "run these through (pdf)latex." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) build/changes - @echo - @echo "The overview file is in build/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) build/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in build/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) build/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in build/doctest/output.txt." diff --git a/documentation/api/make.bat b/documentation/api/make.bat deleted file mode 100644 index 8d21b45ce..000000000 --- a/documentation/api/make.bat +++ /dev/null @@ -1,112 +0,0 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -set SPHINXBUILD=sphinx-build -set ALLSPHINXOPTS=-d build/doctrees %SPHINXOPTS% source -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. changes to make an overview over all changed/added/deprecated items - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (build\*) do rmdir /q /s %%i - del /q /s build\* - goto end -) - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% build/html - echo. - echo.Build finished. The HTML pages are in build/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% build/dirhtml - echo. - echo.Build finished. The HTML pages are in build/dirhtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% build/pickle - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% build/json - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% build/htmlhelp - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in build/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% build/qthelp - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in build/qthelp, like this: - echo.^> qcollectiongenerator build\qthelp\OpenLP.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile build\qthelp\OpenLP.ghc - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% build/latex - echo. - echo.Build finished; the LaTeX files are in build/latex. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% build/changes - echo. - echo.The overview file is in build/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% build/linkcheck - echo. - echo.Link check complete; look for any errors in the above output ^ -or in build/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% build/doctest - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in build/doctest/output.txt. - goto end -) - -:end diff --git a/documentation/api/source/conf.py b/documentation/api/source/conf.py deleted file mode 100644 index 51ecfee0c..000000000 --- a/documentation/api/source/conf.py +++ /dev/null @@ -1,202 +0,0 @@ -# -*- coding: utf-8 -*- -# -# OpenLP documentation build configuration file, created by -# sphinx-quickstart on Fri Jul 10 17:20:40 2009. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import os -import sys - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.insert(0, os.path.abspath(os.path.join('..', '..', '..'))) - -# -- General configuration ----------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc'] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -source_encoding = 'utf-8' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'OpenLP' -copyright = u'2004-2010, Raoul Snyman' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '2.0' -# The full version, including alpha/beta/rc tags. -release = '1.9.3' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of documents that shouldn't be included in the build. -#unused_docs = [] - -# List of directories, relative to source directory, that shouldn't be searched -# for source files. -exclude_trees = [] - -# The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -add_module_names = False - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - - -# -- Options for HTML output --------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. Major themes that come with -# Sphinx are currently 'default' and 'sphinxdoc'. -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' -} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -html_title = 'OpenLP 2.0 Developer API' - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_use_modindex = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = '' - -# Output file base name for HTML help builder. -htmlhelp_basename = 'OpenLP-2.0-api' - - -# -- Options for LaTeX output -------------------------------------------------- - -# The paper size ('letter' or 'a4'). -#latex_paper_size = 'letter' - -# The font size ('10pt', '11pt' or '12pt'). -#latex_font_size = '10pt' - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [ - ('index', 'OpenLP.tex', u'OpenLP 2.0 Developer API', - u'Raoul Snyman', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# Additional stuff for the LaTeX preamble. -#latex_preamble = '' - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_use_modindex = True diff --git a/documentation/api/source/core/index.rst b/documentation/api/source/core/index.rst deleted file mode 100644 index 8555e1ebe..000000000 --- a/documentation/api/source/core/index.rst +++ /dev/null @@ -1,15 +0,0 @@ -.. _core-index: - -:mod:`core` Module -================== - -.. automodule:: openlp.core - :members: - -.. toctree:: - :maxdepth: 2 - - lib - theme - ui - utils \ No newline at end of file diff --git a/documentation/api/source/core/lib.rst b/documentation/api/source/core/lib.rst deleted file mode 100644 index 6ca952d7d..000000000 --- a/documentation/api/source/core/lib.rst +++ /dev/null @@ -1,67 +0,0 @@ -.. _core-lib: - -Object Library -============== - -.. automodule:: openlp.core.lib - :members: - -:mod:`BaseListWithDnD` ----------------------- - -.. autoclass:: openlp.core.lib.baselistwithdnd.BaseListWithDnD - :members: - -:mod:`EventReceiver` --------------------- - -.. autoclass:: openlp.core.lib.eventreceiver.EventReceiver - :members: - -:mod:`MediaManagerItem` ------------------------ - -.. autoclass:: openlp.core.lib.mediamanageritem.MediaManagerItem - :members: - -:mod:`Plugin` -------------- - -.. autoclass:: openlp.core.lib.plugin.Plugin - :members: - -:mod:`PluginManager` --------------------- - -.. autoclass:: openlp.core.lib.pluginmanager.PluginManager - :members: - -:mod:`Renderer` ---------------- - -.. autoclass:: openlp.core.lib.renderer.Renderer - :members: - -:mod:`RenderManager` --------------------- - -.. autoclass:: openlp.core.lib.rendermanager.RenderManager - :members: - -:mod:`ServiceItem` ------------------- - -.. autoclass:: openlp.core.lib.serviceitem.ServiceItem - :members: - -:mod:`SettingsTab` ------------------- - -.. autoclass:: openlp.core.lib.settingstab.SettingsTab - :members: - -:mod:`OpenLPToolbar` --------------------- - -.. autoclass:: openlp.core.lib.toolbar.OpenLPToolbar - :members: diff --git a/documentation/api/source/core/theme.rst b/documentation/api/source/core/theme.rst deleted file mode 100644 index 3dbc7a6ec..000000000 --- a/documentation/api/source/core/theme.rst +++ /dev/null @@ -1,8 +0,0 @@ -.. _core-theme: - -:mod:`theme` Module -=================== - -.. automodule:: openlp.core.theme - :members: - diff --git a/documentation/api/source/core/ui.rst b/documentation/api/source/core/ui.rst deleted file mode 100644 index 63db7478e..000000000 --- a/documentation/api/source/core/ui.rst +++ /dev/null @@ -1,27 +0,0 @@ -.. _core-ui: - -User Interface -============== - -.. automodule:: openlp.core.ui - -Main Windows ------------- - -.. autoclass:: openlp.core.ui.mainwindow.MainWindow - :members: - -.. autoclass:: openlp.core.ui.maindisplay.MainDisplay - :members: - -Managers --------- - -.. autoclass:: openlp.core.ui.servicemanager.ServiceManager - :members: - -.. autoclass:: openlp.core.ui.mediadockmanager.MediaDockManager - :members: - -.. autoclass:: openlp.core.ui.thememanager.ThemeManager - :members: diff --git a/documentation/api/source/core/utils.rst b/documentation/api/source/core/utils.rst deleted file mode 100644 index d0c6a672b..000000000 --- a/documentation/api/source/core/utils.rst +++ /dev/null @@ -1,7 +0,0 @@ -.. _core-utils: - -Utilities -========= - -.. automodule:: openlp.core.utils - :members: diff --git a/documentation/api/source/index.rst b/documentation/api/source/index.rst deleted file mode 100644 index e1aeebbab..000000000 --- a/documentation/api/source/index.rst +++ /dev/null @@ -1,27 +0,0 @@ -.. OpenLP documentation master file, created by - sphinx-quickstart on Fri Jul 10 17:20:40 2009. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome -======= - -Welcome to the OpenLP 2.0 API Documentation! In here you will find all -information relating to OpenLP's core classes, core plugins, and anything else -deemed necessary or interesting by the developers. - -Contents: - -.. toctree:: - :maxdepth: 2 - - core/index - plugins/index - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - 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/alerts.rst b/documentation/api/source/plugins/alerts.rst deleted file mode 100644 index e7cf33d63..000000000 --- a/documentation/api/source/plugins/alerts.rst +++ /dev/null @@ -1,31 +0,0 @@ -.. _plugins-alerts: - -Alerts Plugin -============= - -.. automodule:: openlp.plugins.alerts - :members: - -Plugin Class ------------- - -.. autoclass:: openlp.plugins.alerts.alertsplugin.AlertsPlugin - :members: - -Forms ------ - -.. automodule:: openlp.plugins.alerts.forms - :members: - -.. autoclass:: openlp.plugins.alerts.forms.alertform.AlertForm - :members: - -Helper Classes & Functions --------------------------- - -.. automodule:: openlp.plugins.alerts.lib - :members: - -.. automodule:: openlp.plugins.alerts.lib.db - :members: diff --git a/documentation/api/source/plugins/bibles.rst b/documentation/api/source/plugins/bibles.rst deleted file mode 100644 index 67162d414..000000000 --- a/documentation/api/source/plugins/bibles.rst +++ /dev/null @@ -1,55 +0,0 @@ -.. _plugins-bibles: - -Bibles Plugin -============= - -.. automodule:: openlp.plugins.bibles - :members: - -Plugin Class ------------- - -.. autoclass:: openlp.plugins.bibles.bibleplugin.BiblePlugin - :members: - -Forms ------ - -.. automodule:: openlp.plugins.bibles.forms - :members: - -.. autoclass:: openlp.plugins.bibles.forms.importwizardform.ImportWizardForm - :members: - -Helper Classes & Functions --------------------------- - -.. automodule:: openlp.plugins.bibles.lib - :members: - -.. automodule:: openlp.plugins.bibles.lib.db - :members: - -.. automodule:: openlp.plugins.bibles.lib.biblestab - :members: - -.. automodule:: openlp.plugins.bibles.lib.manager - :members: - -.. automodule:: openlp.plugins.bibles.lib.mediaitem - :members: - -Bible Importers ---------------- - -.. automodule:: openlp.plugins.bibles.lib.csvbible - :members: - -.. automodule:: openlp.plugins.bibles.lib.http - :members: - -.. automodule:: openlp.plugins.bibles.lib.osis - :members: - -.. automodule:: openlp.plugins.bibles.lib.opensong - :members: diff --git a/documentation/api/source/plugins/custom.rst b/documentation/api/source/plugins/custom.rst deleted file mode 100644 index f50b86d41..000000000 --- a/documentation/api/source/plugins/custom.rst +++ /dev/null @@ -1,34 +0,0 @@ -.. _plugins-custom: - -Custom Slides Plugin -==================== - -.. automodule:: openlp.plugins.custom - :members: - -Plugin Class ------------- - -.. autoclass:: openlp.plugins.custom.customplugin.CustomPlugin - :members: - -Forms ------ - -.. automodule:: openlp.plugins.custom.forms - :members: - -.. autoclass:: openlp.plugins.custom.forms.editcustomform.EditCustomForm - :members: - -.. autoclass:: openlp.plugins.custom.forms.editcustomslideform.EditCustomSlideForm - :members: - -Helper Classes & Functions --------------------------- - -.. automodule:: openlp.plugins.custom.lib - :members: - -.. automodule:: openlp.plugins.custom.lib.mediaitem - :members: diff --git a/documentation/api/source/plugins/images.rst b/documentation/api/source/plugins/images.rst deleted file mode 100644 index 1007fc64c..000000000 --- a/documentation/api/source/plugins/images.rst +++ /dev/null @@ -1,22 +0,0 @@ -.. _plugins-images: - -Images Plugin -============= - -.. automodule:: openlp.plugins.images - :members: - -Plugin Class ------------- - -.. autoclass:: openlp.plugins.images.imageplugin.ImagePlugin - :members: - -Helper Classes & Functions --------------------------- - -.. automodule:: openlp.plugins.images.lib - :members: - -.. automodule:: openlp.plugins.images.lib.mediaitem - :members: diff --git a/documentation/api/source/plugins/index.rst b/documentation/api/source/plugins/index.rst deleted file mode 100644 index 78126ff9e..000000000 --- a/documentation/api/source/plugins/index.rst +++ /dev/null @@ -1,20 +0,0 @@ -.. _plugins-index: - -Plugins -======= - -.. automodule:: openlp.plugins - :members: - -.. toctree:: - :maxdepth: 2 - - songs - bibles - presentations - media - images - custom - remotes - songusage - alerts diff --git a/documentation/api/source/plugins/media.rst b/documentation/api/source/plugins/media.rst deleted file mode 100644 index a8486c9b4..000000000 --- a/documentation/api/source/plugins/media.rst +++ /dev/null @@ -1,22 +0,0 @@ -.. _plugins-media: - -Media Plugin -============ - -.. automodule:: openlp.plugins.media - :members: - -Plugin Class ------------- - -.. autoclass:: openlp.plugins.media.mediaplugin.MediaPlugin - :members: - -Helper Classes & Functions --------------------------- - -.. automodule:: openlp.plugins.media.lib - :members: - -.. automodule:: openlp.plugins.media.lib.mediaitem - :members: diff --git a/documentation/api/source/plugins/presentations.rst b/documentation/api/source/plugins/presentations.rst deleted file mode 100644 index dd688ddf6..000000000 --- a/documentation/api/source/plugins/presentations.rst +++ /dev/null @@ -1,40 +0,0 @@ -.. _plugins-presentations: - -Presentations Plugin -==================== - -Plugin Class ------------- - -.. autoclass:: openlp.plugins.presentations.presentationplugin.PresentationPlugin - :members: - -Helper Classes & Functions --------------------------- - -.. automodule:: openlp.plugins.presentations.lib - :members: - -.. automodule:: openlp.plugins.presentations.lib.mediaitem - :members: - -.. automodule:: openlp.plugins.presentations.lib.presentationtab - :members: - -.. automodule:: openlp.plugins.presentations.lib.messagelistener - :members: - -.. automodule:: openlp.plugins.presentations.lib.presentationcontroller - :members: - -Presentation Application Controllers ------------------------------------- - -.. automodule:: openlp.plugins.presentations.lib.impresscontroller - :members: - -.. automodule:: openlp.plugins.presentations.lib.pptviewcontroller - :members: - -.. automodule:: openlp.plugins.presentations.lib.powerpointcontroller - :members: diff --git a/documentation/api/source/plugins/remotes.rst b/documentation/api/source/plugins/remotes.rst deleted file mode 100644 index 0bcd37119..000000000 --- a/documentation/api/source/plugins/remotes.rst +++ /dev/null @@ -1,19 +0,0 @@ -.. _plugins-remotes: - -Remotes Plugin -============== - -.. automodule:: openlp.plugins.remotes - :members: - -Plugin Class ------------- - -.. autoclass:: openlp.plugins.remotes.remoteplugin.RemotesPlugin - :members: - -Helper Classes & Functions --------------------------- - -.. automodule:: openlp.plugins.remotes.lib - :members: diff --git a/documentation/api/source/plugins/songs.rst b/documentation/api/source/plugins/songs.rst deleted file mode 100644 index fed9907a2..000000000 --- a/documentation/api/source/plugins/songs.rst +++ /dev/null @@ -1,103 +0,0 @@ -.. _plugins-songs: - -Songs Plugin -============ - -.. automodule:: openlp.plugins.songs - :members: - -Plugin Class ------------- - -.. autoclass:: openlp.plugins.songs.songsplugin.SongsPlugin - :members: - -Forms ------ - -.. automodule:: openlp.plugins.songs.forms - :members: - -.. autoclass:: openlp.plugins.songs.forms.authorsform.AuthorsForm - :members: - -.. autoclass:: openlp.plugins.songs.forms.editsongform.EditSongForm - :members: - -.. autoclass:: openlp.plugins.songs.forms.editverseform.EditVerseForm - :members: - -.. autoclass:: openlp.plugins.songs.forms.songbookform.SongBookForm - :members: - -.. autoclass:: openlp.plugins.songs.forms.songimportform.SongImportForm - :members: - -.. autoclass:: openlp.plugins.songs.forms.songmaintenanceform.SongMaintenanceForm - :members: - -.. autoclass:: openlp.plugins.songs.forms.topicsform.TopicsForm - :members: - -Helper Classes & Functions --------------------------- - -.. automodule:: openlp.plugins.songs.lib - :members: - -.. automodule:: openlp.plugins.songs.lib.db - :members: - -.. automodule:: openlp.plugins.songs.lib.importer - :members: - -.. automodule:: openlp.plugins.songs.lib.mediaitem - :members: - -.. autoclass:: openlp.plugins.songs.lib.mediaitem.SongListView - :members: - -.. automodule:: openlp.plugins.songs.lib.songimport - :members: - -.. automodule:: openlp.plugins.songs.lib.songstab - :members: - -.. automodule:: openlp.plugins.songs.lib.xml - :members: - -Song Importers --------------- - -.. automodule:: openlp.plugins.songs.lib.cclifileimport - :members: - -.. autoclass:: openlp.plugins.songs.lib.cclifileimport.CCLIFileImportError - :members: - -.. automodule:: openlp.plugins.songs.lib.ewimport - :members: - -.. autoclass:: openlp.plugins.songs.lib.ewimport.FieldDescEntry - :members: - -.. automodule:: openlp.plugins.songs.lib.olp1import - :members: - -.. automodule:: openlp.plugins.songs.lib.olpimport - :members: - -.. automodule:: openlp.plugins.songs.lib.oooimport - :members: - -.. automodule:: openlp.plugins.songs.lib.opensongimport - :members: - -.. automodule:: openlp.plugins.songs.lib.sofimport - :members: - -.. automodule:: openlp.plugins.songs.lib.songbeamerimport - :members: - -.. automodule:: openlp.plugins.songs.lib.wowimport - :members: diff --git a/documentation/api/source/plugins/songusage.rst b/documentation/api/source/plugins/songusage.rst deleted file mode 100644 index e4804ea34..000000000 --- a/documentation/api/source/plugins/songusage.rst +++ /dev/null @@ -1,34 +0,0 @@ -.. _plugins-songusage: - -Song Usage Plugin -================= - -.. automodule:: openlp.plugins.songusage - :members: - -Plugin Class ------------- - -.. autoclass:: openlp.plugins.songusage.songusageplugin.SongUsagePlugin - :members: - -Forms ------ - -.. automodule:: openlp.plugins.songusage.forms - :members: - -.. autoclass:: openlp.plugins.songusage.forms.songusagedeleteform.SongUsageDeleteForm - :members: - -.. autoclass:: openlp.plugins.songusage.forms.songusagedetailform.SongUsageDetailForm - :members: - -Helper Classes & Functions --------------------------- - -.. automodule:: openlp.plugins.songusage.lib - :members: - -.. automodule:: openlp.plugins.songusage.lib.db - :members: diff --git a/documentation/manual.txt b/documentation/manual.txt new file mode 100644 index 000000000..55a3f7d98 --- /dev/null +++ b/documentation/manual.txt @@ -0,0 +1,7 @@ +OpenLP Manual +============= + +If you're reading this file, you're probably looking for the OpenLP manual. The +manual is hosted online at http://manual.openlp.org/. If you want to help with +the manual, contact the OpenLP team via IRC in the #openlp.org channel on the +Freenode network. diff --git a/documentation/manual/Makefile b/documentation/manual/Makefile deleted file mode 100644 index 70c821142..000000000 --- a/documentation/manual/Makefile +++ /dev/null @@ -1,88 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source - -.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - -rm -rf build/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) build/html - @echo - @echo "Build finished. The HTML pages are in build/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) build/dirhtml - @echo - @echo "Build finished. The HTML pages are in build/dirhtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) build/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) build/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) build/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in build/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) build/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in build/qthelp, like this:" - @echo "# qcollectiongenerator build/qthelp/OpenLP.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile build/qthelp/OpenLP.qhc" - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) build/latex - @echo - @echo "Build finished; the LaTeX files are in build/latex." - @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ - "run these through (pdf)latex." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) build/changes - @echo - @echo "The overview file is in build/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) build/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in build/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) build/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in build/doctest/output.txt." diff --git a/documentation/manual/make.bat b/documentation/manual/make.bat deleted file mode 100644 index 8d21b45ce..000000000 --- a/documentation/manual/make.bat +++ /dev/null @@ -1,112 +0,0 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -set SPHINXBUILD=sphinx-build -set ALLSPHINXOPTS=-d build/doctrees %SPHINXOPTS% source -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. changes to make an overview over all changed/added/deprecated items - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (build\*) do rmdir /q /s %%i - del /q /s build\* - goto end -) - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% build/html - echo. - echo.Build finished. The HTML pages are in build/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% build/dirhtml - echo. - echo.Build finished. The HTML pages are in build/dirhtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% build/pickle - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% build/json - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% build/htmlhelp - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in build/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% build/qthelp - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in build/qthelp, like this: - echo.^> qcollectiongenerator build\qthelp\OpenLP.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile build\qthelp\OpenLP.ghc - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% build/latex - echo. - echo.Build finished; the LaTeX files are in build/latex. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% build/changes - echo. - echo.The overview file is in build/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% build/linkcheck - echo. - echo.Link check complete; look for any errors in the above output ^ -or in build/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% build/doctest - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in build/doctest/output.txt. - goto end -) - -:end diff --git a/documentation/manual/source/conf.py b/documentation/manual/source/conf.py deleted file mode 100644 index 517fc2f44..000000000 --- a/documentation/manual/source/conf.py +++ /dev/null @@ -1,208 +0,0 @@ -# -*- coding: utf-8 -*- -# -# OpenLP documentation build configuration file, created by -# sphinx-quickstart on Fri Jul 10 17:20:40 2009. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import os -import sys - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath(os.path.join('..', '..'))) - -# -- General configuration ----------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -#extensions = ['sphinx.ext.autodoc'] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -source_encoding = 'utf-8' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'OpenLP' -copyright = u'2004-2010 Raoul Snyman' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '2.0' -# The full version, including alpha/beta/rc tags. -release = '1.9.3' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of documents that shouldn't be included in the build. -#unused_docs = [] - -# List of directories, relative to source directory, that shouldn't be searched -# for source files. -exclude_trees = [] - -# The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -add_module_names = False - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - - -# -- Options for HTML output --------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. Major themes that come with -# Sphinx are currently 'default' and 'sphinxdoc'. -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' -} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -html_title = u'OpenLP 2.0 User Manual' - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_use_modindex = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = '' - -# Output file base name for HTML help builder. -htmlhelp_basename = 'OpenLP-2.0-manual' - - -# -- Options for LaTeX output -------------------------------------------------- - -# The paper size ('letter' or 'a4'). -#latex_paper_size = 'letter' - -# The font size ('10pt', '11pt' or '12pt'). -#latex_font_size = '10pt' - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [ - ('index', 'OpenLP.tex', u'OpenLP 2.0 User Manual', - u'Raoul Snyman', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# Additional stuff for the LaTeX preamble. -#latex_preamble = '' - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_use_modindex = True - -# A dictionary that contains LaTeX snippets that override those Sphinx usually -# puts into the generated .tex files. -latex_elements = { - 'fontpkg': '\\usepackage{helvet}' -} diff --git a/documentation/manual/source/dualmonitors.rst b/documentation/manual/source/dualmonitors.rst deleted file mode 100644 index 7e5fdc19b..000000000 --- a/documentation/manual/source/dualmonitors.rst +++ /dev/null @@ -1,180 +0,0 @@ -================== -Dual Monitor Setup -================== - -The first step in getting OpenLP working on your system is to setup your -computer properly for dual monitors. This is not very difficult, but the steps -do vary depending on operating system. - -Most modern computers do have the ability for dual monitors. To be certain -check your computer's documentation. A typical desktop computer capable of dual -monitors will have two of, or a combination of the two connectors below. - -**VGA** - -.. image:: pics/vga.png - -**DVI** - -.. image:: pics/dvi.png - -A laptop computer setup only varies slightly, generally you will need only one -of outputs pictured above since your laptops screen serves as one of the -monitors. Sometimes with older laptops a key stroke generally involving the -:kbd:`Fn` key and another key is required to enable the second monitor on -laptops. - -Some computers also incorporate the use of :abbr:`S-Video (Separate Video)` or -:abbr:`HDMI (High-Definition Multimedia Interface)` connections. - -A typical OpenLP set up consist of your normal single monitor setup, with your -projector setup as the second monitor. With the option of extending your -desktop across the second monitor, or your operating system's equivalent. - -Microsoft Windows ------------------ - -Dual monitor setup is similar among all the currently supported Windows -releases (XP, Vista, Windows 7), but does vary slightly from one release to the -next. - -Windows 7 -^^^^^^^^^ - -Windows 7 has using a projector in mind. Simply connect your projector and -press :kbd:`Windows+P`. - -The more traditional way is also fairly straight forward. Go to -:guilabel:`Control Panel` and click on :guilabel:`Display`. This will open up -the :guilabel:`Display` dialog. You can also bypass this step by right click on -a blank area on your desktop and selecting :guilabel:`Resolution`. - -.. image:: pics/winsevendisplay.png - -Then click on the :guilabel:`Adjust resolution` link in the left pane. Enable -your projector and make sure that the selected value for :guilabel:`Multiple -displays` is :guilabel:`Extend these displays`. - -.. image:: pics/winsevenresolution.png - -Windows Vista -^^^^^^^^^^^^^ - -From :guilabel:`Control Panel` click on :guilabel:`Personalize`, or right click -a blank place on the desktop and click :guilabel:`Personalization`. - -.. image:: pics/vistapersonalize.png - -From the :guilabel:`Personalization` window click on :guilabel:`Display -Settings`. Then enable the montior that represents your projector and make sure -you have checked :guilabel:`Extend the desktop onto this monitor`. - -.. image:: pics/vistadisplaysettings.png - -Windows XP -^^^^^^^^^^ - -From :guilabel:`Control Panel` select :guilabel:`Display`, or right click on a -blank area of the desktop and select :guilabel:`Properties`. From the -:guilabel:`Display Properties` window click on the :guilabel:`Settings` tab. -Then click on the monitor that represents your projector and make sure you have -checked :guilabel:`Extend my Windows desktop onto this monitor`. - -.. image:: pics/xpdisplaysettings.png - -Linux ------ - -Due to the vast varieties of hardware, distributions, desktops, and drivers -this is not an exhaustive guide to dual monitor setup on Linux. This guide -assumes that you have properly set up any proprietary drivers if needed. You -should seek out your distributions documentation if this general guide does not -work. - -GNOME -^^^^^ - -This guide is for users of the GNOME desktop who do not use proprietary drivers. -From most distros go to :menuselection:`System --> Preferences --> Display -Settings (Monitors)`. Set up your projector with the correct resolution and make -sure that :guilabel:`Same image on all monitors` is **unchecked**. - -.. image:: pics/gnome.png - -KDE -^^^ - -This guide is for users of the KDE desktop who do not use proprietary drivers. -From most distros click the Kick Off menu and navigate to -:guilabel:`System Settings` - -.. image:: pics/kdesystemsettings.png - -Click on the display and monitor icon. - -.. image:: pics/kdedisplay.png - -From here you will need to set up your projector with the appropriate -resolution, and position. OpenLP works best projecting to the monitor on the -right. - -Linux Systems Using nVidia Drivers -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -This guide is for users of the proprietary nVidia driver on Linux Distributions. -It is assumed that you have properly setup your drivers according to your -distribution's documentation, and you have a working ``xorg.conf`` file in place. - -If you wish to make the changes permanent in setting up your system for dual -monitors it will be necessary to modify your ``xorg.conf`` file. It is always a -good idea to make a backup of any critical file before making changes:: - - user@linux:~ $ sudo cp /etc/X11/xorg.conf /etc/X11/xorg.conf.old - -Or for those using systems that use the root user instead of sudo, change to -root and enter:: - - root@linux: # cp /etc/X11/xorg.conf /etc/X11/xorg.conf.old - -The exact location of the ``xorg.conf`` file can vary so check your -distribution's documentation. - -If you want to make your changes permanent run nVidia settings from the -terminal:: - - user@linux:~ $ sudo nvidia-settings - -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:: - - 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`. - -.. image:: pics/nvlinux1.png - -After clicking :guilabel:`Configure`, select :guilabel:`TwinView`. Then click -:guilabel:`OK`. - -.. image:: pics/twinview.png - -Then click :guilabel:`Apply` and if you are happy with the way things look click -:guilabel:`Keep` to keep your new settings. Don't worry if all goes wrong the -settings will return back to the previous settings in 15 seconds without any -action. nVidia Settings should take care of selecting your optimum resolution -etc, but that can be changed as needed. When you are happy with everything click -on :guilabel:`Save to X Configuration File`. - -.. image:: pics/xorgwrite.png - -Then click :guilabel:`Save` and you should be set. You may want to restart X or -your machine just to make sure all the settings carry over the next time you log -in. diff --git a/documentation/manual/source/glossary.rst b/documentation/manual/source/glossary.rst deleted file mode 100644 index 6f4ebcdd6..000000000 --- a/documentation/manual/source/glossary.rst +++ /dev/null @@ -1,70 +0,0 @@ -======== -Glossary -======== - -The developers of OpenLP have strived to make it a straightforward and easy to -use application. However, it is good to be familiar with a few terms that will -be used throughout this documentation, and when seeking support. - -Main Window ------------ - -The Main Window is what you will see when you first open OpenLP - -.. image:: pics/mainwindow.png - -The Main Window contains all the tools and plugins that make OpenLP function - -Media Manager -------------- - -The Media Manager contains a number of tabs that plugins supply to OpenLP. -Each tab in the Media Manager is called a **Media Item** - -.. image:: pics/mediamanager.png - -From the Media Manager you can send Media Items to the Preview or Live screens. - -Preview -------- - -The preview pane is a section to preview your media items before you go live -with them. - -.. image:: pics/preview.png - -Service File ------------- - -A service file, is the file that is created when you save your work on OpenLP. -The service file consist of **Service Items** - -Service Item ------------- - -A service item are the **media items** that are in the **service manager** - -Service Manger --------------- - -The service manager contains the media items in your service file. This is the -area from which your media items go live, and you can also save, open, and edit -services files. - -.. image:: pics/servicemanager.png - -Slide Controller ----------------- - -The Slide Controller controls which slide from a **Service Item** is currently -being displayed, and moving between the various slides. - -.. image:: pics/slidecontroller.png - -Theme Manager -------------- - -The theme manager is where themes are created and edited. Themes are the text -styles backgrounds that you use to personalize your services. - -.. image:: pics/thememanager.png diff --git a/documentation/manual/source/index.rst b/documentation/manual/source/index.rst deleted file mode 100644 index 5786af1ae..000000000 --- a/documentation/manual/source/index.rst +++ /dev/null @@ -1,26 +0,0 @@ -.. OpenLP documentation master file, created by - sphinx-quickstart on Thu Sep 30 21:24:54 2010. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to the OpenLP 2.0 User Manual -===================================== - -Contents: - -.. toctree:: - :maxdepth: 2 - - introduction - glossary - dualmonitors - mediamanager - songs - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - diff --git a/documentation/manual/source/introduction.rst b/documentation/manual/source/introduction.rst deleted file mode 100644 index 02ecf7dbd..000000000 --- a/documentation/manual/source/introduction.rst +++ /dev/null @@ -1,46 +0,0 @@ -============= -Introduction -============= - -About ------ - -OpenLP is an open source lyrics projection application developed specifically -for churches. It is licensed under the GNU Generic Public License, which means -that it is free to use and distribute, and it stays free. - -Lyrics Projection ------------------ - -OpenLP's purpose is to project the lyrics of songs and Bible verses using a -computer and a data projector. OpenLP also has the ability to project videos, -images, and also play audio. OpenLP also is highly customizable providing users -with the ability to set up a wide variety of themes, including themes with -video backgrounds. - -Open Source ------------ - -OpenLP is open source software. This means that the source code (the -programming instructions the developers write) is open to anyone who wants to -look at it. This gives you, the end user, a few freedoms. - -From a developer's perspective, it gives you the freedom to inspect the code -and make sure that it is not malicious. Also, it gives you the freedom to -change the code and the freedom to "fork" the project and make it your own. - -For end users open source software gives you the freedom to use software as -you wish. You are not required to pay for the software and you are free to -make copies and distribute it to anyone you want. - -GNU General Public License --------------------------- - -The GNU General Public License was specifically chosen because it ensures the -above mentioned freedoms. It specifically states that you are not allowed -to charge for the software, and that you have to distribute the source code as -well. - -You can find a copy of the GNU General Public License from the Help menu -selecting about OpenLP or on-line -at: http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt diff --git a/documentation/manual/source/mediamanager.rst b/documentation/manual/source/mediamanager.rst deleted file mode 100644 index 2cf1d2c57..000000000 --- a/documentation/manual/source/mediamanager.rst +++ /dev/null @@ -1,26 +0,0 @@ -============= -Media Manager -============= - -Once you get your system set up for OpenLP you will be ready to add content to -your setup. This will all happen through the **Media Manager**. The -`Media Manager` contains all the bibles, songs, presentations, media, and -everything else that you will project through OpenLP. - -Enabling the Plugins --------------------- - -You may need to enable the plugins that came with OpenLP. As you can see below -this is what the `Media Manager` looks like with all the plugins enabled. - -.. image:: pics/mediamanager.png - -To enable the plugins navigate to :menuselection:`Settings --> Plugins` or -press `F7`. You will then want to click on the plugin to the left that you want -to enable and select **active** from the drop down box to the right. - -.. image:: pics/plugins.png - - -Now you should be ready to add content to OpenLP check out the section of this -guide on the individual plugins. diff --git a/documentation/manual/source/pics/dvi.png b/documentation/manual/source/pics/dvi.png deleted file mode 100644 index bc9e345b3..000000000 Binary files a/documentation/manual/source/pics/dvi.png and /dev/null differ diff --git a/documentation/manual/source/pics/finishedimport.png b/documentation/manual/source/pics/finishedimport.png deleted file mode 100644 index d49876ec9..000000000 Binary files a/documentation/manual/source/pics/finishedimport.png and /dev/null differ diff --git a/documentation/manual/source/pics/gnome.png b/documentation/manual/source/pics/gnome.png deleted file mode 100644 index 1ea060b7a..000000000 Binary files a/documentation/manual/source/pics/gnome.png and /dev/null differ diff --git a/documentation/manual/source/pics/kdedisplay.png b/documentation/manual/source/pics/kdedisplay.png deleted file mode 100644 index ddbbae028..000000000 Binary files a/documentation/manual/source/pics/kdedisplay.png and /dev/null differ diff --git a/documentation/manual/source/pics/kdesystemsettings.png b/documentation/manual/source/pics/kdesystemsettings.png deleted file mode 100644 index 7d7ac1f76..000000000 Binary files a/documentation/manual/source/pics/kdesystemsettings.png and /dev/null differ diff --git a/documentation/manual/source/pics/mainwindow.png b/documentation/manual/source/pics/mainwindow.png deleted file mode 100644 index 217751062..000000000 Binary files a/documentation/manual/source/pics/mainwindow.png and /dev/null differ diff --git a/documentation/manual/source/pics/mediamanager.png b/documentation/manual/source/pics/mediamanager.png deleted file mode 100644 index 245f2469b..000000000 Binary files a/documentation/manual/source/pics/mediamanager.png and /dev/null differ diff --git a/documentation/manual/source/pics/nvlinux1.png b/documentation/manual/source/pics/nvlinux1.png deleted file mode 100644 index 6c585bae4..000000000 Binary files a/documentation/manual/source/pics/nvlinux1.png and /dev/null differ diff --git a/documentation/manual/source/pics/plugins.png b/documentation/manual/source/pics/plugins.png deleted file mode 100644 index b6810242b..000000000 Binary files a/documentation/manual/source/pics/plugins.png and /dev/null differ diff --git a/documentation/manual/source/pics/preview.png b/documentation/manual/source/pics/preview.png deleted file mode 100644 index f3041726b..000000000 Binary files a/documentation/manual/source/pics/preview.png and /dev/null differ diff --git a/documentation/manual/source/pics/selectsongs.png b/documentation/manual/source/pics/selectsongs.png deleted file mode 100644 index 20df3ba2a..000000000 Binary files a/documentation/manual/source/pics/selectsongs.png and /dev/null differ diff --git a/documentation/manual/source/pics/servicemanager.png b/documentation/manual/source/pics/servicemanager.png deleted file mode 100644 index 8e2842eb5..000000000 Binary files a/documentation/manual/source/pics/servicemanager.png and /dev/null differ diff --git a/documentation/manual/source/pics/slidecontroller.png b/documentation/manual/source/pics/slidecontroller.png deleted file mode 100644 index 01e5c86d0..000000000 Binary files a/documentation/manual/source/pics/slidecontroller.png and /dev/null differ diff --git a/documentation/manual/source/pics/songimporter.png b/documentation/manual/source/pics/songimporter.png deleted file mode 100644 index 96c39ea38..000000000 Binary files a/documentation/manual/source/pics/songimporter.png and /dev/null differ diff --git a/documentation/manual/source/pics/songimporterchoices.png b/documentation/manual/source/pics/songimporterchoices.png deleted file mode 100644 index 5c458838e..000000000 Binary files a/documentation/manual/source/pics/songimporterchoices.png and /dev/null differ diff --git a/documentation/manual/source/pics/songselectlyrics.png b/documentation/manual/source/pics/songselectlyrics.png deleted file mode 100644 index 2e3d92f0d..000000000 Binary files a/documentation/manual/source/pics/songselectlyrics.png and /dev/null differ diff --git a/documentation/manual/source/pics/songselectsongsearch.png b/documentation/manual/source/pics/songselectsongsearch.png deleted file mode 100644 index de0ea12ca..000000000 Binary files a/documentation/manual/source/pics/songselectsongsearch.png and /dev/null differ diff --git a/documentation/manual/source/pics/songusage.png b/documentation/manual/source/pics/songusage.png deleted file mode 100644 index 10f29f2a9..000000000 Binary files a/documentation/manual/source/pics/songusage.png and /dev/null differ diff --git a/documentation/manual/source/pics/songusagedelete.png b/documentation/manual/source/pics/songusagedelete.png deleted file mode 100644 index fec1b5e5d..000000000 Binary files a/documentation/manual/source/pics/songusagedelete.png and /dev/null differ diff --git a/documentation/manual/source/pics/songusagereport.png b/documentation/manual/source/pics/songusagereport.png deleted file mode 100644 index c0d9df3dd..000000000 Binary files a/documentation/manual/source/pics/songusagereport.png and /dev/null differ diff --git a/documentation/manual/source/pics/thememanager.png b/documentation/manual/source/pics/thememanager.png deleted file mode 100644 index be35cadc1..000000000 Binary files a/documentation/manual/source/pics/thememanager.png and /dev/null differ diff --git a/documentation/manual/source/pics/twinview.png b/documentation/manual/source/pics/twinview.png deleted file mode 100644 index d8a659f53..000000000 Binary files a/documentation/manual/source/pics/twinview.png and /dev/null differ diff --git a/documentation/manual/source/pics/vga.png b/documentation/manual/source/pics/vga.png deleted file mode 100644 index 3e02726a8..000000000 Binary files a/documentation/manual/source/pics/vga.png and /dev/null differ diff --git a/documentation/manual/source/pics/vistadisplaysettings.png b/documentation/manual/source/pics/vistadisplaysettings.png deleted file mode 100644 index 1fd2c8b16..000000000 Binary files a/documentation/manual/source/pics/vistadisplaysettings.png and /dev/null differ diff --git a/documentation/manual/source/pics/vistapersonalize.png b/documentation/manual/source/pics/vistapersonalize.png deleted file mode 100644 index c24cc15dd..000000000 Binary files a/documentation/manual/source/pics/vistapersonalize.png and /dev/null differ diff --git a/documentation/manual/source/pics/winsevendisplay.png b/documentation/manual/source/pics/winsevendisplay.png deleted file mode 100644 index deea325b5..000000000 Binary files a/documentation/manual/source/pics/winsevendisplay.png and /dev/null differ diff --git a/documentation/manual/source/pics/winsevenresolution.png b/documentation/manual/source/pics/winsevenresolution.png deleted file mode 100644 index c60bf4c0c..000000000 Binary files a/documentation/manual/source/pics/winsevenresolution.png and /dev/null differ diff --git a/documentation/manual/source/pics/xorgwrite.png b/documentation/manual/source/pics/xorgwrite.png deleted file mode 100644 index 812b9e0ae..000000000 Binary files a/documentation/manual/source/pics/xorgwrite.png and /dev/null differ diff --git a/documentation/manual/source/pics/xpdisplaysettings.png b/documentation/manual/source/pics/xpdisplaysettings.png deleted file mode 100644 index e1ec66c6f..000000000 Binary files a/documentation/manual/source/pics/xpdisplaysettings.png and /dev/null differ diff --git a/documentation/manual/source/songs.rst b/documentation/manual/source/songs.rst deleted file mode 100644 index 678a6206c..000000000 --- a/documentation/manual/source/songs.rst +++ /dev/null @@ -1,100 +0,0 @@ -===== -Songs -===== - -Managing your songs in OpenLP is a relatively simple process. There are also -converters provided to get data from other formats into OpenLP. - -Song Importer -============= - -If you are using an earlier version of OpenLP or come from another software -package, you may be able to convert your existing database to work in OpenLP -2.0. To access the Song Importer :menuselection:`File --> Import --> Song`. -You will then see the Song Importer window, then click :guilabel:`Next`. - -.. image:: pics/songimporter.png - -After choosing :guilabel:`Next` you can then select from the various types of -software that OpenLP will convert songs from. - -.. image:: pics/songimporterchoices.png - -Then click on the file folder icon to choose the file of the song database you -want to import. See the following sections for information on the different -formats that OpenLP will import. - -Importing from OpenLP Version 1 -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Converting from OpenLP Version 1 is a pretty simple process. You will first -need to locate your version 1 database file. - -Windows XP:: - - C:\Documents and Settings\All Users\Application Data\openlp.org\Data\songs.olp - -Windows Vista / Windows 7:: - - C:\ProgramData\openlp.org\Data\songs.olp - -After clicking :guilabel:`Next` your conversion should be complete. - -.. image:: pics/finishedimport.png - -Then press :guilabel:`Finish` and you should now be ready to use your OpenLP -version one songs. - -Importing from OpenSong -^^^^^^^^^^^^^^^^^^^^^^^ - -Converting from OpenSong you will need to locate your songs database. In the -later versions of OpenSong you are asked to define the location of this. The -songs will be located in a folder named :guilabel:`Songs`. This folder should -contain files with all your songs in them without a file extension. (file.xxx). -When you have located this folder you will then need to select the songs from -the folder. - -.. image:: pics/selectsongs.png - -On most operating systems to select all the songs, first select the first song -in the lest then press shift and select the last song in the list. After this -press :guilabel:`Next` and you should see that your import has been successful. - -.. image:: pics/finishedimport.png - -Press :guilabel:`Finish` and you will now be ready to use your songs imported -from OpenSong. - -Importing from CCLI Song Select -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -To import from CCLI Song Select you must be a CCLI Subscriber and also a -subscriber of the Song Select service. For info on that go to: -http://www.ccli.com - -The first step for importing from CCLI Song Select is to log into your account. -Then search for your desired song. For this example we will be adding the song -"Amazing Grace". - -.. image:: pics/songselectsongsearch.png - -For the song you are searching for select `lyrics` This should take you to a -page displaying the lyrics and copyright info for your song. - -.. image:: pics/songselectlyrics.png - -Next, hover over the :guilabel:`Lyrics` menu from the upper right corner. Then -choose either the .txt or .usr file. You will then be asked to chose a download -location if your browser does not automatically select that for you. Select -this file from the OpenLP import window and then click :guilabel:`Next` You can -also select multiple songs for import at once on most operating systems by -selecting the first item in the list then holding shift select the last item in -the list. When finished you should see that your import has completed. - -.. image:: pics/finishedimport.png - -Press :guilabel:`Finish` and you will now be ready to use your songs imported -from CCLI SongSelect. - - diff --git a/documentation/plugin.txt b/documentation/plugin.txt deleted file mode 100644 index dff6de06a..000000000 --- a/documentation/plugin.txt +++ /dev/null @@ -1,97 +0,0 @@ -A plugin architecture for openlp 2.0 ------------------------------------- - -Why? ----- -To allow easy development of new "things to display". Examples: -* Bible passages -* Video -* Powerpoint/Openoffice Impress -* Lyrics :) (with chords, rich text, etc...) -* Musical score -* Midi files (hmmm, that's not a thing to display, but feels like it should be - there...) -* Audio files, CDs (hmmm again) -* Collections of pictures -* Alerts to members of the congregation -... etc. - -The scope of these plugins is "things for display purposes", so each needs to -be able to: -* Render their display (on the projection screen and in a "shrunken form" - for preview purposes) - -These plugins need to be part of a service. This means they need to -* Be able to tell the service manager code what to put in the service for their - "bit" -* Have a "tab" in the media manager, which they can render on request - to allow bits to be added to the service (or indeed shown live) - -In addition, some plugins need to be able to show -* How their multiple screens of data are split (eg verses) -* be told which screen to display - -Some plugins will also have things to configure, so will need -* to tell the core app that they need a preferences page -* and be able to render it and handle those prefs - -Some plugins may need to define -* Hot keys for their display actions. The core will have to pass these on... - -Other basic things all plugins will need: -* A name -* A version number -* Helpfile? - -Funnily enough, the core lyrics engine fits those requirements, so could -actually form a plugin... - -Each service entry may be made up of multiple plugins (to do text on video), so -each plugin that contributes to a service item will need a "layering" -priority. - -Plugin management ------------------ - -Plugins will be packages within the plugins/ directory. The plugin manager -will scan this directory when openlp loads for any class which is based on the -base Plugin class (or should we call it the DisplayPlugin class to allow for -other sorts??) - -These plugins are then queried for their capabilities/requirements and -spaces made in the prefs UI as required, and in the media manager. - -The service manager can find out what plugins it has available (we need to -report missing plugins when a service is loaded). - -The display manager will get a ref to a/some plugin(s) from the service -manager when each service item is made live, and can then call on each to -render their display. - -Each plugin will have basic attributes for -* name -* version number -* capabilities/requirements - (eg: - needs a prefs page, - needs key presses forwarding - has multiple screensful? - ) - -and a set of API functions for -* media manager rendering and handling -* creating service data -* being told service data -* set paint context -* render -* selecting a screen to display -* handling preferences -* shortcut key handling... - - -Other things to add: -Multiple monitors -Openoffice like plugins - external rendering. -update hook for rendering? -Event hook from app -Event hook to app (MIDI events....) diff --git a/openlp.pyw b/openlp.pyw index f3455962d..64ffb3321 100755 --- a/openlp.pyw +++ b/openlp.pyw @@ -6,10 +6,11 @@ # 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 # +# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # +# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias # +# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, 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 # @@ -25,257 +26,14 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### -import os -import sys -import logging -from optparse import OptionParser -from traceback import format_exception -from subprocess import Popen, PIPE +# Import uuid now, to avoid the rare bug described in the support system: +# http://support.openlp.org/issues/102 +# If https://bugs.gentoo.org/show_bug.cgi?id=317557 is fixed, the import can be +# removed. +import uuid -from PyQt4 import QtCore, QtGui +from openlp.core import main -from openlp.core.lib import Receiver -from openlp.core.resources import qInitResources -from openlp.core.ui.mainwindow import MainWindow -from openlp.core.ui.exceptionform import ExceptionForm -from openlp.core.ui import SplashScreen, ScreenList -from openlp.core.utils import AppLocation, LanguageManager, VersionThread - -log = logging.getLogger() - -application_stylesheet = u""" -QMainWindow::separator -{ - border: none; -} - -QDockWidget::title -{ - border: 1px solid palette(dark); - padding-left: 5px; - padding-top: 2px; - margin: 1px 0; -} - -QToolBar -{ - border: none; - margin: 0; - padding: 0; -} -""" - -class OpenLP(QtGui.QApplication): - """ - The core application class. This class inherits from Qt's QApplication - class in order to provide the core of the application. - """ - log.info(u'OpenLP Application Loaded') - - def _get_version(self): - """ - Load and store current Application Version - """ - if u'--dev-version' in sys.argv: - # If we're running the dev version, let's use bzr to get the version - try: - # If bzrlib is availble, use it - from bzrlib.branch import Branch - b = Branch.open_containing('.')[0] - b.lock_read() - try: - # Get the branch's latest revision number. - revno = b.revno() - # Convert said revision number into a bzr revision id. - revision_id = b.dotted_revno_to_revision_id((revno,)) - # Get a dict of tags, with the revision id as the key. - tags = b.tags.get_reverse_tag_dict() - # Check if the latest - if revision_id in tags: - full_version = u'%s' % tags[revision_id][0] - else: - full_version = '%s-bzr%s' % \ - (sorted(b.tags.get_tag_dict().keys())[-1], revno) - finally: - b.unlock() - except: - # Otherwise run the command line bzr client - bzr = Popen((u'bzr', u'tags', u'--sort', u'time'), stdout=PIPE) - output, error = bzr.communicate() - code = bzr.wait() - if code != 0: - raise Exception(u'Error running bzr tags') - lines = output.splitlines() - if len(lines) == 0: - tag = u'0.0.0' - revision = u'0' - else: - tag, revision = lines[-1].split() - bzr = Popen((u'bzr', u'log', u'--line', u'-r', u'-1'), - stdout=PIPE) - output, error = bzr.communicate() - code = bzr.wait() - if code != 0: - raise Exception(u'Error running bzr log') - latest = output.split(u':')[0] - full_version = latest == revision and tag or \ - u'%s-bzr%s' % (tag, latest) - else: - # We're not running the development version, let's use the file - filepath = AppLocation.get_directory(AppLocation.VersionDir) - filepath = os.path.join(filepath, u'.version') - fversion = None - try: - fversion = open(filepath, u'r') - full_version = unicode(fversion.read()).rstrip() - except IOError: - log.exception('Error in version file.') - full_version = u'0.0.0-bzr000' - finally: - if fversion: - fversion.close() - bits = full_version.split(u'-') - app_version = { - u'full': full_version, - u'version': bits[0], - u'build': bits[1] if len(bits) > 1 else None - } - if app_version[u'build']: - log.info( - u'Openlp version %s build %s', - app_version[u'version'], - app_version[u'build'] - ) - else: - log.info(u'Openlp version %s' % app_version[u'version']) - return app_version - - def notify(self, obj, evt): - #TODO needed for presentation exceptions - return QtGui.QApplication.notify(self, obj, evt) - - def run(self): - """ - Run the OpenLP application. - """ - app_version = self._get_version() - #provide a listener for widgets to reqest a screen update. - QtCore.QObject.connect(Receiver.get_receiver(), - QtCore.SIGNAL(u'openlp_process_events'), self.processEvents) - QtCore.QObject.connect(Receiver.get_receiver(), - QtCore.SIGNAL(u'cursor_busy'), self.setBusyCursor) - QtCore.QObject.connect(Receiver.get_receiver(), - QtCore.SIGNAL(u'cursor_normal'), self.setNormalCursor) - self.setOrganizationName(u'OpenLP') - self.setOrganizationDomain(u'openlp.org') - self.setApplicationName(u'OpenLP') - self.setApplicationVersion(app_version[u'version']) - if os.name == u'nt': - self.setStyleSheet(application_stylesheet) - show_splash = QtCore.QSettings().value( - u'general/show splash', QtCore.QVariant(True)).toBool() - if show_splash: - self.splash = SplashScreen() - self.splash.show() - # make sure Qt really display the splash screen - self.processEvents() - screens = ScreenList() - # Decide how many screens we have and their size - for screen in xrange(0, self.desktop().numScreens()): - size = self.desktop().screenGeometry(screen); - screens.add_screen({u'number': screen, - u'size': size, - u'primary': (self.desktop().primaryScreen() == screen)}) - log.info(u'Screen %d found with resolution %s', screen, size) - # start the main app window - self.mainWindow = MainWindow(screens, app_version) - self.mainWindow.show() - if show_splash: - # now kill the splashscreen - self.splash.finish(self.mainWindow) - self.mainWindow.repaint() - VersionThread(self.mainWindow, app_version).start() - return self.exec_() - - def hookException(self, exctype, value, traceback): - if not hasattr(self, u'mainWindow'): - log.exception(''.join(format_exception(exctype, value, traceback))) - return - if not hasattr(self, u'exceptionForm'): - self.exceptionForm = ExceptionForm(self.mainWindow) - self.exceptionForm.exceptionTextEdit.setPlainText( - ''.join(format_exception(exctype, value, traceback))) - self.setNormalCursor() - self.exceptionForm.exec_() - - def setBusyCursor(self): - """ - Sets the Busy Cursor for the Application - """ - self.setOverrideCursor(QtCore.Qt.BusyCursor) - - def setNormalCursor(self): - """ - Sets the Normal Cursor forthe Application - """ - self.restoreOverrideCursor() - -def main(): - """ - The main function which parses command line options and then runs - the PyQt4 Application. - """ - # Set up command line options. - usage = 'Usage: %prog [options] [qt-options]' - parser = OptionParser(usage=usage) - parser.add_option('-e', '--no-error-form', dest='no_error_form', - action='store_true', help='Disable the error notification form.') - parser.add_option('-l', '--log-level', dest='loglevel', - default='warning', metavar='LEVEL', help='Set logging to LEVEL ' - 'level. Valid values are "debug", "info", "warning".') - parser.add_option('-p', '--portable', dest='portable', - action='store_true', help='Specify if this should be run as a ' - 'portable app, off a USB flash drive (not implemented).') - parser.add_option('-d', '--dev-version', dest='dev_version', - action='store_true', help='Ignore the version file and pull the ' - 'version directly from Bazaar') - parser.add_option('-s', '--style', dest='style', - help='Set the Qt4 style (passed directly to Qt4).') - # Set up logging - log_path = AppLocation.get_directory(AppLocation.CacheDir) - if not os.path.exists(log_path): - os.makedirs(log_path) - filename = os.path.join(log_path, u'openlp.log') - logfile = logging.FileHandler(filename, u'w') - logfile.setFormatter(logging.Formatter( - u'%(asctime)s %(name)-55s %(levelname)-8s %(message)s')) - log.addHandler(logfile) - logging.addLevelName(15, u'Timer') - # Parse command line options and deal with them. - (options, args) = parser.parse_args() - qt_args = [] - if options.loglevel.lower() in ['d', 'debug']: - log.setLevel(logging.DEBUG) - print 'Logging to:', filename - elif options.loglevel.lower() in ['w', 'warning']: - log.setLevel(logging.WARNING) - else: - log.setLevel(logging.INFO) - if options.style: - qt_args.extend(['-style', options.style]) - # Throw the rest of the arguments at Qt, just in case. - qt_args.extend(args) - # Initialise the resources - qInitResources() - # Now create and actually run the application. - app = OpenLP(qt_args) - #i18n Set Language - language = LanguageManager.get_language() - appTranslator = LanguageManager.get_translator(language) - app.installTranslator(appTranslator) - if not options.no_error_form: - sys.excepthook = app.hookException - sys.exit(app.run()) if __name__ == u'__main__': """ diff --git a/openlp/.version b/openlp/.version index ae8249b9a..998994b7f 100644 --- a/openlp/.version +++ b/openlp/.version @@ -1 +1 @@ -1.9.2-bzr987 \ No newline at end of file +1.9.5-bzr1421 \ No newline at end of file diff --git a/openlp/__init__.py b/openlp/__init__.py index b88547df6..5f7608770 100644 --- a/openlp/__init__.py +++ b/openlp/__init__.py @@ -5,10 +5,11 @@ # 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 # +# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # +# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias # +# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, 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 # @@ -25,4 +26,10 @@ ############################################################################### """ The :mod:`openlp` module contains all the project produced OpenLP functionality -""" \ No newline at end of file +""" + +import core +import plugins + +__all__ = [u'core', u'plugins'] + diff --git a/openlp/core/__init__.py b/openlp/core/__init__.py index 17db85184..82a30b19f 100644 --- a/openlp/core/__init__.py +++ b/openlp/core/__init__.py @@ -5,10 +5,11 @@ # 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 # +# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # +# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias # +# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, 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 # @@ -23,9 +24,268 @@ # with this program; if not, write to the Free Software Foundation, Inc., 59 # # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### + """ The :mod:`core` module provides all core application functions All the core functions of the OpenLP application including the GUI, settings, logging and a plugin framework are contained within the openlp.core module. -""" \ No newline at end of file +""" + +import os +import sys +import logging +from optparse import OptionParser +from traceback import format_exception + +from PyQt4 import QtCore, QtGui + +from openlp.core.lib import Receiver, check_directory_exists +from openlp.core.lib.ui import UiStrings +from openlp.core.resources import qInitResources +from openlp.core.ui.mainwindow import MainWindow +from openlp.core.ui.firsttimelanguageform import FirstTimeLanguageForm +from openlp.core.ui.firsttimeform import FirstTimeForm +from openlp.core.ui.exceptionform import ExceptionForm +from openlp.core.ui import SplashScreen, ScreenList +from openlp.core.utils import AppLocation, LanguageManager, VersionThread, \ + get_application_version, DelayStartThread + + +__all__ = [u'OpenLP', u'main'] + + +log = logging.getLogger() +application_stylesheet = u""" +QMainWindow::separator +{ + border: none; +} + +QDockWidget::title +{ + border: 1px solid palette(dark); + padding-left: 5px; + padding-top: 2px; + margin: 1px 0; +} + +QToolBar +{ + border: none; + margin: 0; + padding: 0; +} +""" + + +class OpenLP(QtGui.QApplication): + """ + The core application class. This class inherits from Qt's QApplication + class in order to provide the core of the application. + """ + + args = [] + + def exec_(self): + """ + Override exec method to allow the shared memory to be released on exit + """ + QtGui.QApplication.exec_() + self.sharedMemory.detach() + + def run(self, args, testing=False): + """ + Run the OpenLP application. + """ + # On Windows, the args passed into the constructor are + # ignored. Not very handy, so set the ones we want to use. + self.args.extend(args) + # provide a listener for widgets to reqest a screen update. + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'openlp_process_events'), self.processEvents) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'cursor_busy'), self.setBusyCursor) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'cursor_normal'), self.setNormalCursor) + # Decide how many screens we have and their size + screens = ScreenList(self.desktop()) + # First time checks in settings + has_run_wizard = QtCore.QSettings().value( + u'general/has run wizard', QtCore.QVariant(False)).toBool() + if not has_run_wizard: + if FirstTimeForm(screens).exec_() == QtGui.QDialog.Accepted: + QtCore.QSettings().setValue(u'general/has run wizard', + QtCore.QVariant(True)) + if os.name == u'nt': + self.setStyleSheet(application_stylesheet) + show_splash = QtCore.QSettings().value( + u'general/show splash', QtCore.QVariant(True)).toBool() + if show_splash: + self.splash = SplashScreen() + self.splash.show() + # make sure Qt really display the splash screen + self.processEvents() + # start the main app window + self.mainWindow = MainWindow(self.clipboard(), self.args) + self.mainWindow.show() + if show_splash: + # now kill the splashscreen + self.splash.finish(self.mainWindow) + log.debug(u'Splashscreen closed') + # make sure Qt really display the splash screen + self.processEvents() + self.mainWindow.repaint() + self.processEvents() + if not has_run_wizard: + self.mainWindow.firstTime() + update_check = QtCore.QSettings().value( + u'general/update check', QtCore.QVariant(True)).toBool() + if update_check: + VersionThread(self.mainWindow).start() + Receiver.send_message(u'live_display_blank_check') + self.mainWindow.appStartup() + DelayStartThread(self.mainWindow).start() + # Skip exec_() for gui tests + if not testing: + return self.exec_() + + def isAlreadyRunning(self): + """ + Look to see if OpenLP is already running and ask if a 2nd copy + is to be started. + """ + self.sharedMemory = QtCore.QSharedMemory('OpenLP') + if self.sharedMemory.attach(): + status = QtGui.QMessageBox.critical(None, + UiStrings().Error, UiStrings().OpenLPStart, + QtGui.QMessageBox.StandardButtons( + QtGui.QMessageBox.Yes | QtGui.QMessageBox.No)) + if status == QtGui.QMessageBox.No: + return True + return False + else: + self.sharedMemory.create(1) + return False + + def hookException(self, exctype, value, traceback): + if not hasattr(self, u'mainWindow'): + log.exception(''.join(format_exception(exctype, value, traceback))) + return + if not hasattr(self, u'exceptionForm'): + self.exceptionForm = ExceptionForm(self.mainWindow) + self.exceptionForm.exceptionTextEdit.setPlainText( + ''.join(format_exception(exctype, value, traceback))) + self.setNormalCursor() + self.exceptionForm.exec_() + + def setBusyCursor(self): + """ + Sets the Busy Cursor for the Application + """ + self.setOverrideCursor(QtCore.Qt.BusyCursor) + self.processEvents() + + def setNormalCursor(self): + """ + Sets the Normal Cursor for the Application + """ + self.restoreOverrideCursor() + + def event(self, event): + """ + Enables direct file opening on OS X + """ + if event.type() == QtCore.QEvent.FileOpen: + file_name = event.file() + log.debug(u'Got open file event for %s!', file_name) + self.args.insert(0, unicode(file_name)) + return True + else: + return QtGui.QApplication.event(self, event) + + +def main(args=None): + """ + The main function which parses command line options and then runs + the PyQt4 Application. + """ + # Set up command line options. + usage = 'Usage: %prog [options] [qt-options]' + parser = OptionParser(usage=usage) + parser.add_option('-e', '--no-error-form', dest='no_error_form', + action='store_true', help='Disable the error notification form.') + parser.add_option('-l', '--log-level', dest='loglevel', + default='warning', metavar='LEVEL', help='Set logging to LEVEL ' + 'level. Valid values are "debug", "info", "warning".') + parser.add_option('-p', '--portable', dest='portable', + action='store_true', help='Specify if this should be run as a ' + 'portable app, off a USB flash drive (not implemented).') + parser.add_option('-d', '--dev-version', dest='dev_version', + action='store_true', help='Ignore the version file and pull the ' + 'version directly from Bazaar') + parser.add_option('-s', '--style', dest='style', + help='Set the Qt4 style (passed directly to Qt4).') + parser.add_option('--testing', dest='testing', + action='store_true', help='Run by testing framework') + # Set up logging + log_path = AppLocation.get_directory(AppLocation.CacheDir) + check_directory_exists(log_path) + filename = os.path.join(log_path, u'openlp.log') + logfile = logging.FileHandler(filename, u'w') + logfile.setFormatter(logging.Formatter( + u'%(asctime)s %(name)-55s %(levelname)-8s %(message)s')) + log.addHandler(logfile) + # Parse command line options and deal with them. + # Use args supplied programatically if possible. + (options, args) = parser.parse_args(args) if args else parser.parse_args() + qt_args = [] + if options.loglevel.lower() in ['d', 'debug']: + log.setLevel(logging.DEBUG) + print 'Logging to:', filename + elif options.loglevel.lower() in ['w', 'warning']: + log.setLevel(logging.WARNING) + else: + log.setLevel(logging.INFO) + if options.style: + qt_args.extend(['-style', options.style]) + # Throw the rest of the arguments at Qt, just in case. + qt_args.extend(args) + # Initialise the resources + qInitResources() + # Now create and actually run the application. + app = OpenLP(qt_args) + app.setOrganizationName(u'OpenLP') + app.setOrganizationDomain(u'openlp.org') + app.setApplicationName(u'OpenLP') + app.setApplicationVersion(get_application_version()[u'version']) + # Instance check + if not options.testing: + # Instance check + if app.isAlreadyRunning(): + sys.exit() + # First time checks in settings + if not QtCore.QSettings().value(u'general/has run wizard', + QtCore.QVariant(False)).toBool(): + if not FirstTimeLanguageForm().exec_(): + # if cancel then stop processing + sys.exit() + # i18n Set Language + language = LanguageManager.get_language() + app_translator, default_translator = \ + LanguageManager.get_translator(language) + if not app_translator.isEmpty(): + app.installTranslator(app_translator) + if not default_translator.isEmpty(): + app.installTranslator(default_translator) + else: + log.debug(u'Could not find default_translator.') + if not options.no_error_form: + sys.excepthook = app.hookException + # Do not run method app.exec_() when running gui tests + if options.testing: + app.run(qt_args, testing=True) + # For gui tests we need access to window intances and their components + return app + else: + sys.exit(app.run(qt_args)) diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index ebbe31597..a687ded64 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -5,10 +5,11 @@ # 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 # +# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # +# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias # +# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, 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 # @@ -35,57 +36,16 @@ from PyQt4 import QtCore, QtGui log = logging.getLogger(__name__) -# TODO make external and configurable in alpha 4 via a settings dialog -html_expands = [] - -html_expands.append({u'desc': u'Red', u'start tag': u'{r}', - u'start html': u'', - u'end tag': u'{/r}', u'end html': u'', u'protected': False}) -html_expands.append({u'desc': u'Black', u'start tag': u'{b}', - u'start html': u'', - u'end tag': u'{/b}', u'end html': u'', u'protected': False}) -html_expands.append({u'desc': u'Blue', u'start tag': u'{bl}', - u'start html': u'', - u'end tag': u'{/bl}', u'end html': u'', u'protected': False}) -html_expands.append({u'desc': u'Yellow', u'start tag': u'{y}', - u'start html': u'', - u'end tag': u'{/y}', u'end html': u'', u'protected': False}) -html_expands.append({u'desc': u'Green', u'start tag': u'{g}', - u'start html': u'', - u'end tag': u'{/g}', u'end html': u'', u'protected': False}) -html_expands.append({u'desc': u'Pink', u'start tag': u'{pk}', - u'start html': u'', - u'end tag': u'{/pk}', u'end html': u'', u'protected': False}) -html_expands.append({u'desc': u'Orange', u'start tag': u'{o}', - u'start html': u'', - u'end tag': u'{/o}', u'end html': u'', u'protected': False}) -html_expands.append({u'desc': u'Purple', u'start tag': u'{pp}', - u'start html': u'', - u'end tag': u'{/pp}', u'end html': u'', u'protected': False}) -html_expands.append({u'desc': u'White', u'start tag': u'{w}', - u'start html': u'', - u'end tag': u'{/w}', u'end html': u'', u'protected': False}) -html_expands.append({u'desc': u'Superscript', u'start tag': u'{su}', - u'start html': u'', u'end tag': u'{/su}', u'end html': u'', - u'protected': True}) -html_expands.append({u'desc': u'Subscript', u'start tag': u'{sb}', - u'start html': u'', u'end tag': u'{/sb}', u'end html': u'', - u'protected': True}) -html_expands.append({u'desc': u'Paragraph', u'start tag': u'{p}', - u'start html': u'

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

', - u'protected': True}) -html_expands.append({u'desc': u'Bold', u'start tag': u'{st}', - u'start html': u'', u'end tag': u'{/st}', u'end html': u'', - u'protected': True}) -html_expands.append({u'desc': u'Italics', u'start tag': u'{it}', - u'start html': u'', u'end tag': u'{/it}', u'end html': u'', - u'protected': True}) -html_expands.append({u'desc': u'Underline', u'start tag': u'{u}', - u'start html': u'', - u'end tag': u'{/u}', u'end html': u'', u'protected': True}) +class MediaType(object): + """ + An enumeration class for types of media. + """ + Audio = 1 + Video = 2 def translate(context, text, comment=None, - encoding=QtCore.QCoreApplication.CodecForTr, n=-1): + encoding=QtCore.QCoreApplication.CodecForTr, n=-1, + translate=QtCore.QCoreApplication.translate): """ A special shortcut method to wrap around the Qt4 translation functions. This abstracts the translation procedure so that we can change it if at a @@ -102,12 +62,12 @@ def translate(context, text, comment=None, An identifying string for when the same text is used in different roles within the same context. """ - return QtCore.QCoreApplication.translate(context, text, comment, encoding, n) + return translate(context, text, comment, encoding, n) def get_text_file_string(text_file): """ - Open a file and return its content as unicode string. If the supplied file - name is not a file then the function returns False. If there is an error + Open a file and return its content as unicode string. If the supplied file + name is not a file then the function returns False. If there is an error loading the file or the content can't be decoded then the function will return None. @@ -120,6 +80,9 @@ def get_text_file_string(text_file): content_string = None try: file_handle = open(text_file, u'r') + if not file_handle.read(3) == '\xEF\xBB\xBF': + # no BOM was found + file_handle.seek(0) content = file_handle.read() content_string = content.decode(u'utf-8') except (IOError, UnicodeError): @@ -166,56 +129,6 @@ def build_icon(icon): QtGui.QIcon.Normal, QtGui.QIcon.Off) return button_icon -def context_menu_action(base, icon, text, slot): - """ - Utility method to help build context menus for plugins - - ``base`` - The parent menu to add this menu item to - - ``icon`` - An icon for this action - - ``text`` - The text to display for this action - - ``slot`` - The code to run when this action is triggered - """ - action = QtGui.QAction(text, base) - if icon: - action.setIcon(build_icon(icon)) - QtCore.QObject.connect(action, QtCore.SIGNAL(u'triggered()'), slot) - return action - -def context_menu(base, icon, text): - """ - Utility method to help build context menus for plugins - - ``base`` - The parent object to add this menu to - - ``icon`` - An icon for this menu - - ``text`` - The text to display for this menu - """ - action = QtGui.QMenu(text, base) - action.setIcon(build_icon(icon)) - return action - -def context_menu_separator(base): - """ - Add a separator to a context menu - - ``base`` - The menu object to add the separator to - """ - action = QtGui.QAction(u'', base) - action.setSeparator(True) - return action - def image_to_byte(image): """ Resize an image to fit on the current screen for the web and returns @@ -234,12 +147,65 @@ def image_to_byte(image): # convert to base64 encoding so does not get missed! return byte_array.toBase64() -def resize_image(image, width, height, background=QtCore.Qt.black): +def create_thumb(image_path, thumb_path, return_icon=True, size=None): + """ + Create a thumbnail from the given image path and depending on + ``return_icon`` it returns an icon from this thumb. + + ``image_path`` + The image file to create the icon from. + + ``thumb_path`` + The filename to save the thumbnail to. + + ``return_icon`` + States if an icon should be build and returned from the thumb. Defaults + to ``True``. + + ``size`` + Allows to state a own size to use. Defaults to ``None``, which means + that a default height of 88 is used. + """ + ext = os.path.splitext(thumb_path)[1].lower() + reader = QtGui.QImageReader(image_path) + if size is None: + ratio = float(reader.size().width()) / float(reader.size().height()) + reader.setScaledSize(QtCore.QSize(int(ratio * 88), 88)) + else: + reader.setScaledSize(size) + thumb = reader.read() + thumb.save(thumb_path, ext[1:]) + if not return_icon: + return + if os.path.exists(thumb_path): + return build_icon(unicode(thumb_path)) + # Fallback for files with animation support. + return build_icon(unicode(image_path)) + +def validate_thumb(file_path, thumb_path): + """ + Validates whether an file's thumb still exists and if is up to date. + **Note**, you must **not** call this function, before checking the + existence of the file. + + ``file_path`` + The path to the file. The file **must** exist! + + ``thumb_path`` + The path to the thumb. + """ + if not os.path.exists(thumb_path): + return False + image_date = os.stat(file_path).st_mtime + thumb_date = os.stat(thumb_path).st_mtime + return image_date <= thumb_date + +def resize_image(image_path, width, height, background=u'#000000'): """ Resize an image to fit on the current screen. - ``image`` - The image to resize. + ``image_path`` + The path to the image to resize. ``width`` The new image width. @@ -247,28 +213,37 @@ def resize_image(image, width, height, background=QtCore.Qt.black): ``height`` The new image height. - ``background`` - The background colour defaults to black. + ``background`` + The background colour. Defaults to black. + DO NOT REMOVE THE DEFAULT BACKGROUND VALUE! """ log.debug(u'resize_image - start') - if isinstance(image, QtGui.QImage): - preview = image + reader = QtGui.QImageReader(image_path) + # The image's ratio. + image_ratio = float(reader.size().width()) / float(reader.size().height()) + resize_ratio = float(width) / float(height) + # Figure out the size we want to resize the image to (keep aspect ratio). + if image_ratio == resize_ratio: + size = QtCore.QSize(width, height) + elif image_ratio < resize_ratio: + # Use the image's height as reference for the new size. + size = QtCore.QSize(image_ratio * height, height) else: - preview = QtGui.QImage(image) - if not preview.isNull(): - # Only resize if different size - if preview.width() == width and preview.height == height: - return preview - preview = preview.scaled(width, height, QtCore.Qt.KeepAspectRatio, - QtCore.Qt.SmoothTransformation) + # Use the image's width as reference for the new size. + size = QtCore.QSize(width, 1 / (image_ratio / width)) + reader.setScaledSize(size) + preview = reader.read() + if image_ratio == resize_ratio: + # We neither need to centre the image nor add "bars" to the image. + return preview realw = preview.width() realh = preview.height() # and move it to the centre of the preview space new_image = QtGui.QImage(width, height, QtGui.QImage.Format_ARGB32_Premultiplied) painter = QtGui.QPainter(new_image) - painter.fillRect(new_image.rect(), background) + painter.fillRect(new_image.rect(), QtGui.QColor(background)) painter.drawImage((width - realw) / 2, (height - realh) / 2, preview) return new_image @@ -293,7 +268,9 @@ def clean_tags(text): Remove Tags from text for display """ text = text.replace(u'
', u'\n') - for tag in html_expands: + text = text.replace(u'{br}', u'\n') + text = text.replace(u' ', u' ') + for tag in FormattingTags.get_html_tags(): text = text.replace(tag[u'start tag'], u'') text = text.replace(tag[u'end tag'], u'') return text @@ -302,28 +279,39 @@ def expand_tags(text): """ Expand tags HTML for display """ - for tag in html_expands: + for tag in FormattingTags.get_html_tags(): text = text.replace(tag[u'start tag'], tag[u'start html']) text = text.replace(tag[u'end tag'], tag[u'end html']) return text -from theme import ThemeLevel, ThemeXML, BackgroundGradientType, \ - BackgroundType, HorizontalType, VerticalType -from spelltextedit import SpellTextEdit +def check_directory_exists(dir): + """ + Check a theme directory exists and if not create it + + ``dir`` + Theme directory to make sure exists + """ + log.debug(u'check_directory_exists %s' % dir) + try: + if not os.path.exists(dir): + os.makedirs(dir) + except IOError: + pass + from eventreceiver import Receiver -from imagemanager import ImageManager +from listwidgetwithdnd import ListWidgetWithDnD +from formattingtags import FormattingTags +from spelltextedit import SpellTextEdit from settingsmanager import SettingsManager from plugin import PluginStatus, StringContent, Plugin from pluginmanager import PluginManager from settingstab import SettingsTab -from serviceitem import ServiceItem -from serviceitem import ServiceItemType -from serviceitem import ItemCapabilities +from serviceitem import ServiceItem, ServiceItemType, ItemCapabilities from htmlbuilder import build_html, build_lyrics_format_css, \ build_lyrics_outline_css from toolbar import OpenLPToolbar from dockwidget import OpenLPDockWidget +from imagemanager import ImageManager from renderer import Renderer -from rendermanager import RenderManager from mediamanageritem import MediaManagerItem -from baselistwithdnd import BaseListWithDnD +from openlp.core.utils.actions import ActionList diff --git a/openlp/core/lib/db.py b/openlp/core/lib/db.py index c2e1243ce..b6f12d180 100644 --- a/openlp/core/lib/db.py +++ b/openlp/core/lib/db.py @@ -5,10 +5,11 @@ # 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 # +# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # +# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias # +# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, 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 # @@ -28,13 +29,17 @@ The :mod:`db` module provides the core database functionality for OpenLP """ import logging import os +from urllib import quote_plus as urlquote from PyQt4 import QtCore -from sqlalchemy import create_engine, MetaData -from sqlalchemy.exceptions import InvalidRequestError -from sqlalchemy.orm import scoped_session, sessionmaker +from sqlalchemy import Table, MetaData, Column, types, create_engine +from sqlalchemy.exc import SQLAlchemyError, InvalidRequestError, DBAPIError +from sqlalchemy.orm import scoped_session, sessionmaker, mapper +from sqlalchemy.pool import NullPool -from openlp.core.utils import AppLocation +from openlp.core.lib import translate +from openlp.core.lib.ui import critical_error_message_box +from openlp.core.utils import AppLocation, delete_file log = logging.getLogger(__name__) @@ -51,12 +56,70 @@ def init_db(url, auto_flush=True, auto_commit=False): ``auto_commit`` Sets the commit behaviour of the session """ - engine = create_engine(url) + engine = create_engine(url, poolclass=NullPool) metadata = MetaData(bind=engine) session = scoped_session(sessionmaker(autoflush=auto_flush, autocommit=auto_commit, bind=engine)) return session, metadata + +def upgrade_db(url, upgrade): + """ + Upgrade a database. + + ``url`` + The url of the database to upgrade. + + ``upgrade`` + The python module that contains the upgrade instructions. + """ + session, metadata = init_db(url) + + class Metadata(BaseModel): + """ + Provides a class for the metadata table. + """ + pass + load_changes = True + try: + tables = upgrade.upgrade_setup(metadata) + except (SQLAlchemyError, DBAPIError): + load_changes = False + metadata_table = Table(u'metadata', metadata, + Column(u'key', types.Unicode(64), primary_key=True), + Column(u'value', types.UnicodeText(), default=None) + ) + metadata_table.create(checkfirst=True) + mapper(Metadata, metadata_table) + version_meta = session.query(Metadata).get(u'version') + if version_meta is None: + version_meta = Metadata.populate(key=u'version', value=u'0') + version = 0 + else: + version = int(version_meta.value) + if version > upgrade.__version__: + return version, upgrade.__version__ + version += 1 + if load_changes: + while hasattr(upgrade, u'upgrade_%d' % version): + log.debug(u'Running upgrade_%d', version) + try: + getattr(upgrade, u'upgrade_%d' % version) \ + (session, metadata, tables) + version_meta.value = unicode(version) + except (SQLAlchemyError, DBAPIError): + log.exception(u'Could not run database upgrade script ' + '"upgrade_%s", upgrade process has been halted.', version) + break + version += 1 + else: + version_meta = Metadata.populate(key=u'version', + value=int(upgrade.__version__)) + session.add(version_meta) + session.commit() + return int(version_meta.value), upgrade.__version__ + + def delete_database(plugin_name, db_file_name=None): """ Remove a database file from the system. @@ -65,7 +128,7 @@ def delete_database(plugin_name, db_file_name=None): The name of the plugin to remove the database for ``db_file_name`` - The database file name. Defaults to None resulting in the + The database file name. Defaults to None resulting in the plugin_name being used. """ db_file_path = None @@ -75,11 +138,8 @@ def delete_database(plugin_name, db_file_name=None): else: db_file_path = os.path.join( AppLocation.get_section_data_path(plugin_name), plugin_name) - try: - os.remove(db_file_path) - return True - except OSError: - return False + return delete_file(db_file_path) + class BaseModel(object): """ @@ -90,16 +150,17 @@ class BaseModel(object): """ Creates an instance of a class and populates it, returning the instance """ - me = cls() - for key in kwargs: - me.__setattr__(key, kwargs[key]) - return me + instance = cls() + for key, value in kwargs.iteritems(): + instance.__setattr__(key, value) + return instance class Manager(object): """ Provide generic object persistence management """ - def __init__(self, plugin_name, init_schema, db_file_name=None): + def __init__(self, plugin_name, init_schema, db_file_name=None, + upgrade_mod=None): """ Runs the initialisation process that includes creating the connection to the database and the tables if they don't exist. @@ -110,14 +171,18 @@ class Manager(object): ``init_schema`` The init_schema function for this database + ``upgrade_schema`` + The upgrade_schema function for this database + ``db_file_name`` - The file name to use for this database. Defaults to None resulting + The file name to use for this database. Defaults to None resulting in the plugin_name being used. """ settings = QtCore.QSettings() settings.beginGroup(plugin_name) self.db_url = u'' self.is_dirty = False + self.session = None db_type = unicode( settings.value(u'db type', QtCore.QVariant(u'sqlite')).toString()) if db_type == u'sqlite': @@ -130,12 +195,33 @@ class Manager(object): AppLocation.get_section_data_path(plugin_name), plugin_name) else: self.db_url = u'%s://%s:%s@%s/%s' % (db_type, - unicode(settings.value(u'db username').toString()), - unicode(settings.value(u'db password').toString()), - unicode(settings.value(u'db hostname').toString()), - unicode(settings.value(u'db database').toString())) + urlquote(unicode(settings.value(u'db username').toString())), + urlquote(unicode(settings.value(u'db password').toString())), + urlquote(unicode(settings.value(u'db hostname').toString())), + urlquote(unicode(settings.value(u'db database').toString()))) settings.endGroup() - self.session = init_schema(self.db_url) + if upgrade_mod: + db_ver, up_ver = upgrade_db(self.db_url, upgrade_mod) + if db_ver > up_ver: + critical_error_message_box( + translate('OpenLP.Manager', 'Database Error'), + unicode(translate('OpenLP.Manager', 'The database being ' + 'loaded was created in a more recent version of ' + 'OpenLP. The database is version %d, while OpenLP ' + 'expects version %d. The database will not be loaded.' + '\n\nDatabase: %s')) % \ + (db_ver, up_ver, self.db_url) + ) + return + try: + self.session = init_schema(self.db_url) + except (SQLAlchemyError, DBAPIError): + log.exception(u'Error loading database: %s', self.db_url) + critical_error_message_box( + translate('OpenLP.Manager', 'Database Error'), + unicode(translate('OpenLP.Manager', 'OpenLP cannot load your ' + 'database.\n\nDatabase: %s')) % self.db_url + ) def save_object(self, object_instance, commit=True): """ @@ -215,16 +301,18 @@ class Manager(object): The type of objects to return ``filter_clause`` - The filter governing selection of objects to return. Defaults to + The filter governing selection of objects to return. Defaults to None. ``order_by_ref`` - Any parameters to order the returned objects by. Defaults to None. + Any parameters to order the returned objects by. Defaults to None. """ query = self.session.query(object_class) if filter_clause is not None: query = query.filter(filter_clause) - if order_by_ref is not None: + if isinstance(order_by_ref, list): + return query.order_by(*order_by_ref).all() + elif order_by_ref is not None: return query.order_by(order_by_ref).all() return query.all() @@ -236,7 +324,7 @@ class Manager(object): The type of objects to return. ``filter_clause`` - The filter governing selection of objects to return. Defaults to + The filter governing selection of objects to return. Defaults to None. """ query = self.session.query(object_class) @@ -270,10 +358,17 @@ class Manager(object): def delete_all_objects(self, object_class, filter_clause=None): """ - Delete all object records + Delete all object records. + This method should only be used for simple tables and not ones with + relationships. The relationships are not deleted from the database and + this will lead to database corruptions. ``object_class`` The type of object to delete + + ``filter_clause`` + The filter governing selection of objects to return. Defaults to + None. """ try: query = self.session.query(object_class) @@ -295,4 +390,4 @@ class Manager(object): if self.is_dirty: engine = create_engine(self.db_url) if self.db_url.startswith(u'sqlite'): - engine.execute("vacuum") \ No newline at end of file + engine.execute("vacuum") diff --git a/openlp/core/lib/dockwidget.py b/openlp/core/lib/dockwidget.py index 9c4187337..8c942171c 100644 --- a/openlp/core/lib/dockwidget.py +++ b/openlp/core/lib/dockwidget.py @@ -5,10 +5,11 @@ # 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 # +# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # +# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias # +# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, 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 # @@ -31,6 +32,9 @@ import logging from PyQt4 import QtGui +from openlp.core.lib import build_icon +from openlp.core.ui import ScreenList + log = logging.getLogger(__name__) class OpenLPDockWidget(QtGui.QDockWidget): @@ -43,9 +47,15 @@ class OpenLPDockWidget(QtGui.QDockWidget): """ log.debug(u'Initialise the %s widget' % name) QtGui.QDockWidget.__init__(self, parent) - self.parent = parent if name: self.setObjectName(name) if icon: - self.setWindowIcon(icon) - self.setFloating(False) + self.setWindowIcon(build_icon(icon)) + # Sort out the minimum width. + screens = ScreenList.get_instance() + screen_width = screens.current[u'size'].width() + mainwindow_docbars = screen_width / 5 + if mainwindow_docbars > 300: + self.setMinimumWidth(300) + else: + self.setMinimumWidth(mainwindow_docbars) diff --git a/openlp/core/lib/eventreceiver.py b/openlp/core/lib/eventreceiver.py index 63ad5b796..40cac8e35 100644 --- a/openlp/core/lib/eventreceiver.py +++ b/openlp/core/lib/eventreceiver.py @@ -5,10 +5,11 @@ # 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 # +# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # +# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias # +# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, 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 # @@ -34,206 +35,194 @@ log = logging.getLogger(__name__) class EventReceiver(QtCore.QObject): """ - Class to allow events to be passed from different parts of the - system. This is a private class and should not be used directly - but rather via the Receiver class. + Class to allow events to be passed from different parts of the system. This + is a private class and should not be used directly but rather via the + Receiver class. + + **Mainwindow related and generic signals** + + ``mainwindow_status_text`` + Changes the bottom status bar text on the mainwindow. + + ``openlp_warning_message`` + Displays a standalone Warning Message. + + ``openlp_error_message`` + Displays a standalone Error Message. + + ``openlp_information_message`` + Displays a standalone Information Message. + + ``cursor_busy`` + Makes the cursor got to a busy form. + + ``cursor_normal`` + Resets the cursor to default. ``openlp_process_events`` - Requests the Application to flush the events queue + Requests the Application to flush the events queue. ``openlp_version_check`` Version has changed so pop up window. + ``openlp_stop_wizard`` + Stops a wizard before completion. + + **Setting related signals** + ``config_updated`` - Informs components the config has changed + Informs components that the config has changed. ``config_screen_changed`` - The display monitor has been changed + The display monitor has been changed. - ``slidecontroller_{live|preview}_first`` - Moves to the first slide + **Slidecontroller signals** ``slidecontroller_{live|preview}_next`` - Moves to the next slide + Moves to the next slide. ``slidecontroller_{live|preview}_next_noloop`` - Moves to the next slide without auto advance + Moves to the next slide without auto advance. ``slidecontroller_{live|preview}_previous`` - Moves to the previous slide + Moves to the previous slide. ``slidecontroller_{live|preview}_previous_noloop`` - Moves to the previous slide, without auto advance - - ``slidecontroller_{live|preview}_last`` - Moves to the last slide + Moves to the previous slide, without auto advance. ``slidecontroller_{live|preview}_set`` - Moves to a specific slide, by index + Moves to a specific slide, by index. ``slidecontroller_{live|preview}_started`` - Broadcasts that an item has been made live/previewed + Broadcasts that an item has been made live/previewed. ``slidecontroller_{live|preview}_change`` Informs the slidecontroller that a slide change has occurred and to - update itself + update itself. ``slidecontroller_{live|preview}_changed`` - Broadcasts that the slidecontroller has changed the current slide - - ``slidecontroller_{live|preview}_text_request`` - Request the text for the current item in the controller - Returns a slidecontroller_{live|preview}_text_response with an - array of dictionaries with the tag and verse text + Broadcasts that the slidecontroller has changed the current slide. ``slidecontroller_{live|preview}_blank`` - Request that the output screen is blanked + Request that the output screen is blanked. ``slidecontroller_{live|preview}_unblank`` - Request that the output screen is unblanked + Request that the output screen is unblanked. ``slidecontroller_live_spin_delay`` - Pushes out the loop delay + Pushes out the loop delay. ``slidecontroller_live_stop_loop`` - Stop the loop on the main display + Stop the loop on the main display. + + + **Servicemanager related signals** + + ``servicemanager_new_service`` + A new service is being loaded or created. ``servicemanager_previous_item`` - Display the previous item in the service + Display the previous item in the service. + + ``servicemanager_preview_live`` + Requests a Preview item from the Service Manager to update live and add + a new item to the preview panel. ``servicemanager_next_item`` - Display the next item in the service + Display the next item in the service. ``servicemanager_set_item`` - Go live on a specific item, by index - - ``servicemanager_list_request`` - Request the service list. Responds with servicemanager_list_response - containing a array of dictionaries - - ``maindisplay_blank`` - Blank the maindisplay window - - ``maindisplay_hide`` - Hide the maindisplay window - - ``maindisplay_show`` - Return the maindisplay window - - ``maindisplay_active`` - The maindisplay has been made active - - ``maindisplay_status_text`` - Changes the bottom status bar text on the maindisplay window - - ``maindisplay_blank_check`` - Check to see if the blank display message is required - - ``videodisplay_start`` - Open a media item and prepare for playing - - ``videodisplay_play`` - Start playing a media item - - ``videodisplay_pause`` - Pause a media item - - ``videodisplay_stop`` - Stop playing a media item - - ``videodisplay_background`` - Replace the background video - - ``theme_update_list`` - send out message with new themes - - ``theme_update_global`` - Tell the components we have a new global theme - - ``{plugin}_start`` - Requests a plugin to start a external program - Path and file provided in message - - ``{plugin}_first`` - Requests a plugin to handle a first event - - ``{plugin}_previous`` - Requests a plugin to handle a previous event - - ``{plugin}_next`` - Requests a plugin to handle a next event - - ``{plugin}_last`` - Requests a plugin to handle a last event - - ``{plugin}_slide`` - Requests a plugin to handle a go to specific slide event - - ``{plugin}_stop`` - Requests a plugin to handle a stop event - - ``{plugin}_blank`` - Requests a plugin to handle a blank screen event - - ``{plugin}_unblank`` - Requests a plugin to handle an unblank screen event - - ``{plugin}_edit`` - Requests a plugin edit a database item with the key as the payload - - ``{plugin}_edit_clear`` - Editing has been completed - - ``{plugin}_load_list`` - Tells the the plugin to reload the media manager list - - ``{plugin}_preview`` - Tells the plugin it's item can be previewed - - ``{plugin}_add_service_item`` - Ask the plugin to push the selected items to the service item - - ``{plugin}_service_load`` - Ask the plugin to process an individual service item after it has been - loaded + Go live on a specific item, by index. ``service_item_update`` Passes back to the service manager the service item after it has been - processed by the plugin + processed by the plugin. + + **Display signals** + + ``update_display_css`` + CSS has been updated which needs to be changed on the main display. + + **Live Display signals** + + ``live_display_hide`` + Hide the live display. + + ``live_display_show`` + Return the live display. + + ``live_display_active`` + The live display has been made active. + + ``live_display_blank_check`` + Check to see if the blank display message is required. + + **Theme related singlas** + + ``theme_update_list`` + send out message with new themes. + + ``theme_update_global`` + Tell the components we have a new global theme. + + **Plugin specific signals** + + ``{plugin}_start`` + Requests a plugin to start a external program. Path and file have to + be provided in the message. + + ``{plugin}_first`` + Requests a plugin to handle a first event. + + ``{plugin}_previous`` + Requests a plugin to handle a previous event. + + ``{plugin}_next`` + Requests a plugin to handle a next event. + + ``{plugin}_last`` + Requests a plugin to handle a last event. + + ``{plugin}_slide`` + Requests a plugin to handle a go to specific slide event. + + ``{plugin}_stop`` + Requests a plugin to handle a stop event. + + ``{plugin}_blank`` + Requests a plugin to handle a blank screen event. + + ``{plugin}_unblank`` + Requests a plugin to handle an unblank screen event. + + ``{plugin}_edit`` + Requests a plugin edit a database item with the key as the payload. + + ``{plugin}_edit_clear`` + Editing has been completed. + + ``{plugin}_load_list`` + Tells the the plugin to reload the media manager list. + + ``{plugin}_preview`` + Tells the plugin it's item can be previewed. + + ``{plugin}_add_service_item`` + Ask the plugin to push the selected items to the service item. + + ``{plugin}_service_load`` + Ask the plugin to process an individual service item after it has been + loaded. ``alerts_text`` - Displays an alert message + Displays an alert message. ``bibles_nobook`` - Attempt to find book resulted in no match - - ``bibles_showprogress`` - Show progress of bible verse import - - ``bibles_hideprogress`` - Hide progress of bible verse import - - ``bibles_stop_import`` - Stops the Bible Import + Attempt to find book resulted in no match. ``remotes_poll_request`` Waits for openlp to do something "interesting" and sends a - remotes_poll_response signal when it does - - ``openlp_warning_message`` - Displays a standalone Warning Message - - ``openlp_error_message`` - Displays a standalone Error Message - - ``openlp_information_message`` - Displays a standalone Information Message - - ``cursor_busy`` - Makes the cursor got to a busy form - - ``cursor_normal`` - Resets the cursor to default + ``remotes_poll_response`` signal when it does. """ def __init__(self): diff --git a/openlp/core/lib/formattingtags.py b/openlp/core/lib/formattingtags.py new file mode 100644 index 000000000..bc0055325 --- /dev/null +++ b/openlp/core/lib/formattingtags.py @@ -0,0 +1,237 @@ +# -*- 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, Gerald Britton, Jonathan # +# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # +# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias # +# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, 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 # +############################################################################### +""" +Provide HTML Tag management and Formatting Tag access class +""" +import cPickle + +from PyQt4 import QtCore + +from openlp.core.lib import translate + + +class FormattingTags(object): + """ + Static Class to HTML Tags to be access around the code the list is managed + by the Options Tab. + """ + html_expands = [] + + @staticmethod + def get_html_tags(): + """ + Provide access to the html_expands list. + """ + # Load user defined tags otherwise user defined tags are not present. + FormattingTags.load_tags() + return FormattingTags.html_expands + + @staticmethod + def reset_html_tags(): + """ + Resets the html_expands list. + """ + temporary_tags = [tag for tag in FormattingTags.html_expands + if tag.get(u'temporary')] + FormattingTags.html_expands = [] + base_tags = [] + # Append the base tags. + # Hex Color tags from http://www.w3schools.com/html/html_colornames.asp + base_tags.append({u'desc': translate('OpenLP.FormattingTags', 'Red'), + u'start tag': u'{r}', + u'start html': u'', + u'end tag': u'{/r}', u'end html': u'', u'protected': True, + u'temporary': False}) + base_tags.append({u'desc': translate('OpenLP.FormattingTags', 'Black'), + u'start tag': u'{b}', + u'start html': u'', + u'end tag': u'{/b}', u'end html': u'', u'protected': True, + u'temporary': False}) + base_tags.append({u'desc': translate('OpenLP.FormattingTags', 'Blue'), + u'start tag': u'{bl}', + u'start html': u'', + u'end tag': u'{/bl}', u'end html': u'', u'protected': True, + u'temporary': False}) + base_tags.append({u'desc': translate('OpenLP.FormattingTags', 'Yellow'), + u'start tag': u'{y}', + u'start html': u'', + u'end tag': u'{/y}', u'end html': u'', u'protected': True, + u'temporary': False}) + base_tags.append({u'desc': translate('OpenLP.FormattingTags', 'Green'), + u'start tag': u'{g}', + u'start html': u'', + u'end tag': u'{/g}', u'end html': u'', u'protected': True, + u'temporary': False}) + base_tags.append({u'desc': translate('OpenLP.FormattingTags', 'Pink'), + u'start tag': u'{pk}', + u'start html': u'', + u'end tag': u'{/pk}', u'end html': u'', u'protected': True, + u'temporary': False}) + base_tags.append({u'desc': translate('OpenLP.FormattingTags', 'Orange'), + u'start tag': u'{o}', + u'start html': u'', + u'end tag': u'{/o}', u'end html': u'', u'protected': True, + u'temporary': False}) + base_tags.append({u'desc': translate('OpenLP.FormattingTags', 'Purple'), + u'start tag': u'{pp}', + u'start html': u'', + u'end tag': u'{/pp}', u'end html': u'', u'protected': True, + u'temporary': False}) + base_tags.append({u'desc': translate('OpenLP.FormattingTags', 'White'), + u'start tag': u'{w}', + u'start html': u'', + u'end tag': u'{/w}', u'end html': u'', u'protected': True, + u'temporary': False}) + base_tags.append({ + u'desc': translate('OpenLP.FormattingTags', 'Superscript'), + u'start tag': u'{su}', u'start html': u'', + u'end tag': u'{/su}', u'end html': u'', u'protected': True, + u'temporary': False}) + base_tags.append({ + u'desc': translate('OpenLP.FormattingTags', 'Subscript'), + u'start tag': u'{sb}', u'start html': u'', + u'end tag': u'{/sb}', u'end html': u'', u'protected': True, + u'temporary': False}) + base_tags.append({ + u'desc': translate('OpenLP.FormattingTags', 'Paragraph'), + u'start tag': u'{p}', u'start html': u'

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

', u'protected': True, + u'temporary': False}) + base_tags.append({u'desc': translate('OpenLP.FormattingTags', 'Bold'), + u'start tag': u'{st}', u'start html': u'', + u'end tag': u'{/st}', u'end html': u'', + u'protected': True, u'temporary': False}) + base_tags.append({ + u'desc': translate('OpenLP.FormattingTags', 'Italics'), + u'start tag': u'{it}', u'start html': u'', u'end tag': u'{/it}', + u'end html': u'', u'protected': True, u'temporary': False}) + base_tags.append({ + u'desc': translate('OpenLP.FormattingTags', 'Underline'), + u'start tag': u'{u}', + u'start html': u'', + u'end tag': u'{/u}', u'end html': u'', u'protected': True, + u'temporary': False}) + base_tags.append({u'desc': translate('OpenLP.FormattingTags', 'Break'), + u'start tag': u'{br}', u'start html': u'
', u'end tag': u'', + u'end html': u'', u'protected': True, u'temporary': False}) + FormattingTags.add_html_tags(base_tags) + FormattingTags.add_html_tags(temporary_tags) + + @staticmethod + def save_html_tags(): + """ + Saves all formatting tags except protected ones. + """ + tags = [] + for tag in FormattingTags.html_expands: + if not tag[u'protected'] and not tag.get(u'temporary'): + # Using dict ensures that copy is made and encoding of values + # a little later does not affect tags in the original list + tags.append(dict(tag)) + tag = tags[-1] + # Remove key 'temporary' from tags. + # It is not needed to be saved. + if u'temporary' in tag: + del tag[u'temporary'] + for element in tag: + if isinstance(tag[element], unicode): + tag[element] = tag[element].encode('utf8') + # Formatting Tags were also known as display tags. + QtCore.QSettings().setValue(u'displayTags/html_tags', + QtCore.QVariant(cPickle.dumps(tags) if tags else u'')) + + @staticmethod + def load_tags(): + """ + Load the Tags from store so can be used in the system or used to + update the display. If Cancel was selected this is needed to reset the + dsiplay to the correct version. + """ + # Initial Load of the Tags + FormattingTags.reset_html_tags() + # Formatting Tags were also known as display tags. + user_expands = QtCore.QSettings().value(u'displayTags/html_tags', + QtCore.QVariant(u'')).toString() + # cPickle only accepts str not unicode strings + user_expands_string = str(user_expands) + if user_expands_string: + user_tags = cPickle.loads(user_expands_string) + for tag in user_tags: + for element in tag: + if isinstance(tag[element], str): + tag[element] = tag[element].decode('utf8') + # If we have some user ones added them as well + FormattingTags.add_html_tags(user_tags) + + @staticmethod + def add_html_tags(tags, save=False): + """ + Add a list of tags to the list. + + ``tags`` + The list with tags to add. + + ``save`` + Defaults to ``False``. If set to ``True`` the given ``tags`` are + saved to the config. + + Each **tag** has to be a ``dict`` and should have the following keys: + + * desc + The formatting tag's description, e. g. **Red** + + * start tag + The start tag, e. g. ``{r}`` + + * end tag + The end tag, e. g. ``{/r}`` + + * start html + The start html tag. For instance ```` + + * end html + The end html tag. For example ```` + + * protected + A boolean stating whether this is a build-in tag or not. Should be + ``True`` in most cases. + + * temporary + A temporary tag will not be saved, but is also considered when + displaying text containing the tag. It has to be a ``boolean``. + """ + FormattingTags.html_expands.extend(tags) + if save: + FormattingTags.save_html_tags() + + @staticmethod + def remove_html_tag(tag_id): + """ + Removes an individual html_expands tag. + """ + FormattingTags.html_expands.pop(tag_id) diff --git a/openlp/core/lib/htmlbuilder.py b/openlp/core/lib/htmlbuilder.py index 0a26382f8..e4cdecd72 100644 --- a/openlp/core/lib/htmlbuilder.py +++ b/openlp/core/lib/htmlbuilder.py @@ -5,10 +5,11 @@ # 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 # +# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # +# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias # +# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, 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 # @@ -28,11 +29,13 @@ import logging from PyQt4 import QtWebKit -from openlp.core.lib import BackgroundType, BackgroundGradientType +from openlp.core.lib.theme import BackgroundType, BackgroundGradientType, \ + VerticalType, HorizontalType log = logging.getLogger(__name__) HTMLSRC = u""" + OpenLP Display @@ -50,127 +53,39 @@ body { position: absolute; left: 0px; top: 0px; - width: %spx; - height: %spx; + width: 100%%; + height: 100%%; } #black { - z-index:8; + z-index: 8; background-color: black; display: none; } #bgimage { - z-index:1; + z-index: 1; } #image { - z-index:2; -} -#video1 { - z-index:3; -} -#video2 { - z-index:3; -} -#alert { - position: absolute; - left: 0px; - top: 0px; - z-index:10; - %s + z-index: 2; } +%s #footer { position: absolute; - z-index:6; + z-index: 6; %s } /* lyric css */ %s - +sup { + font-size: 0.6em; + vertical-align: top; + position: relative; + top: -0.3em; +} - - - - + +%s %s
- - """ +""" -def build_html(item, screen, alert, islive): +def build_html(item, screen, islive, background, image=None, + plugins=None): """ Build the full web paged structure for display - `item` + ``item`` Service Item to be displayed - `screen` + + ``screen`` Current display information - `alert` - Alert display display information - `islive` + + ``islive`` Item is going live, rather than preview/theme building + + ``background`` + Theme background image - bytes + + ``image`` + Image media item - bytes + + ``plugins`` + The List of available plugins """ width = screen[u'size'].width() height = screen[u'size'].height() theme = item.themedata webkitvers = webkit_version() # Image generated and poked in - if item.bg_image_bytes: - image = u'src="data:image/png;base64,%s"' % item.bg_image_bytes + if background: + bgimage_src = u'src="data:image/png;base64,%s"' % background + elif item.bg_image_bytes: + bgimage_src = u'src="data:image/png;base64,%s"' % item.bg_image_bytes else: - image = u'style="display:none;"' + bgimage_src = u'style="display:none;"' + if image: + image_src = u'src="data:image/png;base64,%s"' % image + else: + image_src = u'style="display:none;"' + css_additions = u'' + js_additions = u'' + html_additions = u'' + if plugins: + for plugin in plugins: + css_additions += plugin.getDisplayCss() + js_additions += plugin.getDisplayJavaScript() + html_additions += plugin.getDisplayHtml() html = HTMLSRC % (build_background_css(item, width, height), - width, height, - build_alert_css(alert, width), + css_additions, build_footer_css(item, height), build_lyrics_css(item, webkitvers), u'true' if theme and theme.display_slide_transition and islive \ else u'false', - image, + js_additions, + bgimage_src, image_src, + html_additions, build_lyrics_html(item, webkitvers)) return html @@ -363,7 +279,7 @@ def build_background_css(item, width, height): """ Build the background css - `item` + ``item`` Service Item containing theme and location information """ @@ -380,7 +296,7 @@ def build_background_css(item, width, height): background = \ u'background: ' \ u'-webkit-gradient(linear, left top, left bottom, ' \ - 'from(%s), to(%s))' % (theme.background_start_color, + 'from(%s), to(%s)) fixed' % (theme.background_start_color, theme.background_end_color) elif theme.background_direction == \ BackgroundGradientType.to_string( \ @@ -388,7 +304,7 @@ def build_background_css(item, width, height): background = \ u'background: ' \ u'-webkit-gradient(linear, left top, right bottom, ' \ - 'from(%s), to(%s))' % (theme.background_start_color, + 'from(%s), to(%s)) fixed' % (theme.background_start_color, theme.background_end_color) elif theme.background_direction == \ BackgroundGradientType.to_string \ @@ -396,42 +312,43 @@ def build_background_css(item, width, height): background = \ u'background: ' \ u'-webkit-gradient(linear, left bottom, right top, ' \ - 'from(%s), to(%s))' % (theme.background_start_color, + 'from(%s), to(%s)) fixed' % (theme.background_start_color, theme.background_end_color) elif theme.background_direction == \ BackgroundGradientType.to_string \ (BackgroundGradientType.Vertical): background = \ u'background: -webkit-gradient(linear, left top, ' \ - u'right top, from(%s), to(%s))' % \ + u'right top, from(%s), to(%s)) fixed' % \ (theme.background_start_color, theme.background_end_color) else: background = \ u'background: -webkit-gradient(radial, %s 50%%, 100, %s ' \ - u'50%%, %s, from(%s), to(%s))' % (width, width, width, - theme.background_start_color, theme.background_end_color) + u'50%%, %s, from(%s), to(%s)) fixed' % (width, width, + width, theme.background_start_color, + theme.background_end_color) return background def build_lyrics_css(item, webkitvers): """ Build the lyrics display css - `item` + ``item`` Service Item containing theme and location information - `webkitvers` + ``webkitvers`` The version of qtwebkit we're using """ - style = """ + style = u""" .lyricstable { - z-index:5; + z-index: 5; position: absolute; display: table; %s } .lyricscell { - display:table-cell; + display: table-cell; word-wrap: break-word; %s } @@ -444,7 +361,7 @@ def build_lyrics_css(item, webkitvers): .lyricsshadow { %s } - """ + """ theme = item.themedata lyricstable = u'' lyrics = u'' @@ -452,8 +369,7 @@ def build_lyrics_css(item, webkitvers): outline = u'' shadow = u'' if theme and item.main: - lyricstable = u'left: %spx; top: %spx;' % \ - (item.main.x(), item.main.y()) + lyricstable = u'left: %spx; top: %spx;' % (item.main.x(), item.main.y()) lyrics = build_lyrics_format_css(theme, item.main.width(), item.main.height()) # For performance reasons we want to show as few DIV's as possible, @@ -464,11 +380,11 @@ def build_lyrics_css(item, webkitvers): # Before 533.3 the webkit-text-fill colour wasn't displayed, only the # stroke (outline) color. So put stroke layer underneath the main text. # - # Before 534.4 the webkit-text-stroke was sometimes out of alignment + # Up to 534.3 the webkit-text-stroke was sometimes out of alignment # with the fill, or normal text. letter-spacing=1 is workaround # https://bugs.webkit.org/show_bug.cgi?id=44403 # - # Before 534.4 the text-shadow didn't get displayed when + # Up to 534.3 the text-shadow didn't get displayed when # webkit-text-stroke was used. So use an offset text layer underneath. # https://bugs.webkit.org/show_bug.cgi?id=19728 if webkitvers >= 533.3: @@ -476,7 +392,7 @@ def build_lyrics_css(item, webkitvers): else: outline = build_lyrics_outline_css(theme) if theme.font_main_shadow: - if theme.font_main_outline and webkitvers < 534.3: + if theme.font_main_outline and webkitvers <= 534.3: shadow = u'padding-left: %spx; padding-top: %spx;' % \ (int(theme.font_main_shadow_size) + (int(theme.font_main_outline_size) * 2), @@ -494,10 +410,10 @@ def build_lyrics_outline_css(theme, is_shadow=False): Build the css which controls the theme outline Also used by renderer for splitting verses - `theme` + ``theme`` Object containing theme information - `is_shadow` + ``is_shadow`` If true, use the shadow colors instead """ if theme.font_main_outline: @@ -518,41 +434,35 @@ def build_lyrics_format_css(theme, width, height): Build the css which controls the theme format Also used by renderer for splitting verses - `theme` + ``theme`` Object containing theme information - `width` + ``width`` Width of the lyrics block - `height` + ``height`` Height of the lyrics block """ - if theme.display_horizontal_align == 2: - align = u'center' - elif theme.display_horizontal_align == 1: - align = u'right' - else: - align = u'left' - if theme.display_vertical_align == 2: - valign = u'bottom' - elif theme.display_vertical_align == 1: - valign = u'middle' - else: - valign = u'top' + align = HorizontalType.Names[theme.display_horizontal_align] + valign = VerticalType.Names[theme.display_vertical_align] if theme.font_main_outline: left_margin = int(theme.font_main_outline_size) * 2 else: left_margin = 0 - lyrics = u'white-space:pre-wrap; word-wrap: break-word; ' \ + justify = u'white-space:pre-wrap;' + # fix tag incompatibilities + if theme.display_horizontal_align == HorizontalType.Justify: + justify = u'' + lyrics = u'%s word-wrap: break-word; ' \ 'text-align: %s; vertical-align: %s; font-family: %s; ' \ - 'font-size: %spt; color: %s; line-height: %d%%; margin:0;' \ - 'padding:0; padding-left:%spx; width: %spx; height: %spx; ' % \ - (align, valign, theme.font_main_name, theme.font_main_size, + 'font-size: %spt; color: %s; line-height: %d%%; margin: 0;' \ + 'padding: 0; padding-left: %spx; width: %spx; height: %spx; ' % \ + (justify, align, valign, theme.font_main_name, theme.font_main_size, theme.font_main_color, 100 + int(theme.font_main_line_adjustment), left_margin, width, height) if theme.font_main_outline: - if webkit_version() < 534.3: + if webkit_version() <= 534.3: lyrics += u' letter-spacing: 1px;' if theme.font_main_italics: lyrics += u' font-style:italic; ' @@ -564,10 +474,10 @@ def build_lyrics_html(item, webkitvers): """ Build the HTML required to show the lyrics - `item` + ``item`` Service Item containing theme and location information - `webkitvers` + ``webkitvers`` The version of qtwebkit we're using """ # Bugs in some versions of QtWebKit mean we sometimes need additional @@ -576,7 +486,7 @@ def build_lyrics_html(item, webkitvers): # display:table/display:table-cell are required for each lyric block. lyrics = u'' theme = item.themedata - if webkitvers < 534.4 and theme and theme.font_main_outline: + if webkitvers <= 534.3 and theme and theme.font_main_outline: lyrics += u'
' \ u'
' @@ -593,10 +503,10 @@ def build_footer_css(item, height): """ Build the display of the item footer - `item` + ``item`` Service Item to be processed. """ - style = """ + style = u""" left: %spx; bottom: %spx; width: %spx; @@ -604,7 +514,7 @@ def build_footer_css(item, height): font-size: %spt; color: %s; text-align: left; - white-space:nowrap; + white-space: nowrap; """ theme = item.themedata if not theme or not item.footer: @@ -614,31 +524,3 @@ def build_footer_css(item, height): item.footer.width(), theme.font_footer_name, theme.font_footer_size, theme.font_footer_color) return lyrics_html - -def build_alert_css(alertTab, width): - """ - Build the display of the footer - - `alertTab` - Details from the Alert tab for fonts etc - """ - style = """ - width: %spx; - vertical-align: %s; - font-family: %s; - font-size: %spt; - color: %s; - background-color: %s; - """ - if not alertTab: - return u'' - align = u'' - if alertTab.location == 2: - align = u'bottom' - elif alertTab.location == 1: - align = u'middle' - else: - align = u'top' - alert = style % (width, align, alertTab.font_face, alertTab.font_size, - alertTab.font_color, alertTab.bg_color) - return alert \ No newline at end of file diff --git a/openlp/core/lib/imagemanager.py b/openlp/core/lib/imagemanager.py index 0be1a01c8..0dcc2246b 100644 --- a/openlp/core/lib/imagemanager.py +++ b/openlp/core/lib/imagemanager.py @@ -5,10 +5,11 @@ # 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 # +# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # +# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias # +# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, 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 # @@ -30,19 +31,20 @@ A Thread is used to convert the image to a byte array so the user does not need to wait for the conversion to happen. """ import logging -import os import time +import Queue from PyQt4 import QtCore -from openlp.core.lib import resize_image, image_to_byte +from openlp.core.lib import resize_image, image_to_byte, Receiver +from openlp.core.ui import ScreenList log = logging.getLogger(__name__) class ImageThread(QtCore.QThread): """ - A special Qt thread class to speed up the display of text based frames. - This is threaded so it loads the frames in background + A special Qt thread class to speed up the display of images. This is + threaded so it loads the frames and generates byte stream in background. """ def __init__(self, manager): QtCore.QThread.__init__(self, None) @@ -52,117 +54,251 @@ class ImageThread(QtCore.QThread): """ Run the thread. """ - self.imageManager.process() + self.imageManager._process() + + +class Priority(object): + """ + Enumeration class for different priorities. + + ``Lowest`` + Only the image's byte stream has to be generated. But neither the + ``QImage`` nor the byte stream has been requested yet. + + ``Low`` + Only the image's byte stream has to be generated. Because the image's + ``QImage`` has been requested previously it is reasonable to assume that + the byte stream will be needed before the byte stream of other images + whose ``QImage`` were not generated due to a request. + + ``Normal`` + The image's byte stream as well as the image has to be generated. + Neither the ``QImage`` nor the byte stream has been requested yet. + + ``High`` + The image's byte stream as well as the image has to be generated. The + ``QImage`` for this image has been requested. + **Note**, this priority is only set when the ``QImage`` has not been + generated yet. + + ``Urgent`` + The image's byte stream as well as the image has to be generated. The + byte stream for this image has been requested. + **Note**, this priority is only set when the byte stream has not been + generated yet. + """ + Lowest = 4 + Low = 3 + Normal = 2 + High = 1 + Urgent = 0 class Image(object): - name = '' - path = '' - dirty = True - image = None - image_bytes = None + """ + This class represents an image. To mark an image as *dirty* set the instance + variables ``image`` and ``image_bytes`` to ``None`` and add the image object + to the queue of images to process. + """ + def __init__(self, name, path, source, background): + self.name = name + self.path = path + self.image = None + self.image_bytes = None + self.priority = Priority.Normal + self.source = source + self.background = background + + +class PriorityQueue(Queue.PriorityQueue): + """ + Customised ``Queue.PriorityQueue``. + """ + def modify_priority(self, image, new_priority): + """ + Modifies the priority of the given ``image``. + + ``image`` + The image to remove. This should be an ``Image`` instance. + + ``new_priority`` + The image's new priority. + """ + self.remove(image) + image.priority = new_priority + self.put((image.priority, image)) + + def remove(self, image): + """ + Removes the given ``image`` from the queue. + + ``image`` + The image to remove. This should be an ``Image`` instance. + """ + if (image.priority, image) in self.queue: + self.queue.remove((image.priority, image)) + class ImageManager(QtCore.QObject): """ Image Manager handles the conversion and sizing of images. - """ log.info(u'Image Manager loaded') def __init__(self): + QtCore.QObject.__init__(self) + current_screen = ScreenList.get_instance().current + self.width = current_screen[u'size'].width() + self.height = current_screen[u'size'].height() self._cache = {} - self._thread_running = False - self._cache_dirty = False - self.image_thread = ImageThread(self) + self._imageThread = ImageThread(self) + self._conversion_queue = PriorityQueue() + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'config_updated'), self.process_updates) - def update_display(self, width, height): + def update_display(self): """ - Screen has changed size so rebuild the cache to new size + Screen has changed size so rebuild the cache to new size. """ log.debug(u'update_display') - self.width = width - self.height = height - # mark the images as dirty for a rebuild - for key in self._cache.keys(): - image = self._cache[key] - image.dirty = True - fullpath = os.path.join(image.path, image.name) - image.image = resize_image(fullpath, - self.width, self.height) - self._cache_dirty = True - # only one thread please - if not self._thread_running: - self.image_thread.start() + current_screen = ScreenList.get_instance().current + self.width = current_screen[u'size'].width() + self.height = current_screen[u'size'].height() + # Mark the images as dirty for a rebuild by setting the image and byte + # stream to None. + for key, image in self._cache.iteritems(): + self._reset_image(image) + + def update_images(self, image_type, background): + """ + Border has changed so update all the images affected. + """ + log.debug(u'update_images') + # Mark the images as dirty for a rebuild by setting the image and byte + # stream to None. + for key, image in self._cache.iteritems(): + if image.source == image_type: + image.background = background + self._reset_image(image) + + def update_image(self, name, image_type, background): + """ + Border has changed so update the image affected. + """ + log.debug(u'update_images') + # Mark the images as dirty for a rebuild by setting the image and byte + # stream to None. + for key, image in self._cache.iteritems(): + if image.source == image_type and image.name == name: + image.background = background + self._reset_image(image) + + def _reset_image(self, image): + image.image = None + image.image_bytes = None + self._conversion_queue.modify_priority(image, Priority.Normal) + + def process_updates(self): + """ + Flush the queue to updated any data to update + """ + # We want only one thread. + if not self._imageThread.isRunning(): + self._imageThread.start() def get_image(self, name): """ - Return the Qimage from the cache + Return the ``QImage`` from the cache. If not present wait for the + background thread to process it. """ log.debug(u'get_image %s' % name) - return self._cache[name].image + image = self._cache[name] + if image.image is None: + self._conversion_queue.modify_priority(image, Priority.High) + # make sure we are running and if not give it a kick + self.process_updates() + while image.image is None: + log.debug(u'get_image - waiting') + time.sleep(0.1) + elif image.image_bytes is None: + # Set the priority to Low, because the image was requested but the + # byte stream was not generated yet. However, we only need to do + # this, when the image was generated before it was requested + # (otherwise this is already taken care of). + self._conversion_queue.modify_priority(image, Priority.Low) + return image.image def get_image_bytes(self, name): """ - Returns the byte string for an image - If not present wait for the background thread to process it. + Returns the byte string for an image. If not present wait for the + background thread to process it. """ log.debug(u'get_image_bytes %s' % name) - if not self._cache[name].image_bytes: - while self._cache[name].dirty: + image = self._cache[name] + if image.image_bytes is None: + self._conversion_queue.modify_priority(image, Priority.Urgent) + # make sure we are running and if not give it a kick + self.process_updates() + while image.image_bytes is None: log.debug(u'get_image_bytes - waiting') time.sleep(0.1) - return self._cache[name].image_bytes + return image.image_bytes def del_image(self, name): """ - Delete the Image from the Cache + Delete the Image from the cache. """ log.debug(u'del_image %s' % name) if name in self._cache: + self._conversion_queue.remove(self._cache[name]) del self._cache[name] - def add_image(self, name, path): + def add_image(self, name, path, source, background): """ - Add image to cache if it is not already there + Add image to cache if it is not already there. """ log.debug(u'add_image %s:%s' % (name, path)) if not name in self._cache: - image = Image() - image.name = name - image.path = path - image.image = resize_image(path, - self.width, self.height) + image = Image(name, path, source, background) self._cache[name] = image + self._conversion_queue.put((image.priority, image)) else: log.debug(u'Image in cache %s:%s' % (name, path)) - self._cache_dirty = True - # only one thread please - if not self._thread_running: - self.image_thread.start() + # We want only one thread. + if not self._imageThread.isRunning(): + self._imageThread.start() - def process(self): + def _process(self): """ - Controls the processing called from a QThread + Controls the processing called from a ``QtCore.QThread``. """ - log.debug(u'process - started') - self._thread_running = True - self.clean_cache() - # data loaded since we started ? - while self._cache_dirty: - log.debug(u'process - recycle') - self.clean_cache() - self._thread_running = False - log.debug(u'process - ended') + log.debug(u'_process - started') + while not self._conversion_queue.empty(): + self._process_cache() + log.debug(u'_process - ended') - def clean_cache(self): + def _process_cache(self): """ Actually does the work. """ - log.debug(u'clean_cache') - # we will clean the cache now - self._cache_dirty = False - for key in self._cache.keys(): - image = self._cache[key] - if image.dirty: - image.image_bytes = image_to_byte(image.image) - image.dirty = False \ No newline at end of file + log.debug(u'_process_cache') + image = self._conversion_queue.get()[1] + # Generate the QImage for the image. + if image.image is None: + image.image = resize_image(image.path, self.width, self.height, + image.background) + # Set the priority to Lowest and stop here as we need to process + # more important images first. + if image.priority == Priority.Normal: + self._conversion_queue.modify_priority(image, Priority.Lowest) + return + # For image with high priority we set the priority to Low, as the + # byte stream might be needed earlier the byte stream of image with + # Normal priority. We stop here as we need to process more important + # images first. + elif image.priority == Priority.High: + self._conversion_queue.modify_priority(image, Priority.Low) + return + # Generate the byte stream for the image. + if image.image_bytes is None: + image.image_bytes = image_to_byte(image.image) diff --git a/openlp/core/lib/listwidgetwithdnd.py b/openlp/core/lib/listwidgetwithdnd.py new file mode 100644 index 000000000..31be1f5be --- /dev/null +++ b/openlp/core/lib/listwidgetwithdnd.py @@ -0,0 +1,110 @@ +# -*- 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, Gerald Britton, Jonathan # +# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # +# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias # +# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, 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 # +############################################################################### +""" +Extend QListWidget to handle drag and drop functionality +""" +import os.path + +from PyQt4 import QtCore, QtGui + +from openlp.core.lib import Receiver + +class ListWidgetWithDnD(QtGui.QListWidget): + """ + Provide a list widget to store objects and handle drag and drop events + """ + def __init__(self, parent=None, name=u''): + """ + Initialise the list widget + """ + QtGui.QListWidget.__init__(self, parent) + self.mimeDataText = name + assert(self.mimeDataText) + + def activateDnD(self): + """ + Activate DnD of widget + """ + self.setAcceptDrops(True) + self.setDragDropMode(QtGui.QAbstractItemView.DragDrop) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'%s_dnd' % self.mimeDataText), + self.parent().loadFile) + + def mouseMoveEvent(self, event): + """ + Drag and drop event does not care what data is selected + as the recipient will use events to request the data move + just tell it what plugin to call + """ + if event.buttons() != QtCore.Qt.LeftButton: + event.ignore() + return + if not self.selectedItems(): + event.ignore() + return + drag = QtGui.QDrag(self) + mimeData = QtCore.QMimeData() + drag.setMimeData(mimeData) + mimeData.setText(self.mimeDataText) + drag.start(QtCore.Qt.CopyAction) + + def dragEnterEvent(self, event): + if event.mimeData().hasUrls(): + event.accept() + else: + event.ignore() + + def dragMoveEvent(self, event): + if event.mimeData().hasUrls(): + event.setDropAction(QtCore.Qt.CopyAction) + event.accept() + else: + event.ignore() + + def dropEvent(self, event): + """ + Receive drop event check if it is a file and process it if it is. + + ``event`` + Handle of the event pint passed + """ + if event.mimeData().hasUrls(): + event.setDropAction(QtCore.Qt.CopyAction) + event.accept() + files = [] + for url in event.mimeData().urls(): + localFile = unicode(url.toLocalFile()) + if os.path.isfile(localFile): + files.append(localFile) + elif os.path.isdir(localFile): + listing = os.listdir(localFile) + for file in listing: + files.append(os.path.join(localFile, file)) + Receiver.send_message(u'%s_dnd' % self.mimeDataText, files) + else: + event.ignore() diff --git a/openlp/core/lib/mailto/LICENSE b/openlp/core/lib/mailto/LICENSE deleted file mode 100644 index d8ab2d8d2..000000000 --- a/openlp/core/lib/mailto/LICENSE +++ /dev/null @@ -1,38 +0,0 @@ -PSF LICENSE AGREEMENT FOR PYTHON 2.7.1 - - 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), - and the Individual or Organization ("Licensee") accessing and otherwise - using Python 2.7.1 software in source or binary form and its associated - documentation. - 2. Subject to the terms and conditions of this License Agreement, PSF hereby - grants Licensee a nonexclusive, royalty-free, world-wide license to - reproduce, analyze, test, perform and/or display publicly, prepare - derivative works, distribute, and otherwise use Python 2.7.1 alone or in any - derivative version, provided, however, that PSF's License Agreement and - PSF's notice of copyright, i.e., "Copyright (c) 2001-2010 Python Software - Foundation; All Rights Reserved" are retained in Python 2.7.1 alone or in - any derivative version prepared by Licensee. - 3. In the event Licensee prepares a derivative work that is based on or - incorporates Python 2.7.1 or any part thereof, and wants to make the - derivative work available to others as provided herein, then Licensee hereby - agrees to include in any such work a brief summary of the changes made to - Python 2.7.1. - 4. PSF is making Python 2.7.1 available to Licensee on an "AS IS" basis. PSF - MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF - EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION - OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT - THE USE OF PYTHON 2.7.1 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. - 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 2.7.1 FOR - ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF - MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 2.7.1, OR ANY DERIVATIVE - THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - 6. This License Agreement will automatically terminate upon a material breach - of its terms and conditions. - 7. Nothing in this License Agreement shall be deemed to create any relationship - of agency, partnership, or joint venture between PSF and Licensee. This - License Agreement does not grant permission to use PSF trademarks or trade - name in a trademark sense to endorse or promote products or services of - Licensee, or any third party. - 8. By copying, installing or otherwise using Python 2.7.1, Licensee agrees to - be bound by the terms and conditions of this License Agreement. - diff --git a/openlp/core/lib/mailto/__init__.py b/openlp/core/lib/mailto/__init__.py deleted file mode 100644 index f0e23f1b5..000000000 --- a/openlp/core/lib/mailto/__init__.py +++ /dev/null @@ -1,321 +0,0 @@ -# -*- coding: utf-8 -*- -# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 - -############################################################################### -# Utilities for opening files or URLs in the registered default application # -# and for sending e-mail using the user's preferred composer. # -# --------------------------------------------------------------------------- # -# Copyright (c) 2007 Antonio Valentino # -# All rights reserved. # -# --------------------------------------------------------------------------- # -# This program offered under the PSF License as published by the Python # -# Software Foundation. # -# # -# The license text can be found at http://docs.python.org/license.html # -# # -# This code is taken from: http://code.activestate.com/recipes/511443 # -# Modified for use in OpenLP # -############################################################################### - -__version__ = u'1.1' -__all__ = [u'open', u'mailto'] - -import os -import sys -import webbrowser -import subprocess - -from email.Utils import encode_rfc2231 - -_controllers = {} -_open = None - - -class BaseController(object): - """ - Base class for open program controllers. - """ - - def __init__(self, name): - self.name = name - - def open(self, filename): - raise NotImplementedError - - -class Controller(BaseController): - """ - Controller for a generic open program. - """ - - def __init__(self, *args): - super(Controller, self).__init__(os.path.basename(args[0])) - self.args = list(args) - - def _invoke(self, cmdline): - if sys.platform[:3] == u'win': - closefds = False - startupinfo = subprocess.STARTUPINFO() - startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW - else: - closefds = True - startupinfo = None - - if (os.environ.get(u'DISPLAY') or sys.platform[:3] == u'win' or \ - sys.platform == u'darwin'): - inout = file(os.devnull, u'r+') - else: - # for TTY programs, we need stdin/out - inout = None - - # if possible, put the child precess in separate process group, - # so keyboard interrupts don't affect child precess as well as - # Python - setsid = getattr(os, u'setsid', None) - if not setsid: - setsid = getattr(os, u'setpgrp', None) - - pipe = subprocess.Popen(cmdline, stdin=inout, stdout=inout, - stderr=inout, close_fds=closefds, preexec_fn=setsid, - startupinfo=startupinfo) - - # It is assumed that this kind of tools (gnome-open, kfmclient, - # exo-open, xdg-open and open for OSX) immediately exit after lauching - # the specific application - returncode = pipe.wait() - if hasattr(self, u'fixreturncode'): - returncode = self.fixreturncode(returncode) - return not returncode - - def open(self, filename): - if isinstance(filename, basestring): - cmdline = self.args + [filename] - else: - # assume it is a sequence - cmdline = self.args + filename - try: - return self._invoke(cmdline) - except OSError: - return False - - -# Platform support for Windows -if sys.platform[:3] == u'win': - - class Start(BaseController): - """ - Controller for the win32 start progam through os.startfile. - """ - - def open(self, filename): - try: - os.startfile(filename) - except WindowsError: - # [Error 22] No application is associated with the specified - # file for this operation: '' - return False - else: - return True - - _controllers[u'windows-default'] = Start(u'start') - _open = _controllers[u'windows-default'].open - - -# Platform support for MacOS -elif sys.platform == u'darwin': - _controllers[u'open'] = Controller(u'open') - _open = _controllers[u'open'].open - - -# Platform support for Unix -else: - - import commands - - # @WARNING: use the private API of the webbrowser module - from webbrowser import _iscommand - - class KfmClient(Controller): - """ - Controller for the KDE kfmclient program. - """ - - def __init__(self, kfmclient=u'kfmclient'): - super(KfmClient, self).__init__(kfmclient, u'exec') - self.kde_version = self.detect_kde_version() - - def detect_kde_version(self): - kde_version = None - try: - info = commands.getoutput(u'kfmclient --version') - - for line in info.splitlines(): - if line.startswith(u'KDE'): - kde_version = line.split(u':')[-1].strip() - break - except (OSError, RuntimeError): - pass - - return kde_version - - def fixreturncode(self, returncode): - if returncode is not None and self.kde_version > u'3.5.4': - return returncode - else: - return os.EX_OK - - def detect_desktop_environment(): - """ - Checks for known desktop environments - - Return the desktop environments name, lowercase (kde, gnome, xfce) - or "generic" - """ - - desktop_environment = u'generic' - - if os.environ.get(u'KDE_FULL_SESSION') == u'true': - desktop_environment = u'kde' - elif os.environ.get(u'GNOME_DESKTOP_SESSION_ID'): - desktop_environment = u'gnome' - else: - try: - info = commands.getoutput(u'xprop -root _DT_SAVE_MODE') - if u' = "xfce4"' in info: - desktop_environment = u'xfce' - except (OSError, RuntimeError): - pass - - return desktop_environment - - - def register_X_controllers(): - if _iscommand(u'kfmclient'): - _controllers[u'kde-open'] = KfmClient() - - for command in (u'gnome-open', u'exo-open', u'xdg-open'): - if _iscommand(command): - _controllers[command] = Controller(command) - - - def get(): - controllers_map = { - u'gnome': u'gnome-open', - u'kde': u'kde-open', - u'xfce': u'exo-open', - } - - desktop_environment = detect_desktop_environment() - - try: - controller_name = controllers_map[desktop_environment] - return _controllers[controller_name].open - - except KeyError: - if _controllers.has_key(u'xdg-open'): - return _controllers[u'xdg-open'].open - else: - return webbrowser.open - - if os.environ.get(u'DISPLAY'): - register_X_controllers() - _open = get() - - -def open(filename): - """ - Open a file or an URL in the registered default application. - """ - - return _open(filename) - - -def _fix_addersses(**kwargs): - for headername in (u'address', u'to', u'cc', u'bcc'): - try: - headervalue = kwargs[headername] - if not headervalue: - del kwargs[headername] - continue - elif not isinstance(headervalue, basestring): - # assume it is a sequence - headervalue = u','.join(headervalue) - except KeyError: - pass - except TypeError: - raise TypeError(u'string or sequence expected for "%s", %s ' - u'found' % (headername, type(headervalue).__name__)) - else: - translation_map = {u'%': u'%25', u'&': u'%26', u'?': u'%3F'} - for char, replacement in translation_map.items(): - headervalue = headervalue.replace(char, replacement) - kwargs[headername] = headervalue - - return kwargs - - -def mailto_format(**kwargs): - """ - Compile mailto string from call parameters - """ - # @TODO: implement utf8 option - - kwargs = _fix_addersses(**kwargs) - parts = [] - for headername in (u'to', u'cc', u'bcc', u'subject', u'body', u'attach'): - if kwargs.has_key(headername): - headervalue = kwargs[headername] - if not headervalue: - continue - if headername in (u'address', u'to', u'cc', u'bcc'): - parts.append(u'%s=%s' % (headername, headervalue)) - else: - headervalue = encode_rfc2231(headervalue) # @TODO: check - parts.append(u'%s=%s' % (headername, headervalue)) - - mailto_string = u'mailto:%s' % kwargs.get(u'address', '') - if parts: - mailto_string = u'%s?%s' % (mailto_string, u'&'.join(parts)) - - return mailto_string - - -def mailto(address, to=None, cc=None, bcc=None, subject=None, body=None, - attach=None): - """ - Send an e-mail using the user's preferred composer. - - Open the user's preferred e-mail composer in order to send a mail to - address(es) that must follow the syntax of RFC822. Multiple addresses - may be provided (for address, cc and bcc parameters) as separate - arguments. - - All parameters provided are used to prefill corresponding fields in - the user's e-mail composer. The user will have the opportunity to - change any of this information before actually sending the e-mail. - - ``address`` - specify the destination recipient - - ``cc`` - specify a recipient to be copied on the e-mail - - ``bcc`` - specify a recipient to be blindly copied on the e-mail - - ``subject`` - specify a subject for the e-mail - - ``body`` - specify a body for the e-mail. Since the user will be able to make - changes before actually sending the e-mail, this can be used to provide - the user with a template for the e-mail text may contain linebreaks - - ``attach`` - specify an attachment for the e-mail. file must point to an existing - file - """ - - mailto_string = mailto_format(**locals()) - return open(mailto_string) - diff --git a/openlp/core/lib/mediamanageritem.py b/openlp/core/lib/mediamanageritem.py index a9484795b..1bddb2d93 100644 --- a/openlp/core/lib/mediamanageritem.py +++ b/openlp/core/lib/mediamanageritem.py @@ -5,10 +5,11 @@ # 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 # +# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # +# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias # +# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, 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 # @@ -28,12 +29,14 @@ Provides the generic functions for interfacing plugins with the Media Manager. """ import logging import os +import re from PyQt4 import QtCore, QtGui -from openlp.core.lib import context_menu_action, context_menu_separator, \ - SettingsManager, OpenLPToolbar, ServiceItem, StringContent, build_icon, \ - translate, Receiver +from openlp.core.lib import SettingsManager, OpenLPToolbar, ServiceItem, \ + StringContent, build_icon, translate, Receiver, ListWidgetWithDnD +from openlp.core.lib.ui import UiStrings, context_menu_action, \ + context_menu_separator, critical_error_message_box log = logging.getLogger(__name__) @@ -65,19 +68,15 @@ class MediaManagerItem(QtGui.QWidget): When creating a descendant class from this class for your plugin, the following member variables should be set. - ``self.OnNewPrompt`` + ``self.onNewPrompt`` + Defaults to *'Select Image(s)'*. - ``self.OnNewFileMasks`` + ``self.onNewFileMasks`` Defaults to *'Images (*.jpg *jpeg *.gif *.png *.bmp)'*. This assumes that the new action is to load a file. If not, you need to override the ``OnNew`` method. - ``self.ListViewWithDnD_class`` - This must be a **class**, not an object, descended from - ``openlp.core.lib.BaseListWithDnD`` that is not used in any - other part of OpenLP. - ``self.PreviewFunction`` This must be a method which returns a QImage to represent the item (usually a preview). No scaling is required, that is @@ -92,31 +91,29 @@ class MediaManagerItem(QtGui.QWidget): Constructor to create the media manager item. """ QtGui.QWidget.__init__(self) - self.parent = parent - #TODO: plugin should not be the parent in future - self.plugin = parent # plugin + self.hide() + self.whitespace = re.compile(r'[\W_]+', re.UNICODE) + self.plugin = plugin visible_title = self.plugin.getString(StringContent.VisibleName) self.title = unicode(visible_title[u'title']) - self.settingsSection = self.plugin.name.lower() - if isinstance(icon, QtGui.QIcon): - self.icon = icon - elif isinstance(icon, basestring): - self.icon.addPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(icon)), - QtGui.QIcon.Normal, QtGui.QIcon.Off) - else: - self.icon = None + self.settingsSection = self.plugin.name + self.icon = None + if icon: + self.icon = build_icon(icon) self.toolbar = None self.remoteTriggered = None - self.serviceItemIconName = None self.singleServiceItem = True + self.quickPreviewAllowed = False + self.hasSearch = False self.pageLayout = QtGui.QVBoxLayout(self) self.pageLayout.setSpacing(0) - self.pageLayout.setContentsMargins(4, 0, 4, 0) + self.pageLayout.setMargin(0) self.requiredIcons() self.setupUi() self.retranslateUi() + self.autoSelectId = -1 QtCore.QObject.connect(Receiver.get_receiver(), - QtCore.SIGNAL(u'%s_service_load' % self.parent.name.lower()), + QtCore.SIGNAL(u'%s_service_load' % self.plugin.name), self.serviceLoad) def requiredIcons(self): @@ -163,20 +160,22 @@ class MediaManagerItem(QtGui.QWidget): ``icon`` The icon of the button. This can be an instance of QIcon, or a - string cotaining either the absolute path to the image, or an + string containing either the absolute path to the image, or an internal resource path starting with ':/'. ``slot`` The method to call when the button is clicked. - ``objectname`` - The name of the button. + ``checkable`` + If *True* the button has two, *off* and *on*, states. Default is + *False*, which means the buttons has only one state. """ # NB different order (when I broke this out, I didn't want to # break compatability), but it makes sense for the icon to # come before the tooltip (as you have to have an icon, but # not neccesarily a tooltip) - self.toolbar.addToolbarButton(title, icon, tooltip, slot, checkable) + return self.toolbar.addToolbarButton(title, icon, tooltip, slot, + checkable) def addToolbarSeparator(self): """ @@ -192,133 +191,122 @@ class MediaManagerItem(QtGui.QWidget): """ # Add a toolbar self.addToolbar() - #Allow the plugin to define buttons at start of bar + # Allow the plugin to define buttons at start of bar self.addStartHeaderBar() - #Add the middle of the tool bar (pre defined) + # Add the middle of the tool bar (pre defined) self.addMiddleHeaderBar() - #Allow the plugin to define buttons at end of bar + # Allow the plugin to define buttons at end of bar self.addEndHeaderBar() - #Add the list view + # Add the list view self.addListViewToToolBar() def addMiddleHeaderBar(self): """ Create buttons for the media item toolbar """ + toolbar_actions = [] ## Import Button ## if self.hasImportIcon: - import_string = self.plugin.getString(StringContent.Import) - self.addToolbarButton( - import_string[u'title'], - import_string[u'tooltip'], - u':/general/general_import.png', self.onImportClick) + toolbar_actions.append([StringContent.Import, + u':/general/general_import.png', self.onImportClick]) ## Load Button ## if self.hasFileIcon: - load_string = self.plugin.getString(StringContent.Load) - self.addToolbarButton( - load_string[u'title'], - load_string[u'tooltip'], - u':/general/general_open.png', self.onFileClick) + toolbar_actions.append([StringContent.Load, + u':/general/general_open.png', self.onFileClick]) ## New Button ## if self.hasNewIcon: - new_string = self.plugin.getString(StringContent.New) - self.addToolbarButton( - new_string[u'title'], - new_string[u'tooltip'], - u':/general/general_new.png', self.onNewClick) + toolbar_actions.append([StringContent.New, + u':/general/general_new.png', self.onNewClick]) ## Edit Button ## if self.hasEditIcon: - edit_string = self.plugin.getString(StringContent.Edit) - self.addToolbarButton( - edit_string[u'title'], - edit_string[u'tooltip'], - u':/general/general_edit.png', self.onEditClick) + toolbar_actions.append([StringContent.Edit, + u':/general/general_edit.png', self.onEditClick]) ## Delete Button ## if self.hasDeleteIcon: - delete_string = self.plugin.getString(StringContent.Delete) - self.addToolbarButton( - delete_string[u'title'], - delete_string[u'tooltip'], - u':/general/general_delete.png', self.onDeleteClick) - ## Separator Line ## - self.addToolbarSeparator() + toolbar_actions.append([StringContent.Delete, + u':/general/general_delete.png', self.onDeleteClick]) ## Preview ## - preview_string = self.plugin.getString(StringContent.Preview) - self.addToolbarButton( - preview_string[u'title'], - preview_string[u'tooltip'], - u':/general/general_preview.png', self.onPreviewClick) - ## Live Button ## - live_string = self.plugin.getString(StringContent.Live) - self.addToolbarButton( - live_string[u'title'], - live_string[u'tooltip'], - u':/general/general_live.png', self.onLiveClick) + toolbar_actions.append([StringContent.Preview, + u':/general/general_preview.png', self.onPreviewClick]) + ## Live Button ## + toolbar_actions.append([StringContent.Live, + u':/general/general_live.png', self.onLiveClick]) ## Add to service Button ## - service_string = self.plugin.getString(StringContent.Service) - self.addToolbarButton( - service_string[u'title'], - service_string[u'tooltip'], - u':/general/general_add.png', self.onAddClick) + toolbar_actions.append([StringContent.Service, + u':/general/general_add.png', self.onAddClick]) + for action in toolbar_actions: + if action[0] == StringContent.Preview: + self.addToolbarSeparator() + self.addToolbarButton( + self.plugin.getString(action[0])[u'title'], + self.plugin.getString(action[0])[u'tooltip'], + action[1], action[2]) def addListViewToToolBar(self): """ Creates the main widget for listing items the media item is tracking """ - #Add the List widget - self.listView = self.ListViewWithDnD_class(self) - self.listView.uniformItemSizes = True - self.listView.setGeometry(QtCore.QRect(10, 100, 256, 591)) + # Add the List widget + self.listView = ListWidgetWithDnD(self, self.plugin.name) self.listView.setSpacing(1) self.listView.setSelectionMode( QtGui.QAbstractItemView.ExtendedSelection) self.listView.setAlternatingRowColors(True) - self.listView.setDragEnabled(True) self.listView.setObjectName(u'%sListView' % self.plugin.name) - #Add to pageLayout + # Add to pageLayout self.pageLayout.addWidget(self.listView) - #define and add the context menu - self.listView.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) - name_string = self.plugin.getString(StringContent.Name) + # define and add the context menu + self.listView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) if self.hasEditIcon: - self.listView.addAction( - context_menu_action( - self.listView, u':/general/general_edit.png', - self.plugin.getString(StringContent.Edit)[u'title'], - self.onEditClick)) - self.listView.addAction(context_menu_separator(self.listView)) + context_menu_action( + self.listView, u':/general/general_edit.png', + self.plugin.getString(StringContent.Edit)[u'title'], + self.onEditClick) + context_menu_separator(self.listView) if self.hasDeleteIcon: - self.listView.addAction( - context_menu_action( - self.listView, u':/general/general_delete.png', - self.plugin.getString(StringContent.Delete)[u'title'], - self.onDeleteClick)) - self.listView.addAction(context_menu_separator(self.listView)) - self.listView.addAction( context_menu_action( - self.listView, u':/general/general_preview.png', - self.plugin.getString(StringContent.Preview)[u'title'], - self.onPreviewClick)) - self.listView.addAction( - context_menu_action( - self.listView, u':/general/general_live.png', - self.plugin.getString(StringContent.Live)[u'title'], - self.onLiveClick)) - self.listView.addAction( + self.listView, u':/general/general_delete.png', + self.plugin.getString(StringContent.Delete)[u'title'], + self.onDeleteClick, [QtCore.Qt.Key_Delete]) + context_menu_separator(self.listView) + context_menu_action( + self.listView, u':/general/general_preview.png', + self.plugin.getString(StringContent.Preview)[u'title'], + self.onPreviewClick, [QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return]) + context_menu_action( + self.listView, u':/general/general_live.png', + self.plugin.getString(StringContent.Live)[u'title'], + self.onLiveClick, [QtCore.Qt.ShiftModifier + QtCore.Qt.Key_Enter, + QtCore.Qt.ShiftModifier + QtCore.Qt.Key_Return]) + context_menu_action( + self.listView, u':/general/general_add.png', + self.plugin.getString(StringContent.Service)[u'title'], + self.onAddClick, [QtCore.Qt.Key_Plus, QtCore.Qt.Key_Equal]) + if self.addToServiceItem: context_menu_action( self.listView, u':/general/general_add.png', - self.plugin.getString(StringContent.Service)[u'title'], - self.onAddClick)) - if self.addToServiceItem: - self.listView.addAction( - context_menu_action( - self.listView, u':/general/general_add.png', - translate('OpenLP.MediaManagerItem', - '&Add to selected Service Item'), - self.onAddEditClick)) + translate('OpenLP.MediaManagerItem', + '&Add to selected Service Item'), self.onAddEditClick) + self.addCustomContextActions() + # Create the context menu and add all actions from the listView. + self.menu = QtGui.QMenu() + self.menu.addActions(self.listView.actions()) QtCore.QObject.connect(self.listView, QtCore.SIGNAL(u'doubleClicked(QModelIndex)'), self.onClickPressed) + QtCore.QObject.connect(self.listView, + QtCore.SIGNAL(u'itemSelectionChanged()'), + self.onSelectionChange) + QtCore.QObject.connect(self.listView, + QtCore.SIGNAL('customContextMenuRequested(QPoint)'), + self.contextMenu) + + def addCustomContextActions(self): + """ + Implement this method in your descendent media manager item to + add any context menu items. This method is called automatically. + """ + pass def initialise(self): """ @@ -344,18 +332,85 @@ class MediaManagerItem(QtGui.QWidget): Add a file to the list widget to make it available for showing """ files = QtGui.QFileDialog.getOpenFileNames( - self, self.OnNewPrompt, + self, self.onNewPrompt, SettingsManager.get_last_dir(self.settingsSection), - self.OnNewFileMasks) + self.onNewFileMasks) log.info(u'New files(s) %s', unicode(files)) if files: Receiver.send_message(u'cursor_busy') - self.loadList(files) + self.validateAndLoad(files) + Receiver.send_message(u'cursor_normal') + + def loadFile(self, files): + """ + Turn file from Drag and Drop into an array so the Validate code + can run it. + + ``files`` + The list of files to be loaded + """ + newFiles = [] + errorShown = False + for file in files: + type = file.split(u'.')[-1] + if type.lower() not in self.onNewFileMasks: + if not errorShown: + critical_error_message_box( + translate('OpenLP.MediaManagerItem', + 'Invalid File Type'), + unicode(translate('OpenLP.MediaManagerItem', + 'Invalid File %s.\nSuffix not supported')) + % file) + errorShown = True + else: + newFiles.append(file) + if file: + self.validateAndLoad(newFiles) + + def validateAndLoad(self, files): + """ + Process a list for files either from the File Dialog or from Drag and + Drop + + ``files`` + The files to be loaded + """ + names = [] + fullList = [] + for count in range(0, self.listView.count()): + names.append(unicode(self.listView.item(count).text())) + fullList.append(unicode(self.listView.item(count). + data(QtCore.Qt.UserRole).toString())) + duplicatesFound = False + filesAdded = False + for file in files: + filename = os.path.split(unicode(file))[1] + if filename in names: + duplicatesFound = True + else: + filesAdded = True + fullList.append(file) + if fullList and filesAdded: + self.listView.clear() + self.loadList(fullList) lastDir = os.path.split(unicode(files[0]))[0] SettingsManager.set_last_dir(self.settingsSection, lastDir) SettingsManager.set_list(self.settingsSection, self.settingsSection, self.getFileList()) - Receiver.send_message(u'cursor_normal') + if duplicatesFound: + critical_error_message_box( + UiStrings().Duplicate, + unicode(translate('OpenLP.MediaManagerItem', + 'Duplicate files were found on import and were ignored.'))) + + def contextMenu(self, point): + item = self.listView.itemAt(point) + # Decide if we have to show the context menu or not. + if item is None: + return + if not item.flags() & QtCore.Qt.ItemIsSelectable: + return + self.menu.exec_(self.listView.mapToGlobal(point)) def getFileList(self): """ @@ -370,55 +425,35 @@ class MediaManagerItem(QtGui.QWidget): count += 1 return filelist - def validate(self, file, thumb): - """ - Validates to see if the file still exists or thumbnail is up to date - """ - if not os.path.exists(file): - return False - if os.path.exists(thumb): - filedate = os.stat(file).st_mtime - thumbdate = os.stat(thumb).st_mtime - # if file updated rebuild icon - if filedate > thumbdate: - self.iconFromFile(file, thumb) - else: - self.iconFromFile(file, thumb) - return True - - def iconFromFile(self, file, thumb): - """ - Create a thumbnail icon from a given file - - ``file`` - The file to create the icon from - - ``thumb`` - The filename to save the thumbnail to - """ - icon = build_icon(unicode(file)) - pixmap = icon.pixmap(QtCore.QSize(88, 50)) - ext = os.path.splitext(thumb)[1].lower() - pixmap.save(thumb, ext[1:]) - return icon - def loadList(self, list): raise NotImplementedError(u'MediaManagerItem.loadList needs to be ' u'defined by the plugin') def onNewClick(self): - raise NotImplementedError(u'MediaManagerItem.onNewClick needs to be ' - u'defined by the plugin') + """ + Hook for plugins to define behaviour for adding new items. + """ + pass def onEditClick(self): - raise NotImplementedError(u'MediaManagerItem.onEditClick needs to be ' - u'defined by the plugin') + """ + Hook for plugins to define behaviour for editing items. + """ + pass def onDeleteClick(self): raise NotImplementedError(u'MediaManagerItem.onDeleteClick needs to ' u'be defined by the plugin') - def generateSlideData(self, serviceItem, item=None, xmlVersion=False): + def onFocus(self): + """ + Run when a tab in the media manager gains focus. This gives the media + item a chance to focus any elements it wants to. + """ + pass + + def generateSlideData(self, serviceItem, item=None, xmlVersion=False, + remote=False): raise NotImplementedError(u'MediaManagerItem.generateSlideData needs ' u'to be defined by the plugin') @@ -432,22 +467,33 @@ class MediaManagerItem(QtGui.QWidget): else: self.onPreviewClick() - def onPreviewClick(self): + def onSelectionChange(self): + """ + Allows the change of current item in the list to be actioned + """ + if QtCore.QSettings().value(u'advanced/single click preview', + QtCore.QVariant(False)).toBool() and self.quickPreviewAllowed \ + and self.listView.selectedIndexes() \ + and self.autoSelectId == -1: + self.onPreviewClick(True) + + def onPreviewClick(self, keepFocus=False): """ Preview an item by building a service item then adding that service item to the preview slide controller. """ if not self.listView.selectedIndexes() and not self.remoteTriggered: - QtGui.QMessageBox.information(self, - translate('OpenLP.MediaManagerItem', 'No Items Selected'), + QtGui.QMessageBox.information(self, UiStrings().NISp, translate('OpenLP.MediaManagerItem', - 'You must select one or more items to preview.')) + 'You must select one or more items to preview.')) else: log.debug(u'%s Preview requested', self.plugin.name) serviceItem = self.buildServiceItem() if serviceItem: serviceItem.from_plugin = True - self.parent.previewController.addServiceItem(serviceItem) + self.plugin.previewController.addServiceItem(serviceItem) + if keepFocus: + self.listView.setFocus() def onLiveClick(self): """ @@ -455,65 +501,72 @@ class MediaManagerItem(QtGui.QWidget): item to the live slide controller. """ if not self.listView.selectedIndexes(): - QtGui.QMessageBox.information(self, - translate('OpenLP.MediaManagerItem', 'No Items Selected'), + QtGui.QMessageBox.information(self, UiStrings().NISp, translate('OpenLP.MediaManagerItem', 'You must select one or more items to send live.')) else: - log.debug(u'%s Live requested', self.plugin.name) - serviceItem = self.buildServiceItem() - if serviceItem: + self.goLive() + + def goLive(self, item_id=None, remote=False): + log.debug(u'%s Live requested', self.plugin.name) + item = None + if item_id: + item = self.createItemFromId(item_id) + serviceItem = self.buildServiceItem(item, remote=remote) + if serviceItem: + if not item_id: serviceItem.from_plugin = True - self.parent.liveController.addServiceItem(serviceItem) + self.plugin.liveController.addServiceItem(serviceItem) + + def createItemFromId(self, item_id): + item = QtGui.QListWidgetItem() + item.setData(QtCore.Qt.UserRole, QtCore.QVariant(item_id)) + return item def onAddClick(self): """ Add a selected item to the current service """ if not self.listView.selectedIndexes() and not self.remoteTriggered: - QtGui.QMessageBox.information(self, - translate('OpenLP.MediaManagerItem', 'No Items Selected'), + QtGui.QMessageBox.information(self, UiStrings().NISp, translate('OpenLP.MediaManagerItem', - 'You must select one or more items.')) + 'You must select one or more items to add.')) else: # Is it posssible to process multiple list items to generate # multiple service items? if self.singleServiceItem or self.remoteTriggered: log.debug(u'%s Add requested', self.plugin.name) - serviceItem = self.buildServiceItem(None, True) - if serviceItem: - serviceItem.from_plugin = False - self.parent.serviceManager.addServiceItem(serviceItem, - replace=self.remoteTriggered) + self.addToService(replace=self.remoteTriggered) else: items = self.listView.selectedIndexes() for item in items: - serviceItem = self.buildServiceItem(item, True) - if serviceItem: - serviceItem.from_plugin = False - self.parent.serviceManager.addServiceItem(serviceItem) + self.addToService(item) + + def addToService(self, item=None, replace=None, remote=False): + serviceItem = self.buildServiceItem(item, True, remote=remote) + if serviceItem: + serviceItem.from_plugin = False + self.plugin.serviceManager.addServiceItem(serviceItem, + replace=replace) def onAddEditClick(self): """ Add a selected item to an existing item in the current service. """ if not self.listView.selectedIndexes() and not self.remoteTriggered: - QtGui.QMessageBox.information(self, - translate('OpenLP.MediaManagerItem', 'No items selected'), + QtGui.QMessageBox.information(self, UiStrings().NISp, translate('OpenLP.MediaManagerItem', - 'You must select one or more items')) + 'You must select one or more items.')) else: log.debug(u'%s Add requested', self.plugin.name) - serviceItem = self.parent.serviceManager.getServiceItem() + serviceItem = self.plugin.serviceManager.getServiceItem() if not serviceItem: - QtGui.QMessageBox.information(self, - translate('OpenLP.MediaManagerItem', - 'No Service Item Selected'), + QtGui.QMessageBox.information(self, UiStrings().NISs, translate('OpenLP.MediaManagerItem', 'You must select an existing service item to add to.')) - elif self.title.lower() == serviceItem.name.lower(): + elif self.plugin.name == serviceItem.name: self.generateSlideData(serviceItem) - self.parent.serviceManager.addServiceItem(serviceItem, + self.plugin.serviceManager.addServiceItem(serviceItem, replace=True) else: # Turn off the remote edit update message indicator @@ -523,16 +576,13 @@ class MediaManagerItem(QtGui.QWidget): unicode(translate('OpenLP.MediaManagerItem', 'You must select a %s service item.')) % self.title) - def buildServiceItem(self, item=None, xmlVersion=False): + def buildServiceItem(self, item=None, xmlVersion=False, remote=False): """ Common method for generating a service item """ - serviceItem = ServiceItem(self.parent) - if self.serviceItemIconName: - serviceItem.add_icon(self.serviceItemIconName) - else: - serviceItem.add_icon(self.parent.icon_path) - if self.generateSlideData(serviceItem, item, xmlVersion): + serviceItem = ServiceItem(self.plugin) + serviceItem.add_icon(self.plugin.icon_path) + if self.generateSlideData(serviceItem, item, xmlVersion, remote): return serviceItem else: return None @@ -543,3 +593,56 @@ class MediaManagerItem(QtGui.QWidget): individual service items need to be processed by the plugins """ pass + + def checkSearchResult(self): + """ + Checks if the listView is empty and adds a "No Search Results" item. + """ + if self.listView.count(): + return + message = translate('OpenLP.MediaManagerItem', 'No Search Results') + item = QtGui.QListWidgetItem(message) + item.setFlags(QtCore.Qt.NoItemFlags) + font = QtGui.QFont() + font.setItalic(True) + item.setFont(font) + self.listView.addItem(item) + + def _getIdOfItemToGenerate(self, item, remoteItem): + """ + Utility method to check items being submitted for slide generation. + + ``item`` + The item to check. + + ``remoteItem`` + The id to assign if the slide generation was remotely triggered. + """ + if item is None: + if self.remoteTriggered is None: + item = self.listView.currentItem() + if item is None: + return False + item_id = (item.data(QtCore.Qt.UserRole)).toInt()[0] + else: + item_id = remoteItem + else: + item_id = (item.data(QtCore.Qt.UserRole)).toInt()[0] + return item_id + + def saveAutoSelectId(self): + """ + Sorts out, what item to select after loading a list. + """ + # The item to select has not been set. + if self.autoSelectId == -1: + item = self.listView.currentItem() + if item: + self.autoSelectId = item.data(QtCore.Qt.UserRole).toInt()[0] + + def search(self, string): + """ + Performs a plugin specific search for items containing ``string`` + """ + raise NotImplementedError( + u'Plugin.search needs to be defined by the plugin') diff --git a/openlp/core/lib/mediaplayer.py b/openlp/core/lib/mediaplayer.py new file mode 100644 index 000000000..9a86f708b --- /dev/null +++ b/openlp/core/lib/mediaplayer.py @@ -0,0 +1,137 @@ +# -*- 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, Gerald Britton, Jonathan # +# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # +# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias # +# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### + +from openlp.core.ui.media import MediaState + +class MediaPlayer(object): + """ + This is the base class media Player class to provide OpenLP with a pluggable media display + framework. + """ + + def __init__(self, parent, name=u'media_player'): + self.parent = parent + self.name = name + self.available = self.check_available() + self.isActive = False + self.canBackground = False + self.canFolder = False + self.state = MediaState.Off + self.hasOwnWidget = False + self.audio_extensions_list = [] + self.video_extensions_list = [] + + def check_available(self): + """ + Player is available on this machine + """ + return False + + def setup(self, display): + """ + Create the related widgets for the current display + """ + pass + + def load(self, display): + """ + Load a new media file and check if it is valid + """ + return True + + def resize(self, display): + """ + If the main display size or position is changed, the media widgets + should also resized + """ + pass + + def play(self, display): + """ + Starts playing of current Media File + """ + pass + + def pause(self, display): + """ + Pause of current Media File + """ + pass + + def stop(self, display): + """ + Stop playing of current Media File + """ + pass + + def volume(self, display, vol): + """ + Change volume of current Media File + """ + pass + + def seek(self, display, seekVal): + """ + Change playing position of current Media File + """ + pass + + def reset(self, display): + """ + Remove the current loaded video + """ + pass + + def set_visible(self, display, status): + """ + Show/Hide the media widgets + """ + pass + + def update_ui(self, display): + """ + Do some ui related stuff (e.g. update the seek slider) + """ + pass + + def get_media_display_css(self): + """ + Add css style sheets to htmlbuilder + """ + return u'' + + def get_media_display_javascript(self): + """ + Add javascript functions to htmlbuilder + """ + return u'' + + def get_media_display_html(self): + """ + Add html code to htmlbuilder + """ + return u'' diff --git a/openlp/core/lib/plugin.py b/openlp/core/lib/plugin.py index 8bc5fdb96..624ce2083 100644 --- a/openlp/core/lib/plugin.py +++ b/openlp/core/lib/plugin.py @@ -5,10 +5,11 @@ # 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 # +# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # +# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias # +# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, 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 # @@ -31,6 +32,8 @@ import logging from PyQt4 import QtCore from openlp.core.lib import Receiver +from openlp.core.lib.ui import UiStrings +from openlp.core.utils import get_application_version log = logging.getLogger(__name__) @@ -42,7 +45,11 @@ class PluginStatus(object): Inactive = 0 Disabled = -1 + class StringContent(object): + """ + Provide standard strings for objects to use. + """ Name = u'name' Import = u'import' Load = u'load' @@ -54,6 +61,7 @@ class StringContent(object): Service = u'service' VisibleName = u'visible_name' + class Plugin(QtCore.QObject): """ Base class for openlp plugins to inherit from. @@ -83,8 +91,9 @@ class Plugin(QtCore.QObject): ``checkPreConditions()`` Provides the Plugin with a handle to check if it can be loaded. - ``getMediaManagerItem()`` - Returns an instance of MediaManagerItem to be used in the Media Manager. + ``createMediaManagerItem()`` + Creates a new instance of MediaManagerItem to be used in the Media + Manager. ``addImportMenuItem(import_menu)`` Add an item to the Import menu. @@ -92,8 +101,8 @@ class Plugin(QtCore.QObject): ``addExportMenuItem(export_menu)`` Add an item to the Export menu. - ``getSettingsTab()`` - Returns an instance of SettingsTabItem to be used in the Settings + ``createSettingsTab()`` + Creates a new instance of SettingsTabItem to be used in the Settings dialog. ``addToMenu(menubar)`` @@ -108,7 +117,8 @@ class Plugin(QtCore.QObject): """ log.info(u'loaded') - def __init__(self, name, version=None, pluginHelpers=None): + def __init__(self, name, plugin_helpers=None, media_item_class=None, + settings_tab_class=None, version=None): """ This is the constructor for the plugin object. This provides an easy way for descendent plugins to populate common data. This method *must* @@ -116,7 +126,7 @@ class Plugin(QtCore.QObject): class MyPlugin(Plugin): def __init__(self): - Plugin.__init(self, u'MyPlugin', u'0.1') + Plugin.__init__(self, u'MyPlugin', version=u'0.1') ``name`` Defaults to *None*. The name of the plugin. @@ -124,29 +134,42 @@ class Plugin(QtCore.QObject): ``version`` Defaults to *None*. The version of the plugin. - ``pluginHelpers`` + ``plugin_helpers`` Defaults to *None*. A list of helper objects. + + ``media_item_class`` + The class name of the plugin's media item. + + ``settings_tab_class`` + The class name of the plugin's settings tab. """ + log.debug(u'Plugin %s initialised' % name) QtCore.QObject.__init__(self) self.name = name self.textStrings = {} self.setPluginTextStrings() + self.nameStrings = self.textStrings[StringContent.Name] if version: self.version = version - self.settingsSection = self.name.lower() + else: + self.version = get_application_version()[u'version'] + self.settingsSection = self.name self.icon = None + self.media_item_class = media_item_class + self.settings_tab_class = settings_tab_class + self.settings_tab = None + self.mediaItem = None self.weight = 0 self.status = PluginStatus.Inactive - # Set up logging - self.log = logging.getLogger(self.name) - self.previewController = pluginHelpers[u'preview'] - self.liveController = pluginHelpers[u'live'] - self.renderManager = pluginHelpers[u'render'] - self.serviceManager = pluginHelpers[u'service'] - self.settingsForm = pluginHelpers[u'settings form'] - self.mediadock = pluginHelpers[u'toolbox'] - self.pluginManager = pluginHelpers[u'pluginmanager'] - self.formparent = pluginHelpers[u'formparent'] + self.previewController = plugin_helpers[u'preview'] + self.liveController = plugin_helpers[u'live'] + self.renderer = plugin_helpers[u'renderer'] + self.serviceManager = plugin_helpers[u'service'] + self.settingsForm = plugin_helpers[u'settings form'] + self.mediadock = plugin_helpers[u'toolbox'] + self.pluginManager = plugin_helpers[u'pluginmanager'] + self.formparent = plugin_helpers[u'formparent'] + self.mediaController = plugin_helpers[u'mediacontroller'] QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'%s_add_service_item' % self.name), self.processAddServiceEvent) @@ -156,7 +179,7 @@ class Plugin(QtCore.QObject): Provides the Plugin with a handle to check if it can be loaded. Failing Preconditions does not stop a settings Tab being created - Returns True or False. + Returns ``True`` or ``False``. """ return True @@ -175,6 +198,10 @@ class Plugin(QtCore.QObject): self.status = new_status QtCore.QSettings().setValue( self.settingsSection + u'/status', QtCore.QVariant(self.status)) + if new_status == PluginStatus.Active: + self.initialise() + elif new_status == PluginStatus.Inactive: + self.finalise() def isActive(self): """ @@ -184,12 +211,14 @@ class Plugin(QtCore.QObject): """ return self.status == PluginStatus.Active - def getMediaManagerItem(self): + def createMediaManagerItem(self): """ Construct a MediaManagerItem object with all the buttons and things - you need, and return it for integration into openlp.org. + you need, and return it for integration into OpenLP. """ - pass + if self.media_item_class: + self.mediaItem = self.media_item_class(self.mediadock.media_dock, + self, self.icon) def addImportMenuItem(self, importMenu): """ @@ -218,11 +247,15 @@ class Plugin(QtCore.QObject): """ pass - def getSettingsTab(self): + def createSettingsTab(self, parent): """ - Create a tab for the settings window. + Create a tab for the settings window to display the configurable options + for this plugin to the user. """ - pass + if self.settings_tab_class: + self.settings_tab = self.settings_tab_class(parent, self.name, + self.getString(StringContent.VisibleName)[u'title'], + self.icon_path) def addToMenu(self, menubar): """ @@ -258,31 +291,20 @@ class Plugin(QtCore.QObject): """ if self.mediaItem: self.mediaItem.initialise() - self.insertToolboxItem() + self.mediadock.insert_dock(self.mediaItem, self.icon, self.weight) def finalise(self): """ Called by the plugin Manager to cleanup things. """ - self.removeToolboxItem() - - def removeToolboxItem(self): - """ - Called by the plugin to remove toolbar - """ if self.mediaItem: self.mediadock.remove_dock(self.mediaItem) - if self.settings_tab: - self.settingsForm.removeTab(self.settings_tab) - def insertToolboxItem(self): + def appStartup(self): """ - Called by plugin to replace toolbar + Perform tasks on application starup """ - if self.mediaItem: - self.mediadock.insert_dock(self.mediaItem, self.icon, self.weight) - if self.settings_tab: - self.settingsForm.insertTab(self.settings_tab, self.weight) + pass def usesTheme(self, theme): """ @@ -310,8 +332,66 @@ class Plugin(QtCore.QObject): """ return self.textStrings[name] - def setPluginTextStrings(self): + def setPluginUiTextStrings(self, tooltips): """ Called to define all translatable texts of the plugin """ - pass \ No newline at end of file + ## Load Action ## + self.__setNameTextString(StringContent.Load, + UiStrings().Load, tooltips[u'load']) + ## Import Action ## + self.__setNameTextString(StringContent.Import, + UiStrings().Import, tooltips[u'import']) + ## New Action ## + self.__setNameTextString(StringContent.New, + UiStrings().Add, tooltips[u'new']) + ## Edit Action ## + self.__setNameTextString(StringContent.Edit, + UiStrings().Edit, tooltips[u'edit']) + ## Delete Action ## + self.__setNameTextString(StringContent.Delete, + UiStrings().Delete, tooltips[u'delete']) + ## Preview Action ## + self.__setNameTextString(StringContent.Preview, + UiStrings().Preview, tooltips[u'preview']) + ## Send Live Action ## + self.__setNameTextString(StringContent.Live, + UiStrings().Live, tooltips[u'live']) + ## Add to Service Action ## + self.__setNameTextString(StringContent.Service, + UiStrings().Service, tooltips[u'service']) + + def __setNameTextString(self, name, title, tooltip): + """ + Utility method for creating a plugin's textStrings. This method makes + use of the singular name of the plugin object so must only be called + after this has been set. + """ + self.textStrings[name] = {u'title': title, u'tooltip': tooltip} + + def getDisplayCss(self): + """ + Add css style sheets to htmlbuilder. + """ + return u'' + + def getDisplayJavaScript(self): + """ + Add javascript functions to htmlbuilder. + """ + return u'' + + def refreshCss(self, frame): + """ + Allow plugins to refresh javascript on displayed screen. + + ``frame`` + The Web frame holding the page. + """ + return u'' + + def getDisplayHtml(self): + """ + Add html code to htmlbuilder. + """ + return u'' diff --git a/openlp/core/lib/pluginmanager.py b/openlp/core/lib/pluginmanager.py index 8081cbf71..d63a37dde 100644 --- a/openlp/core/lib/pluginmanager.py +++ b/openlp/core/lib/pluginmanager.py @@ -5,10 +5,11 @@ # 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 # +# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # +# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias # +# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, 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 # @@ -30,7 +31,7 @@ import os import sys import logging -from openlp.core.lib import Plugin, StringContent, PluginStatus +from openlp.core.lib import Plugin, PluginStatus log = logging.getLogger(__name__) @@ -41,6 +42,13 @@ class PluginManager(object): """ log.info(u'Plugin manager loaded') + @staticmethod + def get_instance(): + """ + Obtain a single instance of class. + """ + return PluginManager.instance + def __init__(self, plugin_dir): """ The constructor for the plugin manager. Passes the controllers on to @@ -49,16 +57,14 @@ class PluginManager(object): ``plugin_dir`` The directory to search for plugins. """ - log.info(u'Plugin manager initing') + log.info(u'Plugin manager Initialising') + PluginManager.instance = self if not plugin_dir in sys.path: log.debug(u'Inserting %s into sys.path', plugin_dir) sys.path.insert(0, plugin_dir) self.basepath = os.path.abspath(plugin_dir) log.debug(u'Base path %s ', self.basepath) - self.plugin_helpers = [] self.plugins = [] - # this has to happen after the UI is sorted - # self.find_plugins(plugin_dir) log.info(u'Plugin manager Initialised') def find_plugins(self, plugin_dir, plugin_helpers): @@ -73,7 +79,7 @@ class PluginManager(object): A list of helper objects to pass to the plugins. """ - self.plugin_helpers = plugin_helpers + log.info(u'Finding plugins') startdepth = len(os.path.abspath(plugin_dir).split(os.sep)) log.debug(u'finding plugins in %s at depth %d', unicode(plugin_dir), startdepth) @@ -84,7 +90,7 @@ class PluginManager(object): thisdepth = len(path.split(os.sep)) if thisdepth - startdepth > 2: # skip anything lower down - continue + break modulename = os.path.splitext(path)[0] prefix = os.path.commonprefix([self.basepath, path]) # hack off the plugin base path @@ -102,12 +108,12 @@ class PluginManager(object): plugin_objects = [] for p in plugin_classes: try: - plugin = p(self.plugin_helpers) - log.debug(u'Loaded plugin %s with helpers', unicode(p)) + plugin = p(plugin_helpers) + log.debug(u'Loaded plugin %s', unicode(p)) plugin_objects.append(plugin) except TypeError: - log.exception(u'loaded plugin %s has no helpers', unicode(p)) - plugins_list = sorted(plugin_objects, self.order_by_weight) + log.exception(u'Failed to load plugin %s', unicode(p)) + plugins_list = sorted(plugin_objects, key=lambda plugin: plugin.weight) for plugin in plugins_list: if plugin.checkPreConditions(): log.debug(u'Plugin %s active', unicode(plugin.name)) @@ -116,51 +122,27 @@ class PluginManager(object): plugin.status = PluginStatus.Disabled self.plugins.append(plugin) - def order_by_weight(self, x, y): + def hook_media_manager(self): """ - Sort two plugins and order them by their weight. - - ``x`` - The first plugin. - - ``y`` - The second plugin. - """ - return cmp(x.weight, y.weight) - - def hook_media_manager(self, mediadock): - """ - Loop through all the plugins. If a plugin has a valid media manager - item, add it to the media manager. - - ``mediatoolbox`` - The Media Manager itself. + Create the plugins' media manager items. """ for plugin in self.plugins: if plugin.status is not PluginStatus.Disabled: - plugin.mediaItem = plugin.getMediaManagerItem() + plugin.createMediaManagerItem() - def hook_settings_tabs(self, settingsform=None): + def hook_settings_tabs(self, settings_form=None): """ Loop through all the plugins. If a plugin has a valid settings tab item, add it to the settings tab. Tabs are set for all plugins not just Active ones - ``settingsform`` + ``settings_form`` Defaults to *None*. The settings form to add tabs to. """ for plugin in self.plugins: if plugin.status is not PluginStatus.Disabled: - plugin.settings_tab = plugin.getSettingsTab() - visible_title = plugin.getString(StringContent.VisibleName) - if plugin.settings_tab: - log.debug(u'Inserting settings tab item from %s' % - visible_title[u'title']) - settingsform.addTab(visible_title[u'title'], - plugin.settings_tab) - else: - log.debug( - u'No tab settings in %s' % visible_title[u'title']) + plugin.createSettingsTab(settings_form) + settings_form.plugins = self.plugins def hook_import_menu(self, import_menu): """ @@ -203,14 +185,14 @@ class PluginManager(object): Loop through all the plugins and give them an opportunity to initialise themselves. """ + log.info(u'Initialise Plugins - Started') for plugin in self.plugins: log.info(u'initialising plugins %s in a %s state' % (plugin.name, plugin.isActive())) if plugin.isActive(): plugin.initialise() log.info(u'Initialisation Complete for %s ' % plugin.name) - if not plugin.isActive(): - plugin.removeToolboxItem() + log.info(u'Initialise Plugins - Finished') def finalise_plugins(self): """ @@ -221,4 +203,13 @@ class PluginManager(object): for plugin in self.plugins: if plugin.isActive(): plugin.finalise() - log.info(u'Finalisation Complete for %s ' % plugin.name) \ No newline at end of file + log.info(u'Finalisation Complete for %s ' % plugin.name) + + def get_plugin_by_name(self, name): + """ + Return the plugin which has a name with value ``name``. + """ + for plugin in self.plugins: + if plugin.name == name: + return plugin + return None diff --git a/openlp/core/lib/renderer.py b/openlp/core/lib/renderer.py index fe118b76b..503c60b04 100644 --- a/openlp/core/lib/renderer.py +++ b/openlp/core/lib/renderer.py @@ -5,10 +5,11 @@ # 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 # +# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # +# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias # +# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, 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 # @@ -23,46 +24,325 @@ # with this program; if not, write to the Free Software Foundation, Inc., 59 # # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### -""" -The :mod:`renderer` module enables OpenLP to take the input from plugins and -format it for the output display. -""" + import logging -from PyQt4 import QtWebKit +from PyQt4 import QtGui, QtCore, QtWebKit -from openlp.core.lib import expand_tags, build_lyrics_format_css, \ - build_lyrics_outline_css, Receiver +from openlp.core.lib import ServiceItem, expand_tags, \ + build_lyrics_format_css, build_lyrics_outline_css, Receiver, \ + ItemCapabilities, FormattingTags +from openlp.core.lib.theme import ThemeLevel +from openlp.core.ui import MainDisplay, ScreenList log = logging.getLogger(__name__) +VERSE = u'The Lord said to {r}Noah{/r}: \n' \ + 'There\'s gonna be a {su}floody{/su}, {sb}floody{/sb}\n' \ + 'The Lord said to {g}Noah{/g}:\n' \ + 'There\'s gonna be a {st}floody{/st}, {it}floody{/it}\n' \ + 'Get those children out of the muddy, muddy \n' \ + '{r}C{/r}{b}h{/b}{bl}i{/bl}{y}l{/y}{g}d{/g}{pk}' \ + 'r{/pk}{o}e{/o}{pp}n{/pp} of the Lord\n' +VERSE_FOR_LINE_COUNT = u'\n'.join(map(unicode, xrange(50))) +FOOTER = [u'Arky Arky (Unknown)', u'Public Domain', u'CCLI 123456'] + class Renderer(object): """ - Genarates a pixmap image of a array of text. The Text is formatted to - make sure it fits on the screen and if not extra frames are generated. + Class to pull all Renderer interactions into one place. The plugins will + call helper methods to do the rendering but this class will provide + display defense code. """ log.info(u'Renderer Loaded') - def __init__(self): + def __init__(self, imageManager, themeManager): """ Initialise the renderer. - """ - self._rect = None - self.theme_name = None - self._theme = None - def set_theme(self, theme): + ``imageManager`` + A imageManager instance which takes care of e. g. caching and resizing + images. + + ``themeManager`` + The themeManager instance, used to get the current theme details. """ - Set the theme to be used. + log.debug(u'Initialisation started') + self.themeManager = themeManager + self.imageManager = imageManager + self.screens = ScreenList.get_instance() + self.service_theme = u'' + self.theme_level = u'' + self.override_background = None + self.theme_data = None + self.bg_frame = None + self.force_page = False + self.display = MainDisplay(None, self.imageManager, False, self) + self.display.setup() + + def update_display(self): + """ + Updates the renderer's information about the current screen. + """ + log.debug(u'Update Display') + self._calculate_default() + if self.display: + self.display.close() + self.display = MainDisplay(None, self.imageManager, False, self) + self.display.setup() + self.bg_frame = None + self.theme_data = None + + def set_global_theme(self, global_theme, theme_level=ThemeLevel.Global): + """ + Set the global-level theme and the theme level. + + ``global_theme`` + The global-level theme to be set. + + ``theme_level`` + Defaults to ``ThemeLevel.Global``. The theme level, can be + ``ThemeLevel.Global``, ``ThemeLevel.Service`` or + ``ThemeLevel.Song``. + """ + self.global_theme = global_theme + self.theme_level = theme_level + self.global_theme_data = \ + self.themeManager.getThemeData(self.global_theme) + self.theme_data = None + + def set_service_theme(self, service_theme): + """ + Set the service-level theme. + + ``service_theme`` + The service-level theme to be set. + """ + self.service_theme = service_theme + self.theme_data = None + + def set_override_theme(self, override_theme, override_levels=False): + """ + Set the appropriate theme depending on the theme level. + Called by the service item when building a display frame ``theme`` - The theme to be used. - """ - log.debug(u'set theme') - self._theme = theme - self.theme_name = theme.theme_name + The name of the song-level theme. None means the service + item wants to use the given value. - def set_text_rectangle(self, rect_main, rect_footer): + ``override_levels`` + Used to force the theme data passed in to be used. + + """ + log.debug(u'set override theme to %s', override_theme) + theme_level = self.theme_level + if override_levels: + theme_level = ThemeLevel.Song + if theme_level == ThemeLevel.Global: + theme = self.global_theme + elif theme_level == ThemeLevel.Service: + if self.service_theme == u'': + theme = self.global_theme + else: + theme = self.service_theme + else: + # Images have a theme of -1 + if override_theme and override_theme != -1: + theme = override_theme + elif theme_level == ThemeLevel.Song or \ + theme_level == ThemeLevel.Service: + if self.service_theme == u'': + theme = self.global_theme + else: + theme = self.service_theme + else: + theme = self.global_theme + log.debug(u'theme is now %s', theme) + # Force the theme to be the one passed in. + if override_levels: + self.theme_data = override_theme + else: + self.theme_data = self.themeManager.getThemeData(theme) + self._calculate_default() + self._build_text_rectangle(self.theme_data) + # if No file do not update cache + if self.theme_data.background_filename: + self.imageManager.add_image(self.theme_data.theme_name, + self.theme_data.background_filename, u'theme', + QtGui.QColor(self.theme_data.background_border_color)) + return self._rect, self._rect_footer + + def generate_preview(self, theme_data, force_page=False): + """ + Generate a preview of a theme. + + ``theme_data`` + The theme to generated a preview for. + + ``force_page`` + Flag to tell message lines per page need to be generated. + """ + log.debug(u'generate preview') + # save value for use in format_slide + self.force_page = force_page + # set the default image size for previews + self._calculate_default() + # build a service item to generate preview + serviceItem = ServiceItem() + serviceItem.theme = theme_data + if self.force_page: + # make big page for theme edit dialog to get line count + serviceItem.add_from_text(u'', VERSE_FOR_LINE_COUNT) + else: + self.imageManager.del_image(theme_data.theme_name) + serviceItem.add_from_text(u'', VERSE) + serviceItem.renderer = self + serviceItem.raw_footer = FOOTER + serviceItem.render(True) + if not self.force_page: + self.display.buildHtml(serviceItem) + raw_html = serviceItem.get_rendered_frame(0) + self.display.text(raw_html) + preview = self.display.preview() + # Reset the real screen size for subsequent render requests + self._calculate_default() + return preview + self.force_page = False + + def format_slide(self, text, item): + """ + Calculate how much text can fit on a slide. + + ``text`` + The words to go on the slides. + + ``item`` + The :class:`~openlp.core.lib.serviceitem.ServiceItem` item object. + """ + log.debug(u'format slide') + # Add line endings after each line of text used for bibles. + line_end = u'
' + if item.is_capable(ItemCapabilities.NoLineBreaks): + line_end = u' ' + # Bibles + if item.is_capable(ItemCapabilities.CanWordSplit): + pages = self._paginate_slide_words(text.split(u'\n'), line_end) + # Songs and Custom + elif item.is_capable(ItemCapabilities.CanSoftBreak): + pages = [] + if u'[---]' in text: + while True: + slides = text.split(u'\n[---]\n', 2) + # If there are (at least) two occurrences of [---] we use + # the first two slides (and neglect the last for now). + if len(slides) == 3: + html_text = expand_tags(u'\n'.join(slides[:2])) + # We check both slides to determine if the virtual break is + # needed (there is only one virtual break). + else: + html_text = expand_tags(u'\n'.join(slides)) + html_text = html_text.replace(u'\n', u'
') + if self._text_fits_on_slide(html_text): + # The first two virtual slides fit (as a whole) on one + # slide. Replace the first occurrence of [---]. + text = text.replace(u'\n[---]', u'', 1) + else: + # The first virtual slide fits, which means we have to + # render the first virtual slide. + text_contains_break = u'[---]' in text + if text_contains_break: + try: + text_to_render, text = \ + text.split(u'\n[---]\n', 1) + except: + text_to_render = text.split(u'\n[---]\n')[0] + text = u'' + else: + text_to_render = text + text = u'' + lines = text_to_render.strip(u'\n').split(u'\n') + slides = self._paginate_slide(lines, line_end) + if len(slides) > 1 and text: + # Add all slides apart from the last one the list. + pages.extend(slides[:-1]) + if text_contains_break: + text = slides[-1] + u'\n[---]\n' + text + else: + text = slides[-1] + u'\n'+ text + text = text.replace(u'
', u'\n') + else: + pages.extend(slides) + if u'[---]' not in text: + lines = text.strip(u'\n').split(u'\n') + pages.extend(self._paginate_slide(lines, line_end)) + break + else: + # Clean up line endings. + pages = self._paginate_slide(text.split(u'\n'), line_end) + else: + pages = self._paginate_slide(text.split(u'\n'), line_end) + new_pages = [] + for page in pages: + while page.endswith(u'
'): + page = page[:-4] + new_pages.append(page) + return new_pages + + def _calculate_default(self): + """ + Calculate the default dimentions of the screen. + """ + screen_size = self.screens.current[u'size'] + self.width = screen_size.width() + self.height = screen_size.height() + self.screen_ratio = float(self.height) / float(self.width) + log.debug(u'_calculate default %s, %f' % (screen_size, + self.screen_ratio)) + # 90% is start of footer + self.footer_start = int(self.height * 0.90) + + def _build_text_rectangle(self, theme): + """ + Builds a text block using the settings in ``theme`` + and the size of the display screen.height. + Note the system has a 10 pixel border round the screen + + ``theme`` + The theme to build a text block for. + """ + log.debug(u'_build_text_rectangle') + main_rect = self.get_main_rectangle(theme) + footer_rect = self.get_footer_rectangle(theme) + self._set_text_rectangle(main_rect, footer_rect) + + def get_main_rectangle(self, theme): + """ + Calculates the placement and size of the main rectangle. + + ``theme`` + The theme information + """ + if not theme.font_main_override: + return QtCore.QRect(10, 0, self.width - 20, self.footer_start) + else: + return QtCore.QRect(theme.font_main_x, theme.font_main_y, + theme.font_main_width - 1, theme.font_main_height - 1) + + def get_footer_rectangle(self, theme): + """ + Calculates the placement and size of the footer rectangle. + + ``theme`` + The theme information + """ + if not theme.font_footer_override: + return QtCore.QRect(10, self.footer_start, self.width - 20, + self.height - self.footer_start) + else: + return QtCore.QRect(theme.font_footer_x, + theme.font_footer_y, theme.font_footer_width - 1, + theme.font_footer_height - 1) + + def _set_text_rectangle(self, rect_main, rect_footer): """ Sets the rectangle within which text should be rendered. @@ -72,76 +352,260 @@ class Renderer(object): ``rect_footer`` The footer text block. """ - log.debug(u'set_text_rectangle %s , %s' % (rect_main, rect_footer)) + log.debug(u'_set_text_rectangle %s , %s' % (rect_main, rect_footer)) self._rect = rect_main self._rect_footer = rect_footer self.page_width = self._rect.width() self.page_height = self._rect.height() - if self._theme.font_main_shadow: - self.page_width -= int(self._theme.font_main_shadow_size) - self.page_height -= int(self._theme.font_main_shadow_size) + if self.theme_data.font_main_shadow: + self.page_width -= int(self.theme_data.font_main_shadow_size) + self.page_height -= int(self.theme_data.font_main_shadow_size) self.web = QtWebKit.QWebView() self.web.setVisible(False) self.web.resize(self.page_width, self.page_height) self.web_frame = self.web.page().mainFrame() # Adjust width and height to account for shadow. outline done in css - self.page_shell = u'' \ - u'
' % \ - (build_lyrics_format_css(self._theme, self.page_width, - self.page_height), build_lyrics_outline_css(self._theme)) + html = u""" +
""" % \ + (build_lyrics_format_css(self.theme_data, self.page_width, + self.page_height), build_lyrics_outline_css(self.theme_data)) + self.web.setHtml(html) - def format_slide(self, words, line_break, force_page=False): + def _paginate_slide(self, lines, line_end): """ Figure out how much text can appear on a slide, using the current theme settings. + **Note:** The smallest possible "unit" of text for a slide is one line. + If the line is too long it will be cut off when displayed. - ``words`` - The words to be fitted on the slide. - - ``line_break`` - Add line endings after each line of text used for bibles. - - ``force_page`` - Flag to tell message lines in page. + ``lines`` + The text to be fitted on the slide split into lines. + ``line_end`` + The text added after each line. Either ``u' '`` or ``u'
``. """ - log.debug(u'format_slide - Start') - line_end = u'' - if line_break: - line_end = u'
' - words = words.replace(u'\r\n', u'\n') - verses_text = words.split(u'\n') - text = [] - for verse in verses_text: - lines = verse.split(u'\n') - for line in lines: - text.append(line) + log.debug(u'_paginate_slide - Start') formatted = [] - html_text = u'' - styled_text = u'' - line_count = 0 - for line in text: - if line_count != -1: - line_count += 1 - styled_line = expand_tags(line) + line_end - styled_text += styled_line - html = self.page_shell + styled_text + u'
' - self.web.setHtml(html) - # Text too long so go to next page - if self.web_frame.contentsSize().height() > self.page_height: - if force_page and line_count > 0: - Receiver.send_message(u'theme_line_count', line_count) - line_count = -1 - if html_text.endswith(u'
'): - html_text = html_text[:len(html_text)-4] - formatted.append(html_text) - html_text = u'' - styled_text = styled_line - html_text += line + line_end - if html_text.endswith(u'
'): - html_text = html_text[:len(html_text)-4] - formatted.append(html_text) - log.debug(u'format_slide - End') - return formatted \ No newline at end of file + previous_html = u'' + previous_raw = u'' + separator = u'
' + html_lines = map(expand_tags, lines) + # Text too long so go to next page. + if not self._text_fits_on_slide(separator.join(html_lines)): + html_text, previous_raw = self._binary_chop(formatted, + previous_html, previous_raw, html_lines, lines, separator, u'') + else: + previous_raw = separator.join(lines) + if previous_raw: + formatted.append(previous_raw) + log.debug(u'_paginate_slide - End') + return formatted + + def _paginate_slide_words(self, lines, line_end): + """ + Figure out how much text can appear on a slide, using the current + theme settings. + **Note:** The smallest possible "unit" of text for a slide is one word. + If one line is too long it will be processed word by word. This is + sometimes need for **bible** verses. + + ``lines`` + The text to be fitted on the slide split into lines. + + ``line_end`` + The text added after each line. Either ``u' '`` or ``u'
``. + This is needed for **bibles**. + """ + log.debug(u'_paginate_slide_words - Start') + formatted = [] + previous_html = u'' + previous_raw = u'' + for line in lines: + line = line.strip() + html_line = expand_tags(line) + # Text too long so go to next page. + if not self._text_fits_on_slide(previous_html + html_line): + # Check if there was a verse before the current one and append + # it, when it fits on the page. + if previous_html: + if self._text_fits_on_slide(previous_html): + formatted.append(previous_raw) + previous_html = u'' + previous_raw = u'' + # Now check if the current verse will fit, if it does + # not we have to start to process the verse word by + # word. + if self._text_fits_on_slide(html_line): + previous_html = html_line + line_end + previous_raw = line + line_end + continue + # Figure out how many words of the line will fit on screen as + # the line will not fit as a whole. + raw_words = self._words_split(line) + html_words = map(expand_tags, raw_words) + previous_html, previous_raw = self._binary_chop( + formatted, previous_html, previous_raw, html_words, + raw_words, u' ', line_end) + else: + previous_html += html_line + line_end + previous_raw += line + line_end + formatted.append(previous_raw) + log.debug(u'_paginate_slide_words - End') + return formatted + + def _get_start_tags(self, raw_text): + """ + Tests the given text for not closed formatting tags and returns a tuple + consisting of three unicode strings:: + + (u'{st}{r}Text text text{/r}{/st}', u'{st}{r}', u' + ') + + The first unicode string is the text, with correct closing tags. The + second unicode string are OpenLP's opening formatting tags and the third + unicode string the html opening formatting tags. + + ``raw_text`` + The text to test. The text must **not** contain html tags, only + OpenLP formatting tags are allowed:: + + {st}{r}Text text text + """ + raw_tags = [] + html_tags = [] + for tag in FormattingTags.get_html_tags(): + if tag[u'start tag'] == u'{br}': + continue + if raw_text.count(tag[u'start tag']) != \ + raw_text.count(tag[u'end tag']): + raw_tags.append( + (raw_text.find(tag[u'start tag']), tag[u'start tag'], + tag[u'end tag'])) + html_tags.append( + (raw_text.find(tag[u'start tag']), tag[u'start html'])) + # Sort the lists, so that the tags which were opened first on the first + # slide (the text we are checking) will be opened first on the next + # slide as well. + raw_tags.sort(key=lambda tag: tag[0]) + html_tags.sort(key=lambda tag: tag[0]) + # Create a list with closing tags for the raw_text. + end_tags = [tag[2] for tag in raw_tags] + end_tags.reverse() + # Remove the indexes. + raw_tags = [tag[1] for tag in raw_tags] + html_tags = [tag[1] for tag in html_tags] + return raw_text + u''.join(end_tags), u''.join(raw_tags), \ + u''.join(html_tags) + + def _binary_chop(self, formatted, previous_html, previous_raw, html_list, + raw_list, separator, line_end): + """ + This implements the binary chop algorithm for faster rendering. This + algorithm works line based (line by line) and word based (word by word). + It is assumed that this method is **only** called, when the lines/words + to be rendered do **not** fit as a whole. + + ``formatted`` + The list to append any slides. + + ``previous_html`` + The html text which is know to fit on a slide, but is not yet added + to the list of slides. (unicode string) + + ``previous_raw`` + The raw text (with formatting tags) which is know to fit on a slide, + but is not yet added to the list of slides. (unicode string) + + ``html_list`` + The elements which do not fit on a slide and needs to be processed + using the binary chop. The text contains html. + + ``raw_list`` + The elements which do not fit on a slide and needs to be processed + using the binary chop. The elements can contain formatting tags. + + ``separator`` + The separator for the elements. For lines this is ``u'
'`` and + for words this is ``u' '``. + + ``line_end`` + The text added after each "element line". Either ``u' '`` or + ``u'
``. This is needed for bibles. + """ + smallest_index = 0 + highest_index = len(html_list) - 1 + index = int(highest_index / 2) + while True: + if not self._text_fits_on_slide( + previous_html + separator.join(html_list[:index + 1]).strip()): + # We know that it does not fit, so change/calculate the + # new index and highest_index accordingly. + highest_index = index + index = int(index - (index - smallest_index) / 2) + else: + smallest_index = index + index = int(index + (highest_index - index) / 2) + # We found the number of words which will fit. + if smallest_index == index or highest_index == index: + index = smallest_index + text = previous_raw.rstrip(u'
') + \ + separator.join(raw_list[:index + 1]) + text, raw_tags, html_tags = self._get_start_tags(text) + formatted.append(text) + previous_html = u'' + previous_raw = u'' + # Stop here as the theme line count was requested. + if self.force_page: + Receiver.send_message(u'theme_line_count', index + 1) + break + else: + continue + # Check if the remaining elements fit on the slide. + if self._text_fits_on_slide( + html_tags + separator.join(html_list[index + 1:]).strip()): + previous_html = html_tags + separator.join( + html_list[index + 1:]).strip() + line_end + previous_raw = raw_tags + separator.join( + raw_list[index + 1:]).strip() + line_end + break + else: + # The remaining elements do not fit, thus reset the indexes, + # create a new list and continue. + raw_list = raw_list[index + 1:] + raw_list[0] = raw_tags + raw_list[0] + html_list = html_list[index + 1:] + html_list[0] = html_tags + html_list[0] + smallest_index = 0 + highest_index = len(html_list) - 1 + index = int(highest_index / 2) + return previous_html, previous_raw + + def _text_fits_on_slide(self, text): + """ + Checks if the given ``text`` fits on a slide. If it does ``True`` is + returned, otherwise ``False``. + + ``text`` + The text to check. It may contain HTML tags. + """ + self.web_frame.evaluateJavaScript(u'show_text("%s")' % + text.replace(u'\\', u'\\\\').replace(u'\"', u'\\\"')) + return self.web_frame.contentsSize().height() <= self.page_height + + def _words_split(self, line): + """ + Split the slide up by word so can wrap better + """ + # this parse we are to be wordy + line = line.replace(u'\n', u' ') + return line.split(u' ') diff --git a/openlp/core/lib/rendermanager.py b/openlp/core/lib/rendermanager.py deleted file mode 100644 index 5896ca4e6..000000000 --- a/openlp/core/lib/rendermanager.py +++ /dev/null @@ -1,264 +0,0 @@ -# -*- 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 # -############################################################################### - -import logging - -from PyQt4 import QtCore - -from openlp.core.lib import Renderer, ThemeLevel, ServiceItem, ImageManager -from openlp.core.ui import MainDisplay - -log = logging.getLogger(__name__) - -class RenderManager(object): - """ - Class to pull all Renderer interactions into one place. The plugins will - call helper methods to do the rendering but this class will provide - display defense code. - - ``theme_manager`` - The ThemeManager instance, used to get the current theme details. - - ``screens`` - Contains information about the Screens. - - ``screen_number`` - Defaults to *0*. The index of the output/display screen. - """ - log.info(u'RenderManager Loaded') - - def __init__(self, theme_manager, screens): - """ - Initialise the render manager. - """ - log.debug(u'Initilisation started') - self.screens = screens - self.image_manager = ImageManager() - self.display = MainDisplay(self, screens, False) - self.display.imageManager = self.image_manager - self.display.setup() - self.theme_manager = theme_manager - self.renderer = Renderer() - self.calculate_default(self.screens.current[u'size']) - self.theme = u'' - self.service_theme = u'' - self.theme_level = u'' - self.override_background = None - self.theme_data = None - self.alertTab = None - self.force_page = False - - def update_display(self): - """ - Updates the render manager's information about the current screen. - """ - log.debug(u'Update Display') - self.calculate_default(self.screens.current[u'size']) - self.display = MainDisplay(self, self.screens, False) - self.display.imageManager = self.image_manager - self.display.setup() - self.renderer.bg_frame = None - self.theme_data = None - self.image_manager.update_display(self.width, self.height) - - def set_global_theme(self, global_theme, theme_level=ThemeLevel.Global): - """ - Set the global-level theme and the theme level. - - ``global_theme`` - The global-level theme to be set. - - ``theme_level`` - Defaults to *``ThemeLevel.Global``*. The theme level, can be - ``ThemeLevel.Global``, ``ThemeLevel.Service`` or - ``ThemeLevel.Song``. - """ - self.global_theme = global_theme - self.theme_level = theme_level - self.global_theme_data = \ - self.theme_manager.getThemeData(self.global_theme) - self.theme_data = None - - def set_service_theme(self, service_theme): - """ - Set the service-level theme. - - ``service_theme`` - The service-level theme to be set. - """ - self.service_theme = service_theme - self.theme_data = None - - def set_override_theme(self, theme, overrideLevels=False): - """ - Set the appropriate theme depending on the theme level. - Called by the service item when building a display frame - - ``theme`` - The name of the song-level theme. None means the service - item wants to use the given value. - - ``overrideLevels`` - Used to force the theme data passed in to be used. - - """ - log.debug(u'set override theme to %s', theme) - theme_level = self.theme_level - if overrideLevels: - theme_level = ThemeLevel.Song - if theme_level == ThemeLevel.Global: - self.theme = self.global_theme - elif theme_level == ThemeLevel.Service: - if self.service_theme == u'': - self.theme = self.global_theme - else: - self.theme = self.service_theme - else: - if theme: - self.theme = theme - elif theme_level == ThemeLevel.Song or \ - theme_level == ThemeLevel.Service: - if self.service_theme == u'': - self.theme = self.global_theme - else: - self.theme = self.service_theme - else: - self.theme = self.global_theme - if self.theme != self.renderer.theme_name or self.theme_data is None \ - or overrideLevels: - log.debug(u'theme is now %s', self.theme) - # Force the theme to be the one passed in. - if overrideLevels: - self.theme_data = theme - else: - self.theme_data = self.theme_manager.getThemeData(self.theme) - self.calculate_default(self.screens.current[u'size']) - self.renderer.set_theme(self.theme_data) - self.build_text_rectangle(self.theme_data) - self.image_manager.add_image(self.theme_data.theme_name, - self.theme_data.background_filename) - return self.renderer._rect, self.renderer._rect_footer - - def build_text_rectangle(self, theme): - """ - Builds a text block using the settings in ``theme`` - and the size of the display screen.height. - - ``theme`` - The theme to build a text block for. - """ - log.debug(u'build_text_rectangle') - main_rect = None - footer_rect = None - if not theme.font_main_override: - main_rect = QtCore.QRect(10, 0, - self.width - 20, self.footer_start) - else: - main_rect = QtCore.QRect(theme.font_main_x, theme.font_main_y, - theme.font_main_width - 1, theme.font_main_height - 1) - if not theme.font_footer_override: - footer_rect = QtCore.QRect(10, self.footer_start, - self.width - 20, self.height - self.footer_start) - else: - footer_rect = QtCore.QRect(theme.font_footer_x, - theme.font_footer_y, theme.font_footer_width - 1, - theme.font_footer_height - 1) - self.renderer.set_text_rectangle(main_rect, footer_rect) - - def generate_preview(self, theme_data, force_page=False): - """ - Generate a preview of a theme. - - ``theme_data`` - The theme to generated a preview for. - - ``force_page`` - Flag to tell message lines per page need to be generated. - """ - log.debug(u'generate preview') - # save value for use in format_slide - self.force_page = force_page - # set the default image size for previews - self.calculate_default(self.screens.preview[u'size']) - verse = u'The Lord said to {r}Noah{/r}: \n' \ - 'There\'s gonna be a {su}floody{/su}, {sb}floody{/sb}\n' \ - 'The Lord said to {g}Noah{/g}:\n' \ - 'There\'s gonna be a {st}floody{/st}, {it}floody{/it}\n' \ - 'Get those children out of the muddy, muddy \n' \ - '{r}C{/r}{b}h{/b}{bl}i{/bl}{y}l{/y}{g}d{/g}{pk}' \ - 'r{/pk}{o}e{/o}{pp}n{/pp} of the Lord\n' - # make big page for theme edit dialog to get line count - if self.force_page: - verse = verse + verse + verse - else: - self.image_manager.del_image(theme_data.theme_name) - footer = [] - footer.append(u'Arky Arky (Unknown)' ) - footer.append(u'Public Domain') - footer.append(u'CCLI 123456') - # build a service item to generate preview - serviceItem = ServiceItem() - serviceItem.theme = theme_data - serviceItem.add_from_text(u'', verse, footer) - serviceItem.render_manager = self - serviceItem.raw_footer = footer - serviceItem.render(True) - if not self.force_page: - self.display.buildHtml(serviceItem) - raw_html = serviceItem.get_rendered_frame(0) - preview = self.display.text(raw_html) - # Reset the real screen size for subsequent render requests - self.calculate_default(self.screens.current[u'size']) - return preview - - def format_slide(self, words, line_break): - """ - Calculate how much text can fit on a slide. - - ``words`` - The words to go on the slides. - - ``line_break`` - Add line endings after each line of text used for bibles. - """ - log.debug(u'format slide') - return self.renderer.format_slide(words, line_break, self.force_page) - - def calculate_default(self, screen): - """ - Calculate the default dimentions of the screen. - - ``screen`` - The QSize of the screen. - """ - log.debug(u'calculate default %s', screen) - self.width = screen.width() - self.height = screen.height() - self.screen_ratio = float(self.height) / float(self.width) - log.debug(u'calculate default %d, %d, %f', - self.width, self.height, self.screen_ratio ) - # 90% is start of footer - self.footer_start = int(self.height * 0.90) \ No newline at end of file diff --git a/openlp/core/lib/searchedit.py b/openlp/core/lib/searchedit.py index 738661e14..c172ba21d 100644 --- a/openlp/core/lib/searchedit.py +++ b/openlp/core/lib/searchedit.py @@ -5,10 +5,11 @@ # 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 # +# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # +# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias # +# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, 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 # @@ -29,6 +30,7 @@ import logging from PyQt4 import QtCore, QtGui from openlp.core.lib import build_icon +from openlp.core.lib.ui import icon_action log = logging.getLogger(__name__) @@ -61,6 +63,7 @@ class SearchEdit(QtGui.QLineEdit): self._onSearchEditTextChanged ) self._updateStyleSheet() + self.setAcceptDrops(False) def _updateStyleSheet(self): """ @@ -69,19 +72,19 @@ class SearchEdit(QtGui.QLineEdit): """ frameWidth = self.style().pixelMetric( QtGui.QStyle.PM_DefaultFrameWidth) - rightPadding = self.clearButton.sizeHint().width() + frameWidth + rightPadding = self.clearButton.width() + frameWidth if hasattr(self, u'menuButton'): leftPadding = self.menuButton.width() self.setStyleSheet( - u'QLineEdit { padding-left: %spx; padding-right: %spx; } ' % \ + u'QLineEdit { padding-left: %spx; padding-right: %spx; } ' % (leftPadding, rightPadding)) else: - self.setStyleSheet(u'QLineEdit { padding-right: %spx; } ' % \ + self.setStyleSheet(u'QLineEdit { padding-right: %spx; } ' % rightPadding) - msz = self.minimumSizeHint(); + msz = self.minimumSizeHint() self.setMinimumSize( max(msz.width(), - self.clearButton.sizeHint().width() + (frameWidth * 2) + 2), + self.clearButton.width() + (frameWidth * 2) + 2), max(msz.height(), self.clearButton.height() + (frameWidth * 2) + 2) ) @@ -93,15 +96,15 @@ class SearchEdit(QtGui.QLineEdit): ``event`` The event that happened. """ - sz = self.clearButton.sizeHint() + size = self.clearButton.size() frameWidth = self.style().pixelMetric( QtGui.QStyle.PM_DefaultFrameWidth) - self.clearButton.move(self.rect().right() - frameWidth - sz.width(), - (self.rect().bottom() + 1 - sz.height()) / 2) + self.clearButton.move(self.rect().right() - frameWidth - size.width(), + (self.rect().bottom() + 1 - size.height()) / 2) if hasattr(self, u'menuButton'): - sz = self.menuButton.sizeHint() + size = self.menuButton.size() self.menuButton.move(self.rect().left() + frameWidth + 2, - (self.rect().bottom() + 1 - sz.height()) / 2) + (self.rect().bottom() + 1 - size.height()) / 2) def currentSearchType(self): """ @@ -109,6 +112,21 @@ class SearchEdit(QtGui.QLineEdit): """ return self._currentSearchType + def setCurrentSearchType(self, identifier): + """ + Set a new current search type. + + ``identifier`` + The search type identifier (int). + """ + menu = self.menuButton.menu() + for action in menu.actions(): + if identifier == action.data().toInt()[0]: + self.menuButton.setDefaultAction(action) + self._currentSearchType = identifier + self.emit(QtCore.SIGNAL(u'searchTypeChanged(int)'), identifier) + return True + def setSearchTypes(self, items): """ A list of tuples to be used in the search type menu. The first item in @@ -132,7 +150,8 @@ class SearchEdit(QtGui.QLineEdit): menu = QtGui.QMenu(self) first = None for identifier, icon, title in items: - action = QtGui.QAction(build_icon(icon), title, menu) + action = icon_action(menu, u'', icon) + action.setText(title) action.setData(QtCore.QVariant(identifier)) menu.addAction(action) QtCore.QObject.connect(action, QtCore.SIGNAL(u'triggered(bool)'), diff --git a/openlp/core/lib/serviceitem.py b/openlp/core/lib/serviceitem.py index f18605711..62418f49e 100644 --- a/openlp/core/lib/serviceitem.py +++ b/openlp/core/lib/serviceitem.py @@ -5,10 +5,11 @@ # 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 # +# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # +# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias # +# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, 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 # @@ -28,11 +29,13 @@ The :mod:`serviceitem` provides the service item functionality including the type and capability of an item. """ +import cgi +import datetime import logging import os import uuid -from openlp.core.lib import build_icon, clean_tags, expand_tags +from openlp.core.lib import build_icon, clean_tags, expand_tags, translate log = logging.getLogger(__name__) @@ -49,16 +52,22 @@ class ItemCapabilities(object): """ Provides an enumeration of a serviceitem's capabilities """ - AllowsPreview = 1 - AllowsEdit = 2 - AllowsMaintain = 3 + CanPreview = 1 + CanEdit = 2 + CanMaintain = 3 RequiresMedia = 4 - AllowsLoop = 5 - AllowsAdditions = 6 + CanLoop = 5 + CanAppend = 6 NoLineBreaks = 7 OnLoadUpdate = 8 AddIfNewItem = 9 ProvidesOwnDisplay = 10 + HasDetailedTitleDisplay = 11 + HasVariableStartTime = 12 + CanSoftBreak = 13 + CanWordSplit = 14 + HasBackgroundAudio = 15 + class ServiceItem(object): """ @@ -76,15 +85,15 @@ class ServiceItem(object): The plugin that this service item belongs to. """ if plugin: - self.render_manager = plugin.renderManager + self.renderer = plugin.renderer self.name = plugin.name self.title = u'' self.shortname = u'' self.audit = u'' self.items = [] self.iconic_representation = None - self.raw_footer = None - self.foot_text = None + self.raw_footer = [] + self.foot_text = u'' self.theme = None self.service_item_type = None self._raw_frames = [] @@ -103,13 +112,20 @@ class ServiceItem(object): self.data_string = u'' self.edit_id = None self.xml_version = None + self.start_time = 0 + self.end_time = 0 + self.media_length = 0 + self.from_service = False + self.image_border = u'#000000' + self.background_audio = [] + self.theme_overwritten = False + self.temporary_edit = False self._new_item() def _new_item(self): """ - Method to set the internal id of the item - This is used to compare service items to see if they are - the same + Method to set the internal id of the item. This is used to compare + service items to see if they are the same. """ self._uuid = unicode(uuid.uuid1()) @@ -142,51 +158,47 @@ class ServiceItem(object): self.icon = icon self.iconic_representation = build_icon(icon) - def render(self, useOverride=False): + def render(self, use_override=False): """ The render method is what generates the frames for the screen and - obtains the display information from the renderemanager. - At this point all the slides are build for the given - display size. + obtains the display information from the renderer. At this point all + slides are built for the given display size. """ log.debug(u'Render called') self._display_frames = [] self.bg_image_bytes = None - line_break = True - if self.is_capable(ItemCapabilities.NoLineBreaks): - line_break = False - theme = None - if self.theme: - theme = self.theme + theme = self.theme if self.theme else None self.main, self.footer = \ - self.render_manager.set_override_theme(theme, useOverride) - self.themedata = self.render_manager.renderer._theme + self.renderer.set_override_theme(theme, use_override) + self.themedata = self.renderer.theme_data if self.service_item_type == ServiceItemType.Text: log.debug(u'Formatting slides') for slide in self._raw_frames: - formatted = self.render_manager \ - .format_slide(slide[u'raw_slide'], line_break) - for page in formatted: - self._display_frames.append( - {u'title': clean_tags(page), + pages = self.renderer.format_slide(slide[u'raw_slide'], self) + for page in pages: + page = page.replace(u'
', u'{br}') + html = expand_tags(cgi.escape(page.rstrip())) + self._display_frames.append({ + u'title': clean_tags(page), u'text': clean_tags(page.rstrip()), - u'html': expand_tags(page.rstrip()), - u'verseTag': slide[u'verseTag'] }) + u'html': html.replace(u'&nbsp;', u' '), + u'verseTag': slide[u'verseTag'] + }) elif self.service_item_type == ServiceItemType.Image or \ self.service_item_type == ServiceItemType.Command: pass else: - log.error(u'Invalid value renderer :%s' % self.service_item_type) + log.error(u'Invalid value renderer: %s' % self.service_item_type) self.title = clean_tags(self.title) - self.foot_text = None - if self.raw_footer: - for foot in self.raw_footer: - if not self.foot_text: - self.foot_text = foot - else: - self.foot_text = u'%s
%s' % (self.foot_text, foot) + # The footer should never be None, but to be compatible with a few + # nightly builds between 1.9.4 and 1.9.5, we have to correct this to + # avoid tracebacks. + if self.raw_footer is None: + self.raw_footer = [] + self.foot_text = \ + u'
'.join([footer for footer in self.raw_footer if footer]) - def add_from_image(self, path, title): + def add_from_image(self, path, title, background=None): """ Add an image slide to the service item. @@ -196,10 +208,12 @@ class ServiceItem(object): ``title`` A title for the slide in the service item. """ + if background: + self.image_border = background self.service_item_type = ServiceItemType.Image - self._raw_frames.append( - {u'title': title, u'path': path}) - self.render_manager.image_manager.add_image(title, path) + self._raw_frames.append({u'title': title, u'path': path}) + self.renderer.imageManager.add_image(title, path, u'image', + self.image_border) self._new_item() def add_from_text(self, title, raw_slide, verse_tag=None): @@ -212,6 +226,8 @@ class ServiceItem(object): ``raw_slide`` The raw text of the slide. """ + if verse_tag: + verse_tag = verse_tag.upper() self.service_item_type = ServiceItemType.Text title = title.split(u'\n')[0] self._raw_frames.append( @@ -242,7 +258,7 @@ class ServiceItem(object): file to represent this item. """ service_header = { - u'name': self.name.lower(), + u'name': self.name, u'plugin': self.name, u'theme': self.theme, u'title': self.title, @@ -255,15 +271,18 @@ class ServiceItem(object): u'capabilities': self.capabilities, u'search': self.search_string, u'data': self.data_string, - u'xml_version': self.xml_version + u'xml_version': self.xml_version, + u'start_time': self.start_time, + u'end_time': self.end_time, + u'media_length': self.media_length, + u'background_audio': self.background_audio, + u'theme_overwritten': self.theme_overwritten } service_data = [] if self.service_item_type == ServiceItemType.Text: - for slide in self._raw_frames: - service_data.append(slide) + service_data = [slide for slide in self._raw_frames] elif self.service_item_type == ServiceItemType.Image: - for slide in self._raw_frames: - service_data.append(slide[u'title']) + service_data = [slide[u'title'] for slide in self._raw_frames] elif self.service_item_type == ServiceItemType.Command: for slide in self._raw_frames: service_data.append( @@ -299,6 +318,15 @@ class ServiceItem(object): self.data_string = header[u'data'] if u'xml_version' in header: self.xml_version = header[u'xml_version'] + if u'start_time' in header: + self.start_time = header[u'start_time'] + if u'end_time' in header: + self.end_time = header[u'end_time'] + if u'media_length' in header: + self.media_length = header[u'media_length'] + if u'background_audio' in header: + self.background_audio = header[u'background_audio'] + self.theme_overwritten = header.get(u'theme_overwritten', False) if self.service_item_type == ServiceItemType.Text: for slide in serviceitem[u'serviceitem'][u'data']: self._raw_frames.append(slide) @@ -310,16 +338,42 @@ class ServiceItem(object): for text_image in serviceitem[u'serviceitem'][u'data']: filename = os.path.join(path, text_image[u'title']) self.add_from_command( - path, text_image[u'title'], text_image[u'image'] ) + path, text_image[u'title'], text_image[u'image']) self._new_item() + def get_display_title(self): + """ + Returns the title of the service item. + """ + if self.is_text(): + return self.title + else: + if ItemCapabilities.HasDetailedTitleDisplay in self.capabilities: + return self._raw_frames[0][u'title'] + elif len(self._raw_frames) > 1: + return self.title + else: + return self._raw_frames[0][u'title'] + def merge(self, other): """ Updates the _uuid with the value from the original one The _uuid is unique for a given service item but this allows one to replace an original version. + + ``other`` + The service item to be merged with """ self._uuid = other._uuid + self.notes = other.notes + self.temporary_edit = other.temporary_edit + # Copy theme over if present. + if other.theme is not None: + self.theme = other.theme + self._new_item() + self.render() + if self.is_capable(ItemCapabilities.HasBackgroundAudio): + log.debug(self.background_audio) def __eq__(self, other): """ @@ -391,10 +445,52 @@ class ServiceItem(object): """ Returns the title of the raw frame """ - return self._raw_frames[row][u'title'] + try: + return self._raw_frames[row][u'title'] + except IndexError: + return u'' def get_frame_path(self, row=0): """ Returns the path of the raw frame """ - return self._raw_frames[row][u'path'] + try: + return self._raw_frames[row][u'path'] + except IndexError: + return u'' + + def get_media_time(self): + """ + Returns the start and finish time for a media item + """ + start = None + end = None + if self.start_time != 0: + start = unicode(translate('OpenLP.ServiceItem', + 'Start: %s')) % \ + unicode(datetime.timedelta(seconds=self.start_time)) + if self.media_length != 0: + end = unicode(translate('OpenLP.ServiceItem', + 'Length: %s')) % \ + unicode(datetime.timedelta(seconds=self.media_length)) + if not start and not end: + return u'' + elif start and not end: + return start + elif not start and end: + return end + else: + return u'%s
%s' % (start, end) + + def update_theme(self, theme): + """ + updates the theme in the service item + + ``theme`` + The new theme to be replaced in the service item + """ + self.theme_overwritten = (theme == None) + self.theme = theme + self._new_item() + self.render() + diff --git a/openlp/core/lib/settingsmanager.py b/openlp/core/lib/settingsmanager.py index 89d56cea2..9bcbf2f07 100644 --- a/openlp/core/lib/settingsmanager.py +++ b/openlp/core/lib/settingsmanager.py @@ -5,10 +5,11 @@ # 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 # +# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # +# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias # +# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, 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 # @@ -37,26 +38,9 @@ from openlp.core.utils import AppLocation class SettingsManager(object): """ - Class to control the initial settings for the UI and provide helper - functions for the loading and saving of application settings. + Class to provide helper functions for the loading and saving of application + settings. """ - def __init__(self, screen): - self.screen = screen.current - self.width = self.screen[u'size'].width() - self.height = self.screen[u'size'].height() - self.mainwindow_height = self.height * 0.8 - mainwindow_docbars = self.width / 5 - self.mainwindow_left = 0 - self.mainwindow_right = 0 - if mainwindow_docbars > 300: - self.mainwindow_left = 300 - self.mainwindow_right = 300 - else: - self.mainwindow_left = mainwindow_docbars - self.mainwindow_right = mainwindow_docbars - self.slidecontroller = (self.width - ( - self.mainwindow_left + self.mainwindow_right) - 100) / 2 - self.slidecontroller_image = self.slidecontroller - 50 @staticmethod def get_last_dir(section, num=None): @@ -64,7 +48,7 @@ class SettingsManager(object): Read the last directory used for plugin. ``section`` - The section of code calling the method. This is used in the + The section of code calling the method. This is used in the settings key. ``num`` @@ -84,7 +68,7 @@ class SettingsManager(object): Save the last directory used for plugin. ``section`` - The section of code calling the method. This is used in the + The section of code calling the method. This is used in the settings key. ``directory`` @@ -160,11 +144,11 @@ class SettingsManager(object): Get a list of files from the data files path. ``section`` - Defaults to *None*. The section of code getting the files - used + Defaults to *None*. The section of code getting the files - used to load from a section's data subdirectory. ``extension`` - Defaults to *None*. The extension to search for. + Defaults to *None*. The extension to search for. """ path = AppLocation.get_data_path() if section: @@ -178,4 +162,4 @@ class SettingsManager(object): if extension == os.path.splitext(filename)[1]] else: # no filtering required - return files \ No newline at end of file + return files diff --git a/openlp/core/lib/settingstab.py b/openlp/core/lib/settingstab.py index 6586a50f2..f36f9f561 100644 --- a/openlp/core/lib/settingstab.py +++ b/openlp/core/lib/settingstab.py @@ -5,10 +5,11 @@ # 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 # +# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # +# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias # +# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, 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 # @@ -31,7 +32,7 @@ class SettingsTab(QtGui.QWidget): SettingsTab is a helper widget for plugins to define Tabs for the settings dialog. """ - def __init__(self, title, visible_title=None): + def __init__(self, parent, title, visible_title=None, icon_path=None): """ Constructor to create the Settings tab item. @@ -41,27 +42,49 @@ class SettingsTab(QtGui.QWidget): ``visible_title`` The title of the tab, which is usually displayed on the tab. """ - QtGui.QWidget.__init__(self) + QtGui.QWidget.__init__(self, parent) self.tabTitle = title self.tabTitleVisible = visible_title self.settingsSection = self.tabTitle.lower() + if icon_path: + self.icon_path = icon_path self.setupUi() self.retranslateUi() self.initialise() - self.preLoad() self.load() def setupUi(self): """ Setup the tab's interface. """ - pass + self.tabLayout = QtGui.QHBoxLayout(self) + self.tabLayout.setObjectName(u'tabLayout') + self.leftColumn = QtGui.QWidget(self) + self.leftColumn.setObjectName(u'leftColumn') + self.leftLayout = QtGui.QVBoxLayout(self.leftColumn) + self.leftLayout.setMargin(0) + self.leftLayout.setObjectName(u'leftLayout') + self.tabLayout.addWidget(self.leftColumn) + self.rightColumn = QtGui.QWidget(self) + self.rightColumn.setObjectName(u'rightColumn') + self.rightLayout = QtGui.QVBoxLayout(self.rightColumn) + self.rightLayout.setMargin(0) + self.rightLayout.setObjectName(u'rightLayout') + self.tabLayout.addWidget(self.rightColumn) - def preLoad(self): + def resizeEvent(self, event=None): """ - Setup the tab's interface. + Resize the sides in two equal halves if the layout allows this. """ - pass + if event: + QtGui.QWidget.resizeEvent(self, event) + width = self.width() - self.tabLayout.spacing() - \ + self.tabLayout.contentsMargins().left() - \ + self.tabLayout.contentsMargins().right() + left_width = min(width - self.rightColumn.minimumSizeHint().width(), + width / 2) + left_width = max(left_width, self.leftColumn.minimumSizeHint().width()) + self.leftColumn.setFixedWidth(left_width) def retranslateUi(self): """ @@ -87,6 +110,12 @@ class SettingsTab(QtGui.QWidget): """ pass + def cancel(self): + """ + Reset any settings if cancel pressed + """ + self.load() + def postSetUp(self, postUpdate=False): """ Changes which need to be made after setup of application @@ -96,3 +125,9 @@ class SettingsTab(QtGui.QWidget): """ pass + + def tabVisible(self): + """ + Tab has just been made visible to the user + """ + pass diff --git a/openlp/core/lib/spelltextedit.py b/openlp/core/lib/spelltextedit.py index ebd4046c0..25e4e24ae 100644 --- a/openlp/core/lib/spelltextedit.py +++ b/openlp/core/lib/spelltextedit.py @@ -5,10 +5,11 @@ # 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 # +# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # +# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias # +# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, 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 # @@ -23,37 +24,48 @@ # with this program; if not, write to the Free Software Foundation, Inc., 59 # # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### - +import logging import re try: import enchant from enchant import DictNotFoundError - enchant_available = True + from enchant.errors import Error + ENCHANT_AVAILABLE = True except ImportError: - enchant_available = False + ENCHANT_AVAILABLE = False # based on code from # http://john.nachtimwald.com/2009/08/22/qplaintextedit-with-in-line-spell-check from PyQt4 import QtCore, QtGui -from openlp.core.lib import html_expands, translate + +from openlp.core.lib import translate, FormattingTags +from openlp.core.lib.ui import checkable_action + +log = logging.getLogger(__name__) class SpellTextEdit(QtGui.QPlainTextEdit): """ Spell checking widget based on QPlanTextEdit. """ - def __init__(self, *args): - QtGui.QPlainTextEdit.__init__(self, *args) + def __init__(self, parent=None, formattingTagsAllowed=True): + global ENCHANT_AVAILABLE + QtGui.QPlainTextEdit.__init__(self, parent) + self.formattingTagsAllowed = formattingTagsAllowed # Default dictionary based on the current locale. - if enchant_available: + if ENCHANT_AVAILABLE: try: - self.dict = enchant.Dict() - except DictNotFoundError: - self.dict = enchant.Dict(u'en_US') - self.highlighter = Highlighter(self.document()) - self.highlighter.setDict(self.dict) + self.dictionary = enchant.Dict() + self.highlighter = Highlighter(self.document()) + self.highlighter.spellingDictionary = self.dictionary + except (Error, DictNotFoundError): + ENCHANT_AVAILABLE = False + log.debug(u'Could not load default dictionary') def mousePressEvent(self, event): + """ + Handle mouse clicks within the text edit region. + """ if event.button() == QtCore.Qt.RightButton: # Rewrite the mouse event to a left button event so the cursor is # moved to the location of the pointer. @@ -63,6 +75,9 @@ class SpellTextEdit(QtGui.QPlainTextEdit): QtGui.QPlainTextEdit.mousePressEvent(self, event) def contextMenuEvent(self, event): + """ + Provide the context menu for the text edit region. + """ popupMenu = self.createStandardContextMenu() # Select the word under the cursor. cursor = self.textCursor() @@ -70,32 +85,57 @@ class SpellTextEdit(QtGui.QPlainTextEdit): if not cursor.hasSelection(): cursor.select(QtGui.QTextCursor.WordUnderCursor) self.setTextCursor(cursor) + # Add menu with available languages. + if ENCHANT_AVAILABLE: + lang_menu = QtGui.QMenu( + translate('OpenLP.SpellTextEdit', 'Language:')) + for lang in enchant.list_languages(): + action = checkable_action( + lang_menu, lang, lang == self.dictionary.tag) + action.setText(lang) + lang_menu.addAction(action) + popupMenu.insertSeparator(popupMenu.actions()[0]) + popupMenu.insertMenu(popupMenu.actions()[0], lang_menu) + QtCore.QObject.connect(lang_menu, + QtCore.SIGNAL(u'triggered(QAction*)'), self.setLanguage) # Check if the selected word is misspelled and offer spelling # suggestions if it is. - if enchant_available and self.textCursor().hasSelection(): + if ENCHANT_AVAILABLE and self.textCursor().hasSelection(): text = unicode(self.textCursor().selectedText()) - if not self.dict.check(text): + if not self.dictionary.check(text): spell_menu = QtGui.QMenu(translate('OpenLP.SpellTextEdit', 'Spelling Suggestions')) - for word in self.dict.suggest(text): + for word in self.dictionary.suggest(text): action = SpellAction(word, spell_menu) action.correct.connect(self.correctWord) spell_menu.addAction(action) # Only add the spelling suggests to the menu if there are # suggestions. - if len(spell_menu.actions()) != 0: - popupMenu.insertSeparator(popupMenu.actions()[0]) + if spell_menu.actions(): popupMenu.insertMenu(popupMenu.actions()[0], spell_menu) tagMenu = QtGui.QMenu(translate('OpenLP.SpellTextEdit', 'Formatting Tags')) - for html in html_expands: - action = SpellAction( html[u'desc'], tagMenu) - action.correct.connect(self.htmlTag) - tagMenu.addAction(action) - popupMenu.insertSeparator(popupMenu.actions()[0]) - popupMenu.insertMenu(popupMenu.actions()[0], tagMenu) + if self.formattingTagsAllowed: + for html in FormattingTags.get_html_tags(): + action = SpellAction(html[u'desc'], tagMenu) + action.correct.connect(self.htmlTag) + tagMenu.addAction(action) + popupMenu.insertSeparator(popupMenu.actions()[0]) + popupMenu.insertMenu(popupMenu.actions()[0], tagMenu) popupMenu.exec_(event.globalPos()) + def setLanguage(self, action): + """ + Changes the language for this spelltextedit. + + ``action`` + The action. + """ + self.dictionary = enchant.Dict(action.text()) + self.highlighter.spellingDictionary = self.dictionary + self.highlighter.highlightBlock(self.toPlainText()) + self.highlighter.rehighlight() + def correctWord(self, word): """ Replaces the selected text with word. @@ -110,7 +150,7 @@ class SpellTextEdit(QtGui.QPlainTextEdit): """ Replaces the selected text with word. """ - for html in html_expands: + for html in FormattingTags.get_html_tags(): if tag == html[u'desc']: cursor = self.textCursor() if self.textCursor().hasSelection(): @@ -126,28 +166,32 @@ class SpellTextEdit(QtGui.QPlainTextEdit): cursor.insertText(html[u'start tag']) cursor.insertText(html[u'end tag']) -class Highlighter(QtGui.QSyntaxHighlighter): +class Highlighter(QtGui.QSyntaxHighlighter): + """ + Provides a text highlighter for pointing out spelling errors in text. + """ WORDS = u'(?iu)[\w\']+' def __init__(self, *args): QtGui.QSyntaxHighlighter.__init__(self, *args) - self.dict = None - - def setDict(self, dict): - self.dict = dict + self.spellingDictionary = None def highlightBlock(self, text): - if not self.dict: + """ + Highlight misspelt words in a block of text + """ + if not self.spellingDictionary: return text = unicode(text) - format = QtGui.QTextCharFormat() - format.setUnderlineColor(QtCore.Qt.red) - format.setUnderlineStyle(QtGui.QTextCharFormat.SpellCheckUnderline) + charFormat = QtGui.QTextCharFormat() + charFormat.setUnderlineColor(QtCore.Qt.red) + charFormat.setUnderlineStyle(QtGui.QTextCharFormat.SpellCheckUnderline) for word_object in re.finditer(self.WORDS, text): - if not self.dict.check(word_object.group()): + if not self.spellingDictionary.check(word_object.group()): self.setFormat(word_object.start(), - word_object.end() - word_object.start(), format) + word_object.end() - word_object.start(), charFormat) + class SpellAction(QtGui.QAction): """ @@ -158,4 +202,4 @@ class SpellAction(QtGui.QAction): def __init__(self, *args): QtGui.QAction.__init__(self, *args) self.triggered.connect(lambda x: self.correct.emit( - unicode(self.text()))) \ No newline at end of file + unicode(self.text()))) diff --git a/openlp/core/lib/theme.py b/openlp/core/lib/theme.py index 1e4a9854e..34a3b9d98 100644 --- a/openlp/core/lib/theme.py +++ b/openlp/core/lib/theme.py @@ -5,10 +5,11 @@ # 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 # +# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # +# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias # +# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, 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 # @@ -43,6 +44,7 @@ BLANK_THEME_XML = \ + #000000 #000000 @@ -90,22 +92,32 @@ class ThemeLevel(object): Service = 2 Song = 3 + class BackgroundType(object): + """ + Type enumeration for backgrounds. + """ Solid = 0 Gradient = 1 Image = 2 @staticmethod - def to_string(type): - if type == BackgroundType.Solid: + def to_string(background_type): + """ + Return a string representation of a background type. + """ + if background_type == BackgroundType.Solid: return u'solid' - elif type == BackgroundType.Gradient: + elif background_type == BackgroundType.Gradient: return u'gradient' - elif type == BackgroundType.Image: + elif background_type == BackgroundType.Image: return u'image' @staticmethod def from_string(type_string): + """ + Return a background type for the given string. + """ if type_string == u'solid': return BackgroundType.Solid elif type_string == u'gradient': @@ -113,7 +125,11 @@ class BackgroundType(object): elif type_string == u'image': return BackgroundType.Image + class BackgroundGradientType(object): + """ + Type enumeration for background gradients. + """ Horizontal = 0 Vertical = 1 Circular = 2 @@ -121,20 +137,26 @@ class BackgroundGradientType(object): LeftBottom = 4 @staticmethod - def to_string(type): - if type == BackgroundGradientType.Horizontal: + def to_string(gradient_type): + """ + Return a string representation of a background gradient type. + """ + if gradient_type == BackgroundGradientType.Horizontal: return u'horizontal' - elif type == BackgroundGradientType.Vertical: + elif gradient_type == BackgroundGradientType.Vertical: return u'vertical' - elif type == BackgroundGradientType.Circular: + elif gradient_type == BackgroundGradientType.Circular: return u'circular' - elif type == BackgroundGradientType.LeftTop: + elif gradient_type == BackgroundGradientType.LeftTop: return u'leftTop' - elif type == BackgroundGradientType.LeftBottom: + elif gradient_type == BackgroundGradientType.LeftBottom: return u'leftBottom' @staticmethod def from_string(type_string): + """ + Return a background gradient type for the given string. + """ if type_string == u'horizontal': return BackgroundGradientType.Horizontal elif type_string == u'vertical': @@ -146,27 +168,44 @@ class BackgroundGradientType(object): elif type_string == u'leftBottom': return BackgroundGradientType.LeftBottom + class HorizontalType(object): + """ + Type enumeration for horizontal alignment. + """ Left = 0 - Center = 1 - Right = 2 + Right = 1 + Center = 2 + Justify = 3 + + Names = [u'left', u'right', u'center', u'justify'] + class VerticalType(object): + """ + Type enumeration for vertical alignment. + """ Top = 0 Middle = 1 Bottom = 2 -boolean_list = [u'italics', u'override', u'outline', u'shadow', + Names = [u'top', u'middle', u'bottom'] + + +BOOLEAN_LIST = [u'bold', u'italics', u'override', u'outline', u'shadow', u'slide_transition'] -integer_list = [u'size', u'line_adjustment', u'x', u'height', u'y', +INTEGER_LIST = [u'size', u'line_adjustment', u'x', u'height', u'y', u'width', u'shadow_size', u'outline_size', u'horizontal_align', u'vertical_align', u'wrap_style'] + class ThemeXML(object): """ A class to encapsulate the Theme XML. """ + FIRST_CAMEL_REGEX = re.compile(u'(.)([A-Z][a-z]+)') + SECOND_CAMEL_REGEX = re.compile(u'([a-z0-9])([A-Z])') def __init__(self): """ Initialise the theme object. @@ -245,7 +284,7 @@ class ThemeXML(object): # Create direction element self.child_element(background, u'direction', unicode(direction)) - def add_background_image(self, filename): + def add_background_image(self, filename, borderColor): """ Add a image background. @@ -257,11 +296,13 @@ class ThemeXML(object): self.theme.appendChild(background) # Create Filename element self.child_element(background, u'filename', filename) + # Create endColor element + self.child_element(background, u'borderColor', unicode(borderColor)) def add_font(self, name, color, size, override, fonttype=u'main', bold=u'False', italics=u'False', line_adjustment=0, xpos=0, ypos=0, width=0, height=0 , outline=u'False', - outline_color=u'#ffffff', outline_pixel=2, shadow=u'False', + outline_color=u'#ffffff', outline_pixel=2, shadow=u'False', shadow_color=u'#ffffff', shadow_pixel=5): """ Add a Font. @@ -514,9 +555,9 @@ class ThemeXML(object): return field = self._de_hump(element) tag = master + u'_' + field - if field in boolean_list: + if field in BOOLEAN_LIST: setattr(self, tag, str_to_bool(value)) - elif field in integer_list: + elif field in INTEGER_LIST: setattr(self, tag, int(value)) else: # make string value unicode @@ -541,8 +582,8 @@ class ThemeXML(object): """ Change Camel Case string to python string """ - sub_name = re.sub(u'(.)([A-Z][a-z]+)', r'\1_\2', name) - return re.sub(u'([a-z0-9])([A-Z])', r'\1_\2', sub_name).lower() + sub_name = ThemeXML.FIRST_CAMEL_REGEX.sub(r'\1_\2', name) + return ThemeXML.SECOND_CAMEL_REGEX.sub(r'\1_\2', sub_name).lower() def _build_xml_from_attrs(self): """ @@ -559,9 +600,8 @@ class ThemeXML(object): self.background_end_color, self.background_direction) else: - filename = \ - os.path.split(self.background_filename)[1] - self.add_background_image(filename) + filename = os.path.split(self.background_filename)[1] + self.add_background_image(filename, self.background_border_color) self.add_font(self.font_main_name, self.font_main_color, self.font_main_size, @@ -598,4 +638,4 @@ class ThemeXML(object): self.font_footer_shadow_size) self.add_display(self.display_horizontal_align, self.display_vertical_align, - self.display_slide_transition) \ No newline at end of file + self.display_slide_transition) diff --git a/openlp/core/lib/toolbar.py b/openlp/core/lib/toolbar.py index e37dc6f22..cf550d875 100644 --- a/openlp/core/lib/toolbar.py +++ b/openlp/core/lib/toolbar.py @@ -5,10 +5,11 @@ # 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 # +# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # +# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias # +# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, 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 # @@ -48,10 +49,10 @@ class OpenLPToolbar(QtGui.QToolBar): self.icons = {} self.setIconSize(QtCore.QSize(20, 20)) self.actions = {} - log.debug(u'Init done') + log.debug(u'Init done for %s' % parent.__class__.__name__) def addToolbarButton(self, title, icon, tooltip=None, slot=None, - checkable=False): + checkable=False, shortcuts=None, context=QtCore.Qt.WidgetShortcut): """ A method to help developers easily add a button to the toolbar. @@ -60,7 +61,7 @@ class OpenLPToolbar(QtGui.QToolBar): ``icon`` The icon of the button. This can be an instance of QIcon, or a - string cotaining either the absolute path to the image, or an + string containing either the absolute path to the image, or an internal resource path starting with ':/'. ``tooltip`` @@ -69,30 +70,39 @@ class OpenLPToolbar(QtGui.QToolBar): ``slot`` The method to run when this button is clicked. - ``objectname`` - The name of the object, as used in `