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/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 index 0b26d74fc..b937e1d5f 100644 --- a/README.txt +++ b/README.txt @@ -8,12 +8,7 @@ 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 -contribution page on the web site:: - - http://openlp.org/en/documentation/introduction/contributing.html - -If you've looked at that page, and are wanting to help develop, test or -translate OpenLP, have a look at the OpenLP wiki:: +OpenLP wiki:: http://wiki.openlp.org/ diff --git a/copyright.txt b/copyright.txt index 0e405e6e9..df5563844 100644 --- a/copyright.txt +++ b/copyright.txt @@ -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, Armin Köhler, 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 # 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/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 6be95de5f..000000000 --- a/documentation/api/source/core/lib.rst +++ /dev/null @@ -1,67 +0,0 @@ -.. _core-lib: - -Object Library -============== - -.. automodule:: openlp.core.lib - :members: - -:mod:`EventReceiver` --------------------- - -.. autoclass:: openlp.core.lib.eventreceiver.EventReceiver - :members: - -:mod:`ListWidgetWithDnD` ------------------------- - -.. autoclass:: openlp.core.lib.listwidgetwithdnd.ListWidgetWithDnD - :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 3621c6581..000000000 --- a/documentation/api/source/core/theme.rst +++ /dev/null @@ -1,10 +0,0 @@ -.. _core-theme: - -Theme Function Library -====================== - -.. automodule:: openlp.core.theme - :members: - -.. autoclass:: openlp.core.theme.theme.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/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 c89f9c6ae..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.bibleimportform.BibleImportForm - :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 a9a3a8219..000000000 --- a/documentation/api/source/plugins/songs.rst +++ /dev/null @@ -1,97 +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: - -.. 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: - -.. 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 f0d918c11..000000000 --- a/documentation/manual/source/conf.py +++ /dev/null @@ -1,210 +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.5' - -# 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 = 'openlp_qthelp' -html_theme = 'default' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -if html_theme == 'default': - html_theme_options = { - 'sidebarbgcolor': '#3a60a9', - 'relbarbgcolor': '#203b6f', - 'footerbgcolor': '#26437c', - 'headtextcolor': '#203b6f', - 'linkcolor': '#26437c', - 'sidebarlinkcolor': '#ceceff' - } - -# Add any paths that contain custom themes here, relative to this directory. -html_theme_path = [u'../themes'] - -# 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 ee4bc90a2..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 (:command:`nvidia-settings`) from your desktop's -menu, usually in an administration or system menu, or from the terminal as a -normal user run:: - - user@linux:~ $ nvidia-settings - -Once you have opened nVidia Settings, click on :guilabel:`X Server Display -Configuration`. Then select the monitor you are wanting to use as your second -monitor and click :guilabel:`Configure`. - -.. 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 378cfe2a0..000000000 --- a/documentation/manual/source/index.rst +++ /dev/null @@ -1,19 +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 - 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/manual/themes/openlp_qthelp/layout.html b/documentation/manual/themes/openlp_qthelp/layout.html deleted file mode 100644 index d16116773..000000000 --- a/documentation/manual/themes/openlp_qthelp/layout.html +++ /dev/null @@ -1,68 +0,0 @@ -{# - openlp_qthelp/layout.html - ~~~~~~~~~~~~~~~~~ - - Sphinx layout template for the openlp_qthelp theme. - - :copyright: Copyright 2004-2010 Raoul Snyman. - :license: GPL -#} -{% extends "basic/layout.html" %} -{% set script_files = script_files + ['_static/theme_extras.js'] %} -{% set css_files = css_files + ['_static/print.css'] %} - -{# do not display relbars #} -{% block relbar1 %}{% endblock %} -{% block relbar2 %}{% endblock %} - -{% macro nav() %} -

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

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

Table Of Contents

- {{ toc }} -
- {%- endif %}#} - {% block body %}{% endblock %} -
-
- {{ nav() }} -
-{% endblock %} diff --git a/documentation/manual/themes/openlp_qthelp/static/alert_info_32.png b/documentation/manual/themes/openlp_qthelp/static/alert_info_32.png deleted file mode 100644 index 05b4fe898..000000000 Binary files a/documentation/manual/themes/openlp_qthelp/static/alert_info_32.png and /dev/null differ diff --git a/documentation/manual/themes/openlp_qthelp/static/alert_warning_32.png b/documentation/manual/themes/openlp_qthelp/static/alert_warning_32.png deleted file mode 100644 index f13611cde..000000000 Binary files a/documentation/manual/themes/openlp_qthelp/static/alert_warning_32.png and /dev/null differ diff --git a/documentation/manual/themes/openlp_qthelp/static/bg-page.png b/documentation/manual/themes/openlp_qthelp/static/bg-page.png deleted file mode 100644 index c6f3bc477..000000000 Binary files a/documentation/manual/themes/openlp_qthelp/static/bg-page.png and /dev/null differ diff --git a/documentation/manual/themes/openlp_qthelp/static/bullet_orange.png b/documentation/manual/themes/openlp_qthelp/static/bullet_orange.png deleted file mode 100644 index ad5d02f34..000000000 Binary files a/documentation/manual/themes/openlp_qthelp/static/bullet_orange.png and /dev/null differ diff --git a/documentation/manual/themes/openlp_qthelp/static/openlp_qthelp.css_t b/documentation/manual/themes/openlp_qthelp/static/openlp_qthelp.css_t deleted file mode 100644 index 918ed661b..000000000 --- a/documentation/manual/themes/openlp_qthelp/static/openlp_qthelp.css_t +++ /dev/null @@ -1,372 +0,0 @@ -/* - * openlp_qthelp.css_t - * ~~~~~~~~~~~ - * - * Sphinx stylesheet -- openlp_qthelp theme. - * - * Adapted from http://openlp_qthelp-os.org/docs/Haiku-doc.css. - * Original copyright message: - * - * Copyright 2008-2009, Haiku. All rights reserved. - * Distributed under the terms of the MIT License. - * - * Authors: - * Francois Revol - * Stephan Assmus - * Braden Ewing - * Humdinger - * - * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -@import url("basic.css"); - -html { - margin: 0px; - padding: 0px; - background-color: #fff; - background-image: none; -} - -body { - line-height: 1.5; - margin: auto; - padding: 0px; - font-family: "DejaVu Sans", Arial, Helvetica, sans-serif; - min-width: 59em; - max-width: 70em; - color: {{ theme_textcolor }}; -} - -div.footer { - padding: 8px; - font-size: 11px; - text-align: center; - letter-spacing: 0.5px; -} - -/* link colors and text decoration */ - -a:link { - font-weight: bold; - text-decoration: none; - color: {{ theme_linkcolor }}; -} - -a:visited { - font-weight: bold; - text-decoration: none; - color: {{ theme_visitedlinkcolor }}; -} - -a:hover, a:active { - text-decoration: underline; - color: {{ theme_hoverlinkcolor }}; -} - -/* Some headers act as anchors, don't give them a hover effect */ - -h1 a:hover, a:active { - text-decoration: none; - color: {{ theme_headingcolor }}; -} - -h2 a:hover, a:active { - text-decoration: none; - color: {{ theme_headingcolor }}; -} - -h3 a:hover, a:active { - text-decoration: none; - color: {{ theme_headingcolor }}; -} - -h4 a:hover, a:active { - text-decoration: none; - color: {{ theme_headingcolor }}; -} - -a.headerlink { - color: #a7ce38; - padding-left: 5px; -} - -a.headerlink:hover { - color: #a7ce38; -} - -/* basic text elements */ - -div.content { - margin-top: 20px; - margin-left: 40px; - margin-right: 40px; - margin-bottom: 50px; - font-size: 0.9em; -} - -/* heading and navigation */ - -div.header { - position: relative; - left: 0px; - top: 0px; - height: 85px; - /* background: #eeeeee; */ - padding: 0 40px; -} -div.header h1 { - font-size: 1.6em; - font-weight: normal; - letter-spacing: 1px; - color: {{ theme_headingcolor }}; - border: 0; - margin: 0; - padding-top: 15px; -} -div.header h1 a { - font-weight: normal; - color: {{ theme_headingcolor }}; -} -div.header h2 { - font-size: 1.3em; - font-weight: normal; - letter-spacing: 1px; - text-transform: uppercase; - color: #aaa; - border: 0; - margin-top: -3px; - padding: 0; -} - -div.header img.rightlogo { - float: right; -} - - -div.title { - font-size: 1.3em; - font-weight: bold; - color: {{ theme_headingcolor }}; - border-bottom: dotted thin #e0e0e0; - margin-bottom: 25px; -} -div.topnav { - /* background: #e0e0e0; */ -} -div.topnav p { - margin-top: 0; - margin-left: 40px; - margin-right: 40px; - margin-bottom: 0px; - text-align: right; - font-size: 0.8em; -} -div.bottomnav { - background: #eeeeee; -} -div.bottomnav p { - margin-right: 40px; - text-align: right; - font-size: 0.8em; -} - -a.uplink { - font-weight: normal; -} - - -/* contents box */ - -table.index { - margin: 0px 0px 30px 30px; - padding: 1px; - border-width: 1px; - border-style: dotted; - border-color: #e0e0e0; -} -table.index tr.heading { - background-color: #e0e0e0; - text-align: center; - font-weight: bold; - font-size: 1.1em; -} -table.index tr.index { - background-color: #eeeeee; -} -table.index td { - padding: 5px 20px; -} - -table.index a:link, table.index a:visited { - font-weight: normal; - text-decoration: none; - color: {{ theme_linkcolor }}; -} -table.index a:hover, table.index a:active { - text-decoration: underline; - color: {{ theme_hoverlinkcolor }}; -} - - -/* Haiku User Guide styles and layout */ - -/* Rounded corner boxes */ -/* Common declarations */ -div.admonition { - -webkit-border-radius: 10px; - -khtml-border-radius: 10px; - -moz-border-radius: 10px; - border-radius: 10px; - border-style: dotted; - border-width: thin; - border-color: #dcdcdc; - padding: 10px 15px 10px 15px; - margin-bottom: 15px; - margin-top: 15px; -} -div.note { - padding: 10px 15px 10px 80px; - background: #e4ffde url(alert_info_32.png) 15px 15px no-repeat; - min-height: 42px; -} -div.warning { - padding: 10px 15px 10px 80px; - background: #fffbc6 url(alert_warning_32.png) 15px 15px no-repeat; - min-height: 42px; -} -div.seealso { - background: #e4ffde; -} - -/* More layout and styles */ -h1 { - font-size: 1.3em; - font-weight: bold; - color: {{ theme_headingcolor }}; - border-bottom: dotted thin #e0e0e0; - margin-top: 30px; -} - -h2 { - font-size: 1.2em; - font-weight: normal; - color: {{ theme_headingcolor }}; - border-bottom: dotted thin #e0e0e0; - margin-top: 30px; -} - -h3 { - font-size: 1.1em; - font-weight: normal; - color: {{ theme_headingcolor }}; - margin-top: 30px; -} - -h4 { - font-size: 1.0em; - font-weight: normal; - color: {{ theme_headingcolor }}; - margin-top: 30px; -} - -p { - text-align: justify; -} - -p.last { - margin-bottom: 0; -} - -ol { - padding-left: 20px; -} - -ul { - padding-left: 5px; - margin-top: 3px; -} - -li { - line-height: 1.3; -} - -div.content ul > li { - -moz-background-clip:border; - -moz-background-inline-policy:continuous; - -moz-background-origin:padding; - background: transparent url(bullet_orange.png) no-repeat scroll left 0.45em; - list-style-image: none; - list-style-type: none; - padding: 0 0 0 1.666em; - margin-bottom: 3px; -} - -td { - vertical-align: top; -} - -tt { - background-color: #e2e2e2; - font-size: 1.0em; - font-family: monospace; -} - -pre { - border-color: #0c3762; - border-style: dotted; - border-width: thin; - margin: 0 0 12px 0; - padding: 0.8em; - background-color: #f0f0f0; -} - -hr { - border-top: 1px solid #ccc; - border-bottom: 0; - border-right: 0; - border-left: 0; - margin-bottom: 10px; - margin-top: 20px; -} - -/* printer only pretty stuff */ -@media print { - .noprint { - display: none; - } - /* for acronyms we want their definitions inlined at print time */ - acronym[title]:after { - font-size: small; - content: " (" attr(title) ")"; - font-style: italic; - } - /* and not have mozilla dotted underline */ - acronym { - border: none; - } - div.topnav, div.bottomnav, div.header, table.index { - display: none; - } - div.content { - margin: 0px; - padding: 0px; - } - html { - background: #FFF; - } -} - -.viewcode-back { - font-family: "DejaVu Sans", Arial, Helvetica, sans-serif; -} - -div.viewcode-block:target { - background-color: #f4debf; - border-top: 1px solid #ac9; - border-bottom: 1px solid #ac9; - margin: -1px -12px; - padding: 0 12px; -} diff --git a/documentation/manual/themes/openlp_qthelp/theme.conf b/documentation/manual/themes/openlp_qthelp/theme.conf deleted file mode 100644 index 0d44b0faa..000000000 --- a/documentation/manual/themes/openlp_qthelp/theme.conf +++ /dev/null @@ -1,12 +0,0 @@ -[theme] -inherit = basic -stylesheet = openlp_qthelp.css -pygments_style = autumn - -[options] -full_logo = false -textcolor = #333333 -headingcolor = #203b6f -linkcolor = #26437c -visitedlinkcolor = #26437c -hoverlinkcolor = #26437c diff --git a/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 39719a80e..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, Armin Köhler, 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 # @@ -24,271 +25,15 @@ # with this program; if not, write to the Free Software Foundation, Inc., 59 # # 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 -from PyQt4 import QtCore, QtGui +# 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 openlp.core.lib import Receiver, check_directory_exists -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 +from openlp.core import 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. - """ - log.info(u'OpenLP Application Loaded') - - def _get_version(self): - """ - Load and store current Application Version - """ - if u'--dev-version' in sys.argv or u'-d' 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 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']) - # Decide how many screens we have and their size - screens = ScreenList(self.desktop()) - # First time checks in settings - firstTime = QtCore.QSettings().value( - u'general/first time', QtCore.QVariant(True)).toBool() - if firstTime: - FirstTimeForm(screens).exec_() - 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.appClipboard = self.clipboard() - self.mainWindow = MainWindow(screens, app_version, self.appClipboard, - firstTime) - self.mainWindow.show() - if show_splash: - # now kill the splashscreen - self.splash.finish(self.mainWindow) - self.mainWindow.repaint() - update_check = QtCore.QSettings().value( - u'general/update check', QtCore.QVariant(True)).toBool() - if update_check: - 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) - self.processEvents() - - def setNormalCursor(self): - """ - Sets the Normal Cursor for the 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) - 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) - 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) - # Define the settings environment - QtCore.QSettings(u'OpenLP', u'OpenLP') - # First time checks in settings - # Use explicit reference as not inside a QT environment yet - if QtCore.QSettings(u'OpenLP', u'OpenLP').value( - u'general/first time', QtCore.QVariant(True)).toBool(): - if not FirstTimeLanguageForm().exec_(): - # if cancel then stop processing - sys.exit() - if sys.platform == u'darwin': - OpenLP.addLibraryPath(QtGui.QApplication.applicationDirPath() - + "/qt4_plugins") - # 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 9c22aab8b..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, Armin Köhler, 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 # @@ -26,3 +27,9 @@ """ The :mod:`openlp` module contains all the project produced OpenLP functionality """ + +import core +import plugins + +__all__ = [u'core', u'plugins'] + diff --git a/openlp/core/__init__.py b/openlp/core/__init__.py index 00d8b78c0..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, Armin Köhler, 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. """ + +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 dc60a5a65..e104f4022 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, Armin Köhler, 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,56 +36,16 @@ from PyQt4 import QtCore, QtGui log = logging.getLogger(__name__) -base_html_expands = [] - -base_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': True}) -base_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': True}) -base_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': True}) -base_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': True}) -base_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': True}) -base_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': True}) -base_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': True}) -base_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': True}) -base_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': True}) -base_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}) -base_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}) -base_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}) -base_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}) -base_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}) -base_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 @@ -101,8 +62,7 @@ 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): """ @@ -166,56 +126,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,13 +144,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(unicode(thumb_path)): + return False + image_date = os.stat(unicode(file_path)).st_mtime + thumb_date = os.stat(unicode(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. It has to be either a ``QImage`` instance or the - path to the image. + ``image_path`` + The path to the image to resize. ``width`` The new image width. @@ -248,28 +210,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 @@ -294,8 +265,9 @@ def clean_tags(text): Remove Tags from text for display """ text = text.replace(u'
', u'\n') + text = text.replace(u'{br}', u'\n') text = text.replace(u' ', u' ') - for tag in DisplayTags.get_html_tags(): + 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 @@ -304,7 +276,7 @@ def expand_tags(text): """ Expand tags HTML for display """ - for tag in DisplayTags.get_html_tags(): + 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 @@ -317,25 +289,26 @@ def check_directory_exists(dir): Theme directory to make sure exists """ log.debug(u'check_directory_exists %s' % dir) - if not os.path.exists(dir): - os.makedirs(dir) + try: + if not os.path.exists(dir): + os.makedirs(dir) + except IOError: + pass -from listwidgetwithdnd import ListWidgetWithDnD -from displaytags import DisplayTags -from spelltextedit import SpellTextEdit 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 openlp.core.utils.actions import ActionList diff --git a/openlp/core/lib/db.py b/openlp/core/lib/db.py index c92f992c8..cafc867b3 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, Armin Köhler, 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,16 @@ 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.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. @@ -77,6 +140,7 @@ def delete_database(plugin_name, db_file_name=None): AppLocation.get_section_data_path(plugin_name), plugin_name) return delete_file(db_file_path) + class BaseModel(object): """ BaseModel provides a base object with a set of generic functions @@ -87,16 +151,16 @@ class BaseModel(object): Creates an instance of a class and populates it, returning the instance """ instance = cls() - for key in kwargs: - instance.__setattr__(key, kwargs[key]) + 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. @@ -107,6 +171,9 @@ 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 in the plugin_name being used. @@ -127,12 +194,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): """ @@ -221,7 +309,9 @@ class Manager(object): 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() diff --git a/openlp/core/lib/dockwidget.py b/openlp/core/lib/dockwidget.py index efc0a3066..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, Armin Köhler, 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 # @@ -32,6 +33,7 @@ import logging from PyQt4 import QtGui from openlp.core.lib import build_icon +from openlp.core.ui import ScreenList log = logging.getLogger(__name__) @@ -45,8 +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(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 78b0c6324..14b6126bc 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, Armin Köhler, 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,200 +35,190 @@ 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_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 - - ``openlp_stop_wizard`` - Stops a wizard before completion + 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..ea5547f27 --- /dev/null +++ b/openlp/core/lib/formattingtags.py @@ -0,0 +1,227 @@ +# -*- 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'): + tags.append(tag) + # Remove key 'temporary' from tags. It is not needed to be saved. + for tag in tags: + if u'temporary' in tag: + del tag[u'temporary'] + # 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(unicode(user_expands).encode(u'utf8')) + if user_expands_string: + user_tags = cPickle.loads(user_expands_string) + # 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 4faf47ddc..5dfe00d36 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, Armin Köhler, 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,6 +35,7 @@ from openlp.core.lib.theme import BackgroundType, BackgroundGradientType, \ log = logging.getLogger(__name__) HTMLSRC = u""" + OpenLP Display @@ -55,39 +57,38 @@ body { height: %spx; } #black { - z-index:8; + z-index: 8; background-color: black; display: none; } #bgimage { - z-index:1; + z-index: 1; } #image { - z-index:2; + z-index: 2; } #video1 { - z-index:3; + z-index: 3; } #video2 { - z-index:3; -} -#alert { - position: absolute; - left: 0px; - top: 0px; - z-index:10; - %s + z-index: 3; } +%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; +} - +
""" % \ + (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') + 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 0c9549ea5..000000000 --- a/openlp/core/lib/rendermanager.py +++ /dev/null @@ -1,260 +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, Armin Köhler, 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, ServiceItem, ImageManager -from openlp.core.lib.theme import ThemeLevel -from openlp.core.ui import MainDisplay - -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' -FOOTER = [u'Arky Arky (Unknown)', u'Public Domain', u'CCLI 123456'] - -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.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.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']) - # 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 + VERSE + VERSE, FOOTER) - else: - self.image_manager.del_image(theme_data.theme_name) - 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) diff --git a/openlp/core/lib/searchedit.py b/openlp/core/lib/searchedit.py index d2424d00e..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, Armin Köhler, 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): """ @@ -73,10 +76,10 @@ class SearchEdit(QtGui.QLineEdit): 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() self.setMinimumSize( @@ -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 2dd87f6f5..b12a27f1d 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, Armin Köhler, 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,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.ui import UiStrings +from openlp.core.lib import build_icon, clean_tags, expand_tags, translate log = logging.getLogger(__name__) @@ -51,18 +52,21 @@ 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 - AllowsDetailedTitleDisplay = 11 - AllowsVarableStartTime = 12 + HasDetailedTitleDisplay = 11 + HasVariableStartTime = 12 + CanSoftBreak = 13 + CanWordSplit = 14 + HasBackgroundAudio = 15 class ServiceItem(object): @@ -81,7 +85,7 @@ 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'' @@ -109,14 +113,18 @@ class ServiceItem(object): 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._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()) @@ -149,44 +157,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 = 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) + # 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,9 +207,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.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): @@ -211,6 +225,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( @@ -256,15 +272,16 @@ class ServiceItem(object): u'data': self.data_string, u'xml_version': self.xml_version, u'start_time': self.start_time, - u'media_length': self.media_length + 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( @@ -302,8 +319,13 @@ class ServiceItem(object): 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) @@ -325,7 +347,7 @@ class ServiceItem(object): if self.is_text(): return self.title else: - if ItemCapabilities.AllowsDetailedTitleDisplay in self.capabilities: + if ItemCapabilities.HasDetailedTitleDisplay in self.capabilities: return self._raw_frames[0][u'title'] elif len(self._raw_frames) > 1: return self.title @@ -337,8 +359,19 @@ class ServiceItem(object): 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 + # 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): """ @@ -431,16 +464,31 @@ class ServiceItem(object): start = None end = None if self.start_time != 0: - start = UiStrings.StartTimeCode % \ + start = unicode(translate('OpenLP.ServiceItem', + 'Start: %s')) % \ unicode(datetime.timedelta(seconds=self.start_time)) if self.media_length != 0: - end = UiStrings.LengthTime % \ + end = unicode(translate('OpenLP.ServiceItem', + 'Length: %s')) % \ unicode(datetime.timedelta(seconds=self.media_length)) if not start and not end: - return None + return u'' elif start and not end: return start elif not start and end: return end else: - return u'%s : %s' % (start, end) + 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 d09497540..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, Armin Köhler, 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): diff --git a/openlp/core/lib/settingstab.py b/openlp/core/lib/settingstab.py index 4f3748c8d..46263efca 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, Armin Köhler, 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,14 +42,15 @@ 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): @@ -84,12 +86,6 @@ class SettingsTab(QtGui.QWidget): left_width = max(left_width, self.leftColumn.minimumSizeHint().width()) self.leftColumn.setFixedWidth(left_width) - def preLoad(self): - """ - Setup the tab's interface. - """ - pass - def retranslateUi(self): """ Setup the interface translation strings. @@ -116,9 +112,9 @@ class SettingsTab(QtGui.QWidget): def cancel(self): """ - Reset any settings + Reset any settings if cancel pressed """ - pass + self.load() def postSetUp(self, postUpdate=False): """ diff --git a/openlp/core/lib/spelltextedit.py b/openlp/core/lib/spelltextedit.py index 493781ccc..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, Armin Köhler, 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,11 +24,12 @@ # 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 + from enchant.errors import Error ENCHANT_AVAILABLE = True except ImportError: ENCHANT_AVAILABLE = False @@ -36,22 +38,29 @@ except ImportError: # http://john.nachtimwald.com/2009/08/22/qplaintextedit-with-in-line-spell-check from PyQt4 import QtCore, QtGui -from openlp.core.lib import translate, DisplayTags + +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: try: self.dictionary = enchant.Dict() - except DictNotFoundError: - self.dictionary = enchant.Dict(u'en_US') - self.highlighter = Highlighter(self.document()) - self.highlighter.spellingDictionary = self.dictionary + 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): """ @@ -76,6 +85,19 @@ 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(): @@ -89,19 +111,31 @@ class SpellTextEdit(QtGui.QPlainTextEdit): 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 DisplayTags.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) + 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. @@ -116,7 +150,7 @@ class SpellTextEdit(QtGui.QPlainTextEdit): """ Replaces the selected text with word. """ - for html in DisplayTags.get_html_tags(): + for html in FormattingTags.get_html_tags(): if tag == html[u'desc']: cursor = self.textCursor() if self.textCursor().hasSelection(): diff --git a/openlp/core/lib/theme.py b/openlp/core/lib/theme.py index ef26ca842..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, Armin Köhler, 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 # @@ -33,8 +34,7 @@ import logging from xml.dom.minidom import Document from lxml import etree, objectify -from openlp.core.lib import str_to_bool, translate -from openlp.core.lib.ui import UiStrings +from openlp.core.lib import str_to_bool log = logging.getLogger(__name__) @@ -44,6 +44,7 @@ BLANK_THEME_XML = \ + #000000 #000000 @@ -175,12 +176,9 @@ class HorizontalType(object): Left = 0 Right = 1 Center = 2 + Justify = 3 - Names = [u'left', u'right', u'center'] - TranslatedNames = [ - translate('OpenLP.ThemeWizard', 'Left'), - translate('OpenLP.ThemeWizard', 'Right'), - translate('OpenLP.ThemeWizard', 'Center')] + Names = [u'left', u'right', u'center', u'justify'] class VerticalType(object): @@ -192,7 +190,6 @@ class VerticalType(object): Bottom = 2 Names = [u'top', u'middle', u'bottom'] - TranslatedNames = [UiStrings.Top, UiStrings.Middle, UiStrings.Bottom] BOOLEAN_LIST = [u'bold', u'italics', u'override', u'outline', u'shadow', @@ -207,6 +204,8 @@ 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. @@ -285,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. @@ -297,6 +296,8 @@ 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, @@ -581,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): """ @@ -600,7 +601,7 @@ class ThemeXML(object): self.background_direction) else: filename = os.path.split(self.background_filename)[1] - self.add_background_image(filename) + self.add_background_image(filename, self.background_border_color) self.add_font(self.font_main_name, self.font_main_color, self.font_main_size, diff --git a/openlp/core/lib/toolbar.py b/openlp/core/lib/toolbar.py index 37fb67d52..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, Armin Köhler, 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,11 +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, shortcut=0, alternate=0, - context=QtCore.Qt.WidgetShortcut): + checkable=False, shortcuts=None, context=QtCore.Qt.WidgetShortcut): """ A method to help developers easily add a button to the toolbar. @@ -74,16 +74,12 @@ class OpenLPToolbar(QtGui.QToolBar): If *True* the button has two, *off* and *on*, states. Default is *False*, which means the buttons has only one state. - ``shortcut`` - The primary shortcut for this action - - ``alternate`` - The alternate shortcut for this action + ``shortcuts`` + The list of shortcuts for this action ``context`` Specify the context in which this shortcut is valid """ - newAction = None if icon: actionIcon = build_icon(icon) if slot and not checkable: @@ -92,7 +88,7 @@ class OpenLPToolbar(QtGui.QToolBar): newAction = self.addAction(actionIcon, title) self.icons[title] = actionIcon else: - newAction = QtGui.QAction(title, newAction) + newAction = QtGui.QAction(title, self) self.addAction(newAction) QtCore.QObject.connect(newAction, QtCore.SIGNAL(u'triggered()'), slot) @@ -103,8 +99,9 @@ class OpenLPToolbar(QtGui.QToolBar): QtCore.QObject.connect(newAction, QtCore.SIGNAL(u'toggled(bool)'), slot) self.actions[title] = newAction - newAction.setShortcuts([shortcut, alternate]) - newAction.setShortcutContext(context) + if shortcuts is not None: + newAction.setShortcuts(shortcuts) + newAction.setShortcutContext(context) return newAction def addToolbarSeparator(self, handle): diff --git a/openlp/core/lib/ui.py b/openlp/core/lib/ui.py index eae4f60ca..756df36c3 100644 --- a/openlp/core/lib/ui.py +++ b/openlp/core/lib/ui.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, Armin Köhler, 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,7 @@ import logging from PyQt4 import QtCore, QtGui from openlp.core.lib import build_icon, Receiver, translate +from openlp.core.utils.actions import ActionList log = logging.getLogger(__name__) @@ -38,61 +40,105 @@ class UiStrings(object): """ Provide standard strings for objects to use. """ - # These strings should need a good reason to be retranslated elsewhere. - # Should some/more/less of these have an & attached? - About = translate('OpenLP.Ui', 'About') - Add = translate('OpenLP.Ui', '&Add') - Advanced = translate('OpenLP.Ui', 'Advanced') - AllFiles = translate('OpenLP.Ui', 'All Files') - Bottom = translate('OpenLP.Ui', 'Bottom') - Browse = translate('OpenLP.Ui', 'Browse...') - Cancel = translate('OpenLP.Ui', 'Cancel') - CCLINumberLabel = translate('OpenLP.Ui', 'CCLI number:') - CreateService = translate('OpenLP.Ui', 'Create a new service.') - Delete = translate('OpenLP.Ui', '&Delete') - Edit = translate('OpenLP.Ui', '&Edit') - EmptyField = translate('OpenLP.Ui', 'Empty Field') - Error = translate('OpenLP.Ui', 'Error') - Export = translate('OpenLP.Ui', 'Export') - FontSizePtUnit = translate('OpenLP.Ui', 'pt', - 'Abbreviated font pointsize unit') - Image = translate('OpenLP.Ui', 'Image') - Import = translate('OpenLP.Ui', 'Import') - LengthTime = unicode(translate('OpenLP.Ui', 'Length %s')) - Live = translate('OpenLP.Ui', 'Live') - LiveBGError = translate('OpenLP.Ui', 'Live Background Error') - LivePanel = translate('OpenLP.Ui', 'Live Panel') - Load = translate('OpenLP.Ui', 'Load') - Middle = translate('OpenLP.Ui', 'Middle') - New = translate('OpenLP.Ui', 'New') - NewService = translate('OpenLP.Ui', 'New Service') - NewTheme = translate('OpenLP.Ui', 'New Theme') - NFSs = translate('OpenLP.Ui', 'No File Selected', 'Singular') - NFSp = translate('OpenLP.Ui', 'No Files Selected', 'Plural') - NISs = translate('OpenLP.Ui', 'No Item Selected', 'Singular') - NISp = translate('OpenLP.Ui', 'No Items Selected', 'Plural') - OLPV1 = translate('OpenLP.Ui', 'openlp.org 1.x') - OLPV2 = translate('OpenLP.Ui', 'OpenLP 2.0') - OpenService = translate('OpenLP.Ui', 'Open Service') - Preview = translate('OpenLP.Ui', 'Preview') - PreviewPanel = translate('OpenLP.Ui', 'Preview Panel') - PrintServiceOrder = translate('OpenLP.Ui', 'Print Service Order') - ReplaceBG = translate('OpenLP.Ui', 'Replace Background') - ReplaceLiveBG = translate('OpenLP.Ui', 'Replace Live Background') - ResetBG = translate('OpenLP.Ui', 'Reset Background') - ResetLiveBG = translate('OpenLP.Ui', 'Reset Live Background') - S = translate('OpenLP.Ui', 's', 'The abbreviated unit for seconds') - SaveAndPreview = translate('OpenLP.Ui', 'Save && Preview') - Search = translate('OpenLP.Ui', 'Search') - SelectDelete = translate('OpenLP.Ui', 'You must select an item to delete.') - SelectEdit = translate('OpenLP.Ui', 'You must select an item to edit.') - SaveService = translate('OpenLP.Ui', 'Save Service') - Service = translate('OpenLP.Ui', 'Service') - StartTimeCode = unicode(translate('OpenLP.Ui', 'Start %s')) - Theme = translate('OpenLP.Ui', 'Theme', 'Singular') - Themes = translate('OpenLP.Ui', 'Themes', 'Plural') - Top = translate('OpenLP.Ui', 'Top') - Version = translate('OpenLP.Ui', 'Version') + __instance__ = None + + def __new__(cls): + """ + Override the default object creation method to return a single instance. + """ + if not cls.__instance__: + cls.__instance__ = object.__new__(cls) + return cls.__instance__ + + def __init__(self): + """ + These strings should need a good reason to be retranslated elsewhere. + Should some/more/less of these have an & attached? + """ + self.About = translate('OpenLP.Ui', 'About') + self.Add = translate('OpenLP.Ui', '&Add') + self.Advanced = translate('OpenLP.Ui', 'Advanced') + self.AllFiles = translate('OpenLP.Ui', 'All Files') + self.Bottom = translate('OpenLP.Ui', 'Bottom') + self.Browse = translate('OpenLP.Ui', 'Browse...') + self.Cancel = translate('OpenLP.Ui', 'Cancel') + self.CCLINumberLabel = translate('OpenLP.Ui', 'CCLI number:') + self.CreateService = translate('OpenLP.Ui', 'Create a new service.') + self.ConfirmDelete = translate('OpenLP.Ui', 'Confirm Delete') + self.Continuous = translate('OpenLP.Ui', 'Continuous') + self.Default = unicode(translate('OpenLP.Ui', 'Default')) + self.Delete = translate('OpenLP.Ui', '&Delete') + self.DisplayStyle = translate('OpenLP.Ui', 'Display style:') + self.Duplicate = translate('OpenLP.Ui', 'Duplicate Error') + self.Edit = translate('OpenLP.Ui', '&Edit') + self.EmptyField = translate('OpenLP.Ui', 'Empty Field') + self.Error = translate('OpenLP.Ui', 'Error') + self.Export = translate('OpenLP.Ui', 'Export') + self.File = translate('OpenLP.Ui', 'File') + self.FontSizePtUnit = translate('OpenLP.Ui', 'pt', + 'Abbreviated font pointsize unit') + self.Help = translate('OpenLP.Ui', 'Help') + self.Hours = translate('OpenLP.Ui', 'h', + 'The abbreviated unit for hours') + self.Image = translate('OpenLP.Ui', 'Image') + self.Import = translate('OpenLP.Ui', 'Import') + self.LayoutStyle = translate('OpenLP.Ui', 'Layout style:') + self.Live = translate('OpenLP.Ui', 'Live') + self.LiveBGError = translate('OpenLP.Ui', 'Live Background Error') + self.LiveToolbar = translate('OpenLP.Ui', 'Live Toolbar') + self.Load = translate('OpenLP.Ui', 'Load') + self.Minutes = translate('OpenLP.Ui', 'm', + 'The abbreviated unit for minutes') + self.Middle = translate('OpenLP.Ui', 'Middle') + self.New = translate('OpenLP.Ui', 'New') + self.NewService = translate('OpenLP.Ui', 'New Service') + self.NewTheme = translate('OpenLP.Ui', 'New Theme') + self.NFSs = translate('OpenLP.Ui', 'No File Selected', 'Singular') + self.NFSp = translate('OpenLP.Ui', 'No Files Selected', 'Plural') + self.NISs = translate('OpenLP.Ui', 'No Item Selected', 'Singular') + self.NISp = translate('OpenLP.Ui', 'No Items Selected', 'Plural') + self.OLPV1 = translate('OpenLP.Ui', 'openlp.org 1.x') + self.OLPV2 = translate('OpenLP.Ui', 'OpenLP 2.0') + self.OpenLPStart = translate('OpenLP.Ui', 'OpenLP is already running. ' + 'Do you wish to continue?') + self.OpenService = translate('OpenLP.Ui', 'Open service.') + self.PlaySlidesInLoop = translate('OpenLP.Ui','Play Slides in Loop') + self.PlaySlidesToEnd = translate('OpenLP.Ui','Play Slides to End') + self.Preview = translate('OpenLP.Ui', 'Preview') + self.PrintService = translate('OpenLP.Ui', 'Print Service') + self.ReplaceBG = translate('OpenLP.Ui', 'Replace Background') + self.ReplaceLiveBG = translate('OpenLP.Ui', 'Replace live background.') + self.ResetBG = translate('OpenLP.Ui', 'Reset Background') + self.ResetLiveBG = translate('OpenLP.Ui', 'Reset live background.') + self.Seconds = translate('OpenLP.Ui', 's', + 'The abbreviated unit for seconds') + self.SaveAndPreview = translate('OpenLP.Ui', 'Save && Preview') + self.Search = translate('OpenLP.Ui', 'Search') + self.SelectDelete = translate('OpenLP.Ui', 'You must select an item ' + 'to delete.') + self.SelectEdit = translate('OpenLP.Ui', 'You must select an item to ' + 'edit.') + self.Settings = translate('OpenLP.Ui', 'Settings') + self.SaveService = translate('OpenLP.Ui', 'Save Service') + self.Service = translate('OpenLP.Ui', 'Service') + self.Split = translate('OpenLP.Ui', '&Split') + self.SplitToolTip = translate('OpenLP.Ui', 'Split a slide into two ' + 'only if it does not fit on the screen as one slide.') + self.StartTimeCode = unicode(translate('OpenLP.Ui', 'Start %s')) + self.StopPlaySlidesInLoop = translate('OpenLP.Ui', + 'Stop Play Slides in Loop') + self.StopPlaySlidesToEnd = translate('OpenLP.Ui', + 'Stop Play Slides to End') + self.Theme = translate('OpenLP.Ui', 'Theme', 'Singular') + self.Themes = translate('OpenLP.Ui', 'Themes', 'Plural') + self.Tools = translate('OpenLP.Ui', 'Tools') + self.Top = translate('OpenLP.Ui', 'Top') + self.UnsupportedFile = translate('OpenLP.Ui', 'Unsupported File') + self.VersePerSlide = translate('OpenLP.Ui', 'Verse Per Slide') + self.VersePerLine = translate('OpenLP.Ui', 'Verse Per Line') + self.Version = translate('OpenLP.Ui', 'Version') + self.View = translate('OpenLP.Ui', 'View') + self.ViewMode = translate('OpenLP.Ui', 'View Mode') def add_welcome_page(parent, image): """ @@ -139,7 +185,8 @@ def create_accept_reject_button_box(parent, okay=False): accept_button = QtGui.QDialogButtonBox.Save if okay: accept_button = QtGui.QDialogButtonBox.Ok - button_box.setStandardButtons(accept_button | QtGui.QDialogButtonBox.Cancel) + button_box.setStandardButtons( + accept_button | QtGui.QDialogButtonBox.Cancel) button_box.setObjectName(u'%sButtonBox' % parent) QtCore.QObject.connect(button_box, QtCore.SIGNAL(u'accepted()'), parent.accept) @@ -166,11 +213,11 @@ def critical_error_message_box(title=None, message=None, parent=None, Should this message box question the user. """ if question: - return QtGui.QMessageBox.critical(parent, UiStrings.Error, message, + return QtGui.QMessageBox.critical(parent, UiStrings().Error, message, QtGui.QMessageBox.StandardButtons( QtGui.QMessageBox.Yes | QtGui.QMessageBox.No)) data = {u'message': message} - data[u'title'] = title if title else UiStrings.Error + data[u'title'] = title if title else UiStrings().Error return Receiver.send_message(u'openlp_error_message', data) def media_item_combo_box(parent, name): @@ -200,7 +247,7 @@ def create_delete_push_button(parent, icon=None): delete_button.setObjectName(u'deleteButton') delete_icon = icon if icon else u':/general/general_delete.png' delete_button.setIcon(build_icon(delete_icon)) - delete_button.setText(UiStrings.Delete) + delete_button.setText(UiStrings().Delete) delete_button.setToolTip( translate('OpenLP.Ui', 'Delete the selected item.')) QtCore.QObject.connect(delete_button, @@ -233,43 +280,129 @@ def create_up_down_push_button_set(parent): QtCore.SIGNAL(u'clicked()'), parent.onDownButtonClicked) return up_button, down_button -def base_action(parent, name): +def base_action(parent, name, category=None): """ Return the most basic action with the object name set. + + ``category`` + The category the action should be listed in the shortcut dialog. If you + not wish, that this action is added to the shortcut dialog, then do not + state any. """ action = QtGui.QAction(parent) action.setObjectName(name) + if category is not None: + action_list = ActionList.get_instance() + action_list.add_action(action, category) return action -def checkable_action(parent, name, checked=None): +def checkable_action(parent, name, checked=None, category=None): """ Return a standard action with the checkable attribute set. """ - action = base_action(parent, name) + action = base_action(parent, name, category) action.setCheckable(True) if checked is not None: action.setChecked(checked) return action -def icon_action(parent, name, icon, checked=None): +def icon_action(parent, name, icon, checked=None, category=None): """ Return a standard action with an icon. """ if checked is not None: - action = checkable_action(parent, name, checked) + action = checkable_action(parent, name, checked, category) else: - action = base_action(parent, name) + action = base_action(parent, name, category) action.setIcon(build_icon(icon)) return action -def shortcut_action(parent, text, shortcuts, function): +def shortcut_action(parent, name, shortcuts, function, icon=None, checked=None, + category=None, context=QtCore.Qt.WindowShortcut): """ Return a shortcut enabled action. """ - action = QtGui.QAction(text, parent) - action.setShortcuts(shortcuts) - action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut) - QtCore.QObject.connect(action, QtCore.SIGNAL(u'triggered()'), function) + action = QtGui.QAction(parent) + action.setObjectName(name) + if icon is not None: + action.setIcon(build_icon(icon)) + if checked is not None: + action.setCheckable(True) + action.setChecked(checked) + if shortcuts: + action.setShortcuts(shortcuts) + action.setShortcutContext(context) + action_list = ActionList.get_instance() + action_list.add_action(action, category) + QtCore.QObject.connect(action, QtCore.SIGNAL(u'triggered(bool)'), function) + return action + +def context_menu_action(base, icon, text, slot, shortcuts=None, category=None, + context=QtCore.Qt.WidgetShortcut): + """ + Utility method to help build context menus. + + ``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 + + ``shortcuts`` + The action's shortcuts. + + ``category`` + The category the shortcut should be listed in the shortcut dialog. If + left to ``None``, then the action will be hidden in the shortcut dialog. + + ``context`` + The context the shortcut is valid. + """ + action = QtGui.QAction(text, base) + if icon: + action.setIcon(build_icon(icon)) + QtCore.QObject.connect(action, QtCore.SIGNAL(u'triggered(bool)'), slot) + if shortcuts is not None: + action.setShortcuts(shortcuts) + action.setShortcutContext(context) + action_list = ActionList.get_instance() + action_list.add_action(action) + base.addAction(action) + return action + +def context_menu(base, icon, text): + """ + Utility method to help build context menus. + + ``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) + base.addAction(action) return action def add_widget_completer(cache, widget): @@ -305,8 +438,25 @@ def create_valign_combo(form, parent, layout): verticalLabel.setText(translate('OpenLP.Ui', '&Vertical Align:')) form.verticalComboBox = QtGui.QComboBox(parent) form.verticalComboBox.setObjectName(u'VerticalComboBox') - form.verticalComboBox.addItem(UiStrings.Top) - form.verticalComboBox.addItem(UiStrings.Middle) - form.verticalComboBox.addItem(UiStrings.Bottom) + form.verticalComboBox.addItem(UiStrings().Top) + form.verticalComboBox.addItem(UiStrings().Middle) + form.verticalComboBox.addItem(UiStrings().Bottom) verticalLabel.setBuddy(form.verticalComboBox) layout.addRow(verticalLabel, form.verticalComboBox) + +def find_and_set_in_combo_box(combo_box, value_to_find): + """ + Find a string in a combo box and set it as the selected item if present + + ``combo_box`` + The combo box to check for selected items + + ``value_to_find`` + The value to find + """ + index = combo_box.findText(value_to_find, + QtCore.Qt.MatchExactly) + if index == -1: + # Not Found. + index = 0 + combo_box.setCurrentIndex(index) diff --git a/openlp/core/theme/__init__.py b/openlp/core/theme/__init__.py index 52f7120b1..44a937608 100644 --- a/openlp/core/theme/__init__.py +++ b/openlp/core/theme/__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, Armin Köhler, 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 # diff --git a/openlp/core/theme/theme.py b/openlp/core/theme/theme.py index bb6b25a9e..48e364dbd 100644 --- a/openlp/core/theme/theme.py +++ b/openlp/core/theme/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, Armin Köhler, 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 # diff --git a/openlp/core/ui/__init__.py b/openlp/core/ui/__init__.py index 485d2adda..6a04a080b 100644 --- a/openlp/core/ui/__init__.py +++ b/openlp/core/ui/__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, Armin Köhler, 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 # @@ -51,15 +52,34 @@ class HideMode(object): Theme = 2 Screen = 3 +class AlertLocation(object): + """ + This is an enumeration class which controls where Alerts are placed on the + screen. + + ``Top`` + Place the text at the top of the screen. + + ``Middle`` + Place the text in the middle of the screen. + + ``Bottom`` + Place the text at the bottom of the screen. + """ + Top = 0 + Middle = 1 + Bottom = 2 + from firsttimeform import FirstTimeForm from firsttimelanguageform import FirstTimeLanguageForm +from themelayoutform import ThemeLayoutForm from themeform import ThemeForm from filerenameform import FileRenameForm from starttimeform import StartTimeForm +from screen import ScreenList from maindisplay import MainDisplay from servicenoteform import ServiceNoteForm from serviceitemeditform import ServiceItemEditForm -from screen import ScreenList from slidecontroller import SlideController from splashscreen import SplashScreen from generaltab import GeneralTab @@ -68,7 +88,7 @@ from advancedtab import AdvancedTab from aboutform import AboutForm from pluginform import PluginForm from settingsform import SettingsForm -from displaytagform import DisplayTagForm +from formattingtagform import FormattingTagForm from shortcutlistform import ShortcutListForm from mediadockmanager import MediaDockManager from servicemanager import ServiceManager diff --git a/openlp/core/ui/aboutdialog.py b/openlp/core/ui/aboutdialog.py index d973c5680..8216d3e7a 100644 --- a/openlp/core/ui/aboutdialog.py +++ b/openlp/core/ui/aboutdialog.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, Armin Köhler, 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 # @@ -87,7 +88,7 @@ class Ui_AboutDialog(object): QtCore.QMetaObject.connectSlotsByName(aboutDialog) def retranslateUi(self, aboutDialog): - aboutDialog.setWindowTitle(u'%s OpenLP' % UiStrings.About) + aboutDialog.setWindowTitle(u'%s OpenLP' % UiStrings().About) self.aboutTextEdit.setPlainText(translate('OpenLP.AboutForm', 'OpenLP - Open Source Lyrics ' 'Projection\n' @@ -95,7 +96,7 @@ class Ui_AboutDialog(object): 'OpenLP is free church presentation software, or lyrics ' 'projection software, used to display slides of songs, Bible ' 'verses, videos, images, and even presentations (if ' - 'OpenOffice.org, PowerPoint or PowerPoint Viewer is installed) ' + 'Impress, PowerPoint or PowerPoint Viewer is installed) ' 'for church worship using a computer and a data projector.\n' '\n' 'Find out more about OpenLP: http://openlp.org/\n' @@ -105,39 +106,42 @@ class Ui_AboutDialog(object): 'consider contributing by using the button below.' )) self.aboutNotebook.setTabText( - self.aboutNotebook.indexOf(self.aboutTab), UiStrings.About) + self.aboutNotebook.indexOf(self.aboutTab), UiStrings().About) lead = u'Raoul "superfly" Snyman' developers = [u'Tim "TRB143" Bentley', u'Jonathan "gushie" Corwin', u'Michael "cocooncrash" Gorven', u'Andreas "googol" Preikschat', u'Raoul "superfly" Snyman', u'Martin "mijiti" Thompson', u'Jon "Meths" Tibble'] - contributors = [u'Scott "sguerrieri" Guerrieri', - u'Meinert "m2j" Jordan', u'Armin "orangeshirt" K\xf6hler', + contributors = [u'Gerald "jerryb" Britton', + u'Scott "sguerrieri" Guerrieri', + u'Matthias "matthub" Hub', u'Meinert "m2j" Jordan', + u'Armin "orangeshirt" K\xf6hler', u'Joshua "milleja46" Miller', + u'Stevan "ElderP" Pettit', u'Mattias "mahfiaz" P\xf5ldaru', u'Christian "crichter" Richter', u'Philip "Phill" Ridout', - u'Jeffrey "whydoubt" Smith', u'Maikel Stuivenberg', - u'Carsten "catini" Tingaard', u'Frode "frodus" Woldsund'] + u'Simon "samscudder" Scudder', u'Jeffrey "whydoubt" Smith', + u'Maikel Stuivenberg', u'Frode "frodus" Woldsund'] testers = [u'Philip "Phill" Ridout', u'Wesley "wrst" Stout', u'John "jseagull1" Cegalis (lead)'] packagers = ['Thomas "tabthorpe" Abthorpe (FreeBSD)', - u'Tim "TRB143" Bentley (Fedora)', - u'Michael "cocooncrash" Gorven (Ubuntu)', + u'Tim "TRB143" Bentley (Fedora and Android)', u'Matthias "matthub" Hub (Mac OS X)', - u'Raoul "superfly" Snyman (Windows, Ubuntu)'] + u'Stevan "ElderP" Pettit (Windows)', + u'Raoul "superfly" Snyman (Ubuntu)'] translators = { u'af': [u'Johan "nuvolari" Mynhardt'], u'de': [u'Patrick "madmuffin" Br\xfcckner', - u'Meinert "m2j" Jordan', - u'Andreas "googol" Preikschat', + u'Meinert "m2j" Jordan', u'Andreas "googol" Preikschat', u'Christian "crichter" Richter'], u'en_GB': [u'Tim "TRB143" Bentley', u'Jonathan "gushie" Corwin'], u'en_ZA': [u'Raoul "superfly" Snyman'], u'et': [u'Mattias "mahfiaz" P\xf5ldaru'], u'fr': [u'Stephan\xe9 "stbrunner" Brunner'], - u'hu': [u'Gyuris Gellért'], + u'hu': [u'Gyuris Gell\xe9rt'], u'ja': [u'Kunio "Kunio" Nakamaru'], u'nb': [u'Atle "pendlaren" Weibell', u'Frode "frodus" Woldsund'], u'nl': [u'Arjen "typovar" van Voorst'], - u'pt_BR': [u'Rafael "rafaellerm" Lerm'], + u'pt_BR': [u'Rafael "rafaellerm" Lerm', u'Gustavo Bim', + u'Simon "samscudder" Scudder'], u'ru': [u'Sergey "ratz" Ratz'] } documentors = [u'Wesley "wrst" Stout', @@ -221,13 +225,15 @@ class Ui_AboutDialog(object): self.aboutNotebook.setTabText( self.aboutNotebook.indexOf(self.creditsTab), translate('OpenLP.AboutForm', 'Credits')) - copyright = translate('OpenLP.AboutForm', - 'Copyright \xa9 2004-2011 Raoul Snyman\n' - 'Portions copyright \xa9 2004-2011 ' - 'Tim Bentley, Jonathan Corwin, Michael Gorven, Scott Guerrieri,\n' - 'Meinert Jordan, Andreas Preikschat, Christian Richter, Philip\n' - 'Ridout, Maikel Stuivenberg, Martin Thompson, Jon Tibble, Carstenn' - 'Tinggaard, Frode Woldsund') + copyright = unicode(translate('OpenLP.AboutForm', + 'Copyright \xa9 2004-2011 %s\n' + 'Portions copyright \xa9 2004-2011 %s')) % (u'Raoul Snyman', + u'Tim Bentley, Jonathan Corwin, Michael Gorven, Gerald Britton, ' + u'Scott Guerrieri, Matthias Hub, Meinert Jordan, Armin K\xf6hler, ' + u'Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias ' + u'P\xf5ldaru, Christian Richter, Philip Ridout, Simon Scudder, ' + u'Jeffrey Smith, Maikel Stuivenberg, Martin Thompson, Jon Tibble, ' + u'Frode Woldsund') licence = translate('OpenLP.AboutForm', 'This program is free software; you can redistribute it and/or ' 'modify it under the terms of the GNU General Public License as ' diff --git a/openlp/core/ui/aboutform.py b/openlp/core/ui/aboutform.py index a6550b23c..4e031656c 100644 --- a/openlp/core/ui/aboutform.py +++ b/openlp/core/ui/aboutform.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, Armin Köhler, 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,25 +29,26 @@ from PyQt4 import QtCore, QtGui from aboutdialog import Ui_AboutDialog from openlp.core.lib import translate +from openlp.core.utils import get_application_version class AboutForm(QtGui.QDialog, Ui_AboutDialog): """ The About dialog """ - def __init__(self, parent, applicationVersion): + def __init__(self, parent): """ Do some initialisation stuff """ QtGui.QDialog.__init__(self, parent) - self.applicationVersion = applicationVersion + applicationVersion = get_application_version() self.setupUi(self) about_text = self.aboutTextEdit.toPlainText() about_text = about_text.replace(u'', - self.applicationVersion[u'version']) - if self.applicationVersion[u'build']: + applicationVersion[u'version']) + if applicationVersion[u'build']: build_text = unicode(translate('OpenLP.AboutForm', ' build %s')) % \ - self.applicationVersion[u'build'] + applicationVersion[u'build'] else: build_text = u'' about_text = about_text.replace(u'', build_text) @@ -59,6 +61,5 @@ class AboutForm(QtGui.QDialog, Ui_AboutDialog): Launch a web browser and go to the contribute page on the site. """ import webbrowser - url = u'http://www.openlp.org/en/documentation/introduction/' \ - + u'contributing.html' + url = u'http://openlp.org/en/documentation/introduction/contributing' webbrowser.open_new(url) diff --git a/openlp/core/ui/advancedtab.py b/openlp/core/ui/advancedtab.py index a8e8b294e..43e2a9915 100644 --- a/openlp/core/ui/advancedtab.py +++ b/openlp/core/ui/advancedtab.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, Armin Köhler, 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,13 +38,15 @@ class AdvancedTab(SettingsTab): The :class:`AdvancedTab` manages the advanced settings tab including the UI and the loading and saving of the displayed settings. """ - def __init__(self): + def __init__(self, parent): """ Initialise the settings tab """ - SettingsTab.__init__(self, u'Advanced') + advancedTranslated = translate('OpenLP.AdvancedTab', 'Advanced') self.default_image = u':/graphics/openlp-splash-screen.png' self.default_color = u'#ffffff' + self.icon_path = u':/system/system_settings.png' + SettingsTab.__init__(self, parent, u'Advanced', advancedTranslated) def setupUi(self): """ @@ -67,6 +70,10 @@ class AdvancedTab(SettingsTab): self.doubleClickLiveCheckBox = QtGui.QCheckBox(self.uiGroupBox) self.doubleClickLiveCheckBox.setObjectName(u'doubleClickLiveCheckBox') self.uiLayout.addRow(self.doubleClickLiveCheckBox) + self.singleClickPreviewCheckBox = QtGui.QCheckBox(self.uiGroupBox) + self.singleClickPreviewCheckBox.setObjectName( + u'singleClickPreviewCheckBox') + self.uiLayout.addRow(self.singleClickPreviewCheckBox) self.expandServiceItemCheckBox = QtGui.QCheckBox(self.uiGroupBox) self.expandServiceItemCheckBox.setObjectName( u'expandServiceItemCheckBox') @@ -76,14 +83,6 @@ class AdvancedTab(SettingsTab): u'enableAutoCloseCheckBox') self.uiLayout.addRow(self.enableAutoCloseCheckBox) self.leftLayout.addWidget(self.uiGroupBox) - self.hideMouseGroupBox = QtGui.QGroupBox(self.leftColumn) - self.hideMouseGroupBox.setObjectName(u'hideMouseGroupBox') - self.hideMouseLayout = QtGui.QVBoxLayout(self.hideMouseGroupBox) - self.hideMouseLayout.setObjectName(u'hideMouseLayout') - self.hideMouseCheckBox = QtGui.QCheckBox(self.hideMouseGroupBox) - self.hideMouseCheckBox.setObjectName(u'hideMouseCheckBox') - self.hideMouseLayout.addWidget(self.hideMouseCheckBox) - self.leftLayout.addWidget(self.hideMouseGroupBox) self.leftLayout.addStretch() self.defaultImageGroupBox = QtGui.QGroupBox(self.rightColumn) self.defaultImageGroupBox.setObjectName(u'defaultImageGroupBox') @@ -103,26 +102,42 @@ class AdvancedTab(SettingsTab): self.defaultBrowseButton.setObjectName(u'defaultBrowseButton') self.defaultBrowseButton.setIcon( build_icon(u':/general/general_open.png')) + self.defaultRevertButton = QtGui.QToolButton(self.defaultImageGroupBox) + self.defaultRevertButton.setObjectName(u'defaultRevertButton') + self.defaultRevertButton.setIcon( + build_icon(u':/general/general_revert.png')) self.defaultFileLayout = QtGui.QHBoxLayout() self.defaultFileLayout.setObjectName(u'defaultFileLayout') self.defaultFileLayout.addWidget(self.defaultFileEdit) self.defaultFileLayout.addWidget(self.defaultBrowseButton) + self.defaultFileLayout.addWidget(self.defaultRevertButton) self.defaultImageLayout.addRow(self.defaultFileLabel, self.defaultFileLayout) self.rightLayout.addWidget(self.defaultImageGroupBox) + self.hideMouseGroupBox = QtGui.QGroupBox(self.leftColumn) + self.hideMouseGroupBox.setObjectName(u'hideMouseGroupBox') + self.hideMouseLayout = QtGui.QVBoxLayout(self.hideMouseGroupBox) + self.hideMouseLayout.setObjectName(u'hideMouseLayout') + self.hideMouseCheckBox = QtGui.QCheckBox(self.hideMouseGroupBox) + self.hideMouseCheckBox.setObjectName(u'hideMouseCheckBox') + self.hideMouseLayout.addWidget(self.hideMouseCheckBox) + self.rightLayout.addWidget(self.hideMouseGroupBox) self.rightLayout.addStretch() QtCore.QObject.connect(self.defaultColorButton, QtCore.SIGNAL(u'pressed()'), self.onDefaultColorButtonPressed) QtCore.QObject.connect(self.defaultBrowseButton, QtCore.SIGNAL(u'pressed()'), self.onDefaultBrowseButtonPressed) + QtCore.QObject.connect(self.defaultRevertButton, + QtCore.SIGNAL(u'pressed()'), self.onDefaultRevertButtonPressed) def retranslateUi(self): """ Setup the interface translation strings. """ - self.tabTitleVisible = UiStrings.Advanced - self.uiGroupBox.setTitle(translate('OpenLP.AdvancedTab', 'UI Settings')) + self.tabTitleVisible = UiStrings().Advanced + self.uiGroupBox.setTitle( + translate('OpenLP.AdvancedTab', 'UI Settings')) self.recentLabel.setText( translate('OpenLP.AdvancedTab', 'Number of recent files to display:')) @@ -130,6 +145,8 @@ class AdvancedTab(SettingsTab): 'Remember active media manager tab on startup')) self.doubleClickLiveCheckBox.setText(translate('OpenLP.AdvancedTab', 'Double-click to send items straight to live')) + self.singleClickPreviewCheckBox.setText(translate('OpenLP.AdvancedTab', + 'Preview items when clicked in Media Manager')) self.expandServiceItemCheckBox.setText(translate('OpenLP.AdvancedTab', 'Expand new service items on creation')) self.enableAutoCloseCheckBox.setText(translate('OpenLP.AdvancedTab', @@ -142,8 +159,14 @@ class AdvancedTab(SettingsTab): 'Default Image')) self.defaultColorLabel.setText(translate('OpenLP.AdvancedTab', 'Background color:')) + self.defaultColorButton.setToolTip(translate('OpenLP.AdvancedTab', + 'Click to select a color.')) self.defaultFileLabel.setText(translate('OpenLP.AdvancedTab', 'Image file:')) + self.defaultBrowseButton.setToolTip(translate('OpenLP.AdvancedTab', + 'Browse for an image file to display.')) + self.defaultRevertButton.setToolTip(translate('OpenLP.AdvancedTab', + 'Revert to the default OpenLP logo.')) def load(self): """ @@ -164,6 +187,9 @@ class AdvancedTab(SettingsTab): self.doubleClickLiveCheckBox.setChecked( settings.value(u'double click live', QtCore.QVariant(False)).toBool()) + self.singleClickPreviewCheckBox.setChecked( + settings.value(u'single click preview', + QtCore.QVariant(False)).toBool()) self.expandServiceItemCheckBox.setChecked( settings.value(u'expand service item', QtCore.QVariant(False)).toBool()) @@ -193,6 +219,8 @@ class AdvancedTab(SettingsTab): QtCore.QVariant(self.mediaPluginCheckBox.isChecked())) settings.setValue(u'double click live', QtCore.QVariant(self.doubleClickLiveCheckBox.isChecked())) + settings.setValue(u'single click preview', + QtCore.QVariant(self.singleClickPreviewCheckBox.isChecked())) settings.setValue(u'expand service item', QtCore.QVariant(self.expandServiceItemCheckBox.isChecked())) settings.setValue(u'enable exit confirmation', @@ -213,10 +241,14 @@ class AdvancedTab(SettingsTab): def onDefaultBrowseButtonPressed(self): file_filters = u'%s;;%s (*.*) (*)' % (get_images_filter(), - UiStrings.AllFiles) + UiStrings().AllFiles) filename = QtGui.QFileDialog.getOpenFileName(self, translate('OpenLP.AdvancedTab', 'Open File'), '', file_filters) if filename: self.defaultFileEdit.setText(filename) self.defaultFileEdit.setFocus() + + def onDefaultRevertButtonPressed(self): + self.defaultFileEdit.setText(u':/graphics/openlp-splash-screen.png') + self.defaultFileEdit.setFocus() diff --git a/openlp/core/ui/exceptiondialog.py b/openlp/core/ui/exceptiondialog.py index 770913b01..cbb3fb50c 100644 --- a/openlp/core/ui/exceptiondialog.py +++ b/openlp/core/ui/exceptiondialog.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, Armin Köhler, 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 # diff --git a/openlp/core/ui/exceptionform.py b/openlp/core/ui/exceptionform.py index 6fb2d3f84..0cda4bd60 100644 --- a/openlp/core/ui/exceptionform.py +++ b/openlp/core/ui/exceptionform.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, Armin Köhler, 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 # @@ -38,6 +39,11 @@ try: PHONON_VERSION = Phonon.phononVersion() except ImportError: PHONON_VERSION = u'-' +try: + import migrate + MIGRATE_VERSION = getattr(migrate, u'__version__', u'< 0.7') +except ImportError: + MIGRATE_VERSION = u'-' try: import chardet CHARDET_VERSION = chardet.__version__ @@ -53,10 +59,28 @@ try: SQLITE_VERSION = sqlite.version except ImportError: SQLITE_VERSION = u'-' +try: + import mako + MAKO_VERSION = mako.__version__ +except ImportError: + MAKO_VERSION = u'-' +try: + import uno + arg = uno.createUnoStruct(u'com.sun.star.beans.PropertyValue') + arg.Name = u'nodepath' + arg.Value = u'/org.openoffice.Setup/Product' + context = uno.getComponentContext() + provider = context.ServiceManager.createInstance( + u'com.sun.star.configuration.ConfigurationProvider') + node = provider.createInstanceWithArguments( + u'com.sun.star.configuration.ConfigurationAccess', (arg,)) + UNO_VERSION = node.getByName(u'ooSetupVersion') +except ImportError: + UNO_VERSION = u'-' from openlp.core.lib import translate, SettingsManager -from openlp.core.lib.mailto import mailto from openlp.core.lib.ui import UiStrings +from openlp.core.utils import get_application_version from exceptiondialog import Ui_ExceptionDialog @@ -78,7 +102,7 @@ class ExceptionForm(QtGui.QDialog, Ui_ExceptionDialog): return QtGui.QDialog.exec_(self) def _createReport(self): - openlp_version = self.parent().applicationVersion[u'full'] + openlp_version = get_application_version() description = unicode(self.descriptionTextEdit.toPlainText()) traceback = unicode(self.exceptionTextEdit.toPlainText()) system = unicode(translate('OpenLP.ExceptionForm', @@ -88,11 +112,14 @@ class ExceptionForm(QtGui.QDialog, Ui_ExceptionDialog): u'Phonon: %s\n' % PHONON_VERSION + \ u'PyQt4: %s\n' % Qt.PYQT_VERSION_STR + \ u'SQLAlchemy: %s\n' % sqlalchemy.__version__ + \ + u'SQLAlchemy Migrate: %s\n' % MIGRATE_VERSION + \ u'BeautifulSoup: %s\n' % BeautifulSoup.__version__ + \ u'lxml: %s\n' % etree.__version__ + \ u'Chardet: %s\n' % CHARDET_VERSION + \ u'PyEnchant: %s\n' % ENCHANT_VERSION + \ - u'PySQLite: %s\n' % SQLITE_VERSION + u'PySQLite: %s\n' % SQLITE_VERSION + \ + u'Mako: %s\n' % MAKO_VERSION + \ + u'pyUNO bridge: %s\n' % UNO_VERSION if platform.system() == u'Linux': if os.environ.get(u'KDE_FULL_SESSION') == u'true': system = system + u'Desktop: KDE SC\n' @@ -104,7 +131,7 @@ class ExceptionForm(QtGui.QDialog, Ui_ExceptionDialog): """ Saving exception log and system informations to a file. """ - report = unicode(translate('OpenLP.ExceptionForm', + report_text = unicode(translate('OpenLP.ExceptionForm', '**OpenLP Bug Report**\n' 'Version: %s\n\n' '--- Details of the Exception. ---\n\n%s\n\n ' @@ -120,18 +147,21 @@ class ExceptionForm(QtGui.QDialog, Ui_ExceptionDialog): filename = unicode(QtCore.QDir.toNativeSeparators(filename)) SettingsManager.set_last_dir(self.settingsSection, os.path.dirname( filename)) - report = report % self._createReport() + report_text = report_text % self._createReport() try: - file = open(filename, u'w') + report_file = open(filename, u'w') try: - file.write(report) + report_file.write(report_text) except UnicodeError: - file.close() - file = open(filename, u'wb') - file.write(report.encode(u'utf-8')) - file.close() + report_file.close() + report_file = open(filename, u'wb') + report_file.write(report_text.encode(u'utf-8')) + finally: + report_file.close() except IOError: log.exception(u'Failed to write crash report') + finally: + report_file.close() def onSendReportButtonPressed(self): """ @@ -148,18 +178,20 @@ class ExceptionForm(QtGui.QDialog, Ui_ExceptionDialog): 'Please add the information that bug reports are favoured written ' 'in English.')) content = self._createReport() + source = u'' + exception = u'' for line in content[2].split(u'\n'): if re.search(r'[/\\]openlp[/\\]', line): source = re.sub(r'.*[/\\]openlp[/\\](.*)".*', r'\1', line) if u':' in line: exception = line.split(u'\n')[-1].split(u':')[0] subject = u'Bug report: %s in %s' % (exception, source) + mailto_url = QtCore.QUrl(u'mailto:bugs@openlp.org') + mailto_url.addQueryItem(u'subject', subject) + mailto_url.addQueryItem(u'body', body % content) if self.fileAttachment: - mailto(address=u'bugs@openlp.org', subject=subject, - body=body % content, attach=self.fileAttachment) - else: - mailto(address=u'bugs@openlp.org', subject=subject, - body=body % content) + mailto_url.addQueryItem(u'attach', self.fileAttachment) + QtGui.QDesktopServices.openUrl(mailto_url) def onDescriptionUpdated(self): count = int(20 - len(self.descriptionTextEdit.toPlainText())) @@ -177,7 +209,7 @@ class ExceptionForm(QtGui.QDialog, Ui_ExceptionDialog): self,translate('ImagePlugin.ExceptionDialog', 'Select Attachment'), SettingsManager.get_last_dir(u'exceptions'), - u'%s (*.*) (*)' % UiStrings.AllFiles) + u'%s (*.*) (*)' % UiStrings().AllFiles) log.info(u'New files(s) %s', unicode(files)) if files: self.fileAttachment = unicode(files) @@ -185,3 +217,4 @@ class ExceptionForm(QtGui.QDialog, Ui_ExceptionDialog): def __buttonState(self, state): self.saveReportButton.setEnabled(state) self.sendReportButton.setEnabled(state) + diff --git a/openlp/core/ui/filerenamedialog.py b/openlp/core/ui/filerenamedialog.py index 30c206d2c..6611a9df7 100644 --- a/openlp/core/ui/filerenamedialog.py +++ b/openlp/core/ui/filerenamedialog.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, Armin Köhler, 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 # diff --git a/openlp/core/ui/filerenameform.py b/openlp/core/ui/filerenameform.py index 69a1c3f6a..0bdbdf892 100644 --- a/openlp/core/ui/filerenameform.py +++ b/openlp/core/ui/filerenameform.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, Armin Köhler, 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 # diff --git a/openlp/core/ui/firsttimeform.py b/openlp/core/ui/firsttimeform.py index 88cd7a5cc..bfa4bf6b1 100644 --- a/openlp/core/ui/firsttimeform.py +++ b/openlp/core/ui/firsttimeform.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, Armin Köhler, 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 # @@ -24,19 +25,21 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### -import ConfigParser import io import logging import os +import sys import urllib +import urllib2 +from tempfile import gettempdir +from ConfigParser import SafeConfigParser from PyQt4 import QtCore, QtGui -from firsttimewizard import Ui_FirstTimeWizard - -from openlp.core.lib import translate, PluginStatus, check_directory_exists, \ - Receiver +from openlp.core.lib import translate, PluginStatus, Receiver, build_icon, \ + check_directory_exists from openlp.core.utils import get_web_page, AppLocation +from firsttimewizard import Ui_FirstTimeWizard, FirstTimePage log = logging.getLogger(__name__) @@ -48,27 +51,28 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard): log.info(u'ThemeWizardForm loaded') def __init__(self, screens, parent=None): + QtGui.QWizard.__init__(self, parent) + self.setupUi(self) + self.screens = screens # check to see if we have web access self.web = u'http://openlp.org/files/frw/' - self.config = ConfigParser.ConfigParser() + self.config = SafeConfigParser() self.webAccess = get_web_page(u'%s%s' % (self.web, u'download.cfg')) if self.webAccess: files = self.webAccess.read() self.config.readfp(io.BytesIO(files)) - QtGui.QWizard.__init__(self, parent) - self.setupUi(self) - for screen in screens.get_screen_list(): - self.displaySelectionComboBox.addItem(screen) - self.songsText = translate('OpenLP.FirstTimeWizard', 'Songs') - self.biblesText = translate('OpenLP.FirstTimeWizard', 'Bibles') - self.themesText = translate('OpenLP.FirstTimeWizard', 'Themes') - self.startUpdates = translate('OpenLP.FirstTimeWizard', - 'Starting Updates') + self.updateScreenListCombo() + self.downloadCanceled = False self.downloading = unicode(translate('OpenLP.FirstTimeWizard', - 'Downloading %s')) + 'Downloading %s...')) + QtCore.QObject.connect(self.cancelButton,QtCore.SIGNAL('clicked()'), + self.onCancelButtonClicked) + QtCore.QObject.connect(self.noInternetFinishButton, + QtCore.SIGNAL('clicked()'), self.onNoInternetFinishButtonClicked) QtCore.QObject.connect(self, - QtCore.SIGNAL(u'currentIdChanged(int)'), - self.onCurrentIdChanged) + QtCore.SIGNAL(u'currentIdChanged(int)'), self.onCurrentIdChanged) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'config_screen_changed'), self.updateScreenListCombo) def exec_(self, edit=False): """ @@ -82,136 +86,347 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard): Set up display at start of theme edit. """ self.restart() + check_directory_exists(os.path.join(gettempdir(), u'openlp')) + self.noInternetFinishButton.setVisible(False) + # Check if this is a re-run of the wizard. + self.hasRunWizard = QtCore.QSettings().value( + u'general/has run wizard', QtCore.QVariant(False)).toBool() # Sort out internet access for downloads if self.webAccess: - self.internetGroupBox.setVisible(True) - self.noInternetLabel.setVisible(False) - # If songs database exists do not allow a copy - songs = os.path.join(AppLocation.get_section_data_path(u'songs'), - u'songs.sqlite') - if not os.path.exists(songs): - treewidgetitem = QtGui.QTreeWidgetItem(self.selectionTreeWidget) - treewidgetitem.setText(0, self.songsText) - self._loadChild(treewidgetitem, u'songs', u'languages', u'songs') - treewidgetitem = QtGui.QTreeWidgetItem(self.selectionTreeWidget) - treewidgetitem.setText(0, self.biblesText) - self._loadChild(treewidgetitem, u'bibles', u'translations', - u'bible') - treewidgetitem = QtGui.QTreeWidgetItem(self.selectionTreeWidget) - treewidgetitem.setText(0, self.themesText) - self._loadChild(treewidgetitem, u'themes', u'files', 'theme') - else: - self.internetGroupBox.setVisible(False) - self.noInternetLabel.setVisible(True) + songs = self.config.get(u'songs', u'languages') + songs = songs.split(u',') + for song in songs: + title = unicode(self.config.get( + u'songs_%s' % song, u'title'), u'utf8') + filename = unicode(self.config.get( + u'songs_%s' % song, u'filename'), u'utf8') + item = QtGui.QListWidgetItem(title, self.songsListWidget) + item.setData(QtCore.Qt.UserRole, QtCore.QVariant(filename)) + item.setCheckState(QtCore.Qt.Unchecked) + item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable) + bible_languages = self.config.get(u'bibles', u'languages') + bible_languages = bible_languages.split(u',') + for lang in bible_languages: + language = unicode(self.config.get( + u'bibles_%s' % lang, u'title'), u'utf8') + langItem = QtGui.QTreeWidgetItem( + self.biblesTreeWidget, QtCore.QStringList(language)) + bibles = self.config.get(u'bibles_%s' % lang, u'translations') + bibles = bibles.split(u',') + for bible in bibles: + title = unicode(self.config.get( + u'bible_%s' % bible, u'title'), u'utf8') + filename = unicode(self.config.get( + u'bible_%s' % bible, u'filename')) + item = QtGui.QTreeWidgetItem( + langItem, QtCore.QStringList(title)) + item.setData(0, QtCore.Qt.UserRole, + QtCore.QVariant(filename)) + item.setCheckState(0, QtCore.Qt.Unchecked) + item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable) + self.biblesTreeWidget.expandAll() + themes = self.config.get(u'themes', u'files') + themes = themes.split(u',') + for theme in themes: + title = self.config.get(u'theme_%s' % theme, u'title') + filename = self.config.get(u'theme_%s' % theme, u'filename') + screenshot = self.config.get(u'theme_%s' % theme, u'screenshot') + urllib.urlretrieve(u'%s%s' % (self.web, screenshot), + os.path.join(gettempdir(), u'openlp', screenshot)) + item = QtGui.QListWidgetItem(title, self.themesListWidget) + item.setData(QtCore.Qt.UserRole, + QtCore.QVariant(filename)) + item.setIcon(build_icon( + os.path.join(gettempdir(), u'openlp', screenshot))) + item.setCheckState(QtCore.Qt.Unchecked) + item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable) + Receiver.send_message(u'cursor_normal') - def _loadChild(self, tree, list, tag, root): - files = self.config.get(list, tag) - files = files.split(u',') - for file in files: - if file: - child = QtGui.QTreeWidgetItem(tree) - child.setText(0, self.config.get(u'%s_%s' - % (root, file), u'title')) - child.setData(0, QtCore.Qt.UserRole, - QtCore.QVariant(self.config.get(u'%s_%s' - % (root, file), u'filename'))) - child.setCheckState(0, QtCore.Qt.Unchecked) - child.setFlags(QtCore.Qt.ItemIsUserCheckable | - QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + def nextId(self): + """ + Determine the next page in the Wizard to go to. + """ + Receiver.send_message(u'openlp_process_events') + if self.currentId() == FirstTimePage.Plugins: + if not self.webAccess: + return FirstTimePage.NoInternet + else: + return FirstTimePage.Songs + elif self.currentId() == FirstTimePage.Progress: + return -1 + elif self.currentId() == FirstTimePage.NoInternet: + return FirstTimePage.Progress + else: + return self.currentId() + 1 def onCurrentIdChanged(self, pageId): """ Detects Page changes and updates as approprate. """ - if self.page(pageId) == self.DefaultsPage: - self.themeSelectionComboBox.clear() - listIterator = QtGui.QTreeWidgetItemIterator( - self.selectionTreeWidget) - while listIterator.value(): - parent = listIterator.value().parent() - if parent and listIterator.value().checkState(0) \ - == QtCore.Qt.Checked: - if unicode(parent.text(0)) == self.themesText: - self.themeSelectionComboBox.addItem( - listIterator.value().text(0)) - listIterator += 1 + # Keep track of the page we are at. Pressing "Cancel" causes pageId + # to be a -1. + if pageId != -1: + self.lastId = pageId + if pageId == FirstTimePage.Plugins: + # Set the no internet page text. + if self.hasRunWizard: + self.noInternetLabel.setText(self.noInternetText) + else: + self.noInternetLabel.setText(self.noInternetText + + self.cancelWizardText) + elif pageId == FirstTimePage.Defaults: + self.themeComboBox.clear() + for iter in xrange(self.themesListWidget.count()): + item = self.themesListWidget.item(iter) + if item.checkState() == QtCore.Qt.Checked: + self.themeComboBox.addItem(item.text()) + if self.hasRunWizard: + # Add any existing themes to list. + for theme in self.parent().themeManagerContents.getThemes(): + index = self.themeComboBox.findText(theme) + if index == -1: + self.themeComboBox.addItem(theme) + default_theme = unicode(QtCore.QSettings().value( + u'themes/global theme', + QtCore.QVariant(u'')).toString()) + # Pre-select the current default theme. + index = self.themeComboBox.findText(default_theme) + self.themeComboBox.setCurrentIndex(index) + elif pageId == FirstTimePage.NoInternet: + self.backButton.setVisible(False) + self.nextButton.setVisible(False) + self.noInternetFinishButton.setVisible(True) + if self.hasRunWizard: + self.cancelButton.setVisible(False) + elif pageId == FirstTimePage.Progress: + Receiver.send_message(u'cursor_busy') + self._preWizard() + Receiver.send_message(u'openlp_process_events') + self._performWizard() + Receiver.send_message(u'openlp_process_events') + self._postWizard() + Receiver.send_message(u'cursor_normal') + Receiver.send_message(u'openlp_process_events') - def accept(self): - Receiver.send_message(u'cursor_busy') - self._updateMessage(self.startUpdates) - # Set up the Plugin status's - self._pluginStatus(self.songsCheckBox, u'songs/status') - self._pluginStatus(self.bibleCheckBox, u'bibles/status') - self._pluginStatus(self.presentationCheckBox, u'presentations/status') - self._pluginStatus(self.imageCheckBox, u'images/status') - self._pluginStatus(self.mediaCheckBox, u'media/status') - self._pluginStatus(self.remoteCheckBox, u'remotes/status') - self._pluginStatus(self.customCheckBox, u'custom/status') - self._pluginStatus(self.songUsageCheckBox, u'songusage/status') - self._pluginStatus(self.alertCheckBox, u'alerts/status') - # Build directories for downloads - songsDestination = AppLocation.get_section_data_path(u'songs') - check_directory_exists(songsDestination) - bibleDestination = AppLocation.get_section_data_path(u'bibles') - check_directory_exists(bibleDestination) - themeDestination = AppLocation.get_section_data_path(u'themes') - check_directory_exists(themeDestination) - # Install Selected Items looping through them - listIterator = QtGui.QTreeWidgetItemIterator(self.selectionTreeWidget) - while listIterator.value(): - type = listIterator.value().parent() - if listIterator.value().parent(): - if listIterator.value().checkState(0) == QtCore.Qt.Checked: - # Install items as theu have been selected - item = unicode(listIterator.value().text(0)) - # Download Song database if selected - if unicode(type.text(0)) == self.songsText: - songs = unicode(listIterator.value().data(0, - QtCore.Qt.UserRole).toString()) - message = self.downloading % item - self._updateMessage(message) - # Song database is a fixed file name - urllib.urlretrieve(u'%s%s' % (self.web, songs), - os.path.join(songsDestination, u'songs.sqlite')) - # Download and selected Bibles - if unicode(type.text(0)) == self.biblesText: - bible = unicode(listIterator.value().data(0, - QtCore.Qt.UserRole).toString()) - message = self.downloading % item - self._updateMessage(message) - urllib.urlretrieve(u'%s%s' % (self.web, bible), - os.path.join(bibleDestination, bible)) - # Download any themes - if unicode(type.text(0)) == self.themesText: - theme = unicode(listIterator.value().data(0, - QtCore.Qt.UserRole).toString()) - message = self.downloading % item - self._updateMessage(message) - urllib.urlretrieve(u'%s%s' % (self.web, theme), - os.path.join(themeDestination, theme)) - listIterator += 1 - # Set Default Display - if self.displaySelectionComboBox.currentIndex() != -1: - QtCore.QSettings().setValue(u'General/monitor', - QtCore.QVariant(self.displaySelectionComboBox. - currentIndex())) - # Set Global Theme - if self.themeSelectionComboBox.currentIndex() != -1: - QtCore.QSettings().setValue(u'themes/global theme', - QtCore.QVariant(self.themeSelectionComboBox.currentText())) - QtCore.QSettings().setValue(u'general/first time', - QtCore.QVariant(False)) + def updateScreenListCombo(self): + """ + The user changed screen resolution or enabled/disabled more screens, so + we need to update the combo box. + """ + self.displayComboBox.clear() + self.displayComboBox.addItems(self.screens.get_screen_list()) + self.displayComboBox.setCurrentIndex(self.displayComboBox.count() - 1) + + def onCancelButtonClicked(self): + """ + Process the pressing of the cancel button. + """ + if self.lastId == FirstTimePage.NoInternet or \ + (self.lastId <= FirstTimePage.Plugins and \ + not self.hasRunWizard): + QtCore.QCoreApplication.exit() + sys.exit() + self.downloadCanceled = True Receiver.send_message(u'cursor_normal') - return QtGui.QWizard.accept(self) - def _pluginStatus(self, field, tag): + def onNoInternetFinishButtonClicked(self): + """ + Process the pressing of the "Finish" button on the No Internet page. + """ + Receiver.send_message(u'cursor_busy') + self._performWizard() + Receiver.send_message(u'openlp_process_events') + Receiver.send_message(u'cursor_normal') + QtCore.QSettings().setValue(u'general/has run wizard', + QtCore.QVariant(True)) + self.close() + + def urlGetFile(self, url, fpath): + """" + Download a file given a URL. The file is retrieved in chunks, giving + the ability to cancel the download at any point. + """ + block_count = 0 + block_size = 4096 + urlfile = urllib2.urlopen(url) + filesize = urlfile.headers["Content-Length"] + filename = open(fpath, "wb") + # Download until finished or canceled. + while not self.downloadCanceled: + data = urlfile.read(block_size) + if not data: + break + filename.write(data) + block_count += 1 + self._downloadProgress(block_count, block_size, filesize) + filename.close() + # Delete file if canceled, it may be a partial file. + if self.downloadCanceled: + os.remove(fpath) + + def _getFileSize(self, url): + site = urllib.urlopen(url) + meta = site.info() + return int(meta.getheaders("Content-Length")[0]) + + def _downloadProgress(self, count, block_size, total_size): + increment = (count * block_size) - self.previous_size + self._incrementProgressBar(None, increment) + self.previous_size = count * block_size + + def _incrementProgressBar(self, status_text, increment=1): + """ + Update the wizard progress page. + + ``status_text`` + Current status information to display. + + ``increment`` + The value to increment the progress bar by. + """ + if status_text: + self.progressLabel.setText(status_text) + if increment > 0: + self.progressBar.setValue(self.progressBar.value() + increment) + Receiver.send_message(u'openlp_process_events') + + def _preWizard(self): + """ + Prepare the UI for the process. + """ + self.max_progress = 0 + self.finishButton.setVisible(False) + Receiver.send_message(u'openlp_process_events') + # Loop through the songs list and increase for each selected item + for i in xrange(self.songsListWidget.count()): + item = self.songsListWidget.item(i) + if item.checkState() == QtCore.Qt.Checked: + filename = item.data(QtCore.Qt.UserRole).toString() + size = self._getFileSize(u'%s%s' % (self.web, filename)) + self.max_progress += size + # Loop through the Bibles list and increase for each selected item + iterator = QtGui.QTreeWidgetItemIterator(self.biblesTreeWidget) + while iterator.value(): + item = iterator.value() + if item.parent() and item.checkState(0) == QtCore.Qt.Checked: + filename = item.data(0, QtCore.Qt.UserRole).toString() + size = self._getFileSize(u'%s%s' % (self.web, filename)) + self.max_progress += size + iterator += 1 + # Loop through the themes list and increase for each selected item + for i in xrange(self.themesListWidget.count()): + item = self.themesListWidget.item(i) + if item.checkState() == QtCore.Qt.Checked: + filename = item.data(QtCore.Qt.UserRole).toString() + size = self._getFileSize(u'%s%s' % (self.web, filename)) + self.max_progress += size + if self.max_progress: + # Add on 2 for plugins status setting plus a "finished" point. + self.max_progress = self.max_progress + 2 + self.progressBar.setValue(0) + self.progressBar.setMinimum(0) + self.progressBar.setMaximum(self.max_progress) + self.progressPage.setTitle(translate('OpenLP.FirstTimeWizard', + 'Setting Up And Downloading')) + self.progressPage.setSubTitle(translate('OpenLP.FirstTimeWizard', + 'Please wait while OpenLP is set up ' + 'and your data is downloaded.')) + else: + self.progressBar.setVisible(False) + self.progressPage.setTitle(translate('OpenLP.FirstTimeWizard', + 'Setting Up')) + self.progressPage.setSubTitle(u'Setup complete.') + + def _postWizard(self): + """ + Clean up the UI after the process has finished. + """ + if self.max_progress: + self.progressBar.setValue(self.progressBar.maximum()) + if self.hasRunWizard: + self.progressLabel.setText(translate('OpenLP.FirstTimeWizard', + 'Download complete.' + ' Click the finish button to return to OpenLP.')) + else: + self.progressLabel.setText(translate('OpenLP.FirstTimeWizard', + 'Download complete.' + ' Click the finish button to start OpenLP.')) + else: + if self.hasRunWizard: + self.progressLabel.setText(translate('OpenLP.FirstTimeWizard', + 'Click the finish button to return to OpenLP.')) + else: + self.progressLabel.setText(translate('OpenLP.FirstTimeWizard', + 'Click the finish button to start OpenLP.')) + self.finishButton.setVisible(True) + self.finishButton.setEnabled(True) + self.cancelButton.setVisible(False) + self.nextButton.setVisible(False) + Receiver.send_message(u'openlp_process_events') + + def _performWizard(self): + """ + Run the tasks in the wizard. + """ + # Set plugin states + self._incrementProgressBar(translate('OpenLP.FirstTimeWizard', + 'Enabling selected plugins...')) + self._setPluginStatus(self.songsCheckBox, u'songs/status') + self._setPluginStatus(self.bibleCheckBox, u'bibles/status') + self._setPluginStatus(self.presentationCheckBox, + u'presentations/status') + self._setPluginStatus(self.imageCheckBox, u'images/status') + self._setPluginStatus(self.mediaCheckBox, u'media/status') + self._setPluginStatus(self.remoteCheckBox, u'remotes/status') + self._setPluginStatus(self.customCheckBox, u'custom/status') + self._setPluginStatus(self.songUsageCheckBox, u'songusage/status') + self._setPluginStatus(self.alertCheckBox, u'alerts/status') + if self.webAccess: + # Build directories for downloads + songs_destination = os.path.join(unicode(gettempdir()), u'openlp') + bibles_destination = AppLocation.get_section_data_path(u'bibles') + themes_destination = AppLocation.get_section_data_path(u'themes') + # Download songs + for i in xrange(self.songsListWidget.count()): + item = self.songsListWidget.item(i) + if item.checkState() == QtCore.Qt.Checked: + filename = item.data(QtCore.Qt.UserRole).toString() + self._incrementProgressBar(self.downloading % filename, 0) + self.previous_size = 0 + destination = os.path.join(songs_destination, + unicode(filename)) + self.urlGetFile(u'%s%s' % (self.web, filename), destination) + # Download Bibles + bibles_iterator = QtGui.QTreeWidgetItemIterator( + self.biblesTreeWidget) + while bibles_iterator.value(): + item = bibles_iterator.value() + if item.parent() and item.checkState(0) == QtCore.Qt.Checked: + bible = unicode(item.data(0, QtCore.Qt.UserRole).toString()) + self._incrementProgressBar(self.downloading % bible, 0) + self.previous_size = 0 + self.urlGetFile(u'%s%s' % (self.web, bible), + os.path.join(bibles_destination, bible)) + bibles_iterator += 1 + # Download themes + for i in xrange(self.themesListWidget.count()): + item = self.themesListWidget.item(i) + if item.checkState() == QtCore.Qt.Checked: + theme = unicode(item.data(QtCore.Qt.UserRole).toString()) + self._incrementProgressBar(self.downloading % theme, 0) + self.previous_size = 0 + self.urlGetFile(u'%s%s' % (self.web, theme), + os.path.join(themes_destination, theme)) + # Set Default Display + if self.displayComboBox.currentIndex() != -1: + QtCore.QSettings().setValue(u'General/monitor', + QtCore.QVariant(self.displayComboBox.currentIndex())) + # Set Global Theme + if self.themeComboBox.currentIndex() != -1: + QtCore.QSettings().setValue(u'themes/global theme', + QtCore.QVariant(self.themeComboBox.currentText())) + + def _setPluginStatus(self, field, tag): status = PluginStatus.Active if field.checkState() \ == QtCore.Qt.Checked else PluginStatus.Inactive QtCore.QSettings().setValue(tag, QtCore.QVariant(status)) - - def _updateMessage(self, text): - """ - Keep screen up to date - """ - self.updateLabel.setText(text) - Receiver.send_message(u'openlp_process_events') diff --git a/openlp/core/ui/firsttimelanguagedialog.py b/openlp/core/ui/firsttimelanguagedialog.py index fbf817939..172fd3e0f 100644 --- a/openlp/core/ui/firsttimelanguagedialog.py +++ b/openlp/core/ui/firsttimelanguagedialog.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, Armin Köhler, 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,26 +31,38 @@ from openlp.core.lib import translate from openlp.core.lib.ui import create_accept_reject_button_box class Ui_FirstTimeLanguageDialog(object): - def setupUi(self, firstTimeLanguageDialog): - firstTimeLanguageDialog.setObjectName(u'firstTimeLanguageDialog') - firstTimeLanguageDialog.resize(300, 10) - self.dialogLayout = QtGui.QGridLayout(firstTimeLanguageDialog) + def setupUi(self, languageDialog): + languageDialog.setObjectName(u'languageDialog') + languageDialog.resize(300, 50) + self.dialogLayout = QtGui.QVBoxLayout(languageDialog) + self.dialogLayout.setContentsMargins(8, 8, 8, 8) + self.dialogLayout.setSpacing(8) self.dialogLayout.setObjectName(u'dialogLayout') - self.fileNameLabel = QtGui.QLabel(firstTimeLanguageDialog) - self.fileNameLabel.setObjectName(u'fileNameLabel') - self.dialogLayout.addWidget(self.fileNameLabel, 0, 0) - self.LanguageComboBox = QtGui.QComboBox(firstTimeLanguageDialog) - self.LanguageComboBox.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents) - self.LanguageComboBox.setObjectName("LanguageComboBox") - self.dialogLayout.addWidget(self.LanguageComboBox, 0, 1) - self.buttonBox = create_accept_reject_button_box(firstTimeLanguageDialog, True) - self.dialogLayout.addWidget(self.buttonBox, 1, 0, 1, 2) - self.retranslateUi(firstTimeLanguageDialog) - self.setMaximumHeight(self.sizeHint().height()) - QtCore.QMetaObject.connectSlotsByName(firstTimeLanguageDialog) + self.infoLabel = QtGui.QLabel(languageDialog) + self.infoLabel.setObjectName(u'infoLabel') + self.dialogLayout.addWidget(self.infoLabel) + self.languageLayout = QtGui.QHBoxLayout() + self.languageLayout.setObjectName(u'languageLayout') + self.languageLabel = QtGui.QLabel(languageDialog) + self.languageLabel.setObjectName(u'languageLabel') + self.languageLayout.addWidget(self.languageLabel) + self.languageComboBox = QtGui.QComboBox(languageDialog) + self.languageComboBox.setSizeAdjustPolicy( + QtGui.QComboBox.AdjustToContents) + self.languageComboBox.setObjectName("languageComboBox") + self.languageLayout.addWidget(self.languageComboBox) + self.dialogLayout.addLayout(self.languageLayout) + self.buttonBox = create_accept_reject_button_box(languageDialog, True) + self.dialogLayout.addWidget(self.buttonBox) - def retranslateUi(self, firstTimeLanguageDialog): + self.retranslateUi(languageDialog) + self.setMaximumHeight(self.sizeHint().height()) + QtCore.QMetaObject.connectSlotsByName(languageDialog) + + def retranslateUi(self, languageDialog): self.setWindowTitle(translate('OpenLP.FirstTimeLanguageForm', - 'Initial Set up Language')) - self.fileNameLabel.setText(translate('OpenLP.FirstTimeLanguageForm', - 'Initial Language:')) + 'Select Translation')) + self.infoLabel.setText(translate('OpenLP.FirstTimeLanguageForm', + 'Choose the translation you\'d like to use in OpenLP.')) + self.languageLabel.setText(translate('OpenLP.FirstTimeLanguageForm', + 'Translation:')) diff --git a/openlp/core/ui/firsttimelanguageform.py b/openlp/core/ui/firsttimelanguageform.py index 98489fde7..dcde212eb 100644 --- a/openlp/core/ui/firsttimelanguageform.py +++ b/openlp/core/ui/firsttimelanguageform.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, Armin Köhler, 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 # @@ -26,10 +27,8 @@ from PyQt4 import QtGui -from firsttimelanguagedialog import Ui_FirstTimeLanguageDialog - -from openlp.core.lib import translate from openlp.core.utils import LanguageManager +from firsttimelanguagedialog import Ui_FirstTimeLanguageDialog class FirstTimeLanguageForm(QtGui.QDialog, Ui_FirstTimeLanguageDialog): """ @@ -39,9 +38,9 @@ class FirstTimeLanguageForm(QtGui.QDialog, Ui_FirstTimeLanguageDialog): QtGui.QDialog.__init__(self, parent) self.setupUi(self) self.qmList = LanguageManager.get_qm_list() - self.LanguageComboBox.addItem(u'Automatic') + self.languageComboBox.addItem(u'Autodetect') for key in sorted(self.qmList.keys()): - self.LanguageComboBox.addItem(key) + self.languageComboBox.addItem(key) def exec_(self): """ @@ -51,13 +50,13 @@ class FirstTimeLanguageForm(QtGui.QDialog, Ui_FirstTimeLanguageDialog): def accept(self): # It's the first row so must be Automatic - if self.LanguageComboBox.currentIndex() == 0: + if self.languageComboBox.currentIndex() == 0: LanguageManager.auto_language = True LanguageManager.set_language(False, False) else: LanguageManager.auto_language = False action = QtGui.QAction(None) - action.setObjectName(unicode(self.LanguageComboBox.currentText())) + action.setObjectName(unicode(self.languageComboBox.currentText())) LanguageManager.set_language(action, False) return QtGui.QDialog.accept(self) diff --git a/openlp/core/ui/firsttimewizard.py b/openlp/core/ui/firsttimewizard.py index ee14b6b0e..0f152a396 100644 --- a/openlp/core/ui/firsttimewizard.py +++ b/openlp/core/ui/firsttimewizard.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, Armin Köhler, 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 # @@ -26,7 +27,20 @@ from PyQt4 import QtCore, QtGui +import sys + from openlp.core.lib import translate +from openlp.core.lib.ui import add_welcome_page + +class FirstTimePage(object): + Welcome = 0 + Plugins = 1 + NoInternet = 2 + Songs = 3 + Bibles = 4 + Themes = 5 + Defaults = 6 + Progress = 7 class Ui_FirstTimeWizard(object): @@ -35,144 +49,149 @@ class Ui_FirstTimeWizard(object): FirstTimeWizard.resize(550, 386) FirstTimeWizard.setModal(True) FirstTimeWizard.setWizardStyle(QtGui.QWizard.ModernStyle) - FirstTimeWizard.setOptions(QtGui.QWizard.IndependentPages| - QtGui.QWizard.NoBackButtonOnStartPage) - self.welcomePage = QtGui.QWizardPage() - self.welcomePage.setTitle(u'') - self.welcomePage.setSubTitle(u'') - self.welcomePage.setObjectName(u'welcomePage') - self.welcomeLayout = QtGui.QHBoxLayout(self.welcomePage) - self.welcomeLayout.setSpacing(8) - self.welcomeLayout.setMargin(0) - self.welcomeLayout.setObjectName(u'welcomeLayout') - self.importBibleImage = QtGui.QLabel(self.welcomePage) - self.importBibleImage.setMinimumSize(QtCore.QSize(163, 0)) - self.importBibleImage.setMaximumSize(QtCore.QSize(163, 16777215)) - self.importBibleImage.setLineWidth(0) - self.importBibleImage.setText(u'') - self.importBibleImage.setPixmap( - QtGui.QPixmap(u':/wizards/wizard_importbible.bmp')) - self.importBibleImage.setIndent(0) - self.importBibleImage.setObjectName(u'importBibleImage') - self.welcomeLayout.addWidget(self.importBibleImage) - self.welcomePageLayout = QtGui.QVBoxLayout() - self.welcomePageLayout.setSpacing(8) - self.welcomePageLayout.setObjectName(u'welcomePageLayout') - self.titleLabel = QtGui.QLabel(self.welcomePage) - self.titleLabel.setObjectName(u'titleLabel') - self.welcomePageLayout.addWidget(self.titleLabel) - spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, - QtGui.QSizePolicy.Fixed) - self.welcomePageLayout.addItem(spacerItem) - self.informationLabel = QtGui.QLabel(self.welcomePage) - self.informationLabel.setWordWrap(True) - self.informationLabel.setMargin(10) - self.informationLabel.setObjectName(u'informationLabel') - self.welcomePageLayout.addWidget(self.informationLabel) - spacerItem1 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, - QtGui.QSizePolicy.Expanding) - self.welcomePageLayout.addItem(spacerItem1) - self.welcomeLayout.addLayout(self.welcomePageLayout) - FirstTimeWizard.addPage(self.welcomePage) - self.PluginPagePage = QtGui.QWizardPage() - self.PluginPagePage.setObjectName(u'PluginPagePage') - self.verticalLayout_2 = QtGui.QVBoxLayout(self.PluginPagePage) - self.verticalLayout_2.setObjectName(u'verticalLayout_2') - self.verticalLayout = QtGui.QVBoxLayout() - self.verticalLayout.setObjectName(u'verticalLayout') - self.songsCheckBox = QtGui.QCheckBox(self.PluginPagePage) + FirstTimeWizard.setOptions(QtGui.QWizard.IndependentPages | + QtGui.QWizard.NoBackButtonOnStartPage | + QtGui.QWizard.NoBackButtonOnLastPage | + QtGui.QWizard.HaveCustomButton1) + self.finishButton = self.button(QtGui.QWizard.FinishButton) + self.noInternetFinishButton = self.button(QtGui.QWizard.CustomButton1) + self.cancelButton = self.button(QtGui.QWizard.CancelButton) + self.nextButton = self.button(QtGui.QWizard.NextButton) + self.backButton = self.button(QtGui.QWizard.BackButton) + add_welcome_page(FirstTimeWizard, u':/wizards/wizard_firsttime.bmp') + # The plugins page + self.pluginPage = QtGui.QWizardPage() + self.pluginPage.setObjectName(u'pluginPage') + self.pluginLayout = QtGui.QVBoxLayout(self.pluginPage) + self.pluginLayout.setContentsMargins(40, 15, 40, 0) + self.pluginLayout.setObjectName(u'pluginLayout') + self.songsCheckBox = QtGui.QCheckBox(self.pluginPage) self.songsCheckBox.setChecked(True) self.songsCheckBox.setObjectName(u'songsCheckBox') - self.verticalLayout.addWidget(self.songsCheckBox) - self.customCheckBox = QtGui.QCheckBox(self.PluginPagePage) + self.pluginLayout.addWidget(self.songsCheckBox) + self.customCheckBox = QtGui.QCheckBox(self.pluginPage) self.customCheckBox.setChecked(True) self.customCheckBox.setObjectName(u'customCheckBox') - self.verticalLayout.addWidget(self.customCheckBox) - self.bibleCheckBox = QtGui.QCheckBox(self.PluginPagePage) + self.pluginLayout.addWidget(self.customCheckBox) + self.bibleCheckBox = QtGui.QCheckBox(self.pluginPage) self.bibleCheckBox.setChecked(True) self.bibleCheckBox.setObjectName(u'bibleCheckBox') - self.verticalLayout.addWidget(self.bibleCheckBox) - self.imageCheckBox = QtGui.QCheckBox(self.PluginPagePage) + self.pluginLayout.addWidget(self.bibleCheckBox) + self.imageCheckBox = QtGui.QCheckBox(self.pluginPage) self.imageCheckBox.setChecked(True) self.imageCheckBox.setObjectName(u'imageCheckBox') - self.verticalLayout.addWidget(self.imageCheckBox) - self.presentationCheckBox = QtGui.QCheckBox(self.PluginPagePage) - self.presentationCheckBox.setChecked(True) + self.pluginLayout.addWidget(self.imageCheckBox) + self.presentationCheckBox = QtGui.QCheckBox(self.pluginPage) + if sys.platform == "darwin": + self.presentationCheckBox.setChecked(False) + else: + self.presentationCheckBox.setChecked(True) self.presentationCheckBox.setObjectName(u'presentationCheckBox') - self.verticalLayout.addWidget(self.presentationCheckBox) - self.mediaCheckBox = QtGui.QCheckBox(self.PluginPagePage) + self.pluginLayout.addWidget(self.presentationCheckBox) + self.mediaCheckBox = QtGui.QCheckBox(self.pluginPage) self.mediaCheckBox.setChecked(True) self.mediaCheckBox.setObjectName(u'mediaCheckBox') - self.verticalLayout.addWidget(self.mediaCheckBox) - self.remoteCheckBox = QtGui.QCheckBox(self.PluginPagePage) + self.pluginLayout.addWidget(self.mediaCheckBox) + self.remoteCheckBox = QtGui.QCheckBox(self.pluginPage) self.remoteCheckBox.setObjectName(u'remoteCheckBox') - self.verticalLayout.addWidget(self.remoteCheckBox) - self.songUsageCheckBox = QtGui.QCheckBox(self.PluginPagePage) + self.pluginLayout.addWidget(self.remoteCheckBox) + self.songUsageCheckBox = QtGui.QCheckBox(self.pluginPage) self.songUsageCheckBox.setChecked(True) self.songUsageCheckBox.setObjectName(u'songUsageCheckBox') - self.verticalLayout.addWidget(self.songUsageCheckBox) - self.alertCheckBox = QtGui.QCheckBox(self.PluginPagePage) + self.pluginLayout.addWidget(self.songUsageCheckBox) + self.alertCheckBox = QtGui.QCheckBox(self.pluginPage) self.alertCheckBox.setChecked(True) self.alertCheckBox.setObjectName(u'alertCheckBox') - self.verticalLayout.addWidget(self.alertCheckBox) - self.verticalLayout_2.addLayout(self.verticalLayout) - FirstTimeWizard.addPage(self.PluginPagePage) - self.downloadDefaultsPage = QtGui.QWizardPage() - self.downloadDefaultsPage.setObjectName(u'downloadDefaultsPage') - self.noInternetLabel = QtGui.QLabel(self.downloadDefaultsPage) - self.noInternetLabel.setGeometry(QtCore.QRect(20, 20, 461, 17)) + self.pluginLayout.addWidget(self.alertCheckBox) + FirstTimeWizard.setPage(FirstTimePage.Plugins, self.pluginPage) + # The "you don't have an internet connection" page. + self.noInternetPage = QtGui.QWizardPage() + self.noInternetPage.setObjectName(u'noInternetPage') + self.noInternetLayout = QtGui.QVBoxLayout(self.noInternetPage) + self.noInternetLayout.setContentsMargins(50, 30, 50, 40) + self.noInternetLayout.setObjectName(u'noInternetLayout') + self.noInternetLabel = QtGui.QLabel(self.noInternetPage) + self.noInternetLabel.setWordWrap(True) self.noInternetLabel.setObjectName(u'noInternetLabel') - self.internetGroupBox = QtGui.QGroupBox(self.downloadDefaultsPage) - self.internetGroupBox.setGeometry(QtCore.QRect(20, 10, 501, 271)) - self.internetGroupBox.setObjectName(u'internetGroupBox') - self.verticalLayout_4 = QtGui.QVBoxLayout(self.internetGroupBox) - self.verticalLayout_4.setObjectName(u'verticalLayout_4') - self.selectionTreeWidget = QtGui.QTreeWidget(self.internetGroupBox) - self.selectionTreeWidget.setHorizontalScrollBarPolicy( - QtCore.Qt.ScrollBarAlwaysOff) - self.selectionTreeWidget.setProperty(u'showDropIndicator', False) - self.selectionTreeWidget.setAlternatingRowColors(True) - self.selectionTreeWidget.setObjectName(u'selectionTreeWidget') - self.selectionTreeWidget.headerItem().setText(0, u'1') - self.selectionTreeWidget.header().setVisible(False) - self.verticalLayout_4.addWidget(self.selectionTreeWidget) - FirstTimeWizard.addPage(self.downloadDefaultsPage) - self.DefaultsPage = QtGui.QWizardPage() - self.DefaultsPage.setObjectName(u'DefaultsPage') - self.layoutWidget = QtGui.QWidget(self.DefaultsPage) - self.layoutWidget.setGeometry(QtCore.QRect(20, 20, 491, 113)) - self.layoutWidget.setObjectName(u'layoutWidget') - self.gridLayout = QtGui.QGridLayout(self.layoutWidget) - self.gridLayout.setMargin(0) - self.gridLayout.setObjectName(u'gridLayout') - self.displaySelectionLabel = QtGui.QLabel(self.layoutWidget) - self.displaySelectionLabel.setObjectName(u'displaySelectionLabel') - self.gridLayout.addWidget(self.displaySelectionLabel, 0, 0, 1, 1) - self.displaySelectionComboBox = QtGui.QComboBox(self.layoutWidget) - self.displaySelectionComboBox.setEditable(False) - self.displaySelectionComboBox.setInsertPolicy(QtGui.QComboBox.NoInsert) - self.displaySelectionComboBox.setSizeAdjustPolicy( + self.noInternetLayout.addWidget(self.noInternetLabel) + FirstTimeWizard.setPage(FirstTimePage.NoInternet, self.noInternetPage) + # The song samples page + self.songsPage = QtGui.QWizardPage() + self.songsPage.setObjectName(u'songsPage') + self.songsLayout = QtGui.QVBoxLayout(self.songsPage) + self.songsLayout.setContentsMargins(50, 20, 50, 20) + self.songsLayout.setObjectName(u'songsLayout') + self.songsListWidget = QtGui.QListWidget(self.songsPage) + self.songsListWidget.setAlternatingRowColors(True) + self.songsListWidget.setObjectName(u'songsListWidget') + self.songsLayout.addWidget(self.songsListWidget) + FirstTimeWizard.setPage(FirstTimePage.Songs, self.songsPage) + # The Bible samples page + self.biblesPage = QtGui.QWizardPage() + self.biblesPage.setObjectName(u'biblesPage') + self.biblesLayout = QtGui.QVBoxLayout(self.biblesPage) + self.biblesLayout.setContentsMargins(50, 20, 50, 20) + self.biblesLayout.setObjectName(u'biblesLayout') + self.biblesTreeWidget = QtGui.QTreeWidget(self.biblesPage) + self.biblesTreeWidget.setAlternatingRowColors(True) + self.biblesTreeWidget.header().setVisible(False) + self.biblesTreeWidget.setObjectName(u'biblesTreeWidget') + self.biblesLayout.addWidget(self.biblesTreeWidget) + FirstTimeWizard.setPage(FirstTimePage.Bibles, self.biblesPage) + # The theme samples page + self.themesPage = QtGui.QWizardPage() + self.themesPage.setObjectName(u'themesPage') + self.themesLayout = QtGui.QVBoxLayout(self.themesPage) + self.themesLayout.setContentsMargins(20, 50, 20, 60) + self.themesLayout.setObjectName(u'themesLayout') + self.themesListWidget = QtGui.QListWidget(self.themesPage) + self.themesListWidget.setViewMode(QtGui.QListView.IconMode) + self.themesListWidget.setMovement(QtGui.QListView.Static) + self.themesListWidget.setFlow(QtGui.QListView.LeftToRight) + self.themesListWidget.setSpacing(4) + self.themesListWidget.setUniformItemSizes(True) + self.themesListWidget.setIconSize(QtCore.QSize(133, 100)) + self.themesListWidget.setWrapping(False) + self.themesListWidget.setObjectName(u'themesListWidget') + self.themesLayout.addWidget(self.themesListWidget) + FirstTimeWizard.setPage(FirstTimePage.Themes, self.themesPage) + # the default settings page + self.defaultsPage = QtGui.QWizardPage() + self.defaultsPage.setObjectName(u'defaultsPage') + self.defaultsLayout = QtGui.QFormLayout(self.defaultsPage) + self.defaultsLayout.setContentsMargins(50, 20, 50, 20) + self.defaultsLayout.setObjectName(u'defaultsLayout') + self.displayLabel = QtGui.QLabel(self.defaultsPage) + self.displayLabel.setObjectName(u'displayLabel') + self.displayComboBox = QtGui.QComboBox(self.defaultsPage) + self.displayComboBox.setEditable(False) + self.displayComboBox.setInsertPolicy(QtGui.QComboBox.NoInsert) + self.displayComboBox.setObjectName(u'displayComboBox') + self.defaultsLayout.addRow(self.displayLabel, self.displayComboBox) + self.themeLabel = QtGui.QLabel(self.defaultsPage) + self.themeLabel.setObjectName(u'themeLabel') + self.themeComboBox = QtGui.QComboBox(self.defaultsPage) + self.themeComboBox.setEditable(False) + self.themeComboBox.setInsertPolicy(QtGui.QComboBox.NoInsert) + self.themeComboBox.setSizeAdjustPolicy( QtGui.QComboBox.AdjustToContents) - self.displaySelectionComboBox.setObjectName(u'displaySelectionComboBox') - self.gridLayout.addWidget(self.displaySelectionComboBox, 0, 1, 1, 1) - self.themeSelectionLabel = QtGui.QLabel(self.layoutWidget) - self.themeSelectionLabel.setObjectName(u'themeSelectionLabel') - self.gridLayout.addWidget(self.themeSelectionLabel, 1, 0, 1, 1) - self.themeSelectionComboBox = QtGui.QComboBox(self.layoutWidget) - self.themeSelectionComboBox.setSizeAdjustPolicy( - QtGui.QComboBox.AdjustToContents) - self.themeSelectionComboBox.setObjectName(u'themeSelectionComboBox') - self.gridLayout.addWidget(self.themeSelectionComboBox, 1, 1, 1, 1) - self.messageLabel = QtGui.QLabel(self.DefaultsPage) - self.messageLabel.setGeometry(QtCore.QRect(60, 160, 471, 17)) - self.messageLabel.setObjectName(u'messageLabel') - self.updateLabel = QtGui.QLabel(self.DefaultsPage) - self.updateLabel.setGeometry(QtCore.QRect(60, 220, 351, 17)) - self.updateLabel.setObjectName(u'updateLabel') - FirstTimeWizard.addPage(self.DefaultsPage) - + self.themeComboBox.setObjectName(u'themeComboBox') + self.defaultsLayout.addRow(self.themeLabel, self.themeComboBox) + FirstTimeWizard.setPage(FirstTimePage.Defaults, self.defaultsPage) + # Progress page + self.progressPage = QtGui.QWizardPage() + self.progressPage.setObjectName(u'progressPage') + self.progressLayout = QtGui.QVBoxLayout(self.progressPage) + self.progressLayout.setMargin(48) + self.progressLayout.setObjectName(u'progressLayout') + self.progressLabel = QtGui.QLabel(self.progressPage) + self.progressLabel.setObjectName(u'progressLabel') + self.progressLayout.addWidget(self.progressLabel) + self.progressBar = QtGui.QProgressBar(self.progressPage) + self.progressBar.setObjectName(u'progressBar') + self.progressLayout.addWidget(self.progressBar) + FirstTimeWizard.setPage(FirstTimePage.Progress, self.progressPage) self.retranslateUi(FirstTimeWizard) - QtCore.QMetaObject.connectSlotsByName(FirstTimeWizard) def retranslateUi(self, FirstTimeWizard): FirstTimeWizard.setWindowTitle(translate( @@ -182,21 +201,22 @@ class Ui_FirstTimeWizard(object): translate('OpenLP.FirstTimeWizard', 'Welcome to the First Time Wizard')) self.informationLabel.setText(translate('OpenLP.FirstTimeWizard', - 'This wizard will help you to configure OpenLP for initial use .' - ' Click the next button below to start the process of selection ' - 'your initial options. ')) - self.PluginPagePage.setTitle(translate('OpenLP.FirstTimeWizard', + 'This wizard will help you to configure OpenLP for initial use.' + ' Click the next button below to start.')) + self.pluginPage.setTitle(translate('OpenLP.FirstTimeWizard', 'Activate required Plugins')) - self.PluginPagePage.setSubTitle(translate('OpenLP.FirstTimeWizard', + self.pluginPage.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Select the Plugins you wish to use. ')) self.songsCheckBox.setText(translate('OpenLP.FirstTimeWizard', 'Songs')) self.customCheckBox.setText(translate('OpenLP.FirstTimeWizard', - 'Custom Text')) + 'Custom Slides')) self.bibleCheckBox.setText(translate('OpenLP.FirstTimeWizard', 'Bible')) self.imageCheckBox.setText(translate('OpenLP.FirstTimeWizard', 'Images')) self.presentationCheckBox.setText(translate('OpenLP.FirstTimeWizard', 'Presentations')) + if sys.platform == "darwin": + self.presentationCheckBox.setEnabled(False) self.mediaCheckBox.setText(translate('OpenLP.FirstTimeWizard', 'Media (Audio and Video)')) self.remoteCheckBox.setText(translate('OpenLP.FirstTimeWizard', @@ -205,23 +225,43 @@ class Ui_FirstTimeWizard(object): 'Monitor Song Usage')) self.alertCheckBox.setText(translate('OpenLP.FirstTimeWizard', 'Allow Alerts')) - self.downloadDefaultsPage.setTitle(translate('OpenLP.FirstTimeWizard', - 'Download Samples from OpenLP.org')) - self.downloadDefaultsPage.setSubTitle(translate( + self.noInternetPage.setTitle(translate('OpenLP.FirstTimeWizard', + 'No Internet Connection')) + self.noInternetPage.setSubTitle(translate( 'OpenLP.FirstTimeWizard', - 'Select samples to downlaod and install for use.')) - self.noInternetLabel.setText(translate('OpenLP.FirstTimeWizard', - 'No Internet connection found so unable to download any default' - ' files.')) - self.internetGroupBox.setTitle(translate('OpenLP.FirstTimeWizard', - 'Download Example Files')) - self.DefaultsPage.setTitle(translate('OpenLP.FirstTimeWizard', + 'Unable to detect an Internet connection.')) + self.noInternetText = translate('OpenLP.FirstTimeWizard', + 'No Internet connection was found. The First Time Wizard needs an ' + 'Internet connection in order to be able to download sample ' + 'songs, Bibles and themes. Press the Finish button now to start ' + 'OpenLP with initial settings and no sample data.\n\nTo re-run the ' + 'First Time Wizard and import this sample data at a later time, ' + 'check your Internet connection and re-run this wizard by ' + 'selecting "Tools/Re-run First Time Wizard" from OpenLP.') + self.cancelWizardText = translate('OpenLP.FirstTimeWizard', + '\n\nTo cancel the First Time Wizard completely (and not start ' + 'OpenLP), press the Cancel button now.') + self.songsPage.setTitle(translate('OpenLP.FirstTimeWizard', + 'Sample Songs')) + self.songsPage.setSubTitle(translate('OpenLP.FirstTimeWizard', + 'Select and download public domain songs.')) + self.biblesPage.setTitle(translate('OpenLP.FirstTimeWizard', + 'Sample Bibles')) + self.biblesPage.setSubTitle(translate('OpenLP.FirstTimeWizard', + 'Select and download free Bibles.')) + self.themesPage.setTitle(translate('OpenLP.FirstTimeWizard', + 'Sample Themes')) + self.themesPage.setSubTitle(translate('OpenLP.FirstTimeWizard', + 'Select and download sample themes.')) + self.defaultsPage.setTitle(translate('OpenLP.FirstTimeWizard', 'Default Settings')) - self.DefaultsPage.setSubTitle(translate('OpenLP.FirstTimeWizard', - 'Set up default values to be used by OpenLP')) - self.displaySelectionLabel.setText(translate('OpenLP.FirstTimeWizard', - 'Default output display')) - self.themeSelectionLabel.setText(translate('OpenLP.FirstTimeWizard', - 'Select the default Theme')) - self.messageLabel.setText(translate('OpenLP.FirstTimeWizard', - 'Press finish to apply all your changes and start OpenLP')) + self.defaultsPage.setSubTitle(translate('OpenLP.FirstTimeWizard', + 'Set up default settings to be used by OpenLP.')) + self.displayLabel.setText(translate('OpenLP.FirstTimeWizard', + 'Default output display:')) + self.themeLabel.setText(translate('OpenLP.FirstTimeWizard', + 'Select default theme:')) + self.progressLabel.setText(translate('OpenLP.FirstTimeWizard', + 'Starting configuration process...')) + FirstTimeWizard.setButtonText(QtGui.QWizard.CustomButton1, + translate('OpenLP.FirstTimeWizard', 'Finish')) diff --git a/openlp/core/ui/displaytagdialog.py b/openlp/core/ui/formattingtagdialog.py similarity index 68% rename from openlp/core/ui/displaytagdialog.py rename to openlp/core/ui/formattingtagdialog.py index 2b6441e16..186f4739b 100644 --- a/openlp/core/ui/displaytagdialog.py +++ b/openlp/core/ui/formattingtagdialog.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, Armin Köhler, 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 # @@ -27,20 +28,17 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import translate -from openlp.core.lib.ui import UiStrings, create_accept_reject_button_box +from openlp.core.lib.ui import UiStrings -class Ui_DisplayTagDialog(object): +class Ui_FormattingTagDialog(object): - def setupUi(self, displayTagDialog): - displayTagDialog.setObjectName(u'displayTagDialog') - displayTagDialog.resize(725, 548) - self.widget = QtGui.QWidget(displayTagDialog) - self.widget.setGeometry(QtCore.QRect(10, 10, 701, 521)) - self.widget.setObjectName(u'widget') - self.listdataGridLayout = QtGui.QGridLayout(self.widget) - self.listdataGridLayout.setMargin(0) + def setupUi(self, formattingTagDialog): + formattingTagDialog.setObjectName(u'formattingTagDialog') + formattingTagDialog.resize(725, 548) + self.listdataGridLayout = QtGui.QGridLayout(formattingTagDialog) + self.listdataGridLayout.setMargin(8) self.listdataGridLayout.setObjectName(u'listdataGridLayout') - self.tagTableWidget = QtGui.QTableWidget(self.widget) + self.tagTableWidget = QtGui.QTableWidget(formattingTagDialog) self.tagTableWidget.setHorizontalScrollBarPolicy( QtCore.Qt.ScrollBarAlwaysOff) self.tagTableWidget.setEditTriggers( @@ -54,6 +52,7 @@ class Ui_DisplayTagDialog(object): self.tagTableWidget.setObjectName(u'tagTableWidget') self.tagTableWidget.setColumnCount(4) self.tagTableWidget.setRowCount(0) + self.tagTableWidget.horizontalHeader().setStretchLastSection(True) item = QtGui.QTableWidgetItem() self.tagTableWidget.setHorizontalHeaderItem(0, item) item = QtGui.QTableWidgetItem() @@ -68,14 +67,11 @@ class Ui_DisplayTagDialog(object): spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem) - self.defaultPushButton = QtGui.QPushButton(self.widget) - self.defaultPushButton.setObjectName(u'defaultPushButton') - self.horizontalLayout.addWidget(self.defaultPushButton) - self.deletePushButton = QtGui.QPushButton(self.widget) + self.deletePushButton = QtGui.QPushButton(formattingTagDialog) self.deletePushButton.setObjectName(u'deletePushButton') self.horizontalLayout.addWidget(self.deletePushButton) self.listdataGridLayout.addLayout(self.horizontalLayout, 1, 0, 1, 1) - self.editGroupBox = QtGui.QGroupBox(self.widget) + self.editGroupBox = QtGui.QGroupBox(formattingTagDialog) self.editGroupBox.setObjectName(u'editGroupBox') self.dataGridLayout = QtGui.QGridLayout(self.editGroupBox) self.dataGridLayout.setObjectName(u'dataGridLayout') @@ -112,43 +108,42 @@ class Ui_DisplayTagDialog(object): self.endTagLineEdit = QtGui.QLineEdit(self.editGroupBox) self.endTagLineEdit.setObjectName(u'endTagLineEdit') self.dataGridLayout.addWidget(self.endTagLineEdit, 4, 1, 1, 1) - self.updatePushButton = QtGui.QPushButton(self.editGroupBox) - self.updatePushButton.setObjectName(u'updatePushButton') - self.dataGridLayout.addWidget(self.updatePushButton, 4, 2, 1, 1) + self.savePushButton = QtGui.QPushButton(self.editGroupBox) + self.savePushButton.setObjectName(u'savePushButton') + self.dataGridLayout.addWidget(self.savePushButton, 4, 2, 1, 1) self.listdataGridLayout.addWidget(self.editGroupBox, 2, 0, 1, 1) - self.buttonBox = create_accept_reject_button_box(displayTagDialog) + self.buttonBox = QtGui.QDialogButtonBox(formattingTagDialog) + self.buttonBox.setObjectName('formattingTagDialogButtonBox') + self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Close) self.listdataGridLayout.addWidget(self.buttonBox, 3, 0, 1, 1) - self.retranslateUi(displayTagDialog) - QtCore.QMetaObject.connectSlotsByName(displayTagDialog) + self.retranslateUi(formattingTagDialog) + QtCore.QMetaObject.connectSlotsByName(formattingTagDialog) - def retranslateUi(self, displayTagDialog): - displayTagDialog.setWindowTitle(translate('OpenLP.displayTagDialog', - 'Configure Display Tags')) + def retranslateUi(self, formattingTagDialog): + formattingTagDialog.setWindowTitle(translate( + 'OpenLP.FormattingTagDialog', 'Configure Formatting Tags')) self.editGroupBox.setTitle( - translate('OpenLP.DisplayTagDialog', 'Edit Selection')) - self.updatePushButton.setText( - translate('OpenLP.DisplayTagDialog', 'Update')) + translate('OpenLP.FormattingTagDialog', 'Edit Selection')) + self.savePushButton.setText( + translate('OpenLP.FormattingTagDialog', 'Save')) self.descriptionLabel.setText( - translate('OpenLP.DisplayTagDialog', 'Description')) - self.tagLabel.setText(translate('OpenLP.DisplayTagDialog', 'Tag')) + translate('OpenLP.FormattingTagDialog', 'Description')) + self.tagLabel.setText(translate('OpenLP.FormattingTagDialog', 'Tag')) self.startTagLabel.setText( - translate('OpenLP.DisplayTagDialog', 'Start tag')) + translate('OpenLP.FormattingTagDialog', 'Start tag')) self.endTagLabel.setText( - translate('OpenLP.DisplayTagDialog', 'End tag')) - self.deletePushButton.setText(UiStrings.Delete) - self.defaultPushButton.setText( - translate('OpenLP.DisplayTagDialog', 'Default')) - self.newPushButton.setText(UiStrings.New) + translate('OpenLP.FormattingTagDialog', 'End tag')) + self.deletePushButton.setText(UiStrings().Delete) + self.newPushButton.setText(UiStrings().New) self.tagTableWidget.horizontalHeaderItem(0).setText( - translate('OpenLP.DisplayTagDialog', 'Description')) + translate('OpenLP.FormattingTagDialog', 'Description')) self.tagTableWidget.horizontalHeaderItem(1).setText( - translate('OpenLP.DisplayTagDialog', 'Tag id')) + translate('OpenLP.FormattingTagDialog', 'Tag Id')) self.tagTableWidget.horizontalHeaderItem(2).setText( - translate('OpenLP.DisplayTagDialog', 'Start Html')) + translate('OpenLP.FormattingTagDialog', 'Start HTML')) self.tagTableWidget.horizontalHeaderItem(3).setText( - translate('OpenLP.DisplayTagDialog', 'End Html')) + translate('OpenLP.FormattingTagDialog', 'End HTML')) self.tagTableWidget.setColumnWidth(0, 120) - self.tagTableWidget.setColumnWidth(1, 40) - self.tagTableWidget.setColumnWidth(2, 240) - self.tagTableWidget.setColumnWidth(3, 240) + self.tagTableWidget.setColumnWidth(1, 80) + self.tagTableWidget.setColumnWidth(2, 330) diff --git a/openlp/core/ui/displaytagform.py b/openlp/core/ui/formattingtagform.py similarity index 62% rename from openlp/core/ui/displaytagform.py rename to openlp/core/ui/formattingtagform.py index e29fe3384..e7435e5b7 100644 --- a/openlp/core/ui/displaytagform.py +++ b/openlp/core/ui/formattingtagform.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, Armin Köhler, 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 # @@ -24,22 +25,21 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ -The :mod:`DisplayTagTab` provides an Tag Edit facility. The Base set are -protected and included each time loaded. Custom tags can be defined and saved. -The Custom Tag arrays are saved in a pickle so QSettings works on them. Base +The :mod:`formattingtagform` provides an Tag Edit facility. The Base set are +protected and included each time loaded. Custom tags can be defined and saved. +The Custom Tag arrays are saved in a pickle so QSettings works on them. Base Tags cannot be changed. """ -import cPickle - from PyQt4 import QtCore, QtGui -from openlp.core.lib import translate, DisplayTags +from openlp.core.lib import translate, FormattingTags from openlp.core.lib.ui import critical_error_message_box -from openlp.core.ui.displaytagdialog import Ui_DisplayTagDialog +from openlp.core.ui.formattingtagdialog import Ui_FormattingTagDialog -class DisplayTagForm(QtGui.QDialog, Ui_DisplayTagDialog): + +class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog): """ - The :class:`DisplayTagTab` manages the settings tab . + The :class:`FormattingTagForm` manages the settings tab . """ def __init__(self, parent): """ @@ -47,76 +47,34 @@ class DisplayTagForm(QtGui.QDialog, Ui_DisplayTagDialog): """ QtGui.QDialog.__init__(self, parent) self.setupUi(self) - self.preLoad() QtCore.QObject.connect(self.tagTableWidget, QtCore.SIGNAL(u'clicked(QModelIndex)'), self.onRowSelected) - QtCore.QObject.connect(self.defaultPushButton, - QtCore.SIGNAL(u'pressed()'), self.onDefaultPushed) QtCore.QObject.connect(self.newPushButton, QtCore.SIGNAL(u'pressed()'), self.onNewPushed) - QtCore.QObject.connect(self.updatePushButton, - QtCore.SIGNAL(u'pressed()'), self.onUpdatePushed) + QtCore.QObject.connect(self.savePushButton, + QtCore.SIGNAL(u'pressed()'), self.onSavedPushed) QtCore.QObject.connect(self.deletePushButton, QtCore.SIGNAL(u'pressed()'), self.onDeletePushed) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(u'rejected()'), + self.close) + # Forces reloading of tags from openlp configuration. + FormattingTags.load_tags() def exec_(self): """ Load Display and set field state. """ # Create initial copy from master - self.preLoad() self._resetTable() self.selected = -1 return QtGui.QDialog.exec_(self) - def preLoad(self): - """ - 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 - DisplayTags.reset_html_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(unicode(user_expands).encode(u'utf8')) - if user_expands_string: - user_tags = cPickle.loads(user_expands_string) - # If we have some user ones added them as well - for t in user_tags: - DisplayTags.add_html_tag(t) - - def accept(self): - """ - Save Custom tags in a pickle . - """ - temp = [] - for tag in DisplayTags.get_html_tags(): - if not tag[u'protected']: - temp.append(tag) - if temp: - ctemp = cPickle.dumps(temp) - QtCore.QSettings().setValue(u'displayTags/html_tags', - QtCore.QVariant(ctemp)) - else: - QtCore.QSettings().setValue(u'displayTags/html_tags', - QtCore.QVariant(u'')) - return QtGui.QDialog.accept(self) - - def reject(self): - """ - Reset Custom tags from Settings. - """ - self._resetTable() - return QtGui.QDialog.reject(self) - def onRowSelected(self): """ Table Row selected so display items and set field state. """ row = self.tagTableWidget.currentRow() - html = DisplayTags.get_html_tags()[row] + html = FormattingTags.html_expands[row] self.selected = row self.descriptionLineEdit.setText(html[u'desc']) self.tagLineEdit.setText(self._strip(html[u'start tag'])) @@ -127,58 +85,59 @@ class DisplayTagForm(QtGui.QDialog, Ui_DisplayTagDialog): self.tagLineEdit.setEnabled(False) self.startTagLineEdit.setEnabled(False) self.endTagLineEdit.setEnabled(False) - self.updatePushButton.setEnabled(False) + self.savePushButton.setEnabled(False) self.deletePushButton.setEnabled(False) else: self.descriptionLineEdit.setEnabled(True) self.tagLineEdit.setEnabled(True) self.startTagLineEdit.setEnabled(True) self.endTagLineEdit.setEnabled(True) - self.updatePushButton.setEnabled(True) + self.savePushButton.setEnabled(True) self.deletePushButton.setEnabled(True) def onNewPushed(self): """ Add a new tag to list only if it is not a duplicate. """ - for html in DisplayTags.get_html_tags(): + for html in FormattingTags.html_expands: if self._strip(html[u'start tag']) == u'n': critical_error_message_box( - translate('OpenLP.DisplayTagTab', 'Update Error'), - translate('OpenLP.DisplayTagTab', + translate('OpenLP.FormattingTagForm', 'Update Error'), + translate('OpenLP.FormattingTagForm', 'Tag "n" already defined.')) return # Add new tag to list - tag = {u'desc': u'New Item', u'start tag': u'{n}', - u'start html': u'', u'end tag': u'{/n}', - u'end html': u'', u'protected': False} - DisplayTags.add_html_tag(tag) + tag = { + u'desc': translate('OpenLP.FormattingTagForm', 'New Tag'), + u'start tag': u'{n}', + u'start html': translate('OpenLP.FormattingTagForm', ''), + u'end tag': u'{/n}', + u'end html': translate('OpenLP.FormattingTagForm', ''), + u'protected': False, + u'temporary': False + } + FormattingTags.add_html_tags([tag]) self._resetTable() # Highlight new row self.tagTableWidget.selectRow(self.tagTableWidget.rowCount() - 1) self.onRowSelected() - - def onDefaultPushed(self): - """ - Remove all Custom Tags and reset to base set only. - """ - DisplayTags.reset_html_tags() - self._resetTable() + self.tagTableWidget.scrollToBottom() def onDeletePushed(self): """ Delete selected custom tag. """ if self.selected != -1: - DisplayTags.remove_html_tag(self.selected) + FormattingTags.remove_html_tag(self.selected) self.selected = -1 self._resetTable() + FormattingTags.save_html_tags() - def onUpdatePushed(self): + def onSavedPushed(self): """ - Update Custom Tag details if not duplicate. + Update Custom Tag details if not duplicate and save the data. """ - html_expands = DisplayTags.get_html_tags() + html_expands = FormattingTags.html_expands if self.selected != -1: html = html_expands[self.selected] tag = unicode(self.tagLineEdit.text()) @@ -186,8 +145,8 @@ class DisplayTagForm(QtGui.QDialog, Ui_DisplayTagDialog): if self._strip(html1[u'start tag']) == tag and \ linenumber != self.selected: critical_error_message_box( - translate('OpenLP.DisplayTagTab', 'Update Error'), - unicode(translate('OpenLP.DisplayTagTab', + translate('OpenLP.FormattingTagForm', 'Update Error'), + unicode(translate('OpenLP.FormattingTagForm', 'Tag %s already defined.')) % tag) return html[u'desc'] = unicode(self.descriptionLineEdit.text()) @@ -195,8 +154,11 @@ class DisplayTagForm(QtGui.QDialog, Ui_DisplayTagDialog): html[u'end html'] = unicode(self.endTagLineEdit.text()) html[u'start tag'] = u'{%s}' % tag html[u'end tag'] = u'{/%s}' % tag + # Keep temporary tags when the user changes one. + html[u'temporary'] = False self.selected = -1 self._resetTable() + FormattingTags.save_html_tags() def _resetTable(self): """ @@ -205,11 +167,10 @@ class DisplayTagForm(QtGui.QDialog, Ui_DisplayTagDialog): self.tagTableWidget.clearContents() self.tagTableWidget.setRowCount(0) self.newPushButton.setEnabled(True) - self.updatePushButton.setEnabled(False) + self.savePushButton.setEnabled(False) self.deletePushButton.setEnabled(False) - for linenumber, html in enumerate(DisplayTags.get_html_tags()): - self.tagTableWidget.setRowCount( - self.tagTableWidget.rowCount() + 1) + for linenumber, html in enumerate(FormattingTags.html_expands): + self.tagTableWidget.setRowCount(self.tagTableWidget.rowCount() + 1) self.tagTableWidget.setItem(linenumber, 0, QtGui.QTableWidgetItem(html[u'desc'])) self.tagTableWidget.setItem(linenumber, 1, @@ -218,6 +179,9 @@ class DisplayTagForm(QtGui.QDialog, Ui_DisplayTagDialog): QtGui.QTableWidgetItem(html[u'start html'])) self.tagTableWidget.setItem(linenumber, 3, QtGui.QTableWidgetItem(html[u'end html'])) + # Permanent (persistent) tags do not have this key. + if u'temporary' not in html: + html[u'temporary'] = False self.tagTableWidget.resizeRowsToContents() self.descriptionLineEdit.setText(u'') self.tagLineEdit.setText(u'') diff --git a/openlp/core/ui/generaltab.py b/openlp/core/ui/generaltab.py index e705d5ec3..be02b3caa 100644 --- a/openlp/core/ui/generaltab.py +++ b/openlp/core/ui/generaltab.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, Armin Köhler, 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,58 +30,22 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import SettingsTab, Receiver, translate from openlp.core.lib.ui import UiStrings +from openlp.core.ui import ScreenList log = logging.getLogger(__name__) -class ValidEdit(QtGui.QLineEdit): - """ - Only allow numeric characters to be edited - """ - def __init__(self, parent): - """ - Set up Override and Validator - """ - QtGui.QLineEdit.__init__(self, parent) - self.setValidator(QtGui.QIntValidator(0, 9999, self)) - - def validText(self): - """ - Only return Integers. Space is 0 - """ - if self.text().isEmpty(): - return QtCore.QString(u'0') - else: - return self.text() - - class GeneralTab(SettingsTab): """ GeneralTab is the general settings tab in the settings dialog. """ - def __init__(self, screens): + def __init__(self, parent): """ Initialise the general settings tab """ - self.screens = screens - self.monitorNumber = 0 - # Set to True to allow PostSetup to work on application start up - self.overrideChanged = True - SettingsTab.__init__(self, u'General') - - def preLoad(self): - """ - Set up the display screen and set correct screen values. - If not set before default to last screen. - """ - settings = QtCore.QSettings() - settings.beginGroup(self.settingsSection) - self.monitorNumber = settings.value(u'monitor', - QtCore.QVariant(self.screens.display_count - 1)).toInt()[0] - self.screens.set_current_display(self.monitorNumber) - self.screens.monitor_number = self.monitorNumber - self.screens.display = settings.value( - u'display on monitor', QtCore.QVariant(True)).toBool() - settings.endGroup() + self.screens = ScreenList.get_instance() + self.icon_path = u':/icon/openlp-logo-16x16.png' + generalTranslated = translate('OpenLP.GeneralTab', 'General') + SettingsTab.__init__(self, parent, u'General', generalTranslated) def setupUi(self): """ @@ -126,14 +91,21 @@ class GeneralTab(SettingsTab): self.saveCheckServiceCheckBox = QtGui.QCheckBox(self.settingsGroupBox) self.saveCheckServiceCheckBox.setObjectName(u'saveCheckServiceCheckBox') self.settingsLayout.addRow(self.saveCheckServiceCheckBox) + self.autoUnblankCheckBox = QtGui.QCheckBox(self.settingsGroupBox) + self.autoUnblankCheckBox.setObjectName(u'autoUnblankCheckBox') + self.settingsLayout.addRow(self.autoUnblankCheckBox) self.autoPreviewCheckBox = QtGui.QCheckBox(self.settingsGroupBox) self.autoPreviewCheckBox.setObjectName(u'autoPreviewCheckBox') self.settingsLayout.addRow(self.autoPreviewCheckBox) + self.enableLoopCheckBox = QtGui.QCheckBox(self.settingsGroupBox) + self.enableLoopCheckBox.setObjectName(u'enableLoopCheckBox') + self.settingsLayout.addRow(self.enableLoopCheckBox) # Moved here from image tab self.timeoutLabel = QtGui.QLabel(self.settingsGroupBox) self.timeoutLabel.setObjectName(u'timeoutLabel') self.timeoutSpinBox = QtGui.QSpinBox(self.settingsGroupBox) self.timeoutSpinBox.setObjectName(u'timeoutSpinBox') + self.timeoutSpinBox.setRange(1, 180) self.settingsLayout.addRow(self.timeoutLabel, self.timeoutSpinBox) self.leftLayout.addWidget(self.settingsGroupBox) self.leftLayout.addStretch() @@ -164,30 +136,6 @@ class GeneralTab(SettingsTab): self.displayGroupBox.setObjectName(u'displayGroupBox') self.displayLayout = QtGui.QGridLayout(self.displayGroupBox) self.displayLayout.setObjectName(u'displayLayout') - self.currentXLabel = QtGui.QLabel(self.displayGroupBox) - self.currentXLabel.setObjectName(u'currentXLabel') - self.displayLayout.addWidget(self.currentXLabel, 0, 0) - self.currentXValueLabel = QtGui.QLabel(self.displayGroupBox) - self.currentXValueLabel.setObjectName(u'currentXValueLabel') - self.displayLayout.addWidget(self.currentXValueLabel, 1, 0) - self.currentYLabel = QtGui.QLabel(self.displayGroupBox) - self.currentYLabel.setObjectName(u'currentYLabel') - self.displayLayout.addWidget(self.currentYLabel, 0, 1) - self.currentYValueLabel = QtGui.QLabel(self.displayGroupBox) - self.currentYValueLabel.setObjectName(u'currentYValueLabel') - self.displayLayout.addWidget(self.currentYValueLabel, 1, 1) - self.currentWidthLabel = QtGui.QLabel(self.displayGroupBox) - self.currentWidthLabel.setObjectName(u'currentWidthLabel') - self.displayLayout.addWidget(self.currentWidthLabel, 0, 2) - self.currentWidthValueLabel = QtGui.QLabel(self.displayGroupBox) - self.currentWidthValueLabel.setObjectName(u'currentWidthValueLabel') - self.displayLayout.addWidget(self.currentWidthValueLabel, 1, 2) - self.currentHeightLabel = QtGui.QLabel(self.displayGroupBox) - self.currentHeightLabel.setObjectName(u'currentHeightLabel') - self.displayLayout.addWidget(self.currentHeightLabel, 0, 3) - self.currentHeightValueLabel = QtGui.QLabel(self.displayGroupBox) - self.currentHeightValueLabel.setObjectName(u'Height') - self.displayLayout.addWidget(self.currentHeightValueLabel, 1, 3) self.overrideCheckBox = QtGui.QCheckBox(self.displayGroupBox) self.overrideCheckBox.setObjectName(u'overrideCheckBox') self.displayLayout.addWidget(self.overrideCheckBox, 2, 0, 1, 4) @@ -196,47 +144,63 @@ class GeneralTab(SettingsTab): self.customXLabel = QtGui.QLabel(self.displayGroupBox) self.customXLabel.setObjectName(u'customXLabel') self.displayLayout.addWidget(self.customXLabel, 3, 0) - self.customXValueEdit = ValidEdit(self.displayGroupBox) + self.customXValueEdit = QtGui.QSpinBox(self.displayGroupBox) self.customXValueEdit.setObjectName(u'customXValueEdit') + self.customXValueEdit.setRange(-9999, 9999) self.displayLayout.addWidget(self.customXValueEdit, 4, 0) self.customYLabel = QtGui.QLabel(self.displayGroupBox) self.customYLabel.setObjectName(u'customYLabel') self.displayLayout.addWidget(self.customYLabel, 3, 1) - self.customYValueEdit = ValidEdit(self.displayGroupBox) + self.customYValueEdit = QtGui.QSpinBox(self.displayGroupBox) self.customYValueEdit.setObjectName(u'customYValueEdit') + self.customYValueEdit.setRange(-9999, 9999) self.displayLayout.addWidget(self.customYValueEdit, 4, 1) self.customWidthLabel = QtGui.QLabel(self.displayGroupBox) self.customWidthLabel.setObjectName(u'customWidthLabel') self.displayLayout.addWidget(self.customWidthLabel, 3, 2) - self.customWidthValueEdit = ValidEdit(self.displayGroupBox) + self.customWidthValueEdit = QtGui.QSpinBox(self.displayGroupBox) self.customWidthValueEdit.setObjectName(u'customWidthValueEdit') + self.customWidthValueEdit.setMaximum(9999) self.displayLayout.addWidget(self.customWidthValueEdit, 4, 2) self.customHeightLabel = QtGui.QLabel(self.displayGroupBox) self.customHeightLabel.setObjectName(u'customHeightLabel') self.displayLayout.addWidget(self.customHeightLabel, 3, 3) - self.customHeightValueEdit = ValidEdit(self.displayGroupBox) + self.customHeightValueEdit = QtGui.QSpinBox(self.displayGroupBox) self.customHeightValueEdit.setObjectName(u'customHeightValueEdit') + self.customHeightValueEdit.setMaximum(9999) self.displayLayout.addWidget(self.customHeightValueEdit, 4, 3) self.rightLayout.addWidget(self.displayGroupBox) + # Background audio + self.audioGroupBox = QtGui.QGroupBox(self.rightColumn) + self.audioGroupBox.setObjectName(u'audioGroupBox') + self.audioLayout = QtGui.QVBoxLayout(self.audioGroupBox) + self.audioLayout.setObjectName(u'audioLayout') + self.startPausedCheckBox = QtGui.QCheckBox(self.audioGroupBox) + self.startPausedCheckBox.setObjectName(u'startPausedCheckBox') + self.audioLayout.addWidget(self.startPausedCheckBox) + self.rightLayout.addWidget(self.audioGroupBox) self.rightLayout.addStretch() # Signals and slots QtCore.QObject.connect(self.overrideCheckBox, QtCore.SIGNAL(u'toggled(bool)'), self.onOverrideCheckBoxToggled) QtCore.QObject.connect(self.customHeightValueEdit, - QtCore.SIGNAL(u'textEdited(const QString&)'), - self.onDisplayPositionChanged) + QtCore.SIGNAL(u'valueChanged(int)'), self.onDisplayChanged) QtCore.QObject.connect(self.customWidthValueEdit, - QtCore.SIGNAL(u'textEdited(const QString&)'), - self.onDisplayPositionChanged) + QtCore.SIGNAL(u'valueChanged(int)'), self.onDisplayChanged) QtCore.QObject.connect(self.customYValueEdit, - QtCore.SIGNAL(u'textEdited(const QString&)'), - self.onDisplayPositionChanged) + QtCore.SIGNAL(u'valueChanged(int)'), self.onDisplayChanged) QtCore.QObject.connect(self.customXValueEdit, - QtCore.SIGNAL(u'textEdited(const QString&)'), - self.onDisplayPositionChanged) + QtCore.SIGNAL(u'valueChanged(int)'), self.onDisplayChanged) + QtCore.QObject.connect(self.monitorComboBox, + QtCore.SIGNAL(u'currentIndexChanged(int)'), self.onDisplayChanged) # Reload the tab, as the screen resolution/count may have changed. QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'config_screen_changed'), self.load) + # Remove for now + self.usernameLabel.setVisible(False) + self.usernameEdit.setVisible(False) + self.passwordLabel.setVisible(False) + self.passwordEdit.setVisible(False) def retranslateUi(self): """ @@ -263,15 +227,18 @@ class GeneralTab(SettingsTab): translate('OpenLP.GeneralTab', 'Application Settings')) self.saveCheckServiceCheckBox.setText(translate('OpenLP.GeneralTab', 'Prompt to save before starting a new service')) + self.autoUnblankCheckBox.setText(translate('OpenLP.GeneralTab', + 'Unblank display when adding new live item')) self.autoPreviewCheckBox.setText(translate('OpenLP.GeneralTab', 'Automatically preview next item in service')) + self.enableLoopCheckBox.setText(translate('OpenLP.GeneralTab', + 'Enable slide wrap-around')) self.timeoutLabel.setText(translate('OpenLP.GeneralTab', - 'Slide loop delay:')) - self.timeoutSpinBox.setSuffix( - translate('OpenLP.GeneralTab', ' sec')) + 'Timed slide interval:')) + self.timeoutSpinBox.setSuffix(translate('OpenLP.GeneralTab', ' sec')) self.ccliGroupBox.setTitle( translate('OpenLP.GeneralTab', 'CCLI Details')) - self.numberLabel.setText(UiStrings.CCLINumberLabel) + self.numberLabel.setText(UiStrings().CCLINumberLabel) self.usernameLabel.setText( translate('OpenLP.GeneralTab', 'SongSelect username:')) self.passwordLabel.setText( @@ -279,23 +246,16 @@ class GeneralTab(SettingsTab): # Moved from display tab self.displayGroupBox.setTitle( translate('OpenLP.GeneralTab', 'Display Position')) - self.currentXLabel.setText(translate('OpenLP.GeneralTab', 'X')) - self.currentXValueLabel.setText(u'0') - self.currentYLabel.setText(translate('OpenLP.GeneralTab', 'Y')) - self.currentYValueLabel.setText(u'0') - self.currentHeightLabel.setText( - translate('OpenLP.GeneralTab', 'Height')) - self.currentHeightValueLabel.setText(u'0') - self.currentWidthLabel.setText( - translate('OpenLP.GeneralTab', 'Width')) - self.currentWidthValueLabel.setText(u'0') self.overrideCheckBox.setText(translate('OpenLP.GeneralTab', 'Override display position')) self.customXLabel.setText(translate('OpenLP.GeneralTab', 'X')) self.customYLabel.setText(translate('OpenLP.GeneralTab', 'Y')) - self.customHeightLabel.setText( - translate('OpenLP.GeneralTab', 'Height')) + self.customHeightLabel.setText(translate('OpenLP.GeneralTab', 'Height')) self.customWidthLabel.setText(translate('OpenLP.GeneralTab', 'Width')) + self.audioGroupBox.setTitle( + translate('OpenLP.GeneralTab', 'Background Audio')) + self.startPausedCheckBox.setText( + translate('OpenLP.GeneralTab', 'Start background audio paused')) def load(self): """ @@ -304,8 +264,10 @@ class GeneralTab(SettingsTab): settings = QtCore.QSettings() settings.beginGroup(self.settingsSection) self.monitorComboBox.clear() - for screen in self.screens.get_screen_list(): - self.monitorComboBox.addItem(screen) + self.monitorComboBox.addItems(self.screens.get_screen_list()) + monitorNumber = settings.value(u'monitor', + QtCore.QVariant(self.screens.display_count - 1)).toInt()[0] + self.monitorComboBox.setCurrentIndex(monitorNumber) self.numberEdit.setText(unicode(settings.value( u'ccli number', QtCore.QVariant(u'')).toString())) self.usernameEdit.setText(unicode(settings.value( @@ -314,7 +276,8 @@ class GeneralTab(SettingsTab): u'songselect password', QtCore.QVariant(u'')).toString())) self.saveCheckServiceCheckBox.setChecked(settings.value(u'save prompt', QtCore.QVariant(False)).toBool()) - self.monitorComboBox.setCurrentIndex(self.monitorNumber) + self.autoUnblankCheckBox.setChecked(settings.value(u'auto unblank', + QtCore.QVariant(False)).toBool()) self.displayOnMonitorCheck.setChecked(self.screens.display) self.warningCheckBox.setChecked(settings.value(u'blank warning', QtCore.QVariant(False)).toBool()) @@ -326,42 +289,37 @@ class GeneralTab(SettingsTab): QtCore.QVariant(True)).toBool()) self.autoPreviewCheckBox.setChecked(settings.value(u'auto preview', QtCore.QVariant(False)).toBool()) + self.enableLoopCheckBox.setChecked(settings.value(u'enable slide loop', + QtCore.QVariant(True)).toBool()) self.timeoutSpinBox.setValue(settings.value(u'loop delay', QtCore.QVariant(5)).toInt()[0]) - self.currentXValueLabel.setText( - unicode(self.screens.current[u'size'].x())) - self.currentYValueLabel.setText( - unicode(self.screens.current[u'size'].y())) - self.currentHeightValueLabel.setText( - unicode(self.screens.current[u'size'].height())) - self.currentWidthValueLabel.setText( - unicode(self.screens.current[u'size'].width())) self.overrideCheckBox.setChecked(settings.value(u'override position', QtCore.QVariant(False)).toBool()) - self.customXValueEdit.setText(settings.value(u'x position', - QtCore.QVariant(self.screens.current[u'size'].x())).toString()) - self.customYValueEdit.setText(settings.value(u'y position', - QtCore.QVariant(self.screens.current[u'size'].y())).toString()) - self.customHeightValueEdit.setText( - settings.value(u'height', QtCore.QVariant( - self.screens.current[u'size'].height())).toString()) - self.customWidthValueEdit.setText( - settings.value(u'width', QtCore.QVariant( - self.screens.current[u'size'].width())).toString()) + self.customXValueEdit.setValue(settings.value(u'x position', + QtCore.QVariant(self.screens.current[u'size'].x())).toInt()[0]) + self.customYValueEdit.setValue(settings.value(u'y position', + QtCore.QVariant(self.screens.current[u'size'].y())).toInt()[0]) + self.customHeightValueEdit.setValue(settings.value(u'height', + QtCore.QVariant(self.screens.current[u'size'].height())).toInt()[0]) + self.customWidthValueEdit.setValue(settings.value(u'width', + QtCore.QVariant(self.screens.current[u'size'].width())).toInt()[0]) + self.startPausedCheckBox.setChecked(settings.value( + u'audio start paused', QtCore.QVariant(True)).toBool()) settings.endGroup() self.customXValueEdit.setEnabled(self.overrideCheckBox.isChecked()) self.customYValueEdit.setEnabled(self.overrideCheckBox.isChecked()) self.customHeightValueEdit.setEnabled(self.overrideCheckBox.isChecked()) self.customWidthValueEdit.setEnabled(self.overrideCheckBox.isChecked()) + self.display_changed = False def save(self): """ Save the settings from the form """ - self.monitorNumber = self.monitorComboBox.currentIndex() settings = QtCore.QSettings() settings.beginGroup(self.settingsSection) - settings.setValue(u'monitor', QtCore.QVariant(self.monitorNumber)) + settings.setValue(u'monitor', + QtCore.QVariant(self.monitorComboBox.currentIndex())) settings.setValue(u'display on monitor', QtCore.QVariant(self.displayOnMonitorCheck.isChecked())) settings.setValue(u'blank warning', @@ -374,8 +332,12 @@ class GeneralTab(SettingsTab): QtCore.QVariant(self.checkForUpdatesCheckBox.isChecked())) settings.setValue(u'save prompt', QtCore.QVariant(self.saveCheckServiceCheckBox.isChecked())) + settings.setValue(u'auto unblank', + QtCore.QVariant(self.autoUnblankCheckBox.isChecked())) settings.setValue(u'auto preview', QtCore.QVariant(self.autoPreviewCheckBox.isChecked())) + settings.setValue(u'enable slide loop', + QtCore.QVariant(self.enableLoopCheckBox.isChecked())) settings.setValue(u'loop delay', QtCore.QVariant(self.timeoutSpinBox.value())) settings.setValue(u'ccli number', @@ -385,25 +347,20 @@ class GeneralTab(SettingsTab): settings.setValue(u'songselect password', QtCore.QVariant(self.passwordEdit.displayText())) settings.setValue(u'x position', - QtCore.QVariant(self.customXValueEdit.text())) + QtCore.QVariant(self.customXValueEdit.value())) settings.setValue(u'y position', - QtCore.QVariant(self.customYValueEdit.text())) + QtCore.QVariant(self.customYValueEdit.value())) settings.setValue(u'height', - QtCore.QVariant(self.customHeightValueEdit.text())) + QtCore.QVariant(self.customHeightValueEdit.value())) settings.setValue(u'width', - QtCore.QVariant(self.customWidthValueEdit.text())) + QtCore.QVariant(self.customWidthValueEdit.value())) settings.setValue(u'override position', QtCore.QVariant(self.overrideCheckBox.isChecked())) + settings.setValue(u'audio start paused', + QtCore.QVariant(self.startPausedCheckBox.isChecked())) settings.endGroup() - self.screens.display = self.displayOnMonitorCheck.isChecked() - # Monitor Number has changed. - postUpdate = False - if self.screens.monitor_number != self.monitorNumber: - self.screens.monitor_number = self.monitorNumber - self.screens.set_current_display(self.monitorNumber) - postUpdate = True # On save update the screens as well - self.postSetUp(postUpdate) + self.postSetUp(True) def postSetUp(self, postUpdate=False): """ @@ -412,21 +369,23 @@ class GeneralTab(SettingsTab): """ Receiver.send_message(u'slidecontroller_live_spin_delay', self.timeoutSpinBox.value()) - # Reset screens after initial definition - if self.overrideChanged: - self.screens.override[u'size'] = QtCore.QRect( - int(self.customXValueEdit.validText()), - int(self.customYValueEdit.validText()), - int(self.customWidthValueEdit.validText()), - int(self.customHeightValueEdit.validText())) + # Do not continue on start up. + if not postUpdate: + return + self.screens.set_current_display(self.monitorComboBox.currentIndex()) + self.screens.display = self.displayOnMonitorCheck.isChecked() + self.screens.override[u'size'] = QtCore.QRect( + self.customXValueEdit.value(), + self.customYValueEdit.value(), + self.customWidthValueEdit.value(), + self.customHeightValueEdit.value()) if self.overrideCheckBox.isChecked(): self.screens.set_override_display() else: self.screens.reset_current_display() - # Order is important so be careful if you change - if self.overrideChanged or postUpdate: + if self.display_changed: Receiver.send_message(u'config_screen_changed') - self.overrideChanged = False + self.display_changed = False def onOverrideCheckBoxToggled(self, checked): """ @@ -439,10 +398,10 @@ class GeneralTab(SettingsTab): self.customYValueEdit.setEnabled(checked) self.customHeightValueEdit.setEnabled(checked) self.customWidthValueEdit.setEnabled(checked) - self.overrideChanged = True + self.display_changed = True - def onDisplayPositionChanged(self): + def onDisplayChanged(self): """ Called when the width, height, x position or y position has changed. """ - self.overrideChanged = True + self.display_changed = True diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index 72ebe422a..2a16867e5 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.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, Armin Köhler, 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,51 +35,74 @@ from PyQt4 import QtCore, QtGui, QtWebKit from PyQt4.phonon import Phonon from openlp.core.lib import Receiver, build_html, ServiceItem, image_to_byte, \ - build_icon, translate + translate, PluginManager -from openlp.core.ui import HideMode +from openlp.core.ui import HideMode, ScreenList, AlertLocation log = logging.getLogger(__name__) #http://www.steveheffernan.com/html5-video-player/demo-video-player.html #http://html5demos.com/two-videos -class DisplayWidget(QtGui.QGraphicsView): - """ - Customised version of QTableWidget which can respond to keyboard - events. - """ - log.info(u'Display Widget loaded') - - def __init__(self, live, parent=None): - QtGui.QGraphicsView.__init__(self) - self.parent = parent - self.live = live - - -class MainDisplay(DisplayWidget): +class MainDisplay(QtGui.QGraphicsView): """ This is the display screen. """ - def __init__(self, parent, screens, live): - DisplayWidget.__init__(self, live, parent=None) - self.parent = parent - self.screens = screens + def __init__(self, parent, imageManager, live): + if live: + QtGui.QGraphicsView.__init__(self) + # Overwrite the parent() method. + self.parent = lambda: parent + else: + QtGui.QGraphicsView.__init__(self, parent) self.isLive = live - self.alertTab = None + self.imageManager = imageManager + self.screens = ScreenList.get_instance() + self.plugins = PluginManager.get_instance().plugins + self.rebuildCSS = False self.hideMode = None + self.videoHide = False self.override = {} - mainIcon = build_icon(u':/icon/openlp-logo-16x16.png') - self.setWindowIcon(mainIcon) self.retranslateUi() + self.mediaObject = None + if live: + self.audioPlayer = AudioPlayer(self) + else: + self.audioPlayer = None + self.firstTime = True self.setStyleSheet(u'border: 0px; margin: 0px; padding: 0px;') self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.Tool | - QtCore.Qt.WindowStaysOnTopHint) + QtCore.Qt.WindowStaysOnTopHint | + QtCore.Qt.X11BypassWindowManagerHint) + self.setAttribute(QtCore.Qt.WA_DeleteOnClose) if self.isLive: QtCore.QObject.connect(Receiver.get_receiver(), - QtCore.SIGNAL(u'maindisplay_hide'), self.hideDisplay) + QtCore.SIGNAL(u'live_display_hide'), self.hideDisplay) QtCore.QObject.connect(Receiver.get_receiver(), - QtCore.SIGNAL(u'maindisplay_show'), self.showDisplay) + QtCore.SIGNAL(u'live_display_show'), self.showDisplay) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'openlp_phonon_creation'), + self.createMediaObject) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'update_display_css'), self.cssChanged) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'config_updated'), self.configChanged) + + def cssChanged(self): + """ + We may need to rebuild the CSS on the live display. + """ + self.rebuildCSS = True + + def configChanged(self): + """ + Call the plugins to rebuild the Live display CSS as the screen has + not been rebuild on exit of config. + """ + if self.rebuildCSS and self.plugins: + for plugin in self.plugins: + plugin.refreshCss(self.frame) + self.rebuildCSS = False def retranslateUi(self): """ @@ -90,8 +114,7 @@ class MainDisplay(DisplayWidget): """ Set up and build the output screen """ - log.debug(u'Start setup for monitor %s (live = %s)' % - (self.screens.monitor_number, self.isLive)) + log.debug(u'Start MainDisplay setup (live = %s)' % self.isLive) self.usePhonon = QtCore.QSettings().value( u'media/use phonon', QtCore.QVariant(True)).toBool() self.phononActive = False @@ -102,20 +125,18 @@ class MainDisplay(DisplayWidget): self.videoWidget.setVisible(False) self.videoWidget.setGeometry(QtCore.QRect(0, 0, self.screen[u'size'].width(), self.screen[u'size'].height())) - log.debug(u'Setup Phonon for monitor %s' % self.screens.monitor_number) - self.mediaObject = Phonon.MediaObject(self) - self.audio = Phonon.AudioOutput(Phonon.VideoCategory, self.mediaObject) - Phonon.createPath(self.mediaObject, self.videoWidget) - Phonon.createPath(self.mediaObject, self.audio) - QtCore.QObject.connect(self.mediaObject, - QtCore.SIGNAL(u'stateChanged(Phonon::State, Phonon::State)'), - self.videoStart) - log.debug(u'Setup webView for monitor %s' % self.screens.monitor_number) + if self.isLive: + if not self.firstTime: + self.createMediaObject() + log.debug(u'Setup webView') self.webView = QtWebKit.QWebView(self) self.webView.setGeometry(0, 0, self.screen[u'size'].width(), self.screen[u'size'].height()) self.page = self.webView.page() self.frame = self.page.mainFrame() + if self.isLive and log.getEffectiveLevel() == logging.DEBUG: + self.webView.settings().setAttribute( + QtWebKit.QWebSettings.DeveloperExtrasEnabled, True) QtCore.QObject.connect(self.webView, QtCore.SIGNAL(u'loadFinished(bool)'), self.isWebLoaded) self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) @@ -125,82 +146,91 @@ class MainDisplay(DisplayWidget): self.frame.setScrollBarPolicy(QtCore.Qt.Horizontal, QtCore.Qt.ScrollBarAlwaysOff) if self.isLive: - # Build the initial frame. - self.black = QtGui.QImage( - self.screens.current[u'size'].width(), - self.screens.current[u'size'].height(), - QtGui.QImage.Format_ARGB32_Premultiplied) - painter_image = QtGui.QPainter() - painter_image.begin(self.black) - painter_image.fillRect(self.black.rect(), QtCore.Qt.black) # Build the initial frame. image_file = QtCore.QSettings().value(u'advanced/default image', QtCore.QVariant(u':/graphics/openlp-splash-screen.png'))\ .toString() - background_color = QtGui.QColor(QtCore.QSettings().value( + background_color = QtGui.QColor() + background_color.setNamedColor(QtCore.QSettings().value( u'advanced/default color', QtCore.QVariant(u'#ffffff')).toString()) if not background_color.isValid(): background_color = QtCore.Qt.white splash_image = QtGui.QImage(image_file) - initialFrame = QtGui.QImage( - self.screens.current[u'size'].width(), - self.screens.current[u'size'].height(), + self.initialFrame = QtGui.QImage( + self.screen[u'size'].width(), + self.screen[u'size'].height(), QtGui.QImage.Format_ARGB32_Premultiplied) painter_image = QtGui.QPainter() - painter_image.begin(initialFrame) - painter_image.fillRect(initialFrame.rect(), background_color) + painter_image.begin(self.initialFrame) + painter_image.fillRect(self.initialFrame.rect(), background_color) painter_image.drawImage( - (self.screens.current[u'size'].width() - - splash_image.width()) / 2, - (self.screens.current[u'size'].height() - - splash_image.height()) / 2, splash_image) + (self.screen[u'size'].width() - splash_image.width()) / 2, + (self.screen[u'size'].height() - splash_image.height()) / 2, + splash_image) serviceItem = ServiceItem() - serviceItem.bg_image_bytes = image_to_byte(initialFrame) + serviceItem.bg_image_bytes = image_to_byte(self.initialFrame) self.webView.setHtml(build_html(serviceItem, self.screen, - self.alertTab, self.isLive, None)) - self.initialFrame = True + self.isLive, None, plugins=self.plugins)) self.__hideMouse() # To display or not to display? if not self.screen[u'primary']: - self.show() self.primary = False else: self.primary = True - log.debug( - u'Finished setup for monitor %s' % self.screens.monitor_number) + log.debug(u'Finished MainDisplay setup') + + def createMediaObject(self): + self.firstTime = False + log.debug(u'Creating Phonon objects - Start for %s', self.isLive) + self.mediaObject = Phonon.MediaObject(self) + self.audio = Phonon.AudioOutput(Phonon.VideoCategory, self.mediaObject) + Phonon.createPath(self.mediaObject, self.videoWidget) + Phonon.createPath(self.mediaObject, self.audio) + QtCore.QObject.connect(self.mediaObject, + QtCore.SIGNAL(u'stateChanged(Phonon::State, Phonon::State)'), + self.videoState) + QtCore.QObject.connect(self.mediaObject, + QtCore.SIGNAL(u'finished()'), + self.videoFinished) + QtCore.QObject.connect(self.mediaObject, + QtCore.SIGNAL(u'tick(qint64)'), + self.videoTick) + log.debug(u'Creating Phonon objects - Finished for %s', self.isLive) def text(self, slide): """ Add the slide text from slideController - `slide` + ``slide`` The slide text to be displayed """ log.debug(u'text to display') # Wait for the webview to update before displaying text. while not self.webLoaded: Receiver.send_message(u'openlp_process_events') - self.frame.evaluateJavaScript(u'show_text("%s")' % \ + self.setGeometry(self.screen[u'size']) + self.frame.evaluateJavaScript(u'show_text("%s")' % slide.replace(u'\\', u'\\\\').replace(u'\"', u'\\\"')) - return self.preview() - def alert(self, text): + def alert(self, text, location): """ - Add the alert text + Display an alert. - `slide` - The slide text to be displayed + ``text`` + The text to be displayed. """ log.debug(u'alert to display') - if self.height() != self.screen[u'size'].height() \ - or not self.isVisible() or self.videoWidget.isVisible(): + if self.height() != self.screen[u'size'].height() or not \ + self.isVisible() or self.videoWidget.isVisible(): shrink = True + js = u'show_alert("%s", "%s")' % ( + text.replace(u'\\', u'\\\\').replace(u'\"', u'\\\"'), + u'top') else: shrink = False - js = u'show_alert("%s", "%s")' % ( - text.replace(u'\\', u'\\\\').replace(u'\"', u'\\\"'), - u'top' if shrink else u'') + js = u'show_alert("%s", "")' % ( + text.replace(u'\\', u'\\\\').replace(u'\"', u'\\\"')) height = self.frame.evaluateJavaScript(js) if shrink: if self.phononActive: @@ -208,64 +238,70 @@ class MainDisplay(DisplayWidget): else: shrinkItem = self if text: - shrinkItem.resize(self.width(), int(height.toString())) + alert_height = int(height.toString()) + shrinkItem.resize(self.width(), alert_height) shrinkItem.setVisible(True) + if location == AlertLocation.Middle: + shrinkItem.move(self.screen[u'size'].left(), + (self.screen[u'size'].height() - alert_height) / 2) + elif location == AlertLocation.Bottom: + shrinkItem.move(self.screen[u'size'].left(), + self.screen[u'size'].height() - alert_height) else: shrinkItem.setVisible(False) - shrinkItem.resize(self.screen[u'size'].width(), - self.screen[u'size'].height()) + self.setGeometry(self.screen[u'size']) - def directImage(self, name, path): + def directImage(self, name, path, background): """ - API for replacement backgrounds so Images are added directly to cache + API for replacement backgrounds so Images are added directly to cache. """ - self.imageManager.add_image(name, path) - self.image(name) + self.imageManager.add_image(name, path, u'image', background) if hasattr(self, u'serviceItem'): self.override[u'image'] = name self.override[u'theme'] = self.serviceItem.themedata.theme_name + self.image(name) + # Update the preview frame. + if self.isLive: + self.parent().updatePreview() + return True + return False def image(self, name): """ Add an image as the background. The image has already been added to the cache. - `Image` - The name of the image to be displayed + ``Image`` + The name of the image to be displayed. """ log.debug(u'image to display') image = self.imageManager.get_image_bytes(name) self.resetVideo() self.displayImage(image) - return self.preview() def displayImage(self, image): """ Display an image, as is. """ + self.setGeometry(self.screen[u'size']) if image: js = u'show_image("data:image/png;base64,%s");' % image else: js = u'show_image("");' self.frame.evaluateJavaScript(js) - # Update the preview frame. - if self.isLive: - Receiver.send_message(u'maindisplay_active') def resetImage(self): """ - Reset the backgound image to the service item image. - Used after Image plugin has changed the background + Reset the backgound image to the service item image. Used after the + image plugin has changed the background. """ log.debug(u'resetImage') if hasattr(self, u'serviceItem'): self.displayImage(self.serviceItem.bg_image_bytes) else: self.displayImage(None) + # clear the cache self.override = {} - # Update the preview frame. - if self.isLive: - Receiver.send_message(u'maindisplay_active') def resetVideo(self): """ @@ -281,9 +317,6 @@ class MainDisplay(DisplayWidget): else: self.frame.evaluateJavaScript(u'show_video("close");') self.override = {} - # Update the preview frame. - if self.isLive: - Receiver.send_message(u'maindisplay_active') def videoPlay(self): """ @@ -334,8 +367,14 @@ class MainDisplay(DisplayWidget): """ Loads and starts a video to run with the option of sound """ + # We request a background video but have no service Item + if isBackground and not hasattr(self, u'serviceItem'): + return False + if not self.mediaObject: + self.createMediaObject() log.debug(u'video') self.webLoaded = True + self.setGeometry(self.screen[u'size']) # We are running a background theme self.override[u'theme'] = u'' self.override[u'video'] = True @@ -349,22 +388,43 @@ class MainDisplay(DisplayWidget): self.mediaObject.stop() self.mediaObject.clearQueue() self.mediaObject.setCurrentSource(Phonon.MediaSource(videoPath)) + # Need the timer to trigger set the trigger to 200ms + # Value taken from web documentation. + if self.serviceItem.end_time != 0: + self.mediaObject.setTickInterval(200) self.mediaObject.play() self.webView.setVisible(False) self.videoWidget.setVisible(True) self.audio.setVolume(vol) - # Update the preview frame. - if self.isLive: - Receiver.send_message(u'maindisplay_active') - return self.preview() + return True - def videoStart(self, newState, oldState): + def videoState(self, newState, oldState): """ Start the video at a predetermined point. """ - if newState == Phonon.PlayingState: + if newState == Phonon.PlayingState \ + and oldState != Phonon.PausedState \ + and self.serviceItem.start_time > 0: + # set start time in milliseconds self.mediaObject.seek(self.serviceItem.start_time * 1000) + def videoFinished(self): + """ + Blank the Video when it has finished so the final frame is not left + hanging + """ + self.videoStop() + self.hideDisplay(HideMode.Blank) + self.phononActive = False + self.videoHide = True + + def videoTick(self, tick): + """ + Triggered on video tick every 200 milli seconds + """ + if tick > self.serviceItem.end_time * 1000: + self.videoFinished() + def isWebLoaded(self): """ Called by webView event to show display is fully loaded @@ -396,24 +456,30 @@ class MainDisplay(DisplayWidget): if self.hideMode: self.hideDisplay(self.hideMode) else: - self.setVisible(True) - preview = QtGui.QImage(self.screen[u'size'].width(), - self.screen[u'size'].height(), - QtGui.QImage.Format_ARGB32_Premultiplied) + # Single screen active + if self.screens.display_count == 1: + # Only make visible if setting enabled + if QtCore.QSettings().value(u'general/display on monitor', + QtCore.QVariant(True)).toBool(): + self.setVisible(True) + else: + self.setVisible(True) + preview = QtGui.QPixmap(self.screen[u'size'].width(), + self.screen[u'size'].height()) painter = QtGui.QPainter(preview) painter.setRenderHint(QtGui.QPainter.Antialiasing) self.frame.render(painter) painter.end() return preview - def buildHtml(self, serviceItem): + def buildHtml(self, serviceItem, image=None): """ Store the serviceItem and build the new HTML from it. Add the HTML to the display """ log.debug(u'buildHtml') self.webLoaded = False - self.initialFrame = False + self.initialFrame = None self.serviceItem = serviceItem background = None # We have an image override so keep the image till the theme changes @@ -422,17 +488,23 @@ class MainDisplay(DisplayWidget): if u'video' in self.override: Receiver.send_message(u'video_background_replaced') self.override = {} + # We have a different theme. elif self.override[u'theme'] != serviceItem.themedata.theme_name: Receiver.send_message(u'live_theme_changed') self.override = {} else: + # replace the background background = self.imageManager. \ get_image_bytes(self.override[u'image']) if self.serviceItem.themedata.background_filename: self.serviceItem.bg_image_bytes = self.imageManager. \ get_image_bytes(self.serviceItem.themedata.theme_name) - html = build_html(self.serviceItem, self.screen, self.alertTab, - self.isLive, background) + if image: + image_bytes = self.imageManager.get_image_bytes(image) + else: + image_bytes = None + html = build_html(self.serviceItem, self.screen, self.isLive, + background, image_bytes, self.plugins) log.debug(u'buildHtml - pre setHtml') self.webView.setHtml(html) log.debug(u'buildHtml - post setHtml') @@ -440,7 +512,15 @@ class MainDisplay(DisplayWidget): self.footer(serviceItem.foot_text) # if was hidden keep it hidden if self.hideMode and self.isLive: - self.hideDisplay(self.hideMode) + if QtCore.QSettings().value(u'general/auto unblank', + QtCore.QVariant(False)).toBool(): + Receiver.send_message(u'slidecontroller_live_unblank') + else: + self.hideDisplay(self.hideMode) + # display hidden for video end we have a new item so must be shown + if self.videoHide and self.isLive: + self.videoHide = False + self.showDisplay() self.__hideMouse() def footer(self, text): @@ -490,7 +570,7 @@ class MainDisplay(DisplayWidget): self.hideMode = None # Trigger actions when display is active again if self.isLive: - Receiver.send_message(u'maindisplay_active') + Receiver.send_message(u'live_display_active') def __hideMouse(self): # Hide mouse cursor when moved over display if enabled in settings @@ -519,61 +599,75 @@ class AudioPlayer(QtCore.QObject): """ log.debug(u'AudioPlayer Initialisation started') QtCore.QObject.__init__(self, parent) - self.message = None + self.currentIndex = -1 + self.playlist = [] self.mediaObject = Phonon.MediaObject() self.audioObject = Phonon.AudioOutput(Phonon.VideoCategory) Phonon.createPath(self.mediaObject, self.audioObject) + QtCore.QObject.connect(self.mediaObject, + QtCore.SIGNAL(u'aboutToFinish()'), self.onAboutToFinish) - def setup(self): - """ - Sets up the Audio Player for use - """ - log.debug(u'AudioPlayer Setup') - - def close(self): + def __del__(self): """ Shutting down so clean up connections """ - self.onMediaStop() + self.stop() for path in self.mediaObject.outputPaths(): path.disconnect() - def onMediaQueue(self, message): + def onAboutToFinish(self): """ - Set up a video to play from the serviceitem. + Just before the audio player finishes the current track, queue the next + item in the playlist, if there is one. """ - log.debug(u'AudioPlayer Queue new media message %s' % message) - mfile = os.path.join(message[0].get_frame_path(), - message[0].get_frame_title()) - self.mediaObject.setCurrentSource(Phonon.MediaSource(mfile)) - self.onMediaPlay() + self.currentIndex += 1 + if len(self.playlist) > self.currentIndex: + self.mediaObject.enqueue(self.playlist[self.currentIndex]) - def onMediaPlay(self): + def connectVolumeSlider(self, slider): + slider.setAudioOutput(self.audioObject) + + def reset(self): """ - We want to play the play so start it + Reset the audio player, clearing the playlist and the queue. """ - log.debug(u'AudioPlayer _play called') + self.currentIndex = -1 + self.playlist = [] + self.stop() + self.mediaObject.clear() + + def play(self): + """ + We want to play the file so start it + """ + log.debug(u'AudioPlayer.play() called') + if self.currentIndex == -1: + self.onAboutToFinish() self.mediaObject.play() - def onMediaPause(self): + def pause(self): """ Pause the Audio """ - log.debug(u'AudioPlayer Media paused by user') + log.debug(u'AudioPlayer.pause() called') self.mediaObject.pause() - def onMediaStop(self): + def stop(self): """ Stop the Audio and clean up """ - log.debug(u'AudioPlayer Media stopped by user') - self.message = None + log.debug(u'AudioPlayer.stop() called') self.mediaObject.stop() - self.onMediaFinish() - def onMediaFinish(self): + def addToPlaylist(self, filenames): """ - Clean up the Object queue + Add another file to the playlist. + + ``filename`` + The file to add to the playlist. """ - log.debug(u'AudioPlayer Reached end of media playlist') - self.mediaObject.clearQueue() + if not isinstance(filenames, list): + filenames = [filenames] + for filename in filenames: + self.playlist.append(Phonon.MediaSource(filename)) + diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index bb35936d5..47570cd8a 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.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, Armin Köhler, 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,18 +26,27 @@ ############################################################################### import logging +import os +import sys +import shutil +from tempfile import gettempdir +from datetime import datetime from PyQt4 import QtCore, QtGui -from openlp.core.lib import RenderManager, build_icon, OpenLPDockWidget, \ - SettingsManager, PluginManager, Receiver, translate +from openlp.core.lib import Renderer, build_icon, OpenLPDockWidget, \ + PluginManager, Receiver, translate, ImageManager, PluginStatus, \ + SettingsManager from openlp.core.lib.ui import UiStrings, base_action, checkable_action, \ - icon_action + icon_action, shortcut_action from openlp.core.ui import AboutForm, SettingsForm, ServiceManager, \ ThemeManager, SlideController, PluginForm, MediaDockManager, \ - ShortcutListForm, DisplayTagForm + ShortcutListForm, FormattingTagForm from openlp.core.utils import AppLocation, add_actions, LanguageManager, \ - ActionList + get_application_version, delete_file +from openlp.core.utils.actions import ActionList, CategoryOrder +from openlp.core.ui.firsttimeform import FirstTimeForm +from openlp.core.ui import ScreenList log = logging.getLogger(__name__) @@ -60,98 +70,104 @@ MEDIA_MANAGER_STYLE = """ } """ +PROGRESSBAR_STYLE = """ + QProgressBar{ + height: 10px; + } +""" + class Ui_MainWindow(object): def setupUi(self, mainWindow): """ Set up the user interface """ mainWindow.setObjectName(u'MainWindow') - mainWindow.resize(self.settingsmanager.width, - self.settingsmanager.height) - mainWindow.setWindowIcon(build_icon(u':/icon/openlp-logo-16x16.png')) + mainWindow.setWindowIcon(build_icon(u':/icon/openlp-logo-64x64.png')) mainWindow.setDockNestingEnabled(True) # Set up the main container, which contains all the other form widgets. - self.MainContent = QtGui.QWidget(mainWindow) - self.MainContent.setObjectName(u'MainContent') - self.mainContentLayout = QtGui.QHBoxLayout(self.MainContent) + self.mainContent = QtGui.QWidget(mainWindow) + self.mainContent.setObjectName(u'mainContent') + self.mainContentLayout = QtGui.QHBoxLayout(self.mainContent) self.mainContentLayout.setSpacing(0) self.mainContentLayout.setMargin(0) self.mainContentLayout.setObjectName(u'mainContentLayout') - mainWindow.setCentralWidget(self.MainContent) - self.controlSplitter = QtGui.QSplitter(self.MainContent) + mainWindow.setCentralWidget(self.mainContent) + self.controlSplitter = QtGui.QSplitter(self.mainContent) self.controlSplitter.setOrientation(QtCore.Qt.Horizontal) self.controlSplitter.setObjectName(u'controlSplitter') self.mainContentLayout.addWidget(self.controlSplitter) # Create slide controllers - self.previewController = SlideController(self, self.settingsmanager, - self.screens) - self.liveController = SlideController(self, self.settingsmanager, - self.screens, True) + self.previewController = SlideController(self) + self.liveController = SlideController(self, True) previewVisible = QtCore.QSettings().value( u'user interface/preview panel', QtCore.QVariant(True)).toBool() self.previewController.panel.setVisible(previewVisible) liveVisible = QtCore.QSettings().value(u'user interface/live panel', QtCore.QVariant(True)).toBool() + panelLocked = QtCore.QSettings().value(u'user interface/lock panel', + QtCore.QVariant(False)).toBool() self.liveController.panel.setVisible(liveVisible) # Create menu - self.MenuBar = QtGui.QMenuBar(mainWindow) - self.MenuBar.setObjectName(u'MenuBar') - self.FileMenu = QtGui.QMenu(self.MenuBar) - self.FileMenu.setObjectName(u'FileMenu') - self.FileImportMenu = QtGui.QMenu(self.FileMenu) - self.FileImportMenu.setObjectName(u'FileImportMenu') - self.FileExportMenu = QtGui.QMenu(self.FileMenu) - self.FileExportMenu.setObjectName(u'FileExportMenu') + self.menuBar = QtGui.QMenuBar(mainWindow) + self.menuBar.setObjectName(u'menuBar') + self.fileMenu = QtGui.QMenu(self.menuBar) + self.fileMenu.setObjectName(u'fileMenu') + self.recentFilesMenu = QtGui.QMenu(self.fileMenu) + self.recentFilesMenu.setObjectName(u'recentFilesMenu') + self.fileImportMenu = QtGui.QMenu(self.fileMenu) + self.fileImportMenu.setObjectName(u'fileImportMenu') + self.fileExportMenu = QtGui.QMenu(self.fileMenu) + self.fileExportMenu.setObjectName(u'fileExportMenu') # View Menu - self.viewMenu = QtGui.QMenu(self.MenuBar) + self.viewMenu = QtGui.QMenu(self.menuBar) self.viewMenu.setObjectName(u'viewMenu') - self.ViewModeMenu = QtGui.QMenu(self.viewMenu) - self.ViewModeMenu.setObjectName(u'ViewModeMenu') + self.viewModeMenu = QtGui.QMenu(self.viewMenu) + self.viewModeMenu.setObjectName(u'viewModeMenu') # Tools Menu - self.ToolsMenu = QtGui.QMenu(self.MenuBar) - self.ToolsMenu.setObjectName(u'ToolsMenu') + self.toolsMenu = QtGui.QMenu(self.menuBar) + self.toolsMenu.setObjectName(u'toolsMenu') # Settings Menu - self.SettingsMenu = QtGui.QMenu(self.MenuBar) - self.SettingsMenu.setObjectName(u'SettingsMenu') - self.SettingsLanguageMenu = QtGui.QMenu(self.SettingsMenu) - self.SettingsLanguageMenu.setObjectName(u'SettingsLanguageMenu') + self.settingsMenu = QtGui.QMenu(self.menuBar) + self.settingsMenu.setObjectName(u'settingsMenu') + self.settingsLanguageMenu = QtGui.QMenu(self.settingsMenu) + self.settingsLanguageMenu.setObjectName(u'settingsLanguageMenu') # Help Menu - self.HelpMenu = QtGui.QMenu(self.MenuBar) - self.HelpMenu.setObjectName(u'HelpMenu') - mainWindow.setMenuBar(self.MenuBar) - self.StatusBar = QtGui.QStatusBar(mainWindow) - self.StatusBar.setObjectName(u'StatusBar') - mainWindow.setStatusBar(self.StatusBar) - self.DefaultThemeLabel = QtGui.QLabel(self.StatusBar) - self.DefaultThemeLabel.setObjectName(u'DefaultThemeLabel') - self.StatusBar.addPermanentWidget(self.DefaultThemeLabel) + self.helpMenu = QtGui.QMenu(self.menuBar) + self.helpMenu.setObjectName(u'helpMenu') + mainWindow.setMenuBar(self.menuBar) + self.statusBar = QtGui.QStatusBar(mainWindow) + self.statusBar.setObjectName(u'statusBar') + mainWindow.setStatusBar(self.statusBar) + self.loadProgressBar = QtGui.QProgressBar(self.statusBar) + self.loadProgressBar.setObjectName(u'loadProgressBar') + self.statusBar.addPermanentWidget(self.loadProgressBar) + self.loadProgressBar.hide() + self.loadProgressBar.setValue(0) + self.loadProgressBar.setStyleSheet(PROGRESSBAR_STYLE) + self.defaultThemeLabel = QtGui.QLabel(self.statusBar) + self.defaultThemeLabel.setObjectName(u'defaultThemeLabel') + self.statusBar.addPermanentWidget(self.defaultThemeLabel) # Create the MediaManager self.mediaManagerDock = OpenLPDockWidget(mainWindow, u'mediaManagerDock', u':/system/system_mediamanager.png') self.mediaManagerDock.setStyleSheet(MEDIA_MANAGER_STYLE) - self.mediaManagerDock.setMinimumWidth( - self.settingsmanager.mainwindow_left) # Create the media toolbox - self.MediaToolBox = QtGui.QToolBox(self.mediaManagerDock) - self.MediaToolBox.setObjectName(u'MediaToolBox') - self.mediaManagerDock.setWidget(self.MediaToolBox) + self.mediaToolBox = QtGui.QToolBox(self.mediaManagerDock) + self.mediaToolBox.setObjectName(u'mediaToolBox') + self.mediaManagerDock.setWidget(self.mediaToolBox) mainWindow.addDockWidget(QtCore.Qt.LeftDockWidgetArea, self.mediaManagerDock) # Create the service manager self.serviceManagerDock = OpenLPDockWidget(mainWindow, u'serviceManagerDock', u':/system/system_servicemanager.png') - self.serviceManagerDock.setMinimumWidth( - self.settingsmanager.mainwindow_right) - self.ServiceManagerContents = ServiceManager(mainWindow, + self.serviceManagerContents = ServiceManager(mainWindow, self.serviceManagerDock) - self.serviceManagerDock.setWidget(self.ServiceManagerContents) + self.serviceManagerDock.setWidget(self.serviceManagerContents) mainWindow.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.serviceManagerDock) # Create the theme manager self.themeManagerDock = OpenLPDockWidget(mainWindow, u'themeManagerDock', u':/system/system_thememanager.png') - self.themeManagerDock.setMinimumWidth( - self.settingsmanager.mainwindow_right) self.themeManagerContents = ThemeManager(mainWindow, self.themeManagerDock) self.themeManagerContents.setObjectName(u'themeManagerContents') @@ -159,297 +175,349 @@ class Ui_MainWindow(object): mainWindow.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.themeManagerDock) # Create the menu items - self.FileNewItem = icon_action(mainWindow, u'FileNewItem', - u':/general/general_new.png') - mainWindow.actionList.add_action(self.FileNewItem, u'File') - self.FileOpenItem = icon_action(mainWindow, u'FileOpenItem', - u':/general/general_open.png') - mainWindow.actionList.add_action(self.FileOpenItem, u'File') - self.FileSaveItem = icon_action(mainWindow, u'FileSaveItem', - u':/general/general_save.png') - mainWindow.actionList.add_action(self.FileSaveItem, u'File') - self.FileSaveAsItem = base_action(mainWindow, u'FileSaveAsItem') - mainWindow.actionList.add_action(self.FileSaveAsItem, u'File') - self.printServiceOrderItem = base_action( - mainWindow, u'printServiceItem') - mainWindow.actionList.add_action( - self.printServiceOrderItem, u'Print Service Order') - self.FileExitItem = icon_action(mainWindow, u'FileExitItem', - u':/system/system_exit.png') - mainWindow.actionList.add_action(self.FileExitItem, u'File') - self.ImportThemeItem = base_action(mainWindow, u'ImportThemeItem') - mainWindow.actionList.add_action(self.ImportThemeItem, u'Import') - self.ImportLanguageItem = base_action(mainWindow, u'ImportLanguageItem') - mainWindow.actionList.add_action(self.ImportLanguageItem, u'Import') - self.ExportThemeItem = base_action(mainWindow, u'ExportThemeItem') - mainWindow.actionList.add_action(self.ExportThemeItem, u'Export') - self.ExportLanguageItem = base_action(mainWindow, u'ExportLanguageItem') - mainWindow.actionList.add_action(self.ExportLanguageItem, u'Export') - self.ViewMediaManagerItem = icon_action(mainWindow, - u'ViewMediaManagerItem', u':/system/system_mediamanager.png', - self.mediaManagerDock.isVisible()) - self.ViewThemeManagerItem = icon_action(mainWindow, - u'ViewThemeManagerItem', u':/system/system_thememanager.png', - self.themeManagerDock.isVisible()) - mainWindow.actionList.add_action(self.ViewMediaManagerItem, u'View') - self.ViewServiceManagerItem = icon_action(mainWindow, - u'ViewServiceManagerItem', u':/system/system_servicemanager.png', - self.serviceManagerDock.isVisible()) - mainWindow.actionList.add_action(self.ViewServiceManagerItem, u'View') - self.ViewPreviewPanel = checkable_action(mainWindow, - u'ViewPreviewPanel', previewVisible) - mainWindow.actionList.add_action(self.ViewPreviewPanel, u'View') - self.ViewLivePanel = checkable_action(mainWindow, u'ViewLivePanel', - liveVisible) - mainWindow.actionList.add_action(self.ViewLivePanel, u'View') - self.ModeDefaultItem = checkable_action(mainWindow, u'ModeDefaultItem') - mainWindow.actionList.add_action(self.ModeDefaultItem, u'View Mode') - self.ModeSetupItem = checkable_action(mainWindow, u'ModeLiveItem') - mainWindow.actionList.add_action(self.ModeSetupItem, u'View Mode') - self.ModeLiveItem = checkable_action(mainWindow, u'ModeLiveItem', True) - mainWindow.actionList.add_action(self.ModeLiveItem, u'View Mode') - self.ModeGroup = QtGui.QActionGroup(mainWindow) - self.ModeGroup.addAction(self.ModeDefaultItem) - self.ModeGroup.addAction(self.ModeSetupItem) - self.ModeGroup.addAction(self.ModeLiveItem) - self.ModeDefaultItem.setChecked(True) - self.ToolsAddToolItem = icon_action(mainWindow, u'ToolsAddToolItem', - u':/tools/tools_add.png') - # Hide the entry, as it does not have any functionality yet. - self.ToolsAddToolItem.setVisible(False) - mainWindow.actionList.add_action(self.ToolsAddToolItem, u'Tools') - self.ToolsOpenDataFolder = icon_action(mainWindow, - u'ToolsOpenDataFolder', u':/general/general_open.png') - mainWindow.actionList.add_action(self.ToolsOpenDataFolder, u'Tools') - self.settingsPluginListItem = icon_action(mainWindow, - u'settingsPluginListItem', u':/system/settings_plugin_list.png') - mainWindow.actionList.add_action(self.settingsPluginListItem, - u'Settings') + action_list = ActionList.get_instance() + action_list.add_category(UiStrings().File, CategoryOrder.standardMenu) + self.fileNewItem = shortcut_action(mainWindow, u'fileNewItem', + [QtGui.QKeySequence(u'Ctrl+N')], + self.serviceManagerContents.onNewServiceClicked, + u':/general/general_new.png', category=UiStrings().File) + self.fileOpenItem = shortcut_action(mainWindow, u'fileOpenItem', + [QtGui.QKeySequence(u'Ctrl+O')], + self.serviceManagerContents.onLoadServiceClicked, + u':/general/general_open.png', category=UiStrings().File) + self.fileSaveItem = shortcut_action(mainWindow, u'fileSaveItem', + [QtGui.QKeySequence(u'Ctrl+S')], + self.serviceManagerContents.saveFile, + u':/general/general_save.png', category=UiStrings().File) + self.fileSaveAsItem = shortcut_action(mainWindow, u'fileSaveAsItem', + [QtGui.QKeySequence(u'Ctrl+Shift+S')], + self.serviceManagerContents.saveFileAs, category=UiStrings().File) + self.printServiceOrderItem = shortcut_action(mainWindow, + u'printServiceItem', [QtGui.QKeySequence(u'Ctrl+P')], + self.serviceManagerContents.printServiceOrder, + category=UiStrings().File) + self.fileExitItem = shortcut_action(mainWindow, u'fileExitItem', + [QtGui.QKeySequence(u'Alt+F4')], mainWindow.close, + u':/system/system_exit.png', category=UiStrings().File) + action_list.add_category(UiStrings().Import, CategoryOrder.standardMenu) + self.importThemeItem = base_action( + mainWindow, u'importThemeItem', UiStrings().Import) + self.importLanguageItem = base_action( + mainWindow, u'importLanguageItem')#, UiStrings().Import) + action_list.add_category(UiStrings().Export, CategoryOrder.standardMenu) + self.exportThemeItem = base_action( + mainWindow, u'exportThemeItem', UiStrings().Export) + self.exportLanguageItem = base_action( + mainWindow, u'exportLanguageItem')#, UiStrings().Export) + action_list.add_category(UiStrings().View, CategoryOrder.standardMenu) + self.viewMediaManagerItem = shortcut_action(mainWindow, + u'viewMediaManagerItem', [QtGui.QKeySequence(u'F8')], + self.toggleMediaManager, u':/system/system_mediamanager.png', + self.mediaManagerDock.isVisible(), UiStrings().View) + self.viewThemeManagerItem = shortcut_action(mainWindow, + u'viewThemeManagerItem', [QtGui.QKeySequence(u'F10')], + self.toggleThemeManager, u':/system/system_thememanager.png', + self.themeManagerDock.isVisible(), UiStrings().View) + self.viewServiceManagerItem = shortcut_action(mainWindow, + u'viewServiceManagerItem', [QtGui.QKeySequence(u'F9')], + self.toggleServiceManager, u':/system/system_servicemanager.png', + self.serviceManagerDock.isVisible(), UiStrings().View) + self.viewPreviewPanel = shortcut_action(mainWindow, + u'viewPreviewPanel', [QtGui.QKeySequence(u'F11')], + self.setPreviewPanelVisibility, checked=previewVisible, + category=UiStrings().View) + self.viewLivePanel = shortcut_action(mainWindow, u'viewLivePanel', + [QtGui.QKeySequence(u'F12')], self.setLivePanelVisibility, + checked=liveVisible, category=UiStrings().View) + self.lockPanel = shortcut_action(mainWindow, u'lockPanel', + None, self.setLockPanel, + checked=panelLocked, category=None) + action_list.add_category(UiStrings().ViewMode, + CategoryOrder.standardMenu) + self.modeDefaultItem = checkable_action( + mainWindow, u'modeDefaultItem', category=UiStrings().ViewMode) + self.modeSetupItem = checkable_action( + mainWindow, u'modeSetupItem', category=UiStrings().ViewMode) + self.modeLiveItem = checkable_action( + mainWindow, u'modeLiveItem', True, UiStrings().ViewMode) + self.modeGroup = QtGui.QActionGroup(mainWindow) + self.modeGroup.addAction(self.modeDefaultItem) + self.modeGroup.addAction(self.modeSetupItem) + self.modeGroup.addAction(self.modeLiveItem) + self.modeDefaultItem.setChecked(True) + action_list.add_category(UiStrings().Tools, CategoryOrder.standardMenu) + self.toolsAddToolItem = icon_action(mainWindow, u'toolsAddToolItem', + u':/tools/tools_add.png', category=UiStrings().Tools) + self.toolsOpenDataFolder = icon_action(mainWindow, + u'toolsOpenDataFolder', u':/general/general_open.png', + category=UiStrings().Tools) + self.toolsFirstTimeWizard = icon_action(mainWindow, + u'toolsFirstTimeWizard', u':/general/general_revert.png', + category=UiStrings().Tools) + self.updateThemeImages = base_action(mainWindow, + u'updateThemeImages', category=UiStrings().Tools) + action_list.add_category(UiStrings().Settings, + CategoryOrder.standardMenu) + self.settingsPluginListItem = shortcut_action(mainWindow, + u'settingsPluginListItem', [QtGui.QKeySequence(u'Alt+F7')], + self.onPluginItemClicked, u':/system/settings_plugin_list.png', + category=UiStrings().Settings) # i18n Language Items - self.AutoLanguageItem = checkable_action(mainWindow, - u'AutoLanguageItem', LanguageManager.auto_language) - mainWindow.actionList.add_action(self.AutoLanguageItem, u'Settings') - self.LanguageGroup = QtGui.QActionGroup(mainWindow) - self.LanguageGroup.setExclusive(True) - self.LanguageGroup.setObjectName(u'LanguageGroup') - self.LanguageGroup.setDisabled(LanguageManager.auto_language) + self.autoLanguageItem = checkable_action(mainWindow, + u'autoLanguageItem', LanguageManager.auto_language) + self.languageGroup = QtGui.QActionGroup(mainWindow) + self.languageGroup.setExclusive(True) + self.languageGroup.setObjectName(u'languageGroup') + add_actions(self.languageGroup, [self.autoLanguageItem]) qmList = LanguageManager.get_qm_list() savedLanguage = LanguageManager.get_language() for key in sorted(qmList.keys()): languageItem = checkable_action( mainWindow, key, qmList[key] == savedLanguage) - add_actions(self.LanguageGroup, [languageItem]) - self.SettingsShortcutsItem = icon_action(mainWindow, - u'SettingsShortcutsItem', - u':/system/system_configure_shortcuts.png') - self.DisplayTagItem = icon_action(mainWindow, - u'DisplayTagItem', u':/system/tag_editor.png') - self.SettingsConfigureItem = icon_action(mainWindow, - u'SettingsConfigureItem', u':/system/system_settings.png') - mainWindow.actionList.add_action(self.SettingsShortcutsItem, - u'Settings') - self.HelpDocumentationItem = icon_action(mainWindow, - u'HelpDocumentationItem', u':/system/system_help_contents.png') - self.HelpDocumentationItem.setEnabled(False) - mainWindow.actionList.add_action(self.HelpDocumentationItem, u'Help') - self.HelpAboutItem = icon_action(mainWindow, u'HelpAboutItem', - u':/system/system_about.png') - mainWindow.actionList.add_action(self.HelpAboutItem, u'Help') - self.HelpOnlineHelpItem = base_action(mainWindow, u'HelpOnlineHelpItem') - self.HelpOnlineHelpItem.setEnabled(False) - mainWindow.actionList.add_action(self.HelpOnlineHelpItem, u'Help') - self.helpWebSiteItem = base_action(mainWindow, u'helpWebSiteItem') - mainWindow.actionList.add_action(self.helpWebSiteItem, u'Help') - add_actions(self.FileImportMenu, - (self.ImportThemeItem, self.ImportLanguageItem)) - add_actions(self.FileExportMenu, - (self.ExportThemeItem, self.ExportLanguageItem)) - self.FileMenuActions = (self.FileNewItem, self.FileOpenItem, - self.FileSaveItem, self.FileSaveAsItem, None, - self.printServiceOrderItem, None, self.FileImportMenu.menuAction(), - self.FileExportMenu.menuAction(), self.FileExitItem) - add_actions(self.ViewModeMenu, (self.ModeDefaultItem, - self.ModeSetupItem, self.ModeLiveItem)) - add_actions(self.viewMenu, (self.ViewModeMenu.menuAction(), - None, self.ViewMediaManagerItem, self.ViewServiceManagerItem, - self.ViewThemeManagerItem, None, self.ViewPreviewPanel, - self.ViewLivePanel)) + add_actions(self.languageGroup, [languageItem]) + self.settingsShortcutsItem = icon_action(mainWindow, + u'settingsShortcutsItem', + u':/system/system_configure_shortcuts.png', + category=UiStrings().Settings) + # Formatting Tags were also known as display tags. + self.formattingTagItem = icon_action(mainWindow, + u'displayTagItem', u':/system/tag_editor.png', + category=UiStrings().Settings) + self.settingsConfigureItem = icon_action(mainWindow, + u'settingsConfigureItem', u':/system/system_settings.png', + category=UiStrings().Settings) + self.settingsImportItem = base_action(mainWindow, + u'settingsImportItem', category=UiStrings().Settings) + self.settingsExportItem = base_action(mainWindow, + u'settingsExportItem', category=UiStrings().Settings) + action_list.add_category(UiStrings().Help, CategoryOrder.standardMenu) + self.aboutItem = shortcut_action(mainWindow, u'aboutItem', + [QtGui.QKeySequence(u'Ctrl+F1')], self.onAboutItemClicked, + u':/system/system_about.png', category=UiStrings().Help) + if os.name == u'nt': + self.localHelpFile = os.path.join( + AppLocation.get_directory(AppLocation.AppDir), 'OpenLP.chm') + self.offlineHelpItem = shortcut_action( + mainWindow, u'offlineHelpItem', [QtGui.QKeySequence(u'F1')], + self.onOfflineHelpClicked, + u':/system/system_help_contents.png', category=UiStrings().Help) + self.onlineHelpItem = shortcut_action( + mainWindow, u'onlineHelpItem', + [QtGui.QKeySequence(u'Alt+F1')], self.onOnlineHelpClicked, + u':/system/system_online_help.png', category=UiStrings().Help) + self.webSiteItem = base_action( + mainWindow, u'webSiteItem', category=UiStrings().Help) + add_actions(self.fileImportMenu, (self.settingsImportItem, None, + self.importThemeItem, self.importLanguageItem)) + add_actions(self.fileExportMenu, (self.settingsExportItem, None, + self.exportThemeItem, self.exportLanguageItem)) + add_actions(self.fileMenu, (self.fileNewItem, self.fileOpenItem, + self.fileSaveItem, self.fileSaveAsItem, + self.recentFilesMenu.menuAction(), None, + self.fileImportMenu.menuAction(), self.fileExportMenu.menuAction(), + None, self.printServiceOrderItem, self.fileExitItem)) + add_actions(self.viewModeMenu, (self.modeDefaultItem, + self.modeSetupItem, self.modeLiveItem)) + add_actions(self.viewMenu, (self.viewModeMenu.menuAction(), + None, self.viewMediaManagerItem, self.viewServiceManagerItem, + self.viewThemeManagerItem, None, self.viewPreviewPanel, + self.viewLivePanel, None, self.lockPanel)) # i18n add Language Actions - add_actions(self.SettingsLanguageMenu, (self.AutoLanguageItem, None)) - add_actions(self.SettingsLanguageMenu, self.LanguageGroup.actions()) - add_actions(self.SettingsMenu, (self.settingsPluginListItem, - self.SettingsLanguageMenu.menuAction(), None, - self.DisplayTagItem, self.SettingsShortcutsItem, - self.SettingsConfigureItem)) - add_actions(self.ToolsMenu, (self.ToolsAddToolItem, None)) - add_actions(self.ToolsMenu, (self.ToolsOpenDataFolder, None)) - add_actions(self.HelpMenu, (self.HelpDocumentationItem, - self.HelpOnlineHelpItem, None, self.helpWebSiteItem, - self.HelpAboutItem)) - add_actions(self.MenuBar, (self.FileMenu.menuAction(), - self.viewMenu.menuAction(), self.ToolsMenu.menuAction(), - self.SettingsMenu.menuAction(), self.HelpMenu.menuAction())) + add_actions(self.settingsLanguageMenu, (self.autoLanguageItem, None)) + add_actions(self.settingsLanguageMenu, self.languageGroup.actions()) + # Order things differently in OS X so that Preferences menu item in the + # app menu is correct (this gets picked up automatically by Qt). + if sys.platform == u'darwin': + add_actions(self.settingsMenu, (self.settingsPluginListItem, + self.settingsLanguageMenu.menuAction(), None, + self.settingsConfigureItem, self.settingsShortcutsItem, + self.formattingTagItem)) + else: + add_actions(self.settingsMenu, (self.settingsPluginListItem, + self.settingsLanguageMenu.menuAction(), None, + self.formattingTagItem, self.settingsShortcutsItem, + self.settingsConfigureItem)) + add_actions(self.toolsMenu, (self.toolsAddToolItem, None)) + add_actions(self.toolsMenu, (self.toolsOpenDataFolder, None)) + add_actions(self.toolsMenu, (self.toolsFirstTimeWizard, None)) + add_actions(self.toolsMenu, [self.updateThemeImages]) + if os.name == u'nt': + add_actions(self.helpMenu, (self.offlineHelpItem, + self.onlineHelpItem, None, self.webSiteItem, + self.aboutItem)) + else: + add_actions(self.helpMenu, (self.onlineHelpItem, None, + self.webSiteItem, self.aboutItem)) + add_actions(self.menuBar, (self.fileMenu.menuAction(), + self.viewMenu.menuAction(), self.toolsMenu.menuAction(), + self.settingsMenu.menuAction(), self.helpMenu.menuAction())) # Initialise the translation self.retranslateUi(mainWindow) - self.MediaToolBox.setCurrentIndex(0) + self.mediaToolBox.setCurrentIndex(0) # Connect up some signals and slots - QtCore.QObject.connect(self.FileMenu, - QtCore.SIGNAL(u'aboutToShow()'), self.updateFileMenu) - QtCore.QObject.connect(self.FileExitItem, - QtCore.SIGNAL(u'triggered()'), mainWindow.close) + QtCore.QObject.connect(self.fileMenu, + QtCore.SIGNAL(u'aboutToShow()'), self.updateRecentFilesMenu) QtCore.QMetaObject.connectSlotsByName(mainWindow) + # Hide the entry, as it does not have any functionality yet. + self.toolsAddToolItem.setVisible(False) + self.importLanguageItem.setVisible(False) + self.exportLanguageItem.setVisible(False) + self.setLockPanel(panelLocked) + self.settingsImported = False def retranslateUi(self, mainWindow): """ Set up the translation system """ - mainWindow.mainTitle = UiStrings.OLPV2 + mainWindow.mainTitle = UiStrings().OLPV2 mainWindow.setWindowTitle(mainWindow.mainTitle) - self.FileMenu.setTitle(translate('OpenLP.MainWindow', '&File')) - self.FileImportMenu.setTitle(translate('OpenLP.MainWindow', '&Import')) - self.FileExportMenu.setTitle(translate('OpenLP.MainWindow', '&Export')) + self.fileMenu.setTitle(translate('OpenLP.MainWindow', '&File')) + self.fileImportMenu.setTitle(translate('OpenLP.MainWindow', '&Import')) + self.fileExportMenu.setTitle(translate('OpenLP.MainWindow', '&Export')) + self.recentFilesMenu.setTitle( + translate('OpenLP.MainWindow', '&Recent Files')) self.viewMenu.setTitle(translate('OpenLP.MainWindow', '&View')) - self.ViewModeMenu.setTitle(translate('OpenLP.MainWindow', 'M&ode')) - self.ToolsMenu.setTitle(translate('OpenLP.MainWindow', '&Tools')) - self.SettingsMenu.setTitle(translate('OpenLP.MainWindow', '&Settings')) - self.SettingsLanguageMenu.setTitle(translate('OpenLP.MainWindow', + self.viewModeMenu.setTitle(translate('OpenLP.MainWindow', 'M&ode')) + self.toolsMenu.setTitle(translate('OpenLP.MainWindow', '&Tools')) + self.settingsMenu.setTitle(translate('OpenLP.MainWindow', '&Settings')) + self.settingsLanguageMenu.setTitle(translate('OpenLP.MainWindow', '&Language')) - self.HelpMenu.setTitle(translate('OpenLP.MainWindow', '&Help')) + self.helpMenu.setTitle(translate('OpenLP.MainWindow', '&Help')) self.mediaManagerDock.setWindowTitle( translate('OpenLP.MainWindow', 'Media Manager')) self.serviceManagerDock.setWindowTitle( translate('OpenLP.MainWindow', 'Service Manager')) self.themeManagerDock.setWindowTitle( translate('OpenLP.MainWindow', 'Theme Manager')) - self.FileNewItem.setText(translate('OpenLP.MainWindow', '&New')) - self.FileNewItem.setToolTip(UiStrings.NewService) - self.FileNewItem.setStatusTip(UiStrings.CreateService) - self.FileNewItem.setShortcut(translate('OpenLP.MainWindow', 'Ctrl+N')) - self.FileOpenItem.setText(translate('OpenLP.MainWindow', '&Open')) - self.FileOpenItem.setToolTip(UiStrings.OpenService) - self.FileOpenItem.setStatusTip( + self.fileNewItem.setText(translate('OpenLP.MainWindow', '&New')) + self.fileNewItem.setToolTip(UiStrings().NewService) + self.fileNewItem.setStatusTip(UiStrings().CreateService) + self.fileOpenItem.setText(translate('OpenLP.MainWindow', '&Open')) + self.fileOpenItem.setToolTip(UiStrings().OpenService) + self.fileOpenItem.setStatusTip( translate('OpenLP.MainWindow', 'Open an existing service.')) - self.FileOpenItem.setShortcut(translate('OpenLP.MainWindow', 'Ctrl+O')) - self.FileSaveItem.setText(translate('OpenLP.MainWindow', '&Save')) - self.FileSaveItem.setToolTip(UiStrings.SaveService) - self.FileSaveItem.setStatusTip( + self.fileSaveItem.setText(translate('OpenLP.MainWindow', '&Save')) + self.fileSaveItem.setToolTip(UiStrings().SaveService) + self.fileSaveItem.setStatusTip( translate('OpenLP.MainWindow', 'Save the current service to disk.')) - self.FileSaveItem.setShortcut(translate('OpenLP.MainWindow', 'Ctrl+S')) - self.FileSaveAsItem.setText( + self.fileSaveAsItem.setText( translate('OpenLP.MainWindow', 'Save &As...')) - self.FileSaveAsItem.setToolTip( + self.fileSaveAsItem.setToolTip( translate('OpenLP.MainWindow', 'Save Service As')) - self.FileSaveAsItem.setStatusTip(translate('OpenLP.MainWindow', + self.fileSaveAsItem.setStatusTip(translate('OpenLP.MainWindow', 'Save the current service under a new name.')) - self.FileSaveAsItem.setShortcut( - translate('OpenLP.MainWindow', 'Ctrl+Shift+S')) - self.printServiceOrderItem.setText(UiStrings.PrintServiceOrder) + self.printServiceOrderItem.setText(UiStrings().PrintService) self.printServiceOrderItem.setStatusTip(translate('OpenLP.MainWindow', - 'Print the current Service Order.')) - self.printServiceOrderItem.setShortcut( - translate('OpenLP.MainWindow', 'Ctrl+P')) - self.FileExitItem.setText( + 'Print the current service.')) + self.fileExitItem.setText( translate('OpenLP.MainWindow', 'E&xit')) - self.FileExitItem.setStatusTip( + self.fileExitItem.setStatusTip( translate('OpenLP.MainWindow', 'Quit OpenLP')) - self.FileExitItem.setShortcut( - translate('OpenLP.MainWindow', 'Alt+F4')) - self.ImportThemeItem.setText( + self.importThemeItem.setText( translate('OpenLP.MainWindow', '&Theme')) - self.ImportLanguageItem.setText( + self.importLanguageItem.setText( translate('OpenLP.MainWindow', '&Language')) - self.ExportThemeItem.setText( + self.exportThemeItem.setText( translate('OpenLP.MainWindow', '&Theme')) - self.ExportLanguageItem.setText( + self.exportLanguageItem.setText( translate('OpenLP.MainWindow', '&Language')) - self.SettingsShortcutsItem.setText( + self.settingsShortcutsItem.setText( translate('OpenLP.MainWindow', 'Configure &Shortcuts...')) - self.DisplayTagItem.setText( - translate('OpenLP.MainWindow', '&Configure Display Tags')) - self.SettingsConfigureItem.setText( + self.formattingTagItem.setText( + translate('OpenLP.MainWindow', 'Configure &Formatting Tags...')) + self.settingsConfigureItem.setText( translate('OpenLP.MainWindow', '&Configure OpenLP...')) - self.ViewMediaManagerItem.setText( + self.settingsExportItem.setStatusTip(translate('OpenLP.MainWindow', + 'Export OpenLP settings to a specified *.config file')) + self.settingsExportItem.setText( + translate('OpenLP.MainWindow', 'Settings')) + self.settingsImportItem.setStatusTip(translate('OpenLP.MainWindow', + 'Import OpenLP settings from a specified *.config file previously ' + 'exported on this or another machine')) + self.settingsImportItem.setText( + translate('OpenLP.MainWindow', 'Settings')) + self.viewMediaManagerItem.setText( translate('OpenLP.MainWindow', '&Media Manager')) - self.ViewMediaManagerItem.setToolTip( + self.viewMediaManagerItem.setToolTip( translate('OpenLP.MainWindow', 'Toggle Media Manager')) - self.ViewMediaManagerItem.setStatusTip(translate('OpenLP.MainWindow', + self.viewMediaManagerItem.setStatusTip(translate('OpenLP.MainWindow', 'Toggle the visibility of the media manager.')) - self.ViewMediaManagerItem.setShortcut( - translate('OpenLP.MainWindow', 'F8')) - self.ViewThemeManagerItem.setText( + self.viewThemeManagerItem.setText( translate('OpenLP.MainWindow', '&Theme Manager')) - self.ViewThemeManagerItem.setToolTip( + self.viewThemeManagerItem.setToolTip( translate('OpenLP.MainWindow', 'Toggle Theme Manager')) - self.ViewThemeManagerItem.setStatusTip(translate('OpenLP.MainWindow', + self.viewThemeManagerItem.setStatusTip(translate('OpenLP.MainWindow', 'Toggle the visibility of the theme manager.')) - self.ViewThemeManagerItem.setShortcut( - translate('OpenLP.MainWindow', 'F10')) - self.ViewServiceManagerItem.setText( + self.viewServiceManagerItem.setText( translate('OpenLP.MainWindow', '&Service Manager')) - self.ViewServiceManagerItem.setToolTip( + self.viewServiceManagerItem.setToolTip( translate('OpenLP.MainWindow', 'Toggle Service Manager')) - self.ViewServiceManagerItem.setStatusTip(translate('OpenLP.MainWindow', + self.viewServiceManagerItem.setStatusTip(translate('OpenLP.MainWindow', 'Toggle the visibility of the service manager.')) - self.ViewServiceManagerItem.setShortcut( - translate('OpenLP.MainWindow', 'F9')) - self.ViewPreviewPanel.setText( + self.viewPreviewPanel.setText( translate('OpenLP.MainWindow', '&Preview Panel')) - self.ViewPreviewPanel.setToolTip( + self.viewPreviewPanel.setToolTip( translate('OpenLP.MainWindow', 'Toggle Preview Panel')) - self.ViewPreviewPanel.setStatusTip(translate('OpenLP.MainWindow', + self.viewPreviewPanel.setStatusTip(translate('OpenLP.MainWindow', 'Toggle the visibility of the preview panel.')) - self.ViewPreviewPanel.setShortcut( - translate('OpenLP.MainWindow', 'F11')) - self.ViewLivePanel.setText( + self.viewLivePanel.setText( translate('OpenLP.MainWindow', '&Live Panel')) - self.ViewLivePanel.setToolTip( + self.viewLivePanel.setToolTip( translate('OpenLP.MainWindow', 'Toggle Live Panel')) - self.ViewLivePanel.setStatusTip(translate('OpenLP.MainWindow', + self.lockPanel.setText( + translate('OpenLP.MainWindow', 'L&ock Panels')) + self.lockPanel.setStatusTip( + translate('OpenLP.MainWindow', 'Prevent the panels being moved.')) + self.viewLivePanel.setStatusTip(translate('OpenLP.MainWindow', 'Toggle the visibility of the live panel.')) - self.ViewLivePanel.setShortcut( - translate('OpenLP.MainWindow', 'F12')) self.settingsPluginListItem.setText(translate('OpenLP.MainWindow', '&Plugin List')) self.settingsPluginListItem.setStatusTip( translate('OpenLP.MainWindow', 'List the Plugins')) - self.settingsPluginListItem.setShortcut( - translate('OpenLP.MainWindow', 'Alt+F7')) - self.HelpDocumentationItem.setText( - translate('OpenLP.MainWindow', '&User Guide')) - self.HelpAboutItem.setText(translate('OpenLP.MainWindow', '&About')) - self.HelpAboutItem.setStatusTip( + self.aboutItem.setText(translate('OpenLP.MainWindow', '&About')) + self.aboutItem.setStatusTip( translate('OpenLP.MainWindow', 'More information about OpenLP')) - self.HelpAboutItem.setShortcut( - translate('OpenLP.MainWindow', 'Ctrl+F1')) - self.HelpOnlineHelpItem.setText( + if os.name == u'nt': + self.offlineHelpItem.setText( + translate('OpenLP.MainWindow', '&User Guide')) + self.onlineHelpItem.setText( translate('OpenLP.MainWindow', '&Online Help')) - self.helpWebSiteItem.setText( + self.webSiteItem.setText( translate('OpenLP.MainWindow', '&Web Site')) - self.AutoLanguageItem.setText( - translate('OpenLP.MainWindow', '&Auto Detect')) - self.AutoLanguageItem.setStatusTip(translate('OpenLP.MainWindow', - 'Use the system language, if available.')) - for item in self.LanguageGroup.actions(): + for item in self.languageGroup.actions(): item.setText(item.objectName()) item.setStatusTip(unicode(translate('OpenLP.MainWindow', 'Set the interface language to %s')) % item.objectName()) - self.ToolsAddToolItem.setText( + self.autoLanguageItem.setText( + translate('OpenLP.MainWindow', '&Autodetect')) + self.autoLanguageItem.setStatusTip(translate('OpenLP.MainWindow', + 'Use the system language, if available.')) + self.toolsAddToolItem.setText( translate('OpenLP.MainWindow', 'Add &Tool...')) - self.ToolsAddToolItem.setStatusTip(translate('OpenLP.MainWindow', + self.toolsAddToolItem.setStatusTip(translate('OpenLP.MainWindow', 'Add an application to the list of tools.')) - self.ToolsOpenDataFolder.setText( + self.toolsOpenDataFolder.setText( translate('OpenLP.MainWindow', 'Open &Data Folder...')) - self.ToolsOpenDataFolder.setStatusTip(translate('OpenLP.MainWindow', + self.toolsOpenDataFolder.setStatusTip(translate('OpenLP.MainWindow', 'Open the folder where songs, bibles and other data resides.')) - self.ModeDefaultItem.setText( + self.toolsFirstTimeWizard.setText( + translate('OpenLP.MainWindow', 'Re-run First Time Wizard')) + self.toolsFirstTimeWizard.setStatusTip(translate('OpenLP.MainWindow', + 'Re-run the First Time Wizard, importing songs, Bibles and themes.')) + self.updateThemeImages.setText( + translate('OpenLP.MainWindow', 'Update Theme Images')) + self.updateThemeImages.setStatusTip( + translate('OpenLP.MainWindow', 'Update the preview images for all ' + 'themes.')) + self.modeDefaultItem.setText( translate('OpenLP.MainWindow', '&Default')) - self.ModeDefaultItem.setStatusTip(translate('OpenLP.MainWindow', + self.modeDefaultItem.setStatusTip(translate('OpenLP.MainWindow', 'Set the view mode back to the default.')) - self.ModeSetupItem.setText(translate('OpenLP.MainWindow', '&Setup')) - self.ModeSetupItem.setStatusTip( + self.modeSetupItem.setText(translate('OpenLP.MainWindow', '&Setup')) + self.modeSetupItem.setStatusTip( translate('OpenLP.MainWindow', 'Set the view mode to Setup.')) - self.ModeLiveItem.setText(translate('OpenLP.MainWindow', '&Live')) - self.ModeLiveItem.setStatusTip( + self.modeLiveItem.setText(translate('OpenLP.MainWindow', '&Live')) + self.modeLiveItem.setStatusTip( translate('OpenLP.MainWindow', 'Set the view mode to Live.')) @@ -459,116 +527,98 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): """ log.info(u'MainWindow loaded') - actionList = ActionList() - - def __init__(self, screens, applicationVersion, clipboard, firstTime): + def __init__(self, clipboard, arguments): """ This constructor sets up the interface, the various managers, and the plugins. """ QtGui.QMainWindow.__init__(self) - self.screens = screens - self.actionList = ActionList() - self.applicationVersion = applicationVersion self.clipboard = clipboard + self.arguments = arguments # Set up settings sections for the main application # (not for use by plugins) self.uiSettingsSection = u'user interface' self.generalSettingsSection = u'general' - self.serviceSettingsSection = u'servicemanager' + self.advancedlSettingsSection = u'advanced' + self.shortcutsSettingsSection = u'shortcuts' + self.servicemanagerSettingsSection = u'servicemanager' self.songsSettingsSection = u'songs' + self.themesSettingsSection = u'themes' + self.displayTagsSection = u'displayTags' + self.headerSection = u'SettingsImport' self.serviceNotSaved = False - self.settingsmanager = SettingsManager(screens) - self.aboutForm = AboutForm(self, applicationVersion) - self.settingsForm = SettingsForm(self.screens, self, self) - self.displayTagForm = DisplayTagForm(self) + self.aboutForm = AboutForm(self) + self.settingsForm = SettingsForm(self, self) + self.formattingTagForm = FormattingTagForm(self) self.shortcutForm = ShortcutListForm(self) self.recentFiles = QtCore.QStringList() # Set up the path with plugins pluginpath = AppLocation.get_directory(AppLocation.PluginsDir) self.pluginManager = PluginManager(pluginpath) self.pluginHelpers = {} + self.imageManager = ImageManager() # Set up the interface self.setupUi(self) # Load settings after setupUi so default UI sizes are overwritten self.loadSettings() - # Once settings are loaded update FileMenu with recentFiles - self.updateFileMenu() + # Once settings are loaded update the menu with the recent files. + self.updateRecentFilesMenu() self.pluginForm = PluginForm(self) # Set up signals and slots - QtCore.QObject.connect(self.ImportThemeItem, + QtCore.QObject.connect(self.importThemeItem, QtCore.SIGNAL(u'triggered()'), self.themeManagerContents.onImportTheme) - QtCore.QObject.connect(self.ExportThemeItem, + QtCore.QObject.connect(self.exportThemeItem, QtCore.SIGNAL(u'triggered()'), self.themeManagerContents.onExportTheme) - QtCore.QObject.connect(self.ViewMediaManagerItem, - QtCore.SIGNAL(u'triggered(bool)'), self.toggleMediaManager) - QtCore.QObject.connect(self.ViewServiceManagerItem, - QtCore.SIGNAL(u'triggered(bool)'), self.toggleServiceManager) - QtCore.QObject.connect(self.ViewThemeManagerItem, - QtCore.SIGNAL(u'triggered(bool)'), self.toggleThemeManager) - QtCore.QObject.connect(self.ViewPreviewPanel, - QtCore.SIGNAL(u'toggled(bool)'), self.setPreviewPanelVisibility) - QtCore.QObject.connect(self.ViewLivePanel, - QtCore.SIGNAL(u'toggled(bool)'), self.setLivePanelVisibility) QtCore.QObject.connect(self.mediaManagerDock, QtCore.SIGNAL(u'visibilityChanged(bool)'), - self.ViewMediaManagerItem.setChecked) + self.viewMediaManagerItem.setChecked) QtCore.QObject.connect(self.serviceManagerDock, QtCore.SIGNAL(u'visibilityChanged(bool)'), - self.ViewServiceManagerItem.setChecked) + self.viewServiceManagerItem.setChecked) QtCore.QObject.connect(self.themeManagerDock, QtCore.SIGNAL(u'visibilityChanged(bool)'), - self.ViewThemeManagerItem.setChecked) - QtCore.QObject.connect(self.helpWebSiteItem, + self.viewThemeManagerItem.setChecked) + QtCore.QObject.connect(self.webSiteItem, QtCore.SIGNAL(u'triggered()'), self.onHelpWebSiteClicked) - QtCore.QObject.connect(self.HelpAboutItem, - QtCore.SIGNAL(u'triggered()'), self.onHelpAboutItemClicked) - QtCore.QObject.connect(self.ToolsOpenDataFolder, + QtCore.QObject.connect(self.toolsOpenDataFolder, QtCore.SIGNAL(u'triggered()'), self.onToolsOpenDataFolderClicked) - QtCore.QObject.connect(self.settingsPluginListItem, - QtCore.SIGNAL(u'triggered()'), self.onPluginItemClicked) - QtCore.QObject.connect(self.DisplayTagItem, - QtCore.SIGNAL(u'triggered()'), self.onDisplayTagItemClicked) - QtCore.QObject.connect(self.SettingsConfigureItem, + QtCore.QObject.connect(self.toolsFirstTimeWizard, + QtCore.SIGNAL(u'triggered()'), self.onFirstTimeWizardClicked) + QtCore.QObject.connect(self.updateThemeImages, + QtCore.SIGNAL(u'triggered()'), self.onUpdateThemeImages) + QtCore.QObject.connect(self.formattingTagItem, + QtCore.SIGNAL(u'triggered()'), self.onFormattingTagItemClicked) + QtCore.QObject.connect(self.settingsConfigureItem, QtCore.SIGNAL(u'triggered()'), self.onSettingsConfigureItemClicked) - QtCore.QObject.connect(self.SettingsShortcutsItem, + QtCore.QObject.connect(self.settingsShortcutsItem, QtCore.SIGNAL(u'triggered()'), self.onSettingsShortcutsItemClicked) - QtCore.QObject.connect(self.FileNewItem, QtCore.SIGNAL(u'triggered()'), - self.ServiceManagerContents.onNewServiceClicked) - QtCore.QObject.connect(self.FileOpenItem, - QtCore.SIGNAL(u'triggered()'), - self.ServiceManagerContents.onLoadServiceClicked) - QtCore.QObject.connect(self.FileSaveItem, - QtCore.SIGNAL(u'triggered()'), - self.ServiceManagerContents.saveFile) - QtCore.QObject.connect(self.FileSaveAsItem, - QtCore.SIGNAL(u'triggered()'), - self.ServiceManagerContents.saveFileAs) - QtCore.QObject.connect(self.printServiceOrderItem, - QtCore.SIGNAL(u'triggered()'), - self.ServiceManagerContents.printServiceOrder) + QtCore.QObject.connect(self.settingsImportItem, + QtCore.SIGNAL(u'triggered()'), self.onSettingsImportItemClicked) + QtCore.QObject.connect(self.settingsExportItem, + QtCore.SIGNAL(u'triggered()'), self.onSettingsExportItemClicked) # i18n set signals for languages - QtCore.QObject.connect(self.AutoLanguageItem, - QtCore.SIGNAL(u'toggled(bool)'), self.setAutoLanguage) - self.LanguageGroup.triggered.connect(LanguageManager.set_language) - QtCore.QObject.connect(self.ModeDefaultItem, + self.languageGroup.triggered.connect(LanguageManager.set_language) + QtCore.QObject.connect(self.modeDefaultItem, QtCore.SIGNAL(u'triggered()'), self.onModeDefaultItemClicked) - QtCore.QObject.connect(self.ModeSetupItem, + QtCore.QObject.connect(self.modeSetupItem, QtCore.SIGNAL(u'triggered()'), self.onModeSetupItemClicked) - QtCore.QObject.connect(self.ModeLiveItem, + QtCore.QObject.connect(self.modeLiveItem, QtCore.SIGNAL(u'triggered()'), self.onModeLiveItemClicked) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'theme_update_global'), self.defaultThemeChanged) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'openlp_version_check'), self.versionNotice) QtCore.QObject.connect(Receiver.get_receiver(), - QtCore.SIGNAL(u'maindisplay_blank_check'), self.blankCheck) + QtCore.SIGNAL(u'live_display_blank_check'), self.blankCheck) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'config_screen_changed'), self.screenChanged) QtCore.QObject.connect(Receiver.get_receiver(), - QtCore.SIGNAL(u'maindisplay_status_text'), self.showStatusMessage) + QtCore.SIGNAL(u'mainwindow_status_text'), self.showStatusMessage) + # Media Manager + QtCore.QObject.connect(self.mediaToolBox, + QtCore.SIGNAL(u'currentChanged(int)'), self.onMediaToolBoxChanged) Receiver.send_message(u'cursor_busy') # Simple message boxes QtCore.QObject.connect(Receiver.get_receiver(), @@ -579,18 +629,17 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): QtCore.SIGNAL(u'openlp_information_message'), self.onInformationMessage) # warning cyclic dependency - # RenderManager needs to call ThemeManager and - # ThemeManager needs to call RenderManager - self.renderManager = RenderManager( - self.themeManagerContents, self.screens) + # renderer needs to call ThemeManager and + # ThemeManager needs to call Renderer + self.renderer = Renderer(self.imageManager, self.themeManagerContents) # Define the media Dock Manager - self.mediaDockManager = MediaDockManager(self.MediaToolBox) + self.mediaDockManager = MediaDockManager(self.mediaToolBox) log.info(u'Load Plugins') # make the controllers available to the plugins self.pluginHelpers[u'preview'] = self.previewController self.pluginHelpers[u'live'] = self.liveController - self.pluginHelpers[u'render'] = self.renderManager - self.pluginHelpers[u'service'] = self.ServiceManagerContents + self.pluginHelpers[u'renderer'] = self.renderer + self.pluginHelpers[u'service'] = self.serviceManagerContents self.pluginHelpers[u'settings form'] = self.settingsForm self.pluginHelpers[u'toolbox'] = self.mediaDockManager self.pluginHelpers[u'pluginmanager'] = self.pluginManager @@ -606,35 +655,42 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): self.pluginManager.hook_media_manager(self.mediaDockManager) # Call the hook method to pull in import menus. log.info(u'hook menus') - self.pluginManager.hook_import_menu(self.FileImportMenu) + self.pluginManager.hook_import_menu(self.fileImportMenu) # Call the hook method to pull in export menus. - self.pluginManager.hook_export_menu(self.FileExportMenu) + self.pluginManager.hook_export_menu(self.fileExportMenu) # Call the hook method to pull in tools menus. - self.pluginManager.hook_tools_menu(self.ToolsMenu) + self.pluginManager.hook_tools_menu(self.toolsMenu) # Call the initialise method to setup plugins. log.info(u'initialise plugins') self.pluginManager.initialise_plugins() - # Once all components are initialised load the Themes - log.info(u'Load Themes') - self.themeManagerContents.loadThemes() + # Create the displays as all necessary components are loaded. + self.previewController.screenSizeChanged() + self.liveController.screenSizeChanged() log.info(u'Load data from Settings') if QtCore.QSettings().value(u'advanced/save current plugin', QtCore.QVariant(False)).toBool(): savedPlugin = QtCore.QSettings().value( u'advanced/current media plugin', QtCore.QVariant()).toInt()[0] if savedPlugin != -1: - self.MediaToolBox.setCurrentIndex(savedPlugin) + self.mediaToolBox.setCurrentIndex(savedPlugin) self.settingsForm.postSetUp() + # Once all components are initialised load the Themes + log.info(u'Load Themes') + self.themeManagerContents.loadThemes(True) + # Hide/show the theme combobox on the service manager + self.serviceManagerContents.themeChange() + # Reset the cursor Receiver.send_message(u'cursor_normal') - # Import themes if first time - if firstTime: - self.themeManagerContents.firstTime() - def setAutoLanguage(self, value): - self.LanguageGroup.setDisabled(value) + self.languageGroup.setDisabled(value) LanguageManager.auto_language = value - LanguageManager.set_language(self.LanguageGroup.checkedAction()) + LanguageManager.set_language(self.languageGroup.checkedAction()) + + def onMediaToolBoxChanged(self, index): + widget = self.mediaToolBox.widget(index) + if widget: + widget.onFocus() def versionNotice(self, version): """ @@ -647,7 +703,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): 'version from http://openlp.org/.')) QtGui.QMessageBox.question(self, translate('OpenLP.MainWindow', 'OpenLP Version Updated'), - version_text % (version, self.applicationVersion[u'full'])) + version_text % (version, get_application_version()[u'full'])) def show(self): """ @@ -657,31 +713,98 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): if self.liveController.display.isVisible(): self.liveController.display.setFocus() self.activateWindow() - if QtCore.QSettings().value( + if len(self.arguments): + args = [] + for a in self.arguments: + args.extend([a]) + self.serviceManagerContents.loadFile(unicode(args[0])) + elif QtCore.QSettings().value( self.generalSettingsSection + u'/auto open', QtCore.QVariant(False)).toBool(): - self.ServiceManagerContents.loadLastFile() + self.serviceManagerContents.loadLastFile() view_mode = QtCore.QSettings().value(u'%s/view mode' % \ - self.generalSettingsSection, u'default') + self.generalSettingsSection, u'default').toString() if view_mode == u'default': - self.ModeDefaultItem.setChecked(True) + self.modeDefaultItem.setChecked(True) elif view_mode == u'setup': self.setViewMode(True, True, False, True, False) - self.ModeSetupItem.setChecked(True) + self.modeSetupItem.setChecked(True) elif view_mode == u'live': self.setViewMode(False, True, False, False, True) - self.ModeLiveItem.setChecked(True) + self.modeLiveItem.setChecked(True) + + def appStartup(self): + """ + Give all the plugins a chance to perform some tasks at startup + """ + Receiver.send_message(u'openlp_process_events') + for plugin in self.pluginManager.plugins: + if plugin.isActive(): + plugin.appStartup() + Receiver.send_message(u'openlp_process_events') + + def firstTime(self): + # Import themes if first time + Receiver.send_message(u'openlp_process_events') + for plugin in self.pluginManager.plugins: + if hasattr(plugin, u'firstTime'): + Receiver.send_message(u'openlp_process_events') + plugin.firstTime() + Receiver.send_message(u'openlp_process_events') + temp_dir = os.path.join(unicode(gettempdir()), u'openlp') + shutil.rmtree(temp_dir, True) + + def onFirstTimeWizardClicked(self): + """ + Re-run the first time wizard. Prompts the user for run confirmation + If wizard is run, songs, bibles and themes are imported. The default + theme is changed (if necessary). The plugins in pluginmanager are + set active/in-active to match the selection in the wizard. + """ + answer = QtGui.QMessageBox.warning(self, + translate('OpenLP.MainWindow', 'Re-run First Time Wizard?'), + translate('OpenLP.MainWindow', + 'Are you sure you want to re-run the First Time Wizard?\n\n' + 'Re-running this wizard may make changes to your current ' + 'OpenLP configuration and possibly add songs to your ' + 'existing songs list and change your default theme.'), + QtGui.QMessageBox.StandardButtons( + QtGui.QMessageBox.Yes | + QtGui.QMessageBox.No), + QtGui.QMessageBox.No) + if answer == QtGui.QMessageBox.No: + return + Receiver.send_message(u'cursor_busy') + screens = ScreenList.get_instance() + FirstTimeForm(screens, self).exec_() + self.firstTime() + for plugin in self.pluginManager.plugins: + self.activePlugin = plugin + oldStatus = self.activePlugin.status + self.activePlugin.setStatus() + if oldStatus != self.activePlugin.status: + if self.activePlugin.status == PluginStatus.Active: + self.activePlugin.toggleStatus(PluginStatus.Active) + self.activePlugin.appStartup() + else: + self.activePlugin.toggleStatus(PluginStatus.Inactive) + self.themeManagerContents.configUpdated() + self.themeManagerContents.loadThemes(True) + Receiver.send_message(u'theme_update_global', + self.themeManagerContents.global_theme) + # Check if any Bibles downloaded. If there are, they will be + # processed. + Receiver.send_message(u'bibles_load_list', True) def blankCheck(self): """ Check and display message if screen blank on setup. - Triggered by delay thread. """ settings = QtCore.QSettings() + self.liveController.mainDisplaySetBackground() if settings.value(u'%s/screen blank' % self.generalSettingsSection, QtCore.QVariant(False)).toBool(): - self.liveController.mainDisplaySetBackground() - if settings.value(u'blank warning', + if settings.value(u'%s/blank warning' % self.generalSettingsSection, QtCore.QVariant(False)).toBool(): QtGui.QMessageBox.question(self, translate('OpenLP.MainWindow', @@ -690,12 +813,15 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): 'The Main Display has been blanked out')) def onErrorMessage(self, data): + Receiver.send_message(u'close_splash') QtGui.QMessageBox.critical(self, data[u'title'], data[u'message']) def onWarningMessage(self, data): + Receiver.send_message(u'close_splash') QtGui.QMessageBox.warning(self, data[u'title'], data[u'message']) def onInformationMessage(self, data): + Receiver.send_message(u'close_splash') QtGui.QMessageBox.information(self, data[u'title'], data[u'message']) def onHelpWebSiteClicked(self): @@ -705,11 +831,23 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): import webbrowser webbrowser.open_new(u'http://openlp.org/') - def onHelpAboutItemClicked(self): + def onOfflineHelpClicked(self): + """ + Load the local OpenLP help file + """ + os.startfile(self.localHelpFile) + + def onOnlineHelpClicked(self): + """ + Load the online OpenLP manual + """ + import webbrowser + webbrowser.open_new(u'http://manual.openlp.org/') + + def onAboutItemClicked(self): """ Show the About form """ - self.aboutForm.applicationVersion = self.applicationVersion self.aboutForm.exec_() def onPluginItemClicked(self): @@ -726,11 +864,17 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): path = AppLocation.get_data_path() QtGui.QDesktopServices.openUrl(QtCore.QUrl("file:///" + path)) - def onDisplayTagItemClicked(self): + def onUpdateThemeImages(self): + """ + Updates the new theme preview images. + """ + self.themeManagerContents.updatePreviewImages() + + def onFormattingTagItemClicked(self): """ Show the Settings dialog """ - self.displayTagForm.exec_() + self.formattingTagForm.exec_() def onSettingsConfigureItemClicked(self): """ @@ -749,7 +893,173 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): """ Show the shortcuts dialog """ - self.shortcutForm.exec_(self.actionList) + if self.shortcutForm.exec_(): + self.shortcutForm.save() + + def onSettingsImportItemClicked(self): + """ + Import settings from an export INI file + """ + answer = QtGui.QMessageBox.critical(self, + translate('OpenLP.MainWindow', 'Import settings?'), + translate('OpenLP.MainWindow', + 'Are you sure you want to import settings?\n\n' + 'Importing settings will make permanent changes to your current ' + 'OpenLP configuration.\n\n' + 'Importing incorrect settings may cause erratic behaviour or ' + 'OpenLP to terminate abnormally.'), + QtGui.QMessageBox.StandardButtons( + QtGui.QMessageBox.Yes | + QtGui.QMessageBox.No), + QtGui.QMessageBox.No) + if answer == QtGui.QMessageBox.No: + return + import_file_name = unicode(QtGui.QFileDialog.getOpenFileName(self, + translate('OpenLP.MainWindow', 'Open File'), + '', + translate('OpenLP.MainWindow', + 'OpenLP Export Settings Files (*.conf)'))) + if not import_file_name: + return + setting_sections = [] + # Add main sections. + setting_sections.extend([self.generalSettingsSection]) + setting_sections.extend([self.advancedlSettingsSection]) + setting_sections.extend([self.uiSettingsSection]) + setting_sections.extend([self.shortcutsSettingsSection]) + setting_sections.extend([self.servicemanagerSettingsSection]) + setting_sections.extend([self.themesSettingsSection]) + setting_sections.extend([self.displayTagsSection]) + setting_sections.extend([self.headerSection]) + # Add plugin sections. + for plugin in self.pluginManager.plugins: + setting_sections.extend([plugin.name]) + settings = QtCore.QSettings() + import_settings = QtCore.QSettings(import_file_name, + QtCore.QSettings.IniFormat) + import_keys = import_settings.allKeys() + for section_key in import_keys: + # We need to handle the really bad files. + try: + section, key = section_key.split(u'/') + except ValueError: + section = u'unknown' + key = u'' + # Switch General back to lowercase. + if section == u'General': + section = u'general' + section_key = section + "/" + key + # Make sure it's a valid section for us. + if not section in setting_sections: + QtGui.QMessageBox.critical(self, + translate('OpenLP.MainWindow', 'Import settings'), + translate('OpenLP.MainWindow', + 'The file you selected does appear to be a valid OpenLP ' + 'settings file.\n\n' + 'Section [%s] is not valid \n\n' + 'Processing has terminated and no changed have been made.' + % section), + QtGui.QMessageBox.StandardButtons( + QtGui.QMessageBox.Ok)) + return + # We have a good file, import it. + for section_key in import_keys: + value = import_settings.value(section_key) + settings.setValue(u'%s' % (section_key) , + QtCore.QVariant(value)) + now = datetime.now() + settings.beginGroup(self.headerSection) + settings.setValue( u'file_imported' , QtCore.QVariant(import_file_name)) + settings.setValue(u'file_date_imported', + now.strftime("%Y-%m-%d %H:%M")) + settings.endGroup() + settings.sync() + # We must do an immediate restart or current configuration will + # overwrite what was just imported when application terminates + # normally. We need to exit without saving configuration. + QtGui.QMessageBox.information(self, + translate('OpenLP.MainWindow', 'Import settings'), + translate('OpenLP.MainWindow', + 'OpenLP will now close. Imported settings will ' + 'be applied the next time you start OpenLP.'), + QtGui.QMessageBox.StandardButtons( + QtGui.QMessageBox.Ok)) + self.settingsImported = True + self.cleanUp() + QtCore.QCoreApplication.exit() + + def onSettingsExportItemClicked(self): + """ + Export settings to a .conf file in INI format + """ + export_file_name = unicode(QtGui.QFileDialog.getSaveFileName(self, + translate('OpenLP.MainWindow', 'Export Settings File'), '', + translate('OpenLP.MainWindow', + 'OpenLP Export Settings File (*.conf)'))) + if not export_file_name: + return + # Make sure it's a .conf file. + if not export_file_name.endswith(u'conf'): + export_file_name = export_file_name + u'.conf' + temp_file = os.path.join(unicode(gettempdir()), + u'openlp', u'exportConf.tmp') + self.saveSettings() + setting_sections = [] + # Add main sections. + setting_sections.extend([self.generalSettingsSection]) + setting_sections.extend([self.advancedlSettingsSection]) + setting_sections.extend([self.uiSettingsSection]) + setting_sections.extend([self.shortcutsSettingsSection]) + setting_sections.extend([self.servicemanagerSettingsSection]) + setting_sections.extend([self.themesSettingsSection]) + setting_sections.extend([self.displayTagsSection]) + # Add plugin sections. + for plugin in self.pluginManager.plugins: + setting_sections.extend([plugin.name]) + # Delete old files if found. + if os.path.exists(temp_file): + os.remove(temp_file) + if os.path.exists(export_file_name): + os.remove(export_file_name) + settings = QtCore.QSettings() + settings.remove(self.headerSection) + # Get the settings. + keys = settings.allKeys() + export_settings = QtCore.QSettings(temp_file, + QtCore.QSettings.IniFormat) + # Add a header section. + # This is to insure it's our conf file for import. + now = datetime.now() + application_version = get_application_version() + # Write INI format using Qsettings. + # Write our header. + export_settings.beginGroup(self.headerSection) + export_settings.setValue(u'Make_Changes', u'At_Own_RISK') + export_settings.setValue(u'type', u'OpenLP_settings_export') + export_settings.setValue(u'file_date_created', + now.strftime("%Y-%m-%d %H:%M")) + export_settings.setValue(u'version', application_version[u'full']) + export_settings.endGroup() + # Write all the sections and keys. + for section_key in keys: + section, key = section_key.split(u'/') + key_value = settings.value(section_key) + export_settings.setValue(section_key, key_value) + export_settings.sync() + # Temp CONF file has been written. Blanks in keys are now '%20'. + # Read the temp file and output the user's CONF file with blanks to + # make it more readable. + temp_conf = open(temp_file, u'r') + export_conf = open(export_file_name, u'w') + for file_record in temp_conf: + # Get rid of any invalid entries. + if file_record.find(u'@Invalid()') == -1: + file_record = file_record.replace(u'%20', u' ') + export_conf.write(file_record) + temp_conf.close() + export_conf.close() + os.remove(temp_file) + return def onModeDefaultItemClicked(self): """ @@ -786,22 +1096,30 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): def screenChanged(self): """ - The screen has changed to so tell the displays to update_display - their locations + The screen has changed so we have to update components such as the + renderer. """ log.debug(u'screenChanged') - self.renderManager.update_display() + Receiver.send_message(u'cursor_busy') + self.imageManager.update_display() + self.renderer.update_display() + self.previewController.screenSizeChanged() + self.liveController.screenSizeChanged() self.setFocus() self.activateWindow() + Receiver.send_message(u'cursor_normal') def closeEvent(self, event): """ Hook to close the main window and display windows on exit """ - if self.ServiceManagerContents.isModified(): - ret = self.ServiceManagerContents.saveModifiedService() + # If we just did a settings import, close without saving changes. + if self.settingsImported: + event.accept() + if self.serviceManagerContents.isModified(): + ret = self.serviceManagerContents.saveModifiedService() if ret == QtGui.QMessageBox.Save: - if self.ServiceManagerContents.saveFile(): + if self.serviceManagerContents.saveFile(): self.cleanUp() event.accept() else: @@ -836,11 +1154,11 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): Runs all the cleanup code before OpenLP shuts down """ # Clean temporary files used by services - self.ServiceManagerContents.cleanUp() + self.serviceManagerContents.cleanUp() if QtCore.QSettings().value(u'advanced/save current plugin', QtCore.QVariant(False)).toBool(): QtCore.QSettings().setValue(u'advanced/current media plugin', - QtCore.QVariant(self.MediaToolBox.currentIndex())) + QtCore.QVariant(self.mediaToolBox.currentIndex())) # Call the cleanup method to shutdown plugins. log.info(u'cleanup plugins') self.pluginManager.finalise_plugins() @@ -889,24 +1207,22 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): self.setWindowTitle(title) def showStatusMessage(self, message): - self.StatusBar.showMessage(message) + self.statusBar.showMessage(message) def defaultThemeChanged(self, theme): - self.DefaultThemeLabel.setText( + self.defaultThemeLabel.setText( unicode(translate('OpenLP.MainWindow', 'Default Theme: %s')) % theme) - def toggleMediaManager(self, visible): - if self.mediaManagerDock.isVisible() != visible: - self.mediaManagerDock.setVisible(visible) + def toggleMediaManager(self): + self.mediaManagerDock.setVisible(not self.mediaManagerDock.isVisible()) - def toggleServiceManager(self, visible): - if self.serviceManagerDock.isVisible() != visible: - self.serviceManagerDock.setVisible(visible) + def toggleServiceManager(self): + self.serviceManagerDock.setVisible( + not self.serviceManagerDock.isVisible()) - def toggleThemeManager(self, visible): - if self.themeManagerDock.isVisible() != visible: - self.themeManagerDock.setVisible(visible) + def toggleThemeManager(self): + self.themeManagerDock.setVisible(not self.themeManagerDock.isVisible()) def setPreviewPanelVisibility(self, visible): """ @@ -921,7 +1237,38 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): self.previewController.panel.setVisible(visible) QtCore.QSettings().setValue(u'user interface/preview panel', QtCore.QVariant(visible)) - self.ViewPreviewPanel.setChecked(visible) + self.viewPreviewPanel.setChecked(visible) + + def setLockPanel(self, lock): + """ + Sets the ability to stop the toolbars being changed. + """ + if lock: + self.themeManagerDock.setFeatures( + QtGui.QDockWidget.NoDockWidgetFeatures) + self.serviceManagerDock.setFeatures( + QtGui.QDockWidget.NoDockWidgetFeatures) + self.mediaManagerDock.setFeatures( + QtGui.QDockWidget.NoDockWidgetFeatures) + self.viewMediaManagerItem.setEnabled(False) + self.viewServiceManagerItem.setEnabled(False) + self.viewThemeManagerItem.setEnabled(False) + self.viewPreviewPanel.setEnabled(False) + self.viewLivePanel.setEnabled(False) + else: + self.themeManagerDock.setFeatures( + QtGui.QDockWidget.AllDockWidgetFeatures) + self.serviceManagerDock.setFeatures( + QtGui.QDockWidget.AllDockWidgetFeatures) + self.mediaManagerDock.setFeatures( + QtGui.QDockWidget.AllDockWidgetFeatures) + self.viewMediaManagerItem.setEnabled(True) + self.viewServiceManagerItem.setEnabled(True) + self.viewThemeManagerItem.setEnabled(True) + self.viewPreviewPanel.setEnabled(True) + self.viewLivePanel.setEnabled(True) + QtCore.QSettings().setValue(u'user interface/lock panel', + QtCore.QVariant(lock)) def setLivePanelVisibility(self, visible): """ @@ -936,7 +1283,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): self.liveController.panel.setVisible(visible) QtCore.QSettings().setValue(u'user interface/live panel', QtCore.QVariant(visible)) - self.ViewLivePanel.setChecked(visible) + self.viewLivePanel.setChecked(visible) def loadSettings(self): """ @@ -944,6 +1291,9 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): """ log.debug(u'Loading QSettings') settings = QtCore.QSettings() + # Remove obsolete entries. + settings.remove(u'custom slide') + settings.remove(u'service') settings.beginGroup(self.generalSettingsSection) self.recentFiles = settings.value(u'recent files').toStringList() settings.endGroup() @@ -953,12 +1303,22 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): self.restoreGeometry( settings.value(u'main window geometry').toByteArray()) self.restoreState(settings.value(u'main window state').toByteArray()) + self.liveController.splitter.restoreState( + settings.value(u'live splitter geometry').toByteArray()) + self.previewController.splitter.restoreState( + settings.value(u'preview splitter geometry').toByteArray()) + self.controlSplitter.restoreState( + settings.value(u'mainwindow splitter geometry').toByteArray()) + settings.endGroup() def saveSettings(self): """ Save the main window settings. """ + # Exit if we just did a settings import. + if self.settingsImported: + return log.debug(u'Saving QSettings') settings = QtCore.QSettings() settings.beginGroup(self.generalSettingsSection) @@ -973,31 +1333,44 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): QtCore.QVariant(self.saveState())) settings.setValue(u'main window geometry', QtCore.QVariant(self.saveGeometry())) + settings.setValue(u'live splitter geometry', + QtCore.QVariant(self.liveController.splitter.saveState())) + settings.setValue(u'preview splitter geometry', + QtCore.QVariant(self.previewController.splitter.saveState())) + settings.setValue(u'mainwindow splitter geometry', + QtCore.QVariant(self.controlSplitter.saveState())) settings.endGroup() - def updateFileMenu(self): + def updateRecentFilesMenu(self): """ - Updates the file menu with the latest list of service files accessed. + Updates the recent file menu with the latest list of service files + accessed. """ recentFileCount = QtCore.QSettings().value( u'advanced/recent file count', QtCore.QVariant(4)).toInt()[0] - self.FileMenu.clear() - add_actions(self.FileMenu, self.FileMenuActions[:-1]) existingRecentFiles = [recentFile for recentFile in self.recentFiles if QtCore.QFile.exists(recentFile)] recentFilesToDisplay = existingRecentFiles[0:recentFileCount] - if recentFilesToDisplay: - self.FileMenu.addSeparator() - for fileId, filename in enumerate(recentFilesToDisplay): - log.debug('Recent file name: %s', filename) - action = QtGui.QAction(u'&%d %s' % (fileId + 1, - QtCore.QFileInfo(filename).fileName()), self) - action.setData(QtCore.QVariant(filename)) - self.connect(action, QtCore.SIGNAL(u'triggered()'), - self.ServiceManagerContents.onRecentServiceClicked) - self.FileMenu.addAction(action) - self.FileMenu.addSeparator() - self.FileMenu.addAction(self.FileMenuActions[-1]) + self.recentFilesMenu.clear() + for fileId, filename in enumerate(recentFilesToDisplay): + log.debug('Recent file name: %s', filename) + action = base_action(self, u'') + action.setText(u'&%d %s' % + (fileId + 1, QtCore.QFileInfo(filename).fileName())) + action.setData(QtCore.QVariant(filename)) + self.connect(action, QtCore.SIGNAL(u'triggered()'), + self.serviceManagerContents.onRecentServiceClicked) + self.recentFilesMenu.addAction(action) + clearRecentFilesAction = base_action(self, u'') + clearRecentFilesAction.setText( + translate('OpenLP.MainWindow', 'Clear List', + 'Clear List of recent files')) + clearRecentFilesAction.setStatusTip( + translate('OpenLP.MainWindow', 'Clear the list of recent files.')) + add_actions(self.recentFilesMenu, (None, clearRecentFilesAction)) + self.connect(clearRecentFilesAction, QtCore.SIGNAL(u'triggered()'), + self.recentFiles.clear) + clearRecentFilesAction.setEnabled(not self.recentFiles.isEmpty()) def addRecentFile(self, filename): """ @@ -1019,3 +1392,34 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): while self.recentFiles.count() > maxRecentFiles: # Don't care what API says takeLast works, removeLast doesn't! self.recentFiles.takeLast() + + def displayProgressBar(self, size): + """ + Make Progress bar visible and set size + """ + self.loadProgressBar.show() + self.loadProgressBar.setMaximum(size) + self.loadProgressBar.setValue(0) + Receiver.send_message(u'openlp_process_events') + + def incrementProgressBar(self): + """ + Increase the Progress Bar value by 1 + """ + self.loadProgressBar.setValue(self.loadProgressBar.value() + 1) + Receiver.send_message(u'openlp_process_events') + + def finishedProgressBar(self): + """ + Trigger it's removal after 2.5 second + """ + self.timer_id = self.startTimer(2500) + + def timerEvent(self, event): + """ + Remove the Progress bar from view. + """ + if event.timerId() == self.timer_id: + self.timer_id = 0 + self.loadProgressBar.hide() + Receiver.send_message(u'openlp_process_events') diff --git a/openlp/core/ui/mediadockmanager.py b/openlp/core/ui/mediadockmanager.py index 2d388faf4..cc6cc92bc 100644 --- a/openlp/core/ui/mediadockmanager.py +++ b/openlp/core/ui/mediadockmanager.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, Armin Köhler, 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 # @@ -65,7 +66,7 @@ class MediaDockManager(object): match = False for dock_index in range(0, self.media_dock.count()): if self.media_dock.widget(dock_index).settingsSection == \ - media_item.plugin.name.lower(): + media_item.plugin.name: match = True break if not match: @@ -83,6 +84,6 @@ class MediaDockManager(object): for dock_index in range(0, self.media_dock.count()): if self.media_dock.widget(dock_index): if self.media_dock.widget(dock_index).settingsSection == \ - media_item.plugin.name.lower(): - self.media_dock.widget(dock_index).hide() + media_item.plugin.name: + self.media_dock.widget(dock_index).setVisible(False) self.media_dock.removeItem(dock_index) diff --git a/openlp/core/ui/plugindialog.py b/openlp/core/ui/plugindialog.py index 0f6ed9395..3fc4bf34b 100644 --- a/openlp/core/ui/plugindialog.py +++ b/openlp/core/ui/plugindialog.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, Armin Köhler, 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 # @@ -78,8 +79,8 @@ class Ui_PluginViewDialog(object): translate('OpenLP.PluginForm', 'Plugin List')) self.pluginInfoGroupBox.setTitle( translate('OpenLP.PluginForm', 'Plugin Details')) - self.versionLabel.setText(u'%s:' % UiStrings.Version) - self.aboutLabel.setText(u'%s:' % UiStrings.About) + self.versionLabel.setText(u'%s:' % UiStrings().Version) + self.aboutLabel.setText(u'%s:' % UiStrings().About) self.statusLabel.setText( translate('OpenLP.PluginForm', 'Status:')) self.statusComboBox.setItemText(0, diff --git a/openlp/core/ui/pluginform.py b/openlp/core/ui/pluginform.py index 15ac62e42..c529248a9 100644 --- a/openlp/core/ui/pluginform.py +++ b/openlp/core/ui/pluginform.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, Armin Köhler, 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 # @@ -39,7 +40,6 @@ class PluginForm(QtGui.QDialog, Ui_PluginViewDialog): """ def __init__(self, parent=None): QtGui.QDialog.__init__(self, parent) - self.parent = parent self.activePlugin = None self.programaticChange = False self.setupUi(self) @@ -64,7 +64,7 @@ class PluginForm(QtGui.QDialog, Ui_PluginViewDialog): self._clearDetails() self.programaticChange = True pluginListWidth = 0 - for plugin in self.parent.pluginManager.plugins: + for plugin in self.parent().pluginManager.plugins: item = QtGui.QListWidgetItem(self.pluginListWidget) # We do this just to make 100% sure the status is an integer as # sometimes when it's loaded from the config, it isn't cast to int. @@ -114,9 +114,9 @@ class PluginForm(QtGui.QDialog, Ui_PluginViewDialog): self._clearDetails() return plugin_name_singular = \ - self.pluginListWidget.currentItem().text().split(u' ')[0] + self.pluginListWidget.currentItem().text().split(u'(')[0][:-1] self.activePlugin = None - for plugin in self.parent.pluginManager.plugins: + for plugin in self.parent().pluginManager.plugins: if plugin.nameStrings[u'singular'] == plugin_name_singular: self.activePlugin = plugin break @@ -132,6 +132,7 @@ class PluginForm(QtGui.QDialog, Ui_PluginViewDialog): Receiver.send_message(u'cursor_busy') self.activePlugin.toggleStatus(PluginStatus.Active) Receiver.send_message(u'cursor_normal') + self.activePlugin.appStartup() else: self.activePlugin.toggleStatus(PluginStatus.Inactive) status_text = unicode( diff --git a/openlp/core/ui/printservicedialog.py b/openlp/core/ui/printservicedialog.py index 41101e8ff..8287ef02a 100644 --- a/openlp/core/ui/printservicedialog.py +++ b/openlp/core/ui/printservicedialog.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, Armin Köhler, 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 # @@ -40,11 +41,6 @@ class ZoomSize(object): Fifty = 4 TwentyFive = 5 - Sizes = [ - translate('OpenLP.PrintServiceDialog', 'Fit Page'), - translate('OpenLP.PrintServiceDialog', 'Fit Width'), - u'100%', u'75%', u'50%', u'25%'] - class Ui_PrintServiceDialog(object): def setupUi(self, printServiceDialog): @@ -58,19 +54,14 @@ class Ui_PrintServiceDialog(object): self.toolbar.setIconSize(QtCore.QSize(22, 22)) self.toolbar.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon) self.printButton = self.toolbar.addAction( - build_icon(u':/general/general_print.png'), 'Print') + build_icon(u':/general/general_print.png'), + translate('OpenLP.PrintServiceForm', 'Print')) self.optionsButton = QtGui.QToolButton(self.toolbar) - self.optionsButton.setText(translate('OpenLP.PrintServiceForm', - 'Options')) self.optionsButton.setToolButtonStyle( QtCore.Qt.ToolButtonTextBesideIcon) - self.optionsButton.setIcon(QtGui.QIcon( - build_icon(u':/system/system_configure.png'))) + self.optionsButton.setIcon(build_icon(u':/system/system_configure.png')) self.optionsButton.setCheckable(True) self.toolbar.addWidget(self.optionsButton) - self.closeButton = self.toolbar.addAction( - build_icon(u':/system/system_close.png'), - translate('OpenLP.PrintServiceForm', 'Close')) self.toolbar.addSeparator() self.plainCopy = self.toolbar.addAction( build_icon(u':/system/system_edit_copy.png'), @@ -80,26 +71,19 @@ class Ui_PrintServiceDialog(object): translate('OpenLP.PrintServiceForm', 'Copy as HTML')) self.toolbar.addSeparator() self.zoomInButton = QtGui.QToolButton(self.toolbar) - self.zoomInButton.setIcon(QtGui.QIcon( - build_icon(u':/general/general_zoom_in.png'))) - self.zoomInButton.setToolTip(translate('OpenLP.PrintServiceForm', - 'Zoom In')) + self.zoomInButton.setIcon(build_icon(u':/general/general_zoom_in.png')) self.zoomInButton.setObjectName(u'zoomInButton') self.zoomInButton.setIconSize(QtCore.QSize(22, 22)) self.toolbar.addWidget(self.zoomInButton) self.zoomOutButton = QtGui.QToolButton(self.toolbar) - self.zoomOutButton.setIcon(QtGui.QIcon( - build_icon(u':/general/general_zoom_out.png'))) - self.zoomOutButton.setToolTip(translate('OpenLP.PrintServiceForm', - 'Zoom Out')) + self.zoomOutButton.setIcon( + build_icon(u':/general/general_zoom_out.png')) self.zoomOutButton.setObjectName(u'zoomOutButton') self.zoomOutButton.setIconSize(QtCore.QSize(22, 22)) self.toolbar.addWidget(self.zoomOutButton) self.zoomOriginalButton = QtGui.QToolButton(self.toolbar) - self.zoomOriginalButton.setIcon(QtGui.QIcon( - build_icon(u':/general/general_zoom_original.png'))) - self.zoomOriginalButton.setToolTip(translate('OpenLP.PrintServiceForm', - 'Zoom Original')) + self.zoomOriginalButton.setIcon( + build_icon(u':/general/general_zoom_original.png')) self.zoomOriginalButton.setObjectName(u'zoomOriginalButton') self.zoomOriginalButton.setIconSize(QtCore.QSize(22, 22)) self.toolbar.addWidget(self.zoomOriginalButton) @@ -117,23 +101,22 @@ class Ui_PrintServiceDialog(object): self.optionsLayout.setContentsMargins(8, 8, 8, 8) self.titleLabel = QtGui.QLabel(self.optionsWidget) self.titleLabel.setObjectName(u'titleLabel') - self.titleLabel.setText(u'Title:') self.optionsLayout.addWidget(self.titleLabel) self.titleLineEdit = QtGui.QLineEdit(self.optionsWidget) self.titleLineEdit.setObjectName(u'titleLineEdit') self.optionsLayout.addWidget(self.titleLineEdit) self.footerLabel = QtGui.QLabel(self.optionsWidget) self.footerLabel.setObjectName(u'footerLabel') - self.footerLabel.setText(u'Custom Footer Text:') self.optionsLayout.addWidget(self.footerLabel) - self.footerTextEdit = SpellTextEdit(self.optionsWidget) + self.footerTextEdit = SpellTextEdit(self.optionsWidget, False) self.footerTextEdit.setObjectName(u'footerTextEdit') self.optionsLayout.addWidget(self.footerTextEdit) - self.optionsGroupBox = QtGui.QGroupBox( - translate('OpenLP.PrintServiceForm','Other Options')) + self.optionsGroupBox = QtGui.QGroupBox() self.groupLayout = QtGui.QVBoxLayout() self.slideTextCheckBox = QtGui.QCheckBox() self.groupLayout.addWidget(self.slideTextCheckBox) + self.pageBreakAfterText = QtGui.QCheckBox() + self.groupLayout.addWidget(self.pageBreakAfterText) self.notesCheckBox = QtGui.QCheckBox() self.groupLayout.addWidget(self.notesCheckBox) self.metaDataCheckBox = QtGui.QCheckBox() @@ -148,18 +131,37 @@ class Ui_PrintServiceDialog(object): QtCore.SIGNAL(u'toggled(bool)'), self.toggleOptions) def retranslateUi(self, printServiceDialog): - printServiceDialog.setWindowTitle(UiStrings.PrintServiceOrder) + printServiceDialog.setWindowTitle(UiStrings().PrintService) + self.zoomOutButton.setToolTip(translate('OpenLP.PrintServiceForm', + 'Zoom Out')) + self.zoomOriginalButton.setToolTip(translate('OpenLP.PrintServiceForm', + 'Zoom Original')) + self.zoomInButton.setToolTip(translate('OpenLP.PrintServiceForm', + 'Zoom In')) + self.optionsButton.setText(translate('OpenLP.PrintServiceForm', + 'Options')) + self.titleLabel.setText(translate('OpenLP.PrintServiceForm', 'Title:')) + self.footerLabel.setText(translate('OpenLP.PrintServiceForm', + 'Custom Footer Text:')) + self.optionsGroupBox.setTitle( + translate('OpenLP.PrintServiceForm','Other Options')) self.slideTextCheckBox.setText(translate('OpenLP.PrintServiceForm', 'Include slide text if available')) + self.pageBreakAfterText.setText(translate('OpenLP.PrintServiceForm', + 'Add page break before each text item')) self.notesCheckBox.setText(translate('OpenLP.PrintServiceForm', 'Include service item notes')) self.metaDataCheckBox.setText(translate('OpenLP.PrintServiceForm', 'Include play length of media items')) self.titleLineEdit.setText(translate('OpenLP.PrintServiceForm', - 'Service Order Sheet')) - self.zoomComboBox.addItem(ZoomSize.Sizes[ZoomSize.Page]) - self.zoomComboBox.addItem(ZoomSize.Sizes[ZoomSize.Width]) - self.zoomComboBox.addItem(ZoomSize.Sizes[ZoomSize.OneHundred]) - self.zoomComboBox.addItem(ZoomSize.Sizes[ZoomSize.SeventyFive]) - self.zoomComboBox.addItem(ZoomSize.Sizes[ZoomSize.Fifty]) - self.zoomComboBox.addItem(ZoomSize.Sizes[ZoomSize.TwentyFive]) + 'Service Sheet')) + # Do not change the order. + self.zoomComboBox.addItems([ + translate('OpenLP.PrintServiceDialog', 'Fit Page'), + translate('OpenLP.PrintServiceDialog', 'Fit Width'), + u'100%', + u'75%', + u'50%', + u'25%'] + ) + diff --git a/openlp/core/ui/printserviceform.py b/openlp/core/ui/printserviceform.py index 713e7c27b..c08b6293e 100644 --- a/openlp/core/ui/printserviceform.py +++ b/openlp/core/ui/printserviceform.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, Armin Köhler, 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,22 +24,95 @@ # with this program; if not, write to the Free Software Foundation, Inc., 59 # # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### +import cgi import datetime +import os from PyQt4 import QtCore, QtGui +from lxml import html -from openlp.core.lib import translate +from openlp.core.lib import translate, get_text_file_string, Receiver from openlp.core.lib.ui import UiStrings from openlp.core.ui.printservicedialog import Ui_PrintServiceDialog, ZoomSize +from openlp.core.utils import AppLocation + +DEFAULT_CSS = """/* +Edit this file to customize the service order print. Note, that not all CSS +properties are supported. See: +http://doc.trolltech.com/4.7/richtext-html-subset.html#css-properties +*/ + +.serviceTitle { + font-weight: 600; + font-size: x-large; + color: black; +} + +.item { + color: black; +} + +.itemTitle { + font-weight: 600; + font-size: large; +} + +.itemText { + margin-top: 10px; +} + +.itemFooter { + font-size: 8px; +} + +.itemNotes {} + +.itemNotesTitle { + font-weight: bold; + font-size: 12px; +} + +.itemNotesText { + font-size: 11px; +} + +.media {} + +.mediaTitle { + font-weight: bold; + font-size: 11px; +} + +.mediaText {} + +.imageList {} + +.customNotes { + margin-top: 10px; +} + +.customNotesTitle { + font-weight: bold; + font-size: 11px; +} + +.customNotesText { + font-size: 11px; +} + +.newPage { + page-break-before: always; +} +""" class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog): - def __init__(self, parent, serviceManager): + def __init__(self, mainWindow, serviceManager): """ Constructor """ - QtGui.QDialog.__init__(self, parent) - self.parent = parent + QtGui.QDialog.__init__(self, mainWindow) + self.mainWindow = mainWindow self.serviceManager = serviceManager self.printer = QtGui.QPrinter() self.printDialog = QtGui.QPrintDialog(self.printer, self) @@ -50,6 +124,10 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog): settings.beginGroup(u'advanced') self.slideTextCheckBox.setChecked(settings.value( u'print slide text', QtCore.QVariant(False)).toBool()) + self.pageBreakAfterText.setChecked(settings.value( + u'add page break', QtCore.QVariant(False)).toBool()) + if not self.slideTextCheckBox.isChecked(): + self.pageBreakAfterText.setDisabled(True) self.metaDataCheckBox.setChecked(settings.value( u'print file meta data', QtCore.QVariant(False)).toBool()) self.notesCheckBox.setChecked(settings.value( @@ -60,8 +138,6 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog): # Signals QtCore.QObject.connect(self.printButton, QtCore.SIGNAL(u'triggered()'), self.printServiceOrder) - QtCore.QObject.connect(self.closeButton, - QtCore.SIGNAL(u'triggered()'), self.accept) QtCore.QObject.connect(self.zoomOutButton, QtCore.SIGNAL(u'clicked()'), self.zoomOut) QtCore.QObject.connect(self.zoomInButton, @@ -76,6 +152,9 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog): QtCore.SIGNAL(u'triggered()'), self.copyText) QtCore.QObject.connect(self.htmlCopy, QtCore.SIGNAL(u'triggered()'), self.copyHtmlText) + QtCore.QObject.connect(self.slideTextCheckBox, + QtCore.SIGNAL(u'stateChanged(int)'), + self.onSlideTextCheckBoxChanged) self.updatePreviewText() def toggleOptions(self, checked): @@ -93,56 +172,123 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog): """ Creates the html text and updates the html of *self.document*. """ - text = u'' - if self.titleLineEdit.text(): - text += u'

%s

' % unicode(self.titleLineEdit.text()) - for item in self.serviceManager.serviceItems: - item = item[u'service_item'] - # Add the title of the service item. - text += u'

%s

' % (item.icon, - item.get_display_title()) - # Add slide text of the service item. - if self.slideTextCheckBox.isChecked(): - if item.is_text(): - # Add the text of the service item. - verse = None - for slide in item.get_frames(): - if not verse: - text += u'

' + slide[u'html'] - verse = slide[u'verseTag'] - elif verse != slide[u'verseTag']: - text += u'<\p>

' + slide[u'html'] - verse = slide[u'verseTag'] - else: - text += u'
' + slide[u'html'] - text += u'

' - elif item.is_image(): - # Add the image names of the service item. - text += u'
    ' - for slide in range(len(item.get_frames())): - text += u'
  1. %s

  2. ' % \ - item.get_frame_title(slide) - text += u'
' - if item.foot_text: - # add footer - text += u'

%s

' % item.foot_text - # Add service items' notes. - if self.notesCheckBox.isChecked(): - if item.notes: - text += u'

%s

%s' % (translate( - 'OpenLP.ServiceManager', 'Notes:'), - item.notes.replace(u'\n', u'
')) - # Add play length of media files. - if item.is_media() and self.metaDataCheckBox.isChecked(): - text += u'

%s %s

' % (translate( - 'OpenLP.ServiceManager', u'Playing time:'), - unicode(datetime.timedelta(seconds=item.media_length))) + html_data = self._addElement(u'html') + self._addElement(u'head', parent=html_data) + self._addElement(u'title', unicode(self.titleLineEdit.text()), + html_data.head) + css_path = os.path.join( + AppLocation.get_data_path(), u'service_print.css') + custom_css = get_text_file_string(css_path) + if not custom_css: + custom_css = DEFAULT_CSS + self._addElement(u'style', custom_css, html_data.head, + attribute=(u'type', u'text/css')) + self._addElement(u'body', parent=html_data) + self._addElement(u'h1', cgi.escape(unicode(self.titleLineEdit.text())), + html_data.body, classId=u'serviceTitle') + for index, item in enumerate(self.serviceManager.serviceItems): + self._addPreviewItem(html_data.body, item[u'service_item'], index) + # Add the custom service notes: if self.footerTextEdit.toPlainText(): - text += u'

%s

%s' % (translate('OpenLP.ServiceManager', - u'Custom Service Notes:'), self.footerTextEdit.toPlainText()) - self.document.setHtml(text) + div = self._addElement(u'div', parent=html_data.body, + classId=u'customNotes') + self._addElement(u'span', translate('OpenLP.ServiceManager', + 'Custom Service Notes: '), div, classId=u'customNotesTitle') + self._addElement(u'span', + cgi.escape(self.footerTextEdit.toPlainText()), + div, classId=u'customNotesText') + self.document.setHtml(html.tostring(html_data)) self.previewWidget.updatePreview() + def _addPreviewItem(self, body, item, index): + div = self._addElement(u'div', classId=u'item', parent=body) + # Add the title of the service item. + item_title = self._addElement(u'h2', parent=div, classId=u'itemTitle') + self._addElement(u'img', parent=item_title, + attribute=(u'src', item.icon)) + self._addElement(u'span', + u' ' + cgi.escape(item.get_display_title()), item_title) + if self.slideTextCheckBox.isChecked(): + # Add the text of the service item. + if item.is_text(): + verse_def = None + for slide in item.get_frames(): + if not verse_def or verse_def != slide[u'verseTag']: + text_div = self._addElement(u'div', parent=div, + classId=u'itemText') + else: + self._addElement(u'br', parent=text_div) + self._addElement(u'span', slide[u'html'], text_div) + verse_def = slide[u'verseTag'] + # Break the page before the div element. + if index != 0 and self.pageBreakAfterText.isChecked(): + div.set(u'class', u'item newPage') + # Add the image names of the service item. + elif item.is_image(): + ol = self._addElement(u'ol', parent=div, classId=u'imageList') + for slide in range(len(item.get_frames())): + self._addElement(u'li', item.get_frame_title(slide), ol) + # add footer + foot_text = item.foot_text + foot_text = foot_text.partition(u'
')[2] + if foot_text: + foot_text = cgi.escape(foot_text.replace(u'
', u'\n')) + self._addElement(u'div', foot_text.replace(u'\n', u'
'), + parent=div, classId=u'itemFooter') + # Add service items' notes. + if self.notesCheckBox.isChecked(): + if item.notes: + p = self._addElement(u'div', classId=u'itemNotes', parent=div) + self._addElement(u'span', + translate('OpenLP.ServiceManager', 'Notes: '), p, + classId=u'itemNotesTitle') + self._addElement(u'span', + cgi.escape(unicode(item.notes)).replace(u'\n', u'
'), p, + classId=u'itemNotesText') + # Add play length of media files. + if item.is_media() and self.metaDataCheckBox.isChecked(): + tme = item.media_length + if item.end_time > 0: + tme = item.end_time - item.start_time + title = self._addElement(u'div', classId=u'media', parent=div) + self._addElement(u'span', translate('OpenLP.ServiceManager', + 'Playing time: '), title, classId=u'mediaTitle') + self._addElement(u'span', unicode(datetime.timedelta(seconds=tme)), + title, classId=u'mediaText') + + def _addElement(self, tag, text=None, parent=None, classId=None, + attribute=None): + """ + Creates a html element. If ``text`` is given, the element's text will + set and if a ``parent`` is given, the element is appended. + + ``tag`` + The html tag, e. g. ``u'span'``. Defaults to ``None``. + + ``text`` + The text for the tag. Defaults to ``None``. + + ``parent`` + The parent element. Defaults to ``None``. + + ``classId`` + Value for the class attribute + + ``attribute`` + Tuple name/value pair to add as an optional attribute + """ + if text is not None: + element = html.fragment_fromstring(unicode(text), create_parent=tag) + else: + element = html.Element(tag) + if parent is not None: + parent.append(element) + if classId is not None: + element.set(u'class', classId) + if attribute is not None: + element.set(attribute[0], attribute[1]) + return element + def paintRequested(self, printer): """ Paint the preview of the *self.document*. @@ -181,13 +327,15 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog): """ Copies the display text to the clipboard as plain text """ - self.parent.clipboard.setText(self.document.toPlainText()) + self.update_song_usage() + self.mainWindow.clipboard.setText(self.document.toPlainText()) def copyHtmlText(self): """ Copies the display text to the clipboard as Html """ - self.parent.clipboard.setText(self.document.toHtml()) + self.update_song_usage() + self.mainWindow.clipboard.setText(self.document.toHtml()) def printServiceOrder(self): """ @@ -195,6 +343,7 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog): """ if not self.printDialog.exec_(): return + self.update_song_usage() # Print the document. self.document.print_(self.printer) @@ -224,9 +373,16 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog): Called when html copy check box is selected. """ if value == QtCore.Qt.Checked: - self.copyTextButton.setText(UiStrings.CopyToHtml) + self.copyTextButton.setText(UiStrings().CopyToHtml) else: - self.copyTextButton.setText(UiStrings.CopyToText) + self.copyTextButton.setText(UiStrings().CopyToText) + + def onSlideTextCheckBoxChanged(self, state): + """ + Disable or enable the ``pageBreakAfterText`` checkbox as it should only + be enabled, when the ``slideTextCheckBox`` is enabled. + """ + self.pageBreakAfterText.setDisabled(state == QtCore.Qt.Unchecked) def saveOptions(self): """ @@ -237,8 +393,16 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog): settings.beginGroup(u'advanced') settings.setValue(u'print slide text', QtCore.QVariant(self.slideTextCheckBox.isChecked())) + settings.setValue(u'add page break', + QtCore.QVariant(self.pageBreakAfterText.isChecked())) settings.setValue(u'print file meta data', QtCore.QVariant(self.metaDataCheckBox.isChecked())) settings.setValue(u'print notes', QtCore.QVariant(self.notesCheckBox.isChecked())) settings.endGroup() + + def update_song_usage(self): + for index, item in enumerate(self.serviceManager.serviceItems): + # Trigger Audit requests + Receiver.send_message(u'print_service_started', + [item[u'service_item']]) diff --git a/openlp/core/ui/screen.py b/openlp/core/ui/screen.py index 6b4978727..5b81c8be8 100644 --- a/openlp/core/ui/screen.py +++ b/openlp/core/ui/screen.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, Armin Köhler, 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 # @@ -38,9 +39,16 @@ log = logging.getLogger(__name__) class ScreenList(object): """ - Wrapper to handle the parameters of the display screen + Wrapper to handle the parameters of the display screen. + + To get access to the screen list call ``ScreenList.get_instance()``. """ log.info(u'Screen loaded') + instance = None + + @staticmethod + def get_instance(): + return ScreenList.instance def __init__(self, desktop): """ @@ -49,17 +57,15 @@ class ScreenList(object): ``desktop`` A ``QDesktopWidget`` object. """ + ScreenList.instance = self self.desktop = desktop self.preview = None self.current = None self.override = None self.screen_list = [] self.display_count = 0 - # actual display number - self.current_display = 0 - # save config display number - self.monitor_number = 0 self.screen_count_changed() + self._load_screen_settings() QtCore.QObject.connect(desktop, QtCore.SIGNAL(u'resized(int)'), self.screen_resolution_changed) QtCore.QObject.connect(desktop, @@ -73,7 +79,7 @@ class ScreenList(object): ``number`` The number of the screen, which size has changed. """ - log.info(u'screenResolutionChanged %d' % number) + log.info(u'screen_resolution_changed %d' % number) for screen in self.screen_list: if number == screen[u'number']: newScreen = { @@ -98,6 +104,9 @@ class ScreenList(object): ``changed_screen`` The screen's number which has been (un)plugged. """ + # Do not log at start up. + if changed_screen != -1: + log.info(u'screen_count_changed %d' % self.desktop.numScreens()) # Remove unplugged screens. for screen in copy.deepcopy(self.screen_list): if screen[u'number'] == self.desktop.numScreens(): @@ -110,8 +119,7 @@ class ScreenList(object): u'size': self.desktop.screenGeometry(number), u'primary': (self.desktop.primaryScreen() == number) }) - # We do not want to send this message, when the method is called the - # first time. + # We do not want to send this message at start up. if changed_screen != -1: # Reload setting tabs to apply possible changes. Receiver.send_message(u'config_screen_changed') @@ -150,6 +158,7 @@ class ScreenList(object): screen[u'number'], screen[u'size']) if screen[u'primary']: self.current = screen + self.override = copy.deepcopy(self.current) self.screen_list.append(screen) self.display_count += 1 @@ -189,13 +198,10 @@ class ScreenList(object): log.debug(u'set_current_display %s', number) if number + 1 > self.display_count: self.current = self.screen_list[0] - self.override = copy.deepcopy(self.current) - self.current_display = 0 else: self.current = self.screen_list[number] - self.override = copy.deepcopy(self.current) self.preview = copy.deepcopy(self.current) - self.current_display = number + self.override = copy.deepcopy(self.current) if self.display_count == 1: self.preview = self.screen_list[0] @@ -214,4 +220,32 @@ class ScreenList(object): use the correct screen attributes. """ log.debug(u'reset_current_display') - self.set_current_display(self.current_display) + self.set_current_display(self.current[u'number']) + + def _load_screen_settings(self): + """ + Loads the screen size and the monitor number from the settings. + """ + settings = QtCore.QSettings() + settings.beginGroup(u'general') + self.set_current_display(settings.value(u'monitor', + QtCore.QVariant(self.display_count - 1)).toInt()[0]) + self.display = settings.value( + u'display on monitor', QtCore.QVariant(True)).toBool() + override_display = settings.value( + u'override position', QtCore.QVariant(False)).toBool() + x = settings.value(u'x position', + QtCore.QVariant(self.current[u'size'].x())).toInt()[0] + y = settings.value(u'y position', + QtCore.QVariant(self.current[u'size'].y())).toInt()[0] + width = settings.value(u'width', + QtCore.QVariant(self.current[u'size'].width())).toInt()[0] + height = settings.value(u'height', + QtCore.QVariant(self.current[u'size'].height())).toInt()[0] + self.override[u'size'] = QtCore.QRect(x, y, width, height) + self.override[u'primary'] = False + settings.endGroup() + if override_display: + self.set_override_display() + else: + self.reset_current_display() diff --git a/openlp/core/ui/serviceitemeditdialog.py b/openlp/core/ui/serviceitemeditdialog.py index f2909eabb..d821430b2 100644 --- a/openlp/core/ui/serviceitemeditdialog.py +++ b/openlp/core/ui/serviceitemeditdialog.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, Armin Köhler, 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,6 +35,8 @@ class Ui_ServiceItemEditDialog(object): def setupUi(self, serviceItemEditDialog): serviceItemEditDialog.setObjectName(u'serviceItemEditDialog') self.dialogLayout = QtGui.QGridLayout(serviceItemEditDialog) + self.dialogLayout.setContentsMargins(8, 8, 8, 8) + self.dialogLayout.setSpacing(8) self.dialogLayout.setObjectName(u'dialogLayout') self.listWidget = QtGui.QListWidget(serviceItemEditDialog) self.listWidget.setAlternatingRowColors(True) diff --git a/openlp/core/ui/serviceitemeditform.py b/openlp/core/ui/serviceitemeditform.py index 588bdbfd6..974133c3d 100644 --- a/openlp/core/ui/serviceitemeditform.py +++ b/openlp/core/ui/serviceitemeditform.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, Armin Köhler, 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 # @@ -78,7 +79,7 @@ class ServiceItemEditForm(QtGui.QDialog, Ui_ServiceItemEditDialog): if not item: return row = self.listWidget.row(item) - self.itemList.remove(self.itemList[row]) + self.itemList.pop(row) self.loadData() if row == self.listWidget.count(): self.listWidget.setCurrentRow(row - 1) @@ -108,7 +109,7 @@ class ServiceItemEditForm(QtGui.QDialog, Ui_ServiceItemEditDialog): return row = self.listWidget.row(item) temp = self.itemList[row] - self.itemList.remove(self.itemList[row]) + self.itemList.pop(row) if direction == u'up': row -= 1 else: @@ -141,4 +142,3 @@ class ServiceItemEditForm(QtGui.QDialog, Ui_ServiceItemEditDialog): else: self.upButton.setEnabled(True) self.deleteButton.setEnabled(True) - diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 05862ce97..8ad2db9c2 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.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, Armin Köhler, 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,40 +24,45 @@ # with this program; if not, write to the Free Software Foundation, Inc., 59 # # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### +import cgi import cPickle import logging import os +import shutil import zipfile +from tempfile import mkstemp log = logging.getLogger(__name__) from PyQt4 import QtCore, QtGui -from openlp.core.lib import OpenLPToolbar, ServiceItem, context_menu_action, \ - Receiver, build_icon, ItemCapabilities, SettingsManager, translate +from openlp.core.lib import OpenLPToolbar, ServiceItem, Receiver, build_icon, \ + ItemCapabilities, SettingsManager, translate from openlp.core.lib.theme import ThemeLevel -from openlp.core.lib.ui import UiStrings, critical_error_message_box +from openlp.core.lib.ui import UiStrings, critical_error_message_box, \ + context_menu_action, context_menu_separator, find_and_set_in_combo_box from openlp.core.ui import ServiceNoteForm, ServiceItemEditForm, StartTimeForm from openlp.core.ui.printserviceform import PrintServiceForm from openlp.core.utils import AppLocation, delete_file, file_is_unicode, \ split_filename +from openlp.core.utils.actions import ActionList, CategoryOrder class ServiceManagerList(QtGui.QTreeWidget): """ Set up key bindings and mouse behaviour for the service list """ - def __init__(self, mainwindow, parent=None, name=None): + def __init__(self, serviceManager, parent=None, name=None): QtGui.QTreeWidget.__init__(self, parent) - self.mainwindow = mainwindow + self.serviceManager = serviceManager def keyPressEvent(self, event): if isinstance(event, QtGui.QKeyEvent): - #here accept the event and do something + # here accept the event and do something if event.key() == QtCore.Qt.Key_Up: - self.mainwindow.onMoveSelectionUp() + self.serviceManager.onMoveSelectionUp() event.accept() elif event.key() == QtCore.Qt.Key_Down: - self.mainwindow.onMoveSelectionDown() + self.serviceManager.onMoveSelectionDown() event.accept() event.ignore() else: @@ -71,6 +77,9 @@ class ServiceManagerList(QtGui.QTreeWidget): 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) @@ -109,24 +118,24 @@ class ServiceManager(QtGui.QWidget): # Create the top toolbar self.toolbar = OpenLPToolbar(self) self.toolbar.addToolbarButton( - UiStrings.NewService, u':/general/general_new.png', - UiStrings.CreateService, self.onNewServiceClicked) + UiStrings().NewService, u':/general/general_new.png', + UiStrings().CreateService, self.onNewServiceClicked) self.toolbar.addToolbarButton( - UiStrings.OpenService, u':/general/general_open.png', - translate('OpenLP.ServiceManager', 'Load an existing service'), + UiStrings().OpenService, u':/general/general_open.png', + translate('OpenLP.ServiceManager', 'Load an existing service.'), self.onLoadServiceClicked) self.toolbar.addToolbarButton( - UiStrings.SaveService, u':/general/general_save.png', - translate('OpenLP.ServiceManager', 'Save this service'), + UiStrings().SaveService, u':/general/general_save.png', + translate('OpenLP.ServiceManager', 'Save this service.'), self.saveFile) self.toolbar.addSeparator() - self.themeLabel = QtGui.QLabel(u'%s:' % UiStrings.Theme, self) + self.themeLabel = QtGui.QLabel(u'%s:' % UiStrings().Theme, self) self.themeLabel.setMargin(3) self.themeLabel.setObjectName(u'themeLabel') self.toolbar.addToolbarWidget(u'ThemeLabel', self.themeLabel) self.themeComboBox = QtGui.QComboBox(self.toolbar) self.themeComboBox.setToolTip(translate('OpenLP.ServiceManager', - 'Select a theme for the service')) + 'Select a theme for the service.')) self.themeComboBox.setSizeAdjustPolicy( QtGui.QComboBox.AdjustToMinimumContentsLength) self.themeComboBox.setSizePolicy( @@ -164,25 +173,58 @@ class ServiceManager(QtGui.QWidget): u':/services/service_top.png', translate('OpenLP.ServiceManager', 'Move item to the top of the service.'), - self.onServiceTop, shortcut=QtCore.Qt.Key_Home) + self.onServiceTop, shortcuts=[QtCore.Qt.Key_Home]) + self.serviceManagerList.moveTop.setObjectName(u'moveTop') + action_list = ActionList.get_instance() + action_list.add_category( + UiStrings().Service, CategoryOrder.standardToolbar) + action_list.add_action( + self.serviceManagerList.moveTop, UiStrings().Service) self.serviceManagerList.moveUp = self.orderToolbar.addToolbarButton( translate('OpenLP.ServiceManager', 'Move &up'), u':/services/service_up.png', translate('OpenLP.ServiceManager', 'Move item up one position in the service.'), - self.onServiceUp, shortcut=QtCore.Qt.Key_PageUp) + self.onServiceUp, shortcuts=[QtCore.Qt.Key_PageUp]) + self.serviceManagerList.moveUp.setObjectName(u'moveUp') + action_list.add_action( + self.serviceManagerList.moveUp, UiStrings().Service) self.serviceManagerList.moveDown = self.orderToolbar.addToolbarButton( translate('OpenLP.ServiceManager', 'Move &down'), u':/services/service_down.png', translate('OpenLP.ServiceManager', 'Move item down one position in the service.'), - self.onServiceDown, shortcut=QtCore.Qt.Key_PageDown) + self.onServiceDown, shortcuts=[QtCore.Qt.Key_PageDown]) + self.serviceManagerList.moveDown.setObjectName(u'moveDown') + action_list.add_action( + self.serviceManagerList.moveDown, UiStrings().Service) self.serviceManagerList.moveBottom = self.orderToolbar.addToolbarButton( translate('OpenLP.ServiceManager', 'Move to &bottom'), u':/services/service_bottom.png', translate('OpenLP.ServiceManager', 'Move item to the end of the service.'), - self.onServiceEnd, shortcut=QtCore.Qt.Key_End) + self.onServiceEnd, shortcuts=[QtCore.Qt.Key_End]) + self.serviceManagerList.moveBottom.setObjectName(u'moveBottom') + action_list.add_action( + self.serviceManagerList.moveBottom, UiStrings().Service) + self.serviceManagerList.down = self.orderToolbar.addToolbarButton( + translate('OpenLP.ServiceManager', 'Move &down'), + None, + translate('OpenLP.ServiceManager', + 'Moves the selection down the window.'), + self.onMoveSelectionDown, shortcuts=[QtCore.Qt.Key_Down]) + self.serviceManagerList.down.setObjectName(u'down') + action_list.add_action(self.serviceManagerList.down) + self.serviceManagerList.down.setVisible(False) + self.serviceManagerList.up = self.orderToolbar.addToolbarButton( + translate('OpenLP.ServiceManager', 'Move up'), + None, + translate('OpenLP.ServiceManager', + 'Moves the selection up the window.'), + self.onMoveSelectionUp, shortcuts=[QtCore.Qt.Key_Up]) + self.serviceManagerList.up.setObjectName(u'up') + action_list.add_action(self.serviceManagerList.up) + self.serviceManagerList.up.setVisible(False) self.orderToolbar.addSeparator() self.serviceManagerList.delete = self.orderToolbar.addToolbarButton( translate('OpenLP.ServiceManager', '&Delete From Service'), @@ -196,42 +238,49 @@ class ServiceManager(QtGui.QWidget): u':/services/service_expand_all.png', translate('OpenLP.ServiceManager', 'Expand all the service items.'), - self.onExpandAll) + self.onExpandAll, shortcuts=[QtCore.Qt.Key_Plus]) + self.serviceManagerList.expand.setObjectName(u'expand') + action_list.add_action( + self.serviceManagerList.expand, UiStrings().Service) self.serviceManagerList.collapse = self.orderToolbar.addToolbarButton( translate('OpenLP.ServiceManager', '&Collapse all'), u':/services/service_collapse_all.png', translate('OpenLP.ServiceManager', 'Collapse all the service items.'), - self.onCollapseAll) + self.onCollapseAll, shortcuts=[QtCore.Qt.Key_Minus]) + self.serviceManagerList.collapse.setObjectName(u'collapse') + action_list.add_action( + self.serviceManagerList.collapse, UiStrings().Service) self.orderToolbar.addSeparator() self.serviceManagerList.makeLive = self.orderToolbar.addToolbarButton( translate('OpenLP.ServiceManager', 'Go Live'), u':/general/general_live.png', translate('OpenLP.ServiceManager', - 'Send the selected item to Live.'), - self.makeLive, shortcut=QtCore.Qt.Key_Enter, - alternate=QtCore.Qt.Key_Return) - self.orderToolbar.setObjectName(u'orderToolbar') + 'Send the selected item to Live.'), self.makeLive, + shortcuts=[QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return]) + self.serviceManagerList.makeLive.setObjectName(u'orderToolbar') + action_list.add_action( + self.serviceManagerList.makeLive, UiStrings().Service) self.layout.addWidget(self.orderToolbar) # Connect up our signals and slots QtCore.QObject.connect(self.themeComboBox, QtCore.SIGNAL(u'activated(int)'), self.onThemeComboBoxSelected) QtCore.QObject.connect(self.serviceManagerList, - QtCore.SIGNAL(u'doubleClicked(QModelIndex)'), self.makeLive) + QtCore.SIGNAL(u'doubleClicked(QModelIndex)'), self.onMakeLive) QtCore.QObject.connect(self.serviceManagerList, QtCore.SIGNAL(u'itemCollapsed(QTreeWidgetItem*)'), self.collapsed) QtCore.QObject.connect(self.serviceManagerList, QtCore.SIGNAL(u'itemExpanded(QTreeWidgetItem*)'), self.expanded) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'theme_update_list'), self.updateThemeList) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'servicemanager_preview_live'), self.previewLive) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'servicemanager_next_item'), self.nextItem) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'servicemanager_previous_item'), self.previousItem) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'servicemanager_set_item'), self.onSetItem) - QtCore.QObject.connect(Receiver.get_receiver(), - QtCore.SIGNAL(u'servicemanager_list_request'), self.listRequest) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'config_updated'), self.configUpdated) QtCore.QObject.connect(Receiver.get_receiver(), @@ -243,7 +292,7 @@ class ServiceManager(QtGui.QWidget): QtCore.SIGNAL(u'service_item_update'), self.serviceItemUpdate) # Last little bits of setting up self.service_theme = unicode(QtCore.QSettings().value( - self.mainwindow.serviceSettingsSection + u'/service theme', + self.mainwindow.servicemanagerSettingsSection + u'/service theme', QtCore.QVariant(u'')).toString()) self.servicePath = AppLocation.get_section_data_path(u'servicemanager') # build the drag and drop context menu @@ -256,59 +305,58 @@ class ServiceManager(QtGui.QWidget): self.addToAction.setIcon(build_icon(u':/general/general_edit.png')) # build the context menu self.menu = QtGui.QMenu() - self.editAction = self.menu.addAction( - translate('OpenLP.ServiceManager', '&Edit Item')) - self.editAction.setIcon(build_icon(u':/general/general_edit.png')) - self.maintainAction = self.menu.addAction( - translate('OpenLP.ServiceManager', '&Reorder Item')) - self.maintainAction.setIcon(build_icon(u':/general/general_edit.png')) - self.notesAction = self.menu.addAction( - translate('OpenLP.ServiceManager', '&Notes')) - self.notesAction.setIcon(build_icon(u':/services/service_notes.png')) - self.timeAction = self.menu.addAction( - translate('OpenLP.ServiceManager', '&Start Time')) - self.timeAction.setIcon(build_icon(u':/media/media_time.png')) - self.deleteAction = self.menu.addAction( - translate('OpenLP.ServiceManager', '&Delete From Service')) - self.deleteAction.setIcon(build_icon(u':/general/general_delete.png')) - self.sep1 = self.menu.addAction(u'') - self.sep1.setSeparator(True) - self.previewAction = self.menu.addAction( - translate('OpenLP.ServiceManager', 'Show &Preview')) - self.previewAction.setIcon(build_icon(u':/general/general_preview.png')) - self.liveAction = self.menu.addAction( - translate('OpenLP.ServiceManager', 'Show &Live')) - self.liveAction.setIcon(build_icon(u':/general/general_live.png')) - self.sep2 = self.menu.addAction(u'') - self.sep2.setSeparator(True) + self.editAction = context_menu_action( + self.menu, u':/general/general_edit.png', + translate('OpenLP.ServiceManager', '&Edit Item'), self.remoteEdit) + self.maintainAction = context_menu_action( + self.menu, u':/general/general_edit.png', + translate('OpenLP.ServiceManager', '&Reorder Item'), + self.onServiceItemEditForm) + self.notesAction = context_menu_action( + self.menu, u':/services/service_notes.png', + translate('OpenLP.ServiceManager', '&Notes'), + self.onServiceItemNoteForm) + self.timeAction = context_menu_action( + self.menu, u':/media/media_time.png', + translate('OpenLP.ServiceManager', '&Start Time'), + self.onStartTimeForm) + self.deleteAction = context_menu_action( + self.menu, u':/general/general_delete.png', + translate('OpenLP.ServiceManager', '&Delete From Service'), + self.onDeleteFromService) + context_menu_separator(self.menu) + self.previewAction = context_menu_action( + self.menu, u':/general/general_preview.png', + translate('OpenLP.ServiceManager', 'Show &Preview'), + self.makePreview) + self.liveAction = context_menu_action( + self.menu, u':/general/general_live.png', + translate('OpenLP.ServiceManager', 'Show &Live'), self.makeLive) + context_menu_separator(self.menu) self.themeMenu = QtGui.QMenu( translate('OpenLP.ServiceManager', '&Change Item Theme')) self.menu.addMenu(self.themeMenu) - self.setServiceHotkeys() self.serviceManagerList.addActions( [self.serviceManagerList.moveDown, self.serviceManagerList.moveUp, self.serviceManagerList.makeLive, self.serviceManagerList.moveTop, self.serviceManagerList.moveBottom, + self.serviceManagerList.up, + self.serviceManagerList.down, + self.serviceManagerList.expand, + self.serviceManagerList.collapse ]) self.configUpdated() - def setServiceHotkeys(self): - actionList = self.mainwindow.actionList - actionList.add_action(self.serviceManagerList.moveDown, u'Service') - actionList.add_action(self.serviceManagerList.moveUp, u'Service') - actionList.add_action(self.serviceManagerList.moveTop, u'Service') - actionList.add_action(self.serviceManagerList.moveBottom, u'Service') - actionList.add_action(self.serviceManagerList.makeLive, u'Service') - def setModified(self, modified=True): """ Setter for property "modified". Sets whether or not the current service has been modified. """ self._modified = modified - serviceFile = self.shortFileName() or u'Untitled Service' + serviceFile = self.shortFileName() or translate( + 'OpenLP.ServiceManager', 'Untitled Service') self.mainwindow.setServiceModified(modified, serviceFile) def isModified(self): @@ -325,7 +373,7 @@ class ServiceManager(QtGui.QWidget): self.mainwindow.setServiceModified(self.isModified(), self.shortFileName()) QtCore.QSettings(). \ - setValue(u'service/last file',QtCore.QVariant(fileName)) + setValue(u'servicemanager/last file',QtCore.QVariant(fileName)) def fileName(self): """ @@ -363,21 +411,35 @@ class ServiceManager(QtGui.QWidget): return False self.newFile() - def onLoadServiceClicked(self): + def onLoadServiceClicked(self, loadFile=None): + """ + Loads the service file and saves the existing one it there is one + unchanged + + ``loadFile`` + The service file to the loaded. Will be None is from menu so + selection will be required. + """ if self.isModified(): result = self.saveModifiedService() if result == QtGui.QMessageBox.Cancel: return False elif result == QtGui.QMessageBox.Save: self.saveFile() - fileName = unicode(QtGui.QFileDialog.getOpenFileName(self.mainwindow, - translate('OpenLP.ServiceManager', 'Open File'), - SettingsManager.get_last_dir( - self.mainwindow.serviceSettingsSection), - translate('OpenLP.ServiceManager', 'OpenLP Service Files (*.osz)'))) - if not fileName: - return False - SettingsManager.set_last_dir(self.mainwindow.serviceSettingsSection, + if not loadFile: + fileName = unicode(QtGui.QFileDialog.getOpenFileName( + self.mainwindow, + translate('OpenLP.ServiceManager', 'Open File'), + SettingsManager.get_last_dir( + self.mainwindow.servicemanagerSettingsSection), + translate('OpenLP.ServiceManager', + 'OpenLP Service Files (*.osz)'))) + if not fileName: + return False + else: + fileName = loadFile + SettingsManager.set_last_dir( + self.mainwindow.servicemanagerSettingsSection, split_filename(fileName)[0]) self.loadFile(fileName) @@ -402,56 +464,145 @@ class ServiceManager(QtGui.QWidget): self.setFileName(u'') self.setModified(False) QtCore.QSettings(). \ - setValue(u'service/last file',QtCore.QVariant(u'')) + setValue(u'servicemanager/last file',QtCore.QVariant(u'')) def saveFile(self): """ - Save the current Service file. + Save the current service file. + + A temporary file is created so that we don't overwrite the existing one + and leave a mangled service file should there be an error when saving. + Audio files are also copied into the service manager directory, and + then packaged into the zip file. """ if not self.fileName(): return self.saveFileAs() - else: - fileName = self.fileName() - log.debug(u'ServiceManager.saveFile - %s' % fileName) - SettingsManager.set_last_dir(self.mainwindow.serviceSettingsSection, - split_filename(fileName)[0]) - service = [] - serviceFileName = fileName.replace(u'.osz', u'.osd') - zip = None - file = None - try: - write_list = [] - zip = zipfile.ZipFile(unicode(fileName), 'w') - for item in self.serviceItems: - service.append({u'serviceitem': \ - item[u'service_item'].get_service_repr()}) - if item[u'service_item'].uses_file(): - for frame in item[u'service_item'].get_frames(): - if item[u'service_item'].is_image(): - path_from = frame[u'path'] - else: - path_from = unicode(os.path.join( - frame[u'path'], - frame[u'title'])) - # On write a file once - if not path_from in write_list: - write_list.append(path_from) - zip.write(path_from.encode(u'utf-8')) - file = open(serviceFileName, u'wb') - cPickle.dump(service, file) - file.close() - zip.write(serviceFileName.encode(u'utf-8')) - except IOError: - log.exception(u'Failed to save service to disk') - finally: - if file: - file.close() - if zip: - zip.close() - delete_file(serviceFileName) - self.mainwindow.addRecentFile(fileName) + temp_file, temp_file_name = mkstemp(u'.osz', u'openlp_') + # We don't need the file handle. + os.close(temp_file) + log.debug(temp_file_name) + path_file_name = unicode(self.fileName()) + path, file_name = os.path.split(path_file_name) + basename, extension = os.path.splitext(file_name) + service_file_name = '%s.osd' % basename + log.debug(u'ServiceManager.saveFile - %s', path_file_name) + SettingsManager.set_last_dir( + self.mainwindow.servicemanagerSettingsSection, + path) + service = [] + write_list = [] + audio_files = [] + total_size = 0 + Receiver.send_message(u'cursor_busy') + # Number of items + 1 to zip it + self.mainwindow.displayProgressBar(len(self.serviceItems) + 1) + for item in self.serviceItems: + self.mainwindow.incrementProgressBar() + service_item = item[u'service_item'].get_service_repr() + # Get all the audio files, and ready them for embedding in the + # service file. + if len(service_item[u'header'][u'background_audio']) > 0: + for i, filename in \ + enumerate(service_item[u'header'][u'background_audio']): + new_file = os.path.join(u'audio', + item[u'service_item']._uuid, + os.path.split(filename)[1]) + audio_files.append((filename, new_file)) + service_item[u'header'][u'background_audio'][i] = new_file + # Add the service item to the service. + service.append({u'serviceitem': service_item}) + if not item[u'service_item'].uses_file(): + continue + skipMissing = False + for frame in item[u'service_item'].get_frames(): + if item[u'service_item'].is_image(): + path_from = frame[u'path'] + else: + path_from = os.path.join(frame[u'path'], frame[u'title']) + # Only write a file once + if path_from in write_list: + continue + if not os.path.exists(path_from): + if not skipMissing: + Receiver.send_message(u'cursor_normal') + title = unicode(translate('OpenLP.ServiceManager', + 'Service File Missing')) + message = unicode(translate('OpenLP.ServiceManager', + 'File missing from service\n\n %s \n\n' + 'Continue saving?' % path_from )) + answer = QtGui.QMessageBox.critical(self, title, + message, + QtGui.QMessageBox.StandardButtons( + QtGui.QMessageBox.Yes | QtGui.QMessageBox.No | + QtGui.QMessageBox.YesToAll)) + if answer == QtGui.QMessageBox.No: + self.mainwindow.finishedProgressBar() + return False + if answer == QtGui.QMessageBox.YesToAll: + skipMissing = True + Receiver.send_message(u'cursor_busy') + else: + file_size = os.path.getsize(path_from) + write_list.append(path_from) + total_size += file_size + log.debug(u'ServiceManager.saveFile - ZIP contents size is %i bytes' % + total_size) + service_content = cPickle.dumps(service) + # Usual Zip file cannot exceed 2GiB, file with Zip64 cannot be + # extracted using unzip in UNIX. + allow_zip_64 = (total_size > 2147483648 + len(service_content)) + log.debug(u'ServiceManager.saveFile - allowZip64 is %s' % allow_zip_64) + zip = None + success = True + self.mainwindow.incrementProgressBar() + try: + zip = zipfile.ZipFile(temp_file_name, 'w', zipfile.ZIP_STORED, + allow_zip_64) + # First we add service contents. + # We save ALL filenames into ZIP using UTF-8. + zip.writestr(service_file_name.encode(u'utf-8'), service_content) + # Finally add all the listed media files. + for write_from in write_list: + zip.write(write_from, write_from.encode(u'utf-8')) + for audio_from, audio_to in audio_files: + if audio_from.startswith(u'audio'): + # When items are saved, they get new UUID's. Let's copy the + # file to the new location. Unused files can be ignored, + # OpenLP automatically cleans up the service manager dir on + # exit. + audio_from = os.path.join(self.servicePath, audio_from) + save_file = os.path.join(self.servicePath, audio_to) + save_path = os.path.split(save_file)[0] + if not os.path.exists(save_path): + os.makedirs(save_path) + if not os.path.exists(save_file): + shutil.copy(audio_from, save_file) + zip.write(audio_from, audio_to.encode(u'utf-8')) + except IOError: + log.exception(u'Failed to save service to disk: %s', temp_file_name) + # Add this line in after the release to notify the user that saving + # their file failed. Commented out due to string freeze. + #Receiver.send_message(u'openlp_error_message', { + # u'title': translate(u'OpenLP.ServiceManager', + # u'Error Saving File'), + # u'message': translate(u'OpenLP.ServiceManager', + # u'There was an error saving your file.') + #}) + success = False + finally: + if zip: + zip.close() + self.mainwindow.finishedProgressBar() + Receiver.send_message(u'cursor_normal') + if success: + shutil.copy(temp_file_name, path_file_name) + self.mainwindow.addRecentFile(path_file_name) self.setModified(False) - return True + try: + delete_file(temp_file_name) + except: + pass + return success def saveFileAs(self): """ @@ -459,9 +610,9 @@ class ServiceManager(QtGui.QWidget): save the file. """ fileName = unicode(QtGui.QFileDialog.getSaveFileName(self.mainwindow, - UiStrings.SaveService, + UiStrings().SaveService, SettingsManager.get_last_dir( - self.mainwindow.serviceSettingsSection), + self.mainwindow.servicemanagerSettingsSection), translate('OpenLP.ServiceManager', 'OpenLP Service Files (*.osz)'))) if not fileName: return False @@ -476,14 +627,15 @@ class ServiceManager(QtGui.QWidget): def loadFile(self, fileName): if not fileName: return False - else: - fileName = unicode(fileName) + fileName = unicode(fileName) + if not os.path.exists(fileName): + return False zip = None fileTo = None try: zip = zipfile.ZipFile(fileName) - for file in zip.namelist(): - ucsfile = file_is_unicode(file) + for zipinfo in zip.infolist(): + ucsfile = file_is_unicode(zipinfo.filename) if not ucsfile: critical_error_message_box( message=translate('OpenLP.ServiceManager', @@ -491,48 +643,73 @@ class ServiceManager(QtGui.QWidget): 'The content encoding is not UTF-8.')) continue osfile = unicode(QtCore.QDir.toNativeSeparators(ucsfile)) - filePath = os.path.join(self.servicePath, - os.path.split(osfile)[1]) - fileTo = open(filePath, u'wb') - fileTo.write(zip.read(file)) - fileTo.flush() - fileTo.close() - if filePath.endswith(u'osd'): - p_file = filePath + if not osfile.startswith(u'audio'): + osfile = os.path.split(osfile)[1] + log.debug(u'Extract file: %s', osfile) + zipinfo.filename = osfile + zip.extract(zipinfo, self.servicePath) + if osfile.endswith(u'osd'): + p_file = os.path.join(self.servicePath, osfile) if 'p_file' in locals(): Receiver.send_message(u'cursor_busy') fileTo = open(p_file, u'r') items = cPickle.load(fileTo) fileTo.close() self.newFile() + self.mainwindow.displayProgressBar(len(items)) for item in items: + self.mainwindow.incrementProgressBar() serviceItem = ServiceItem() - serviceItem.render_manager = self.mainwindow.renderManager + serviceItem.renderer = self.mainwindow.renderer serviceItem.set_from_service(item, self.servicePath) self.validateItem(serviceItem) - self.addServiceItem(serviceItem) + self.loadItem_uuid = 0 if serviceItem.is_capable(ItemCapabilities.OnLoadUpdate): Receiver.send_message(u'%s_service_load' % serviceItem.name.lower(), serviceItem) + # if the item has been processed + if serviceItem._uuid == self.loadItem_uuid: + serviceItem.edit_id = int(self.loadItem_editId) + self.addServiceItem(serviceItem, repaint=False) delete_file(p_file) - Receiver.send_message(u'cursor_normal') + self.setFileName(fileName) + self.mainwindow.addRecentFile(fileName) + self.setModified(False) + QtCore.QSettings().setValue( + 'servicemanager/last file', QtCore.QVariant(fileName)) else: critical_error_message_box( message=translate('OpenLP.ServiceManager', 'File is not a valid service.')) log.exception(u'File contains no service data') - except (IOError, NameError): + except (IOError, NameError, zipfile.BadZipfile): log.exception(u'Problem loading service file %s' % fileName) + critical_error_message_box( + message=translate('OpenLP.ServiceManager', + 'File could not be opened because it is corrupt.')) + except zipfile.BadZipfile: + if os.path.getsize(fileName) == 0: + log.exception(u'Service file is zero sized: %s' % fileName) + QtGui.QMessageBox.information(self, + translate('OpenLP.ServiceManager', 'Empty File'), + translate('OpenLP.ServiceManager', 'This service file ' + 'does not contain any data.')) + else: + log.exception(u'Service file is cannot be extracted as zip: ' + u'%s' % fileName) + QtGui.QMessageBox.information(self, + translate('OpenLP.ServiceManager', 'Corrupt File'), + translate('OpenLP.ServiceManager', 'This file is either ' + 'corrupt or it is not an OpenLP 2.0 service file.')) + return finally: if fileTo: fileTo.close() if zip: zip.close() - self.setFileName(fileName) - self.mainwindow.addRecentFile(fileName) - self.setModified(False) - QtCore.QSettings(). \ - setValue(u'service/last file', QtCore.QVariant(fileName)) + self.mainwindow.finishedProgressBar() + Receiver.send_message(u'cursor_normal') + self.repaintServiceList(-1, -1) def loadLastFile(self): """ @@ -541,7 +718,7 @@ class ServiceManager(QtGui.QWidget): present. """ fileName = QtCore.QSettings(). \ - value(u'service/last file',QtCore.QVariant(u'')).toString() + value(u'servicemanager/last file',QtCore.QVariant(u'')).toString() if fileName: self.loadFile(fileName) @@ -558,35 +735,31 @@ class ServiceManager(QtGui.QWidget): self.maintainAction.setVisible(False) self.notesAction.setVisible(False) self.timeAction.setVisible(False) - if serviceItem[u'service_item'].is_capable(ItemCapabilities.AllowsEdit)\ + if serviceItem[u'service_item'].is_capable(ItemCapabilities.CanEdit)\ and serviceItem[u'service_item'].edit_id: self.editAction.setVisible(True) if serviceItem[u'service_item']\ - .is_capable(ItemCapabilities.AllowsMaintain): + .is_capable(ItemCapabilities.CanMaintain): self.maintainAction.setVisible(True) if item.parent() is None: self.notesAction.setVisible(True) if serviceItem[u'service_item']\ - .is_capable(ItemCapabilities.AllowsVarableStartTime): + .is_capable(ItemCapabilities.HasVariableStartTime): self.timeAction.setVisible(True) self.themeMenu.menuAction().setVisible(False) - if serviceItem[u'service_item'].is_text(): + # Set up the theme menu. + if serviceItem[u'service_item'].is_text() and \ + self.mainwindow.renderer.theme_level == ThemeLevel.Song: self.themeMenu.menuAction().setVisible(True) - action = self.menu.exec_(self.serviceManagerList.mapToGlobal(point)) - if action == self.editAction: - self.remoteEdit() - if action == self.maintainAction: - self.onServiceItemEditForm() - if action == self.deleteAction: - self.onDeleteFromService() - if action == self.notesAction: - self.onServiceItemNoteForm() - if action == self.timeAction: - self.onStartTimeForm() - if action == self.previewAction: - self.makePreview() - if action == self.liveAction: - self.makeLive() + # The service item does not have a theme, check the "Default". + if serviceItem[u'service_item'].theme is None: + themeAction = self.themeMenu.defaultAction() + else: + themeAction = self.themeMenu.findChild( + QtGui.QAction, serviceItem[u'service_item'].theme) + if themeAction is not None: + themeAction.setChecked(True) + self.menu.exec_(self.serviceManagerList.mapToGlobal(point)) def onServiceItemNoteForm(self): item = self.findServiceItem()[0] @@ -596,15 +769,15 @@ class ServiceManager(QtGui.QWidget): self.serviceItems[item][u'service_item'].notes = \ self.serviceNoteForm.textEdit.toPlainText() self.repaintServiceList(item, -1) + self.setModified() def onStartTimeForm(self): + """ + Opens a dialog to type in service item notes. + """ item = self.findServiceItem()[0] self.startTimeForm.item = self.serviceItems[item] if self.startTimeForm.exec_(): - self.serviceItems[item][u'service_item'].start_time = \ - self.startTimeForm.hourSpinBox.value() * 3600 + \ - self.startTimeForm.minuteSpinBox.value() * 60 + \ - self.startTimeForm.secondSpinBox.value() self.repaintServiceList(item, -1) def onServiceItemEditForm(self): @@ -615,6 +788,19 @@ class ServiceManager(QtGui.QWidget): self.addServiceItem(self.serviceItemEditForm.getServiceItem(), replace=True, expand=self.serviceItems[item][u'expanded']) + def previewLive(self, message): + """ + Called by the SlideController to request a preview item be made live + and allows the next preview to be updated if relevent. + """ + uuid, row = message.split(u':') + for sitem in self.serviceItems: + if sitem[u'service_item']._uuid == uuid: + item = self.serviceManagerList.topLevelItem(sitem[u'order'] - 1) + self.serviceManagerList.setCurrentItem(item) + self.makeLive(int(row)) + return + def nextItem(self): """ Called by the SlideController to select the next service item. @@ -656,7 +842,7 @@ class ServiceManager(QtGui.QWidget): """ Called by a signal to select a specific item. """ - self.setItem(int(message[0])) + self.setItem(int(message)) def setItem(self, index): """ @@ -669,50 +855,25 @@ class ServiceManager(QtGui.QWidget): def onMoveSelectionUp(self): """ - Moves the selection up the window. Called by the up arrow. + Moves the cursor selection up the window. + Called by the up arrow. """ - serviceIterator = QtGui.QTreeWidgetItemIterator(self.serviceManagerList) - tempItem = None - setLastItem = False - while serviceIterator.value(): - if serviceIterator.value().isSelected() and tempItem is None: - setLastItem = True - serviceIterator.value().setSelected(False) - if serviceIterator.value().isSelected(): - # We are on the first record - if tempItem: - tempItem.setSelected(True) - serviceIterator.value().setSelected(False) - else: - tempItem = serviceIterator.value() - lastItem = serviceIterator.value() - serviceIterator += 1 - # Top Item was selected so set the last one - if setLastItem: - lastItem.setSelected(True) - self.setModified(True) + item = self.serviceManagerList.currentItem() + itemBefore = self.serviceManagerList.itemAbove(item) + if itemBefore is None: + return + self.serviceManagerList.setCurrentItem(itemBefore) def onMoveSelectionDown(self): """ - Moves the selection down the window. Called by the down arrow. + Moves the cursor selection down the window. + Called by the down arrow. """ - serviceIterator = QtGui.QTreeWidgetItemIterator(self.serviceManagerList) - firstItem = None - setSelected = False - while serviceIterator.value(): - if not firstItem: - firstItem = serviceIterator.value() - if setSelected: - setSelected = False - serviceIterator.value().setSelected(True) - elif serviceIterator.value() and \ - serviceIterator.value().isSelected(): - serviceIterator.value().setSelected(False) - setSelected = True - serviceIterator += 1 - if setSelected: - firstItem.setSelected(True) - self.setModified(True) + item = self.serviceManagerList.currentItem() + itemAfter = self.serviceManagerList.itemBelow(item) + if itemAfter is None: + return + self.serviceManagerList.setCurrentItem(itemAfter) def onCollapseAll(self): """ @@ -720,7 +881,7 @@ class ServiceManager(QtGui.QWidget): """ for item in self.serviceItems: item[u'expanded'] = False - self.regenerateServiceItems() + self.serviceManagerList.collapseAll() def collapsed(self, item): """ @@ -728,7 +889,7 @@ class ServiceManager(QtGui.QWidget): correct state. """ pos = item.data(0, QtCore.Qt.UserRole).toInt()[0] - self.serviceItems[pos -1 ][u'expanded'] = False + self.serviceItems[pos - 1][u'expanded'] = False def onExpandAll(self): """ @@ -736,7 +897,7 @@ class ServiceManager(QtGui.QWidget): """ for item in self.serviceItems: item[u'expanded'] = True - self.regenerateServiceItems() + self.serviceManagerList.expandAll() def expanded(self, item): """ @@ -744,19 +905,19 @@ class ServiceManager(QtGui.QWidget): correct state. """ pos = item.data(0, QtCore.Qt.UserRole).toInt()[0] - self.serviceItems[pos -1 ][u'expanded'] = True + self.serviceItems[pos - 1][u'expanded'] = True def onServiceTop(self): """ Move the current ServiceItem to the top of the list. """ item, child = self.findServiceItem() - if item < len(self.serviceItems) and item is not -1: + if item < len(self.serviceItems) and item != -1: temp = self.serviceItems[item] self.serviceItems.remove(self.serviceItems[item]) self.serviceItems.insert(0, temp) self.repaintServiceList(0, child) - self.setModified(True) + self.setModified() def onServiceUp(self): """ @@ -768,31 +929,31 @@ class ServiceManager(QtGui.QWidget): self.serviceItems.remove(self.serviceItems[item]) self.serviceItems.insert(item - 1, temp) self.repaintServiceList(item - 1, child) - self.setModified(True) + self.setModified() def onServiceDown(self): """ Move the current ServiceItem one position down in the list. """ item, child = self.findServiceItem() - if item < len(self.serviceItems) and item is not -1: + if item < len(self.serviceItems) and item != -1: temp = self.serviceItems[item] self.serviceItems.remove(self.serviceItems[item]) self.serviceItems.insert(item + 1, temp) self.repaintServiceList(item + 1, child) - self.setModified(True) + self.setModified() def onServiceEnd(self): """ Move the current ServiceItem to the bottom of the list. """ item, child = self.findServiceItem() - if item < len(self.serviceItems) and item is not -1: + if item < len(self.serviceItems) and item != -1: temp = self.serviceItems[item] self.serviceItems.remove(self.serviceItems[item]) self.serviceItems.insert(len(self.serviceItems), temp) self.repaintServiceList(len(self.serviceItems) - 1, child) - self.setModified(True) + self.setModified() def onDeleteFromService(self): """ @@ -802,7 +963,7 @@ class ServiceManager(QtGui.QWidget): if item != -1: self.serviceItems.remove(self.serviceItems[item]) self.repaintServiceList(item - 1, -1) - self.setModified(True) + self.setModified() def repaintServiceList(self, serviceItem, serviceItemChild): """ @@ -844,20 +1005,28 @@ class ServiceManager(QtGui.QWidget): treewidgetitem.setIcon(0, build_icon(u':/general/general_delete.png')) treewidgetitem.setText(0, serviceitem.get_display_title()) - treewidgetitem.setToolTip(0, serviceitem.notes) + tips = [] + if serviceitem.theme and serviceitem.theme != -1: + tips.append(u'%s: %s' % + (unicode(translate('OpenLP.ServiceManager', 'Slide theme')), + serviceitem.theme)) + if serviceitem.notes: + tips.append(u'%s: %s' % + (unicode(translate('OpenLP.ServiceManager', 'Notes')), + cgi.escape(unicode(serviceitem.notes)))) + if item[u'service_item'] \ + .is_capable(ItemCapabilities.HasVariableStartTime): + tips.append(item[u'service_item'].get_media_time()) + treewidgetitem.setToolTip(0, u'
'.join(tips)) treewidgetitem.setData(0, QtCore.Qt.UserRole, QtCore.QVariant(item[u'order'])) + treewidgetitem.setSelected(item[u'selected']) # Add the children to their parent treewidgetitem. for count, frame in enumerate(serviceitem.get_frames()): child = QtGui.QTreeWidgetItem(treewidgetitem) text = frame[u'title'].replace(u'\n', u' ') child.setText(0, text[:40]) child.setData(0, QtCore.Qt.UserRole, QtCore.QVariant(count)) - if item[u'service_item'] \ - .is_capable(ItemCapabilities.AllowsVarableStartTime): - tip = item[u'service_item'].get_media_time() - if tip: - child.setToolTip(0, tip) if serviceItem == itemcount: if item[u'expanded'] and serviceItemChild == count: self.serviceManagerList.setCurrentItem(child) @@ -879,9 +1048,12 @@ class ServiceManager(QtGui.QWidget): """ Empties the servicePath of temporary files. """ + log.debug(u'Cleaning up servicePath') for file in os.listdir(self.servicePath): file_path = os.path.join(self.servicePath, file) delete_file(file_path) + if os.path.exists(os.path.join(self.servicePath, u'audio')): + shutil.rmtree(os.path.join(self.servicePath, u'audio'), True) def onThemeComboBoxSelected(self, currentIndex): """ @@ -889,9 +1061,10 @@ class ServiceManager(QtGui.QWidget): """ log.debug(u'onThemeComboBoxSelected') self.service_theme = unicode(self.themeComboBox.currentText()) - self.mainwindow.renderManager.set_service_theme(self.service_theme) + self.mainwindow.renderer.set_service_theme(self.service_theme) QtCore.QSettings().setValue( - self.mainwindow.serviceSettingsSection + u'/service theme', + self.mainwindow.servicemanagerSettingsSection + + u'/service theme', QtCore.QVariant(self.service_theme)) self.regenerateServiceItems() @@ -901,7 +1074,7 @@ class ServiceManager(QtGui.QWidget): sure the theme combo box is in the correct state. """ log.debug(u'themeChange') - if self.mainwindow.renderManager.theme_level == ThemeLevel.Global: + if self.mainwindow.renderer.theme_level == ThemeLevel.Global: self.toolbar.actions[u'ThemeLabel'].setVisible(False) self.toolbar.actions[u'ThemeWidget'].setVisible(False) else: @@ -916,47 +1089,65 @@ class ServiceManager(QtGui.QWidget): Receiver.send_message(u'cursor_busy') log.debug(u'regenerateServiceItems') # force reset of renderer as theme data has changed - self.mainwindow.renderManager.themedata = None + self.mainwindow.renderer.themedata = None if self.serviceItems: + for item in self.serviceItems: + item[u'selected'] = False + serviceIterator = QtGui.QTreeWidgetItemIterator( + self.serviceManagerList) + selectedItem = None + while serviceIterator.value(): + if serviceIterator.value().isSelected(): + selectedItem = serviceIterator.value() + serviceIterator += 1 + if selectedItem is not None: + if selectedItem.parent() is None: + pos = selectedItem.data(0, QtCore.Qt.UserRole).toInt()[0] + else: + pos = selectedItem.parent().data(0, QtCore.Qt.UserRole). \ + toInt()[0] + self.serviceItems[pos - 1][u'selected'] = True tempServiceItems = self.serviceItems self.serviceManagerList.clear() self.serviceItems = [] self.isNew = True for item in tempServiceItems: self.addServiceItem( - item[u'service_item'], False, expand=item[u'expanded']) + item[u'service_item'], False, expand=item[u'expanded'], + repaint=False, selected=item[u'selected']) # Set to False as items may have changed rendering # does not impact the saved song so True may also be valid - self.setModified(True) + self.setModified() + # Repaint it once only at the end + self.repaintServiceList(-1, -1) Receiver.send_message(u'cursor_normal') def serviceItemUpdate(self, message): """ Triggered from plugins to update service items. + Save the values as they will be used as part of the service load """ - editId, uuid = message.split(u':') - for item in self.serviceItems: - if item[u'service_item']._uuid == uuid: - item[u'service_item'].edit_id = editId - self.setModified(True) + editId, self.loadItem_uuid = message.split(u':') + self.loadItem_editId = int(editId) def replaceServiceItem(self, newItem): """ Using the service item passed replace the one with the same edit id if found. """ - newItem.render() for itemcount, item in enumerate(self.serviceItems): if item[u'service_item'].edit_id == newItem.edit_id and \ item[u'service_item'].name == newItem.name: + newItem.render() newItem.merge(item[u'service_item']) item[u'service_item'] = newItem self.repaintServiceList(itemcount + 1, 0) self.mainwindow.liveController.replaceServiceManagerItem( newItem) - self.setModified(True) + self.setModified() - def addServiceItem(self, item, rebuild=False, expand=None, replace=False): + def addServiceItem(self, item, rebuild=False, expand=None, replace=False, + repaint=True, selected=False): """ Add a Service item to the list @@ -969,7 +1160,7 @@ class ServiceManager(QtGui.QWidget): # if not passed set to config value if expand is None: expand = self.expandTabs - item.render() + item.from_service = True if replace: sitem, child = self.findServiceItem() item.merge(self.serviceItems[sitem][u'service_item']) @@ -977,33 +1168,36 @@ class ServiceManager(QtGui.QWidget): self.repaintServiceList(sitem, child) self.mainwindow.liveController.replaceServiceManagerItem(item) else: + item.render() # nothing selected for dnd if self.dropPosition == 0: if isinstance(item, list): for inditem in item: self.serviceItems.append({u'service_item': inditem, u'order': len(self.serviceItems) + 1, - u'expanded': expand}) + u'expanded': expand, u'selected': selected}) else: self.serviceItems.append({u'service_item': item, u'order': len(self.serviceItems) + 1, - u'expanded': expand}) - self.repaintServiceList(len(self.serviceItems) - 1, -1) + u'expanded': expand, u'selected': selected}) + if repaint: + self.repaintServiceList(len(self.serviceItems) - 1, -1) else: self.serviceItems.insert(self.dropPosition, {u'service_item': item, u'order': self.dropPosition, - u'expanded': expand}) + u'expanded': expand, u'selected': selected}) self.repaintServiceList(self.dropPosition, -1) # if rebuilding list make sure live is fixed. if rebuild: self.mainwindow.liveController.replaceServiceManagerItem(item) self.dropPosition = 0 - self.setModified(True) + self.setModified() def makePreview(self): """ Send the current item to the Preview slide controller """ + Receiver.send_message(u'cursor_busy') item, child = self.findServiceItem() if self.serviceItems[item][u'service_item'].is_valid: self.mainwindow.previewController.addServiceManagerItem( @@ -1013,6 +1207,7 @@ class ServiceManager(QtGui.QWidget): translate('OpenLP.ServiceManager', 'Missing Display Handler'), translate('OpenLP.ServiceManager', 'Your item cannot be ' 'displayed as there is no handler to display it')) + Receiver.send_message(u'cursor_normal') def getServiceItem(self): """ @@ -1024,11 +1219,28 @@ class ServiceManager(QtGui.QWidget): else: return self.serviceItems[item][u'service_item'] - def makeLive(self): + def onMakeLive(self): + """ + Send the current item to the Live slide controller but triggered + by a tablewidget click event. + """ + self.makeLive() + + def makeLive(self, row=-1): """ Send the current item to the Live slide controller + + ``row`` + Row number to be displayed if from preview. + -1 is passed if the value is not set """ item, child = self.findServiceItem() + # No items in service + if item == -1: + return + if row != -1: + child = row + Receiver.send_message(u'cursor_busy') if self.serviceItems[item][u'service_item'].is_valid: self.mainwindow.liveController.addServiceManagerItem( self.serviceItems[item][u'service_item'], child) @@ -1038,7 +1250,7 @@ class ServiceManager(QtGui.QWidget): item += 1 if self.serviceItems and item < len(self.serviceItems) and \ self.serviceItems[item][u'service_item'].is_capable( - ItemCapabilities.AllowsPreview): + ItemCapabilities.CanPreview): self.mainwindow.previewController.addServiceManagerItem( self.serviceItems[item][u'service_item'], 0) self.mainwindow.liveController.previewListWidget.setFocus() @@ -1048,6 +1260,7 @@ class ServiceManager(QtGui.QWidget): translate('OpenLP.ServiceManager', 'Your item cannot be ' 'displayed as the plugin required to display it is missing ' 'or inactive')) + Receiver.send_message(u'cursor_normal') def remoteEdit(self): """ @@ -1055,7 +1268,7 @@ class ServiceManager(QtGui.QWidget): """ item = self.findServiceItem()[0] if self.serviceItems[item][u'service_item']\ - .is_capable(ItemCapabilities.AllowsEdit): + .is_capable(ItemCapabilities.CanEdit): Receiver.send_message(u'%s_edit' % self.serviceItems[item][u'service_item'].name.lower(), u'L:%s' % self.serviceItems[item][u'service_item'].edit_id) @@ -1102,7 +1315,14 @@ class ServiceManager(QtGui.QWidget): Handle of the event pint passed """ link = event.mimeData() - if link.hasText(): + if event.mimeData().hasUrls(): + event.setDropAction(QtCore.Qt.CopyAction) + event.accept() + for url in event.mimeData().urls(): + filename = unicode(url.toLocalFile()) + if filename.endswith(u'.osz'): + self.onLoadServiceClicked(filename) + elif event.mimeData().hasText(): plugin = unicode(event.mimeData().text()) item = self.serviceManagerList.itemAt(event.pos()) # ServiceManager started the drag and drop @@ -1119,7 +1339,7 @@ class ServiceManager(QtGui.QWidget): self.serviceItems.remove(serviceItem) self.serviceItems.insert(endpos, serviceItem) self.repaintServiceList(endpos, child) - self.setModified(True) + self.setModified() else: # we are not over anything so drop replace = False @@ -1131,7 +1351,7 @@ class ServiceManager(QtGui.QWidget): serviceItem = self.serviceItems[pos] if (plugin == serviceItem[u'service_item'].name and serviceItem[u'service_item'].is_capable( - ItemCapabilities.AllowsAdditions)): + ItemCapabilities.CanAppend)): action = self.dndMenu.exec_(QtGui.QCursor.pos()) # New action required if action == self.newAction: @@ -1155,25 +1375,35 @@ class ServiceManager(QtGui.QWidget): self.themeComboBox.clear() self.themeMenu.clear() self.themeComboBox.addItem(u'') + themeGroup = QtGui.QActionGroup(self.themeMenu) + themeGroup.setExclusive(True) + themeGroup.setObjectName(u'themeGroup') + # Create a "Default" theme, which allows the user to reset the item's + # theme to the service theme or global theme. + defaultTheme = context_menu_action(self.themeMenu, None, + UiStrings().Default, self.onThemeChangeAction) + defaultTheme.setCheckable(True) + self.themeMenu.setDefaultAction(defaultTheme) + themeGroup.addAction(defaultTheme) + context_menu_separator(self.themeMenu) for theme in theme_list: self.themeComboBox.addItem(theme) - action = context_menu_action(self.serviceManagerList, None, theme, + themeAction = context_menu_action(self.themeMenu, None, theme, self.onThemeChangeAction) - self.themeMenu.addAction(action) - index = self.themeComboBox.findText(self.service_theme, - QtCore.Qt.MatchExactly) - # Not Found - if index == -1: - index = 0 - self.service_theme = u'' - self.themeComboBox.setCurrentIndex(index) - self.mainwindow.renderManager.set_service_theme(self.service_theme) + themeAction.setObjectName(theme) + themeAction.setCheckable(True) + themeGroup.addAction(themeAction) + find_and_set_in_combo_box(self.themeComboBox, self.service_theme) + self.mainwindow.renderer.set_service_theme(self.service_theme) self.regenerateServiceItems() def onThemeChangeAction(self): - theme = unicode(self.sender().text()) + theme = unicode(self.sender().objectName()) + # No object name means that the "Default" theme is supposed to be used. + if not theme: + theme = None item = self.findServiceItem()[0] - self.serviceItems[item][u'service_item'].theme = theme + self.serviceItems[item][u'service_item'].update_theme(theme) self.regenerateServiceItems() def _getParentItemData(self, item): @@ -1183,23 +1413,6 @@ class ServiceManager(QtGui.QWidget): else: return parentitem.data(0, QtCore.Qt.UserRole).toInt()[0] - def listRequest(self, message=None): - data = [] - item = self.findServiceItem()[0] - if item >= 0 and item < len(self.serviceItems): - curitem = self.serviceItems[item] - else: - curitem = None - for item in self.serviceItems: - service_item = item[u'service_item'] - data_item = {} - data_item[u'title'] = unicode(service_item.get_display_title()) - data_item[u'plugin'] = unicode(service_item.name) - data_item[u'notes'] = unicode(service_item.notes) - data_item[u'selected'] = (item == curitem) - data.append(data_item) - Receiver.send_message(u'servicemanager_list_response', data) - def printServiceOrder(self): """ Print a Service Order Sheet. diff --git a/openlp/core/ui/servicenoteform.py b/openlp/core/ui/servicenoteform.py index 200e12818..ddfcb3381 100644 --- a/openlp/core/ui/servicenoteform.py +++ b/openlp/core/ui/servicenoteform.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, Armin Köhler, 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 # @@ -26,7 +27,7 @@ from PyQt4 import QtCore, QtGui -from openlp.core.lib import translate +from openlp.core.lib import translate, SpellTextEdit from openlp.core.lib.ui import create_accept_reject_button_box class ServiceNoteForm(QtGui.QDialog): @@ -41,11 +42,17 @@ class ServiceNoteForm(QtGui.QDialog): self.setupUi() self.retranslateUi() + def exec_(self): + self.textEdit.setFocus() + return QtGui.QDialog.exec_(self) + def setupUi(self): self.setObjectName(u'serviceNoteEdit') self.dialogLayout = QtGui.QVBoxLayout(self) + self.dialogLayout.setContentsMargins(8, 8, 8, 8) + self.dialogLayout.setSpacing(8) self.dialogLayout.setObjectName(u'verticalLayout') - self.textEdit = QtGui.QTextEdit(self) + self.textEdit = SpellTextEdit(self, False) self.textEdit.setObjectName(u'textEdit') self.dialogLayout.addWidget(self.textEdit) self.dialogLayout.addWidget(create_accept_reject_button_box(self)) diff --git a/openlp/core/ui/settingsdialog.py b/openlp/core/ui/settingsdialog.py index af8ba84ab..296337f0f 100644 --- a/openlp/core/ui/settingsdialog.py +++ b/openlp/core/ui/settingsdialog.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, Armin Köhler, 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 # @@ -32,18 +33,29 @@ from openlp.core.lib.ui import create_accept_reject_button_box class Ui_SettingsDialog(object): def setupUi(self, settingsDialog): settingsDialog.setObjectName(u'settingsDialog') - settingsDialog.resize(700, 500) + settingsDialog.resize(800, 500) settingsDialog.setWindowIcon( build_icon(u':/system/system_settings.png')) - self.settingsLayout = QtGui.QVBoxLayout(settingsDialog) - self.settingsLayout.setObjectName(u'settingsLayout') - self.settingsTabWidget = QtGui.QTabWidget(settingsDialog) - self.settingsTabWidget.setObjectName(u'settingsTabWidget') - self.settingsLayout.addWidget(self.settingsTabWidget) + self.dialogLayout = QtGui.QGridLayout(settingsDialog) + self.dialogLayout.setObjectName(u'dialogLayout') + self.dialogLayout.setMargin(8) + self.settingListWidget = QtGui.QListWidget(settingsDialog) + self.settingListWidget.setUniformItemSizes(True) + self.settingListWidget.setMinimumSize(QtCore.QSize(150, 0)) + self.settingListWidget.setHorizontalScrollBarPolicy( + QtCore.Qt.ScrollBarAlwaysOff) + self.settingListWidget.setObjectName(u'settingListWidget') + self.dialogLayout.addWidget(self.settingListWidget, 0, 0, 1, 1) + self.stackedLayout = QtGui.QStackedLayout() + self.stackedLayout.setObjectName(u'stackedLayout') + self.dialogLayout.addLayout(self.stackedLayout, 0, 1, 1, 1) self.buttonBox = create_accept_reject_button_box(settingsDialog, True) - self.settingsLayout.addWidget(self.buttonBox) + self.dialogLayout.addWidget(self.buttonBox, 1, 1, 1, 1) self.retranslateUi(settingsDialog) QtCore.QMetaObject.connectSlotsByName(settingsDialog) + QtCore.QObject.connect(self.settingListWidget, + QtCore.SIGNAL(u'currentRowChanged(int)'), + self.stackedLayout.setCurrentIndex) def retranslateUi(self, settingsDialog): settingsDialog.setWindowTitle(translate('OpenLP.SettingsForm', diff --git a/openlp/core/ui/settingsform.py b/openlp/core/ui/settingsform.py index 872b37586..37da93b5b 100644 --- a/openlp/core/ui/settingsform.py +++ b/openlp/core/ui/settingsform.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, Armin Köhler, 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 logging from PyQt4 import QtGui -from openlp.core.lib import Receiver +from openlp.core.lib import Receiver, build_icon, PluginStatus from openlp.core.ui import AdvancedTab, GeneralTab, ThemesTab from settingsdialog import Ui_SettingsDialog @@ -40,55 +41,59 @@ class SettingsForm(QtGui.QDialog, Ui_SettingsDialog): """ Provide the form to manipulate the settings for OpenLP """ - def __init__(self, screens, mainWindow, parent=None): + def __init__(self, mainWindow, parent=None): """ Initialise the settings form """ QtGui.QDialog.__init__(self, parent) self.setupUi(self) # General tab - generalTab = GeneralTab(screens) - self.addTab(u'General', generalTab) + self.generalTab = GeneralTab(self) # Themes tab - themesTab = ThemesTab(mainWindow) - self.addTab(u'Themes', themesTab) + self.themesTab = ThemesTab(self, mainWindow) # Advanced tab - advancedTab = AdvancedTab() - self.addTab(u'Advanced', advancedTab) + self.advancedTab = AdvancedTab(self) - def addTab(self, name, tab): - """ - Add a tab to the form - """ - log.info(u'Adding %s tab' % tab.tabTitle) - self.settingsTabWidget.addTab(tab, tab.tabTitleVisible) + def exec_(self): + # load all the settings + self.settingListWidget.clear() + for tabIndex in range(0, self.stackedLayout.count() + 1): + # take at 0 and the rest shuffle up. + self.stackedLayout.takeAt(0) + self.insertTab(self.generalTab, 0, PluginStatus.Active) + self.insertTab(self.themesTab, 1, PluginStatus.Active) + self.insertTab(self.advancedTab, 2, PluginStatus.Active) + count = 3 + for plugin in self.plugins: + if plugin.settings_tab: + self.insertTab(plugin.settings_tab, count, plugin.status) + count += 1 + self.settingListWidget.setCurrentRow(0) + return QtGui.QDialog.exec_(self) - def insertTab(self, tab, location): + def insertTab(self, tab, location, is_active): """ Add a tab to the form at a specific location """ log.debug(u'Inserting %s tab' % tab.tabTitle) - # 14 : There are 3 tables currently and locations starts at -10 - self.settingsTabWidget.insertTab( - location + 14, tab, tab.tabTitleVisible) - - def removeTab(self, tab): - """ - Remove a tab from the form - """ - log.debug(u'remove %s tab' % tab.tabTitleVisible) - for tabIndex in range(0, self.settingsTabWidget.count()): - if self.settingsTabWidget.widget(tabIndex): - if self.settingsTabWidget.widget(tabIndex).tabTitleVisible == \ - tab.tabTitleVisible: - self.settingsTabWidget.removeTab(tabIndex) + # add the tab to get it to display in the correct part of the screen + pos = self.stackedLayout.addWidget(tab) + if is_active: + item_name = QtGui.QListWidgetItem(tab.tabTitleVisible) + icon = build_icon(tab.icon_path) + item_name.setIcon(icon) + self.settingListWidget.insertItem(location, item_name) + else: + # then remove tab to stop the UI displaying it even if + # it is not required. + self.stackedLayout.takeAt(pos) def accept(self): """ Process the form saving the settings """ - for tabIndex in range(0, self.settingsTabWidget.count()): - self.settingsTabWidget.widget(tabIndex).save() + for tabIndex in range(0, self.stackedLayout.count()): + self.stackedLayout.widget(tabIndex).save() # Must go after all settings are save Receiver.send_message(u'config_updated') return QtGui.QDialog.accept(self) @@ -97,13 +102,17 @@ class SettingsForm(QtGui.QDialog, Ui_SettingsDialog): """ Process the form saving the settings """ - for tabIndex in range(0, self.settingsTabWidget.count()): - self.settingsTabWidget.widget(tabIndex).cancel() + for tabIndex in range(0, self.stackedLayout.count()): + self.stackedLayout.widget(tabIndex).cancel() return QtGui.QDialog.reject(self) def postSetUp(self): """ Run any post-setup code for the tabs on the form """ - for tabIndex in range(0, self.settingsTabWidget.count()): - self.settingsTabWidget.widget(tabIndex).postSetUp() + self.generalTab.postSetUp() + self.themesTab.postSetUp() + self.advancedTab.postSetUp() + for plugin in self.plugins: + if plugin.settings_tab: + plugin.settings_tab.postSetUp() diff --git a/openlp/core/ui/shortcutlistdialog.py b/openlp/core/ui/shortcutlistdialog.py index b4673f410..a9b9b22cf 100644 --- a/openlp/core/ui/shortcutlistdialog.py +++ b/openlp/core/ui/shortcutlistdialog.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, Armin Köhler, 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,44 +29,91 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import translate, build_icon +class CaptureShortcutButton(QtGui.QPushButton): + """ + A class to encapsulate a ``QPushButton``. + """ + def __init__(self, *args): + QtGui.QPushButton.__init__(self, *args) + self.setCheckable(True) + + def keyPressEvent(self, event): + """ + Block the ``Key_Space`` key, so that the button will not change the + checked state. + """ + if event.key() == QtCore.Qt.Key_Space and self.isChecked(): + # Ignore the event, so that the parent can take care of this. + event.ignore() + + class Ui_ShortcutListDialog(object): def setupUi(self, shortcutListDialog): shortcutListDialog.setObjectName(u'shortcutListDialog') - self.dialogLayout = QtGui.QVBoxLayout(shortcutListDialog) - self.dialogLayout.setObjectName(u'dialogLayout') + shortcutListDialog.resize(500, 438) + self.shortcutListLayout = QtGui.QVBoxLayout(shortcutListDialog) + self.shortcutListLayout.setObjectName(u'shortcutListLayout') + self.descriptionLabel = QtGui.QLabel(shortcutListDialog) + self.descriptionLabel.setObjectName(u'descriptionLabel') + self.descriptionLabel.setWordWrap(True) + self.shortcutListLayout.addWidget(self.descriptionLabel) self.treeWidget = QtGui.QTreeWidget(shortcutListDialog) - self.treeWidget.setAlternatingRowColors(True) self.treeWidget.setObjectName(u'treeWidget') + self.treeWidget.setAlternatingRowColors(True) self.treeWidget.setColumnCount(3) - self.dialogLayout.addWidget(self.treeWidget) - self.defaultButton = QtGui.QRadioButton(shortcutListDialog) - self.defaultButton.setChecked(True) - self.defaultButton.setObjectName(u'defaultButton') - self.dialogLayout.addWidget(self.defaultButton) - self.customLayout = QtGui.QHBoxLayout() - self.customLayout.setObjectName(u'customLayout') - self.customButton = QtGui.QRadioButton(shortcutListDialog) - self.customButton.setObjectName(u'customButton') - self.customLayout.addWidget(self.customButton) - self.shortcutButton = QtGui.QPushButton(shortcutListDialog) - self.shortcutButton.setIcon( + self.treeWidget.setColumnWidth(0, 250) + self.shortcutListLayout.addWidget(self.treeWidget) + self.detailsLayout = QtGui.QGridLayout() + self.detailsLayout.setObjectName(u'detailsLayout') + self.detailsLayout.setContentsMargins(-1, 0, -1, -1) + self.defaultRadioButton = QtGui.QRadioButton(shortcutListDialog) + self.defaultRadioButton.setObjectName(u'defaultRadioButton') + self.defaultRadioButton.setChecked(True) + self.detailsLayout.addWidget(self.defaultRadioButton, 0, 0, 1, 1) + self.customRadioButton = QtGui.QRadioButton(shortcutListDialog) + self.customRadioButton.setObjectName(u'customRadioButton') + self.detailsLayout.addWidget(self.customRadioButton, 1, 0, 1, 1) + self.primaryLayout = QtGui.QHBoxLayout() + self.primaryLayout.setObjectName(u'primaryLayout') + self.primaryPushButton = CaptureShortcutButton(shortcutListDialog) + self.primaryPushButton.setObjectName(u'primaryPushButton') + self.primaryPushButton.setMinimumSize(QtCore.QSize(84, 0)) + self.primaryPushButton.setIcon( build_icon(u':/system/system_configure_shortcuts.png')) - self.shortcutButton.setCheckable(True) - self.shortcutButton.setObjectName(u'shortcutButton') - self.customLayout.addWidget(self.shortcutButton) - self.clearShortcutButton = QtGui.QToolButton(shortcutListDialog) - self.clearShortcutButton.setIcon( + self.primaryLayout.addWidget(self.primaryPushButton) + self.clearPrimaryButton = QtGui.QToolButton(shortcutListDialog) + self.clearPrimaryButton.setObjectName(u'clearPrimaryButton') + self.clearPrimaryButton.setMinimumSize(QtCore.QSize(0, 16)) + self.clearPrimaryButton.setIcon( build_icon(u':/system/clear_shortcut.png')) - self.clearShortcutButton.setAutoRaise(True) - self.clearShortcutButton.setObjectName(u'clearShortcutButton') - self.customLayout.addWidget(self.clearShortcutButton) - self.customLayout.addStretch() - self.dialogLayout.addLayout(self.customLayout) + self.primaryLayout.addWidget(self.clearPrimaryButton) + self.detailsLayout.addLayout(self.primaryLayout, 1, 1, 1, 1) + self.alternateLayout = QtGui.QHBoxLayout() + self.alternateLayout.setObjectName(u'alternateLayout') + self.alternatePushButton = CaptureShortcutButton(shortcutListDialog) + self.alternatePushButton.setObjectName(u'alternatePushButton') + self.alternatePushButton.setIcon( + build_icon(u':/system/system_configure_shortcuts.png')) + self.alternateLayout.addWidget(self.alternatePushButton) + self.clearAlternateButton = QtGui.QToolButton(shortcutListDialog) + self.clearAlternateButton.setObjectName(u'clearAlternateButton') + self.clearAlternateButton.setIcon( + build_icon(u':/system/clear_shortcut.png')) + self.alternateLayout.addWidget(self.clearAlternateButton) + self.detailsLayout.addLayout(self.alternateLayout, 1, 2, 1, 1) + self.primaryLabel = QtGui.QLabel(shortcutListDialog) + self.primaryLabel.setObjectName(u'primaryLabel') + self.detailsLayout.addWidget(self.primaryLabel, 0, 1, 1, 1) + self.alternateLabel = QtGui.QLabel(shortcutListDialog) + self.alternateLabel.setObjectName(u'alternateLabel') + self.detailsLayout.addWidget(self.alternateLabel, 0, 2, 1, 1) + self.shortcutListLayout.addLayout(self.detailsLayout) self.buttonBox = QtGui.QDialogButtonBox(shortcutListDialog) - self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel | - QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Reset) self.buttonBox.setObjectName(u'buttonBox') - self.dialogLayout.addWidget(self.buttonBox) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel | + QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.RestoreDefaults) + self.shortcutListLayout.addWidget(self.buttonBox) self.retranslateUi(shortcutListDialog) QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(u'accepted()'), shortcutListDialog.accept) @@ -75,14 +123,25 @@ class Ui_ShortcutListDialog(object): def retranslateUi(self, shortcutListDialog): shortcutListDialog.setWindowTitle( - translate('OpenLP.ShortcutListDialog', 'Customize Shortcuts')) + translate('OpenLP.ShortcutListDialog', 'Configure Shortcuts')) + self.descriptionLabel.setText(translate('OpenLP.ShortcutListDialog', + 'Select an action and click one of the buttons below to start ' + 'capturing a new primary or alternate shortcut, respectively.')) self.treeWidget.setHeaderLabels([ translate('OpenLP.ShortcutListDialog', 'Action'), translate('OpenLP.ShortcutListDialog', 'Shortcut'), translate('OpenLP.ShortcutListDialog', 'Alternate')]) - self.defaultButton.setText( - translate('OpenLP.ShortcutListDialog', 'Default: %s')) - self.customButton.setText( - translate('OpenLP.ShortcutListDialog', 'Custom:')) - self.shortcutButton.setText( - translate('OpenLP.ShortcutListDialog', 'None')) + self.defaultRadioButton.setText( + translate('OpenLP.ShortcutListDialog', 'Default')) + self.customRadioButton.setText( + translate('OpenLP.ShortcutListDialog', 'Custom')) + self.primaryPushButton.setToolTip( + translate('OpenLP.ShortcutListDialog', 'Capture shortcut.')) + self.alternatePushButton.setToolTip( + translate('OpenLP.ShortcutListDialog', 'Capture shortcut.')) + self.clearPrimaryButton.setToolTip( + translate('OpenLP.ShortcutListDialog', + 'Restore the default shortcut of this action.')) + self.clearAlternateButton.setToolTip( + translate('OpenLP.ShortcutListDialog', + 'Restore the default shortcut of this action.')) diff --git a/openlp/core/ui/shortcutlistform.py b/openlp/core/ui/shortcutlistform.py index 4f770045c..1eccddc95 100644 --- a/openlp/core/ui/shortcutlistform.py +++ b/openlp/core/ui/shortcutlistform.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, Armin Köhler, 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,7 +30,9 @@ import re from PyQt4 import QtCore, QtGui +from openlp.core.lib import Receiver from openlp.core.utils import translate +from openlp.core.utils.actions import ActionList from shortcutlistdialog import Ui_ShortcutListDialog REMOVE_AMPERSAND = re.compile(r'&{1}') @@ -41,72 +44,437 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog): The shortcut list dialog """ - def __init__(self, parent): - """ - Do some initialisation stuff - """ + def __init__(self, parent=None): QtGui.QDialog.__init__(self, parent) self.setupUi(self) - self.actionList = None - self.captureShortcut = False - QtCore.QObject.connect(self.shortcutButton, - QtCore.SIGNAL(u'toggled(bool)'), self.onShortcutButtonClicked) + self.changedActions = {} + self.action_list = ActionList.get_instance() + QtCore.QObject.connect(self.primaryPushButton, + QtCore.SIGNAL(u'toggled(bool)'), self.onPrimaryPushButtonClicked) + QtCore.QObject.connect(self.alternatePushButton, + QtCore.SIGNAL(u'toggled(bool)'), self.onAlternatePushButtonClicked) + QtCore.QObject.connect(self.treeWidget, QtCore.SIGNAL( + u'currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), + self.onCurrentItemChanged) + QtCore.QObject.connect(self.treeWidget, + QtCore.SIGNAL(u'itemDoubleClicked(QTreeWidgetItem*, int)'), + self.onItemDoubleClicked) + QtCore.QObject.connect(self.clearPrimaryButton, + QtCore.SIGNAL(u'clicked(bool)'), self.onClearPrimaryButtonClicked) + QtCore.QObject.connect(self.clearAlternateButton, + QtCore.SIGNAL(u'clicked(bool)'), self.onClearAlternateButtonClicked) + QtCore.QObject.connect(self.buttonBox, + QtCore.SIGNAL(u'clicked(QAbstractButton*)'), + self.onRestoreDefaultsClicked) + QtCore.QObject.connect(self.defaultRadioButton, + QtCore.SIGNAL(u'clicked(bool)'), self.onDefaultRadioButtonClicked) + QtCore.QObject.connect(self.customRadioButton, + QtCore.SIGNAL(u'clicked(bool)'), self.onCustomRadioButtonClicked) + + def keyPressEvent(self, event): + if event.key() == QtCore.Qt.Key_Space: + self.keyReleaseEvent(event) + elif self.primaryPushButton.isChecked() or \ + self.alternatePushButton.isChecked(): + event.ignore() + elif event.key() == QtCore.Qt.Key_Escape: + event.accept() + self.close() def keyReleaseEvent(self, event): - Qt = QtCore.Qt - if not self.captureShortcut: + if not self.primaryPushButton.isChecked() and \ + not self.alternatePushButton.isChecked(): return key = event.key() - if key == Qt.Key_Shift or key == Qt.Key_Control or \ - key == Qt.Key_Meta or key == Qt.Key_Alt: + if key == QtCore.Qt.Key_Shift or key == QtCore.Qt.Key_Control or \ + key == QtCore.Qt.Key_Meta or key == QtCore.Qt.Key_Alt: return key_string = QtGui.QKeySequence(key).toString() - if event.modifiers() & Qt.ControlModifier == Qt.ControlModifier: + if event.modifiers() & QtCore.Qt.ControlModifier == \ + QtCore.Qt.ControlModifier: key_string = u'Ctrl+' + key_string - if event.modifiers() & Qt.AltModifier == Qt.AltModifier: + if event.modifiers() & QtCore.Qt.AltModifier == QtCore.Qt.AltModifier: key_string = u'Alt+' + key_string - if event.modifiers() & Qt.ShiftModifier == Qt.ShiftModifier: + if event.modifiers() & QtCore.Qt.ShiftModifier == \ + QtCore.Qt.ShiftModifier: key_string = u'Shift+' + key_string + if event.modifiers() & QtCore.Qt.MetaModifier == \ + QtCore.Qt.MetaModifier: + key_string = u'Meta+' + key_string key_sequence = QtGui.QKeySequence(key_string) - existing_key = QtGui.QKeySequence(u'Ctrl+Shift+F8') - if key_sequence == existing_key: - QtGui.QMessageBox.warning( - self, - translate('OpenLP.ShortcutListDialog', 'Duplicate Shortcut'), - unicode(translate('OpenLP.ShortcutListDialog', 'The shortcut ' - '"%s" is already assigned to another action, please ' - 'use a different shortcut.')) % key_sequence.toString(), - QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok), - QtGui.QMessageBox.Ok - ) - else: - self.shortcutButton.setText(key_sequence.toString()) - self.shortcutButton.setChecked(False) - self.captureShortcut = False + if self._validiate_shortcut(self._currentItemAction(), key_sequence): + if self.primaryPushButton.isChecked(): + self._adjustButton(self.primaryPushButton, + False, text=key_sequence.toString()) + elif self.alternatePushButton.isChecked(): + self._adjustButton(self.alternatePushButton, + False, text=key_sequence.toString()) - def exec_(self, actionList): - self.actionList = actionList - self.refreshActions() + def exec_(self): + self.changedActions = {} + self.reloadShortcutList() + self._adjustButton(self.primaryPushButton, False, False, u'') + self._adjustButton(self.alternatePushButton, False, False, u'') return QtGui.QDialog.exec_(self) - def refreshActions(self): + def reloadShortcutList(self): + """ + Reload the ``treeWidget`` list to add new and remove old actions. + """ self.treeWidget.clear() - for category in self.actionList.categories: + for category in self.action_list.categories: + # Check if the category is for internal use only. + if category.name is None: + continue item = QtGui.QTreeWidgetItem([category.name]) for action in category.actions: actionText = REMOVE_AMPERSAND.sub('', unicode(action.text())) - if (len(action.shortcuts()) == 2): - shortcutText = action.shortcuts()[0].toString() - alternateText = action.shortcuts()[1].toString() - else: - shortcutText = action.shortcut().toString() - alternateText = u'' - actionItem = QtGui.QTreeWidgetItem( - [actionText, shortcutText, alternateText]) + actionItem = QtGui.QTreeWidgetItem([actionText]) actionItem.setIcon(0, action.icon()) + actionItem.setData(0, + QtCore.Qt.UserRole, QtCore.QVariant(action)) item.addChild(actionItem) - item.setExpanded(True) self.treeWidget.addTopLevelItem(item) + item.setExpanded(True) + self.refreshShortcutList() - def onShortcutButtonClicked(self, toggled): - self.captureShortcut = toggled + def refreshShortcutList(self): + """ + This refreshes the item's shortcuts shown in the list. Note, this + neither adds new actions nor removes old actions. + """ + iterator = QtGui.QTreeWidgetItemIterator(self.treeWidget) + while iterator.value(): + item = iterator.value() + iterator += 1 + action = self._currentItemAction(item) + if action is None: + continue + shortcuts = self._actionShortcuts(action) + if len(shortcuts) == 0: + item.setText(1, u'') + item.setText(2, u'') + elif len(shortcuts) == 1: + item.setText(1, shortcuts[0].toString()) + item.setText(2, u'') + else: + item.setText(1, shortcuts[0].toString()) + item.setText(2, shortcuts[1].toString()) + self.onCurrentItemChanged() + + def onPrimaryPushButtonClicked(self, toggled): + """ + Save the new primary shortcut. + """ + self.customRadioButton.setChecked(True) + if toggled: + self.alternatePushButton.setChecked(False) + self.primaryPushButton.setText(u'') + return + action = self._currentItemAction() + if action is None: + return + shortcuts = self._actionShortcuts(action) + new_shortcuts = [QtGui.QKeySequence(self.primaryPushButton.text())] + if len(shortcuts) == 2: + new_shortcuts.append(shortcuts[1]) + self.changedActions[action] = new_shortcuts + self.refreshShortcutList() + + def onAlternatePushButtonClicked(self, toggled): + """ + Save the new alternate shortcut. + """ + self.customRadioButton.setChecked(True) + if toggled: + self.primaryPushButton.setChecked(False) + self.alternatePushButton.setText(u'') + return + action = self._currentItemAction() + if action is None: + return + shortcuts = self._actionShortcuts(action) + new_shortcuts = [] + if len(shortcuts) != 0: + new_shortcuts.append(shortcuts[0]) + new_shortcuts.append( + QtGui.QKeySequence(self.alternatePushButton.text())) + self.changedActions[action] = new_shortcuts + if not self.primaryPushButton.text(): + # When we do not have a primary shortcut, the just entered alternate + # shortcut will automatically become the primary shortcut. That is + # why we have to adjust the primary button's text. + self.primaryPushButton.setText(self.alternatePushButton.text()) + self.alternatePushButton.setText(u'') + self.refreshShortcutList() + + def onItemDoubleClicked(self, item, column): + """ + A item has been double clicked. The ``primaryPushButton`` will be + checked and the item's shortcut will be displayed. + """ + action = self._currentItemAction(item) + if action is None: + return + self.primaryPushButton.setChecked(column in [0, 1]) + self.alternatePushButton.setChecked(column not in [0, 1]) + if column in [0, 1]: + self.primaryPushButton.setText(u'') + self.primaryPushButton.setFocus(QtCore.Qt.OtherFocusReason) + else: + self.alternatePushButton.setText(u'') + self.alternatePushButton.setFocus(QtCore.Qt.OtherFocusReason) + + def onCurrentItemChanged(self, item=None, previousItem=None): + """ + A item has been pressed. We adjust the button's text to the action's + shortcut which is encapsulate in the item. + """ + action = self._currentItemAction(item) + self.primaryPushButton.setEnabled(action is not None) + self.alternatePushButton.setEnabled(action is not None) + primary_text = u'' + alternate_text = u'' + primary_label_text = u'' + alternate_label_text = u'' + if action is None: + self.primaryPushButton.setChecked(False) + self.alternatePushButton.setChecked(False) + else: + if len(action.defaultShortcuts) != 0: + primary_label_text = action.defaultShortcuts[0].toString() + if len(action.defaultShortcuts) == 2: + alternate_label_text = action.defaultShortcuts[1].toString() + shortcuts = self._actionShortcuts(action) + # We do not want to loose pending changes, that is why we have to + # keep the text when, this function has not been triggered by a + # signal. + if item is None: + primary_text = self.primaryPushButton.text() + alternate_text = self.alternatePushButton.text() + elif len(shortcuts) == 1: + primary_text = shortcuts[0].toString() + elif len(shortcuts) == 2: + primary_text = shortcuts[0].toString() + alternate_text = shortcuts[1].toString() + # When we are capturing a new shortcut, we do not want, the buttons to + # display the current shortcut. + if self.primaryPushButton.isChecked(): + primary_text = u'' + if self.alternatePushButton.isChecked(): + alternate_text = u'' + self.primaryPushButton.setText(primary_text) + self.alternatePushButton.setText(alternate_text) + self.primaryLabel.setText(primary_label_text) + self.alternateLabel.setText(alternate_label_text) + # We do not want to toggle and radio button, as the function has not + # been triggered by a signal. + if item is None: + return + if primary_label_text == primary_text and \ + alternate_label_text == alternate_text: + self.defaultRadioButton.toggle() + else: + self.customRadioButton.toggle() + + def onRestoreDefaultsClicked(self, button): + """ + Restores all default shortcuts. + """ + if self.buttonBox.buttonRole(button) != \ + QtGui.QDialogButtonBox.ResetRole: + return + if QtGui.QMessageBox.question(self, + translate('OpenLP.ShortcutListDialog', 'Restore Default Shortcuts'), + translate('OpenLP.ShortcutListDialog', 'Do you want to restore all ' + 'shortcuts to their defaults?'), QtGui.QMessageBox.StandardButtons( + QtGui.QMessageBox.Yes | + QtGui.QMessageBox.No)) == QtGui.QMessageBox.No: + return + self._adjustButton(self.primaryPushButton, False, text=u'') + self._adjustButton(self.alternatePushButton, False, text=u'') + for category in self.action_list.categories: + for action in category.actions: + self.changedActions[action] = action.defaultShortcuts + self.refreshShortcutList() + + def onDefaultRadioButtonClicked(self, toggled): + """ + The default radio button has been clicked, which means we have to make + sure, that we use the default shortcuts for the action. + """ + if not toggled: + return + action = self._currentItemAction() + if action is None: + return + temp_shortcuts = self._actionShortcuts(action) + self.changedActions[action] = action.defaultShortcuts + self.refreshShortcutList() + primary_button_text = u'' + alternate_button_text = u'' + if len(temp_shortcuts) != 0: + primary_button_text = temp_shortcuts[0].toString() + if len(temp_shortcuts) == 2: + alternate_button_text = temp_shortcuts[1].toString() + self.primaryPushButton.setText(primary_button_text) + self.alternatePushButton.setText(alternate_button_text) + + def onCustomRadioButtonClicked(self, toggled): + """ + The custom shortcut radio button was clicked, thus we have to restore + the custom shortcuts by calling those functions triggered by button + clicks. + """ + if not toggled: + return + self.onPrimaryPushButtonClicked(False) + self.onAlternatePushButtonClicked(False) + self.refreshShortcutList() + + def save(self): + """ + Save the shortcuts. **Note**, that we do not have to load the shortcuts, + as they are loaded in :class:`~openlp.core.utils.ActionList`. + """ + settings = QtCore.QSettings() + settings.beginGroup(u'shortcuts') + for category in self.action_list.categories: + # Check if the category is for internal use only. + if category.name is None: + continue + for action in category.actions: + if self.changedActions .has_key(action): + action.setShortcuts(self.changedActions[action]) + settings.setValue( + action.objectName(), QtCore.QVariant(action.shortcuts())) + settings.endGroup() + + def onClearPrimaryButtonClicked(self, toggled): + """ + Restore the defaults of this action. + """ + self.primaryPushButton.setChecked(False) + action = self._currentItemAction() + if action is None: + return + shortcuts = self._actionShortcuts(action) + new_shortcuts = [] + if len(action.defaultShortcuts) != 0: + new_shortcuts.append(action.defaultShortcuts[0]) + # We have to check if the primary default shortcut is available. But + # we only have to check, if the action has a default primary + # shortcut (an "empty" shortcut is always valid and if the action + # does not have a default primary shortcut, then the alternative + # shortcut (not the default one) will become primary shortcut, thus + # the check will assume that an action were going to have the same + # shortcut twice. + if not self._validiate_shortcut(action, new_shortcuts[0]) and \ + new_shortcuts[0] != shortcuts[0]: + return + if len(shortcuts) == 2: + new_shortcuts.append(shortcuts[1]) + self.changedActions[action] = new_shortcuts + self.refreshShortcutList() + self.onCurrentItemChanged(self.treeWidget.currentItem()) + + def onClearAlternateButtonClicked(self, toggled): + """ + Restore the defaults of this action. + """ + self.alternatePushButton.setChecked(False) + action = self._currentItemAction() + if action is None: + return + shortcuts = self._actionShortcuts(action) + new_shortcuts = [] + if len(shortcuts) != 0: + new_shortcuts.append(shortcuts[0]) + if len(action.defaultShortcuts) == 2: + new_shortcuts.append(action.defaultShortcuts[1]) + if len(new_shortcuts) == 2: + if not self._validiate_shortcut(action, new_shortcuts[1]): + return + self.changedActions[action] = new_shortcuts + self.refreshShortcutList() + self.onCurrentItemChanged(self.treeWidget.currentItem()) + + def _validiate_shortcut(self, changing_action, key_sequence): + """ + Checks if the given ``changing_action `` can use the given + ``key_sequence``. Returns ``True`` if the ``key_sequence`` can be used + by the action, otherwise displays a dialog and returns ``False``. + + ``changing_action`` + The action which wants to use the ``key_sequence``. + + ``key_sequence`` + The key sequence which the action want so use. + """ + is_valid = True + for category in self.action_list.categories: + for action in category.actions: + shortcuts = self._actionShortcuts(action) + if key_sequence not in shortcuts: + continue + if action is changing_action: + if self.primaryPushButton.isChecked() and \ + shortcuts.index(key_sequence) == 0: + continue + if self.alternatePushButton.isChecked() and \ + shortcuts.index(key_sequence) == 1: + continue + # Have the same parent, thus they cannot have the same shortcut. + if action.parent() is changing_action.parent(): + is_valid = False + # The new shortcut is already assigned, but if both shortcuts + # are only valid in a different widget the new shortcut is + # vaild, because they will not interfere. + if action.shortcutContext() in [QtCore.Qt.WindowShortcut, + QtCore.Qt.ApplicationShortcut]: + is_valid = False + if changing_action.shortcutContext() in \ + [QtCore.Qt.WindowShortcut, QtCore.Qt.ApplicationShortcut]: + is_valid = False + if not is_valid: + Receiver.send_message(u'openlp_warning_message', { + u'title': translate('OpenLP.ShortcutListDialog', + 'Duplicate Shortcut'), + u'message': unicode(translate('OpenLP.ShortcutListDialog', + 'The shortcut "%s" is already assigned to another action, ' + 'please use a different shortcut.')) % key_sequence.toString() + }) + return is_valid + + def _actionShortcuts(self, action): + """ + This returns the shortcuts for the given ``action``, which also includes + those shortcuts which are not saved yet but already assigned (as changes + are applied when closing the dialog). + """ + if self.changedActions.has_key(action): + return self.changedActions[action] + return action.shortcuts() + + def _currentItemAction(self, item=None): + """ + Returns the action of the given ``item``. If no item is given, we return + the action of the current item of the ``treeWidget``. + """ + if item is None: + item = self.treeWidget.currentItem() + if item is None: + return + return item.data(0, QtCore.Qt.UserRole).toPyObject() + + def _adjustButton(self, button, checked=None, enabled=None, text=None): + """ + Can be called to adjust more properties of the given ``button`` at once. + """ + # Set the text before checking the button, because this emits a signal. + if text is not None: + button.setText(text) + if checked is not None: + button.setChecked(checked) + if enabled is not None: + button.setEnabled(enabled) diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index c81b987b4..d871dc474 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.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, Armin Köhler, 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 # @@ -26,14 +27,17 @@ import logging import os +import time +import copy from PyQt4 import QtCore, QtGui from PyQt4.phonon import Phonon -from openlp.core.lib import OpenLPToolbar, Receiver, resize_image, \ - ItemCapabilities, translate -from openlp.core.lib.ui import icon_action, UiStrings, shortcut_action -from openlp.core.ui import HideMode, MainDisplay +from openlp.core.lib import OpenLPToolbar, Receiver, ItemCapabilities, \ + translate, build_icon +from openlp.core.lib.ui import UiStrings, shortcut_action +from openlp.core.ui import HideMode, MainDisplay, ScreenList +from openlp.core.utils.actions import ActionList, CategoryOrder log = logging.getLogger(__name__) @@ -44,7 +48,6 @@ class SlideList(QtGui.QTableWidget): """ def __init__(self, parent=None, name=None): QtGui.QTableWidget.__init__(self, parent.controller) - self.parent = parent class SlideController(QtGui.QWidget): @@ -52,20 +55,19 @@ class SlideController(QtGui.QWidget): SlideController is the slide controller widget. This widget is what the user uses to control the displaying of verses/slides/etc on the screen. """ - def __init__(self, parent, settingsmanager, screens, isLive=False): + def __init__(self, parent, isLive=False): """ Set up the Slide Controller. """ QtGui.QWidget.__init__(self, parent) - self.settingsmanager = settingsmanager self.isLive = isLive - self.parent = parent - self.screens = screens + self.display = None + self.screens = ScreenList.get_instance() self.ratio = float(self.screens.current[u'size'].width()) / \ float(self.screens.current[u'size'].height()) - self.display = MainDisplay(self, screens, isLive) + self.imageManager = self.parent().imageManager self.loopList = [ - u'Start Loop', + u'Play Slides Menu', u'Loop Separator', u'Image SpinBox' ] @@ -77,7 +79,6 @@ class SlideController(QtGui.QWidget): self.songEdit = False self.selectedRow = 0 self.serviceItem = None - self.alertTab = None self.panel = QtGui.QWidget(parent.controlSplitter) self.slideList = {} # Layout for holding panel @@ -87,11 +88,11 @@ class SlideController(QtGui.QWidget): # Type label for the top of the slide controller self.typeLabel = QtGui.QLabel(self.panel) if self.isLive: - self.typeLabel.setText(UiStrings.Live) + self.typeLabel.setText(UiStrings().Live) self.split = 1 self.typePrefix = u'live' else: - self.typeLabel.setText(UiStrings.Preview) + self.typeLabel.setText(UiStrings().Preview) self.split = 0 self.typePrefix = u'preview' self.typeLabel.setStyleSheet(u'font-weight: bold; font-size: 12pt;') @@ -116,8 +117,9 @@ class SlideController(QtGui.QWidget): self.previewListWidget.horizontalHeader().setVisible(False) self.previewListWidget.setColumnWidth(0, self.controller.width()) self.previewListWidget.isLive = self.isLive - self.previewListWidget.setObjectName(u'PreviewListWidget') - self.previewListWidget.setSelectionBehavior(1) + self.previewListWidget.setObjectName(u'previewListWidget') + self.previewListWidget.setSelectionBehavior( + QtGui.QAbstractItemView.SelectRows) self.previewListWidget.setSelectionMode( QtGui.QAbstractItemView.SingleSelection) self.previewListWidget.setEditTriggers( @@ -138,31 +140,40 @@ class SlideController(QtGui.QWidget): self.previousItem = self.toolbar.addToolbarButton( translate('OpenLP.SlideController', 'Previous Slide'), u':/slides/slide_previous.png', - translate('OpenLP.SlideController', 'Move to previous'), - self.onSlideSelectedPrevious) + translate('OpenLP.SlideController', 'Move to previous.'), + self.onSlideSelectedPrevious, + shortcuts=[QtCore.Qt.Key_Up, QtCore.Qt.Key_PageUp], + context=QtCore.Qt.WidgetWithChildrenShortcut) self.nextItem = self.toolbar.addToolbarButton( translate('OpenLP.SlideController', 'Next Slide'), u':/slides/slide_next.png', - translate('OpenLP.SlideController', 'Move to next'), - self.onSlideSelectedNext) + translate('OpenLP.SlideController', 'Move to next.'), + self.onSlideSelectedNext, + shortcuts=[QtCore.Qt.Key_Down, QtCore.Qt.Key_PageDown], + context=QtCore.Qt.WidgetWithChildrenShortcut) self.toolbar.addToolbarSeparator(u'Close Separator') if self.isLive: + # Hide Menu self.hideMenu = QtGui.QToolButton(self.toolbar) self.hideMenu.setText(translate('OpenLP.SlideController', 'Hide')) self.hideMenu.setPopupMode(QtGui.QToolButton.MenuButtonPopup) self.toolbar.addToolbarWidget(u'Hide Menu', self.hideMenu) self.hideMenu.setMenu(QtGui.QMenu( translate('OpenLP.SlideController', 'Hide'), self.toolbar)) - self.blankScreen = icon_action(self.hideMenu, u'Blank Screen', - u':/slides/slide_blank.png', False) + self.blankScreen = shortcut_action(self.hideMenu, u'blankScreen', + [QtCore.Qt.Key_Period], self.onBlankDisplay, + u':/slides/slide_blank.png', False, UiStrings().LiveToolbar) self.blankScreen.setText( translate('OpenLP.SlideController', 'Blank Screen')) - self.themeScreen = icon_action(self.hideMenu, u'Blank Theme', - u':/slides/slide_theme.png', False) + self.themeScreen = shortcut_action(self.hideMenu, u'themeScreen', + [QtGui.QKeySequence(u'T')], self.onThemeDisplay, + u':/slides/slide_theme.png', False, UiStrings().LiveToolbar) self.themeScreen.setText( translate('OpenLP.SlideController', 'Blank to Theme')) - self.desktopScreen = icon_action(self.hideMenu, u'Desktop Screen', - u':/slides/slide_desktop.png', False) + self.desktopScreen = shortcut_action(self.hideMenu, + u'desktopScreen', [QtGui.QKeySequence(u'D')], + self.onHideDisplay, u':/slides/slide_desktop.png', False, + UiStrings().LiveToolbar) self.desktopScreen.setText( translate('OpenLP.SlideController', 'Show Desktop')) self.hideMenu.setDefaultAction(self.blankScreen) @@ -170,50 +181,70 @@ class SlideController(QtGui.QWidget): self.hideMenu.menu().addAction(self.themeScreen) self.hideMenu.menu().addAction(self.desktopScreen) self.toolbar.addToolbarSeparator(u'Loop Separator') - self.toolbar.addToolbarButton( - # Does not need translating - control string. - u'Start Loop', u':/media/media_time.png', - translate('OpenLP.SlideController', 'Start continuous loop'), - self.onStartLoop) - self.toolbar.addToolbarButton( - # Does not need translating - control string. - u'Stop Loop', u':/media/media_stop.png', - translate('OpenLP.SlideController', 'Stop continuous loop'), - self.onStopLoop) + # Play Slides Menu + self.playSlidesMenu = QtGui.QToolButton(self.toolbar) + self.playSlidesMenu.setText(translate('OpenLP.SlideController', + 'Play Slides')) + self.playSlidesMenu.setPopupMode(QtGui.QToolButton.MenuButtonPopup) + self.toolbar.addToolbarWidget(u'Play Slides Menu', + self.playSlidesMenu) + self.playSlidesMenu.setMenu(QtGui.QMenu( + translate('OpenLP.SlideController', 'Play Slides'), + self.toolbar)) + self.playSlidesLoop = shortcut_action(self.playSlidesMenu, + u'playSlidesLoop', [], self.onPlaySlidesLoop, + u':/media/media_time.png', False, UiStrings().LiveToolbar) + self.playSlidesLoop.setText(UiStrings().PlaySlidesInLoop) + self.playSlidesOnce = shortcut_action(self.playSlidesMenu, + u'playSlidesOnce', [], self.onPlaySlidesOnce, + u':/media/media_time.png', False, UiStrings().LiveToolbar) + self.playSlidesOnce.setText(UiStrings().PlaySlidesToEnd) + if QtCore.QSettings().value(self.parent().generalSettingsSection + + u'/enable slide loop', QtCore.QVariant(True)).toBool(): + self.playSlidesMenu.setDefaultAction(self.playSlidesLoop) + else: + self.playSlidesMenu.setDefaultAction(self.playSlidesOnce) + self.playSlidesMenu.menu().addAction(self.playSlidesLoop) + self.playSlidesMenu.menu().addAction(self.playSlidesOnce) + # Loop Delay Spinbox self.delaySpinBox = QtGui.QSpinBox() - self.delaySpinBox.setMinimum(1) - self.delaySpinBox.setMaximum(180) + self.delaySpinBox.setRange(1, 180) self.toolbar.addToolbarWidget(u'Image SpinBox', self.delaySpinBox) - self.delaySpinBox.setSuffix(UiStrings.S) + self.delaySpinBox.setSuffix(UiStrings().Seconds) self.delaySpinBox.setToolTip(translate('OpenLP.SlideController', - 'Delay between slides in seconds')) + 'Delay between slides in seconds.')) else: self.toolbar.addToolbarButton( # Does not need translating - control string. u'Go Live', u':/general/general_live.png', - translate('OpenLP.SlideController', 'Move to live'), + translate('OpenLP.SlideController', 'Move to live.'), self.onGoLive) + self.toolbar.addToolbarButton( + # Does not need translating - control string. + u'Add to Service', u':/general/general_add.png', + translate('OpenLP.SlideController', 'Add to Service.'), + self.onPreviewAddToService) self.toolbar.addToolbarSeparator(u'Close Separator') self.toolbar.addToolbarButton( # Does not need translating - control string. u'Edit Song', u':/general/general_edit.png', translate('OpenLP.SlideController', - 'Edit and reload song preview'), + 'Edit and reload song preview.'), self.onEditSong) self.controllerLayout.addWidget(self.toolbar) # Build a Media ToolBar self.mediabar = OpenLPToolbar(self) self.mediabar.addToolbarButton( u'Media Start', u':/slides/media_playback_start.png', - translate('OpenLP.SlideController', 'Start playing media'), + translate('OpenLP.SlideController', 'Start playing media.'), self.onMediaPlay) self.mediabar.addToolbarButton( u'Media Pause', u':/slides/media_playback_pause.png', - translate('OpenLP.SlideController', 'Start playing media'), + translate('OpenLP.SlideController', 'Start playing media.'), self.onMediaPause) self.mediabar.addToolbarButton( u'Media Stop', u':/slides/media_playback_stop.png', - translate('OpenLP.SlideController', 'Start playing media'), + translate('OpenLP.SlideController', 'Start playing media.'), self.onMediaStop) if self.isLive: # Build the Song Toolbar @@ -224,6 +255,12 @@ class SlideController(QtGui.QWidget): self.songMenu.setMenu(QtGui.QMenu( translate('OpenLP.SlideController', 'Go To'), self.toolbar)) self.toolbar.makeWidgetsInvisible([u'Song Menu']) + # Stuff for items with background audio. + self.audioPauseItem = self.toolbar.addToolbarButton( + u'Pause Audio', u':/slides/media_playback_pause.png', + translate('OpenLP.SlideController', 'Pause audio.'), + self.onAudioPauseClicked, True) + self.audioPauseItem.setVisible(False) # Build the volumeSlider. self.volumeSlider = QtGui.QSlider(QtCore.Qt.Horizontal) self.volumeSlider.setTickInterval(1) @@ -250,23 +287,24 @@ class SlideController(QtGui.QWidget): QtGui.QSizePolicy.Label)) self.previewFrame.setFrameShape(QtGui.QFrame.StyledPanel) self.previewFrame.setFrameShadow(QtGui.QFrame.Sunken) - self.previewFrame.setObjectName(u'PreviewFrame') + self.previewFrame.setObjectName(u'previewFrame') self.grid = QtGui.QGridLayout(self.previewFrame) self.grid.setMargin(8) self.grid.setObjectName(u'grid') self.slideLayout = QtGui.QVBoxLayout() self.slideLayout.setSpacing(0) self.slideLayout.setMargin(0) - self.slideLayout.setObjectName(u'SlideLayout') - self.mediaObject = Phonon.MediaObject(self) - self.video = Phonon.VideoWidget() - self.video.setVisible(False) - self.audio = Phonon.AudioOutput(Phonon.VideoCategory, self.mediaObject) - Phonon.createPath(self.mediaObject, self.video) - Phonon.createPath(self.mediaObject, self.audio) + self.slideLayout.setObjectName(u'slideLayout') if not self.isLive: + self.mediaObject = Phonon.MediaObject(self) + self.video = Phonon.VideoWidget() + self.video.setVisible(False) + self.audio = Phonon.AudioOutput(Phonon.VideoCategory, + self.mediaObject) + Phonon.createPath(self.mediaObject, self.video) + Phonon.createPath(self.mediaObject, self.audio) self.video.setGeometry(QtCore.QRect(0, 0, 300, 225)) - self.slideLayout.insertWidget(0, self.video) + self.slideLayout.insertWidget(0, self.video) # Actual preview screen self.slidePreview = QtGui.QLabel(self) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, @@ -276,35 +314,111 @@ class SlideController(QtGui.QWidget): sizePolicy.setHeightForWidth( self.slidePreview.sizePolicy().hasHeightForWidth()) self.slidePreview.setSizePolicy(sizePolicy) - self.slidePreview.setFixedSize( - QtCore.QSize(self.settingsmanager.slidecontroller_image, - self.settingsmanager.slidecontroller_image / self.ratio)) self.slidePreview.setFrameShape(QtGui.QFrame.Box) self.slidePreview.setFrameShadow(QtGui.QFrame.Plain) self.slidePreview.setLineWidth(1) self.slidePreview.setScaledContents(True) - self.slidePreview.setObjectName(u'SlidePreview') + self.slidePreview.setObjectName(u'slidePreview') self.slideLayout.insertWidget(0, self.slidePreview) self.grid.addLayout(self.slideLayout, 0, 0, 1, 1) + if self.isLive: + self.current_shortcut = u'' + self.shortcutTimer = QtCore.QTimer() + self.shortcutTimer.setObjectName(u'shortcutTimer') + self.shortcutTimer.setSingleShot(True) + self.verseShortcut = shortcut_action(self, u'verseShortcut', + [QtGui.QKeySequence(u'V')], self.slideShortcutActivated, + category=UiStrings().LiveToolbar, + context=QtCore.Qt.WidgetWithChildrenShortcut) + self.verseShortcut.setText(translate( + 'OpenLP.SlideController', 'Go to "Verse"')) + self.shortcut0 = shortcut_action(self, u'0', + [QtGui.QKeySequence(u'0')], self.slideShortcutActivated, + context=QtCore.Qt.WidgetWithChildrenShortcut) + self.shortcut1 = shortcut_action(self, u'1', + [QtGui.QKeySequence(u'1')], self.slideShortcutActivated, + context=QtCore.Qt.WidgetWithChildrenShortcut) + self.shortcut2 = shortcut_action(self, u'2', + [QtGui.QKeySequence(u'2')], self.slideShortcutActivated, + context=QtCore.Qt.WidgetWithChildrenShortcut) + self.shortcut3 = shortcut_action(self, u'3', + [QtGui.QKeySequence(u'3')], self.slideShortcutActivated, + context=QtCore.Qt.WidgetWithChildrenShortcut) + self.shortcut4 = shortcut_action(self, u'4', + [QtGui.QKeySequence(u'4')], self.slideShortcutActivated, + context=QtCore.Qt.WidgetWithChildrenShortcut) + self.shortcut5 = shortcut_action(self, u'5', + [QtGui.QKeySequence(u'5')], self.slideShortcutActivated, + context=QtCore.Qt.WidgetWithChildrenShortcut) + self.shortcut6 = shortcut_action(self, u'6', + [QtGui.QKeySequence(u'6')], self.slideShortcutActivated, + context=QtCore.Qt.WidgetWithChildrenShortcut) + self.shortcut7 = shortcut_action(self, u'7', + [QtGui.QKeySequence(u'7')], self.slideShortcutActivated, + context=QtCore.Qt.WidgetWithChildrenShortcut) + self.shortcut8 = shortcut_action(self, u'8', + [QtGui.QKeySequence(u'8')], self.slideShortcutActivated, + context=QtCore.Qt.WidgetWithChildrenShortcut) + self.shortcut9 = shortcut_action(self, u'9', + [QtGui.QKeySequence(u'9')], self.slideShortcutActivated, + context=QtCore.Qt.WidgetWithChildrenShortcut) + self.chorusShortcut = shortcut_action(self, u'chorusShortcut', + [QtGui.QKeySequence(u'C')], self.slideShortcutActivated, + category=UiStrings().LiveToolbar, + context=QtCore.Qt.WidgetWithChildrenShortcut) + self.chorusShortcut.setText(translate( + 'OpenLP.SlideController', 'Go to "Chorus"')) + self.bridgeShortcut = shortcut_action(self, u'bridgeShortcut', + [QtGui.QKeySequence(u'B')], self.slideShortcutActivated, + category=UiStrings().LiveToolbar, + context=QtCore.Qt.WidgetWithChildrenShortcut) + self.bridgeShortcut.setText(translate( + 'OpenLP.SlideController', 'Go to "Bridge"')) + self.preChorusShortcut = shortcut_action(self, u'preChorusShortcut', + [QtGui.QKeySequence(u'P')], self.slideShortcutActivated, + category=UiStrings().LiveToolbar, + context=QtCore.Qt.WidgetWithChildrenShortcut) + self.preChorusShortcut.setText(translate( + 'OpenLP.SlideController', 'Go to "Pre-Chorus"')) + self.introShortcut = shortcut_action(self, u'introShortcut', + [QtGui.QKeySequence(u'I')], self.slideShortcutActivated, + category=UiStrings().LiveToolbar, + context=QtCore.Qt.WidgetWithChildrenShortcut) + self.introShortcut.setText(translate( + 'OpenLP.SlideController', 'Go to "Intro"')) + self.endingShortcut = shortcut_action(self, u'endingShortcut', + [QtGui.QKeySequence(u'E')], self.slideShortcutActivated, + category=UiStrings().LiveToolbar, + context=QtCore.Qt.WidgetWithChildrenShortcut) + self.endingShortcut.setText(translate( + 'OpenLP.SlideController', 'Go to "Ending"')) + self.otherShortcut = shortcut_action(self, u'otherShortcut', + [QtGui.QKeySequence(u'O')], self.slideShortcutActivated, + category=UiStrings().LiveToolbar, + context=QtCore.Qt.WidgetWithChildrenShortcut) + self.otherShortcut.setText(translate( + 'OpenLP.SlideController', 'Go to "Other"')) + self.previewListWidget.addActions([ + self.shortcut0, self.shortcut1, self.shortcut2, self.shortcut3, + self.shortcut4, self.shortcut5, self.shortcut6, self.shortcut7, + self.shortcut8, self.shortcut9, self.verseShortcut, + self.chorusShortcut, self.bridgeShortcut, + self.preChorusShortcut, self.introShortcut, self.endingShortcut, + self.otherShortcut + ]) + QtCore.QObject.connect( + self.shortcutTimer, QtCore.SIGNAL(u'timeout()'), + self.slideShortcutActivated) # Signals QtCore.QObject.connect(self.previewListWidget, QtCore.SIGNAL(u'clicked(QModelIndex)'), self.onSlideSelected) if self.isLive: - QtCore.QObject.connect(self.blankScreen, - QtCore.SIGNAL(u'triggered(bool)'), self.onBlankDisplay) - QtCore.QObject.connect(self.themeScreen, - QtCore.SIGNAL(u'triggered(bool)'), self.onThemeDisplay) - QtCore.QObject.connect(self.desktopScreen, - QtCore.SIGNAL(u'triggered(bool)'), self.onHideDisplay) QtCore.QObject.connect(self.volumeSlider, QtCore.SIGNAL(u'sliderReleased()'), self.mediaVolume) - QtCore.QObject.connect(Receiver.get_receiver(), - QtCore.SIGNAL(u'maindisplay_active'), self.updatePreview) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'slidecontroller_live_spin_delay'), self.receiveSpinDelay) self.toolbar.makeWidgetsInvisible(self.loopList) - self.toolbar.actions[u'Stop Loop'].setVisible(False) else: QtCore.QObject.connect(self.previewListWidget, QtCore.SIGNAL(u'doubleClicked(QModelIndex)'), @@ -314,7 +428,6 @@ class SlideController(QtGui.QWidget): if self.isLive: self.setLiveHotkeys(self) self.__addActionsToWidget(self.previewListWidget) - self.__addActionsToWidget(self.display) else: self.setPreviewHotkeys() self.previewListWidget.addActions( @@ -323,25 +436,12 @@ class SlideController(QtGui.QWidget): QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'slidecontroller_%s_stop_loop' % self.typePrefix), self.onStopLoop) - QtCore.QObject.connect(Receiver.get_receiver(), - QtCore.SIGNAL(u'slidecontroller_%s_first' % self.typePrefix), - self.onSlideSelectedFirst) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'slidecontroller_%s_next' % self.typePrefix), self.onSlideSelectedNext) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'slidecontroller_%s_previous' % self.typePrefix), self.onSlideSelectedPrevious) - QtCore.QObject.connect(Receiver.get_receiver(), - QtCore.SIGNAL(u'slidecontroller_%s_next_noloop' % self.typePrefix), - self.onSlideSelectedNextNoloop) - QtCore.QObject.connect(Receiver.get_receiver(), - QtCore.SIGNAL(u'slidecontroller_%s_previous_noloop' % - self.typePrefix), - self.onSlideSelectedPreviousNoloop) - QtCore.QObject.connect(Receiver.get_receiver(), - QtCore.SIGNAL(u'slidecontroller_%s_last' % self.typePrefix), - self.onSlideSelectedLast) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'slidecontroller_%s_change' % self.typePrefix), self.onSlideChange) @@ -354,49 +454,135 @@ class SlideController(QtGui.QWidget): QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'slidecontroller_%s_unblank' % self.typePrefix), self.onSlideUnblank) - QtCore.QObject.connect(Receiver.get_receiver(), - QtCore.SIGNAL(u'slidecontroller_%s_text_request' % self.typePrefix), - self.onTextRequest) - QtCore.QObject.connect(Receiver.get_receiver(), - QtCore.SIGNAL(u'config_screen_changed'), self.screenSizeChanged) + + def slideShortcutActivated(self): + """ + Called, when a shortcut has been activated to jump to a chorus, verse, + etc. + + **Note**: This implementation is based on shortcuts. But it rather works + like "key sequenes". You have to press one key after the other and + **not** at the same time. + For example to jump to "V3" you have to press "V" and afterwards but + within a time frame of 350ms you have to press "3". + """ + try: + from openlp.plugins.songs.lib import VerseType + SONGS_PLUGIN_AVAILABLE = True + except ImportError: + SONGS_PLUGIN_AVAILABLE = False + verse_type = unicode(self.sender().objectName()) + if verse_type.startswith(u'verseShortcut'): + if SONGS_PLUGIN_AVAILABLE: + self.current_shortcut = \ + VerseType.TranslatedTags[VerseType.Verse] + else: + self.current_shortcut = u'V' + elif verse_type.startswith(u'chorusShortcut'): + if SONGS_PLUGIN_AVAILABLE: + self.current_shortcut = \ + VerseType.TranslatedTags[VerseType.Chorus] + else: + self.current_shortcut = u'C' + elif verse_type.startswith(u'bridgeShortcut'): + if SONGS_PLUGIN_AVAILABLE: + self.current_shortcut = \ + VerseType.TranslatedTags[VerseType.Bridge] + else: + self.current_shortcut = u'B' + elif verse_type.startswith(u'preChorusShortcut'): + if SONGS_PLUGIN_AVAILABLE: + self.current_shortcut = \ + VerseType.TranslatedTags[VerseType.PreChorus] + else: + self.current_shortcut = u'P' + elif verse_type.startswith(u'introShortcut'): + if SONGS_PLUGIN_AVAILABLE: + self.current_shortcut = \ + VerseType.TranslatedTags[VerseType.Intro] + else: + self.current_shortcut = u'I' + elif verse_type.startswith(u'endingShortcut'): + if SONGS_PLUGIN_AVAILABLE: + self.current_shortcut = \ + VerseType.TranslatedTags[VerseType.Ending] + else: + self.current_shortcut = u'E' + elif verse_type.startswith(u'otherShortcut'): + if SONGS_PLUGIN_AVAILABLE: + self.current_shortcut = \ + VerseType.TranslatedTags[VerseType.Other] + else: + self.current_shortcut = u'O' + elif verse_type.isnumeric(): + self.current_shortcut += verse_type + self.current_shortcut = self.current_shortcut.upper() + keys = self.slideList.keys() + matches = [match for match in keys + if match.startswith(self.current_shortcut)] + if len(matches) == 1: + self.shortcutTimer.stop() + self.current_shortcut = u'' + self.__checkUpdateSelectedSlide(self.slideList[matches[0]]) + self.slideSelected() + elif verse_type != u'shortcutTimer': + # Start the time as we did not have any match. + self.shortcutTimer.start(350) + else: + # The timer timed out. + if self.current_shortcut in keys: + # We had more than one match for example "V1" and "V10", but + # "V1" was the slide we wanted to go. + self.__checkUpdateSelectedSlide( + self.slideList[self.current_shortcut]) + self.slideSelected() + # Reset the shortcut. + self.current_shortcut = u'' def setPreviewHotkeys(self, parent=None): - actionList = self.parent.actionList - self.previousItem.setShortcuts([QtCore.Qt.Key_Up, 0]) - actionList.add_action(self.previousItem, u'Preview') - self.nextItem.setShortcuts([QtCore.Qt.Key_Down, 0]) - actionList.add_action(self.nextItem, u'Preview') + self.previousItem.setObjectName(u'previousItemPreview') + self.nextItem.setObjectName(u'nextItemPreview') + action_list = ActionList.get_instance() + action_list.add_action(self.previousItem) + action_list.add_action(self.nextItem) def setLiveHotkeys(self, parent=None): - actionList = self.parent.actionList - self.previousItem.setShortcuts([QtCore.Qt.Key_Up, QtCore.Qt.Key_PageUp]) - self.previousItem.setShortcutContext( - QtCore.Qt.WidgetWithChildrenShortcut) - actionList.add_action(self.previousItem, u'Live') - self.nextItem.setShortcuts([QtCore.Qt.Key_Down, QtCore.Qt.Key_PageDown]) - self.nextItem.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut) - actionList.add_action(self.nextItem, u'Live') - self.previousService = shortcut_action(parent, - translate('OpenLP.SlideController', 'Previous Service'), - [QtCore.Qt.Key_Left, 0], self.servicePrevious) - actionList.add_action(self.previousService, u'Live') - self.nextService = shortcut_action(parent, - translate('OpenLP.SlideController', 'Next Service'), - [QtCore.Qt.Key_Right, 0], self.serviceNext) - actionList.add_action(self.nextService, u'Live') - self.escapeItem = shortcut_action(parent, - translate('OpenLP.SlideController', 'Escape Item'), - [QtCore.Qt.Key_Escape, 0], self.liveEscape) - actionList.add_action(self.escapeItem, u'Live') + self.previousItem.setObjectName(u'previousItemLive') + self.nextItem.setObjectName(u'nextItemLive') + action_list = ActionList.get_instance() + action_list.add_category( + UiStrings().LiveToolbar, CategoryOrder.standardToolbar) + action_list.add_action(self.previousItem) + action_list.add_action(self.nextItem) + self.previousService = shortcut_action(parent, u'previousService', + [QtCore.Qt.Key_Left], self.servicePrevious, + category=UiStrings().LiveToolbar, + context=QtCore.Qt.WidgetWithChildrenShortcut) + self.previousService.setText( + translate('OpenLP.SlideController', 'Previous Service')) + self.nextService = shortcut_action(parent, 'nextService', + [QtCore.Qt.Key_Right], self.serviceNext, + category=UiStrings().LiveToolbar, + context=QtCore.Qt.WidgetWithChildrenShortcut) + self.nextService.setText( + translate('OpenLP.SlideController', 'Next Service')) + self.escapeItem = shortcut_action(parent, 'escapeItem', + [QtCore.Qt.Key_Escape], self.liveEscape, + category=UiStrings().LiveToolbar, + context=QtCore.Qt.WidgetWithChildrenShortcut) + self.escapeItem.setText( + translate('OpenLP.SlideController', 'Escape Item')) def liveEscape(self): self.display.setVisible(False) self.display.videoStop() def servicePrevious(self): + time.sleep(0.1) Receiver.send_message('servicemanager_previous_item') def serviceNext(self): + time.sleep(0.1) Receiver.send_message('servicemanager_next_item') def screenSizeChanged(self): @@ -405,9 +591,9 @@ class SlideController(QtGui.QWidget): screen previews. """ # rebuild display as screen size changed - self.display = MainDisplay(self, self.screens, self.isLive) - self.display.imageManager = self.parent.renderManager.image_manager - self.display.alertTab = self.alertTab + if self.display: + self.display.close() + self.display = MainDisplay(self, self.imageManager, self.isLive) self.display.setup() if self.isLive: self.__addActionsToWidget(self.display) @@ -450,7 +636,7 @@ class SlideController(QtGui.QWidget): self.previewListWidget.resizeRowsToContents() else: # Sort out image heights. - width = self.parent.controlSplitter.sizes()[self.split] + width = self.parent().controlSplitter.sizes()[self.split] for framenumber in range(len(self.serviceItem.get_frames())): self.previewListWidget.setRowHeight( framenumber, width / self.ratio) @@ -459,7 +645,7 @@ class SlideController(QtGui.QWidget): request = unicode(self.sender().text()) slideno = self.slideList[request] self.__updatePreviewSelection(slideno) - self.onSlideSelected() + self.slideSelected() def receiveSpinDelay(self, value): """ @@ -481,36 +667,52 @@ class SlideController(QtGui.QWidget): """ Allows the live toolbar to be customised """ - self.toolbar.setVisible(True) + # Work-around for OS X, hide and then show the toolbar + # See bug #791050 + self.toolbar.hide() self.mediabar.setVisible(False) self.toolbar.makeWidgetsInvisible([u'Song Menu']) self.toolbar.makeWidgetsInvisible(self.loopList) - self.toolbar.actions[u'Stop Loop'].setVisible(False) + # Reset the button + self.playSlidesOnce.setChecked(False) + self.playSlidesOnce.setIcon(build_icon(u':/media/media_time.png')) + self.playSlidesLoop.setChecked(False) + self.playSlidesLoop.setIcon(build_icon(u':/media/media_time.png')) if item.is_text(): if QtCore.QSettings().value( - self.parent.songsSettingsSection + u'/display songbar', + self.parent().songsSettingsSection + u'/display songbar', QtCore.QVariant(True)).toBool() and len(self.slideList) > 0: self.toolbar.makeWidgetsVisible([u'Song Menu']) - if item.is_capable(ItemCapabilities.AllowsLoop) and \ + if item.is_capable(ItemCapabilities.CanLoop) and \ len(item.get_frames()) > 1: self.toolbar.makeWidgetsVisible(self.loopList) if item.is_media(): self.toolbar.setVisible(False) self.mediabar.setVisible(True) + else: + # Work-around for OS X, hide and then show the toolbar + # See bug #791050 + self.toolbar.show() def enablePreviewToolBar(self, item): """ Allows the Preview toolbar to be customised """ - self.toolbar.setVisible(True) + # Work-around for OS X, hide and then show the toolbar + # See bug #791050 + self.toolbar.hide() self.mediabar.setVisible(False) self.toolbar.makeWidgetsInvisible(self.songEditList) - if item.is_capable(ItemCapabilities.AllowsEdit) and item.from_plugin: + if item.is_capable(ItemCapabilities.CanEdit) and item.from_plugin: self.toolbar.makeWidgetsVisible(self.songEditList) elif item.is_media(): self.toolbar.setVisible(False) self.mediabar.setVisible(True) self.volumeSlider.setAudioOutput(self.audio) + if not item.is_media(): + # Work-around for OS X, hide and then show the toolbar + # See bug #791050 + self.toolbar.show() def refreshServiceItem(self): """ @@ -539,7 +741,7 @@ class SlideController(QtGui.QWidget): """ Replacement item following a remote edit """ - if item.__eq__(self.serviceItem): + if item == self.serviceItem: self._processItem(item, self.previewListWidget.currentRow()) def addServiceManagerItem(self, item, slideno): @@ -549,15 +751,17 @@ class SlideController(QtGui.QWidget): Called by ServiceManager """ log.debug(u'addServiceManagerItem live = %s' % self.isLive) - # If no valid slide number is specified we take the first one. + # If no valid slide number is specified we take the first one, but we + # remember the initial value to see if we should reload the song or not + slidenum = slideno if slideno == -1: - slideno = 0 - # If service item is the same as the current on only change slide - if item.__eq__(self.serviceItem): - self.__checkUpdateSelectedSlide(slideno) - self.onSlideSelected() - return - self._processItem(item, slideno) + slidenum = 0 + # If service item is the same as the current one, only change slide + if slideno >= 0 and item == self.serviceItem: + self.__checkUpdateSelectedSlide(slidenum) + self.slideSelected() + else: + self._processItem(item, slidenum) def _processItem(self, serviceItem, slideno): """ @@ -566,29 +770,37 @@ class SlideController(QtGui.QWidget): """ log.debug(u'processManagerItem live = %s' % self.isLive) self.onStopLoop() - # If old item was a command tell it to stop - if self.serviceItem: - if self.serviceItem.is_command(): - Receiver.send_message(u'%s_stop' % - self.serviceItem.name.lower(), [serviceItem, self.isLive]) - if self.serviceItem.is_media(): - self.onMediaClose() - if self.isLive: - if serviceItem.is_capable(ItemCapabilities.ProvidesOwnDisplay): - self._forceUnblank() - blanked = self.blankScreen.isChecked() - else: - blanked = False + old_item = self.serviceItem + # take a copy not a link to the servicemeanager copy. + self.serviceItem = copy.copy(serviceItem) + if old_item and self.isLive and old_item.is_capable( + ItemCapabilities.ProvidesOwnDisplay): + self._resetBlank() Receiver.send_message(u'%s_start' % serviceItem.name.lower(), - [serviceItem, self.isLive, blanked, slideno]) + [serviceItem, self.isLive, self.hideMode(), slideno]) self.slideList = {} - width = self.parent.controlSplitter.sizes()[self.split] - self.serviceItem = serviceItem + width = self.parent().controlSplitter.sizes()[self.split] self.previewListWidget.clear() self.previewListWidget.setRowCount(0) self.previewListWidget.setColumnWidth(0, width) if self.isLive: self.songMenu.menu().clear() + self.display.audioPlayer.reset() + self.setAudioItemsVisibility(False) + self.audioPauseItem.setChecked(False) + if self.serviceItem.is_capable(ItemCapabilities.HasBackgroundAudio): + log.debug(u'Starting to play...') + self.display.audioPlayer.addToPlaylist( + self.serviceItem.background_audio) + if QtCore.QSettings().value( + self.parent().generalSettingsSection + \ + u'/audio start paused', + QtCore.QVariant(True)).toBool(): + self.audioPauseItem.setChecked(True) + self.display.audioPlayer.pause() + else: + self.display.audioPlayer.play() + self.setAudioItemsVisibility(True) row = 0 text = [] for framenumber, frame in enumerate(self.serviceItem.get_frames()): @@ -600,37 +812,35 @@ class SlideController(QtGui.QWidget): if frame[u'verseTag']: # These tags are already translated. verse_def = frame[u'verseTag'] - verse_def = u'%s%s' % (verse_def[0].upper(), verse_def[1:]) + verse_def = u'%s%s' % (verse_def[0], verse_def[1:]) two_line_def = u'%s\n%s' % (verse_def[0], verse_def[1:]) row = two_line_def - if self.isLive: - if verse_def not in self.slideList: - self.slideList[verse_def] = framenumber + if verse_def not in self.slideList: + self.slideList[verse_def] = framenumber + if self.isLive: self.songMenu.menu().addAction(verse_def, self.onSongBarHandler) else: row += 1 + self.slideList[unicode(row)] = row - 1 item.setText(frame[u'text']) else: label = QtGui.QLabel() label.setMargin(4) label.setScaledContents(True) if self.serviceItem.is_command(): - image = resize_image(frame[u'image'], - self.parent.renderManager.width, - self.parent.renderManager.height) + label.setPixmap(QtGui.QPixmap(frame[u'image'])) else: # If current slide set background to image if framenumber == slideno: self.serviceItem.bg_image_bytes = \ - self.parent.renderManager.image_manager. \ - get_image_bytes(frame[u'title']) - image = self.parent.renderManager.image_manager. \ - get_image(frame[u'title']) - label.setPixmap(QtGui.QPixmap.fromImage(image)) + self.imageManager.get_image_bytes(frame[u'title']) + image = self.imageManager.get_image(frame[u'title']) + label.setPixmap(QtGui.QPixmap.fromImage(image)) self.previewListWidget.setCellWidget(framenumber, 0, label) - slideHeight = width * self.parent.renderManager.screen_ratio + slideHeight = width * self.parent().renderer.screen_ratio row += 1 + self.slideList[unicode(row)] = row - 1 text.append(unicode(row)) self.previewListWidget.setItem(framenumber, 0, item) if slideHeight != 0: @@ -642,12 +852,25 @@ class SlideController(QtGui.QWidget): self.previewListWidget.viewport().size().width()) self.__updatePreviewSelection(slideno) self.enableToolBar(serviceItem) - # Pass to display for viewing - self.display.buildHtml(self.serviceItem) + # Pass to display for viewing. + # Postpone image build, we need to do this later to avoid the theme + # flashing on the screen + if not self.serviceItem.is_image(): + self.display.buildHtml(self.serviceItem) if serviceItem.is_media(): self.onMediaStart(serviceItem) - self.onSlideSelected() + self.slideSelected(True) self.previewListWidget.setFocus() + if old_item: + # Close the old item after the new one is opened + # This avoids the service theme/desktop flashing on screen + # However opening a new item of the same type will automatically + # close the previous, so make sure we don't close the new one. + if old_item.is_command() and not serviceItem.is_command(): + Receiver.send_message(u'%s_stop' % + old_item.name.lower(), [old_item, self.isLive]) + if old_item.is_media() and not serviceItem.is_media(): + self.onMediaClose() Receiver.send_message(u'slidecontroller_%s_started' % self.typePrefix, [serviceItem]) @@ -661,41 +884,7 @@ class SlideController(QtGui.QWidget): else: self.__checkUpdateSelectedSlide(slideno) - def onTextRequest(self): - """ - Return the text for the current item in controller - """ - data = [] - if self.serviceItem: - for framenumber, frame in enumerate(self.serviceItem.get_frames()): - dataItem = {} - if self.serviceItem.is_text(): - dataItem[u'tag'] = unicode(frame[u'verseTag']) - dataItem[u'text'] = unicode(frame[u'html']) - else: - dataItem[u'tag'] = unicode(framenumber) - dataItem[u'text'] = u'' - dataItem[u'selected'] = \ - (self.previewListWidget.currentRow() == framenumber) - data.append(dataItem) - Receiver.send_message(u'slidecontroller_%s_text_response' - % self.typePrefix, data) - # Screen event methods - def onSlideSelectedFirst(self): - """ - Go to the first slide. - """ - if not self.serviceItem: - return - if self.serviceItem.is_command(): - Receiver.send_message(u'%s_first' % self.serviceItem.name.lower(), - [self.serviceItem, self.isLive]) - self.updatePreview() - else: - self.previewListWidget.selectRow(0) - self.onSlideSelected() - def onSlideSelectedIndex(self, message): """ Go to the requested slide @@ -709,7 +898,7 @@ class SlideController(QtGui.QWidget): self.updatePreview() else: self.__checkUpdateSelectedSlide(index) - self.onSlideSelected() + self.slideSelected() def mainDisplaySetBackground(self): """ @@ -717,7 +906,7 @@ class SlideController(QtGui.QWidget): """ log.debug(u'mainDisplaySetBackground live = %s' % self.isLive) display_type = QtCore.QSettings().value( - self.parent.generalSettingsSection + u'/screen blank', + self.parent().generalSettingsSection + u'/screen blank', QtCore.QVariant(u'')).toString() if not self.display.primary: # Order done to handle initial conversion @@ -725,8 +914,12 @@ class SlideController(QtGui.QWidget): self.onThemeDisplay(True) elif display_type == u'hidden': self.onHideDisplay(True) - else: + elif display_type == u'blanked': self.onBlankDisplay(True) + else: + Receiver.send_message(u'live_display_show') + else: + Receiver.send_message(u'live_display_hide', HideMode.Screen) def onSlideBlank(self): """ @@ -740,83 +933,93 @@ class SlideController(QtGui.QWidget): """ self.onBlankDisplay(False) - def onBlankDisplay(self, checked): + def onBlankDisplay(self, checked=None): """ Handle the blank screen button actions """ + if checked is None: + checked = self.blankScreen.isChecked() log.debug(u'onBlankDisplay %s' % checked) self.hideMenu.setDefaultAction(self.blankScreen) self.blankScreen.setChecked(checked) self.themeScreen.setChecked(False) self.desktopScreen.setChecked(False) if checked: - Receiver.send_message(u'maindisplay_hide', HideMode.Blank) QtCore.QSettings().setValue( - self.parent.generalSettingsSection + u'/screen blank', + self.parent().generalSettingsSection + u'/screen blank', QtCore.QVariant(u'blanked')) else: - Receiver.send_message(u'maindisplay_show') QtCore.QSettings().remove( - self.parent.generalSettingsSection + u'/screen blank') - self.blankPlugin(checked) + self.parent().generalSettingsSection + u'/screen blank') + self.blankPlugin() self.updatePreview() - def onThemeDisplay(self, checked): + def onThemeDisplay(self, checked=None): """ Handle the Theme screen button """ + if checked is None: + checked = self.themeScreen.isChecked() log.debug(u'onThemeDisplay %s' % checked) self.hideMenu.setDefaultAction(self.themeScreen) self.blankScreen.setChecked(False) self.themeScreen.setChecked(checked) self.desktopScreen.setChecked(False) if checked: - Receiver.send_message(u'maindisplay_hide', HideMode.Theme) QtCore.QSettings().setValue( - self.parent.generalSettingsSection + u'/screen blank', + self.parent().generalSettingsSection + u'/screen blank', QtCore.QVariant(u'themed')) else: - Receiver.send_message(u'maindisplay_show') QtCore.QSettings().remove( - self.parent.generalSettingsSection + u'/screen blank') - self.blankPlugin(checked) + self.parent().generalSettingsSection + u'/screen blank') + self.blankPlugin() self.updatePreview() - def onHideDisplay(self, checked): + def onHideDisplay(self, checked=None): """ Handle the Hide screen button """ + if checked is None: + checked = self.desktopScreen.isChecked() log.debug(u'onHideDisplay %s' % checked) self.hideMenu.setDefaultAction(self.desktopScreen) self.blankScreen.setChecked(False) self.themeScreen.setChecked(False) self.desktopScreen.setChecked(checked) if checked: - Receiver.send_message(u'maindisplay_hide', HideMode.Screen) QtCore.QSettings().setValue( - self.parent.generalSettingsSection + u'/screen blank', + self.parent().generalSettingsSection + u'/screen blank', QtCore.QVariant(u'hidden')) else: - Receiver.send_message(u'maindisplay_show') QtCore.QSettings().remove( - self.parent.generalSettingsSection + u'/screen blank') + self.parent().generalSettingsSection + u'/screen blank') self.hidePlugin(checked) self.updatePreview() - def blankPlugin(self, blank): + def blankPlugin(self): """ - Blank the display screen within a plugin if required. + Blank/Hide the display screen within a plugin if required. """ - log.debug(u'blankPlugin %s ', blank) + hide_mode = self.hideMode() + log.debug(u'blankPlugin %s ', hide_mode) if self.serviceItem is not None: - if blank: + if hide_mode: + if not self.serviceItem.is_command(): + Receiver.send_message(u'live_display_hide', hide_mode) Receiver.send_message(u'%s_blank' % self.serviceItem.name.lower(), - [self.serviceItem, self.isLive]) + [self.serviceItem, self.isLive, hide_mode]) else: + if not self.serviceItem.is_command(): + Receiver.send_message(u'live_display_show') Receiver.send_message(u'%s_unblank' % self.serviceItem.name.lower(), [self.serviceItem, self.isLive]) + else: + if hide_mode: + Receiver.send_message(u'live_display_hide', hide_mode) + else: + Receiver.send_message(u'live_display_show') def hidePlugin(self, hide): """ @@ -825,15 +1028,29 @@ class SlideController(QtGui.QWidget): log.debug(u'hidePlugin %s ', hide) if self.serviceItem is not None: if hide: + Receiver.send_message(u'live_display_hide', HideMode.Screen) Receiver.send_message(u'%s_hide' % self.serviceItem.name.lower(), [self.serviceItem, self.isLive]) else: + if not self.serviceItem.is_command(): + Receiver.send_message(u'live_display_show') Receiver.send_message(u'%s_unblank' % self.serviceItem.name.lower(), [self.serviceItem, self.isLive]) + else: + if hide: + Receiver.send_message(u'live_display_hide', HideMode.Screen) + else: + Receiver.send_message(u'live_display_show') - def onSlideSelected(self): + def onSlideSelected(self, start=False): + """ + Slide selected in controller + """ + self.slideSelected() + + def slideSelected(self, start=False): """ Generate the preview when you click on a slide. if this is the Live Controller also display on the screen @@ -842,21 +1059,24 @@ class SlideController(QtGui.QWidget): self.selectedRow = 0 if row > -1 and row < self.previewListWidget.rowCount(): if self.serviceItem.is_command(): - if self.isLive: + if self.isLive and not start: Receiver.send_message( u'%s_slide' % self.serviceItem.name.lower(), [self.serviceItem, self.isLive, row]) - self.updatePreview() else: toDisplay = self.serviceItem.get_rendered_frame(row) if self.serviceItem.is_text(): - frame = self.display.text(toDisplay) + self.display.text(toDisplay) else: - frame = self.display.image(toDisplay) + if start: + self.display.buildHtml(self.serviceItem, toDisplay) + else: + self.display.image(toDisplay) # reset the store used to display first image self.serviceItem.bg_image_bytes = None - self.slidePreview.setPixmap(QtGui.QPixmap.fromImage(frame)) + self.updatePreview() self.selectedRow = row + self.__checkUpdateSelectedSlide(row) Receiver.send_message(u'slidecontroller_%s_changed' % self.typePrefix, row) @@ -882,8 +1102,7 @@ class SlideController(QtGui.QWidget): QtCore.QTimer.singleShot(0.5, self.grabMainDisplay) QtCore.QTimer.singleShot(2.5, self.grabMainDisplay) else: - self.slidePreview.setPixmap( - QtGui.QPixmap.fromImage(self.display.preview())) + self.slidePreview.setPixmap(self.display.preview()) def grabMainDisplay(self): """ @@ -895,10 +1114,7 @@ class SlideController(QtGui.QWidget): rect.y(), rect.width(), rect.height()) self.slidePreview.setPixmap(winimg) - def onSlideSelectedNextNoloop(self): - self.onSlideSelectedNext(False) - - def onSlideSelectedNext(self, loop=True): + def onSlideSelectedNext(self, wrap=None): """ Go to the next slide. """ @@ -911,18 +1127,18 @@ class SlideController(QtGui.QWidget): else: row = self.previewListWidget.currentRow() + 1 if row == self.previewListWidget.rowCount(): - if loop: + if wrap is None: + wrap = QtCore.QSettings().value( + self.parent().generalSettingsSection + + u'/enable slide loop', QtCore.QVariant(True)).toBool() + if wrap: row = 0 else: - Receiver.send_message('servicemanager_next_item') - return + row = self.previewListWidget.rowCount() - 1 self.__checkUpdateSelectedSlide(row) - self.onSlideSelected() + self.slideSelected() - def onSlideSelectedPreviousNoloop(self): - self.onSlideSelectedPrevious(False) - - def onSlideSelectedPrevious(self, loop=True): + def onSlideSelectedPrevious(self): """ Go to the previous slide. """ @@ -935,12 +1151,13 @@ class SlideController(QtGui.QWidget): else: row = self.previewListWidget.currentRow() - 1 if row == -1: - if loop: + if QtCore.QSettings().value(self.parent().generalSettingsSection + + u'/enable slide loop', QtCore.QVariant(True)).toBool(): row = self.previewListWidget.rowCount() - 1 else: row = 0 self.__checkUpdateSelectedSlide(row) - self.onSlideSelected() + self.slideSelected() def __checkUpdateSelectedSlide(self, row): if row + 1 < self.previewListWidget.rowCount(): @@ -948,20 +1165,14 @@ class SlideController(QtGui.QWidget): self.previewListWidget.item(row + 1, 0)) self.previewListWidget.selectRow(row) - def onSlideSelectedLast(self): + def onToggleLoop(self): """ - Go to the last slide. + Toggles the loop state. """ - if not self.serviceItem: - return - Receiver.send_message(u'%s_last' % self.serviceItem.name.lower(), - [self.serviceItem, self.isLive]) - if self.serviceItem.is_command(): - self.updatePreview() + if self.playSlidesLoop.isChecked() or self.playSlidesOnce.isChecked(): + self.onStartLoop() else: - self.previewListWidget.selectRow( - self.previewListWidget.rowCount() - 1) - self.onSlideSelected() + self.onStopLoop() def onStartLoop(self): """ @@ -970,8 +1181,6 @@ class SlideController(QtGui.QWidget): if self.previewListWidget.rowCount() > 1: self.timer_id = self.startTimer( int(self.delaySpinBox.value()) * 1000) - self.toolbar.actions[u'Stop Loop'].setVisible(True) - self.toolbar.actions[u'Start Loop'].setVisible(False) def onStopLoop(self): """ @@ -980,15 +1189,66 @@ class SlideController(QtGui.QWidget): if self.timer_id != 0: self.killTimer(self.timer_id) self.timer_id = 0 - self.toolbar.actions[u'Start Loop'].setVisible(True) - self.toolbar.actions[u'Stop Loop'].setVisible(False) + + def onPlaySlidesLoop(self, checked=None): + """ + Start or stop 'Play Slides in Loop' + """ + if checked is None: + checked = self.playSlidesLoop.isChecked() + else: + self.playSlidesLoop.setChecked(checked) + log.debug(u'onPlaySlidesLoop %s' % checked) + if checked: + self.playSlidesLoop.setIcon(build_icon(u':/media/media_stop.png')) + self.playSlidesLoop.setText(UiStrings().StopPlaySlidesInLoop) + self.playSlidesOnce.setIcon(build_icon(u':/media/media_time.png')) + self.playSlidesOnce.setText(UiStrings().PlaySlidesToEnd) + else: + self.playSlidesLoop.setIcon(build_icon(u':/media/media_time.png')) + self.playSlidesLoop.setText(UiStrings().PlaySlidesInLoop) + self.playSlidesMenu.setDefaultAction(self.playSlidesLoop) + self.playSlidesOnce.setChecked(False) + self.onToggleLoop() + + def onPlaySlidesOnce(self, checked=None): + """ + Start or stop 'Play Slides to End' + """ + if checked is None: + checked = self.playSlidesOnce.isChecked() + else: + self.playSlidesOnce.setChecked(checked) + log.debug(u'onPlaySlidesOnce %s' % checked) + if checked: + self.playSlidesOnce.setIcon(build_icon(u':/media/media_stop.png')) + self.playSlidesOnce.setText(UiStrings().StopPlaySlidesToEnd) + self.playSlidesLoop.setIcon(build_icon(u':/media/media_time.png')) + self.playSlidesLoop.setText(UiStrings().PlaySlidesInLoop) + else: + self.playSlidesOnce.setIcon(build_icon(u':/media/media_time')) + self.playSlidesOnce.setText(UiStrings().PlaySlidesToEnd) + self.playSlidesMenu.setDefaultAction(self.playSlidesOnce) + self.playSlidesLoop.setChecked(False) + self.onToggleLoop() + + def setAudioItemsVisibility(self, visible): + self.audioPauseItem.setVisible(visible) + + def onAudioPauseClicked(self, checked): + if not self.audioPauseItem.isVisible(): + return + if checked: + self.display.audioPlayer.pause() + else: + self.display.audioPlayer.play() def timerEvent(self, event): """ If the timer event is for this window select next slide """ if event.timerId() == self.timer_id: - self.onSlideSelectedNext() + self.onSlideSelectedNext(self.playSlidesLoop.isChecked()) def onEditSong(self): """ @@ -998,12 +1258,28 @@ class SlideController(QtGui.QWidget): Receiver.send_message(u'%s_edit' % self.serviceItem.name.lower(), u'P:%s' % self.serviceItem.edit_id) + def onPreviewAddToService(self): + """ + From the preview display request the Item to be added to service + """ + if self.serviceItem: + self.parent().serviceManagerContents.addServiceItem( + self.serviceItem) + def onGoLiveClick(self): """ triggered by clicking the Preview slide items """ if QtCore.QSettings().value(u'advanced/double click live', QtCore.QVariant(False)).toBool(): + # Live and Preview have issues if we have video or presentations + # playing in both at the same time. + if self.serviceItem.is_command(): + Receiver.send_message(u'%s_stop' % + self.serviceItem.name.lower(), + [self.serviceItem, self.isLive]) + if self.serviceItem.is_media(): + self.onMediaClose() self.onGoLive() def onGoLive(self): @@ -1012,8 +1288,12 @@ class SlideController(QtGui.QWidget): """ row = self.previewListWidget.currentRow() if row > -1 and row < self.previewListWidget.rowCount(): - self.parent.liveController.addServiceManagerItem( - self.serviceItem, row) + if self.serviceItem.from_service: + Receiver.send_message('servicemanager_preview_live', + u'%s:%s' % (self.serviceItem._uuid, row)) + else: + self.parent().liveController.addServiceManagerItem( + self.serviceItem, row) def onMediaStart(self, item): """ @@ -1089,20 +1369,32 @@ class SlideController(QtGui.QWidget): self.slidePreview.clear() self.slidePreview.show() - def _forceUnblank(self): + def _resetBlank(self): """ Used by command items which provide their own displays to reset the screen hide attributes """ - blank = None - if self.blankScreen.isChecked: - blank = self.blankScreen - if self.themeScreen.isChecked: - blank = self.themeScreen - if self.desktopScreen.isChecked: - blank = self.desktopScreen - if blank: - blank.setChecked(False) - self.hideMenu.setDefaultAction(blank) - QtCore.QSettings().remove( - self.parent.generalSettingsSection + u'/screen blank') + hide_mode = self.hideMode() + if hide_mode == HideMode.Blank: + self.onBlankDisplay(True) + elif hide_mode == HideMode.Theme: + self.onThemeDisplay(True) + elif hide_mode == HideMode.Screen: + self.onHideDisplay(True) + else: + self.hidePlugin(False) + + def hideMode(self): + """ + Determine what the hide mode should be according to the blank button + """ + if not self.isLive: + return None + elif self.blankScreen.isChecked(): + return HideMode.Blank + elif self.themeScreen.isChecked(): + return HideMode.Theme + elif self.desktopScreen.isChecked(): + return HideMode.Screen + else: + return None diff --git a/openlp/core/ui/splashscreen.py b/openlp/core/ui/splashscreen.py index 88c6f09f9..036daf968 100644 --- a/openlp/core/ui/splashscreen.py +++ b/openlp/core/ui/splashscreen.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, Armin Köhler, 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,6 +24,7 @@ # 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.lib import Receiver from PyQt4 import QtCore, QtGui @@ -30,10 +32,11 @@ class SplashScreen(QtGui.QSplashScreen): def __init__(self): QtGui.QSplashScreen.__init__(self) self.setupUi() + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'close_splash'), self.close) def setupUi(self): - self.setObjectName(u'splash_screen') - self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint) + self.setObjectName(u'splashScreen') self.setContextMenuPolicy(QtCore.Qt.PreventContextMenu) splash_image = QtGui.QPixmap(u':/graphics/openlp-splash-screen.png') self.setPixmap(splash_image) diff --git a/openlp/core/ui/starttimedialog.py b/openlp/core/ui/starttimedialog.py index 14ef84aac..61e4eb662 100644 --- a/openlp/core/ui/starttimedialog.py +++ b/openlp/core/ui/starttimedialog.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, Armin Köhler, 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 # @@ -32,39 +33,90 @@ from openlp.core.lib.ui import UiStrings, create_accept_reject_button_box class Ui_StartTimeDialog(object): def setupUi(self, StartTimeDialog): StartTimeDialog.setObjectName(u'StartTimeDialog') - StartTimeDialog.resize(300, 10) + StartTimeDialog.resize(350, 10) self.dialogLayout = QtGui.QGridLayout(StartTimeDialog) self.dialogLayout.setObjectName(u'dialogLayout') + self.startLabel = QtGui.QLabel(StartTimeDialog) + self.startLabel.setObjectName(u'startLabel') + self.startLabel.setAlignment(QtCore.Qt.AlignHCenter) + self.dialogLayout.addWidget(self.startLabel, 0, 1, 1, 1) + self.finishLabel = QtGui.QLabel(StartTimeDialog) + self.finishLabel.setObjectName(u'finishLabel') + self.finishLabel.setAlignment(QtCore.Qt.AlignHCenter) + self.dialogLayout.addWidget(self.finishLabel, 0, 2, 1, 1) + self.lengthLabel = QtGui.QLabel(StartTimeDialog) + self.lengthLabel.setObjectName(u'startLabel') + self.lengthLabel.setAlignment(QtCore.Qt.AlignHCenter) + self.dialogLayout.addWidget(self.lengthLabel, 0, 3, 1, 1) self.hourLabel = QtGui.QLabel(StartTimeDialog) - self.hourLabel.setObjectName("hourLabel") - self.dialogLayout.addWidget(self.hourLabel, 0, 0, 1, 1) + self.hourLabel.setObjectName(u'hourLabel') + self.dialogLayout.addWidget(self.hourLabel, 1, 0, 1, 1) self.hourSpinBox = QtGui.QSpinBox(StartTimeDialog) - self.hourSpinBox.setObjectName("hourSpinBox") - self.dialogLayout.addWidget(self.hourSpinBox, 0, 1, 1, 1) + self.hourSpinBox.setObjectName(u'hourSpinBox') + self.hourSpinBox.setMinimum(0) + self.hourSpinBox.setMaximum(4) + self.dialogLayout.addWidget(self.hourSpinBox, 1, 1, 1, 1) + self.hourFinishSpinBox = QtGui.QSpinBox(StartTimeDialog) + self.hourFinishSpinBox.setObjectName(u'hourFinishSpinBox') + self.hourFinishSpinBox.setMinimum(0) + self.hourFinishSpinBox.setMaximum(4) + self.dialogLayout.addWidget(self.hourFinishSpinBox, 1, 2, 1, 1) + self.hourFinishLabel = QtGui.QLabel(StartTimeDialog) + self.hourFinishLabel.setObjectName(u'hourLabel') + self.hourFinishLabel.setAlignment(QtCore.Qt.AlignRight) + self.dialogLayout.addWidget(self.hourFinishLabel, 1, 3, 1, 1) self.minuteLabel = QtGui.QLabel(StartTimeDialog) - self.minuteLabel.setObjectName("minuteLabel") - self.dialogLayout.addWidget(self.minuteLabel, 1, 0, 1, 1) + self.minuteLabel.setObjectName(u'minuteLabel') + self.dialogLayout.addWidget(self.minuteLabel, 2, 0, 1, 1) self.minuteSpinBox = QtGui.QSpinBox(StartTimeDialog) - self.minuteSpinBox.setObjectName("minuteSpinBox") - self.dialogLayout.addWidget(self.minuteSpinBox, 1, 1, 1, 1) + self.minuteSpinBox.setObjectName(u'minuteSpinBox') + self.minuteSpinBox.setMinimum(0) + self.minuteSpinBox.setMaximum(59) + self.dialogLayout.addWidget(self.minuteSpinBox, 2, 1, 1, 1) + self.minuteFinishSpinBox = QtGui.QSpinBox(StartTimeDialog) + self.minuteFinishSpinBox.setObjectName(u'minuteFinishSpinBox') + self.minuteFinishSpinBox.setMinimum(0) + self.minuteFinishSpinBox.setMaximum(59) + self.dialogLayout.addWidget(self.minuteFinishSpinBox, 2, 2, 1, 1) + self.minuteFinishLabel = QtGui.QLabel(StartTimeDialog) + self.minuteFinishLabel.setObjectName(u'minuteLabel') + self.minuteFinishLabel.setAlignment(QtCore.Qt.AlignRight) + self.dialogLayout.addWidget(self.minuteFinishLabel, 2, 3, 1, 1) self.secondLabel = QtGui.QLabel(StartTimeDialog) - self.secondLabel.setObjectName("secondLabel") - self.dialogLayout.addWidget(self.secondLabel, 2, 0, 1, 1) + self.secondLabel.setObjectName(u'secondLabel') + self.dialogLayout.addWidget(self.secondLabel, 3, 0, 1, 1) self.secondSpinBox = QtGui.QSpinBox(StartTimeDialog) - self.secondSpinBox.setObjectName("secondSpinBox") - self.dialogLayout.addWidget(self.secondSpinBox, 2, 1, 1, 1) + self.secondSpinBox.setObjectName(u'secondSpinBox') + self.secondSpinBox.setMinimum(0) + self.secondSpinBox.setMaximum(59) + self.secondFinishSpinBox = QtGui.QSpinBox(StartTimeDialog) + self.secondFinishSpinBox.setObjectName(u'secondFinishSpinBox') + self.secondFinishSpinBox.setMinimum(0) + self.secondFinishSpinBox.setMaximum(59) + self.dialogLayout.addWidget(self.secondFinishSpinBox, 3, 2, 1, 1) + self.secondFinishLabel = QtGui.QLabel(StartTimeDialog) + self.secondFinishLabel.setObjectName(u'secondLabel') + self.secondFinishLabel.setAlignment(QtCore.Qt.AlignRight) + self.dialogLayout.addWidget(self.secondFinishLabel, 3, 3, 1, 1) + self.dialogLayout.addWidget(self.secondSpinBox, 3, 1, 1, 1) self.buttonBox = create_accept_reject_button_box(StartTimeDialog, True) - self.dialogLayout.addWidget(self.buttonBox, 4, 0, 1, 2) + self.dialogLayout.addWidget(self.buttonBox, 5, 2, 1, 2) self.retranslateUi(StartTimeDialog) self.setMaximumHeight(self.sizeHint().height()) QtCore.QMetaObject.connectSlotsByName(StartTimeDialog) def retranslateUi(self, StartTimeDialog): self.setWindowTitle(translate('OpenLP.StartTimeForm', - 'Item Start Time')) + 'Item Start and Finish Time')) + self.hourSpinBox.setSuffix(UiStrings().Hours) + self.minuteSpinBox.setSuffix(UiStrings().Minutes) + self.secondSpinBox.setSuffix(UiStrings().Seconds) + self.hourFinishSpinBox.setSuffix(UiStrings().Hours) + self.minuteFinishSpinBox.setSuffix(UiStrings().Minutes) + self.secondFinishSpinBox.setSuffix(UiStrings().Seconds) self.hourLabel.setText(translate('OpenLP.StartTimeForm', 'Hours:')) - self.hourSpinBox.setSuffix(translate('OpenLP.StartTimeForm', 'h')) - self.minuteSpinBox.setSuffix(translate('OpenLP.StartTimeForm', 'm')) - self.secondSpinBox.setSuffix(UiStrings.S) self.minuteLabel.setText(translate('OpenLP.StartTimeForm', 'Minutes:')) self.secondLabel.setText(translate('OpenLP.StartTimeForm', 'Seconds:')) + self.startLabel.setText(translate('OpenLP.StartTimeForm', 'Start')) + self.finishLabel.setText(translate('OpenLP.StartTimeForm', 'Finish')) + self.lengthLabel.setText(translate('OpenLP.StartTimeForm', 'Length')) diff --git a/openlp/core/ui/starttimeform.py b/openlp/core/ui/starttimeform.py index 01800602f..45c0b70b7 100644 --- a/openlp/core/ui/starttimeform.py +++ b/openlp/core/ui/starttimeform.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, Armin Köhler, 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,6 +29,9 @@ from PyQt4 import QtGui from starttimedialog import Ui_StartTimeDialog +from openlp.core.lib import translate +from openlp.core.lib.ui import UiStrings, critical_error_message_box + class StartTimeForm(QtGui.QDialog, Ui_StartTimeDialog): """ The exception dialog @@ -40,13 +44,52 @@ class StartTimeForm(QtGui.QDialog, Ui_StartTimeDialog): """ Run the Dialog with correct heading. """ - seconds = self.item[u'service_item'].start_time + hour, minutes, seconds = self._time_split( + self.item[u'service_item'].start_time) + self.hourSpinBox.setValue(hour) + self.minuteSpinBox.setValue(minutes) + self.secondSpinBox.setValue(seconds) + hours, minutes, seconds = self._time_split( + self.item[u'service_item'].media_length) + self.hourFinishSpinBox.setValue(hours) + self.minuteFinishSpinBox.setValue(minutes) + self.secondFinishSpinBox.setValue(seconds) + self.hourFinishLabel.setText(u'%s%s' % (unicode(hour), + UiStrings().Hours)) + self.minuteFinishLabel.setText(u'%s%s' % + (unicode(minutes), UiStrings().Minutes)) + self.secondFinishLabel.setText(u'%s%s' % + (unicode(seconds), UiStrings().Seconds)) + return QtGui.QDialog.exec_(self) + + def accept(self): + start = self.hourSpinBox.value() * 3600 + \ + self.minuteSpinBox.value() * 60 + \ + self.secondSpinBox.value() + end = self.hourFinishSpinBox.value() * 3600 + \ + self.minuteFinishSpinBox.value() * 60 + \ + self.secondFinishSpinBox.value() + if end > self.item[u'service_item'].media_length: + critical_error_message_box( + title=translate('OpenLP.StartTimeForm', + 'Time Validation Error'), + message=translate('OpenLP.StartTimeForm', + 'Finish time is set after the end of the media item')) + return + elif start > end: + critical_error_message_box( + title=translate('OpenLP.StartTimeForm', + 'Time Validation Error'), + message=translate('OpenLP.StartTimeForm', + 'Start time is after the finish time of the media item')) + return + self.item[u'service_item'].start_time = start + self.item[u'service_item'].end_time = end + return QtGui.QDialog.accept(self) + + def _time_split(self, seconds): hours = seconds / 3600 seconds -= 3600 * hours minutes = seconds / 60 seconds -= 60 * minutes - self.hourSpinBox.setValue(hours) - self.minuteSpinBox.setValue(minutes) - self.secondSpinBox.setValue(seconds) - return QtGui.QDialog.exec_(self) - + return hours, minutes, seconds diff --git a/openlp/core/ui/themeform.py b/openlp/core/ui/themeform.py index cc490bf9b..9ccd91d08 100644 --- a/openlp/core/ui/themeform.py +++ b/openlp/core/ui/themeform.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, Armin Köhler, 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 # @@ -32,6 +33,7 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import Receiver, translate from openlp.core.lib.theme import BackgroundType, BackgroundGradientType from openlp.core.lib.ui import UiStrings, critical_error_message_box +from openlp.core.ui import ThemeLayoutForm from openlp.core.utils import get_images_filter from themewizard import Ui_ThemeWizard @@ -56,6 +58,8 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): self.setupUi(self) self.registerFields() self.updateThemeAllowed = True + self.temp_background_filename = u'' + self.themeLayoutForm = ThemeLayoutForm(self) QtCore.QObject.connect(self.backgroundComboBox, QtCore.SIGNAL(u'currentIndexChanged(int)'), self.onBackgroundComboBoxCurrentIndexChanged) @@ -63,26 +67,21 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): QtCore.SIGNAL(u'currentIndexChanged(int)'), self.onGradientComboBoxCurrentIndexChanged) QtCore.QObject.connect(self.colorButton, - QtCore.SIGNAL(u'clicked()'), - self.onColorButtonClicked) + QtCore.SIGNAL(u'clicked()'), self.onColorButtonClicked) + QtCore.QObject.connect(self.imageColorButton, + QtCore.SIGNAL(u'clicked()'), self.onImageColorButtonClicked) QtCore.QObject.connect(self.gradientStartButton, - QtCore.SIGNAL(u'clicked()'), - self.onGradientStartButtonClicked) + QtCore.SIGNAL(u'clicked()'), self.onGradientStartButtonClicked) QtCore.QObject.connect(self.gradientEndButton, - QtCore.SIGNAL(u'clicked()'), - self.onGradientEndButtonClicked) + QtCore.SIGNAL(u'clicked()'), self.onGradientEndButtonClicked) QtCore.QObject.connect(self.imageBrowseButton, - QtCore.SIGNAL(u'clicked()'), - self.onImageBrowseButtonClicked) + QtCore.SIGNAL(u'clicked()'), self.onImageBrowseButtonClicked) QtCore.QObject.connect(self.mainColorButton, - QtCore.SIGNAL(u'clicked()'), - self.onMainColorButtonClicked) + QtCore.SIGNAL(u'clicked()'), self.onMainColorButtonClicked) QtCore.QObject.connect(self.outlineColorButton, - QtCore.SIGNAL(u'clicked()'), - self.onOutlineColorButtonClicked) + QtCore.SIGNAL(u'clicked()'), self.onOutlineColorButtonClicked) QtCore.QObject.connect(self.shadowColorButton, - QtCore.SIGNAL(u'clicked()'), - self.onShadowColorButtonClicked) + QtCore.SIGNAL(u'clicked()'), self.onShadowColorButtonClicked) QtCore.QObject.connect(self.outlineCheckBox, QtCore.SIGNAL(u'stateChanged(int)'), self.onOutlineCheckCheckBoxStateChanged) @@ -90,8 +89,10 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): QtCore.SIGNAL(u'stateChanged(int)'), self.onShadowCheckCheckBoxStateChanged) QtCore.QObject.connect(self.footerColorButton, - QtCore.SIGNAL(u'clicked()'), - self.onFooterColorButtonClicked) + QtCore.SIGNAL(u'clicked()'), self.onFooterColorButtonClicked) + QtCore.QObject.connect(self, + QtCore.SIGNAL(u'customButtonClicked(int)'), + self.onCustom1ButtonClicked) QtCore.QObject.connect(self.mainPositionCheckBox, QtCore.SIGNAL(u'stateChanged(int)'), self.onMainPositionCheckBoxStateChanged) @@ -99,26 +100,23 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): QtCore.SIGNAL(u'stateChanged(int)'), self.onFooterPositionCheckBoxStateChanged) QtCore.QObject.connect(self, - QtCore.SIGNAL(u'currentIdChanged(int)'), - self.onCurrentIdChanged) + QtCore.SIGNAL(u'currentIdChanged(int)'), self.onCurrentIdChanged) QtCore.QObject.connect(Receiver.get_receiver(), - QtCore.SIGNAL(u'theme_line_count'), - self.updateLinesText) + QtCore.SIGNAL(u'theme_line_count'), self.updateLinesText) QtCore.QObject.connect(self.mainSizeSpinBox, - QtCore.SIGNAL(u'valueChanged(int)'), - self.calculateLines) + QtCore.SIGNAL(u'valueChanged(int)'), self.calculateLines) QtCore.QObject.connect(self.lineSpacingSpinBox, - QtCore.SIGNAL(u'valueChanged(int)'), - self.calculateLines) + QtCore.SIGNAL(u'valueChanged(int)'), self.calculateLines) QtCore.QObject.connect(self.outlineSizeSpinBox, - QtCore.SIGNAL(u'valueChanged(int)'), - self.calculateLines) + QtCore.SIGNAL(u'valueChanged(int)'), self.calculateLines) QtCore.QObject.connect(self.shadowSizeSpinBox, - QtCore.SIGNAL(u'valueChanged(int)'), - self.calculateLines) + QtCore.SIGNAL(u'valueChanged(int)'), self.calculateLines) QtCore.QObject.connect(self.mainFontComboBox, - QtCore.SIGNAL(u'activated(int)'), - self.calculateLines) + QtCore.SIGNAL(u'activated(int)'), self.calculateLines) + QtCore.QObject.connect(self.footerFontComboBox, + QtCore.SIGNAL(u'activated(int)'), self.updateTheme) + QtCore.QObject.connect(self.footerSizeSpinBox, + QtCore.SIGNAL(u'valueChanged(int)'), self.updateTheme) def setDefaults(self): """ @@ -211,7 +209,7 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): Updates the lines on a page on the wizard """ self.mainLineCountLabel.setText(unicode(translate('OpenLP.ThemeForm', - '(%d lines per slide)')) % int(lines)) + '(approximately %d lines per slide)')) % int(lines)) def resizeEvent(self, event=None): """ @@ -236,13 +234,36 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): """ Detects Page changes and updates as approprate. """ + if self.page(pageId) == self.areaPositionPage: + self.setOption(QtGui.QWizard.HaveCustomButton1, True) + else: + self.setOption(QtGui.QWizard.HaveCustomButton1, False) if self.page(pageId) == self.previewPage: self.updateTheme() frame = self.thememanager.generateImage(self.theme) - self.previewBoxLabel.setPixmap(QtGui.QPixmap.fromImage(frame)) + self.previewBoxLabel.setPixmap(frame) self.displayAspectRatio = float(frame.width()) / frame.height() self.resizeEvent() + def onCustom1ButtonClicked(self, number): + """ + Generate layout preview and display the form. + """ + self.updateTheme() + width = self.thememanager.mainwindow.renderer.width + height = self.thememanager.mainwindow.renderer.height + pixmap = QtGui.QPixmap(width, height) + pixmap.fill(QtCore.Qt.white) + paint = QtGui.QPainter(pixmap) + paint.setPen(QtGui.QPen(QtCore.Qt.blue, 2)) + paint.drawRect(self.thememanager.mainwindow.renderer. + get_main_rectangle(self.theme)) + paint.setPen(QtGui.QPen(QtCore.Qt.red, 2)) + paint.drawRect(self.thememanager.mainwindow.renderer. + get_footer_rectangle(self.theme)) + paint.end() + self.themeLayoutForm.exec_(pixmap) + def onOutlineCheckCheckBoxStateChanged(self, state): """ Change state as Outline check box changed @@ -290,6 +311,7 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): Run the wizard. """ log.debug(u'Editing theme %s' % self.theme.theme_name) + self.temp_background_filename = u'' self.updateThemeAllowed = False self.setDefaults() self.updateThemeAllowed = True @@ -301,7 +323,7 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): 'Edit Theme - %s')) % self.theme.theme_name) self.next() else: - self.setWindowTitle(UiStrings.NewTheme) + self.setWindowTitle(UiStrings().NewTheme) return QtGui.QWizard.exec_(self) def initializePage(self, id): @@ -338,6 +360,8 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): self.theme.background_end_color) self.setField(u'background_type', QtCore.QVariant(1)) else: + self.imageColorButton.setStyleSheet(u'background-color: %s' % + self.theme.background_border_color) self.imageFileEdit.setText(self.theme.background_filename) self.setField(u'background_type', QtCore.QVariant(2)) if self.theme.background_direction == \ @@ -389,7 +413,7 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): Handle the display and state of the Footer Area page. """ self.footerFontComboBox.setCurrentFont( - QtGui.QFont(self.theme.font_main_name)) + QtGui.QFont(self.theme.font_footer_name)) self.footerColorButton.setStyleSheet(u'background-color: %s' % self.theme.font_footer_color) self.setField(u'footerSizeSpinBox', @@ -443,6 +467,16 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): # do not allow updates when screen is building for the first time. if self.updateThemeAllowed: self.theme.background_type = BackgroundType.to_string(index) + if self.theme.background_type != \ + BackgroundType.to_string(BackgroundType.Image) and \ + self.temp_background_filename == u'': + self.temp_background_filename = self.theme.background_filename + self.theme.background_filename = u'' + if self.theme.background_type == \ + BackgroundType.to_string(BackgroundType.Image) and \ + self.temp_background_filename != u'': + self.theme.background_filename = self.temp_background_filename + self.temp_background_filename = u'' self.setBackgroundPageValues() def onGradientComboBoxCurrentIndexChanged(self, index): @@ -462,6 +496,14 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): self._colorButton(self.theme.background_color) self.setBackgroundPageValues() + def onImageColorButtonClicked(self): + """ + Background / Gradient 1 Color button pushed. + """ + self.theme.background_border_color = \ + self._colorButton(self.theme.background_border_color) + self.setBackgroundPageValues() + def onGradientStartButtonClicked(self): """ Gradient 2 Color button pushed. @@ -484,7 +526,7 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): """ images_filter = get_images_filter() images_filter = u'%s;;%s (*.*) (*)' % ( - images_filter, UiStrings.AllFiles) + images_filter, UiStrings().AllFiles) filename = QtGui.QFileDialog.getOpenFileName(self, translate('OpenLP.ThemeForm', 'Select Image'), u'', images_filter) @@ -562,7 +604,7 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): def accept(self): """ - Lets save the them as Finish has been pressed + Lets save the theme as Finish has been pressed """ # Save the theme name self.theme.theme_name = unicode(self.field(u'name').toString()) diff --git a/openlp/core/ui/themelayoutdialog.py b/openlp/core/ui/themelayoutdialog.py new file mode 100644 index 000000000..5be08ad65 --- /dev/null +++ b/openlp/core/ui/themelayoutdialog.py @@ -0,0 +1,75 @@ +# -*- 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 PyQt4 import QtCore, QtGui + +from openlp.core.lib import translate +from openlp.core.lib.ui import create_accept_reject_button_box + + +class Ui_ThemeLayoutDialog(object): + def setupUi(self, themeLayoutDialog): + themeLayoutDialog.setObjectName(u'themeLayoutDialogDialog') + #themeLayoutDialog.resize(300, 200) + self.previewLayout = QtGui.QVBoxLayout(themeLayoutDialog) + self.previewLayout.setObjectName(u'PreviewLayout') + self.previewArea = QtGui.QWidget(themeLayoutDialog) + self.previewArea.setObjectName(u'PreviewArea') + self.previewAreaLayout = QtGui.QGridLayout(self.previewArea) + self.previewAreaLayout.setMargin(0) + self.previewAreaLayout.setColumnStretch(0, 1) + self.previewAreaLayout.setRowStretch(0, 1) + self.previewAreaLayout.setObjectName(u'PreviewAreaLayout') + self.themeDisplayLabel = QtGui.QLabel(self.previewArea) + self.themeDisplayLabel.setFrameShape(QtGui.QFrame.Box) + self.themeDisplayLabel.setScaledContents(True) + self.themeDisplayLabel.setObjectName(u'ThemeDisplayLabel') + self.previewAreaLayout.addWidget(self.themeDisplayLabel) + self.previewLayout.addWidget(self.previewArea) + self.mainColourLabel = QtGui.QLabel(self.previewArea) + self.mainColourLabel.setObjectName(u'MainColourLabel') + self.previewLayout.addWidget(self.mainColourLabel) + self.footerColourLabel = QtGui.QLabel(self.previewArea) + self.footerColourLabel.setObjectName(u'FooterColourLabel') + self.previewLayout.addWidget(self.footerColourLabel) + self.buttonBox = QtGui.QDialogButtonBox(themeLayoutDialog) + self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Ok) + self.buttonBox.setObjectName(u'ButtonBox') + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(u'accepted()'), + themeLayoutDialog.accept) + self.previewLayout.addWidget(self.buttonBox) + self.retranslateUi(themeLayoutDialog) + QtCore.QMetaObject.connectSlotsByName(themeLayoutDialog) + + def retranslateUi(self, themeLayoutDialog): + themeLayoutDialog.setWindowTitle( + translate('OpenLP.StartTimeForm', 'Theme Layout')) + self.mainColourLabel.setText(translate('OpenLP.StartTimeForm', + 'The blue box shows the main area.')) + self.footerColourLabel.setText(translate('OpenLP.StartTimeForm', + 'The red box shows the footer.')) + diff --git a/openlp/core/ui/themelayoutform.py b/openlp/core/ui/themelayoutform.py new file mode 100644 index 000000000..6f77d31da --- /dev/null +++ b/openlp/core/ui/themelayoutform.py @@ -0,0 +1,55 @@ +# -*- 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 PyQt4 import QtGui, QtCore + +from themelayoutdialog import Ui_ThemeLayoutDialog + +from openlp.core.lib import translate +from openlp.core.lib.ui import UiStrings, critical_error_message_box + +class ThemeLayoutForm(QtGui.QDialog, Ui_ThemeLayoutDialog): + """ + The exception dialog + """ + def __init__(self, parent): + QtGui.QDialog.__init__(self, parent) + self.setupUi(self) + + def exec_(self, image): + """ + Run the Dialog with correct heading. + """ + pixmap = image.scaledToHeight(400, QtCore.Qt.SmoothTransformation) + self.themeDisplayLabel.setPixmap(image) + displayAspectRatio = float(image.width()) / image.height() + self.themeDisplayLabel.setFixedSize(400, 400 / displayAspectRatio ) + return QtGui.QDialog.exec_(self) + + def accept(self): + return QtGui.QDialog.accept(self) + diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index 407d0cfa8..596bce5d9 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.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, Armin Köhler, 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,16 +29,18 @@ import os import zipfile import shutil import logging +import locale from xml.etree.ElementTree import ElementTree, XML from PyQt4 import QtCore, QtGui from openlp.core.lib import OpenLPToolbar, get_text_file_string, build_icon, \ Receiver, SettingsManager, translate, check_item_selected, \ - check_directory_exists + check_directory_exists, create_thumb, validate_thumb from openlp.core.lib.theme import ThemeXML, BackgroundType, VerticalType, \ BackgroundGradientType -from openlp.core.lib.ui import UiStrings, critical_error_message_box +from openlp.core.lib.ui import UiStrings, critical_error_message_box, \ + context_menu_action, context_menu_separator from openlp.core.theme import Theme from openlp.core.ui import FileRenameForm, ThemeForm from openlp.core.utils import AppLocation, delete_file, file_is_unicode, \ @@ -55,15 +58,13 @@ class ThemeManager(QtGui.QWidget): self.settingsSection = u'themes' self.themeForm = ThemeForm(self) self.fileRenameForm = FileRenameForm(self) - self.serviceComboBox = \ - self.mainwindow.ServiceManagerContents.themeComboBox # start with the layout self.layout = QtGui.QVBoxLayout(self) self.layout.setSpacing(0) self.layout.setMargin(0) self.layout.setObjectName(u'layout') self.toolbar = OpenLPToolbar(self) - self.toolbar.addToolbarButton(UiStrings.NewTheme, + self.toolbar.addToolbarButton(UiStrings().NewTheme, u':/themes/theme_new.png', translate('OpenLP.ThemeManager', 'Create a new theme.'), self.onAddTheme) @@ -104,31 +105,35 @@ class ThemeManager(QtGui.QWidget): self.contextMenu) # build the context menu self.menu = QtGui.QMenu() - self.editAction = self.menu.addAction( - translate('OpenLP.ThemeManager', '&Edit Theme')) - self.editAction.setIcon(build_icon(u':/themes/theme_edit.png')) - self.copyAction = self.menu.addAction( - translate('OpenLP.ThemeManager', '&Copy Theme')) - self.copyAction.setIcon(build_icon(u':/themes/theme_edit.png')) - self.renameAction = self.menu.addAction( - translate('OpenLP.ThemeManager', '&Rename Theme')) - self.renameAction.setIcon(build_icon(u':/themes/theme_edit.png')) - self.deleteAction = self.menu.addAction( - translate('OpenLP.ThemeManager', '&Delete Theme')) - self.deleteAction.setIcon(build_icon(u':/general/general_delete.png')) - self.separator = self.menu.addSeparator() - self.globalAction = self.menu.addAction( - translate('OpenLP.ThemeManager', 'Set As &Global Default')) - self.globalAction.setIcon(build_icon(u':/general/general_export.png')) - self.exportAction = self.menu.addAction( - translate('OpenLP.ThemeManager', '&Export Theme')) - self.exportAction.setIcon(build_icon(u':/general/general_export.png')) + self.editAction = context_menu_action( + self.menu, u':/themes/theme_edit.png', + translate('OpenLP.ThemeManager', '&Edit Theme'), self.onEditTheme) + self.copyAction = context_menu_action( + self.menu, u':/themes/theme_edit.png', + translate('OpenLP.ThemeManager', '&Copy Theme'), self.onCopyTheme) + self.renameAction = context_menu_action( + self.menu, u':/themes/theme_edit.png', + translate('OpenLP.ThemeManager', '&Rename Theme'), + self.onRenameTheme) + self.deleteAction = context_menu_action( + self.menu, u':/general/general_delete.png', + translate('OpenLP.ThemeManager', '&Delete Theme'), + self.onDeleteTheme) + context_menu_separator(self.menu) + self.globalAction = context_menu_action( + self.menu, u':/general/general_export.png', + translate('OpenLP.ThemeManager', 'Set As &Global Default'), + self.changeGlobalFromScreen) + self.exportAction = context_menu_action( + self.menu, u':/general/general_export.png', + translate('OpenLP.ThemeManager', '&Export Theme'), + self.onExportTheme) # Signals QtCore.QObject.connect(self.themeListWidget, QtCore.SIGNAL(u'doubleClicked(QModelIndex)'), self.changeGlobalFromScreen) - QtCore.QObject.connect(self.themeListWidget, - QtCore.SIGNAL(u'itemClicked(QListWidgetItem *)'), + QtCore.QObject.connect(self.themeListWidget, QtCore.SIGNAL( + u'currentItemChanged(QListWidgetItem *, QListWidgetItem *)'), self.checkListState) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'theme_update_global'), self.changeGlobalFromTab) @@ -156,10 +161,9 @@ class ThemeManager(QtGui.QWidget): file = os.path.join(self.path, file).encode(encoding) self.unzipTheme(file, self.path) delete_file(file) - self.loadThemes() Receiver.send_message(u'cursor_normal') - def configUpdated(self, firstTime=False): + def configUpdated(self): """ Triggered when Config dialog is updated. """ @@ -171,6 +175,8 @@ class ThemeManager(QtGui.QWidget): """ If Default theme selected remove delete button. """ + if item is None: + return realThemeName = unicode(item.data(QtCore.Qt.UserRole).toString()) themeName = unicode(item.text()) # If default theme restrict actions @@ -197,19 +203,7 @@ class ThemeManager(QtGui.QWidget): self.deleteAction.setVisible(True) self.renameAction.setVisible(True) self.globalAction.setVisible(True) - action = self.menu.exec_(self.themeListWidget.mapToGlobal(point)) - if action == self.editAction: - self.onEditTheme() - if action == self.copyAction: - self.onCopyTheme() - if action == self.renameAction: - self.onRenameTheme() - if action == self.deleteAction: - self.onDeleteTheme() - if action == self.globalAction: - self.changeGlobalFromScreen() - if action == self.exportAction: - self.onExportTheme() + self.menu.exec_(self.themeListWidget.mapToGlobal(point)) def changeGlobalFromTab(self, themeName): """ @@ -281,6 +275,8 @@ class ThemeManager(QtGui.QWidget): self.fileRenameForm.fileNameEdit.setText(oldThemeName) if self.fileRenameForm.exec_(): newThemeName = unicode(self.fileRenameForm.fileNameEdit.text()) + if oldThemeName == newThemeName: + return if self.checkIfThemeExists(newThemeName): oldThemeData = self.getThemeData(oldThemeName) self.cloneThemeData(oldThemeData, newThemeName) @@ -296,13 +292,14 @@ class ThemeManager(QtGui.QWidget): """ item = self.themeListWidget.currentItem() oldThemeName = unicode(item.data(QtCore.Qt.UserRole).toString()) - self.fileRenameForm.fileNameEdit.setText(oldThemeName) + self.fileRenameForm.fileNameEdit.setText( + unicode(translate('OpenLP.ThemeManager', + 'Copy of %s','Copy of ')) % oldThemeName) if self.fileRenameForm.exec_(True): newThemeName = unicode(self.fileRenameForm.fileNameEdit.text()) if self.checkIfThemeExists(newThemeName): themeData = self.getThemeData(oldThemeName) self.cloneThemeData(themeData, newThemeName) - self.loadThemes() def cloneThemeData(self, themeData, newThemeName): """ @@ -334,6 +331,7 @@ class ThemeManager(QtGui.QWidget): self.oldBackgroundImage = theme.background_filename self.themeForm.theme = theme self.themeForm.exec_(True) + self.oldBackgroundImage = None def onDeleteTheme(self): """ @@ -360,7 +358,7 @@ class ThemeManager(QtGui.QWidget): The theme to delete. """ self.themelist.remove(theme) - thumb = theme + u'.png' + thumb = u'%s.png' % theme delete_file(os.path.join(self.path, thumb)) delete_file(os.path.join(self.thumbPath, thumb)) try: @@ -371,7 +369,7 @@ class ThemeManager(QtGui.QWidget): def onExportTheme(self): """ - Save the theme in a zip file + Export the theme in a zip file """ item = self.themeListWidget.currentItem() if item is None: @@ -424,16 +422,16 @@ class ThemeManager(QtGui.QWidget): unicode(translate('OpenLP.ThemeManager', 'OpenLP Themes (*.theme *.otz)'))) log.info(u'New Themes %s', unicode(files)) + if not files: + return Receiver.send_message(u'cursor_busy') - if files: - for file in files: - SettingsManager.set_last_dir( - self.settingsSection, unicode(file)) - self.unzipTheme(file, self.path) + for file in files: + SettingsManager.set_last_dir(self.settingsSection, unicode(file)) + self.unzipTheme(file, self.path) self.loadThemes() Receiver.send_message(u'cursor_normal') - def loadThemes(self): + def loadThemes(self, firstTime=False): """ Loads the theme lists and triggers updates accross the whole system using direct calls or core functions and events for the plugins. @@ -443,31 +441,45 @@ class ThemeManager(QtGui.QWidget): self.themelist = [] self.themeListWidget.clear() dirList = os.listdir(self.path) - dirList.sort() - for name in dirList: - if name.endswith(u'.png'): - # check to see file is in theme root directory - theme = os.path.join(self.path, name) - if os.path.exists(theme): - textName = os.path.splitext(name)[0] - if textName == self.global_theme: - name = unicode(translate('OpenLP.ThemeManager', - '%s (default)')) % textName - else: - name = textName - thumb = os.path.join(self.thumbPath, u'%s.png' % textName) - item_name = QtGui.QListWidgetItem(name) - if os.path.exists(thumb): - icon = build_icon(thumb) - else: - icon = build_icon(theme) - pixmap = icon.pixmap(QtCore.QSize(88, 50)) - pixmap.save(thumb, u'png') - item_name.setIcon(icon) - item_name.setData(QtCore.Qt.UserRole, - QtCore.QVariant(textName)) - self.themeListWidget.addItem(item_name) - self.themelist.append(textName) + files = SettingsManager.get_files(self.settingsSection, u'.png') + if firstTime: + self.firstTime() + files = SettingsManager.get_files(self.settingsSection, u'.png') + # No themes have been found so create one + if len(files) == 0: + theme = ThemeXML() + theme.theme_name = UiStrings().Default + self._writeTheme(theme, None, None) + QtCore.QSettings().setValue( + self.settingsSection + u'/global theme', + QtCore.QVariant(theme.theme_name)) + self.configUpdated() + files = SettingsManager.get_files(self.settingsSection, u'.png') + # Sort the themes by its name considering language specific characters. + # lower() is needed for windows! + files.sort(key=lambda filename: unicode(filename).lower(), + cmp=locale.strcoll) + # now process the file list of png files + for name in files: + # check to see file is in theme root directory + theme = os.path.join(self.path, name) + if os.path.exists(theme): + textName = os.path.splitext(name)[0] + if textName == self.global_theme: + name = unicode(translate('OpenLP.ThemeManager', + '%s (default)')) % textName + else: + name = textName + thumb = os.path.join(self.thumbPath, u'%s.png' % textName) + item_name = QtGui.QListWidgetItem(name) + if validate_thumb(theme, thumb): + icon = build_icon(thumb) + else: + icon = create_thumb(theme, thumb) + item_name.setIcon(icon) + item_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(textName)) + self.themeListWidget.addItem(item_name) + self.themelist.append(textName) self._pushThemes() def _pushThemes(self): @@ -502,16 +514,16 @@ class ThemeManager(QtGui.QWidget): def unzipTheme(self, filename, dir): """ Unzip the theme, remove the preview file if stored - Generate a new preview fileCheck the XML theme version and upgrade if + Generate a new preview file. Check the XML theme version and upgrade if necessary. """ log.debug(u'Unzipping theme %s', filename) filename = unicode(filename) zip = None outfile = None + filexml = None try: zip = zipfile.ZipFile(filename) - filexml = None themename = None for file in zip.namelist(): ucsfile = file_is_unicode(file) @@ -547,7 +559,7 @@ class ThemeManager(QtGui.QWidget): else: outfile = open(fullpath, u'wb') outfile.write(zip.read(file)) - except (IOError, NameError): + except (IOError, NameError, zipfile.BadZipfile): critical_error_message_box( translate('OpenLP.ThemeManager', 'Validation Error'), translate('OpenLP.ThemeManager', 'File is not a valid theme.')) @@ -562,7 +574,9 @@ class ThemeManager(QtGui.QWidget): if filexml: theme = self._createThemeFromXml(filexml, self.path) self.generateAndSaveImage(dir, themename, theme) - else: + # Only show the error message, when IOError was not raised (in this + # case the error message has already been shown). + elif zip is not None: critical_error_message_box( translate('OpenLP.ThemeManager', 'Validation Error'), translate('OpenLP.ThemeManager', @@ -592,6 +606,11 @@ class ThemeManager(QtGui.QWidget): and to trigger the reload of the theme list """ self._writeTheme(theme, imageFrom, imageTo) + if theme.background_type == \ + BackgroundType.to_string(BackgroundType.Image): + self.mainwindow.imageManager.update_image(theme.theme_name, + u'theme', QtGui.QColor(theme.background_border_color)) + self.mainwindow.imageManager.process_updates() self.loadThemes() def _writeTheme(self, theme, imageFrom, imageTo): @@ -605,7 +624,7 @@ class ThemeManager(QtGui.QWidget): theme_dir = os.path.join(self.path, name) check_directory_exists(theme_dir) theme_file = os.path.join(theme_dir, name + u'.xml') - if imageTo and self.oldBackgroundImage and \ + if self.oldBackgroundImage and \ imageTo != self.oldBackgroundImage: delete_file(self.oldBackgroundImage) outfile = None @@ -635,14 +654,24 @@ class ThemeManager(QtGui.QWidget): os.unlink(samplepathname) frame.save(samplepathname, u'png') thumb = os.path.join(self.thumbPath, u'%s.png' % name) - icon = build_icon(frame) - pixmap = icon.pixmap(QtCore.QSize(88, 50)) - pixmap.save(thumb, u'png') + create_thumb(samplepathname, thumb, False) log.debug(u'Theme image written to %s', samplepathname) + def updatePreviewImages(self): + """ + Called to update the themes' preview images. + """ + self.mainwindow.displayProgressBar(len(self.themelist)) + for theme in self.themelist: + self.mainwindow.incrementProgressBar() + self.generateAndSaveImage( + self.path, theme, self.getThemeData(theme)) + self.mainwindow.finishedProgressBar() + self.loadThemes() + def generateImage(self, themeData, forcePage=False): """ - Call the RenderManager to build a Sample Image + Call the renderer to build a Sample Image ``themeData`` The theme to generated a preview for. @@ -651,7 +680,7 @@ class ThemeManager(QtGui.QWidget): Flag to tell message lines per page need to be generated. """ log.debug(u'generateImage \n%s ', themeData) - return self.mainwindow.renderManager.generate_preview( + return self.mainwindow.renderer.generate_preview( themeData, forcePage) def getPreviewImage(self, theme): @@ -730,7 +759,8 @@ class ThemeManager(QtGui.QWidget): 'Theme %s is used in the %s plugin.')) % \ (theme, plugin.name)) return False - return True + return True + return False def _migrateVersion122(self, xml_data): """ @@ -789,3 +819,4 @@ class ThemeManager(QtGui.QWidget): newtheme.display_horizontal_align = theme.HorizontalAlign newtheme.display_vertical_align = vAlignCorrection return newtheme.extract_xml() + diff --git a/openlp/core/ui/themestab.py b/openlp/core/ui/themestab.py index c7442744f..572efdf4b 100644 --- a/openlp/core/ui/themestab.py +++ b/openlp/core/ui/themestab.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, Armin Köhler, 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,15 +29,17 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import SettingsTab, Receiver, translate from openlp.core.lib.theme import ThemeLevel -from openlp.core.lib.ui import UiStrings +from openlp.core.lib.ui import UiStrings, find_and_set_in_combo_box class ThemesTab(SettingsTab): """ ThemesTab is the theme settings tab in the settings dialog. """ - def __init__(self, parent): - self.parent = parent - SettingsTab.__init__(self, u'Themes') + def __init__(self, parent, mainwindow): + self.mainwindow = mainwindow + generalTranslated = translate('OpenLP.ThemesTab', 'Themes') + SettingsTab.__init__(self, parent, u'Themes', generalTranslated) + self.icon_path = u':/themes/theme_new.png' def setupUi(self): self.setObjectName(u'ThemesTab') @@ -100,7 +103,7 @@ class ThemesTab(SettingsTab): QtCore.SIGNAL(u'theme_update_list'), self.updateThemeList) def retranslateUi(self): - self.tabTitleVisible = UiStrings.Themes + self.tabTitleVisible = UiStrings().Themes self.GlobalGroupBox.setTitle( translate('OpenLP.ThemesTab', 'Global Theme')) self.LevelGroupBox.setTitle( @@ -142,12 +145,10 @@ class ThemesTab(SettingsTab): def save(self): settings = QtCore.QSettings() settings.beginGroup(self.settingsSection) - settings.setValue(u'theme level', - QtCore.QVariant(self.theme_level)) - settings.setValue(u'global theme', - QtCore.QVariant(self.global_theme)) + settings.setValue(u'theme level', QtCore.QVariant(self.theme_level)) + settings.setValue(u'global theme', QtCore.QVariant(self.global_theme)) settings.endGroup() - self.parent.renderManager.set_global_theme( + self.mainwindow.renderer.set_global_theme( self.global_theme, self.theme_level) Receiver.send_message(u'theme_update_global', self.global_theme) @@ -165,7 +166,7 @@ class ThemesTab(SettingsTab): def onDefaultComboBoxChanged(self, value): self.global_theme = unicode(self.DefaultComboBox.currentText()) - self.parent.renderManager.set_global_theme( + self.mainwindow.renderer.set_global_theme( self.global_theme, self.theme_level) self.__previewGlobalTheme() @@ -183,15 +184,9 @@ class ThemesTab(SettingsTab): self.settingsSection + u'/global theme', QtCore.QVariant(u'')).toString()) self.DefaultComboBox.clear() - for theme in theme_list: - self.DefaultComboBox.addItem(theme) - id = self.DefaultComboBox.findText( - self.global_theme, QtCore.Qt.MatchExactly) - if id == -1: - id = 0 # Not Found - self.global_theme = u'' - self.DefaultComboBox.setCurrentIndex(id) - self.parent.renderManager.set_global_theme( + self.DefaultComboBox.addItems(theme_list) + find_and_set_in_combo_box(self.DefaultComboBox, self.global_theme) + self.mainwindow.renderer.set_global_theme( self.global_theme, self.theme_level) if self.global_theme is not u'': self.__previewGlobalTheme() @@ -200,7 +195,7 @@ class ThemesTab(SettingsTab): """ Utility method to update the global theme preview image. """ - image = self.parent.themeManagerContents.getPreviewImage( + image = self.mainwindow.themeManagerContents.getPreviewImage( self.global_theme) preview = QtGui.QPixmap(unicode(image)) if not preview.isNull(): diff --git a/openlp/core/ui/themewizard.py b/openlp/core/ui/themewizard.py index 20cca69c6..1135db274 100644 --- a/openlp/core/ui/themewizard.py +++ b/openlp/core/ui/themewizard.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, Armin Köhler, 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,7 +38,8 @@ class Ui_ThemeWizard(object): themeWizard.setModal(True) themeWizard.setWizardStyle(QtGui.QWizard.ModernStyle) themeWizard.setOptions(QtGui.QWizard.IndependentPages | - QtGui.QWizard.NoBackButtonOnStartPage) + QtGui.QWizard.NoBackButtonOnStartPage | + QtGui.QWizard.HaveCustomButton1) self.spacer = QtGui.QSpacerItem(10, 0, QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Minimum) # Welcome Page @@ -104,6 +106,11 @@ class Ui_ThemeWizard(object): self.imageLayout = QtGui.QFormLayout(self.imageWidget) self.imageLayout.setMargin(0) self.imageLayout.setObjectName(u'ImageLayout') + self.imageColorLabel = QtGui.QLabel(self.colorWidget) + self.imageColorLabel.setObjectName(u'ImageColorLabel') + self.imageColorButton = QtGui.QPushButton(self.colorWidget) + self.imageColorButton.setObjectName(u'ImageColorButton') + self.imageLayout.addRow(self.imageColorLabel, self.imageColorButton) self.imageLabel = QtGui.QLabel(self.imageWidget) self.imageLabel.setObjectName(u'ImageLabel') self.imageFileLayout = QtGui.QHBoxLayout() @@ -117,7 +124,7 @@ class Ui_ThemeWizard(object): build_icon(u':/general/general_open.png')) self.imageFileLayout.addWidget(self.imageBrowseButton) self.imageLayout.addRow(self.imageLabel, self.imageFileLayout) - self.imageLayout.setItem(1, QtGui.QFormLayout.LabelRole, self.spacer) + self.imageLayout.setItem(2, QtGui.QFormLayout.LabelRole, self.spacer) self.backgroundStack.addWidget(self.imageWidget) self.backgroundLayout.addLayout(self.backgroundStack) themeWizard.addPage(self.backgroundPage) @@ -240,7 +247,7 @@ class Ui_ThemeWizard(object): self.horizontalLabel = QtGui.QLabel(self.alignmentPage) self.horizontalLabel.setObjectName(u'HorizontalLabel') self.horizontalComboBox = QtGui.QComboBox(self.alignmentPage) - self.horizontalComboBox.addItems([u'', u'', u'']) + self.horizontalComboBox.addItems([u'', u'', u'', u'']) self.horizontalComboBox.setObjectName(u'HorizontalComboBox') self.alignmentLayout.addRow(self.horizontalLabel, self.horizontalComboBox) @@ -424,7 +431,7 @@ class Ui_ThemeWizard(object): self.backgroundComboBox.setItemText(BackgroundType.Gradient, translate('OpenLP.ThemeWizard', 'Gradient')) self.backgroundComboBox.setItemText( - BackgroundType.Image, UiStrings.Image) + BackgroundType.Image, UiStrings().Image) self.colorLabel.setText(translate('OpenLP.ThemeWizard', 'Color:')) self.gradientStartLabel.setText( translate(u'OpenLP.ThemeWizard', 'Starting color:')) @@ -442,7 +449,9 @@ class Ui_ThemeWizard(object): translate('OpenLP.ThemeWizard', 'Top Left - Bottom Right')) self.gradientComboBox.setItemText(BackgroundGradientType.LeftBottom, translate('OpenLP.ThemeWizard', 'Bottom Left - Top Right')) - self.imageLabel.setText(u'%s:' % UiStrings.Image) + self.imageColorLabel.setText( + translate(u'OpenLP.ThemeWizard', 'Background color:')) + self.imageLabel.setText(u'%s:' % UiStrings().Image) self.mainAreaPage.setTitle( translate('OpenLP.ThemeWizard', 'Main Area Font Details')) self.mainAreaPage.setSubTitle( @@ -451,17 +460,17 @@ class Ui_ThemeWizard(object): self.mainFontLabel.setText(translate('OpenLP.ThemeWizard', 'Font:')) self.mainColorLabel.setText(translate('OpenLP.ThemeWizard', 'Color:')) self.mainSizeLabel.setText(translate('OpenLP.ThemeWizard', 'Size:')) - self.mainSizeSpinBox.setSuffix(UiStrings.FontSizePtUnit) + self.mainSizeSpinBox.setSuffix(UiStrings().FontSizePtUnit) self.lineSpacingLabel.setText( translate('OpenLP.ThemeWizard', 'Line Spacing:')) - self.lineSpacingSpinBox.setSuffix(UiStrings.FontSizePtUnit) + self.lineSpacingSpinBox.setSuffix(UiStrings().FontSizePtUnit) self.outlineCheckBox.setText( translate('OpenLP.ThemeWizard', '&Outline:')) self.outlineSizeLabel.setText(translate('OpenLP.ThemeWizard', 'Size:')) - self.outlineSizeSpinBox.setSuffix(UiStrings.FontSizePtUnit) + self.outlineSizeSpinBox.setSuffix(UiStrings().FontSizePtUnit) self.shadowCheckBox.setText(translate('OpenLP.ThemeWizard', '&Shadow:')) self.shadowSizeLabel.setText(translate('OpenLP.ThemeWizard', 'Size:')) - self.shadowSizeSpinBox.setSuffix(UiStrings.FontSizePtUnit) + self.shadowSizeSpinBox.setSuffix(UiStrings().FontSizePtUnit) self.mainBoldCheckBox.setText(translate('OpenLP.ThemeWizard', 'Bold')) self.mainItalicsCheckBox.setText( translate('OpenLP.ThemeWizard', 'Italic')) @@ -473,7 +482,7 @@ class Ui_ThemeWizard(object): self.footerFontLabel.setText(translate('OpenLP.ThemeWizard', 'Font:')) self.footerColorLabel.setText(translate('OpenLP.ThemeWizard', 'Color:')) self.footerSizeLabel.setText(translate('OpenLP.ThemeWizard', 'Size:')) - self.footerSizeSpinBox.setSuffix(UiStrings.FontSizePtUnit) + self.footerSizeSpinBox.setSuffix(UiStrings().FontSizePtUnit) self.alignmentPage.setTitle( translate('OpenLP.ThemeWizard', 'Text Formatting Details')) self.alignmentPage.setSubTitle( @@ -487,6 +496,8 @@ class Ui_ThemeWizard(object): translate('OpenLP.ThemeWizard', 'Right')) self.horizontalComboBox.setItemText(HorizontalType.Center, translate('OpenLP.ThemeWizard', 'Center')) + self.horizontalComboBox.setItemText(HorizontalType.Justify, + translate('OpenLP.ThemeWizard', 'Justify')) self.transitionsLabel.setText( translate('OpenLP.ThemeWizard', 'Transitions:')) self.areaPositionPage.setTitle( @@ -525,6 +536,9 @@ class Ui_ThemeWizard(object): translate('OpenLP.ThemeWizard', 'px')) self.footerPositionCheckBox.setText( translate('OpenLP.ThemeWizard', 'Use default location')) + themeWizard.setOption(QtGui.QWizard.HaveCustomButton1, False) + themeWizard.setButtonText(QtGui.QWizard.CustomButton1, + translate('OpenLP.ThemeWizard', 'Layout Preview')) self.previewPage.setTitle( translate('OpenLP.ThemeWizard', 'Save and Preview')) self.previewPage.setSubTitle( diff --git a/openlp/core/ui/wizard.py b/openlp/core/ui/wizard.py index 0b275733e..9d8a106ed 100644 --- a/openlp/core/ui/wizard.py +++ b/openlp/core/ui/wizard.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, Armin Köhler, 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 # @@ -95,6 +96,10 @@ class OpenLPWizard(QtGui.QWizard): self.customSignals() QtCore.QObject.connect(self, QtCore.SIGNAL(u'currentIdChanged(int)'), self.onCurrentIdChanged) + QtCore.QObject.connect(self.errorCopyToButton, + QtCore.SIGNAL(u'clicked()'), self.onErrorCopyToButtonClicked) + QtCore.QObject.connect(self.errorSaveToButton, + QtCore.SIGNAL(u'clicked()'), self.onErrorSaveToButtonClicked) def setupUi(self, image): """ @@ -129,10 +134,36 @@ class OpenLPWizard(QtGui.QWizard): self.progressLayout.setObjectName(u'progressLayout') self.progressLabel = QtGui.QLabel(self.progressPage) self.progressLabel.setObjectName(u'progressLabel') + self.progressLabel.setWordWrap(True) self.progressLayout.addWidget(self.progressLabel) self.progressBar = QtGui.QProgressBar(self.progressPage) self.progressBar.setObjectName(u'progressBar') self.progressLayout.addWidget(self.progressBar) + # Add a QTextEdit and a copy to file and copy to clipboard button to be + # able to provide feedback to the user. Hidden by default. + self.errorReportTextEdit = QtGui.QTextEdit(self.progressPage) + self.errorReportTextEdit.setObjectName(u'progresserrorReportTextEdit') + self.errorReportTextEdit.setHidden(True) + self.errorReportTextEdit.setReadOnly(True) + self.progressLayout.addWidget(self.errorReportTextEdit) + self.errorButtonLayout = QtGui.QHBoxLayout() + self.errorButtonLayout.setObjectName(u'errorButtonLayout') + spacer = QtGui.QSpacerItem(40, 20, + QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.errorButtonLayout.addItem(spacer) + self.errorCopyToButton = QtGui.QPushButton(self.progressPage) + self.errorCopyToButton.setObjectName(u'errorCopyToButton') + self.errorCopyToButton.setHidden(True) + self.errorCopyToButton.setIcon( + build_icon(u':/system/system_edit_copy.png')) + self.errorButtonLayout.addWidget(self.errorCopyToButton) + self.errorSaveToButton = QtGui.QPushButton(self.progressPage) + self.errorSaveToButton.setObjectName(u'errorSaveToButton') + self.errorSaveToButton.setHidden(True) + self.errorSaveToButton.setIcon( + build_icon(u':/general/general_save.png')) + self.errorButtonLayout.addWidget(self.errorSaveToButton) + self.progressLayout.addLayout(self.errorButtonLayout) self.addPage(self.progressPage) def exec_(self): @@ -159,6 +190,26 @@ class OpenLPWizard(QtGui.QWizard): self.preWizard() self.performWizard() self.postWizard() + else: + self.customPageChanged(pageId) + + def customPageChanged(self, pageId): + """ + Called when changing to a page other than the progress page + """ + pass + + def onErrorCopyToButtonClicked(self): + """ + Called when the ``onErrorCopyToButtonClicked`` has been clicked. + """ + pass + + def onErrorSaveToButtonClicked(self): + """ + Called when the ``onErrorSaveToButtonClicked`` has been clicked. + """ + pass def incrementProgressBar(self, status_text, increment=1): """ @@ -212,7 +263,7 @@ class OpenLPWizard(QtGui.QWizard): """ if filters: filters += u';;' - filters += u'%s (*)' % UiStrings.AllFiles + filters += u'%s (*)' % UiStrings().AllFiles filename = QtGui.QFileDialog.getOpenFileName(self, title, os.path.dirname(SettingsManager.get_last_dir( self.plugin.settingsSection, 1)), filters) @@ -220,3 +271,4 @@ class OpenLPWizard(QtGui.QWizard): editbox.setText(filename) SettingsManager.set_last_dir(self.plugin.settingsSection, filename, 1) + diff --git a/openlp/core/utils/__init__.py b/openlp/core/utils/__init__.py index 70b994653..fbf185474 100644 --- a/openlp/core/utils/__init__.py +++ b/openlp/core/utils/__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, Armin Köhler, 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 # @@ -33,8 +34,10 @@ import sys import time import urllib2 from datetime import datetime +from subprocess import Popen, PIPE from PyQt4 import QtGui, QtCore + if sys.platform != u'win32' and sys.platform != u'darwin': try: from xdg import BaseDirectory @@ -43,34 +46,33 @@ if sys.platform != u'win32' and sys.platform != u'darwin': XDG_BASE_AVAILABLE = False import openlp -from openlp.core.lib import Receiver, translate +from openlp.core.lib import Receiver, translate, check_directory_exists log = logging.getLogger(__name__) +APPLICATION_VERSION = {} IMAGES_FILTER = None UNO_CONNECTION_TYPE = u'pipe' #UNO_CONNECTION_TYPE = u'socket' +VERSION_SPLITTER = re.compile(r'([0-9]+).([0-9]+).([0-9]+)(?:-bzr([0-9]+))?') class VersionThread(QtCore.QThread): """ A special Qt thread class to fetch the version of OpenLP from the website. This is threaded so that it doesn't affect the loading time of OpenLP. """ - def __init__(self, parent, app_version): + def __init__(self, parent): QtCore.QThread.__init__(self, parent) - self.app_version = app_version - self.version_splitter = re.compile( - r'([0-9]+).([0-9]+).([0-9]+)(?:-bzr([0-9]+))?') def run(self): """ Run the thread. """ time.sleep(1) - Receiver.send_message(u'maindisplay_blank_check') - version = check_latest_version(self.app_version) + app_version = get_application_version() + version = check_latest_version(app_version) remote_version = {} local_version = {} - match = self.version_splitter.match(version) + match = VERSION_SPLITTER.match(version) if match: remote_version[u'major'] = int(match.group(1)) remote_version[u'minor'] = int(match.group(2)) @@ -79,7 +81,7 @@ class VersionThread(QtCore.QThread): remote_version[u'revision'] = int(match.group(4)) else: return - match = self.version_splitter.match(self.app_version[u'full']) + match = VERSION_SPLITTER.match(app_version[u'full']) if match: local_version[u'major'] = int(match.group(1)) local_version[u'minor'] = int(match.group(2)) @@ -98,6 +100,20 @@ class VersionThread(QtCore.QThread): Receiver.send_message(u'openlp_version_check', u'%s' % version) +class DelayStartThread(QtCore.QThread): + """ + A special Qt thread class to build things after OpenLP has started + """ + def __init__(self, parent): + QtCore.QThread.__init__(self, parent) + + def run(self): + """ + Run the thread. + """ + Receiver.send_message(u'openlp_phonon_creation') + + class AppLocation(object): """ The :class:`AppLocation` class is a static class which retrieves a @@ -111,6 +127,9 @@ class AppLocation(object): CacheDir = 6 LanguageDir = 7 + # Base path where data/config/cache dir is located + BaseDir = None + @staticmethod def get_directory(dir_type=1): """ @@ -134,8 +153,10 @@ class AppLocation(object): elif dir_type == AppLocation.LanguageDir: app_path = _get_frozen_path( os.path.abspath(os.path.split(sys.argv[0])[0]), - os.path.split(openlp.__file__)[0]) + _get_os_dir_path(dir_type)) return os.path.join(app_path, u'i18n') + elif dir_type == AppLocation.DataDir and AppLocation.BaseDir: + return os.path.join(AppLocation.BaseDir, 'data') else: return _get_os_dir_path(dir_type) @@ -145,8 +166,7 @@ class AppLocation(object): Return the path OpenLP stores all its data under. """ path = AppLocation.get_directory(AppLocation.DataDir) - if not os.path.exists(path): - os.makedirs(path) + check_directory_exists(path) return path @staticmethod @@ -156,8 +176,7 @@ class AppLocation(object): """ data_path = AppLocation.get_data_path() path = os.path.join(data_path, section) - if not os.path.exists(path): - os.makedirs(path) + check_directory_exists(path) return path def _get_os_dir_path(dir_type): @@ -169,15 +188,21 @@ def _get_os_dir_path(dir_type): if dir_type == AppLocation.DataDir: return os.path.join(unicode(os.getenv(u'APPDATA'), encoding), u'openlp', u'data') + elif dir_type == AppLocation.LanguageDir: + return os.path.split(openlp.__file__)[0] return os.path.join(unicode(os.getenv(u'APPDATA'), encoding), u'openlp') elif sys.platform == u'darwin': if dir_type == AppLocation.DataDir: return os.path.join(unicode(os.getenv(u'HOME'), encoding), u'Library', u'Application Support', u'openlp', u'Data') + elif dir_type == AppLocation.LanguageDir: + return os.path.split(openlp.__file__)[0] return os.path.join(unicode(os.getenv(u'HOME'), encoding), u'Library', u'Application Support', u'openlp') else: + if dir_type == AppLocation.LanguageDir: + return os.path.join(u'/usr', u'share', u'openlp') if XDG_BASE_AVAILABLE: if dir_type == AppLocation.ConfigDir: return os.path.join(unicode(BaseDirectory.xdg_config_home, @@ -201,6 +226,85 @@ def _get_frozen_path(frozen_option, non_frozen_option): return frozen_option return non_frozen_option +def get_application_version(): + """ + Returns the application version of the running instance of OpenLP:: + + {u'full': u'1.9.4-bzr1249', u'version': u'1.9.4', u'build': u'bzr1249'} + """ + global APPLICATION_VERSION + if APPLICATION_VERSION: + return APPLICATION_VERSION + if u'--dev-version' in sys.argv or u'-d' in sys.argv: + # If we're running the dev version, let's use bzr to get the version. + try: + # If bzrlib is available, 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'-') + APPLICATION_VERSION = { + u'full': full_version, + u'version': bits[0], + u'build': bits[1] if len(bits) > 1 else None + } + if APPLICATION_VERSION[u'build']: + log.info(u'Openlp version %s build %s', + APPLICATION_VERSION[u'version'], APPLICATION_VERSION[u'build']) + else: + log.info(u'Openlp version %s' % APPLICATION_VERSION[u'version']) + return APPLICATION_VERSION + def check_latest_version(current_version): """ Check the latest version of OpenLP against the version file on the OpenLP @@ -242,7 +346,7 @@ def add_actions(target, actions): The menu or toolbar to add actions to. ``actions`` - The actions to be added. An action consisting of the keyword 'None' + The actions to be added. An action consisting of the keyword ``None`` will result in a separator being inserted into the target. """ for action in actions: @@ -287,6 +391,17 @@ def split_filename(path): else: return os.path.split(path) +def clean_filename(filename): + """ + Removes invalid characters from the given ``filename``. + + ``filename`` + The "dirty" file name to clean. + """ + if not isinstance(filename, unicode): + filename = unicode(filename, u'utf-8') + return re.sub(r'[/\\?*|<>\[\]":<>+%]+', u'_', filename).strip(u'_') + def delete_file(file_path_name): """ Deletes a file from the system. @@ -337,6 +452,7 @@ def get_web_page(url, header=None, update_openlp=False): return None if update_openlp: Receiver.send_message(u'openlp_process_events') + log.debug(page) return page def file_is_unicode(filename): @@ -359,31 +475,12 @@ def file_is_unicode(filename): return None return ucsfile -def string_is_unicode(test_string): - """ - Makes sure a string is unicode. - - ``test_string`` - The string to confirm is unicode. - """ - return_string = u'' - if not test_string: - return return_string - if isinstance(test_string, unicode): - return_string = test_string - if not isinstance(test_string, unicode): - try: - return_string = unicode(test_string, u'utf-8') - except UnicodeError: - log.exception("Error encoding string to unicode") - return return_string - def get_uno_command(): """ Returns the UNO command to launch an openoffice.org instance. """ COMMAND = u'soffice' - OPTIONS = u'-nologo -norestore -minimized -invisible -nofirststartwizard' + OPTIONS = u'-nologo -norestore -minimized -nodefault -nofirststartwizard' if UNO_CONNECTION_TYPE == u'pipe': CONNECTION = u'"-accept=pipe,name=openlp_pipe;urp;"' else: @@ -408,7 +505,7 @@ def get_uno_instance(resolver): from languagemanager import LanguageManager from actions import ActionList -__all__ = [u'AppLocation', u'check_latest_version', u'add_actions', - u'get_filesystem_encoding', u'LanguageManager', u'ActionList', - u'get_web_page', u'file_is_unicode', u'string_is_unicode', - u'get_uno_command', u'get_uno_instance', u'delete_file'] +__all__ = [u'AppLocation', u'get_application_version', u'check_latest_version', + u'add_actions', u'get_filesystem_encoding', u'LanguageManager', + u'ActionList', u'get_web_page', u'file_is_unicode', u'get_uno_command', + u'get_uno_instance', u'delete_file', u'clean_filename'] diff --git a/openlp/core/utils/actions.py b/openlp/core/utils/actions.py index 30d1d7586..86ee69a48 100644 --- a/openlp/core/utils/actions.py +++ b/openlp/core/utils/actions.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, Armin Köhler, 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 # @@ -27,6 +28,8 @@ The :mod:`~openlp.core.utils.actions` module provides action list classes used by the shortcuts system. """ +from PyQt4 import QtCore, QtGui + class ActionCategory(object): """ The :class:`~openlp.core.utils.ActionCategory` class encapsulates a @@ -67,6 +70,7 @@ class CategoryActionList(object): Python 3 "next" method. """ if self.index >= len(self.actions): + self.index = 0 raise StopIteration else: self.index += 1 @@ -94,6 +98,12 @@ class CategoryActionList(object): self.actions.append((weight, action)) self.actions.sort(key=lambda act: act[0]) + def remove(self, remove_action): + for action in self.actions: + if action[1] == remove_action: + self.actions.remove(action) + return + class CategoryList(object): """ @@ -126,6 +136,7 @@ class CategoryList(object): Python 3 "next" method for iterator. """ if self.index >= len(self.categories): + self.index = 0 raise StopIteration else: self.index += 1 @@ -163,6 +174,11 @@ class CategoryList(object): self.categories.append(category) self.categories.sort(key=lambda cat: cat.weight) + def remove(self, name): + for category in self.categories: + if category.name == name: + self.categories.remove(category) + class ActionList(object): """ @@ -171,13 +187,102 @@ class ActionList(object): has a weight by which it is sorted when iterating through the list of actions or categories. """ + instance = None + def __init__(self): self.categories = CategoryList() - def add_action(self, action, category=u'Default', weight=None): + @staticmethod + def get_instance(): + if ActionList.instance is None: + ActionList.instance = ActionList() + return ActionList.instance + + def add_action(self, action, category=None, weight=None): + """ + Add an action to the list of actions. + + ``action`` + The action to add (QAction). **Note**, the action must not have an + empty ``objectName``. + + ``category`` + The category this action belongs to. The category can be a QString + or python unicode string. **Note**, if the category is ``None``, the + category and its actions are being hidden in the shortcut dialog. + However, if they are added, it is possible to avoid assigning + shortcuts twice, which is important. + + ``weight`` + The weight specifies how important a category is. However, this only + has an impact on the order the categories are displayed. + """ + if category is not None: + category = unicode(category) if category not in self.categories: self.categories.append(category) + action.defaultShortcuts = action.shortcuts() if weight is None: self.categories[category].actions.append(action) else: self.categories[category].actions.add(action, weight) + if category is None: + # Stop here, as this action is not configurable. + return + # Load the shortcut from the config. + settings = QtCore.QSettings() + settings.beginGroup(u'shortcuts') + shortcuts = settings.value(action.objectName(), + QtCore.QVariant(action.shortcuts())).toStringList() + action.setShortcuts( + [QtGui.QKeySequence(shortcut) for shortcut in shortcuts]) + settings.endGroup() + + def remove_action(self, action, category=None): + """ + This removes an action from its category. Empty categories are + automatically removed. + + ``action`` + The QAction object to be removed. + + ``category`` + The name (unicode string) of the category, which contains the + action. Defaults to None. + """ + if category is not None: + category = unicode(category) + if category not in self.categories: + return + self.categories[category].actions.remove(action) + # Remove empty categories. + if len(self.categories[category].actions) == 0: + self.categories.remove(category) + + def add_category(self, name, weight): + """ + Add an empty category to the list of categories. This is ony convenient + for categories with a given weight. + + ``name`` + The category's name. + + ``weight`` + The category's weight (int). + """ + if name in self.categories: + # Only change the weight and resort the categories again. + for category in self.categories: + if category.name == name: + category.weight = weight + self.categories.categories.sort(key=lambda cat: cat.weight) + return + self.categories.add(name, weight) + + +class CategoryOrder(object): + """ + An enumeration class for category weights. + """ + standardMenu = -20 + standardToolbar = -10 diff --git a/openlp/core/utils/languagemanager.py b/openlp/core/utils/languagemanager.py index 9cadf06e2..7b57ee2bc 100644 --- a/openlp/core/utils/languagemanager.py +++ b/openlp/core/utils/languagemanager.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, Armin Köhler, 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,6 +29,7 @@ The :mod:`languagemanager` module provides all the translation settings and language file loading for OpenLP. """ import logging +import sys from PyQt4 import QtCore, QtGui @@ -55,18 +57,28 @@ class LanguageManager(object): language = QtCore.QLocale.system().name() lang_path = AppLocation.get_directory(AppLocation.LanguageDir) app_translator = QtCore.QTranslator() - if app_translator.load(language, lang_path): - return app_translator + app_translator.load(language, lang_path) + # A translator for buttons and other default strings provided by Qt. + if sys.platform != u'win32' and sys.platform != u'darwin': + lang_path = QtCore.QLibraryInfo.location( + QtCore.QLibraryInfo.TranslationsPath) + default_translator = QtCore.QTranslator() + default_translator.load(u'qt_%s' % language, lang_path) + return app_translator, default_translator @staticmethod def find_qm_files(): """ Find all available language files in this OpenLP install """ + log.debug(u'Translation files: %s', AppLocation.get_directory( + AppLocation.LanguageDir)) trans_dir = QtCore.QDir(AppLocation.get_directory( AppLocation.LanguageDir)) file_names = trans_dir.entryList(QtCore.QStringList(u'*.qm'), QtCore.QDir.Files, QtCore.QDir.Name) + # Remove qm files from the list which start with "qt_". + file_names = file_names.filter(QtCore.QRegExp("^(?!qt_)")) for name in file_names: file_names.replaceInStrings(name, trans_dir.filePath(name)) return file_names @@ -89,7 +101,7 @@ class LanguageManager(object): """ Retrieve a saved language to use from settings """ - settings = QtCore.QSettings(u'OpenLP', u'OpenLP') + settings = QtCore.QSettings() language = unicode(settings.value( u'general/language', QtCore.QVariant(u'[en]')).toString()) log.info(u'Language file: \'%s\' Loaded from conf file' % language) @@ -112,14 +124,16 @@ class LanguageManager(object): """ language = u'en' if action: - action_name = u'%s' % action.objectName() - qm_list = LanguageManager.get_qm_list() - language = u'%s' % qm_list[action_name] + action_name = unicode(action.objectName()) + if action_name == u'autoLanguageItem': + LanguageManager.auto_language = True + else: + LanguageManager.auto_language = False + qm_list = LanguageManager.get_qm_list() + language = unicode(qm_list[action_name]) if LanguageManager.auto_language: language = u'[%s]' % language - # This needs to be here for the setValue to work - settings = QtCore.QSettings(u'OpenLP', u'OpenLP') - settings.setValue( + QtCore.QSettings().setValue( u'general/language', QtCore.QVariant(language)) log.info(u'Language file: \'%s\' written to conf file' % language) if message: diff --git a/openlp/plugins/__init__.py b/openlp/plugins/__init__.py index 5fd39d572..48fe8cb77 100644 --- a/openlp/plugins/__init__.py +++ b/openlp/plugins/__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, Armin Köhler, 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 # diff --git a/openlp/plugins/alerts/__init__.py b/openlp/plugins/alerts/__init__.py index f3c629b25..006db9361 100644 --- a/openlp/plugins/alerts/__init__.py +++ b/openlp/plugins/alerts/__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, Armin Köhler, 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 # diff --git a/openlp/plugins/alerts/alertsplugin.py b/openlp/plugins/alerts/alertsplugin.py index 1d9bd4c61..493f08ba0 100644 --- a/openlp/plugins/alerts/alertsplugin.py +++ b/openlp/plugins/alerts/alertsplugin.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, Armin Köhler, 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 # @@ -26,24 +27,98 @@ import logging -from PyQt4 import QtCore, QtGui +from PyQt4 import QtCore from openlp.core.lib import Plugin, StringContent, build_icon, translate from openlp.core.lib.db import Manager +from openlp.core.lib.ui import icon_action, UiStrings +from openlp.core.lib.theme import VerticalType +from openlp.core.utils.actions import ActionList from openlp.plugins.alerts.lib import AlertsManager, AlertsTab from openlp.plugins.alerts.lib.db import init_schema from openlp.plugins.alerts.forms import AlertForm log = logging.getLogger(__name__) +JAVASCRIPT = """ + function show_alert(alerttext, position){ + var text = document.getElementById('alert'); + text.innerHTML = alerttext; + if(alerttext == '') { + text.style.visibility = 'hidden'; + return 0; + } + if(position == ''){ + position = getComputedStyle(text, '').verticalAlign; + } + switch(position) + { + case 'top': + text.style.top = '0px'; + break; + case 'middle': + text.style.top = ((window.innerHeight - text.clientHeight) / 2) + + 'px'; + break; + case 'bottom': + text.style.top = (window.innerHeight - text.clientHeight) + + 'px'; + break; + } + text.style.visibility = 'visible'; + return text.clientHeight; + } + + function update_css(align, font, size, color, bgcolor){ + var text = document.getElementById('alert'); + text.style.fontSize = size + "pt"; + text.style.fontFamily = font; + text.style.color = color; + text.style.backgroundColor = bgcolor; + switch(align) + { + case 'top': + text.style.top = '0px'; + break; + case 'middle': + text.style.top = ((window.innerHeight - text.clientHeight) / 2) + + 'px'; + break; + case 'bottom': + text.style.top = (window.innerHeight - text.clientHeight) + + 'px'; + break; + } + } +""" +CSS = """ + #alert { + position: absolute; + left: 0px; + top: 0px; + z-index: 10; + width: 100%%; + vertical-align: %s; + font-family: %s; + font-size: %spt; + color: %s; + background-color: %s; + } +""" + +HTML = """ + +""" + class AlertsPlugin(Plugin): log.info(u'Alerts Plugin loaded') def __init__(self, plugin_helpers): - Plugin.__init__(self, u'Alerts', u'1.9.4', plugin_helpers, - settingsTabClass=AlertsTab) + Plugin.__init__(self, u'alerts', plugin_helpers, + settings_tab_class=AlertsTab) self.weight = -3 - self.icon = build_icon(u':/plugins/plugin_alerts.png') + self.icon_path = u':/plugins/plugin_alerts.png' + self.icon = build_icon(self.icon_path) self.alertsmanager = AlertsManager(self) self.manager = Manager(u'alerts', init_schema) self.alertForm = AlertForm(self) @@ -58,14 +133,13 @@ class AlertsPlugin(Plugin): use it as their parent. """ log.info(u'add tools menu') - self.toolsAlertItem = QtGui.QAction(tools_menu) - self.toolsAlertItem.setIcon(build_icon(u':/plugins/plugin_alerts.png')) - self.toolsAlertItem.setObjectName(u'toolsAlertItem') + self.toolsAlertItem = icon_action(tools_menu, u'toolsAlertItem', + u':/plugins/plugin_alerts.png') self.toolsAlertItem.setText(translate('AlertsPlugin', '&Alert')) self.toolsAlertItem.setStatusTip( translate('AlertsPlugin', 'Show an alert message.')) self.toolsAlertItem.setShortcut(u'F7') - self.serviceManager.mainwindow.ToolsMenu.addAction(self.toolsAlertItem) + self.serviceManager.mainwindow.toolsMenu.addAction(self.toolsAlertItem) QtCore.QObject.connect(self.toolsAlertItem, QtCore.SIGNAL(u'triggered()'), self.onAlertsTrigger) self.toolsAlertItem.setVisible(False) @@ -74,7 +148,8 @@ class AlertsPlugin(Plugin): log.info(u'Alerts Initialising') Plugin.initialise(self) self.toolsAlertItem.setVisible(True) - self.liveController.alertTab = self.settings_tab + action_list = ActionList.get_instance() + action_list.add_action(self.toolsAlertItem, UiStrings().Tools) def finalise(self): """ @@ -84,6 +159,8 @@ class AlertsPlugin(Plugin): self.manager.finalise() Plugin.finalise(self) self.toolsAlertItem.setVisible(False) + action_list = ActionList.get_instance() + action_list.remove_action(self.toolsAlertItem, u'Tools') def toggleAlertsState(self): self.alertsActive = not self.alertsActive @@ -97,7 +174,7 @@ class AlertsPlugin(Plugin): def about(self): about_text = translate('AlertsPlugin', 'Alerts Plugin' '
The alert plugin controls the displaying of nursery alerts ' - 'on the display screen') + 'on the display screen.') return about_text def setPluginTextStrings(self): @@ -113,3 +190,36 @@ class AlertsPlugin(Plugin): self.textStrings[StringContent.VisibleName] = { u'title': translate('AlertsPlugin', 'Alerts', 'container title') } + + def getDisplayJavaScript(self): + """ + Add Javascript to the main display. + """ + return JAVASCRIPT + + def getDisplayCss(self): + """ + Add CSS to the main display. + """ + align = VerticalType.Names[self.settings_tab.location] + return CSS % (align, self.settings_tab.font_face, + self.settings_tab.font_size, self.settings_tab.font_color, + self.settings_tab.bg_color) + + def getDisplayHtml(self): + """ + Add HTML to the main display. + """ + return HTML + + def refreshCss(self, frame): + """ + Trigger an update of the CSS in the maindisplay. + + ``frame`` + The Web frame holding the page. + """ + align = VerticalType.Names[self.settings_tab.location] + frame.evaluateJavaScript(u'update_css("%s", "%s", "%s", "%s", "%s")' % + (align, self.settings_tab.font_face, self.settings_tab.font_size, + self.settings_tab.font_color, self.settings_tab.bg_color)) diff --git a/openlp/plugins/alerts/forms/__init__.py b/openlp/plugins/alerts/forms/__init__.py index e2809ed73..3b11d7c92 100644 --- a/openlp/plugins/alerts/forms/__init__.py +++ b/openlp/plugins/alerts/forms/__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, Armin Köhler, 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 # diff --git a/openlp/plugins/alerts/forms/alertdialog.py b/openlp/plugins/alerts/forms/alertdialog.py index 27b0b0f7d..e42680165 100644 --- a/openlp/plugins/alerts/forms/alertdialog.py +++ b/openlp/plugins/alerts/forms/alertdialog.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, Armin Köhler, 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 # diff --git a/openlp/plugins/alerts/forms/alertform.py b/openlp/plugins/alerts/forms/alertform.py index e29211e66..45d283f28 100644 --- a/openlp/plugins/alerts/forms/alertform.py +++ b/openlp/plugins/alerts/forms/alertform.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, Armin Köhler, 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 # @@ -40,7 +41,7 @@ class AlertForm(QtGui.QDialog, Ui_AlertDialog): Initialise the alert form """ self.manager = plugin.manager - self.parent = plugin + self.plugin = plugin self.item_id = None QtGui.QDialog.__init__(self, plugin.formparent) self.setupUi(self) @@ -61,6 +62,12 @@ class AlertForm(QtGui.QDialog, Ui_AlertDialog): QtCore.QObject.connect(self.alertListWidget, QtCore.SIGNAL(u'currentRowChanged(int)'), self.onCurrentRowChanged) + def exec_(self): + self.displayButton.setEnabled(False) + self.displayCloseButton.setEnabled(False) + self.alertTextEdit.setText(u'') + return QtGui.QDialog.exec_(self) + def loadList(self): """ Loads the list with alerts. @@ -125,6 +132,12 @@ class AlertForm(QtGui.QDialog, Ui_AlertDialog): # Only enable the button, if we are editing an item. if self.item_id: self.saveButton.setEnabled(True) + if self.alertTextEdit.text(): + self.displayButton.setEnabled(True) + self.displayCloseButton.setEnabled(True) + else: + self.displayButton.setEnabled(False) + self.displayCloseButton.setEnabled(False) def onDoubleClick(self): """ @@ -163,8 +176,8 @@ class AlertForm(QtGui.QDialog, Ui_AlertDialog): # We found '<>' in the alert text, but the ParameterEdit field is empty. if text.find(u'<>') != -1 and not self.parameterEdit.text() and \ QtGui.QMessageBox.question(self, - translate('AlertPlugin.AlertForm', 'No Parameter Found'), - translate('AlertPlugin.AlertForm', 'You have not entered a ' + translate('AlertsPlugin.AlertForm', 'No Parameter Found'), + translate('AlertsPlugin.AlertForm', 'You have not entered a ' 'parameter to be replaced.\nDo you want to continue anyway?'), QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.No | QtGui.QMessageBox.Yes)) == QtGui.QMessageBox.No: @@ -174,15 +187,15 @@ class AlertForm(QtGui.QDialog, Ui_AlertDialog): # in the alert text. elif text.find(u'<>') == -1 and self.parameterEdit.text() and \ QtGui.QMessageBox.question(self, - translate('AlertPlugin.AlertForm', 'No Placeholder Found'), - translate('AlertPlugin.AlertForm', 'The alert text does not' + translate('AlertsPlugin.AlertForm', 'No Placeholder Found'), + translate('AlertsPlugin.AlertForm', 'The alert text does not' ' contain \'<>\'.\nDo you want to continue anyway?'), QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.No | QtGui.QMessageBox.Yes)) == QtGui.QMessageBox.No: self.parameterEdit.setFocus() return False text = text.replace(u'<>', unicode(self.parameterEdit.text())) - self.parent.alertsmanager.displayAlert(text) + self.plugin.alertsmanager.displayAlert(text) return True def onCurrentRowChanged(self, row): diff --git a/openlp/plugins/alerts/lib/__init__.py b/openlp/plugins/alerts/lib/__init__.py index 3b446b67d..780f295a8 100644 --- a/openlp/plugins/alerts/lib/__init__.py +++ b/openlp/plugins/alerts/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, Armin Köhler, 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 # diff --git a/openlp/plugins/alerts/lib/alertsmanager.py b/openlp/plugins/alerts/lib/alertsmanager.py index a05a29575..e73273fe7 100644 --- a/openlp/plugins/alerts/lib/alertsmanager.py +++ b/openlp/plugins/alerts/lib/alertsmanager.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, Armin Köhler, 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 # @@ -39,13 +40,12 @@ class AlertsManager(QtCore.QObject): log.info(u'Alert Manager loaded') def __init__(self, parent): - QtCore.QObject.__init__(self) - self.parent = parent + QtCore.QObject.__init__(self, parent) self.screen = None self.timer_id = 0 self.alertList = [] QtCore.QObject.connect(Receiver.get_receiver(), - QtCore.SIGNAL(u'maindisplay_active'), self.generateAlert) + QtCore.SIGNAL(u'live_display_active'), self.generateAlert) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'alerts_text'), self.onAlertText) @@ -69,11 +69,11 @@ class AlertsManager(QtCore.QObject): log.debug(u'display alert called %s' % text) self.alertList.append(text) if self.timer_id != 0: - Receiver.send_message(u'maindisplay_status_text', + Receiver.send_message(u'mainwindow_status_text', translate('AlertsPlugin.AlertsManager', 'Alert message created and displayed.')) return - Receiver.send_message(u'maindisplay_status_text', u'') + Receiver.send_message(u'mainwindow_status_text', u'') self.generateAlert() def generateAlert(self): @@ -81,11 +81,11 @@ class AlertsManager(QtCore.QObject): Format and request the Alert and start the timer """ log.debug(u'Generate Alert called') - if len(self.alertList) == 0: + if not self.alertList: return text = self.alertList.pop(0) - alertTab = self.parent.settings_tab - self.parent.liveController.display.alert(text) + alertTab = self.parent().settings_tab + self.parent().liveController.display.alert(text, alertTab.location) # Check to see if we have a timer running. if self.timer_id == 0: self.timer_id = self.startTimer(int(alertTab.timeout) * 1000) @@ -100,7 +100,8 @@ class AlertsManager(QtCore.QObject): """ log.debug(u'timer event') if event.timerId() == self.timer_id: - self.parent.liveController.display.alert(u'') + alertTab = self.parent().settings_tab + self.parent().liveController.display.alert(u'', alertTab.location) self.killTimer(self.timer_id) self.timer_id = 0 self.generateAlert() diff --git a/openlp/plugins/alerts/lib/alertstab.py b/openlp/plugins/alerts/lib/alertstab.py index 5b4f670a3..bd879fef3 100644 --- a/openlp/plugins/alerts/lib/alertstab.py +++ b/openlp/plugins/alerts/lib/alertstab.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, Armin Köhler, 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 # @@ -26,15 +27,16 @@ from PyQt4 import QtCore, QtGui -from openlp.core.lib import SettingsTab, translate +from openlp.core.lib import SettingsTab, translate, Receiver +from openlp.core.ui import AlertLocation from openlp.core.lib.ui import UiStrings, create_valign_combo class AlertsTab(SettingsTab): """ AlertsTab is the alerts settings tab in the settings dialog. """ - def __init__(self, name, visible_title): - SettingsTab.__init__(self, name, visible_title) + def __init__(self, parent, name, visible_title, icon_path): + SettingsTab.__init__(self, parent, name, visible_title, icon_path) def setupUi(self): self.setObjectName(u'AlertsTab') @@ -43,85 +45,85 @@ class AlertsTab(SettingsTab): self.fontGroupBox.setObjectName(u'fontGroupBox') self.fontLayout = QtGui.QFormLayout(self.fontGroupBox) self.fontLayout.setObjectName(u'fontLayout') - self.FontLabel = QtGui.QLabel(self.fontGroupBox) - self.FontLabel.setObjectName(u'FontLabel') - self.FontComboBox = QtGui.QFontComboBox(self.fontGroupBox) - self.FontComboBox.setObjectName(u'FontComboBox') - self.fontLayout.addRow(self.FontLabel, self.FontComboBox) - self.FontColorLabel = QtGui.QLabel(self.fontGroupBox) - self.FontColorLabel.setObjectName(u'FontColorLabel') - self.ColorLayout = QtGui.QHBoxLayout() - self.ColorLayout.setObjectName(u'ColorLayout') - self.FontColorButton = QtGui.QPushButton(self.fontGroupBox) - self.FontColorButton.setObjectName(u'FontColorButton') - self.ColorLayout.addWidget(self.FontColorButton) - self.ColorLayout.addSpacing(20) - self.BackgroundColorLabel = QtGui.QLabel(self.fontGroupBox) - self.BackgroundColorLabel.setObjectName(u'BackgroundColorLabel') - self.ColorLayout.addWidget(self.BackgroundColorLabel) - self.BackgroundColorButton = QtGui.QPushButton(self.fontGroupBox) - self.BackgroundColorButton.setObjectName(u'BackgroundColorButton') - self.ColorLayout.addWidget(self.BackgroundColorButton) - self.fontLayout.addRow(self.FontColorLabel, self.ColorLayout) - self.FontSizeLabel = QtGui.QLabel(self.fontGroupBox) - self.FontSizeLabel.setObjectName(u'FontSizeLabel') - self.FontSizeSpinBox = QtGui.QSpinBox(self.fontGroupBox) - self.FontSizeSpinBox.setObjectName(u'FontSizeSpinBox') - self.fontLayout.addRow(self.FontSizeLabel, self.FontSizeSpinBox) - self.TimeoutLabel = QtGui.QLabel(self.fontGroupBox) - self.TimeoutLabel.setObjectName(u'TimeoutLabel') - self.TimeoutSpinBox = QtGui.QSpinBox(self.fontGroupBox) - self.TimeoutSpinBox.setMaximum(180) - self.TimeoutSpinBox.setObjectName(u'TimeoutSpinBox') - self.fontLayout.addRow(self.TimeoutLabel, self.TimeoutSpinBox) + self.fontLabel = QtGui.QLabel(self.fontGroupBox) + self.fontLabel.setObjectName(u'fontLabel') + self.fontComboBox = QtGui.QFontComboBox(self.fontGroupBox) + self.fontComboBox.setObjectName(u'fontComboBox') + self.fontLayout.addRow(self.fontLabel, self.fontComboBox) + self.fontColorLabel = QtGui.QLabel(self.fontGroupBox) + self.fontColorLabel.setObjectName(u'fontColorLabel') + self.colorLayout = QtGui.QHBoxLayout() + self.colorLayout.setObjectName(u'colorLayout') + self.fontColorButton = QtGui.QPushButton(self.fontGroupBox) + self.fontColorButton.setObjectName(u'fontColorButton') + self.colorLayout.addWidget(self.fontColorButton) + self.colorLayout.addSpacing(20) + self.backgroundColorLabel = QtGui.QLabel(self.fontGroupBox) + self.backgroundColorLabel.setObjectName(u'backgroundColorLabel') + self.colorLayout.addWidget(self.backgroundColorLabel) + self.backgroundColorButton = QtGui.QPushButton(self.fontGroupBox) + self.backgroundColorButton.setObjectName(u'backgroundColorButton') + self.colorLayout.addWidget(self.backgroundColorButton) + self.fontLayout.addRow(self.fontColorLabel, self.colorLayout) + self.fontSizeLabel = QtGui.QLabel(self.fontGroupBox) + self.fontSizeLabel.setObjectName(u'fontSizeLabel') + self.fontSizeSpinBox = QtGui.QSpinBox(self.fontGroupBox) + self.fontSizeSpinBox.setObjectName(u'fontSizeSpinBox') + self.fontLayout.addRow(self.fontSizeLabel, self.fontSizeSpinBox) + self.timeoutLabel = QtGui.QLabel(self.fontGroupBox) + self.timeoutLabel.setObjectName(u'timeoutLabel') + self.timeoutSpinBox = QtGui.QSpinBox(self.fontGroupBox) + self.timeoutSpinBox.setMaximum(180) + self.timeoutSpinBox.setObjectName(u'timeoutSpinBox') + self.fontLayout.addRow(self.timeoutLabel, self.timeoutSpinBox) create_valign_combo(self, self.fontGroupBox, self.fontLayout) self.leftLayout.addWidget(self.fontGroupBox) self.leftLayout.addStretch() - self.PreviewGroupBox = QtGui.QGroupBox(self.rightColumn) - self.PreviewGroupBox.setObjectName(u'PreviewGroupBox') - self.PreviewLayout = QtGui.QVBoxLayout(self.PreviewGroupBox) - self.PreviewLayout.setObjectName(u'PreviewLayout') - self.FontPreview = QtGui.QLineEdit(self.PreviewGroupBox) - self.FontPreview.setObjectName(u'FontPreview') - self.PreviewLayout.addWidget(self.FontPreview) - self.rightLayout.addWidget(self.PreviewGroupBox) + self.previewGroupBox = QtGui.QGroupBox(self.rightColumn) + self.previewGroupBox.setObjectName(u'previewGroupBox') + self.previewLayout = QtGui.QVBoxLayout(self.previewGroupBox) + self.previewLayout.setObjectName(u'previewLayout') + self.fontPreview = QtGui.QLineEdit(self.previewGroupBox) + self.fontPreview.setObjectName(u'fontPreview') + self.previewLayout.addWidget(self.fontPreview) + self.rightLayout.addWidget(self.previewGroupBox) self.rightLayout.addStretch() # Signals and slots - QtCore.QObject.connect(self.BackgroundColorButton, + QtCore.QObject.connect(self.backgroundColorButton, QtCore.SIGNAL(u'pressed()'), self.onBackgroundColorButtonClicked) - QtCore.QObject.connect(self.FontColorButton, + QtCore.QObject.connect(self.fontColorButton, QtCore.SIGNAL(u'pressed()'), self.onFontColorButtonClicked) - QtCore.QObject.connect(self.FontComboBox, + QtCore.QObject.connect(self.fontComboBox, QtCore.SIGNAL(u'activated(int)'), self.onFontComboBoxClicked) - QtCore.QObject.connect(self.TimeoutSpinBox, + QtCore.QObject.connect(self.timeoutSpinBox, QtCore.SIGNAL(u'valueChanged(int)'), self.onTimeoutSpinBoxChanged) - QtCore.QObject.connect(self.FontSizeSpinBox, + QtCore.QObject.connect(self.fontSizeSpinBox, QtCore.SIGNAL(u'valueChanged(int)'), self.onFontSizeSpinBoxChanged) def retranslateUi(self): self.fontGroupBox.setTitle( translate('AlertsPlugin.AlertsTab', 'Font')) - self.FontLabel.setText( + self.fontLabel.setText( translate('AlertsPlugin.AlertsTab', 'Font name:')) - self.FontColorLabel.setText( + self.fontColorLabel.setText( translate('AlertsPlugin.AlertsTab', 'Font color:')) - self.BackgroundColorLabel.setText( + self.backgroundColorLabel.setText( translate('AlertsPlugin.AlertsTab', 'Background color:')) - self.FontSizeLabel.setText( + self.fontSizeLabel.setText( translate('AlertsPlugin.AlertsTab', 'Font size:')) - self.FontSizeSpinBox.setSuffix(UiStrings.FontSizePtUnit) - self.TimeoutLabel.setText( + self.fontSizeSpinBox.setSuffix(UiStrings().FontSizePtUnit) + self.timeoutLabel.setText( translate('AlertsPlugin.AlertsTab', 'Alert timeout:')) - self.TimeoutSpinBox.setSuffix(UiStrings.S) - self.PreviewGroupBox.setTitle(UiStrings.Preview) - self.FontPreview.setText(UiStrings.OLPV2) + self.timeoutSpinBox.setSuffix(UiStrings().Seconds) + self.previewGroupBox.setTitle(UiStrings().Preview) + self.fontPreview.setText(UiStrings().OLPV2) def onBackgroundColorButtonClicked(self): new_color = QtGui.QColorDialog.getColor( QtGui.QColor(self.bg_color), self) if new_color.isValid(): self.bg_color = new_color.name() - self.BackgroundColorButton.setStyleSheet( + self.backgroundColorButton.setStyleSheet( u'background-color: %s' % self.bg_color) self.updateDisplay() @@ -133,15 +135,16 @@ class AlertsTab(SettingsTab): QtGui.QColor(self.font_color), self) if new_color.isValid(): self.font_color = new_color.name() - self.FontColorButton.setStyleSheet( + self.fontColorButton.setStyleSheet( u'background-color: %s' % self.font_color) self.updateDisplay() def onTimeoutSpinBoxChanged(self): - self.timeout = self.TimeoutSpinBox.value() + self.timeout = self.timeoutSpinBox.value() + self.changed = True def onFontSizeSpinBoxChanged(self): - self.font_size = self.FontSizeSpinBox.value() + self.font_size = self.fontSizeSpinBox.value() self.updateDisplay() def load(self): @@ -157,38 +160,48 @@ class AlertsTab(SettingsTab): self.font_face = unicode(settings.value( u'font face', QtCore.QVariant(QtGui.QFont().family())).toString()) self.location = settings.value( - u'location', QtCore.QVariant(1)).toInt()[0] + u'location', QtCore.QVariant(AlertLocation.Bottom)).toInt()[0] settings.endGroup() - self.FontSizeSpinBox.setValue(self.font_size) - self.TimeoutSpinBox.setValue(self.timeout) - self.FontColorButton.setStyleSheet( + self.fontSizeSpinBox.setValue(self.font_size) + self.timeoutSpinBox.setValue(self.timeout) + self.fontColorButton.setStyleSheet( u'background-color: %s' % self.font_color) - self.BackgroundColorButton.setStyleSheet( + self.backgroundColorButton.setStyleSheet( u'background-color: %s' % self.bg_color) self.verticalComboBox.setCurrentIndex(self.location) font = QtGui.QFont() font.setFamily(self.font_face) - self.FontComboBox.setCurrentFont(font) + self.fontComboBox.setCurrentFont(font) self.updateDisplay() + self.changed = False def save(self): settings = QtCore.QSettings() settings.beginGroup(self.settingsSection) + # Check value has changed as no event handles this field + if settings.value(u'location', QtCore.QVariant(1)).toInt()[0] != \ + self.verticalComboBox.currentIndex(): + self.changed = True settings.setValue(u'background color', QtCore.QVariant(self.bg_color)) settings.setValue(u'font color', QtCore.QVariant(self.font_color)) settings.setValue(u'font size', QtCore.QVariant(self.font_size)) - self.font_face = self.FontComboBox.currentFont().family() + self.font_face = self.fontComboBox.currentFont().family() settings.setValue(u'font face', QtCore.QVariant(self.font_face)) settings.setValue(u'timeout', QtCore.QVariant(self.timeout)) self.location = self.verticalComboBox.currentIndex() settings.setValue(u'location', QtCore.QVariant(self.location)) settings.endGroup() + if self.changed: + Receiver.send_message(u'update_display_css') + self.changed = False def updateDisplay(self): font = QtGui.QFont() - font.setFamily(self.FontComboBox.currentFont().family()) + font.setFamily(self.fontComboBox.currentFont().family()) font.setBold(True) font.setPointSize(self.font_size) - self.FontPreview.setFont(font) - self.FontPreview.setStyleSheet(u'background-color: %s; color: %s' % + self.fontPreview.setFont(font) + self.fontPreview.setStyleSheet(u'background-color: %s; color: %s' % (self.bg_color, self.font_color)) + self.changed = True + diff --git a/openlp/plugins/alerts/lib/db.py b/openlp/plugins/alerts/lib/db.py index 9cfa34846..b5c4f8d60 100644 --- a/openlp/plugins/alerts/lib/db.py +++ b/openlp/plugins/alerts/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, Armin Köhler, 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 # diff --git a/openlp/plugins/bibles/__init__.py b/openlp/plugins/bibles/__init__.py index 29364ab1a..d468ae0dc 100644 --- a/openlp/plugins/bibles/__init__.py +++ b/openlp/plugins/bibles/__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, Armin Köhler, 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 # diff --git a/openlp/plugins/bibles/bibleplugin.py b/openlp/plugins/bibles/bibleplugin.py index 73a9b7e1d..619581b17 100644 --- a/openlp/plugins/bibles/bibleplugin.py +++ b/openlp/plugins/bibles/bibleplugin.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, Armin Köhler, 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,7 +30,10 @@ import logging from PyQt4 import QtCore, QtGui from openlp.core.lib import Plugin, StringContent, build_icon, translate +from openlp.core.lib.ui import base_action, UiStrings +from openlp.core.utils.actions import ActionList from openlp.plugins.bibles.lib import BibleManager, BiblesTab, BibleMediaItem +from openlp.plugins.bibles.forms import BibleUpgradeForm log = logging.getLogger(__name__) @@ -37,7 +41,7 @@ class BiblePlugin(Plugin): log.info(u'Bible Plugin loaded') def __init__(self, plugin_helpers): - Plugin.__init__(self, u'Bibles', u'1.9.4', plugin_helpers, + Plugin.__init__(self, u'bibles', plugin_helpers, BibleMediaItem, BiblesTab) self.weight = -9 self.icon_path = u':/plugins/plugin_bibles.png' @@ -50,7 +54,14 @@ class BiblePlugin(Plugin): self.manager = BibleManager(self) Plugin.initialise(self) self.importBibleItem.setVisible(True) - self.exportBibleItem.setVisible(True) + action_list = ActionList.get_instance() + action_list.add_action(self.importBibleItem, UiStrings().Import) + # Do not add the action to the list yet. + #action_list.add_action(self.exportBibleItem, UiStrings().Export) + # Set to invisible until we can export bibles + self.exportBibleItem.setVisible(False) + if len(self.manager.old_bible_databases): + self.toolsUpgradeItem.setVisible(True) def finalise(self): """ @@ -59,34 +70,80 @@ class BiblePlugin(Plugin): log.info(u'Plugin Finalise') self.manager.finalise() Plugin.finalise(self) + action_list = ActionList.get_instance() + action_list.remove_action(self.importBibleItem, UiStrings().Import) self.importBibleItem.setVisible(False) + #action_list.remove_action(self.exportBibleItem, UiStrings().Export) self.exportBibleItem.setVisible(False) + def appStartup(self): + """ + Perform tasks on application starup + """ + if len(self.manager.old_bible_databases): + if QtGui.QMessageBox.information(self.formparent, + translate('OpenLP', 'Information'), translate('OpenLP', + 'Bible format has changed.\nYou have to upgrade your ' + 'existing Bibles.\nShould OpenLP upgrade now?'), + QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Yes | + QtGui.QMessageBox.No)) == QtGui.QMessageBox.Yes: + self.onToolsUpgradeItemTriggered() + def addImportMenuItem(self, import_menu): - self.importBibleItem = QtGui.QAction(import_menu) - self.importBibleItem.setObjectName(u'importBibleItem') + self.importBibleItem = base_action(import_menu, u'importBibleItem') + self.importBibleItem.setText(translate('BiblesPlugin', '&Bible')) import_menu.addAction(self.importBibleItem) - self.importBibleItem.setText( - translate('BiblesPlugin', '&Bible')) # signals and slots QtCore.QObject.connect(self.importBibleItem, QtCore.SIGNAL(u'triggered()'), self.onBibleImportClick) self.importBibleItem.setVisible(False) def addExportMenuItem(self, export_menu): - self.exportBibleItem = QtGui.QAction(export_menu) - self.exportBibleItem.setObjectName(u'exportBibleItem') - export_menu.addAction(self.exportBibleItem) + self.exportBibleItem = base_action(export_menu, u'exportBibleItem') self.exportBibleItem.setText(translate('BiblesPlugin', '&Bible')) + export_menu.addAction(self.exportBibleItem) self.exportBibleItem.setVisible(False) + def addToolsMenuItem(self, tools_menu): + """ + Give the bible plugin the opportunity to add items to the + **Tools** menu. + + ``tools_menu`` + The actual **Tools** menu item, so that your actions can + use it as their parent. + """ + log.debug(u'add tools menu') + self.toolsUpgradeItem = QtGui.QAction(tools_menu) + self.toolsUpgradeItem.setObjectName(u'toolsUpgradeItem') + self.toolsUpgradeItem.setText( + translate('BiblesPlugin', '&Upgrade older Bibles')) + self.toolsUpgradeItem.setStatusTip( + translate('BiblesPlugin', 'Upgrade the Bible databases to the ' + 'latest format.')) + tools_menu.addAction(self.toolsUpgradeItem) + QtCore.QObject.connect(self.toolsUpgradeItem, + QtCore.SIGNAL(u'triggered()'), self.onToolsUpgradeItemTriggered) + self.toolsUpgradeItem.setVisible(False) + + def onToolsUpgradeItemTriggered(self): + """ + Upgrade older bible databases. + """ + if not hasattr(self, u'upgrade_wizard'): + self.upgrade_wizard = BibleUpgradeForm(self.formparent, + self.manager, self) + # If the import was not cancelled then reload. + if self.upgrade_wizard.exec_(): + self.mediaItem.reloadBibles() + def onBibleImportClick(self): if self.mediaItem: self.mediaItem.onImportClick() def about(self): about_text = translate('BiblesPlugin', 'Bible Plugin' - '
The Bible plugin provides the ability to display bible ' + '
The Bible plugin provides the ability to display Bible ' 'verses from different sources during the service.') return about_text @@ -130,13 +187,14 @@ class BiblePlugin(Plugin): # Middle Header Bar tooltips = { u'load': u'', - u'import': translate('BiblesPlugin', 'Import a Bible'), - u'new': translate('BiblesPlugin', 'Add a new Bible'), - u'edit': translate('BiblesPlugin', 'Edit the selected Bible'), - u'delete': translate('BiblesPlugin', 'Delete the selected Bible'), - u'preview': translate('BiblesPlugin', 'Preview the selected Bible'), - u'live': translate('BiblesPlugin', 'Send the selected Bible live'), + u'import': translate('BiblesPlugin', 'Import a Bible.'), + u'new': translate('BiblesPlugin', 'Add a new Bible.'), + u'edit': translate('BiblesPlugin', 'Edit the selected Bible.'), + u'delete': translate('BiblesPlugin', 'Delete the selected Bible.'), + u'preview': translate('BiblesPlugin', + 'Preview the selected Bible.'), + u'live': translate('BiblesPlugin', 'Send the selected Bible live.'), u'service': translate('BiblesPlugin', - 'Add the selected Bible to the service') + 'Add the selected Bible to the service.') } self.setPluginUiTextStrings(tooltips) diff --git a/openlp/plugins/bibles/forms/__init__.py b/openlp/plugins/bibles/forms/__init__.py index 15f14de9e..5b19d9f24 100644 --- a/openlp/plugins/bibles/forms/__init__.py +++ b/openlp/plugins/bibles/forms/__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, Armin Köhler, 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 # @@ -50,7 +51,10 @@ This allows OpenLP to use ``self.object`` for all the GUI elements while keeping them separate from the functionality, so that it is easier to recreate the GUI from the .ui files later if necessary. """ - +from booknameform import BookNameForm +from languageform import LanguageForm from bibleimportform import BibleImportForm +from bibleupgradeform import BibleUpgradeForm -__all__ = ['BibleImportForm'] +__all__ = [u'BookNameForm', u'LanguageForm', u'BibleImportForm', + u'BibleUpgradeForm'] diff --git a/openlp/plugins/bibles/forms/bibleimportform.py b/openlp/plugins/bibles/forms/bibleimportform.py index 7967b2ec4..7577e86a3 100644 --- a/openlp/plugins/bibles/forms/bibleimportform.py +++ b/openlp/plugins/bibles/forms/bibleimportform.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, Armin Köhler, 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 # @@ -26,10 +27,10 @@ """ The bible import functions for OpenLP """ -import csv import logging import os import os.path +import locale from PyQt4 import QtCore, QtGui @@ -37,8 +38,9 @@ from openlp.core.lib import Receiver, translate from openlp.core.lib.db import delete_database from openlp.core.lib.ui import UiStrings, critical_error_message_box from openlp.core.ui.wizard import OpenLPWizard, WizardStrings -from openlp.core.utils import AppLocation, string_is_unicode +from openlp.core.utils import AppLocation from openlp.plugins.bibles.lib.manager import BibleFormat +from openlp.plugins.bibles.lib.db import BiblesResourcesDB, clean_filename log = logging.getLogger(__name__) @@ -85,8 +87,18 @@ class BibleImportForm(OpenLPWizard): """ OpenLPWizard.setupUi(self, image) QtCore.QObject.connect(self.formatComboBox, - QtCore.SIGNAL(u'currentIndexChanged(int)'), self.selectStack, - QtCore.SLOT(u'setCurrentIndex(int)')) + QtCore.SIGNAL(u'currentIndexChanged(int)'), + self.onCurrentIndexChanged) + + def onCurrentIndexChanged(self, index): + """ + Called when the format combo box's index changed. We have to check if + the import is available and accordingly to disable or enable the next + button. + """ + self.selectStack.setCurrentIndex(index) + next_button = self.button(QtGui.QWizard.NextButton) + next_button.setEnabled(BibleFormat.get_availability(index)) def customInit(self): """ @@ -113,9 +125,6 @@ class BibleImportForm(OpenLPWizard): QtCore.QObject.connect(self.osisBrowseButton, QtCore.SIGNAL(u'clicked()'), self.onOsisBrowseButtonClicked) - QtCore.QObject.connect(self.csvTestamentsButton, - QtCore.SIGNAL(u'clicked()'), - self.onCsvTestamentsBrowseButtonClicked) QtCore.QObject.connect(self.csvBooksButton, QtCore.SIGNAL(u'clicked()'), self.onCsvBooksBrowseButtonClicked) @@ -176,18 +185,6 @@ class BibleImportForm(OpenLPWizard): self.csvLayout = QtGui.QFormLayout(self.csvWidget) self.csvLayout.setMargin(0) self.csvLayout.setObjectName(u'CsvLayout') - self.csvTestamentsLabel = QtGui.QLabel(self.csvWidget) - self.csvTestamentsLabel.setObjectName(u'CsvTestamentsLabel') - self.csvTestamentsLayout = QtGui.QHBoxLayout() - self.csvTestamentsLayout.setObjectName(u'CsvTestamentsLayout') - self.csvTestamentsEdit = QtGui.QLineEdit(self.csvWidget) - self.csvTestamentsEdit.setObjectName(u'CsvTestamentsEdit') - self.csvTestamentsLayout.addWidget(self.csvTestamentsEdit) - self.csvTestamentsButton = QtGui.QToolButton(self.csvWidget) - self.csvTestamentsButton.setIcon(self.openIcon) - self.csvTestamentsButton.setObjectName(u'CsvTestamentsButton') - self.csvTestamentsLayout.addWidget(self.csvTestamentsButton) - self.csvLayout.addRow(self.csvTestamentsLabel, self.csvTestamentsLayout) self.csvBooksLabel = QtGui.QLabel(self.csvWidget) self.csvBooksLabel.setObjectName(u'CsvBooksLabel') self.csvBooksLayout = QtGui.QHBoxLayout() @@ -367,13 +364,11 @@ class BibleImportForm(OpenLPWizard): self.formatComboBox.setItemText(BibleFormat.OpenSong, WizardStrings.OS) self.formatComboBox.setItemText(BibleFormat.WebDownload, translate('BiblesPlugin.ImportWizardForm', 'Web Download')) - self.formatComboBox.setItemText(BibleFormat.OpenLP1, UiStrings.OLPV1) + self.formatComboBox.setItemText(BibleFormat.OpenLP1, UiStrings().OLPV1) self.openlp1FileLabel.setText( translate('BiblesPlugin.ImportWizardForm', 'Bible file:')) self.osisFileLabel.setText( translate('BiblesPlugin.ImportWizardForm', 'Bible file:')) - self.csvTestamentsLabel.setText( - translate('BiblesPlugin.ImportWizardForm', 'Testaments file:')) self.csvBooksLabel.setText( translate('BiblesPlugin.ImportWizardForm', 'Books file:')) self.csvVersesLabel.setText( @@ -424,7 +419,6 @@ class BibleImportForm(OpenLPWizard): # Align all QFormLayouts towards each other. labelWidth = max(self.formatLabel.minimumSizeHint().width(), self.osisFileLabel.minimumSizeHint().width(), - self.csvTestamentsLabel.minimumSizeHint().width(), self.csvBooksLabel.minimumSizeHint().width(), self.csvVersesLabel.minimumSizeHint().width(), self.openSongFileLabel.minimumSizeHint().width(), @@ -441,28 +435,20 @@ class BibleImportForm(OpenLPWizard): elif self.currentPage() == self.selectPage: if self.field(u'source_format').toInt()[0] == BibleFormat.OSIS: if not self.field(u'osis_location').toString(): - critical_error_message_box(UiStrings.NFSs, + critical_error_message_box(UiStrings().NFSs, WizardStrings.YouSpecifyFile % WizardStrings.OSIS) self.osisFileEdit.setFocus() return False elif self.field(u'source_format').toInt()[0] == BibleFormat.CSV: - if not self.field(u'csv_testamentsfile').toString(): - answer = critical_error_message_box(UiStrings.NFSs, - translate('BiblesPlugin.ImportWizardForm', - 'You have not specified a testaments file. Do you ' - 'want to proceed with the import?'), question=True) - if answer == QtGui.QMessageBox.No: - self.csvTestamentsEdit.setFocus() - return False if not self.field(u'csv_booksfile').toString(): - critical_error_message_box(UiStrings.NFSs, + critical_error_message_box(UiStrings().NFSs, translate('BiblesPlugin.ImportWizardForm', 'You need to specify a file with books of ' 'the Bible to use in the import.')) self.csvBooksEdit.setFocus() return False elif not self.field(u'csv_versefile').toString(): - critical_error_message_box(UiStrings.NFSs, + critical_error_message_box(UiStrings().NFSs, translate('BiblesPlugin.ImportWizardForm', 'You need to specify a file of Bible ' 'verses to import.')) @@ -471,14 +457,19 @@ class BibleImportForm(OpenLPWizard): elif self.field(u'source_format').toInt()[0] == \ BibleFormat.OpenSong: if not self.field(u'opensong_file').toString(): - critical_error_message_box(UiStrings.NFSs, + critical_error_message_box(UiStrings().NFSs, WizardStrings.YouSpecifyFile % WizardStrings.OS) self.openSongFileEdit.setFocus() return False + elif self.field(u'source_format').toInt()[0] == \ + BibleFormat.WebDownload: + self.versionNameEdit.setText( + self.webTranslationComboBox.currentText()) + return True elif self.field(u'source_format').toInt()[0] == BibleFormat.OpenLP1: if not self.field(u'openlp1_location').toString(): - critical_error_message_box(UiStrings.NFSs, - WizardStrings.YouSpecifyFile % UiStrings.OLPV1) + critical_error_message_box(UiStrings().NFSs, + WizardStrings.YouSpecifyFile % UiStrings().OLPV1) self.openlp1FileEdit.setFocus() return False return True @@ -486,14 +477,15 @@ class BibleImportForm(OpenLPWizard): license_version = unicode(self.field(u'license_version').toString()) license_copyright = \ unicode(self.field(u'license_copyright').toString()) + path = AppLocation.get_section_data_path(u'bibles') if not license_version: - critical_error_message_box(UiStrings.EmptyField, + critical_error_message_box(UiStrings().EmptyField, translate('BiblesPlugin.ImportWizardForm', 'You need to specify a version name for your Bible.')) self.versionNameEdit.setFocus() return False elif not license_copyright: - critical_error_message_box(UiStrings.EmptyField, + critical_error_message_box(UiStrings().EmptyField, translate('BiblesPlugin.ImportWizardForm', 'You need to set a copyright for your Bible. ' 'Bibles in the Public Domain need to be marked as such.')) @@ -507,6 +499,15 @@ class BibleImportForm(OpenLPWizard): 'a different Bible or first delete the existing one.')) self.versionNameEdit.setFocus() return False + elif os.path.exists(os.path.join(path, clean_filename( + license_version))): + critical_error_message_box( + translate('BiblesPlugin.ImportWizardForm', 'Bible Exists'), + translate('BiblesPlugin.ImportWizardForm', + 'This Bible already exists. Please import ' + 'a different Bible or first delete the existing one.')) + self.versionNameEdit.setFocus() + return False return True if self.currentPage() == self.progressPage: return True @@ -521,7 +522,7 @@ class BibleImportForm(OpenLPWizard): """ self.webTranslationComboBox.clear() bibles = self.web_bible_list[index].keys() - bibles.sort() + bibles.sort(cmp=locale.strcoll) self.webTranslationComboBox.addItems(bibles) def onOsisBrowseButtonClicked(self): @@ -531,14 +532,6 @@ class BibleImportForm(OpenLPWizard): self.getFileName(WizardStrings.OpenTypeFile % WizardStrings.OSIS, self.osisFileEdit) - def onCsvTestamentsBrowseButtonClicked(self): - """ - Show the file open dialog for the testaments CSV file. - """ - self.getFileName(WizardStrings.OpenTypeFile % WizardStrings.CSV, - self.csvTestamentsEdit, u'%s (*.csv)' - % translate('BiblesPlugin.ImportWizardForm', 'CSV File')) - def onCsvBooksBrowseButtonClicked(self): """ Show the file open dialog for the books CSV file. @@ -566,7 +559,7 @@ class BibleImportForm(OpenLPWizard): """ Show the file open dialog for the openlp.org 1.x file. """ - self.getFileName(WizardStrings.OpenTypeFile % UiStrings.OLPV1, + self.getFileName(WizardStrings.OpenTypeFile % UiStrings().OLPV1, self.openlp1FileEdit, u'%s (*.bible)' % translate('BiblesPlugin.ImportWizardForm', 'openlp.org 1.x Bible Files')) @@ -577,8 +570,6 @@ class BibleImportForm(OpenLPWizard): """ self.selectPage.registerField(u'source_format', self.formatComboBox) self.selectPage.registerField(u'osis_location', self.osisFileEdit) - self.selectPage.registerField( - u'csv_testamentsfile', self.csvTestamentsEdit) self.selectPage.registerField(u'csv_booksfile', self.csvBooksEdit) self.selectPage.registerField(u'csv_versefile', self.csvVersesEdit) self.selectPage.registerField(u'opensong_file', self.openSongFileEdit) @@ -607,7 +598,6 @@ class BibleImportForm(OpenLPWizard): self.cancelButton.setVisible(True) self.setField(u'source_format', QtCore.QVariant(0)) self.setField(u'osis_location', QtCore.QVariant('')) - self.setField(u'csv_testamentsfile', QtCore.QVariant('')) self.setField(u'csv_booksfile', QtCore.QVariant('')) self.setField(u'csv_versefile', QtCore.QVariant('')) self.setField(u'opensong_file', QtCore.QVariant('')) @@ -634,46 +624,27 @@ class BibleImportForm(OpenLPWizard): """ Load the lists of Crosswalk, BibleGateway and Bibleserver bibles. """ - filepath = AppLocation.get_directory(AppLocation.PluginsDir) - filepath = os.path.join(filepath, u'bibles', u'resources') # Load Crosswalk Bibles. - self.loadBibleResourceFile( - os.path.join(filepath, u'crosswalkbooks.csv'), - WebDownload.Crosswalk) + self.loadBibleResource(WebDownload.Crosswalk) # Load BibleGateway Bibles. - self.loadBibleResourceFile(os.path.join(filepath, u'biblegateway.csv'), - WebDownload.BibleGateway) + self.loadBibleResource(WebDownload.BibleGateway) # Load and Bibleserver Bibles. - self.loadBibleResourceFile(os.path.join(filepath, u'bibleserver.csv'), - WebDownload.Bibleserver) + self.loadBibleResource(WebDownload.Bibleserver) - def loadBibleResourceFile(self, file_path_name, download_type): + def loadBibleResource(self, download_type): """ - Loads a web bible resource file. - - ``file_path_name`` - The file to load including the file's path. + Loads a web bible from bible_resources.sqlite. ``download_type`` - The WebDownload type this file is for. + The WebDownload type e.g. bibleserver. """ self.web_bible_list[download_type] = {} - books_file = None - try: - books_file = open(file_path_name, 'rb') - dialect = csv.Sniffer().sniff(books_file.read(1024)) - books_file.seek(0) - books_reader = csv.reader(books_file, dialect) - for line in books_reader: - ver = string_is_unicode(line[0]) - name = string_is_unicode(line[1]) - self.web_bible_list[download_type][ver] = name.strip() - except IOError: - log.exception(u'%s resources missing' % - WebDownload.Names[download_type]) - finally: - if books_file: - books_file.close() + bibles = BiblesResourcesDB.get_webbibles( + WebDownload.Names[download_type]) + for bible in bibles: + version = bible[u'name'] + name = bible[u'abbreviation'] + self.web_bible_list[download_type][version] = name.strip() def preWizard(self): """ @@ -684,7 +655,7 @@ class BibleImportForm(OpenLPWizard): if bible_type == BibleFormat.WebDownload: self.progressLabel.setText(translate( 'BiblesPlugin.ImportWizardForm', - 'Starting Registering bible...')) + 'Registering Bible...')) else: self.progressLabel.setText(WizardStrings.StartingImport) Receiver.send_message(u'openlp_process_events') @@ -708,8 +679,7 @@ class BibleImportForm(OpenLPWizard): elif bible_type == BibleFormat.CSV: # Import a CSV bible. importer = self.manager.import_bible(BibleFormat.CSV, - name=license_version, testamentsfile=unicode( - self.field(u'csv_testamentsfile').toString()), + name=license_version, booksfile=unicode(self.field(u'csv_booksfile').toString()), versefile=unicode(self.field(u'csv_versefile').toString()) ) @@ -740,14 +710,14 @@ class BibleImportForm(OpenLPWizard): name=license_version, filename=unicode(self.field(u'openlp1_location').toString()) ) - if importer.do_import(): + if importer.do_import(license_version): self.manager.save_meta_data(license_version, license_version, license_copyright, license_permissions) self.manager.reload_bibles() if bible_type == BibleFormat.WebDownload: self.progressLabel.setText( translate('BiblesPlugin.ImportWizardForm', 'Registered ' - 'bible. Please note, that verses will be downloaded on\n' + 'Bible. Please note, that verses will be downloaded on\n' 'demand and thus an internet connection is required.')) else: self.progressLabel.setText(WizardStrings.FinishedImport) diff --git a/openlp/plugins/bibles/forms/bibleupgradeform.py b/openlp/plugins/bibles/forms/bibleupgradeform.py new file mode 100644 index 000000000..322e3219a --- /dev/null +++ b/openlp/plugins/bibles/forms/bibleupgradeform.py @@ -0,0 +1,628 @@ +# -*- 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, Matthias Hub, Meinert Jordan, Armin Köhler, # +# Andreas Preikschat, Mattias Põldaru, Christian Richter, Philip Ridout, # +# 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 # +############################################################################### +""" +The bible import functions for OpenLP +""" +import logging +import os +import shutil +from tempfile import gettempdir + +from PyQt4 import QtCore, QtGui + +from openlp.core.lib import Receiver, SettingsManager, translate, \ + check_directory_exists +from openlp.core.lib.ui import UiStrings, critical_error_message_box +from openlp.core.ui.wizard import OpenLPWizard, WizardStrings +from openlp.core.utils import AppLocation, delete_file +from openlp.plugins.bibles.lib.db import BibleDB, BibleMeta, OldBibleDB, \ + BiblesResourcesDB +from openlp.plugins.bibles.lib.http import BSExtract, BGExtract, CWExtract + +log = logging.getLogger(__name__) + + +class BibleUpgradeForm(OpenLPWizard): + """ + This is the Bible Upgrade Wizard, which allows easy importing of Bibles + into OpenLP from older OpenLP2 database versions. + """ + log.info(u'BibleUpgradeForm loaded') + + def __init__(self, parent, manager, bibleplugin): + """ + Instantiate the wizard, and run any extra setup we need to. + + ``parent`` + The QWidget-derived parent of the wizard. + + ``manager`` + The Bible manager. + + ``bibleplugin`` + The Bible plugin. + """ + self.manager = manager + self.mediaItem = bibleplugin.mediaItem + self.suffix = u'.sqlite' + self.settingsSection = u'bibles' + self.path = AppLocation.get_section_data_path(self.settingsSection) + self.temp_dir = os.path.join(gettempdir(), u'openlp') + self.files = self.manager.old_bible_databases + self.success = {} + self.newbibles = {} + OpenLPWizard.__init__(self, parent, bibleplugin, u'bibleUpgradeWizard', + u':/wizards/wizard_importbible.bmp') + + def setupUi(self, image): + """ + Set up the UI for the bible wizard. + """ + OpenLPWizard.setupUi(self, image) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'openlp_stop_wizard'), self.stop_import) + + def stop_import(self): + """ + Stops the import of the Bible. + """ + log.debug(u'Stopping import') + self.stop_import_flag = True + + def reject(self): + """ + Stop the wizard on cancel button, close button or ESC key. + """ + log.debug(u'Wizard cancelled by user') + self.stop_import_flag = True + if not self.currentPage() == self.progressPage: + self.done(QtGui.QDialog.Rejected) + + def onCurrentIdChanged(self, pageId): + """ + Perform necessary functions depending on which wizard page is active. + """ + if self.page(pageId) == self.progressPage: + self.preWizard() + self.performWizard() + self.postWizard() + elif self.page(pageId) == self.selectPage and not self.files: + self.next() + + def onBackupBrowseButtonClicked(self): + """ + Show the file open dialog for the OSIS file. + """ + filename = QtGui.QFileDialog.getExistingDirectory(self, translate( + 'BiblesPlugin.UpgradeWizardForm', 'Select a Backup Directory'), + os.path.dirname(SettingsManager.get_last_dir( + self.plugin.settingsSection, 1))) + if filename: + self.backupDirectoryEdit.setText(filename) + SettingsManager.set_last_dir(self.plugin.settingsSection, + filename, 1) + + def onNoBackupCheckBoxToggled(self, checked): + """ + Enable or disable the backup directory widgets. + """ + self.backupDirectoryEdit.setEnabled(not checked) + self.backupBrowseButton.setEnabled(not checked) + + def backupOldBibles(self, backup_directory): + """ + Backup old bible databases in a given folder. + """ + check_directory_exists(backup_directory) + success = True + for filename in self.files: + try: + shutil.copy(os.path.join(self.path, filename[0]), + backup_directory) + except: + success = False + return success + + def customInit(self): + """ + Perform any custom initialisation for bible upgrading. + """ + self.manager.set_process_dialog(self) + self.restart() + + def customSignals(self): + """ + Set up the signals used in the bible importer. + """ + QtCore.QObject.connect(self.backupBrowseButton, + QtCore.SIGNAL(u'clicked()'), self.onBackupBrowseButtonClicked) + QtCore.QObject.connect(self.noBackupCheckBox, + QtCore.SIGNAL(u'toggled(bool)'), self.onNoBackupCheckBoxToggled) + + def addCustomPages(self): + """ + Add the bible import specific wizard pages. + """ + # Backup Page + self.backupPage = QtGui.QWizardPage() + self.backupPage.setObjectName(u'BackupPage') + self.backupLayout = QtGui.QVBoxLayout(self.backupPage) + self.backupLayout.setObjectName(u'BackupLayout') + self.backupInfoLabel = QtGui.QLabel(self.backupPage) + self.backupInfoLabel.setOpenExternalLinks(True) + self.backupInfoLabel.setTextFormat(QtCore.Qt.RichText) + self.backupInfoLabel.setWordWrap(True) + self.backupInfoLabel.setObjectName(u'backupInfoLabel') + self.backupLayout.addWidget(self.backupInfoLabel) + self.selectLabel = QtGui.QLabel(self.backupPage) + self.selectLabel.setObjectName(u'selectLabel') + self.backupLayout.addWidget(self.selectLabel) + self.formLayout = QtGui.QFormLayout() + self.formLayout.setMargin(0) + self.formLayout.setObjectName(u'FormLayout') + self.backupDirectoryLabel = QtGui.QLabel(self.backupPage) + self.backupDirectoryLabel.setObjectName(u'backupDirectoryLabel') + self.backupDirectoryLayout = QtGui.QHBoxLayout() + self.backupDirectoryLayout.setObjectName(u'BackupDirectoryLayout') + self.backupDirectoryEdit = QtGui.QLineEdit(self.backupPage) + self.backupDirectoryEdit.setObjectName(u'BackupFolderEdit') + self.backupDirectoryLayout.addWidget(self.backupDirectoryEdit) + self.backupBrowseButton = QtGui.QToolButton(self.backupPage) + self.backupBrowseButton.setIcon(self.openIcon) + self.backupBrowseButton.setObjectName(u'BackupBrowseButton') + self.backupDirectoryLayout.addWidget(self.backupBrowseButton) + self.formLayout.addRow(self.backupDirectoryLabel, + self.backupDirectoryLayout) + self.backupLayout.addLayout(self.formLayout) + self.noBackupCheckBox = QtGui.QCheckBox(self.backupPage) + self.noBackupCheckBox.setObjectName('NoBackupCheckBox') + self.backupLayout.addWidget(self.noBackupCheckBox) + self.spacer = QtGui.QSpacerItem(10, 0, QtGui.QSizePolicy.Fixed, + QtGui.QSizePolicy.Minimum) + self.backupLayout.addItem(self.spacer) + self.addPage(self.backupPage) + # Select Page + self.selectPage = QtGui.QWizardPage() + self.selectPage.setObjectName(u'SelectPage') + self.pageLayout = QtGui.QVBoxLayout(self.selectPage) + self.pageLayout.setObjectName(u'pageLayout') + self.scrollArea = QtGui.QScrollArea(self.selectPage) + self.scrollArea.setWidgetResizable(True) + self.scrollArea.setObjectName(u'scrollArea') + self.scrollArea.setHorizontalScrollBarPolicy( + QtCore.Qt.ScrollBarAlwaysOff) + self.scrollAreaContents = QtGui.QWidget(self.scrollArea) + self.scrollAreaContents.setObjectName(u'scrollAreaContents') + self.formLayout = QtGui.QVBoxLayout(self.scrollAreaContents) + self.formLayout.setSpacing(2) + self.formLayout.setObjectName(u'formLayout') + self.addScrollArea() + self.pageLayout.addWidget(self.scrollArea) + self.addPage(self.selectPage) + + def addScrollArea(self): + """ + Add the content to the scrollArea. + """ + self.checkBox = {} + for number, filename in enumerate(self.files): + bible = OldBibleDB(self.mediaItem, path=self.path, file=filename[0]) + self.checkBox[number] = QtGui.QCheckBox(self.scrollAreaContents) + self.checkBox[number].setObjectName(u'checkBox[%d]' % number) + self.checkBox[number].setText(bible.get_name()) + self.checkBox[number].setCheckState(QtCore.Qt.Checked) + self.formLayout.addWidget(self.checkBox[number]) + self.spacerItem = QtGui.QSpacerItem(20, 5, QtGui.QSizePolicy.Minimum, + QtGui.QSizePolicy.Expanding) + self.formLayout.addItem(self.spacerItem) + self.scrollArea.setWidget(self.scrollAreaContents) + + def clearScrollArea(self): + """ + Remove the content from the scrollArea. + """ + for number, filename in enumerate(self.files): + self.formLayout.removeWidget(self.checkBox[number]) + self.checkBox[number].setParent(None) + self.formLayout.removeItem(self.spacerItem) + + def retranslateUi(self): + """ + Allow for localisation of the bible import wizard. + """ + self.setWindowTitle(translate('BiblesPlugin.UpgradeWizardForm', + 'Bible Upgrade Wizard')) + self.titleLabel.setText(WizardStrings.HeaderStyle % + translate('OpenLP.Ui', 'Welcome to the Bible Upgrade Wizard')) + self.informationLabel.setText( + translate('BiblesPlugin.UpgradeWizardForm', + 'This wizard will help you to upgrade your existing Bibles from a ' + 'prior version of OpenLP 2. Click the next button below to start ' + 'the upgrade process.')) + self.backupPage.setTitle( + translate('BiblesPlugin.UpgradeWizardForm', + 'Select Backup Directory')) + self.backupPage.setSubTitle( + translate('BiblesPlugin.UpgradeWizardForm', + 'Please select a backup directory for your Bibles')) + self.backupInfoLabel.setText(translate('BiblesPlugin.UpgradeWizardForm', + 'Previous releases of OpenLP 2.0 are unable to use upgraded Bibles.' + ' This will create a backup of your current Bibles so that you can ' + 'simply copy the files back to your OpenLP data directory if you ' + 'need to revert to a previous release of OpenLP. Instructions on ' + 'how to restore the files can be found in our Frequently Asked Questions.')) + self.selectLabel.setText(translate('BiblesPlugin.UpgradeWizardForm', + 'Please select a backup location for your Bibles.')) + self.backupDirectoryLabel.setText( + translate('BiblesPlugin.UpgradeWizardForm', 'Backup Directory:')) + self.noBackupCheckBox.setText( + translate('BiblesPlugin.UpgradeWizardForm', + 'There is no need to backup my Bibles')) + self.selectPage.setTitle( + translate('BiblesPlugin.UpgradeWizardForm', + 'Select Bibles')) + self.selectPage.setSubTitle( + translate('BiblesPlugin.UpgradeWizardForm', + 'Please select the Bibles to upgrade')) + self.progressPage.setTitle(translate('BiblesPlugin.UpgradeWizardForm', + 'Upgrading')) + self.progressPage.setSubTitle( + translate('BiblesPlugin.UpgradeWizardForm', + 'Please wait while your Bibles are upgraded.')) + self.progressLabel.setText(WizardStrings.Ready) + self.progressBar.setFormat(u'%p%') + + def validateCurrentPage(self): + """ + Validate the current page before moving on to the next page. + """ + if self.currentPage() == self.welcomePage: + return True + elif self.currentPage() == self.backupPage: + if not self.noBackupCheckBox.checkState() == QtCore.Qt.Checked: + backup_path = unicode(self.backupDirectoryEdit.text()) + if not backup_path: + critical_error_message_box(UiStrings().EmptyField, + translate('BiblesPlugin.UpgradeWizardForm', + 'You need to specify a backup directory for your ' + 'Bibles.')) + self.backupDirectoryEdit.setFocus() + return False + else: + if not self.backupOldBibles(backup_path): + critical_error_message_box(UiStrings().Error, + translate('BiblesPlugin.UpgradeWizardForm', + 'The backup was not successful.\nTo backup your ' + 'Bibles you need permission to write to the given ' + 'directory.')) + return False + return True + elif self.currentPage() == self.selectPage: + check_directory_exists(self.temp_dir) + for number, filename in enumerate(self.files): + if not self.checkBox[number].checkState() == QtCore.Qt.Checked: + continue + # Move bibles to temp dir. + if not os.path.exists(os.path.join(self.temp_dir, filename[0])): + shutil.move( + os.path.join(self.path, filename[0]), self.temp_dir) + else: + delete_file(os.path.join(self.path, filename[0])) + return True + if self.currentPage() == self.progressPage: + return True + + def setDefaults(self): + """ + Set default values for the wizard pages. + """ + log.debug(u'BibleUpgrade setDefaults') + settings = QtCore.QSettings() + settings.beginGroup(self.plugin.settingsSection) + self.stop_import_flag = False + self.success.clear() + self.newbibles.clear() + self.clearScrollArea() + self.files = self.manager.old_bible_databases + self.addScrollArea() + self.retranslateUi() + for number, filename in enumerate(self.files): + self.checkBox[number].setCheckState(QtCore.Qt.Checked) + self.progressBar.show() + self.restart() + self.finishButton.setVisible(False) + self.cancelButton.setVisible(True) + settings.endGroup() + + def preWizard(self): + """ + Prepare the UI for the upgrade. + """ + OpenLPWizard.preWizard(self) + self.progressLabel.setText( + translate('BiblesPlugin.UpgradeWizardForm', 'Starting upgrade...')) + Receiver.send_message(u'openlp_process_events') + + def performWizard(self): + """ + Perform the actual upgrade. + """ + self.include_webbible = False + proxy_server = None + if not self.files: + self.progressLabel.setText( + translate('BiblesPlugin.UpgradeWizardForm', 'There are no ' + 'Bibles that need to be upgraded.')) + self.progressBar.hide() + return + max_bibles = 0 + for number, file in enumerate(self.files): + if self.checkBox[number].checkState() == QtCore.Qt.Checked: + max_bibles += 1 + oldBible = None + for number, filename in enumerate(self.files): + # Close the previous bible's connection. + if oldBible is not None: + oldBible.close_connection() + # Set to None to make obvious that we have already closed the + # database. + oldBible = None + if self.stop_import_flag: + self.success[number] = False + break + if not self.checkBox[number].checkState() == QtCore.Qt.Checked: + self.success[number] = False + continue + self.progressBar.reset() + oldBible = OldBibleDB(self.mediaItem, path=self.temp_dir, + file=filename[0]) + name = filename[1] + self.progressLabel.setText(unicode(translate( + 'BiblesPlugin.UpgradeWizardForm', + 'Upgrading Bible %s of %s: "%s"\nUpgrading ...')) % + (number + 1, max_bibles, name)) + self.newbibles[number] = BibleDB(self.mediaItem, path=self.path, + name=name, file=filename[0]) + self.newbibles[number].register(self.plugin.upgrade_wizard) + metadata = oldBible.get_metadata() + webbible = False + meta_data = {} + for meta in metadata: + meta_data[meta[u'key']] = meta[u'value'] + if not meta[u'key'] == u'Version' and not meta[u'key'] == \ + u'dbversion': + self.newbibles[number].create_meta(meta[u'key'], + meta[u'value']) + if meta[u'key'] == u'download source': + webbible = True + self.include_webbible = True + if meta.has_key(u'proxy server'): + proxy_server = meta[u'proxy server'] + if webbible: + if meta_data[u'download source'].lower() == u'crosswalk': + handler = CWExtract(proxy_server) + elif meta_data[u'download source'].lower() == u'biblegateway': + handler = BGExtract(proxy_server) + elif meta_data[u'download source'].lower() == u'bibleserver': + handler = BSExtract(proxy_server) + books = handler.get_books_from_http(meta_data[u'download name']) + if not books: + log.error(u'Upgrading books from %s - download '\ + u'name: "%s" failed' % ( + meta_data[u'download source'], + meta_data[u'download name'])) + self.newbibles[number].session.close() + del self.newbibles[number] + critical_error_message_box( + translate('BiblesPlugin.UpgradeWizardForm', + 'Download Error'), + translate('BiblesPlugin.UpgradeWizardForm', + 'To upgrade your Web Bibles an Internet connection is ' + 'required.')) + self.incrementProgressBar(unicode(translate( + 'BiblesPlugin.UpgradeWizardForm', + 'Upgrading Bible %s of %s: "%s"\nFailed')) % + (number + 1, max_bibles, name), + self.progressBar.maximum() - self.progressBar.value()) + self.success[number] = False + continue + bible = BiblesResourcesDB.get_webbible( + meta_data[u'download name'], + meta_data[u'download source'].lower()) + if bible and bible[u'language_id']: + language_id = bible[u'language_id'] + self.newbibles[number].create_meta(u'language_id', + language_id) + else: + language_id = self.newbibles[number].get_language(name) + if not language_id: + log.warn(u'Upgrading from "%s" failed' % filename[0]) + self.newbibles[number].session.close() + del self.newbibles[number] + self.incrementProgressBar(unicode(translate( + 'BiblesPlugin.UpgradeWizardForm', + 'Upgrading Bible %s of %s: "%s"\nFailed')) % + (number + 1, max_bibles, name), + self.progressBar.maximum() - self.progressBar.value()) + self.success[number] = False + continue + self.progressBar.setMaximum(len(books)) + for book in books: + if self.stop_import_flag: + self.success[number] = False + break + self.incrementProgressBar(unicode(translate( + 'BiblesPlugin.UpgradeWizardForm', + 'Upgrading Bible %s of %s: "%s"\n' + 'Upgrading %s ...')) % + (number + 1, max_bibles, name, book)) + book_ref_id = self.newbibles[number].\ + get_book_ref_id_by_name(book, len(books), language_id) + if not book_ref_id: + log.warn(u'Upgrading books from %s - download '\ + u'name: "%s" aborted by user' % ( + meta_data[u'download source'], + meta_data[u'download name'])) + self.newbibles[number].session.close() + del self.newbibles[number] + self.success[number] = False + break + book_details = BiblesResourcesDB.get_book_by_id(book_ref_id) + db_book = self.newbibles[number].create_book(book, + book_ref_id, book_details[u'testament_id']) + # Try to import already downloaded verses. + oldbook = oldBible.get_book(book) + if oldbook: + verses = oldBible.get_verses(oldbook[u'id']) + if not verses: + log.warn(u'No verses found to import for book ' + u'"%s"', book) + continue + for verse in verses: + if self.stop_import_flag: + self.success[number] = False + break + self.newbibles[number].create_verse(db_book.id, + int(verse[u'chapter']), + int(verse[u'verse']), unicode(verse[u'text'])) + Receiver.send_message(u'openlp_process_events') + self.newbibles[number].session.commit() + else: + language_id = self.newbibles[number].get_object(BibleMeta, + u'language_id') + if not language_id: + language_id = self.newbibles[number].get_language(name) + if not language_id: + log.warn(u'Upgrading books from "%s" failed' % name) + self.newbibles[number].session.close() + del self.newbibles[number] + self.incrementProgressBar(unicode(translate( + 'BiblesPlugin.UpgradeWizardForm', + 'Upgrading Bible %s of %s: "%s"\nFailed')) % + (number + 1, max_bibles, name), + self.progressBar.maximum() - self.progressBar.value()) + self.success[number] = False + continue + books = oldBible.get_books() + self.progressBar.setMaximum(len(books)) + for book in books: + if self.stop_import_flag: + self.success[number] = False + break + self.incrementProgressBar(unicode(translate( + 'BiblesPlugin.UpgradeWizardForm', + 'Upgrading Bible %s of %s: "%s"\n' + 'Upgrading %s ...')) % + (number + 1, max_bibles, name, book[u'name'])) + book_ref_id = self.newbibles[number].\ + get_book_ref_id_by_name(book[u'name'], len(books), + language_id) + if not book_ref_id: + log.warn(u'Upgrading books from %s " '\ + 'failed - aborted by user' % name) + self.newbibles[number].session.close() + del self.newbibles[number] + self.success[number] = False + break + book_details = BiblesResourcesDB.get_book_by_id(book_ref_id) + db_book = self.newbibles[number].create_book(book[u'name'], + book_ref_id, book_details[u'testament_id']) + verses = oldBible.get_verses(book[u'id']) + if not verses: + log.warn(u'No verses found to import for book ' + u'"%s"', book[u'name']) + self.newbibles[number].delete_book(db_book) + continue + for verse in verses: + if self.stop_import_flag: + self.success[number] = False + break + self.newbibles[number].create_verse(db_book.id, + int(verse[u'chapter']), + int(verse[u'verse']), unicode(verse[u'text'])) + Receiver.send_message(u'openlp_process_events') + self.newbibles[number].session.commit() + if self.success.has_key(number) and not self.success[number]: + self.incrementProgressBar(unicode(translate( + 'BiblesPlugin.UpgradeWizardForm', + 'Upgrading Bible %s of %s: "%s"\nFailed')) % + (number + 1, max_bibles, name), + self.progressBar.maximum() - self.progressBar.value()) + else: + self.success[number] = True + self.newbibles[number].create_meta(u'Version', name) + self.incrementProgressBar(unicode(translate( + 'BiblesPlugin.UpgradeWizardForm', + 'Upgrading Bible %s of %s: "%s"\n' + 'Complete')) % + (number + 1, max_bibles, name)) + if self.newbibles.has_key(number): + self.newbibles[number].session.close() + # Close the last bible's connection if possible. + if oldBible is not None: + oldBible.close_connection() + + def postWizard(self): + """ + Clean up the UI after the import has finished. + """ + successful_import = 0 + failed_import = 0 + for number, filename in enumerate(self.files): + if self.success.has_key(number) and self.success[number]: + successful_import += 1 + elif self.checkBox[number].checkState() == QtCore.Qt.Checked: + failed_import += 1 + # Delete upgraded (but not complete, corrupted, ...) bible. + delete_file(os.path.join(self.path, filename[0])) + # Copy not upgraded bible back. + shutil.move(os.path.join(self.temp_dir, filename[0]), self.path) + if failed_import > 0: + failed_import_text = unicode(translate( + 'BiblesPlugin.UpgradeWizardForm', + ', %s failed')) % failed_import + else: + failed_import_text = u'' + if successful_import > 0: + if self.include_webbible: + self.progressLabel.setText(unicode( + translate('BiblesPlugin.UpgradeWizardForm', 'Upgrading ' + 'Bible(s): %s successful%s\nPlease note that verses from ' + 'Web Bibles will be downloaded on demand and so an ' + 'Internet connection is required.')) % + (successful_import, failed_import_text)) + else: + self.progressLabel.setText(unicode( + translate('BiblesPlugin.UpgradeWizardForm', 'Upgrading ' + 'Bible(s): %s successful%s')) % (successful_import, + failed_import_text)) + else: + self.progressLabel.setText(translate( + 'BiblesPlugin.UpgradeWizardForm', 'Upgrade failed.')) + # Remove temp directory. + shutil.rmtree(self.temp_dir, True) + OpenLPWizard.postWizard(self) diff --git a/openlp/plugins/bibles/forms/booknamedialog.py b/openlp/plugins/bibles/forms/booknamedialog.py new file mode 100644 index 000000000..e9211b3d5 --- /dev/null +++ b/openlp/plugins/bibles/forms/booknamedialog.py @@ -0,0 +1,114 @@ +# -*- 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, Armin Köhler, Andreas Preikschat, # +# Christian Richter, Philip Ridout, Maikel Stuivenberg, Martin Thompson, Jon # +# Tibble, Carsten Tinggaard, Frode Woldsund # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### + +from PyQt4 import QtCore, QtGui + +from openlp.core.lib import translate + +class Ui_BookNameDialog(object): + def setupUi(self, bookNameDialog): + bookNameDialog.setObjectName(u'bookNameDialog') + bookNameDialog.resize(400, 271) + self.bookNameLayout = QtGui.QVBoxLayout(bookNameDialog) + self.bookNameLayout.setSpacing(8) + self.bookNameLayout.setMargin(8) + self.bookNameLayout.setObjectName(u'bookNameLayout') + self.infoLabel = QtGui.QLabel(bookNameDialog) + self.infoLabel.setWordWrap(True) + self.infoLabel.setObjectName(u'infoLabel') + self.bookNameLayout.addWidget(self.infoLabel) + self.correspondingLayout = QtGui.QGridLayout() + self.correspondingLayout.setColumnStretch(1, 1) + self.correspondingLayout.setSpacing(8) + self.correspondingLayout.setObjectName(u'correspondingLayout') + self.currentLabel = QtGui.QLabel(bookNameDialog) + self.currentLabel.setObjectName(u'currentLabel') + self.correspondingLayout.addWidget(self.currentLabel, 0, 0, 1, 1) + self.currentBookLabel = QtGui.QLabel(bookNameDialog) + self.currentBookLabel.setObjectName(u'currentBookLabel') + self.correspondingLayout.addWidget(self.currentBookLabel, 0, 1, 1, 1) + self.correspondingLabel = QtGui.QLabel(bookNameDialog) + self.correspondingLabel.setObjectName(u'correspondingLabel') + self.correspondingLayout.addWidget( + self.correspondingLabel, 1, 0, 1, 1) + self.correspondingComboBox = QtGui.QComboBox(bookNameDialog) + self.correspondingComboBox.setObjectName(u'correspondingComboBox') + self.correspondingLayout.addWidget( + self.correspondingComboBox, 1, 1, 1, 1) + self.bookNameLayout.addLayout(self.correspondingLayout) + self.optionsGroupBox = QtGui.QGroupBox(bookNameDialog) + self.optionsGroupBox.setObjectName(u'optionsGroupBox') + self.optionsLayout = QtGui.QVBoxLayout(self.optionsGroupBox) + self.optionsLayout.setSpacing(8) + self.optionsLayout.setMargin(8) + self.optionsLayout.setObjectName(u'optionsLayout') + self.oldTestamentCheckBox = QtGui.QCheckBox(self.optionsGroupBox) + self.oldTestamentCheckBox.setObjectName(u'oldTestamentCheckBox') + self.oldTestamentCheckBox.setCheckState(QtCore.Qt.Checked) + self.optionsLayout.addWidget(self.oldTestamentCheckBox) + self.newTestamentCheckBox = QtGui.QCheckBox(self.optionsGroupBox) + self.newTestamentCheckBox.setObjectName(u'newTestamentCheckBox') + self.newTestamentCheckBox.setCheckState(QtCore.Qt.Checked) + self.optionsLayout.addWidget(self.newTestamentCheckBox) + self.apocryphaCheckBox = QtGui.QCheckBox(self.optionsGroupBox) + self.apocryphaCheckBox.setObjectName(u'apocryphaCheckBox') + self.apocryphaCheckBox.setCheckState(QtCore.Qt.Checked) + self.optionsLayout.addWidget(self.apocryphaCheckBox) + self.bookNameLayout.addWidget(self.optionsGroupBox) + self.buttonBox = QtGui.QDialogButtonBox(bookNameDialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons( + QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok) + self.buttonBox.setObjectName(u'buttonBox') + self.bookNameLayout.addWidget(self.buttonBox) + + self.retranslateUi(bookNameDialog) + QtCore.QObject.connect( + self.buttonBox, QtCore.SIGNAL(u'accepted()'), + bookNameDialog.accept) + QtCore.QObject.connect( + self.buttonBox, QtCore.SIGNAL(u'rejected()'), + bookNameDialog.reject) + QtCore.QMetaObject.connectSlotsByName(bookNameDialog) + + def retranslateUi(self, bookNameDialog): + bookNameDialog.setWindowTitle(translate('BiblesPlugin.BookNameDialog', + 'Select Book Name')) + self.infoLabel.setText(translate('BiblesPlugin.BookNameDialog', + 'The following book name cannot be matched up internally. Please ' + 'select the corresponding English name from the list.')) + self.currentLabel.setText(translate('BiblesPlugin.BookNameDialog', + 'Current name:')) + self.correspondingLabel.setText(translate( + 'BiblesPlugin.BookNameDialog', 'Corresponding name:')) + self.optionsGroupBox.setTitle(translate('BiblesPlugin.BookNameDialog', + 'Show Books From')) + self.oldTestamentCheckBox.setText(translate( + 'BiblesPlugin.BookNameDialog', 'Old Testament')) + self.newTestamentCheckBox.setText(translate( + 'BiblesPlugin.BookNameDialog', 'New Testament')) + self.apocryphaCheckBox.setText(translate('BiblesPlugin.BookNameDialog', + 'Apocrypha')) diff --git a/openlp/plugins/bibles/forms/booknameform.py b/openlp/plugins/bibles/forms/booknameform.py new file mode 100644 index 000000000..b07f28bf1 --- /dev/null +++ b/openlp/plugins/bibles/forms/booknameform.py @@ -0,0 +1,123 @@ +# -*- 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, Armin Köhler, 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 # +############################################################################### + +""" +Module implementing BookNameForm. +""" +import logging + +from PyQt4.QtGui import QDialog +from PyQt4 import QtCore + +from openlp.core.lib import translate +from openlp.core.lib.ui import critical_error_message_box +from openlp.plugins.bibles.forms.booknamedialog import \ + Ui_BookNameDialog +from openlp.plugins.bibles.lib.db import BiblesResourcesDB + +log = logging.getLogger(__name__) + +class BookNameForm(QDialog, Ui_BookNameDialog): + """ + Class to manage a dialog which help the user to refer a book name a + to a english book name + """ + log.info(u'BookNameForm loaded') + + def __init__(self, parent = None): + """ + Constructor + """ + QDialog.__init__(self, parent) + self.setupUi(self) + self.customSignals() + + def customSignals(self): + """ + Set up the signals used in the booknameform. + """ + QtCore.QObject.connect(self.oldTestamentCheckBox, + QtCore.SIGNAL(u'stateChanged(int)'), + self.onCheckBoxIndexChanged) + QtCore.QObject.connect(self.newTestamentCheckBox, + QtCore.SIGNAL(u'stateChanged(int)'), + self.onCheckBoxIndexChanged) + QtCore.QObject.connect(self.apocryphaCheckBox, + QtCore.SIGNAL(u'stateChanged(int)'), + self.onCheckBoxIndexChanged) + + def onCheckBoxIndexChanged(self, index): + """ + Reload Combobox if CheckBox state has changed + """ + self.reloadComboBox() + + def reloadComboBox(self): + """ + Reload the Combobox items + """ + self.correspondingComboBox.clear() + items = BiblesResourcesDB.get_books() + for item in items: + addBook = True + for book in self.books: + if book.book_reference_id == item[u'id']: + addBook = False + break + if self.oldTestamentCheckBox.checkState() == QtCore.Qt.Unchecked \ + and item[u'testament_id'] == 1: + addBook = False + elif self.newTestamentCheckBox.checkState() == QtCore.Qt.Unchecked \ + and item[u'testament_id'] == 2: + addBook = False + elif self.apocryphaCheckBox.checkState() == QtCore.Qt.Unchecked \ + and item[u'testament_id'] == 3: + addBook = False + if addBook: + self.correspondingComboBox.addItem(item[u'name']) + + def exec_(self, name, books, maxbooks): + self.books = books + log.debug(maxbooks) + if maxbooks <= 27: + self.oldTestamentCheckBox.setCheckState(QtCore.Qt.Unchecked) + self.apocryphaCheckBox.setCheckState(QtCore.Qt.Unchecked) + elif maxbooks <= 66: + self.apocryphaCheckBox.setCheckState(QtCore.Qt.Unchecked) + self.reloadComboBox() + self.currentBookLabel.setText(unicode(name)) + self.correspondingComboBox.setFocus() + return QDialog.exec_(self) + + def accept(self): + if self.correspondingComboBox.currentText() == u'': + critical_error_message_box( + message=translate('BiblesPlugin.BookNameForm', + 'You need to select a book.')) + self.correspondingComboBox.setFocus() + return False + else: + return QDialog.accept(self) diff --git a/openlp/plugins/bibles/forms/languagedialog.py b/openlp/plugins/bibles/forms/languagedialog.py new file mode 100644 index 000000000..5c1325a54 --- /dev/null +++ b/openlp/plugins/bibles/forms/languagedialog.py @@ -0,0 +1,84 @@ +# -*- 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, Armin Köhler, Andreas Preikschat, # +# Christian Richter, Philip Ridout, Maikel Stuivenberg, Martin Thompson, Jon # +# Tibble, Carsten Tinggaard, Frode Woldsund # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### + +from PyQt4 import QtCore, QtGui + +from openlp.core.lib import translate + +class Ui_LanguageDialog(object): + def setupUi(self, languageDialog): + languageDialog.setObjectName(u'languageDialog') + languageDialog.resize(400, 165) + self.languageLayout = QtGui.QVBoxLayout(languageDialog) + self.languageLayout.setSpacing(8) + self.languageLayout.setMargin(8) + self.languageLayout.setObjectName(u'languageLayout') + self.bibleLabel = QtGui.QLabel(languageDialog) + self.bibleLabel.setObjectName(u'bibleLabel') + self.languageLayout.addWidget(self.bibleLabel) + self.infoLabel = QtGui.QLabel(languageDialog) + self.infoLabel.setWordWrap(True) + self.infoLabel.setObjectName(u'infoLabel') + self.languageLayout.addWidget(self.infoLabel) + self.languageHBoxLayout = QtGui.QHBoxLayout() + self.languageHBoxLayout.setSpacing(8) + self.languageHBoxLayout.setObjectName(u'languageHBoxLayout') + self.languageLabel = QtGui.QLabel(languageDialog) + self.languageLabel.setObjectName(u'languageLabel') + self.languageHBoxLayout.addWidget(self.languageLabel) + self.languageComboBox = QtGui.QComboBox(languageDialog) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding, + QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth( + self.languageComboBox.sizePolicy().hasHeightForWidth()) + self.languageComboBox.setSizePolicy(sizePolicy) + self.languageComboBox.setObjectName(u'languageComboBox') + self.languageHBoxLayout.addWidget(self.languageComboBox) + self.languageLayout.addLayout(self.languageHBoxLayout) + self.buttonBox = QtGui.QDialogButtonBox(languageDialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel| + QtGui.QDialogButtonBox.Ok) + self.buttonBox.setObjectName(u'buttonBox') + self.languageLayout.addWidget(self.buttonBox) + + self.retranslateUi(languageDialog) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(u'accepted()'), + languageDialog.accept) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(u'rejected()'), + languageDialog.reject) + + def retranslateUi(self, languageDialog): + languageDialog.setWindowTitle( + translate('BiblesPlugin.LanguageDialog', 'Select Language')) + self.bibleLabel.setText(translate('BiblesPlugin.LanguageDialog', '')) + self.infoLabel.setText(translate('BiblesPlugin.LanguageDialog', + 'OpenLP is unable to determine the language of this translation ' + 'of the Bible. Please select the language from the list below.')) + self.languageLabel.setText(translate('BiblesPlugin.LanguageDialog', + 'Language:')) diff --git a/openlp/core/lib/displaytags.py b/openlp/plugins/bibles/forms/languageform.py similarity index 59% rename from openlp/core/lib/displaytags.py rename to openlp/plugins/bibles/forms/languageform.py index fc36bc090..c5069815b 100644 --- a/openlp/core/lib/displaytags.py +++ b/openlp/plugins/bibles/forms/languageform.py @@ -23,45 +23,49 @@ # 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 Display Tag access class +Module implementing LanguageForm. """ +import logging -from openlp.core.lib import base_html_expands +from PyQt4.QtGui import QDialog -class DisplayTags(object): +from openlp.core.lib import translate +from openlp.core.lib.ui import critical_error_message_box +from openlp.plugins.bibles.forms.languagedialog import \ + Ui_LanguageDialog +from openlp.plugins.bibles.lib.db import BiblesResourcesDB + +log = logging.getLogger(__name__) + +class LanguageForm(QDialog, Ui_LanguageDialog): """ - Static Class to HTML Tags to be access around the code the list is managed - by the Options Tab. + Class to manage a dialog which ask the user for a language. """ - html_expands = [] + log.info(u'LanguageForm loaded') - @staticmethod - def get_html_tags(): + def __init__(self, parent=None): """ - Provide access to the html_expands list. + Constructor """ - return DisplayTags.html_expands + QDialog.__init__(self, parent) + self.setupUi(self) - @staticmethod - def reset_html_tags(): - """ - Resets the html_expands list. - """ - DisplayTags.html_expands = [] - for html in base_html_expands: - DisplayTags.html_expands.append(html) + def exec_(self, bible_name): + self.languageComboBox.addItem(u'') + if bible_name: + self.bibleLabel.setText(unicode(bible_name)) + items = BiblesResourcesDB.get_languages() + self.languageComboBox.addItems([item[u'name'] for item in items]) + return QDialog.exec_(self) - @staticmethod - def add_html_tag(tag): - """ - Add a new tag to the list - """ - DisplayTags.html_expands.append(tag) - - @staticmethod - def remove_html_tag(tag_id): - """ - Removes an individual html_expands tag. - """ - DisplayTags.html_expands.pop(tag_id) + def accept(self): + if not self.languageComboBox.currentText(): + critical_error_message_box( + message=translate('BiblesPlugin.LanguageForm', + 'You need to choose a language.')) + self.languageComboBox.setFocus() + return False + else: + return QDialog.accept(self) diff --git a/openlp/plugins/bibles/lib/__init__.py b/openlp/plugins/bibles/lib/__init__.py index e8634c3a3..7e40b6230 100644 --- a/openlp/plugins/bibles/lib/__init__.py +++ b/openlp/plugins/bibles/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, Armin Köhler, 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 # @@ -268,6 +269,7 @@ class SearchResults(object): return len(self.verselist) > 0 +from versereferencelist import VerseReferenceList from manager import BibleManager from biblestab import BiblesTab from mediaitem import BibleMediaItem diff --git a/openlp/plugins/bibles/lib/biblestab.py b/openlp/plugins/bibles/lib/biblestab.py index 74be7145b..4a24c146d 100644 --- a/openlp/plugins/bibles/lib/biblestab.py +++ b/openlp/plugins/bibles/lib/biblestab.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, Armin Köhler, 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,6 +31,7 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import Receiver, SettingsTab, translate from openlp.plugins.bibles.lib import LayoutStyle, DisplayStyle +from openlp.core.lib.ui import UiStrings, find_and_set_in_combo_box log = logging.getLogger(__name__) @@ -39,117 +41,115 @@ class BiblesTab(SettingsTab): """ log.info(u'Bible Tab loaded') - def __init__(self, title, visible_title): + def __init__(self, parent, title, visible_title, icon_path): self.paragraph_style = True self.show_new_chapters = False self.display_style = 0 - SettingsTab.__init__(self, title, visible_title) + SettingsTab.__init__(self, parent, title, visible_title, icon_path) def setupUi(self): self.setObjectName(u'BiblesTab') SettingsTab.setupUi(self) - self.VerseDisplayGroupBox = QtGui.QGroupBox(self.leftColumn) - self.VerseDisplayGroupBox.setObjectName(u'VerseDisplayGroupBox') - self.VerseDisplayLayout = QtGui.QFormLayout(self.VerseDisplayGroupBox) - self.VerseDisplayLayout.setObjectName(u'VerseDisplayLayout') - self.NewChaptersCheckBox = QtGui.QCheckBox(self.VerseDisplayGroupBox) - self.NewChaptersCheckBox.setObjectName(u'NewChaptersCheckBox') - self.VerseDisplayLayout.addRow(self.NewChaptersCheckBox) - self.DisplayStyleLabel = QtGui.QLabel(self.VerseDisplayGroupBox) - self.DisplayStyleLabel.setObjectName(u'DisplayStyleLabel') - self.DisplayStyleComboBox = QtGui.QComboBox(self.VerseDisplayGroupBox) - self.DisplayStyleComboBox.addItems([u'', u'', u'', u'']) - self.DisplayStyleComboBox.setObjectName(u'DisplayStyleComboBox') - self.VerseDisplayLayout.addRow(self.DisplayStyleLabel, - self.DisplayStyleComboBox) - self.LayoutStyleLabel = QtGui.QLabel(self.VerseDisplayGroupBox) - self.LayoutStyleLabel.setObjectName(u'LayoutStyleLabel') - self.LayoutStyleComboBox = QtGui.QComboBox(self.VerseDisplayGroupBox) - self.LayoutStyleComboBox.setObjectName(u'LayoutStyleComboBox') - self.LayoutStyleComboBox.addItems([u'', u'', u'']) - self.VerseDisplayLayout.addRow(self.LayoutStyleLabel, - self.LayoutStyleComboBox) - self.BibleSecondCheckBox = QtGui.QCheckBox(self.VerseDisplayGroupBox) - self.BibleSecondCheckBox.setObjectName(u'BibleSecondCheckBox') - self.VerseDisplayLayout.addRow(self.BibleSecondCheckBox) - self.BibleThemeLabel = QtGui.QLabel(self.VerseDisplayGroupBox) - self.BibleThemeLabel.setObjectName(u'BibleThemeLabel') - self.BibleThemeComboBox = QtGui.QComboBox(self.VerseDisplayGroupBox) - self.BibleThemeComboBox.setSizeAdjustPolicy( + self.verseDisplayGroupBox = QtGui.QGroupBox(self.leftColumn) + self.verseDisplayGroupBox.setObjectName(u'verseDisplayGroupBox') + self.verseDisplayLayout = QtGui.QFormLayout(self.verseDisplayGroupBox) + self.verseDisplayLayout.setObjectName(u'verseDisplayLayout') + self.newChaptersCheckBox = QtGui.QCheckBox(self.verseDisplayGroupBox) + self.newChaptersCheckBox.setObjectName(u'newChaptersCheckBox') + self.verseDisplayLayout.addRow(self.newChaptersCheckBox) + self.displayStyleLabel = QtGui.QLabel(self.verseDisplayGroupBox) + self.displayStyleLabel.setObjectName(u'displayStyleLabel') + self.displayStyleComboBox = QtGui.QComboBox(self.verseDisplayGroupBox) + self.displayStyleComboBox.addItems([u'', u'', u'', u'']) + self.displayStyleComboBox.setObjectName(u'displayStyleComboBox') + self.verseDisplayLayout.addRow(self.displayStyleLabel, + self.displayStyleComboBox) + self.layoutStyleLabel = QtGui.QLabel(self.verseDisplayGroupBox) + self.layoutStyleLabel.setObjectName(u'layoutStyleLabel') + self.layoutStyleComboBox = QtGui.QComboBox(self.verseDisplayGroupBox) + self.layoutStyleComboBox.setObjectName(u'layoutStyleComboBox') + self.layoutStyleComboBox.addItems([u'', u'', u'']) + self.verseDisplayLayout.addRow(self.layoutStyleLabel, + self.layoutStyleComboBox) + self.bibleSecondCheckBox = QtGui.QCheckBox(self.verseDisplayGroupBox) + self.bibleSecondCheckBox.setObjectName(u'bibleSecondCheckBox') + self.verseDisplayLayout.addRow(self.bibleSecondCheckBox) + self.bibleThemeLabel = QtGui.QLabel(self.verseDisplayGroupBox) + self.bibleThemeLabel.setObjectName(u'BibleThemeLabel') + self.bibleThemeComboBox = QtGui.QComboBox(self.verseDisplayGroupBox) + self.bibleThemeComboBox.setSizeAdjustPolicy( QtGui.QComboBox.AdjustToMinimumContentsLength) - self.BibleThemeComboBox.setSizePolicy( + self.bibleThemeComboBox.setSizePolicy( QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed) - self.BibleThemeComboBox.addItem(u'') - self.BibleThemeComboBox.setObjectName(u'BibleThemeComboBox') - self.VerseDisplayLayout.addRow(self.BibleThemeLabel, - self.BibleThemeComboBox) - self.ChangeNoteLabel = QtGui.QLabel(self.VerseDisplayGroupBox) - self.ChangeNoteLabel.setWordWrap(True) - self.ChangeNoteLabel.setObjectName(u'ChangeNoteLabel') - self.VerseDisplayLayout.addRow(self.ChangeNoteLabel) - self.leftLayout.addWidget(self.VerseDisplayGroupBox) + self.bibleThemeComboBox.addItem(u'') + self.bibleThemeComboBox.setObjectName(u'BibleThemeComboBox') + self.verseDisplayLayout.addRow(self.bibleThemeLabel, + self.bibleThemeComboBox) + self.changeNoteLabel = QtGui.QLabel(self.verseDisplayGroupBox) + self.changeNoteLabel.setWordWrap(True) + self.changeNoteLabel.setObjectName(u'changeNoteLabel') + self.verseDisplayLayout.addRow(self.changeNoteLabel) + self.leftLayout.addWidget(self.verseDisplayGroupBox) self.leftLayout.addStretch() self.rightColumn.setSizePolicy( QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred) self.rightLayout.addStretch() # Signals and slots QtCore.QObject.connect( - self.NewChaptersCheckBox, QtCore.SIGNAL(u'stateChanged(int)'), + self.newChaptersCheckBox, QtCore.SIGNAL(u'stateChanged(int)'), self.onNewChaptersCheckBoxChanged) QtCore.QObject.connect( - self.DisplayStyleComboBox, QtCore.SIGNAL(u'activated(int)'), + self.displayStyleComboBox, QtCore.SIGNAL(u'activated(int)'), self.onDisplayStyleComboBoxChanged) QtCore.QObject.connect( - self.BibleThemeComboBox, QtCore.SIGNAL(u'activated(int)'), + self.bibleThemeComboBox, QtCore.SIGNAL(u'activated(int)'), self.onBibleThemeComboBoxChanged) QtCore.QObject.connect( - self.LayoutStyleComboBox, QtCore.SIGNAL(u'activated(int)'), + self.layoutStyleComboBox, QtCore.SIGNAL(u'activated(int)'), self.onLayoutStyleComboBoxChanged) QtCore.QObject.connect( - self.BibleSecondCheckBox, QtCore.SIGNAL(u'stateChanged(int)'), + self.bibleSecondCheckBox, QtCore.SIGNAL(u'stateChanged(int)'), self.onBibleSecondCheckBox) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'theme_update_list'), self.updateThemeList) def retranslateUi(self): - self.VerseDisplayGroupBox.setTitle( + self.verseDisplayGroupBox.setTitle( translate('BiblesPlugin.BiblesTab', 'Verse Display')) - self.NewChaptersCheckBox.setText( + self.newChaptersCheckBox.setText( translate('BiblesPlugin.BiblesTab', 'Only show new chapter numbers')) - self.LayoutStyleLabel.setText( - translate('BiblesPlugin.BiblesTab', 'Layout style:')) - self.DisplayStyleLabel.setText( - translate('BiblesPlugin.BiblesTab', 'Display style:')) - self.BibleThemeLabel.setText( + self.layoutStyleLabel.setText(UiStrings().LayoutStyle) + self.displayStyleLabel.setText(UiStrings().DisplayStyle) + self.bibleThemeLabel.setText( translate('BiblesPlugin.BiblesTab', 'Bible theme:')) - self.LayoutStyleComboBox.setItemText(LayoutStyle.VersePerSlide, - translate('BiblesPlugin.BiblesTab', 'Verse Per Slide')) - self.LayoutStyleComboBox.setItemText(LayoutStyle.VersePerLine, - translate('BiblesPlugin.BiblesTab', 'Verse Per Line')) - self.LayoutStyleComboBox.setItemText(LayoutStyle.Continuous, - translate('BiblesPlugin.BiblesTab', 'Continuous')) - self.DisplayStyleComboBox.setItemText(DisplayStyle.NoBrackets, + self.layoutStyleComboBox.setItemText(LayoutStyle.VersePerSlide, + UiStrings().VersePerSlide) + self.layoutStyleComboBox.setItemText(LayoutStyle.VersePerLine, + UiStrings().VersePerLine) + self.layoutStyleComboBox.setItemText(LayoutStyle.Continuous, + UiStrings().Continuous) + self.displayStyleComboBox.setItemText(DisplayStyle.NoBrackets, translate('BiblesPlugin.BiblesTab', 'No Brackets')) - self.DisplayStyleComboBox.setItemText(DisplayStyle.Round, + self.displayStyleComboBox.setItemText(DisplayStyle.Round, translate('BiblesPlugin.BiblesTab', '( And )')) - self.DisplayStyleComboBox.setItemText(DisplayStyle.Curly, + self.displayStyleComboBox.setItemText(DisplayStyle.Curly, translate('BiblesPlugin.BiblesTab', '{ And }')) - self.DisplayStyleComboBox.setItemText(DisplayStyle.Square, + self.displayStyleComboBox.setItemText(DisplayStyle.Square, translate('BiblesPlugin.BiblesTab', '[ And ]')) - self.ChangeNoteLabel.setText(translate('BiblesPlugin.BiblesTab', + self.changeNoteLabel.setText(translate('BiblesPlugin.BiblesTab', 'Note:\nChanges do not affect verses already in the service.')) - self.BibleSecondCheckBox.setText( + self.bibleSecondCheckBox.setText( translate('BiblesPlugin.BiblesTab', 'Display second Bible verses')) def onBibleThemeComboBoxChanged(self): - self.bible_theme = self.BibleThemeComboBox.currentText() + self.bible_theme = self.bibleThemeComboBox.currentText() def onDisplayStyleComboBoxChanged(self): - self.display_style = self.DisplayStyleComboBox.currentIndex() + self.display_style = self.displayStyleComboBox.currentIndex() def onLayoutStyleComboBoxChanged(self): - self.layout_style = self.LayoutStyleComboBox.currentIndex() + self.layout_style = self.layoutStyleComboBox.currentIndex() def onNewChaptersCheckBoxChanged(self, check_state): self.show_new_chapters = False @@ -176,10 +176,10 @@ class BiblesTab(SettingsTab): settings.value(u'bible theme', QtCore.QVariant(u'')).toString()) self.second_bibles = settings.value( u'second bibles', QtCore.QVariant(True)).toBool() - self.NewChaptersCheckBox.setChecked(self.show_new_chapters) - self.DisplayStyleComboBox.setCurrentIndex(self.display_style) - self.LayoutStyleComboBox.setCurrentIndex(self.layout_style) - self.BibleSecondCheckBox.setChecked(self.second_bibles) + self.newChaptersCheckBox.setChecked(self.show_new_chapters) + self.displayStyleComboBox.setCurrentIndex(self.display_style) + self.layoutStyleComboBox.setCurrentIndex(self.layout_style) + self.bibleSecondCheckBox.setChecked(self.second_bibles) settings.endGroup() def save(self): @@ -204,14 +204,8 @@ class BiblesTab(SettingsTab): [u'Bible Theme', u'Song Theme'] """ - self.BibleThemeComboBox.clear() - self.BibleThemeComboBox.addItem(u'') + self.bibleThemeComboBox.clear() + self.bibleThemeComboBox.addItem(u'') for theme in theme_list: - self.BibleThemeComboBox.addItem(theme) - index = self.BibleThemeComboBox.findText( - unicode(self.bible_theme), QtCore.Qt.MatchExactly) - if index == -1: - # Not Found. - index = 0 - self.bible_theme = u'' - self.BibleThemeComboBox.setCurrentIndex(index) + self.bibleThemeComboBox.addItem(theme) + find_and_set_in_combo_box(self.bibleThemeComboBox, self.bible_theme) diff --git a/openlp/plugins/bibles/lib/csvbible.py b/openlp/plugins/bibles/lib/csvbible.py index b96382df2..6735a7344 100644 --- a/openlp/plugins/bibles/lib/csvbible.py +++ b/openlp/plugins/bibles/lib/csvbible.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, Armin Köhler, 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 # @@ -70,7 +71,7 @@ import chardet import csv from openlp.core.lib import Receiver, translate -from openlp.plugins.bibles.lib.db import BibleDB, Testament +from openlp.plugins.bibles.lib.db import BibleDB, BiblesResourcesDB log = logging.getLogger(__name__) @@ -78,6 +79,8 @@ class CSVBible(BibleDB): """ This class provides a specialisation for importing of CSV Bibles. """ + log.info(u'CSVBible loaded') + def __init__(self, parent, **kwargs): """ Loads a Bible from a set of CVS files. @@ -86,48 +89,10 @@ class CSVBible(BibleDB): """ log.info(self.__class__.__name__) BibleDB.__init__(self, parent, **kwargs) - try: - self.testamentsfile = kwargs[u'testamentsfile'] - except KeyError: - self.testamentsfile = None self.booksfile = kwargs[u'booksfile'] self.versesfile = kwargs[u'versefile'] - def setup_testaments(self): - """ - Overrides parent method so we can handle importing a testament file. - """ - if self.testamentsfile: - self.wizard.progressBar.setMinimum(0) - self.wizard.progressBar.setMaximum(2) - self.wizard.progressBar.setValue(0) - testaments_file = None - try: - details = get_file_encoding(self.testamentsfile) - testaments_file = open(self.testamentsfile, 'rb') - testaments_reader = csv.reader(testaments_file, delimiter=',', - quotechar='"') - for line in testaments_reader: - if self.stop_import_flag: - break - self.wizard.incrementProgressBar(unicode( - translate('BibleDB.Wizard', - 'Importing testaments... %s')) % - unicode(line[1], details['encoding']), 0) - self.save_object(Testament.populate( - name=unicode(line[1], details['encoding']))) - Receiver.send_message(u'openlp_process_events') - except (IOError, IndexError): - log.exception(u'Loading testaments from file failed') - finally: - if testaments_file: - testaments_file.close() - self.wizard.incrementProgressBar(unicode(translate( - 'BibleDB.Wizard', 'Importing testaments... done.')), 2) - else: - BibleDB.setup_testaments(self) - - def do_import(self): + def do_import(self, bible_name=None): """ Import the bible books and verses. """ @@ -135,6 +100,10 @@ class CSVBible(BibleDB): self.wizard.progressBar.setMinimum(0) self.wizard.progressBar.setMaximum(66) success = True + language_id = self.get_language(bible_name) + if not language_id: + log.exception(u'Importing books from "%s" failed' % self.filename) + return False books_file = None book_list = {} # Populate the Tables @@ -146,10 +115,18 @@ class CSVBible(BibleDB): if self.stop_import_flag: break self.wizard.incrementProgressBar(unicode( - translate('BibleDB.Wizard', 'Importing books... %s')) % + translate('BiblesPlugin.CSVBible', + 'Importing books... %s')) % unicode(line[2], details['encoding'])) + book_ref_id = self.get_book_ref_id_by_name( + unicode(line[2], details['encoding']), 67, language_id) + if not book_ref_id: + log.exception(u'Importing books from "%s" '\ + 'failed' % self.booksfile) + return False + book_details = BiblesResourcesDB.get_book_by_id(book_ref_id) self.create_book(unicode(line[2], details['encoding']), - unicode(line[3], details['encoding']), int(line[1])) + book_ref_id, book_details[u'testament_id']) book_list[int(line[0])] = unicode(line[2], details['encoding']) Receiver.send_message(u'openlp_process_events') except (IOError, IndexError): @@ -179,7 +156,7 @@ class CSVBible(BibleDB): book = self.get_book(line_book) book_ptr = book.name self.wizard.incrementProgressBar(unicode(translate( - 'BibleDB.Wizard', 'Importing verses from %s...', + 'BiblesPlugin.CSVBible', 'Importing verses from %s...', 'Importing verses from ...')) % book.name) self.session.commit() try: @@ -187,7 +164,7 @@ class CSVBible(BibleDB): except UnicodeError: verse_text = unicode(line[3], u'cp1252') self.create_verse(book.id, line[1], line[2], verse_text) - self.wizard.incrementProgressBar(translate('BibleDB.Wizard', + self.wizard.incrementProgressBar(translate('BiblesPlugin.CSVBible', 'Importing verses... done.')) Receiver.send_message(u'openlp_process_events') self.session.commit() diff --git a/openlp/plugins/bibles/lib/db.py b/openlp/plugins/bibles/lib/db.py index 5cf000ee1..9ec5b45b2 100644 --- a/openlp/plugins/bibles/lib/db.py +++ b/openlp/plugins/bibles/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, Armin Köhler, 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 # @@ -26,7 +27,8 @@ import logging import chardet -import re +import os +import sqlite3 from PyQt4 import QtCore from sqlalchemy import Column, ForeignKey, or_, Table, types @@ -36,6 +38,7 @@ from sqlalchemy.orm.exc import UnmappedClassError from openlp.core.lib import Receiver, translate from openlp.core.lib.db import BaseModel, init_db, Manager from openlp.core.lib.ui import critical_error_message_box +from openlp.core.utils import AppLocation, clean_filename log = logging.getLogger(__name__) @@ -46,13 +49,6 @@ class BibleMeta(BaseModel): pass -class Testament(BaseModel): - """ - Bible Testaments - """ - pass - - class Book(BaseModel): """ Song model @@ -66,7 +62,6 @@ class Verse(BaseModel): """ pass - def init_schema(url): """ Setup a bible database connection and initialise the database schema. @@ -80,19 +75,17 @@ def init_schema(url): Column(u'key', types.Unicode(255), primary_key=True, index=True), Column(u'value', types.Unicode(255)), ) - testament_table = Table(u'testament', metadata, - Column(u'id', types.Integer, primary_key=True), - Column(u'name', types.Unicode(50)), - ) + book_table = Table(u'book', metadata, Column(u'id', types.Integer, primary_key=True), - Column(u'testament_id', types.Integer, ForeignKey(u'testament.id')), + Column(u'book_reference_id', types.Integer, index=True), + Column(u'testament_reference_id', types.Integer), Column(u'name', types.Unicode(50), index=True), - Column(u'abbreviation', types.Unicode(5), index=True), ) verse_table = Table(u'verse', metadata, Column(u'id', types.Integer, primary_key=True, index=True), - Column(u'book_id', types.Integer, ForeignKey(u'book.id'), index=True), + Column(u'book_id', types.Integer, ForeignKey( + u'book.id'), index=True), Column(u'chapter', types.Integer, index=True), Column(u'verse', types.Integer, index=True), Column(u'text', types.UnicodeText, index=True), @@ -102,11 +95,6 @@ def init_schema(url): class_mapper(BibleMeta) except UnmappedClassError: mapper(BibleMeta, meta_table) - try: - class_mapper(Testament) - except UnmappedClassError: - mapper(Testament, testament_table, - properties={'books': relation(Book, backref='testament')}) try: class_mapper(Book) except UnmappedClassError: @@ -128,6 +116,7 @@ class BibleDB(QtCore.QObject, Manager): methods, but benefit from the database methods in here via inheritance, rather than depending on yet another object. """ + log.info(u'BibleDB loaded') def __init__(self, parent, **kwargs): """ @@ -155,12 +144,14 @@ class BibleDB(QtCore.QObject, Manager): self.name = kwargs[u'name'] if not isinstance(self.name, unicode): self.name = unicode(self.name, u'utf-8') - self.file = self.clean_filename(self.name) + self.file = clean_filename(self.name) + u'.sqlite' if u'file' in kwargs: self.file = kwargs[u'file'] Manager.__init__(self, u'bibles', init_schema, self.file) if u'file' in kwargs: self.get_name() + if u'path' in kwargs: + self.path = kwargs[u'path'] self.wizard = None QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'openlp_stop_wizard'), self.stop_import) @@ -177,25 +168,9 @@ class BibleDB(QtCore.QObject, Manager): Returns the version name of the Bible. """ version_name = self.get_object(BibleMeta, u'Version') - if version_name: - self.name = version_name.value - else: - self.name = None + self.name = version_name.value if version_name else None return self.name - def clean_filename(self, old_filename): - """ - Clean up the version name of the Bible and convert it into a valid - file name. - - ``old_filename`` - The "dirty" file name or version name. - """ - if not isinstance(old_filename, unicode): - old_filename = unicode(old_filename, u'utf-8') - old_filename = re.sub(r'[^\w]+', u'_', old_filename).strip(u'_') - return old_filename + u'.sqlite' - def register(self, wizard): """ This method basically just initialialises the database. It is called @@ -208,36 +183,40 @@ class BibleDB(QtCore.QObject, Manager): """ self.wizard = wizard self.create_meta(u'dbversion', u'2') - self.setup_testaments() return self.name - def setup_testaments(self): - """ - Initialise the testaments section of a bible with suitable defaults. - """ - self.save_object(Testament.populate(name=u'Old Testament')) - self.save_object(Testament.populate(name=u'New Testament')) - self.save_object(Testament.populate(name=u'Apocrypha')) - - def create_book(self, name, abbrev, testament=1): + def create_book(self, name, bk_ref_id, testament=1): """ Add a book to the database. ``name`` The name of the book. - ``abbrev`` - The abbreviation of the book. + ``bk_ref_id`` + The book_reference_id from bibles_resources.sqlite of the book. ``testament`` - *Defaults to 1.* The id of the testament this book belongs to. + *Defaults to 1.* The testament_reference_id from + bibles_resources.sqlite of the testament this book belongs to. """ - log.debug(u'create_book %s,%s', name, abbrev) - book = Book.populate(name=name, abbreviation=abbrev, - testament_id=testament) + log.debug(u'BibleDB.create_book("%s", "%s")', name, bk_ref_id) + book = Book.populate(name=name, book_reference_id=bk_ref_id, + testament_reference_id=testament) self.save_object(book) return book + def delete_book(self, db_book): + """ + Delete a book from the database. + + ``db_book`` + The book object. + """ + log.debug(u'BibleDB.delete_book("%s")', db_book.name) + if self.delete_object(Book, db_book.id): + return True + return False + def create_chapter(self, book_id, chapter, textlist): """ Add a chapter and its verses to a book. @@ -252,14 +231,14 @@ class BibleDB(QtCore.QObject, Manager): A dict of the verses to be inserted. The key is the verse number, and the value is the verse text. """ - log.debug(u'create_chapter %s,%s', book_id, chapter) + log.debug(u'BibleDBcreate_chapter("%s", "%s")', book_id, chapter) # Text list has book and chapter as first two elements of the array. for verse_number, verse_text in textlist.iteritems(): verse = Verse.populate( - book_id = book_id, - chapter = chapter, - verse = verse_number, - text = verse_text + book_id=book_id, + chapter=chapter, + verse=verse_number, + text=verse_text ) self.session.add(verse) self.session.commit() @@ -302,7 +281,9 @@ class BibleDB(QtCore.QObject, Manager): ``value`` The value for this instance. """ - log.debug(u'save_meta %s/%s', key, value) + if not isinstance(value, unicode): + value = unicode(value) + log.debug(u'BibleDB.save_meta("%s/%s")', key, value) self.save_object(BibleMeta.populate(key=key, value=value)) def get_book(self, book): @@ -312,21 +293,61 @@ class BibleDB(QtCore.QObject, Manager): ``book`` The name of the book to return. """ - log.debug(u'BibleDb.get_book("%s")', book) - db_book = self.get_object_filtered(Book, Book.name.like(book + u'%')) - if db_book is None: - db_book = self.get_object_filtered(Book, - Book.abbreviation.like(book + u'%')) - return db_book + log.debug(u'BibleDB.get_book("%s")', book) + return self.get_object_filtered(Book, Book.name.like(book + u'%')) def get_books(self): """ A wrapper so both local and web bibles have a get_books() method that manager can call. Used in the media manager advanced search tab. """ + log.debug(u'BibleDB.get_books()') return self.get_all_objects(Book, order_by_ref=Book.id) - def get_verses(self, reference_list): + def get_book_by_book_ref_id(self, id): + """ + Return a book object from the database. + + ``id`` + The reference id of the book to return. + """ + log.debug(u'BibleDB.get_book_by_book_ref_id("%s")', id) + return self.get_object_filtered(Book, Book.book_reference_id.like(id)) + + def get_book_ref_id_by_name(self, book, maxbooks, language_id=None): + log.debug(u'BibleDB.get_book_ref_id_by_name:("%s", "%s")', book, + language_id) + if BiblesResourcesDB.get_book(book, True): + book_temp = BiblesResourcesDB.get_book(book, True) + book_id = book_temp[u'id'] + elif BiblesResourcesDB.get_alternative_book_name(book): + book_id = BiblesResourcesDB.get_alternative_book_name(book) + elif AlternativeBookNamesDB.get_book_reference_id(book): + book_id = AlternativeBookNamesDB.get_book_reference_id(book) + else: + from openlp.plugins.bibles.forms import BookNameForm + book_ref = None + book_name = BookNameForm(self.wizard) + if book_name.exec_(book, self.get_books(), maxbooks): + book_ref = unicode( + book_name.correspondingComboBox.currentText()) + if not book_ref: + return None + else: + book_temp = BiblesResourcesDB.get_book(book_ref) + if book_temp: + book_id = book_temp[u'id'] + else: + return None + if book_id: + AlternativeBookNamesDB.create_alternative_book_name( + book, book_id, language_id) + if book_id: + return book_id + else: + return None + + def get_verses(self, reference_list, show_error=True): """ This is probably the most used function. It retrieves the list of verses based on the user's query. @@ -335,24 +356,25 @@ class BibleDB(QtCore.QObject, Manager): This is the list of references the media manager item wants. It is a list of tuples, with the following format:: - (book, chapter, start_verse, end_verse) + (book_reference_id, chapter, start_verse, end_verse) Therefore, when you are looking for multiple items, simply break them up into references like this, bundle them into a list. This function then runs through the list, and returns an amalgamated list of ``Verse`` objects. For example:: - [(u'Genesis', 1, 1, 1), (u'Genesis', 2, 2, 3)] + [(u'35', 1, 1, 1), (u'35', 2, 2, 3)] """ - log.debug(u'BibleDB.get_verses: %s', reference_list) + log.debug(u'BibleDB.get_verses("%s")', reference_list) verse_list = [] - for book, chapter, start_verse, end_verse in reference_list: - db_book = self.get_book(book) + book_error = False + for book_id, chapter, start_verse, end_verse in reference_list: + db_book = self.get_book_by_book_ref_id(book_id) if db_book: - book = db_book.name - log.debug(u'Book name corrected to "%s"', book) + book_id = db_book.book_reference_id + log.debug(u'Book name corrected to "%s"', db_book.name) if end_verse == -1: - end_verse = self.get_verse_count(book, chapter) + end_verse = self.get_verse_count(book_id, chapter) verses = self.session.query(Verse)\ .filter_by(book_id=db_book.id)\ .filter_by(chapter=chapter)\ @@ -362,12 +384,14 @@ class BibleDB(QtCore.QObject, Manager): .all() verse_list.extend(verses) else: - log.debug(u'OpenLP failed to find book %s', book) - critical_error_message_box( - translate('BiblesPlugin', 'No Book Found'), - translate('BiblesPlugin', 'No matching book ' - 'could be found in this Bible. Check that you have ' - 'spelled the name of the book correctly.')) + log.debug(u'OpenLP failed to find book with id "%s"', book_id) + book_error = True + if book_error and show_error: + critical_error_message_box( + translate('BiblesPlugin', 'No Book Found'), + translate('BiblesPlugin', 'No matching book ' + 'could be found in this Bible. Check that you ' + 'have spelled the name of the book correctly.')) return verse_list def verse_search(self, text): @@ -383,15 +407,13 @@ class BibleDB(QtCore.QObject, Manager): log.debug(u'BibleDB.verse_search("%s")', text) verses = self.session.query(Verse) if text.find(u',') > -1: - or_clause = [] - keywords = [u'%%%s%%' % keyword.strip() - for keyword in text.split(u',')] - for keyword in keywords: - or_clause.append(Verse.text.like(keyword)) + keywords = \ + [u'%%%s%%' % keyword.strip() for keyword in text.split(u',')] + or_clause = [Verse.text.like(keyword) for keyword in keywords] verses = verses.filter(or_(*or_clause)) else: - keywords = [u'%%%s%%' % keyword.strip() - for keyword in text.split(u' ')] + keywords = \ + [u'%%%s%%' % keyword.strip() for keyword in text.split(u' ')] for keyword in keywords: verses = verses.filter(Verse.text.like(keyword)) verses = verses.all() @@ -402,18 +424,18 @@ class BibleDB(QtCore.QObject, Manager): Return the number of chapters in a book. ``book`` - The book to get the chapter count for. + The book object to get the chapter count for. """ - log.debug(u'BibleDB.get_chapter_count("%s")', book) + log.debug(u'BibleDB.get_chapter_count("%s")', book.name) count = self.session.query(Verse.chapter).join(Book)\ - .filter(Book.name==book)\ + .filter(Book.book_reference_id==book.book_reference_id)\ .distinct().count() if not count: return 0 else: return count - def get_verse_count(self, book, chapter): + def get_verse_count(self, book_id, chapter): """ Return the number of verses in a chapter. @@ -423,9 +445,9 @@ class BibleDB(QtCore.QObject, Manager): ``chapter`` The chapter to get the verse count for. """ - log.debug(u'BibleDB.get_verse_count("%s", %s)', book, chapter) + log.debug(u'BibleDB.get_verse_count("%s", "%s")', book_id, chapter) count = self.session.query(Verse).join(Book)\ - .filter(Book.name==book)\ + .filter(Book.book_reference_id==book_id)\ .filter(Verse.chapter==chapter)\ .count() if not count: @@ -433,6 +455,39 @@ class BibleDB(QtCore.QObject, Manager): else: return count + def get_language(self, bible_name=None): + """ + If no language is given it calls a dialog window where the user could + select the bible language. + Return the language id of a bible. + + ``book`` + The language the bible is. + """ + log.debug(u'BibleDB.get_language()') + from openlp.plugins.bibles.forms import LanguageForm + language = None + language_form = LanguageForm(self.wizard) + if language_form.exec_(bible_name): + language = unicode(language_form.languageComboBox.currentText()) + if not language: + return False + language = BiblesResourcesDB.get_language(language) + language_id = language[u'id'] + self.create_meta(u'language_id', language_id) + return language_id + + def is_old_database(self): + """ + Returns ``True`` if it is a bible database, which has been created + prior to 1.9.6. + """ + try: + columns = self.session.query(Book).all() + except: + return True + return False + def dump_bible(self): """ Utility debugging method to dump the contents of a bible. @@ -444,3 +499,595 @@ class BibleDB(QtCore.QObject, Manager): log.debug(u'...............................Verses ') verses = self.session.query(Verse).all() log.debug(verses) + + +class BiblesResourcesDB(QtCore.QObject, Manager): + """ + This class represents the database-bound Bible Resources. It provide + some resources which are used in the Bibles plugin. + A wrapper class around a small SQLite database which contains the download + resources, a biblelist from the different download resources, the books, + chapter counts and verse counts for the web download Bibles, a language + reference, the testament reference and some alternative book names. This + class contains a singleton "cursor" so that only one connection to the + SQLite database is ever used. + """ + cursor = None + + @staticmethod + def get_cursor(): + """ + Return the cursor object. Instantiate one if it doesn't exist yet. + """ + if BiblesResourcesDB.cursor is None: + filepath = os.path.join( + AppLocation.get_directory(AppLocation.PluginsDir), u'bibles', + u'resources', u'bibles_resources.sqlite') + conn = sqlite3.connect(filepath) + BiblesResourcesDB.cursor = conn.cursor() + return BiblesResourcesDB.cursor + + @staticmethod + def run_sql(query, parameters=()): + """ + Run an SQL query on the database, returning the results. + + ``query`` + The actual SQL query to run. + + ``parameters`` + Any variable parameters to add to the query. + """ + cursor = BiblesResourcesDB.get_cursor() + cursor.execute(query, parameters) + return cursor.fetchall() + + @staticmethod + def get_books(): + """ + Return a list of all the books of the Bible. + """ + log.debug(u'BiblesResourcesDB.get_books()') + books = BiblesResourcesDB.run_sql(u'SELECT id, testament_id, name, ' + u'abbreviation, chapters FROM book_reference ORDER BY id') + return [ + { + u'id': book[0], + u'testament_id': book[1], + u'name': unicode(book[2]), + u'abbreviation': unicode(book[3]), + u'chapters': book[4] + } + for book in books + ] + + @staticmethod + def get_book(name, lower=False): + """ + Return a book by name or abbreviation. + + ``name`` + The name or abbreviation of the book. + + ``lower`` + True if the comparsion should be only lowercase + """ + log.debug(u'BiblesResourcesDB.get_book("%s")', name) + if not isinstance(name, unicode): + name = unicode(name) + if lower: + books = BiblesResourcesDB.run_sql(u'SELECT id, testament_id, name, ' + u'abbreviation, chapters FROM book_reference WHERE ' + u'LOWER(name) = ? OR LOWER(abbreviation) = ?', + (name.lower(), name.lower())) + else: + books = BiblesResourcesDB.run_sql(u'SELECT id, testament_id, name, ' + u'abbreviation, chapters FROM book_reference WHERE name = ?' + u' OR abbreviation = ?', (name, name)) + if books: + return { + u'id': books[0][0], + u'testament_id': books[0][1], + u'name': unicode(books[0][2]), + u'abbreviation': unicode(books[0][3]), + u'chapters': books[0][4] + } + else: + return None + + @staticmethod + def get_book_by_id(id): + """ + Return a book by id. + + ``id`` + The id of the book. + """ + log.debug(u'BiblesResourcesDB.get_book_by_id("%s")', id) + if not isinstance(id, int): + id = int(id) + books = BiblesResourcesDB.run_sql(u'SELECT id, testament_id, name, ' + u'abbreviation, chapters FROM book_reference WHERE id = ?', + (id, )) + if books: + return { + u'id': books[0][0], + u'testament_id': books[0][1], + u'name': unicode(books[0][2]), + u'abbreviation': unicode(books[0][3]), + u'chapters': books[0][4] + } + else: + return None + + @staticmethod + def get_chapter(book_id, chapter): + """ + Return the chapter details for a specific chapter of a book. + + ``book_id`` + The id of a book. + + ``chapter`` + The chapter number. + """ + log.debug(u'BiblesResourcesDB.get_chapter("%s", "%s")', book_id, + chapter) + if not isinstance(chapter, int): + chapter = int(chapter) + chapters = BiblesResourcesDB.run_sql(u'SELECT id, book_reference_id, ' + u'chapter, verse_count FROM chapters WHERE book_reference_id = ?', + (book_id,)) + if chapters: + return { + u'id': chapters[chapter-1][0], + u'book_reference_id': chapters[chapter-1][1], + u'chapter': chapters[chapter-1][2], + u'verse_count': chapters[chapter-1][3] + } + else: + return None + + @staticmethod + def get_chapter_count(book_id): + """ + Return the number of chapters in a book. + + ``book_id`` + The id of the book. + """ + log.debug(u'BiblesResourcesDB.get_chapter_count("%s")', book_id) + details = BiblesResourcesDB.get_book_by_id(book_id) + if details: + return details[u'chapters'] + return 0 + + @staticmethod + def get_verse_count(book_id, chapter): + """ + Return the number of verses in a chapter. + + ``book`` + The id of the book. + + ``chapter`` + The number of the chapter. + """ + log.debug(u'BiblesResourcesDB.get_verse_count("%s", "%s")', book_id, + chapter) + details = BiblesResourcesDB.get_chapter(book_id, chapter) + if details: + return details[u'verse_count'] + return 0 + + @staticmethod + def get_download_source(source): + """ + Return a download_source_id by source. + + ``name`` + The name or abbreviation of the book. + """ + log.debug(u'BiblesResourcesDB.get_download_source("%s")', source) + if not isinstance(source, unicode): + source = unicode(source) + source = source.title() + dl_source = BiblesResourcesDB.run_sql(u'SELECT id, source FROM ' + u'download_source WHERE source = ?', (source.lower(),)) + if dl_source: + return { + u'id': dl_source[0][0], + u'source': dl_source[0][1] + } + else: + return None + + @staticmethod + def get_webbibles(source): + """ + Return the bibles a webbible provide for download. + + ``source`` + The source of the webbible. + """ + log.debug(u'BiblesResourcesDB.get_webbibles("%s")', source) + if not isinstance(source, unicode): + source = unicode(source) + source = BiblesResourcesDB.get_download_source(source) + bibles = BiblesResourcesDB.run_sql(u'SELECT id, name, abbreviation, ' + u'language_id, download_source_id FROM webbibles WHERE ' + u'download_source_id = ?', (source[u'id'],)) + if bibles: + return [ + { + u'id': bible[0], + u'name': bible[1], + u'abbreviation': bible[2], + u'language_id': bible[3], + u'download_source_id': bible[4] + } + for bible in bibles + ] + else: + return None + + @staticmethod + def get_webbible(abbreviation, source): + """ + Return the bibles a webbible provide for download. + + ``abbreviation`` + The abbreviation of the webbible. + + ``source`` + The source of the webbible. + """ + log.debug(u'BiblesResourcesDB.get_webbibles("%s", "%s")', abbreviation, + source) + if not isinstance(abbreviation, unicode): + abbreviation = unicode(abbreviation) + if not isinstance(source, unicode): + source = unicode(source) + source = BiblesResourcesDB.get_download_source(source) + bible = BiblesResourcesDB.run_sql(u'SELECT id, name, abbreviation, ' + u'language_id, download_source_id FROM webbibles WHERE ' + u'download_source_id = ? AND abbreviation = ?', (source[u'id'], + abbreviation)) + if bible: + return { + u'id': bible[0][0], + u'name': bible[0][1], + u'abbreviation': bible[0][2], + u'language_id': bible[0][3], + u'download_source_id': bible[0][4] + } + else: + return None + + @staticmethod + def get_alternative_book_name(name, language_id=None): + """ + Return a book_reference_id if the name matches. + + ``name`` + The name to search the id. + + ``language_id`` + The language_id for which language should be searched + """ + log.debug(u'BiblesResourcesDB.get_alternative_book_name("%s", "%s")', + name, language_id) + if language_id: + books = BiblesResourcesDB.run_sql(u'SELECT book_reference_id, name ' + u'FROM alternative_book_names WHERE language_id = ? ORDER BY ' + u'id', (language_id, )) + else: + books = BiblesResourcesDB.run_sql(u'SELECT book_reference_id, name ' + u'FROM alternative_book_names ORDER BY id') + for book in books: + if book[1].lower() == name.lower(): + return book[0] + return None + + @staticmethod + def get_language(name): + """ + Return a dict containing the language id, name and code by name or + abbreviation. + + ``name`` + The name or abbreviation of the language. + """ + log.debug(u'BiblesResourcesDB.get_language("%s")', name) + if not isinstance(name, unicode): + name = unicode(name) + language = BiblesResourcesDB.run_sql(u'SELECT id, name, code FROM ' + u'language WHERE name = ? OR code = ?', (name, name.lower())) + if language: + return { + u'id': language[0][0], + u'name': unicode(language[0][1]), + u'code': unicode(language[0][2]) + } + else: + return None + + @staticmethod + def get_languages(): + """ + Return a dict containing all languages with id, name and code. + """ + log.debug(u'BiblesResourcesDB.get_languages()') + languages = BiblesResourcesDB.run_sql(u'SELECT id, name, code FROM ' + u'language ORDER by name') + if languages: + return [ + { + u'id': language[0], + u'name': unicode(language[1]), + u'code': unicode(language[2]) + } + for language in languages + ] + else: + return None + + @staticmethod + def get_testament_reference(): + """ + Return a list of all testaments and their id of the Bible. + """ + log.debug(u'BiblesResourcesDB.get_testament_reference()') + testaments = BiblesResourcesDB.run_sql(u'SELECT id, name FROM ' + u'testament_reference ORDER BY id') + return [ + { + u'id': testament[0], + u'name': unicode(testament[1]) + } + for testament in testaments + ] + + +class AlternativeBookNamesDB(QtCore.QObject, Manager): + """ + This class represents a database-bound alternative book names system. + """ + cursor = None + conn = None + + @staticmethod + def get_cursor(): + """ + Return the cursor object. Instantiate one if it doesn't exist yet. + If necessary loads up the database and creates the tables if the + database doesn't exist. + """ + if AlternativeBookNamesDB.cursor is None: + filepath = os.path.join( + AppLocation.get_directory(AppLocation.DataDir), u'bibles', + u'alternative_book_names.sqlite') + if not os.path.exists(filepath): + #create new DB, create table alternative_book_names + AlternativeBookNamesDB.conn = sqlite3.connect(filepath) + AlternativeBookNamesDB.conn.execute(u'CREATE TABLE ' + u'alternative_book_names(id INTEGER NOT NULL, ' + u'book_reference_id INTEGER, language_id INTEGER, name ' + u'VARCHAR(50), PRIMARY KEY (id))') + else: + #use existing DB + AlternativeBookNamesDB.conn = sqlite3.connect(filepath) + AlternativeBookNamesDB.cursor = AlternativeBookNamesDB.conn.cursor() + return AlternativeBookNamesDB.cursor + + @staticmethod + def run_sql(query, parameters=(), commit=None): + """ + Run an SQL query on the database, returning the results. + + ``query`` + The actual SQL query to run. + + ``parameters`` + Any variable parameters to add to the query + + ``commit`` + If a commit statement is necessary this should be True. + """ + cursor = AlternativeBookNamesDB.get_cursor() + cursor.execute(query, parameters) + if commit: + AlternativeBookNamesDB.conn.commit() + return cursor.fetchall() + + @staticmethod + def get_book_reference_id(name, language_id=None): + """ + Return a book_reference_id if the name matches. + + ``name`` + The name to search the id. + + ``language_id`` + The language_id for which language should be searched + """ + log.debug(u'AlternativeBookNamesDB.get_book_reference_id("%s", "%s")', + name, language_id) + if language_id: + books = AlternativeBookNamesDB.run_sql(u'SELECT book_reference_id, ' + u'name FROM alternative_book_names WHERE language_id = ?', + (language_id, )) + else: + books = AlternativeBookNamesDB.run_sql(u'SELECT book_reference_id, ' + u'name FROM alternative_book_names') + for book in books: + if book[1].lower() == name.lower(): + return book[0] + return None + + @staticmethod + def create_alternative_book_name(name, book_reference_id, language_id): + """ + Add an alternative book name to the database. + + ``name`` + The name of the alternative book name. + + ``book_reference_id`` + The book_reference_id of the book. + + ``language_id`` + The language to which the alternative book name belong. + """ + log.debug(u'AlternativeBookNamesDB.create_alternative_book_name("%s", ' + '"%s", "%s"', name, book_reference_id, language_id) + return AlternativeBookNamesDB.run_sql(u'INSERT INTO ' + u'alternative_book_names(book_reference_id, language_id, name) ' + u'VALUES (?, ?, ?)', (book_reference_id, language_id, name), True) + + +class OldBibleDB(QtCore.QObject, Manager): + """ + This class conects to the old bible databases to reimport them to the new + database scheme. + """ + cursor = None + + def __init__(self, parent, **kwargs): + """ + The constructor loads up the database and creates and initialises the + tables if the database doesn't exist. + + **Required keyword arguments:** + + ``path`` + The path to the bible database file. + + ``name`` + The name of the database. This is also used as the file name for + SQLite databases. + """ + log.info(u'OldBibleDB loaded') + QtCore.QObject.__init__(self) + if u'path' not in kwargs: + raise KeyError(u'Missing keyword argument "path".') + if u'file' not in kwargs: + raise KeyError(u'Missing keyword argument "file".') + if u'path' in kwargs: + self.path = kwargs[u'path'] + if u'file' in kwargs: + self.file = kwargs[u'file'] + + def get_cursor(self): + """ + Return the cursor object. Instantiate one if it doesn't exist yet. + """ + if self.cursor is None: + filepath = os.path.join(self.path, self.file) + self.connection = sqlite3.connect(filepath) + self.cursor = self.connection.cursor() + return self.cursor + + def run_sql(self, query, parameters=()): + """ + Run an SQL query on the database, returning the results. + + ``query`` + The actual SQL query to run. + + ``parameters`` + Any variable parameters to add to the query. + """ + cursor = self.get_cursor() + cursor.execute(query, parameters) + return cursor.fetchall() + + def get_name(self): + """ + Returns the version name of the Bible. + """ + version_name = self.run_sql(u'SELECT value FROM ' + u'metadata WHERE key = "Version"') + if version_name: + self.name = version_name[0][0] + else: + self.name = None + return self.name + + def get_metadata(self): + """ + Returns the metadata of the Bible. + """ + metadata = self.run_sql(u'SELECT key, value FROM metadata ' + u'ORDER BY rowid') + if metadata: + return [ + { + u'key': unicode(meta[0]), + u'value': unicode(meta[1]) + } + for meta in metadata + ] + else: + return None + + def get_book(self, name): + """ + Return a book by name or abbreviation. + + ``name`` + The name or abbreviation of the book. + """ + if not isinstance(name, unicode): + name = unicode(name) + books = self.run_sql(u'SELECT id, testament_id, name, ' + u'abbreviation FROM book WHERE LOWER(name) = ? OR ' + u'LOWER(abbreviation) = ?', (name.lower(), name.lower())) + if books: + return { + u'id': books[0][0], + u'testament_id': books[0][1], + u'name': unicode(books[0][2]), + u'abbreviation': unicode(books[0][3]) + } + else: + return None + + def get_books(self): + """ + Returns the books of the Bible. + """ + books = self.run_sql(u'SELECT name, id FROM book ORDER BY id') + if books: + return [ + { + u'name': unicode(book[0]), + u'id':int(book[1]) + } + for book in books + ] + else: + return None + + def get_verses(self, book_id): + """ + Returns the verses of the Bible. + """ + verses = self.run_sql(u'SELECT book_id, chapter, verse, text FROM ' + u'verse WHERE book_id = ? ORDER BY id', (book_id, )) + if verses: + return [ + { + u'book_id': int(verse[0]), + u'chapter': int(verse[1]), + u'verse': int(verse[2]), + u'text': unicode(verse[3]) + } + for verse in verses + ] + else: + return None + + def close_connection(self): + self.cursor.close() + self.connection.close() diff --git a/openlp/plugins/bibles/lib/http.py b/openlp/plugins/bibles/lib/http.py index e2dde59fd..228d4758c 100644 --- a/openlp/plugins/bibles/lib/http.py +++ b/openlp/plugins/bibles/lib/http.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, Armin Köhler, 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,9 +29,7 @@ The :mod:`http` module enables OpenLP to retrieve scripture from bible websites. """ import logging -import os import re -import sqlite3 import socket import urllib from HTMLParser import HTMLParseError @@ -39,163 +38,28 @@ from BeautifulSoup import BeautifulSoup, NavigableString, Tag from openlp.core.lib import Receiver, translate from openlp.core.lib.ui import critical_error_message_box -from openlp.core.utils import AppLocation, get_web_page +from openlp.core.utils import get_web_page from openlp.plugins.bibles.lib import SearchResults -from openlp.plugins.bibles.lib.db import BibleDB, Book +from openlp.plugins.bibles.lib.db import BibleDB, BiblesResourcesDB, \ + Book log = logging.getLogger(__name__) -class HTTPBooks(object): - """ - A wrapper class around a small SQLite database which contains the books, - chapter counts and verse counts for the web download Bibles. This class - contains a singleton "cursor" so that only one connection to the SQLite - database is ever used. - """ - cursor = None - - @staticmethod - def get_cursor(): - """ - Return the cursor object. Instantiate one if it doesn't exist yet. - """ - if HTTPBooks.cursor is None: - filepath = os.path.join( - AppLocation.get_directory(AppLocation.PluginsDir), u'bibles', - u'resources', u'httpbooks.sqlite') - conn = sqlite3.connect(filepath) - HTTPBooks.cursor = conn.cursor() - return HTTPBooks.cursor - - @staticmethod - def run_sql(query, parameters=()): - """ - Run an SQL query on the database, returning the results. - - ``query`` - The actual SQL query to run. - - ``parameters`` - Any variable parameters to add to the query. - """ - cursor = HTTPBooks.get_cursor() - cursor.execute(query, parameters) - return cursor.fetchall() - - @staticmethod - def get_books(): - """ - Return a list of all the books of the Bible. - """ - books = HTTPBooks.run_sql(u'SELECT id, testament_id, name, ' - u'abbreviation, chapters FROM books ORDER BY id') - book_list = [] - for book in books: - book_list.append({ - u'id': book[0], - u'testament_id': book[1], - u'name': unicode(book[2]), - u'abbreviation': unicode(book[3]), - u'chapters': book[4] - }) - return book_list - - @staticmethod - def get_book(name): - """ - Return a book by name or abbreviation. - - ``name`` - The name or abbreviation of the book. - """ - if not isinstance(name, unicode): - name = unicode(name) - name = name.title() - books = HTTPBooks.run_sql(u'SELECT id, testament_id, name, ' - u'abbreviation, chapters FROM books WHERE name = ? OR ' - u'abbreviation = ?', (name, name)) - if books: - return { - u'id': books[0][0], - u'testament_id': books[0][1], - u'name': unicode(books[0][2]), - u'abbreviation': unicode(books[0][3]), - u'chapters': books[0][4] - } - else: - return None - - @staticmethod - def get_chapter(name, chapter): - """ - Return the chapter details for a specific chapter of a book. - - ``name`` - The name or abbreviation of a book. - - ``chapter`` - The chapter number. - """ - if not isinstance(name, int): - chapter = int(chapter) - book = HTTPBooks.get_book(name) - chapters = HTTPBooks.run_sql(u'SELECT id, book_id, chapter, ' - u'verses FROM chapters WHERE book_id = ?', (book[u'id'],)) - if chapters: - return { - u'id': chapters[chapter-1][0], - u'book_id': chapters[chapter-1][1], - u'chapter': chapters[chapter-1][2], - u'verses': chapters[chapter-1][3] - } - else: - return None - - @staticmethod - def get_chapter_count(book): - """ - Return the number of chapters in a book. - - ``book`` - The name or abbreviation of the book. - """ - details = HTTPBooks.get_book(book) - if details: - return details[u'chapters'] - return 0 - - @staticmethod - def get_verse_count(book, chapter): - """ - Return the number of verses in a chapter. - - ``book`` - The name or abbreviation of the book. - - ``chapter`` - The number of the chapter. - """ - details = HTTPBooks.get_chapter(book, chapter) - if details: - return details[u'verses'] - return 0 - - class BGExtract(object): """ Extract verses from BibleGateway """ def __init__(self, proxyurl=None): - log.debug(u'init %s', proxyurl) + log.debug(u'BGExtract.init("%s")', proxyurl) self.proxyurl = proxyurl socket.setdefaulttimeout(30) def get_bible_chapter(self, version, bookname, chapter): """ - Access and decode bibles via the BibleGateway website. + Access and decode Bibles via the BibleGateway website. ``version`` - The version of the bible like 31 for New International version. + The version of the Bible like 31 for New International version. ``bookname`` Name of the Book. @@ -203,10 +67,11 @@ class BGExtract(object): ``chapter`` Chapter number. """ - log.debug(u'get_bible_chapter %s, %s, %s', version, bookname, chapter) - url_params = urllib.urlencode( - {u'search': u'%s %s' % (bookname, chapter), - u'version': u'%s' % version}) + log.debug(u'BGExtract.get_bible_chapter("%s", "%s", "%s")', version, + bookname, chapter) + urlbookname = urllib.quote(bookname.encode("utf-8")) + url_params = u'search=%s+%s&version=%s' % (urlbookname, chapter, + version) cleaner = [(re.compile(' |
|\'\+\''), lambda match: '')] soup = get_soup_for_bible_ref( u'http://www.biblegateway.com/passage/?%s' % url_params, @@ -217,18 +82,25 @@ class BGExtract(object): Receiver.send_message(u'openlp_process_events') footnotes = soup.findAll(u'sup', u'footnote') if footnotes: - [footnote.extract() for footnote in footnotes] + for footnote in footnotes: + footnote.extract() crossrefs = soup.findAll(u'sup', u'xref') if crossrefs: - [crossref.extract() for crossref in crossrefs] + for crossref in crossrefs: + crossref.extract() headings = soup.findAll(u'h5') if headings: - [heading.extract() for heading in headings] + for heading in headings: + heading.extract() cleanup = [(re.compile('\s+'), lambda match: ' ')] verses = BeautifulSoup(str(soup), markupMassage=cleanup) verse_list = {} + # Cater for inconsistent mark up in the first verse of a chapter. + first_verse = verses.find(u'versenum') + if first_verse and len(first_verse.contents): + verse_list[1] = unicode(first_verse.contents[0]) for verse in verses(u'sup', u'versenum'): - raw_verse_num = verse.next + raw_verse_num = verse.next clean_verse_num = 0 # Not all verses exist in all translations and may or may not be # represented by a verse number. If they are not fine, if they are @@ -238,7 +110,7 @@ class BGExtract(object): try: clean_verse_num = int(str(raw_verse_num)) except ValueError: - log.exception(u'Illegal verse number in %s %s %s:%s', + log.warn(u'Illegal verse number in %s %s %s:%s', version, bookname, chapter, unicode(raw_verse_num)) if clean_verse_num: verse_text = raw_verse_num.next @@ -251,7 +123,7 @@ class BGExtract(object): if isinstance(part.next, Tag) and part.next.name == u'div': # Run out of verses so stop. break - part = part.next + part = part.next verse_list[clean_verse_num] = unicode(verse_text) if not verse_list: log.debug(u'No content found in the BibleGateway response.') @@ -259,13 +131,63 @@ class BGExtract(object): return None return SearchResults(bookname, chapter, verse_list) + def get_books_from_http(self, version): + """ + Load a list of all books a Bible contaions from BibleGateway website. + + ``version`` + The version of the Bible like NIV for New International Version + """ + log.debug(u'BGExtract.get_books_from_http("%s")', version) + url_params = urllib.urlencode( + {u'action': 'getVersionInfo', u'vid': u'%s' % version}) + reference_url = u'http://www.biblegateway.com/versions/?%s#books' % \ + url_params + page = get_web_page(reference_url) + if not page: + send_error_message(u'download') + return None + page_source = page.read() + try: + page_source = unicode(page_source, u'utf8') + except UnicodeDecodeError: + page_source = unicode(page_source, u'cp1251') + page_source_temp = re.search(u'.*?'\ + u'
', page_source, re.DOTALL) + if page_source_temp: + soup = page_source_temp.group(0) + else: + soup = None + try: + soup = BeautifulSoup(soup) + except HTMLParseError: + log.error(u'BeautifulSoup could not parse the Bible page.') + send_error_message(u'parse') + return None + if not soup: + send_error_message(u'parse') + return None + Receiver.send_message(u'openlp_process_events') + content = soup.find(u'table', {u'class': u'infotable'}) + content = content.findAll(u'tr') + if not content: + log.error(u'No books found in the Biblegateway response.') + send_error_message(u'parse') + return None + books = [] + for book in content: + book = book.find(u'td') + if book: + books.append(book.contents[0]) + return books + class BSExtract(object): """ Extract verses from Bibleserver.com """ def __init__(self, proxyurl=None): - log.debug(u'init %s', proxyurl) + log.debug(u'BSExtract.init("%s")', proxyurl) self.proxyurl = proxyurl socket.setdefaulttimeout(30) @@ -282,9 +204,12 @@ class BSExtract(object): ``chapter`` Chapter number """ - log.debug(u'get_bible_chapter %s,%s,%s', version, bookname, chapter) - chapter_url = u'http://m.bibleserver.com/text/%s/%s%s' % \ - (version, bookname, chapter) + log.debug(u'BSExtract.get_bible_chapter("%s", "%s", "%s")', version, + bookname, chapter) + urlversion = urllib.quote(version.encode("utf-8")) + urlbookname = urllib.quote(bookname.encode("utf-8")) + chapter_url = u'http://m.bibleserver.com/text/%s/%s%d' % \ + (urlversion, urlbookname, chapter) header = (u'Accept-Language', u'en') soup = get_soup_for_bible_ref(chapter_url, header) if not soup: @@ -292,11 +217,11 @@ class BSExtract(object): Receiver.send_message(u'openlp_process_events') content = soup.find(u'div', u'content') if not content: - log.exception(u'No verses found in the Bibleserver response.') + log.error(u'No verses found in the Bibleserver response.') send_error_message(u'parse') return None content = content.find(u'div').findAll(u'div') - verse_number = re.compile(r'v(\d{1,2})(\d{3})(\d{3}) verse') + verse_number = re.compile(r'v(\d{1,2})(\d{3})(\d{3}) verse.*') verses = {} for verse in content: Receiver.send_message(u'openlp_process_events') @@ -304,13 +229,38 @@ class BSExtract(object): verses[versenumber] = verse.contents[1].rstrip(u'\n') return SearchResults(bookname, chapter, verses) + def get_books_from_http(self, version): + """ + Load a list of all books a Bible contains from Bibleserver mobile + website. + + ``version`` + The version of the Bible like NIV for New International Version + """ + log.debug(u'BSExtract.get_books_from_http("%s")', version) + urlversion = urllib.quote(version.encode("utf-8")) + chapter_url = u'http://m.bibleserver.com/overlay/selectBook?'\ + 'translation=%s' % (urlversion) + soup = get_soup_for_bible_ref(chapter_url) + if not soup: + return None + content = soup.find(u'ul') + if not content: + log.error(u'No books found in the Bibleserver response.') + send_error_message(u'parse') + return None + content = content.findAll(u'li') + return [ + book.contents[0].contents[0] for book in content + ] + class CWExtract(object): """ Extract verses from CrossWalk/BibleStudyTools """ def __init__(self, proxyurl=None): - log.debug(u'init %s', proxyurl) + log.debug(u'CWExtract.init("%s")', proxyurl) self.proxyurl = proxyurl socket.setdefaulttimeout(30) @@ -319,7 +269,7 @@ class CWExtract(object): Access and decode bibles via the Crosswalk website ``version`` - The version of the bible like niv for New International Version + The version of the Bible like niv for New International Version ``bookname`` Text name of in english e.g. 'gen' for Genesis @@ -327,17 +277,20 @@ class CWExtract(object): ``chapter`` Chapter number """ - log.debug(u'get_bible_chapter %s,%s,%s', version, bookname, chapter) + log.debug(u'CWExtract.get_bible_chapter("%s", "%s", "%s")', version, + bookname, chapter) urlbookname = bookname.replace(u' ', u'-') + urlbookname = urlbookname.lower() + urlbookname = urllib.quote(urlbookname.encode("utf-8")) chapter_url = u'http://www.biblestudytools.com/%s/%s/%s.html' % \ - (version, urlbookname.lower(), chapter) + (version, urlbookname, chapter) soup = get_soup_for_bible_ref(chapter_url) if not soup: return None Receiver.send_message(u'openlp_process_events') htmlverses = soup.findAll(u'span', u'versetext') if not htmlverses: - log.debug(u'No verses found in the CrossWalk response.') + log.error(u'No verses found in the CrossWalk response.') send_error_message(u'parse') return None verses = {} @@ -373,6 +326,32 @@ class CWExtract(object): verses[versenumber] = versetext return SearchResults(bookname, chapter, verses) + def get_books_from_http(self, version): + """ + Load a list of all books a Bible contain from the Crosswalk website. + + ``version`` + The version of the bible like NIV for New International Version + """ + log.debug(u'CWExtract.get_books_from_http("%s")', version) + chapter_url = u'http://www.biblestudytools.com/%s/'\ + % (version) + soup = get_soup_for_bible_ref(chapter_url) + if not soup: + return None + content = soup.find(u'div', {u'class': u'Body'}) + content = content.find(u'ul', {u'class': u'parent'}) + if not content: + log.error(u'No books found in the Crosswalk response.') + send_error_message(u'parse') + return None + content = content.findAll(u'li') + books = [] + for book in content: + book = book.find(u'a') + books.append(book.contents[0]) + return books + class HTTPBible(BibleDB): log.info(u'%s HTTPBible loaded' , __name__) @@ -395,6 +374,8 @@ class HTTPBible(BibleDB): self.proxy_server = None self.proxy_username = None self.proxy_password = None + if u'path' in kwargs: + self.path = kwargs[u'path'] if u'proxy_server' in kwargs: self.proxy_server = kwargs[u'proxy_server'] if u'proxy_username' in kwargs: @@ -402,13 +383,15 @@ class HTTPBible(BibleDB): if u'proxy_password' in kwargs: self.proxy_password = kwargs[u'proxy_password'] - def do_import(self): + def do_import(self, bible_name=None): """ Run the import. This method overrides the parent class method. Returns ``True`` on success, ``False`` on failure. """ - self.wizard.progressBar.setMaximum(2) - self.wizard.incrementProgressBar('Registering bible...') + self.wizard.progressBar.setMaximum(68) + self.wizard.incrementProgressBar(unicode(translate( + 'BiblesPlugin.HTTPBible', + 'Registering Bible and loading books...'))) self.create_meta(u'download source', self.download_source) self.create_meta(u'download name', self.download_name) if self.proxy_server: @@ -419,9 +402,53 @@ class HTTPBible(BibleDB): if self.proxy_password: # Store the proxy password. self.create_meta(u'proxy password', self.proxy_password) - return True + if self.download_source.lower() == u'crosswalk': + handler = CWExtract(self.proxy_server) + elif self.download_source.lower() == u'biblegateway': + handler = BGExtract(self.proxy_server) + elif self.download_source.lower() == u'bibleserver': + handler = BSExtract(self.proxy_server) + books = handler.get_books_from_http(self.download_name) + if not books: + log.exception(u'Importing books from %s - download name: "%s" '\ + 'failed' % (self.download_source, self.download_name)) + return False + self.wizard.progressBar.setMaximum(len(books)+2) + self.wizard.incrementProgressBar(unicode(translate( + 'BiblesPlugin.HTTPBible', 'Registering Language...'))) + bible = BiblesResourcesDB.get_webbible(self.download_name, + self.download_source.lower()) + if bible[u'language_id']: + language_id = bible[u'language_id'] + self.create_meta(u'language_id', language_id) + else: + language_id = self.get_language(bible_name) + if not language_id: + log.exception(u'Importing books from %s " '\ + 'failed' % self.filename) + return False + for book in books: + if self.stop_import_flag: + break + self.wizard.incrementProgressBar(unicode(translate( + 'BiblesPlugin.HTTPBible', 'Importing %s...', + 'Importing ...')) % book) + book_ref_id = self.get_book_ref_id_by_name(book, len(books), + language_id) + if not book_ref_id: + log.exception(u'Importing books from %s - download name: "%s" '\ + 'failed' % (self.download_source, self.download_name)) + return False + book_details = BiblesResourcesDB.get_book_by_id(book_ref_id) + log.debug(u'Book details: Name:%s; id:%s; testament_id:%s', + book, book_ref_id, book_details[u'testament_id']) + self.create_book(book, book_ref_id, book_details[u'testament_id']) + if self.stop_import_flag: + return False + else: + return True - def get_verses(self, reference_list): + def get_verses(self, reference_list, show_error=True): """ A reimplementation of the ``BibleDB.get_verses`` method, this one is specifically for web Bibles. It first checks to see if the particular @@ -433,33 +460,29 @@ class HTTPBible(BibleDB): This is the list of references the media manager item wants. It is a list of tuples, with the following format:: - (book, chapter, start_verse, end_verse) + (book_reference_id, chapter, start_verse, end_verse) Therefore, when you are looking for multiple items, simply break them up into references like this, bundle them into a list. This function then runs through the list, and returns an amalgamated list of ``Verse`` objects. For example:: - [(u'Genesis', 1, 1, 1), (u'Genesis', 2, 2, 3)] + [(u'35', 1, 1, 1), (u'35', 2, 2, 3)] """ + log.debug(u'HTTPBible.get_verses("%s")', reference_list) for reference in reference_list: - log.debug(u'Reference: %s', reference) - book = reference[0] - db_book = self.get_book(book) + book_id = reference[0] + db_book = self.get_book_by_book_ref_id(book_id) if not db_book: - book_details = HTTPBooks.get_book(book) - if not book_details: + if show_error: critical_error_message_box( translate('BiblesPlugin', 'No Book Found'), translate('BiblesPlugin', 'No matching ' 'book could be found in this Bible. Check that you ' 'have spelled the name of the book correctly.')) - return [] - db_book = self.create_book(book_details[u'name'], - book_details[u'abbreviation'], - book_details[u'testament_id']) + return [] book = db_book.name - if BibleDB.get_verse_count(self, book, reference[1]) == 0: + if BibleDB.get_verse_count(self, book_id, reference[1]) == 0: Receiver.send_message(u'cursor_busy') search_results = self.get_chapter(book, reference[1]) if search_results and search_results.has_verselist(): @@ -476,13 +499,13 @@ class HTTPBible(BibleDB): Receiver.send_message(u'openlp_process_events') Receiver.send_message(u'cursor_normal') Receiver.send_message(u'openlp_process_events') - return BibleDB.get_verses(self, reference_list) + return BibleDB.get_verses(self, reference_list, show_error) def get_chapter(self, book, chapter): """ Receive the request and call the relevant handler methods. """ - log.debug(u'get_chapter %s, %s', book, chapter) + log.debug(u'HTTPBible.get_chapter("%s", "%s")', book, chapter) log.debug(u'source = %s', self.download_source) if self.download_source.lower() == u'crosswalk': handler = CWExtract(self.proxy_server) @@ -496,16 +519,20 @@ class HTTPBible(BibleDB): """ Return the list of books. """ - return [Book.populate(name=book['name']) - for book in HTTPBooks.get_books()] + log.debug(u'HTTPBible.get_books("%s")', Book.name) + return self.get_all_objects(Book, order_by_ref=Book.id) def get_chapter_count(self, book): """ Return the number of chapters in a particular book. - """ - return HTTPBooks.get_chapter_count(book) - def get_verse_count(self, book, chapter): + ``book`` + The book object to get the chapter count for. + """ + log.debug(u'HTTPBible.get_chapter_count("%s")', book.name) + return BiblesResourcesDB.get_chapter_count(book.book_reference_id) + + def get_verse_count(self, book_id, chapter): """ Return the number of verses for the specified chapter and book. @@ -515,7 +542,8 @@ class HTTPBible(BibleDB): ``chapter`` The chapter whose verses are being counted. """ - return HTTPBooks.get_verse_count(book, chapter) + log.debug(u'HTTPBible.get_verse_count("%s", %s)', book_id, chapter) + return BiblesResourcesDB.get_verse_count(book_id, chapter) def get_soup_for_bible_ref(reference_url, header=None, pre_parse_regex=None, pre_parse_substitute=None, cleaner=None): @@ -570,14 +598,14 @@ def send_error_message(error_type): """ if error_type == u'download': critical_error_message_box( - translate('BiblePlugin.HTTPBible', 'Download Error'), - translate('BiblePlugin.HTTPBible', 'There was a ' + translate('BiblesPlugin.HTTPBible', 'Download Error'), + translate('BiblesPlugin.HTTPBible', 'There was a ' 'problem downloading your verse selection. Please check your ' 'Internet connection, and if this error continues to occur ' 'please consider reporting a bug.')) elif error_type == u'parse': critical_error_message_box( - translate('BiblePlugin.HTTPBible', 'Parse Error'), - translate('BiblePlugin.HTTPBible', 'There was a ' + translate('BiblesPlugin.HTTPBible', 'Parse Error'), + translate('BiblesPlugin.HTTPBible', 'There was a ' 'problem extracting your verse selection. If this error continues ' 'to occur please consider reporting a bug.')) diff --git a/openlp/plugins/bibles/lib/manager.py b/openlp/plugins/bibles/lib/manager.py index dcbad3e63..934aa2d90 100644 --- a/openlp/plugins/bibles/lib/manager.py +++ b/openlp/plugins/bibles/lib/manager.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, Armin Köhler, 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,14 +26,15 @@ ############################################################################### import logging +import os from PyQt4 import QtCore from openlp.core.lib import Receiver, SettingsManager, translate -from openlp.core.utils import AppLocation +from openlp.core.lib.ui import critical_error_message_box +from openlp.core.utils import AppLocation, delete_file from openlp.plugins.bibles.lib import parse_reference from openlp.plugins.bibles.lib.db import BibleDB, BibleMeta - from csvbible import CSVBible from http import HTTPBible from opensong import OpenSongBible @@ -139,11 +141,24 @@ class BibleManager(object): """ log.debug(u'Reload bibles') files = SettingsManager.get_files(self.settingsSection, self.suffix) + if u'alternative_book_names.sqlite' in files: + files.remove(u'alternative_book_names.sqlite') log.debug(u'Bible Files %s', files) self.db_cache = {} + self.old_bible_databases = [] for filename in files: bible = BibleDB(self.parent, path=self.path, file=filename) name = bible.get_name() + # Remove corrupted files. + if name is None: + bible.session.close() + delete_file(os.path.join(self.path, filename)) + continue + # Find old database versions. + if bible.is_old_database(): + self.old_bible_databases.append([filename, name]) + bible.session.close() + continue log.debug(u'Bible Name: "%s"', name) self.db_cache[name] = bible # Look to see if lazy load bible exists and get create getter. @@ -206,7 +221,8 @@ class BibleManager(object): return [ { u'name': book.name, - u'chapters': self.db_cache[bible].get_chapter_count(book.name) + u'book_reference_id': book.book_reference_id, + u'chapters': self.db_cache[bible].get_chapter_count(book) } for book in self.db_cache[bible].get_books() ] @@ -214,8 +230,15 @@ class BibleManager(object): def get_chapter_count(self, bible, book): """ Returns the number of Chapters for a given book. + + ``bible`` + Unicode. The Bible to get the list of books from. + + ``book`` + The book object to get the chapter count for. """ - log.debug(u'get_book_chapter_count %s', book) + log.debug(u'BibleManager.get_book_chapter_count ("%s", "%s")', bible, + book.name) return self.db_cache[bible].get_chapter_count(book) def get_verse_count(self, bible, book, chapter): @@ -225,9 +248,11 @@ class BibleManager(object): """ log.debug(u'BibleManager.get_verse_count("%s", "%s", %s)', bible, book, chapter) - return self.db_cache[bible].get_verse_count(book, chapter) + db_book = self.db_cache[bible].get_book(book) + book_ref_id = db_book.book_reference_id + return self.db_cache[bible].get_verse_count(book_ref_id, chapter) - def get_verses(self, bible, versetext): + def get_verses(self, bible, versetext, firstbible=False, show_error=True): """ Parses a scripture reference, fetches the verses from the Bible specified, and returns a list of ``Verse`` objects. @@ -248,32 +273,56 @@ class BibleManager(object): """ log.debug(u'BibleManager.get_verses("%s", "%s")', bible, versetext) if not bible: - Receiver.send_message(u'openlp_information_message', { - u'title': translate('BiblesPlugin.BibleManager', - 'No Bibles Available'), - u'message': translate('BiblesPlugin.BibleManager', - 'There are no Bibles currently installed. Please use the ' - 'Import Wizard to install one or more Bibles.') - }) + if show_error: + Receiver.send_message(u'openlp_information_message', { + u'title': translate('BiblesPlugin.BibleManager', + 'No Bibles Available'), + u'message': translate('BiblesPlugin.BibleManager', + 'There are no Bibles currently installed. Please use the ' + 'Import Wizard to install one or more Bibles.') + }) return None reflist = parse_reference(versetext) if reflist: - return self.db_cache[bible].get_verses(reflist) + new_reflist = [] + for item in reflist: + if item: + if firstbible: + db_book = self.db_cache[firstbible].get_book(item[0]) + db_book = self.db_cache[bible].get_book_by_book_ref_id( + db_book.book_reference_id) + else: + db_book = self.db_cache[bible].get_book(item[0]) + if db_book: + book_id = db_book.book_reference_id + log.debug(u'Book name corrected to "%s"', db_book.name) + new_reflist.append((book_id, item[1], item[2], + item[3])) + else: + log.debug(u'OpenLP failed to find book %s', item[0]) + critical_error_message_box( + translate('BiblesPlugin', 'No Book Found'), + translate('BiblesPlugin', 'No matching book ' + 'could be found in this Bible. Check that you have ' + 'spelled the name of the book correctly.')) + reflist = new_reflist + return self.db_cache[bible].get_verses(reflist, show_error) else: - Receiver.send_message(u'openlp_information_message', { - u'title': translate('BiblesPlugin.BibleManager', - 'Scripture Reference Error'), - u'message': translate('BiblesPlugin.BibleManager', - 'Your scripture reference is either not supported by OpenLP ' - 'or is invalid. Please make sure your reference conforms to ' - 'one of the following patterns:\n\n' - 'Book Chapter\n' - 'Book Chapter-Chapter\n' - 'Book Chapter:Verse-Verse\n' - 'Book Chapter:Verse-Verse,Verse-Verse\n' - 'Book Chapter:Verse-Verse,Chapter:Verse-Verse\n' - 'Book Chapter:Verse-Chapter:Verse') - }) + if show_error: + Receiver.send_message(u'openlp_information_message', { + u'title': translate('BiblesPlugin.BibleManager', + 'Scripture Reference Error'), + u'message': translate('BiblesPlugin.BibleManager', + 'Your scripture reference is either not supported by ' + 'OpenLP or is invalid. Please make sure your reference ' + 'conforms to one of the following patterns:\n\n' + 'Book Chapter\n' + 'Book Chapter-Chapter\n' + 'Book Chapter:Verse-Verse\n' + 'Book Chapter:Verse-Verse,Verse-Verse\n' + 'Book Chapter:Verse-Verse,Chapter:Verse-Verse\n' + 'Book Chapter:Verse-Chapter:Verse') + }) return None def verse_search(self, bible, second_bible, text): @@ -281,7 +330,7 @@ class BibleManager(object): Does a verse search for the given bible and text. ``bible`` - The bible to seach in (unicode). + The bible to search in (unicode). ``second_bible`` The second bible (unicode). We do not search in this bible. @@ -290,6 +339,15 @@ class BibleManager(object): The text to search for (unicode). """ log.debug(u'BibleManager.verse_search("%s", "%s")', bible, text) + if not bible: + Receiver.send_message(u'openlp_information_message', { + u'title': translate('BiblesPlugin.BibleManager', + 'No Bibles Available'), + u'message': translate('BiblesPlugin.BibleManager', + 'There are no Bibles currently installed. Please use the ' + 'Import Wizard to install one or more Bibles.') + }) + return None # Check if the bible or second_bible is a web bible. webbible = self.db_cache[bible].get_object(BibleMeta, u'download source') diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index 690564182..31effe189 100644 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.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, Armin Köhler, 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,6 +26,7 @@ ############################################################################### import logging +import locale from PyQt4 import QtCore, QtGui @@ -32,10 +34,11 @@ from openlp.core.lib import MediaManagerItem, Receiver, ItemCapabilities, \ translate from openlp.core.lib.searchedit import SearchEdit from openlp.core.lib.ui import UiStrings, add_widget_completer, \ - media_item_combo_box, critical_error_message_box + media_item_combo_box, critical_error_message_box, \ + find_and_set_in_combo_box, build_icon from openlp.plugins.bibles.forms import BibleImportForm from openlp.plugins.bibles.lib import LayoutStyle, DisplayStyle, \ - get_reference_match + VerseReferenceList, get_reference_match log = logging.getLogger(__name__) @@ -55,14 +58,52 @@ class BibleMediaItem(MediaManagerItem): def __init__(self, parent, plugin, icon): self.IconPath = u'songs/song' + self.lockIcon = build_icon(u':/bibles/bibles_search_lock.png') + self.unlockIcon = build_icon(u':/bibles/bibles_search_unlock.png') MediaManagerItem.__init__(self, parent, plugin, icon) # Place to store the search results for both bibles. - self.settings = self.parent.settings_tab + self.settings = self.plugin.settings_tab + self.quickPreviewAllowed = True + self.hasSearch = True self.search_results = {} self.second_search_results = {} + self.checkSearchResult() QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'bibles_load_list'), self.reloadBibles) + def __checkSecondBible(self, bible, second_bible): + """ + Check if the first item is a second bible item or not. + """ + bitem = self.listView.item(0) + if not bitem.flags() & QtCore.Qt.ItemIsSelectable: + # The item is the "No Search Results" item. + self.listView.clear() + self.displayResults(bible, second_bible) + return + else: + item_second_bible = self._decodeQtObject(bitem, 'second_bible') + if item_second_bible and second_bible or not item_second_bible and \ + not second_bible: + self.displayResults(bible, second_bible) + elif critical_error_message_box( + message=translate('BiblesPlugin.MediaItem', + 'You cannot combine single and dual Bible verse search results. ' + 'Do you want to delete your search results and start a new ' + 'search?'), + parent=self, question=True) == QtGui.QMessageBox.Yes: + self.listView.clear() + self.displayResults(bible, second_bible) + + def _decodeQtObject(self, bitem, key): + reference = bitem.data(QtCore.Qt.UserRole) + if isinstance(reference, QtCore.QVariant): + reference = reference.toPyObject() + obj = reference[QtCore.QString(key)] + if isinstance(obj, QtCore.QVariant): + obj = obj.toPyObject() + return unicode(obj).strip() + def requiredIcons(self): MediaManagerItem.requiredIcons(self) self.hasImportIcon = True @@ -71,133 +112,142 @@ class BibleMediaItem(MediaManagerItem): self.hasDeleteIcon = False self.addToServiceItem = False + def addSearchTab(self, prefix, name): + self.searchTabBar.addTab(name) + tab = QtGui.QWidget() + tab.setObjectName(prefix + u'Tab') + tab.setSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Minimum) + layout = QtGui.QGridLayout(tab) + layout.setObjectName(prefix + u'Layout') + setattr(self, prefix + u'Tab', tab) + setattr(self, prefix + u'Layout', layout) + + def addSearchFields(self, prefix, name): + """ + Creates and adds generic search tab. + + ``prefix`` + The prefix of the tab, this is either ``quick`` or ``advanced``. + + ``name`` + The translated string to display. + """ + if prefix == u'quick': + idx = 2 + else: + idx = 5 + tab = getattr(self, prefix + u'Tab') + layout = getattr(self, prefix + u'Layout') + versionLabel = QtGui.QLabel(tab) + versionLabel.setObjectName(prefix + u'VersionLabel') + layout.addWidget(versionLabel, idx, 0, QtCore.Qt.AlignRight) + versionComboBox = media_item_combo_box(tab, + prefix + u'VersionComboBox') + versionLabel.setBuddy(versionComboBox) + layout.addWidget(versionComboBox, idx, 1, 1, 2) + secondLabel = QtGui.QLabel(tab) + secondLabel.setObjectName(prefix + u'SecondLabel') + layout.addWidget(secondLabel, idx + 1, 0, QtCore.Qt.AlignRight) + secondComboBox = media_item_combo_box(tab, prefix + u'SecondComboBox') + versionLabel.setBuddy(secondComboBox) + layout.addWidget(secondComboBox, idx + 1, 1, 1, 2) + styleLabel = QtGui.QLabel(tab) + styleLabel.setObjectName(prefix + u'StyleLabel') + layout.addWidget(styleLabel, idx + 2, 0, QtCore.Qt.AlignRight) + styleComboBox = media_item_combo_box(tab, prefix + u'StyleComboBox') + styleComboBox.addItems([u'', u'', u'']) + layout.addWidget(styleComboBox, idx + 2, 1, 1, 2) + searchButtonLayout = QtGui.QHBoxLayout() + searchButtonLayout.setObjectName(prefix + u'SearchButtonLayout') + searchButtonLayout.addStretch() + lockButton = QtGui.QToolButton(tab) + lockButton.setIcon(self.unlockIcon) + lockButton.setCheckable(True) + lockButton.setObjectName(prefix + u'LockButton') + searchButtonLayout.addWidget(lockButton) + searchButton = QtGui.QPushButton(tab) + searchButton.setObjectName(prefix + u'SearchButton') + searchButtonLayout.addWidget(searchButton) + layout.addLayout(searchButtonLayout, idx + 3, 1, 1, 2) + self.pageLayout.addWidget(tab) + tab.setVisible(False) + QtCore.QObject.connect(lockButton, QtCore.SIGNAL(u'toggled(bool)'), + self.onLockButtonToggled) + setattr(self, prefix + u'VersionLabel', versionLabel) + setattr(self, prefix + u'VersionComboBox', versionComboBox) + setattr(self, prefix + u'SecondLabel', secondLabel) + setattr(self, prefix + u'SecondComboBox', secondComboBox) + setattr(self, prefix + u'StyleLabel', styleLabel) + setattr(self, prefix + u'StyleComboBox', styleComboBox) + setattr(self, prefix + u'LockButton', lockButton) + setattr(self, prefix + u'SearchButtonLayout', searchButtonLayout) + setattr(self, prefix + u'SearchButton', searchButton) + def addEndHeaderBar(self): - self.searchTabWidget = QtGui.QTabWidget(self) - self.searchTabWidget.setSizePolicy( - QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Minimum) - self.searchTabWidget.setObjectName(u'SearchTabWidget') + self.searchTabBar = QtGui.QTabBar(self) + self.searchTabBar.setExpanding(False) + self.searchTabBar.setObjectName(u'searchTabBar') + self.pageLayout.addWidget(self.searchTabBar) # Add the Quick Search tab. - self.quickTab = QtGui.QWidget() - self.quickTab.setObjectName(u'quickTab') - self.quickLayout = QtGui.QFormLayout(self.quickTab) - self.quickLayout.setObjectName(u'quickLayout') - self.quickVersionLabel = QtGui.QLabel(self.quickTab) - self.quickVersionLabel.setObjectName(u'quickVersionLabel') - self.quickVersionComboBox = media_item_combo_box(self.quickTab, - u'quickVersionComboBox') - self.quickVersionLabel.setBuddy(self.quickVersionComboBox) - self.quickLayout.addRow(self.quickVersionLabel, - self.quickVersionComboBox) - self.quickSecondLabel = QtGui.QLabel(self.quickTab) - self.quickSecondLabel.setObjectName(u'quickSecondLabel') - self.quickSecondComboBox = media_item_combo_box(self.quickTab, - u'quickSecondComboBox') - self.quickSecondLabel.setBuddy(self.quickSecondComboBox) - self.quickLayout.addRow(self.quickSecondLabel, self.quickSecondComboBox) + self.addSearchTab( + u'quick', translate('BiblesPlugin.MediaItem', 'Quick')) self.quickSearchLabel = QtGui.QLabel(self.quickTab) self.quickSearchLabel.setObjectName(u'quickSearchLabel') + self.quickLayout.addWidget( + self.quickSearchLabel, 0, 0, QtCore.Qt.AlignRight) self.quickSearchEdit = SearchEdit(self.quickTab) self.quickSearchEdit.setObjectName(u'quickSearchEdit') self.quickSearchLabel.setBuddy(self.quickSearchEdit) - self.quickSearchEdit.setSearchTypes([ - (BibleSearch.Reference, u':/bibles/bibles_search_reference.png', - translate('BiblesPlugin.MediaItem', 'Scripture Reference')), - (BibleSearch.Text, u':/bibles/bibles_search_text.png', - translate('BiblesPlugin.MediaItem', 'Text Search')) - ]) - self.quickLayout.addRow(self.quickSearchLabel, self.quickSearchEdit) - self.quickClearLabel = QtGui.QLabel(self.quickTab) - self.quickClearLabel.setObjectName(u'quickClearLabel') - self.quickClearComboBox = media_item_combo_box(self.quickTab, - u'quickClearComboBox') - self.quickLayout.addRow(self.quickClearLabel, self.quickClearComboBox) - self.quickSearchButtonLayout = QtGui.QHBoxLayout() - self.quickSearchButtonLayout.setObjectName(u'quickSearchButtonLayout') - self.quickSearchButtonLayout.addStretch() - self.quickSearchButton = QtGui.QPushButton(self.quickTab) - self.quickSearchButton.setObjectName(u'quickSearchButton') - self.quickSearchButtonLayout.addWidget(self.quickSearchButton) - self.quickLayout.addRow(self.quickSearchButtonLayout) - self.searchTabWidget.addTab(self.quickTab, - translate('BiblesPlugin.MediaItem', 'Quick')) + self.quickLayout.addWidget(self.quickSearchEdit, 0, 1, 1, 2) + self.addSearchFields( + u'quick', translate('BiblesPlugin.MediaItem', 'Quick')) + self.quickTab.setVisible(True) # Add the Advanced Search tab. - self.advancedTab = QtGui.QWidget() - self.advancedTab.setObjectName(u'advancedTab') - self.advancedLayout = QtGui.QGridLayout(self.advancedTab) - self.advancedLayout.setObjectName(u'advancedLayout') - self.advancedVersionLabel = QtGui.QLabel(self.advancedTab) - self.advancedVersionLabel.setObjectName(u'advancedVersionLabel') - self.advancedLayout.addWidget(self.advancedVersionLabel, 0, 0, - QtCore.Qt.AlignRight) - self.advancedVersionComboBox = media_item_combo_box(self.advancedTab, - u'advancedVersionComboBox') - self.advancedVersionLabel.setBuddy(self.advancedVersionComboBox) - self.advancedLayout.addWidget(self.advancedVersionComboBox, 0, 1, 1, 2) - self.advancedSecondLabel = QtGui.QLabel(self.advancedTab) - self.advancedSecondLabel.setObjectName(u'advancedSecondLabel') - self.advancedLayout.addWidget(self.advancedSecondLabel, 1, 0, - QtCore.Qt.AlignRight) - self.advancedSecondComboBox = media_item_combo_box(self.advancedTab, - u'advancedSecondComboBox') - self.advancedSecondLabel.setBuddy(self.advancedSecondComboBox) - self.advancedLayout.addWidget(self.advancedSecondComboBox, 1, 1, 1, 2) + self.addSearchTab(u'advanced', UiStrings().Advanced) self.advancedBookLabel = QtGui.QLabel(self.advancedTab) self.advancedBookLabel.setObjectName(u'advancedBookLabel') - self.advancedLayout.addWidget(self.advancedBookLabel, 2, 0, + self.advancedLayout.addWidget(self.advancedBookLabel, 0, 0, QtCore.Qt.AlignRight) self.advancedBookComboBox = media_item_combo_box(self.advancedTab, u'advancedBookComboBox') self.advancedBookLabel.setBuddy(self.advancedBookComboBox) - self.advancedLayout.addWidget(self.advancedBookComboBox, 2, 1, 1, 2) + self.advancedLayout.addWidget(self.advancedBookComboBox, 0, 1, 1, 2) self.advancedChapterLabel = QtGui.QLabel(self.advancedTab) self.advancedChapterLabel.setObjectName(u'advancedChapterLabel') - self.advancedLayout.addWidget(self.advancedChapterLabel, 3, 1) + self.advancedLayout.addWidget(self.advancedChapterLabel, 1, 1, 1, 2) self.advancedVerseLabel = QtGui.QLabel(self.advancedTab) self.advancedVerseLabel.setObjectName(u'advancedVerseLabel') - self.advancedLayout.addWidget(self.advancedVerseLabel, 3, 2) + self.advancedLayout.addWidget(self.advancedVerseLabel, 1, 2) self.advancedFromLabel = QtGui.QLabel(self.advancedTab) self.advancedFromLabel.setObjectName(u'advancedFromLabel') - self.advancedLayout.addWidget(self.advancedFromLabel, 4, 0, + self.advancedLayout.addWidget(self.advancedFromLabel, 3, 0, QtCore.Qt.AlignRight) self.advancedFromChapter = QtGui.QComboBox(self.advancedTab) self.advancedFromChapter.setObjectName(u'advancedFromChapter') - self.advancedLayout.addWidget(self.advancedFromChapter, 4, 1) + self.advancedLayout.addWidget(self.advancedFromChapter, 3, 1) self.advancedFromVerse = QtGui.QComboBox(self.advancedTab) self.advancedFromVerse.setObjectName(u'advancedFromVerse') - self.advancedLayout.addWidget(self.advancedFromVerse, 4, 2) + self.advancedLayout.addWidget(self.advancedFromVerse, 3, 2) self.advancedToLabel = QtGui.QLabel(self.advancedTab) self.advancedToLabel.setObjectName(u'advancedToLabel') - self.advancedLayout.addWidget(self.advancedToLabel, 5, 0, + self.advancedLayout.addWidget(self.advancedToLabel, 4, 0, QtCore.Qt.AlignRight) self.advancedToChapter = QtGui.QComboBox(self.advancedTab) self.advancedToChapter.setObjectName(u'advancedToChapter') - self.advancedLayout.addWidget(self.advancedToChapter, 5, 1) + self.advancedLayout.addWidget(self.advancedToChapter, 4, 1) self.advancedToVerse = QtGui.QComboBox(self.advancedTab) self.advancedToVerse.setObjectName(u'advancedToVerse') - self.advancedLayout.addWidget(self.advancedToVerse, 5, 2) - self.advancedClearLabel = QtGui.QLabel(self.quickTab) - self.advancedClearLabel.setObjectName(u'advancedClearLabel') - self.advancedLayout.addWidget(self.advancedClearLabel, 6, 0, - QtCore.Qt.AlignRight) - self.advancedClearComboBox = media_item_combo_box(self.quickTab, - u'advancedClearComboBox') - self.advancedClearLabel.setBuddy(self.advancedClearComboBox) - self.advancedLayout.addWidget(self.advancedClearComboBox, 6, 1, 1, 2) - self.advancedSearchButtonLayout = QtGui.QHBoxLayout() - self.advancedSearchButtonLayout.setObjectName( - u'advancedSearchButtonLayout') - self.advancedSearchButtonLayout.addStretch() - self.advancedSearchButton = QtGui.QPushButton(self.advancedTab) - self.advancedSearchButton.setObjectName(u'advancedSearchButton') - self.advancedSearchButtonLayout.addWidget(self.advancedSearchButton) - self.advancedLayout.addLayout( - self.advancedSearchButtonLayout, 7, 0, 1, 3) - self.searchTabWidget.addTab(self.advancedTab, UiStrings.Advanced) - # Add the search tab widget to the page layout. - self.pageLayout.addWidget(self.searchTabWidget) + self.advancedLayout.addWidget(self.advancedToVerse, 4, 2) + self.addSearchFields(u'advanced', UiStrings().Advanced) # Combo Boxes + QtCore.QObject.connect(self.quickVersionComboBox, + QtCore.SIGNAL(u'activated(int)'), self.onQuickVersionComboBox) + QtCore.QObject.connect(self.quickSecondComboBox, + QtCore.SIGNAL(u'activated(int)'), self.onQuickSecondComboBox) QtCore.QObject.connect(self.advancedVersionComboBox, QtCore.SIGNAL(u'activated(int)'), self.onAdvancedVersionComboBox) + QtCore.QObject.connect(self.advancedSecondComboBox, + QtCore.SIGNAL(u'activated(int)'), self.onAdvancedSecondComboBox) QtCore.QObject.connect(self.advancedBookComboBox, QtCore.SIGNAL(u'activated(int)'), self.onAdvancedBookComboBox) QtCore.QObject.connect(self.advancedFromChapter, @@ -210,6 +260,12 @@ class BibleMediaItem(MediaManagerItem): QtCore.SIGNAL(u'searchTypeChanged(int)'), self.updateAutoCompleter) QtCore.QObject.connect(self.quickVersionComboBox, QtCore.SIGNAL(u'activated(int)'), self.updateAutoCompleter) + QtCore.QObject.connect( + self.quickStyleComboBox, QtCore.SIGNAL(u'activated(int)'), + self.onQuickStyleComboBoxChanged) + QtCore.QObject.connect( + self.advancedStyleComboBox, QtCore.SIGNAL(u'activated(int)'), + self.onAdvancedStyleComboBoxChanged) # Buttons QtCore.QObject.connect(self.advancedSearchButton, QtCore.SIGNAL(u'pressed()'), self.onAdvancedSearchButton) @@ -220,6 +276,15 @@ class BibleMediaItem(MediaManagerItem): # Other stuff QtCore.QObject.connect(self.quickSearchEdit, QtCore.SIGNAL(u'returnPressed()'), self.onQuickSearchButton) + QtCore.QObject.connect(self.searchTabBar, + QtCore.SIGNAL(u'currentChanged(int)'), + self.onSearchTabBarCurrentChanged) + + def onFocus(self): + if self.quickTab.isVisible(): + self.quickSearchEdit.setFocus() + else: + self.advancedBookComboBox.setFocus() def configUpdated(self): log.debug(u'configUpdated') @@ -234,20 +299,26 @@ class BibleMediaItem(MediaManagerItem): self.advancedSecondComboBox.setVisible(False) self.quickSecondLabel.setVisible(False) self.quickSecondComboBox.setVisible(False) + self.quickStyleComboBox.setCurrentIndex(self.settings.layout_style) + self.advancedStyleComboBox.setCurrentIndex(self.settings.layout_style) def retranslateUi(self): log.debug(u'retranslateUi') - self.quickVersionLabel.setText(u'%s:' % UiStrings.Version) - self.quickSecondLabel.setText( - translate('BiblesPlugin.MediaItem', 'Second:')) self.quickSearchLabel.setText( translate('BiblesPlugin.MediaItem', 'Find:')) - self.quickSearchButton.setText(UiStrings.Search) - self.quickClearLabel.setText( - translate('BiblesPlugin.MediaItem', 'Results:')) - self.advancedVersionLabel.setText(u'%s:' % UiStrings.Version) - self.advancedSecondLabel.setText( + self.quickVersionLabel.setText(u'%s:' % UiStrings().Version) + self.quickSecondLabel.setText( translate('BiblesPlugin.MediaItem', 'Second:')) + self.quickStyleLabel.setText(UiStrings().LayoutStyle) + self.quickStyleComboBox.setItemText(LayoutStyle.VersePerSlide, + UiStrings().VersePerSlide) + self.quickStyleComboBox.setItemText(LayoutStyle.VersePerLine, + UiStrings().VersePerLine) + self.quickStyleComboBox.setItemText(LayoutStyle.Continuous, + UiStrings().Continuous) + self.quickLockButton.setToolTip(translate('BiblesPlugin.MediaItem', + 'Toggle to keep or clear the previous results.')) + self.quickSearchButton.setText(UiStrings().Search) self.advancedBookLabel.setText( translate('BiblesPlugin.MediaItem', 'Book:')) self.advancedChapterLabel.setText( @@ -258,34 +329,40 @@ class BibleMediaItem(MediaManagerItem): translate('BiblesPlugin.MediaItem', 'From:')) self.advancedToLabel.setText( translate('BiblesPlugin.MediaItem', 'To:')) - self.advancedClearLabel.setText( - translate('BiblesPlugin.MediaItem', 'Results:')) - self.advancedSearchButton.setText(UiStrings.Search) - self.quickClearComboBox.addItem( - translate('BiblesPlugin.MediaItem', 'Clear')) - self.quickClearComboBox.addItem( - translate('BiblesPlugin.MediaItem', 'Keep')) - self.advancedClearComboBox.addItem( - translate('BiblesPlugin.MediaItem', 'Clear')) - self.advancedClearComboBox.addItem( - translate('BiblesPlugin.MediaItem', 'Keep')) + self.advancedVersionLabel.setText(u'%s:' % UiStrings().Version) + self.advancedSecondLabel.setText( + translate('BiblesPlugin.MediaItem', 'Second:')) + self.advancedStyleLabel.setText(UiStrings().LayoutStyle) + self.advancedStyleComboBox.setItemText(LayoutStyle.VersePerSlide, + UiStrings().VersePerSlide) + self.advancedStyleComboBox.setItemText(LayoutStyle.VersePerLine, + UiStrings().VersePerLine) + self.advancedStyleComboBox.setItemText(LayoutStyle.Continuous, + UiStrings().Continuous) + self.advancedLockButton.setToolTip(translate('BiblesPlugin.MediaItem', + 'Toggle to keep or clear the previous results.')) + self.advancedSearchButton.setText(UiStrings().Search) def initialise(self): log.debug(u'bible manager initialise') - self.parent.manager.media = self + self.plugin.manager.media = self self.loadBibles() - self.updateAutoCompleter() + bible = QtCore.QSettings().value( + self.settingsSection + u'/quick bible', QtCore.QVariant( + self.quickVersionComboBox.currentText())).toString() + find_and_set_in_combo_box(self.quickVersionComboBox, bible) + self.quickSearchEdit.setSearchTypes([ + (BibleSearch.Reference, u':/bibles/bibles_search_reference.png', + translate('BiblesPlugin.MediaItem', 'Scripture Reference')), + (BibleSearch.Text, u':/bibles/bibles_search_text.png', + translate('BiblesPlugin.MediaItem', 'Text Search')) + ]) + self.quickSearchEdit.setCurrentSearchType(QtCore.QSettings().value( + u'%s/last search type' % self.settingsSection, + QtCore.QVariant(BibleSearch.Reference)).toInt()[0]) self.configUpdated() log.debug(u'bible manager initialise complete') - def onImportClick(self): - if not hasattr(self, u'import_wizard'): - self.import_wizard = BibleImportForm(self, self.parent.manager, - self.parent) - # If the import was not cancelled then reload. - if self.import_wizard.exec_(): - self.reloadBibles() - def loadBibles(self): log.debug(u'Loading Bibles') self.quickVersionComboBox.clear() @@ -295,26 +372,35 @@ class BibleMediaItem(MediaManagerItem): self.quickSecondComboBox.addItem(u'') self.advancedSecondComboBox.addItem(u'') # Get all bibles and sort the list. - bibles = self.parent.manager.get_bibles().keys() - bibles.sort() + bibles = self.plugin.manager.get_bibles().keys() + bibles.sort(cmp=locale.strcoll) # Load the bibles into the combo boxes. - first = True for bible in bibles: if bible: self.quickVersionComboBox.addItem(bible) self.quickSecondComboBox.addItem(bible) self.advancedVersionComboBox.addItem(bible) self.advancedSecondComboBox.addItem(bible) - if first: - first = False - self.initialiseBible(bible) + # set the default value + bible = QtCore.QSettings().value( + self.settingsSection + u'/advanced bible', + QtCore.QVariant(u'')).toString() + if bible in bibles: + find_and_set_in_combo_box(self.advancedVersionComboBox, bible) + self.initialiseAdvancedBible(unicode(bible)) + elif len(bibles): + self.initialiseAdvancedBible(bibles[0]) - def reloadBibles(self): + def reloadBibles(self, process=False): log.debug(u'Reloading Bibles') - self.parent.manager.reload_bibles() + self.plugin.manager.reload_bibles() self.loadBibles() + # If called from first time wizard re-run, process any new bibles. + if process: + self.plugin.appStartup() + self.updateAutoCompleter() - def initialiseBible(self, bible): + def initialiseAdvancedBible(self, bible): """ This initialises the given bible, which means that its book names and their chapter numbers is added to the combo boxes on the @@ -324,8 +410,18 @@ class BibleMediaItem(MediaManagerItem): ``bible`` The bible to initialise (unicode). """ - log.debug(u'initialiseBible %s', bible) - book_data = self.parent.manager.get_books(bible) + log.debug(u'initialiseAdvancedBible %s', bible) + book_data = self.plugin.manager.get_books(bible) + secondbible = unicode(self.advancedSecondComboBox.currentText()) + if secondbible != u'': + secondbook_data = self.plugin.manager.get_books(secondbible) + book_data_temp = [] + for book in book_data: + for secondbook in secondbook_data: + if book['book_reference_id'] == \ + secondbook['book_reference_id']: + book_data_temp.append(book) + book_data = book_data_temp self.advancedBookComboBox.clear() first = True for book in book_data: @@ -341,11 +437,11 @@ class BibleMediaItem(MediaManagerItem): def initialiseChapterVerse(self, bible, book, chapter_count): log.debug(u'initialiseChapterVerse %s, %s', bible, book) self.chapter_count = chapter_count - verse_count = self.parent.manager.get_verse_count(bible, book, 1) + verse_count = self.plugin.manager.get_verse_count(bible, book, 1) if verse_count == 0: self.advancedSearchButton.setEnabled(False) critical_error_message_box( - message=translate('BiblePlugin.MediaItem', + message=translate('BiblesPlugin.MediaItem', 'Bible not fully loaded.')) else: self.advancedSearchButton.setEnabled(True) @@ -360,19 +456,90 @@ class BibleMediaItem(MediaManagerItem): completion depends on the bible. It is only updated when we are doing a reference search, otherwise the auto completion list is removed. """ + # Save the current search type to the configuration. + QtCore.QSettings().setValue(u'%s/last search type' % + self.settingsSection, + QtCore.QVariant(self.quickSearchEdit.currentSearchType())) + # Save the current bible to the configuration. + QtCore.QSettings().setValue(self.settingsSection + u'/quick bible', + QtCore.QVariant(self.quickVersionComboBox.currentText())) books = [] # We have to do a 'Reference Search'. if self.quickSearchEdit.currentSearchType() == BibleSearch.Reference: - bibles = self.parent.manager.get_bibles() + bibles = self.plugin.manager.get_bibles() bible = unicode(self.quickVersionComboBox.currentText()) if bible: book_data = bibles[bible].get_books() - books = [book.name for book in book_data] - books.sort() + secondbible = unicode(self.quickSecondComboBox.currentText()) + if secondbible != u'': + secondbook_data = bibles[secondbible].get_books() + book_data_temp = [] + for book in book_data: + for secondbook in secondbook_data: + if book.book_reference_id == \ + secondbook.book_reference_id: + book_data_temp.append(book) + book_data = book_data_temp + books = [book.name + u' ' for book in book_data] + books.sort(cmp=locale.strcoll) add_widget_completer(books, self.quickSearchEdit) + def onQuickVersionComboBox(self): + self.updateAutoCompleter() + + def onQuickSecondComboBox(self): + self.updateAutoCompleter() + + def onImportClick(self): + if not hasattr(self, u'import_wizard'): + self.import_wizard = BibleImportForm(self, self.plugin.manager, + self.plugin) + # If the import was not cancelled then reload. + if self.import_wizard.exec_(): + self.reloadBibles() + + def onSearchTabBarCurrentChanged(self, index): + if index == 0: + self.advancedTab.setVisible(False) + self.quickTab.setVisible(True) + self.quickSearchEdit.setFocus() + else: + self.quickTab.setVisible(False) + self.advancedTab.setVisible(True) + self.advancedBookComboBox.setFocus() + + def onLockButtonToggled(self, checked): + if checked: + self.sender().setIcon(self.lockIcon) + else: + self.sender().setIcon(self.unlockIcon) + + def onQuickStyleComboBoxChanged(self): + self.settings.layout_style = self.quickStyleComboBox.currentIndex() + self.advancedStyleComboBox.setCurrentIndex(self.settings.layout_style) + self.settings.layoutStyleComboBox.setCurrentIndex( + self.settings.layout_style) + QtCore.QSettings().setValue( + self.settingsSection + u'/verse layout style', + QtCore.QVariant(self.settings.layout_style)) + + def onAdvancedStyleComboBoxChanged(self): + self.settings.layout_style = self.advancedStyleComboBox.currentIndex() + self.quickStyleComboBox.setCurrentIndex(self.settings.layout_style) + self.settings.layoutStyleComboBox.setCurrentIndex( + self.settings.layout_style) + QtCore.QSettings().setValue( + self.settingsSection + u'/verse layout style', + QtCore.QVariant(self.settings.layout_style)) + def onAdvancedVersionComboBox(self): - self.initialiseBible( + QtCore.QSettings().setValue(self.settingsSection + u'/advanced bible', + QtCore.QVariant(self.advancedVersionComboBox.currentText())) + self.initialiseAdvancedBible( + unicode(self.advancedVersionComboBox.currentText())) + + def onAdvancedSecondComboBox(self): + self.initialiseAdvancedBible( unicode(self.advancedVersionComboBox.currentText())) def onAdvancedBookComboBox(self): @@ -389,7 +556,7 @@ class BibleMediaItem(MediaManagerItem): bible = unicode(self.advancedVersionComboBox.currentText()) book = unicode(self.advancedBookComboBox.currentText()) verse_from = int(self.advancedFromVerse.currentText()) - verse_count = self.parent.manager.get_verse_count(bible, book, + verse_count = self.plugin.manager.get_verse_count(bible, book, chapter_to) self.adjustComboBox(verse_from, verse_count, self.advancedToVerse, True) @@ -401,7 +568,7 @@ class BibleMediaItem(MediaManagerItem): chapter_to = int(self.advancedToChapter.currentText()) verse_from = int(self.advancedFromVerse.currentText()) verse_to = int(self.advancedToVerse.currentText()) - verse_count = self.parent.manager.get_verse_count(bible, book, + verse_count = self.plugin.manager.get_verse_count(bible, book, chapter_to) if chapter_from == chapter_to and verse_from > verse_to: self.adjustComboBox(verse_from, verse_count, self.advancedToVerse) @@ -413,7 +580,7 @@ class BibleMediaItem(MediaManagerItem): book = unicode(self.advancedBookComboBox.currentText()) chapter_from = int(self.advancedFromChapter.currentText()) chapter_to = int(self.advancedToChapter.currentText()) - verse_count = self.parent.manager.get_verse_count(bible, book, + verse_count = self.plugin.manager.get_verse_count(bible, book, chapter_from) self.adjustComboBox(1, verse_count, self.advancedFromVerse) if chapter_from > chapter_to: @@ -449,8 +616,7 @@ class BibleMediaItem(MediaManagerItem): if restore: old_text = unicode(combo.currentText()) combo.clear() - for i in range(range_from, range_to + 1): - combo.addItem(unicode(i)) + combo.addItems(map(unicode, range(range_from, range_to + 1))) if restore and combo.findText(old_text) != -1: combo.setCurrentIndex(combo.findText(old_text)) @@ -474,18 +640,19 @@ class BibleMediaItem(MediaManagerItem): range_separator + chapter_to + verse_separator + verse_to versetext = u'%s %s' % (book, verse_range) Receiver.send_message(u'cursor_busy') - self.search_results = self.parent.manager.get_verses(bible, versetext) + self.search_results = self.plugin.manager.get_verses(bible, versetext) if second_bible: - self.second_search_results = self.parent.manager.get_verses( - second_bible, versetext) - if self.advancedClearComboBox.currentIndex() == 0: + self.second_search_results = self.plugin.manager.get_verses( + second_bible, versetext, bible) + if not self.advancedLockButton.isChecked(): self.listView.clear() if self.listView.count() != 0: self.__checkSecondBible(bible, second_bible) elif self.search_results: self.displayResults(bible, second_bible) - Receiver.send_message(u'cursor_normal') self.advancedSearchButton.setEnabled(True) + self.checkSearchResult() + Receiver.send_message(u'cursor_normal') Receiver.send_message(u'openlp_process_events') def onQuickSearchButton(self): @@ -501,136 +668,137 @@ class BibleMediaItem(MediaManagerItem): text = unicode(self.quickSearchEdit.text()) if self.quickSearchEdit.currentSearchType() == BibleSearch.Reference: # We are doing a 'Reference Search'. - self.search_results = self.parent.manager.get_verses(bible, text) + self.search_results = self.plugin.manager.get_verses(bible, text) if second_bible and self.search_results: - self.second_search_results = self.parent.manager.get_verses( - second_bible, text) + self.second_search_results = self.plugin.manager.get_verses( + second_bible, text, bible) else: # We are doing a 'Text Search'. Receiver.send_message(u'cursor_busy') - bibles = self.parent.manager.get_bibles() - self.search_results = self.parent.manager.verse_search(bible, + bibles = self.plugin.manager.get_bibles() + self.search_results = self.plugin.manager.verse_search(bible, second_bible, text) if second_bible and self.search_results: text = [] + new_search_results = [] + count = 0 + passage_not_found = False for verse in self.search_results: - text.append((verse.book.name, verse.chapter, verse.verse, - verse.verse)) + db_book = bibles[second_bible].get_book_by_book_ref_id( + verse.book.book_reference_id) + if not db_book: + log.debug(u'Passage "%s %d:%d" not found in Second ' + u'Bible' % (verse.book.name, verse.chapter, + verse.verse)) + passage_not_found = True + count += 1 + continue + new_search_results.append(verse) + text.append((verse.book.book_reference_id, verse.chapter, + verse.verse, verse.verse)) + if passage_not_found: + QtGui.QMessageBox.information(self, + translate('BiblesPlugin.MediaItem', 'Information'), + unicode(translate('BiblesPlugin.MediaItem', + 'The second Bible does not contain all the verses ' + 'that are in the main Bible. Only verses found in both ' + 'Bibles will be shown. %d verses have not been ' + 'included in the results.')) % count, + QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok)) + self.search_results = new_search_results self.second_search_results = \ bibles[second_bible].get_verses(text) - if self.quickClearComboBox.currentIndex() == 0: + if not self.quickLockButton.isChecked(): self.listView.clear() if self.listView.count() != 0 and self.search_results: self.__checkSecondBible(bible, second_bible) elif self.search_results: self.displayResults(bible, second_bible) self.quickSearchButton.setEnabled(True) + self.checkSearchResult() Receiver.send_message(u'cursor_normal') Receiver.send_message(u'openlp_process_events') - def __checkSecondBible(self, bible, second_bible): - """ - Check if the first item is a second bible item or not. - """ - bitem = self.listView.item(0) - item_second_bible = self._decodeQtObject(bitem, 'second_bible') - if item_second_bible and second_bible or not item_second_bible and \ - not second_bible: - self.displayResults(bible, second_bible) - elif critical_error_message_box( - message=translate('BiblePlugin.MediaItem', - 'You cannot combine single and dual Bible verse search results. ' - 'Do you want to delete your search results and start a new ' - 'search?'), - parent=self, question=True) == QtGui.QMessageBox.Yes: - self.listView.clear() - self.displayResults(bible, second_bible) - def displayResults(self, bible, second_bible=u''): """ Displays the search results in the media manager. All data needed for further action is saved for/in each row. """ - verse_separator = get_reference_match(u'sep_v_display') - version = self.parent.manager.get_meta_data(bible, u'Version') - copyright = self.parent.manager.get_meta_data(bible, u'Copyright') - permissions = self.parent.manager.get_meta_data(bible, u'Permissions') - if second_bible: - second_version = self.parent.manager.get_meta_data(second_bible, - u'Version') - second_copyright = self.parent.manager.get_meta_data(second_bible, - u'Copyright') - second_permissions = self.parent.manager.get_meta_data(second_bible, - u'Permissions') - if not second_permissions: - second_permissions = u'' - for count, verse in enumerate(self.search_results): - if second_bible: - try: - vdict = { - 'book': QtCore.QVariant(verse.book.name), - 'chapter': QtCore.QVariant(verse.chapter), - 'verse': QtCore.QVariant(verse.verse), - 'bible': QtCore.QVariant(bible), - 'version': QtCore.QVariant(version.value), - 'copyright': QtCore.QVariant(copyright.value), - 'permissions': QtCore.QVariant(permissions.value), - 'text': QtCore.QVariant(verse.text), - 'second_bible': QtCore.QVariant(second_bible), - 'second_version': QtCore.QVariant(second_version.value), - 'second_copyright': QtCore.QVariant( - second_copyright.value), - 'second_permissions': QtCore.QVariant( - second_permissions.value), - 'second_text': QtCore.QVariant( - self.second_search_results[count].text) - } - except IndexError: - break - bible_text = u' %s %d%s%d (%s, %s)' % (verse.book.name, - verse.chapter, verse_separator, verse.verse, version.value, - second_version.value) - else: - vdict = { - 'book': QtCore.QVariant(verse.book.name), - 'chapter': QtCore.QVariant(verse.chapter), - 'verse': QtCore.QVariant(verse.verse), - 'bible': QtCore.QVariant(bible), - 'version': QtCore.QVariant(version.value), - 'copyright': QtCore.QVariant(copyright.value), - 'permissions': QtCore.QVariant(permissions.value), - 'text': QtCore.QVariant(verse.text), - 'second_bible': QtCore.QVariant(u''), - 'second_version': QtCore.QVariant(u''), - 'second_copyright': QtCore.QVariant(u''), - 'second_permissions': QtCore.QVariant(u''), - 'second_text': QtCore.QVariant(u'') - } - bible_text = u'%s %d%s%d (%s)' % (verse.book.name, - verse.chapter, verse_separator, verse.verse, version.value) - bible_verse = QtGui.QListWidgetItem(bible_text) - bible_verse.setData(QtCore.Qt.UserRole, QtCore.QVariant(vdict)) + items = self.buildDisplayResults(bible, second_bible, + self.search_results) + for bible_verse in items: self.listView.addItem(bible_verse) self.listView.selectAll() self.search_results = {} self.second_search_results = {} - def _decodeQtObject(self, bitem, key): - reference = bitem.data(QtCore.Qt.UserRole) - if isinstance(reference, QtCore.QVariant): - reference = reference.toPyObject() - obj = reference[QtCore.QString(key)] - if isinstance(obj, QtCore.QVariant): - obj = obj.toPyObject() - return unicode(obj).strip() + def buildDisplayResults(self, bible, second_bible, search_results): + """ + Displays the search results in the media manager. All data needed for + further action is saved for/in each row. + """ + verse_separator = get_reference_match(u'sep_v_display') + version = self.plugin.manager.get_meta_data(bible, u'Version').value + copyright = self.plugin.manager.get_meta_data(bible, u'Copyright').value + permissions = \ + self.plugin.manager.get_meta_data(bible, u'Permissions').value + second_version = u'' + second_copyright = u'' + second_permissions = u'' + if second_bible: + second_version = self.plugin.manager.get_meta_data( + second_bible, u'Version').value + second_copyright = self.plugin.manager.get_meta_data( + second_bible, u'Copyright').value + second_permissions = self.plugin.manager.get_meta_data( + second_bible, u'Permissions').value + items = [] + for count, verse in enumerate(search_results): + data = { + 'book': QtCore.QVariant(verse.book.name), + 'chapter': QtCore.QVariant(verse.chapter), + 'verse': QtCore.QVariant(verse.verse), + 'bible': QtCore.QVariant(bible), + 'version': QtCore.QVariant(version), + 'copyright': QtCore.QVariant(copyright), + 'permissions': QtCore.QVariant(permissions), + 'text': QtCore.QVariant(verse.text), + 'second_bible': QtCore.QVariant(second_bible), + 'second_version': QtCore.QVariant(second_version), + 'second_copyright': QtCore.QVariant(second_copyright), + 'second_permissions': QtCore.QVariant(second_permissions), + 'second_text': QtCore.QVariant(u'') + } + if second_bible: + try: + data[u'second_text'] = QtCore.QVariant( + self.second_search_results[count].text) + except IndexError: + log.exception(u'The second_search_results does not have as ' + 'many verses as the search_results.') + break + bible_text = u'%s %d%s%d (%s, %s)' % (verse.book.name, + verse.chapter, verse_separator, verse.verse, version, + second_version) + else: + bible_text = u'%s %d%s%d (%s)' % (verse.book.name, + verse.chapter, verse_separator, verse.verse, version) + bible_verse = QtGui.QListWidgetItem(bible_text) + bible_verse.setData(QtCore.Qt.UserRole, QtCore.QVariant(data)) + items.append(bible_verse) + return items - def generateSlideData(self, service_item, item=None, xmlVersion=False): + def generateSlideData(self, service_item, item=None, xmlVersion=False, + remote=False): """ Generates and formats the slides for the service item as well as the service item's title. """ log.debug(u'generating slide data') - items = self.listView.selectedIndexes() + if item: + items = item + else: + items = self.listView.selectedItems() if len(items) == 0: return False bible_text = u'' @@ -638,8 +806,8 @@ class BibleMediaItem(MediaManagerItem): old_chapter = -1 raw_slides = [] raw_title = [] - for item in items: - bitem = self.listView.item(item.row()) + verses = VerseReferenceList() + for bitem in items: book = self._decodeQtObject(bitem, 'book') chapter = int(self._decodeQtObject(bitem, 'chapter')) verse = int(self._decodeQtObject(bitem, 'verse')) @@ -654,15 +822,9 @@ class BibleMediaItem(MediaManagerItem): second_permissions = \ self._decodeQtObject(bitem, 'second_permissions') second_text = self._decodeQtObject(bitem, 'second_text') + verses.add(book, chapter, verse, version, copyright, permissions) verse_text = self.formatVerse(old_chapter, chapter, verse) - footer = u'%s (%s %s %s)' % (book, version, copyright, permissions) - if footer not in service_item.raw_footer: - service_item.raw_footer.append(footer) if second_bible: - footer = u'%s (%s %s %s)' % (book, second_version, - second_copyright, second_permissions) - if footer not in service_item.raw_footer: - service_item.raw_footer.append(footer) bible_text = u'%s %s\n\n%s %s' % (verse_text, text, verse_text, second_text) raw_slides.append(bible_text.rstrip()) @@ -674,18 +836,25 @@ class BibleMediaItem(MediaManagerItem): bible_text = u'' # If we are 'Verse Per Line' then force a new line. elif self.settings.layout_style == LayoutStyle.VersePerLine: - bible_text = u'%s %s %s\n' % (bible_text, verse_text, text) + bible_text = u'%s%s %s\n' % (bible_text, verse_text, text) # We have to be 'Continuous'. else: bible_text = u'%s %s %s\n' % (bible_text, verse_text, text) + bible_text = bible_text.strip(u' ') if not old_item: - start_item = item - elif self.checkTitle(item, old_item): + start_item = bitem + elif self.checkTitle(bitem, old_item): raw_title.append(self.formatTitle(start_item, old_item)) - start_item = item - old_item = item + start_item = bitem + old_item = bitem old_chapter = chapter - raw_title.append(self.formatTitle(start_item, item)) + # Add footer + service_item.raw_footer.append(verses.format_verses()) + if second_bible: + verses.add_version(second_version, second_copyright, + second_permissions) + service_item.raw_footer.append(verses.format_versions()) + raw_title.append(self.formatTitle(start_item, bitem)) # If there are no more items we check whether we have to add bible_text. if bible_text: raw_slides.append(bible_text.lstrip()) @@ -695,8 +864,9 @@ class BibleMediaItem(MediaManagerItem): not second_bible: # Split the line but do not replace line breaks in renderer. service_item.add_capability(ItemCapabilities.NoLineBreaks) - service_item.add_capability(ItemCapabilities.AllowsPreview) - service_item.add_capability(ItemCapabilities.AllowsLoop) + service_item.add_capability(ItemCapabilities.CanPreview) + service_item.add_capability(ItemCapabilities.CanLoop) + service_item.add_capability(ItemCapabilities.CanWordSplit) # Service Item: Title service_item.title = u', '.join(raw_title) # Service Item: Theme @@ -708,9 +878,9 @@ class BibleMediaItem(MediaManagerItem): service_item.add_from_text(slide[:30], slide) return True - def formatTitle(self, start_item, old_item): + def formatTitle(self, start_bitem, old_bitem): """ - This methode is called, when we have to change the title, because + This method is called, when we have to change the title, because we are at the end of a verse range. E. g. if we want to add Genesis 1:1-6 as well as Daniel 2:14. @@ -722,10 +892,8 @@ class BibleMediaItem(MediaManagerItem): """ verse_separator = get_reference_match(u'sep_v_display') range_separator = get_reference_match(u'sep_r_display') - old_bitem = self.listView.item(old_item.row()) old_chapter = self._decodeQtObject(old_bitem, 'chapter') old_verse = self._decodeQtObject(old_bitem, 'verse') - start_bitem = self.listView.item(start_item.row()) start_book = self._decodeQtObject(start_bitem, 'book') start_chapter = self._decodeQtObject(start_bitem, 'chapter') start_verse = self._decodeQtObject(start_bitem, 'verse') @@ -744,12 +912,11 @@ class BibleMediaItem(MediaManagerItem): else: verse_range = start_chapter + verse_separator + start_verse + \ range_separator + old_chapter + verse_separator + old_verse - title = u'%s %s (%s)' % (start_book, verse_range, bibles) - return title + return u'%s %s (%s)' % (start_book, verse_range, bibles) - def checkTitle(self, item, old_item): + def checkTitle(self, bitem, old_bitem): """ - This methode checks if we are at the end of an verse range. If that is + This method checks if we are at the end of an verse range. If that is the case, we return True, otherwise False. E. g. if we added Genesis 1:1-6, but the next verse is Daniel 2:14, we return True. @@ -760,13 +927,11 @@ class BibleMediaItem(MediaManagerItem): The item we were previously dealing with. """ # Get all the necessary meta data. - bitem = self.listView.item(item.row()) book = self._decodeQtObject(bitem, 'book') chapter = int(self._decodeQtObject(bitem, 'chapter')) verse = int(self._decodeQtObject(bitem, 'verse')) bible = self._decodeQtObject(bitem, 'bible') second_bible = self._decodeQtObject(bitem, 'second_bible') - old_bitem = self.listView.item(old_item.row()) old_book = self._decodeQtObject(old_bitem, 'book') old_chapter = int(self._decodeQtObject(old_bitem, 'chapter')) old_verse = int(self._decodeQtObject(old_bitem, 'verse')) @@ -780,7 +945,7 @@ class BibleMediaItem(MediaManagerItem): # We are still in the same chapter, but a verse has been skipped. return True elif old_chapter + 1 == chapter and (verse != 1 or - old_verse != self.parent.manager.get_verse_count( + old_verse != self.plugin.manager.get_verse_count( old_bible, old_book, old_chapter)): # We are in the following chapter, but the last verse was not the # last verse of the chapter or the current verse is not the @@ -812,11 +977,28 @@ class BibleMediaItem(MediaManagerItem): else: verse_text = unicode(verse) if self.settings.display_style == DisplayStyle.Round: - verse_text = u'{su}(' + verse_text + u'){/su}' - elif self.settings.display_style == DisplayStyle.Curly: - verse_text = u'{su}{' + verse_text + u'}{/su}' - elif self.settings.display_style == DisplayStyle.Square: - verse_text = u'{su}[' + verse_text + u']{/su}' - else: - verse_text = u'{su}' + verse_text + u'{/su}' - return verse_text + return u'{su}(%s){/su}' % verse_text + if self.settings.display_style == DisplayStyle.Curly: + return u'{su}{%s}{/su}' % verse_text + if self.settings.display_style == DisplayStyle.Square: + return u'{su}[%s]{/su}' % verse_text + return u'{su}%s{/su}' % verse_text + + def search(self, string): + """ + Search for some Bible verses (by reference). + """ + bible = unicode(self.quickVersionComboBox.currentText()) + search_results = self.plugin.manager.get_verses(bible, string, False, + False) + if search_results: + versetext = u' '.join([verse.text for verse in search_results]) + return [[string, versetext]] + return [] + + def createItemFromId(self, item_id): + item = QtGui.QListWidgetItem() + bible = unicode(self.quickVersionComboBox.currentText()) + search_results = self.plugin.manager.get_verses(bible, item_id, False) + items = self.buildDisplayResults(bible, u'', search_results) + return items diff --git a/openlp/plugins/bibles/lib/openlp1.py b/openlp/plugins/bibles/lib/openlp1.py index 2d19db20c..b822b9b9d 100644 --- a/openlp/plugins/bibles/lib/openlp1.py +++ b/openlp/plugins/bibles/lib/openlp1.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, Armin Köhler, 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,7 +30,7 @@ import sqlite from openlp.core.lib import Receiver from openlp.core.ui.wizard import WizardStrings -from openlp.plugins.bibles.lib.db import BibleDB +from openlp.plugins.bibles.lib.db import BibleDB, BiblesResourcesDB log = logging.getLogger(__name__) @@ -45,7 +46,7 @@ class OpenLP1Bible(BibleDB): BibleDB.__init__(self, parent, **kwargs) self.filename = kwargs[u'filename'] - def do_import(self): + def do_import(self, bible_name=None): """ Imports an openlp.org v1 bible. """ @@ -56,6 +57,11 @@ class OpenLP1Bible(BibleDB): cursor = connection.cursor() except: return False + #Create the bible language + language_id = self.get_language(bible_name) + if not language_id: + log.exception(u'Importing books from "%s" failed' % self.filename) + return False # Create all books. cursor.execute(u'SELECT id, testament_id, name, abbreviation FROM book') books = cursor.fetchall() @@ -68,7 +74,15 @@ class OpenLP1Bible(BibleDB): testament_id = int(book[1]) name = unicode(book[2], u'cp1252') abbreviation = unicode(book[3], u'cp1252') - self.create_book(name, abbreviation, testament_id) + book_ref_id = self.get_book_ref_id_by_name(name, len(books), + language_id) + if not book_ref_id: + log.exception(u'Importing books from "%s" '\ + 'failed' % self.filename) + return False + book_details = BiblesResourcesDB.get_book_by_id(book_ref_id) + db_book = self.create_book(name, book_ref_id, + book_details[u'testament_id']) # Update the progess bar. self.wizard.incrementProgressBar(WizardStrings.ImportingType % name) # Import the verses for this book. @@ -82,7 +96,7 @@ class OpenLP1Bible(BibleDB): chapter = int(verse[0]) verse_number = int(verse[1]) text = unicode(verse[2], u'cp1252') - self.create_verse(book_id, chapter, verse_number, text) + self.create_verse(db_book.id, chapter, verse_number, text) Receiver.send_message(u'openlp_process_events') self.session.commit() connection.close() diff --git a/openlp/plugins/bibles/lib/opensong.py b/openlp/plugins/bibles/lib/opensong.py index a7f1eff33..16820229c 100644 --- a/openlp/plugins/bibles/lib/opensong.py +++ b/openlp/plugins/bibles/lib/opensong.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, Armin Köhler, 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,7 +29,7 @@ import logging from lxml import objectify from openlp.core.lib import Receiver, translate -from openlp.plugins.bibles.lib.db import BibleDB +from openlp.plugins.bibles.lib.db import BibleDB, BiblesResourcesDB log = logging.getLogger(__name__) @@ -45,7 +46,7 @@ class OpenSongBible(BibleDB): BibleDB.__init__(self, parent, **kwargs) self.filename = kwargs['filename'] - def do_import(self): + def do_import(self, bible_name=None): """ Loads a Bible from file. """ @@ -61,11 +62,23 @@ class OpenSongBible(BibleDB): file = open(self.filename, u'r') opensong = objectify.parse(file) bible = opensong.getroot() + language_id = self.get_language(bible_name) + if not language_id: + log.exception(u'Importing books from "%s" '\ + 'failed' % self.filename) + return False for book in bible.b: if self.stop_import_flag: break - db_book = self.create_book(unicode(book.attrib[u'n']), - unicode(book.attrib[u'n'][:4])) + book_ref_id = self.get_book_ref_id_by_name( + unicode(book.attrib[u'n']), len(bible.b), language_id) + if not book_ref_id: + log.exception(u'Importing books from "%s" '\ + 'failed' % self.filename) + return False + book_details = BiblesResourcesDB.get_book_by_id(book_ref_id) + db_book = self.create_book(unicode(book.attrib[u'n']), + book_ref_id, book_details[u'testament_id']) for chapter in book.c: if self.stop_import_flag: break diff --git a/openlp/plugins/bibles/lib/osis.py b/openlp/plugins/bibles/lib/osis.py index 78e2551d9..b802cda85 100644 --- a/openlp/plugins/bibles/lib/osis.py +++ b/openlp/plugins/bibles/lib/osis.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, Armin Köhler, 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 # @@ -33,10 +34,13 @@ import re from openlp.core.lib import Receiver, translate from openlp.core.utils import AppLocation -from openlp.plugins.bibles.lib.db import BibleDB +from openlp.plugins.bibles.lib.db import BibleDB, BiblesResourcesDB log = logging.getLogger(__name__) +def replacement(match): + return match.group(2).upper() + class OSISBible(BibleDB): """ `OSIS `_ Bible format importer class. @@ -60,6 +64,7 @@ class OSISBible(BibleDB): self.lg_regex = re.compile(r'') self.l_regex = re.compile(r'') self.w_regex = re.compile(r'') + self.q_regex = re.compile(r'') self.q1_regex = re.compile(r'') self.q2_regex = re.compile(r'') self.trans_regex = re.compile(r'(.*?)') @@ -81,7 +86,7 @@ class OSISBible(BibleDB): if fbibles: fbibles.close() - def do_import(self): + def do_import(self, bible_name=None): """ Loads a Bible from file. """ @@ -91,7 +96,6 @@ class OSISBible(BibleDB): osis = None success = True last_chapter = 0 - testament = 1 match_count = 0 self.wizard.incrementProgressBar(translate('BiblesPlugin.OsisImport', 'Detecting encoding (this may take a few minutes)...')) @@ -104,8 +108,14 @@ class OSISBible(BibleDB): finally: if detect_file: detect_file.close() + # Set meta language_id + language_id = self.get_language(bible_name) + if not language_id: + log.exception(u'Importing books from "%s" failed' % self.filename) + return False try: osis = codecs.open(self.filename, u'r', details['encoding']) + repl = replacement for file_record in osis: if self.stop_import_flag: break @@ -117,13 +127,19 @@ class OSISBible(BibleDB): verse = int(match.group(3)) verse_text = match.group(4) if not db_book or db_book.name != self.books[book][0]: - log.debug(u'New book: "%s"', self.books[book][0]) - if book == u'Matt' or book == u'Jdt': - testament += 1 + log.debug(u'New book: "%s"' % self.books[book][0]) + book_ref_id = self.get_book_ref_id_by_name(unicode( + self.books[book][0]), 67, language_id) + if not book_ref_id: + log.exception(u'Importing books from "%s" '\ + 'failed' % self.filename) + return False + book_details = BiblesResourcesDB.get_book_by_id( + book_ref_id) db_book = self.create_book( unicode(self.books[book][0]), - unicode(self.books[book][1]), - testament) + book_ref_id, + book_details[u'testament_id']) if last_chapter == 0: if book == u'Gen': self.wizard.progressBar.setMaximum(1188) @@ -148,12 +164,13 @@ class OSISBible(BibleDB): verse_text = self.rf_regex.sub(u'', verse_text) verse_text = self.lb_regex.sub(u' ', verse_text) verse_text = self.lg_regex.sub(u'', verse_text) - verse_text = self.l_regex.sub(u'', verse_text) + verse_text = self.l_regex.sub(u' ', verse_text) verse_text = self.w_regex.sub(u'', verse_text) verse_text = self.q1_regex.sub(u'"', verse_text) verse_text = self.q2_regex.sub(u'\'', verse_text) + verse_text = self.q_regex.sub(u'', verse_text) + verse_text = self.divine_name_regex.sub(repl, verse_text) verse_text = self.trans_regex.sub(u'', verse_text) - verse_text = self.divine_name_regex.sub(u'', verse_text) verse_text = verse_text.replace(u'', u'')\ .replace(u'', u'').replace(u'', u'')\ .replace(u'', u'').replace(u'', u'')\ diff --git a/openlp/plugins/bibles/lib/versereferencelist.py b/openlp/plugins/bibles/lib/versereferencelist.py new file mode 100644 index 000000000..471fc6a66 --- /dev/null +++ b/openlp/plugins/bibles/lib/versereferencelist.py @@ -0,0 +1,103 @@ +# -*- 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 # +############################################################################### + +class VerseReferenceList(object): + """ + The VerseReferenceList class encapsulates a list of verse references, but + maintains the order in which they were added. + """ + + def __init__(self): + self.verse_list = [] + self.version_list = [] + self.current_index = -1 + + def add(self, book, chapter, verse, version, copyright, permission): + self.add_version(version, copyright, permission) + if not self.verse_list or \ + self.verse_list[self.current_index][u'book'] != book: + self.verse_list.append({u'version': version, u'book': book, + u'chapter': chapter, u'start': verse, u'end': verse}) + self.current_index += 1 + elif self.verse_list[self.current_index][u'chapter'] != chapter: + self.verse_list.append({u'version': version, u'book': book, + u'chapter': chapter, u'start': verse, u'end': verse}) + self.current_index += 1 + elif (self.verse_list[self.current_index][u'end'] + 1) == verse: + self.verse_list[self.current_index][u'end'] = verse + else: + self.verse_list.append({u'version': version, u'book': book, + u'chapter': chapter, u'start': verse, u'end': verse}) + self.current_index += 1 + + def add_version(self, version, copyright, permission): + for bible_version in self.version_list: + if bible_version[u'version'] == version: + return + self.version_list.append({u'version': version, u'copyright': copyright, + u'permission': permission}) + + def format_verses(self): + result = u'' + for index, verse in enumerate(self.verse_list): + if index == 0: + result = u'%s %s:%s' % (verse[u'book'], verse[u'chapter'], + verse[u'start']) + if verse[u'start'] != verse[u'end']: + result = u'%s-%s' % (result, verse[u'end']) + continue + prev = index - 1 + if self.verse_list[prev][u'version'] != verse[u'version']: + result = u'%s (%s)' % (result, self.verse_list[prev][u'version']) + result = result + u', ' + if self.verse_list[prev][u'book'] != verse[u'book']: + result = u'%s%s %s:' % (result, verse[u'book'], + verse[u'chapter']) + elif self.verse_list[prev][u'chapter'] != verse[u'chapter']: + result = u'%s%s:' % (result, verse[u'chapter']) + result = result + str(verse[u'start']) + if verse[u'start'] != verse[u'end']: + result = u'%s-%s' % (result, verse[u'end']) + if len(self.version_list) > 1: + result = u'%s (%s)' % (result, verse[u'version']) + return result + + def format_versions(self): + result = u'' + for index, version in enumerate(self.version_list): + if index > 0: + if result[-1] not in [u';', u',', u'.']: + result = result + u';' + result = result + u' ' + result = u'%s%s, %s' % (result, version[u'version'], + version[u'copyright']) + if version[u'permission'].strip(): + result = result + u', ' + version[u'permission'] + result = result.rstrip() + if result.endswith(u','): + return result[:len(result)-1] + return result diff --git a/openlp/plugins/bibles/resources/biblegateway.csv b/openlp/plugins/bibles/resources/biblegateway.csv deleted file mode 100644 index ad8052704..000000000 --- a/openlp/plugins/bibles/resources/biblegateway.csv +++ /dev/null @@ -1,81 +0,0 @@ -João Ferreira de Almeida Atualizada,AA -التفسير التطبيقى للكتاب المقدس,ALAB -Shqip,ALB -Amplified Bible,AMP -Amuzgo de Guerrero,AMU -American Standard Version,ASV -La Bible du Semeur,BDS -Български 1940,BG1940 -Български,BULG -Chinanteco de Comaltepec,CCO -Contemporary English Version,CEV -Cakchiquel Occidental,CKW -Hrvatski,CRO -Castilian,CST -聖經和合本 (简体中文),CUVS -聖經和合本 (繁体中文),CUV -Darby Translation,DARBY -Dette er Biblen på dansk,DN1933 -Det Norsk Bibelselskap 1930,DNB1930 -English Standard Version,ESV -GOD’S WORD Translation,GW -Holman Christian Standard Bible,HCSB -Kreyòl ayisyen bib,HCV -Hiligaynon Bible,HLGN -Hoffnung für Alle,HOF -Het Boek,HTB -Icelandic Bible,ICELAND -Jacalteco – Oriental,JAC -Károlyi-biblia,KAR -Kekchi,KEK -21st Century King James Version,KJ21 -King James Version,KJV -La Biblia de las Américas,LBLA -Levande Bibeln,LB -La Parola è Vita,LM -La Nuova Diodati,LND -Louis Segond,LSG -Luther Bibel 1545,LUTH1545 -Māori Bible,MAORI -Македонски Новиот Завет,MNT -The Message,MSG -Mam de Comitancillo Central,MVC -Mam de Todos Santos Cuchumatán,MVJ -New American Standard Bible,NASB -New Century Version,NCV -Náhuatl de Guerrero,NGU -New International Reader's Version,NIRV -New International Version 1984,NIV1984 -New International Version 2010,NIV -New International Version - UK,NIVUK -New King James Version,NKJV -New Living Translation,NLT -Nádej pre kazdého,NPK -Nueva Versión Internacional,NVI -O Livro,OL -Quiché – Centro Occidental,QUT -Reimer 2001,REIMER -Română Cornilescu,RMNN -Новый перевод на русский язык,RUSV -Reina-Valera Antigua,RVA -Reina-Valera 1960,RVR1960 -Reina-Valera 1995,RVR1995 -Slovo na cestu,SNC -Ang Salita ng Diyos,SND -Swahili New Testament,SNT -Svenska 1917,SV1917 -Levande Bibeln,SVL -Создать страницу,SZ -Traducción en lenguaje actual,TLA -New Romanian Translation,TLCR -Today’s New International Version 2005,TNIV -Textus Receptus Stephanus 1550,TR1550 -Textus Receptus Scrivener 1894,TR1894 -Українська Біблія. Переклад Івана Огієнка,UKR -Uspanteco,USP -Kinh Thánh tiếng Việt 1934,VIET -Worldwide English (New Testament),WE -Codex Vaticanus Westcott-Hort 1881,WHNU -Westminster Leningrad Codex,WLC -Wycliffe New Testament,WYC -Young's Literal Translation,YLT diff --git a/openlp/plugins/bibles/resources/bibles_resources.sqlite b/openlp/plugins/bibles/resources/bibles_resources.sqlite new file mode 100644 index 000000000..c0fa931d1 Binary files /dev/null and b/openlp/plugins/bibles/resources/bibles_resources.sqlite differ diff --git a/openlp/plugins/bibles/resources/bibleserver.csv b/openlp/plugins/bibles/resources/bibleserver.csv deleted file mode 100644 index 942d43116..000000000 --- a/openlp/plugins/bibles/resources/bibleserver.csv +++ /dev/null @@ -1,39 +0,0 @@ -عربي, ARA -Bible – překlad 21. století, B21 -Bible du Semeur, BDS -Българската Библия, BLG -Český ekumenický překlad, CEP -Hrvatski, CRO -Священное Писание, CRS -Version La Biblia al Dia, CST -中文和合本(简体), CUVS -Bibelen på hverdagsdansk, DK -Revidierte Elberfelder, ELB -Einheitsübersetzung, EU -Gute Nachricht Bibel, GNB -Hoffnung für alle, HFA -Hungarian, HUN -Het Boek, HTB -La Parola è Vita, ITA -IBS-fordítás (Új Károli), KAR -King James Version, KJV -Luther 1984, LUT -Septuaginta, LXX -Neue Genfer Übersetzung, NGU -New International Readers Version, NIRV -New International Version, NIV -Neues Leben, NL -En Levende Bok (NOR), NOR -Nádej pre kazdého, NPK -Noua traducere în limba românã, NTR -Nueva Versión Internacional, NVI -הברית הישנה, OT -Słowo Życia, POL -O Livro, PRT -Новый перевод на русский язык, RUS -Slovo na cestu, SNC -Schlachter 2000, SLT -En Levande Bok (SWE), SVL -Today's New International Version, TNIV -Türkçe, TR -Biblia Vulgata, VUL diff --git a/openlp/plugins/bibles/resources/crosswalkbooks.csv b/openlp/plugins/bibles/resources/crosswalkbooks.csv deleted file mode 100644 index 7957bfdc8..000000000 --- a/openlp/plugins/bibles/resources/crosswalkbooks.csv +++ /dev/null @@ -1,27 +0,0 @@ -New American Standard,nas -American Standard Version,asv -English Standard Version,esv -New King James Version,nkj -King James Version,kjv -Holman Christian Standard Bible,csb -Third Millennium Bible,tmb -New International Version,niv -New Living Translation,nlt -New Revised Standard,nrs -Revised Standard Version,rsv -Good News Translation,gnt -Douay-Rheims Bible,rhe -The Message,msg -The Complete Jewish Bible,cjb -New Century Version,ncv -GOD'S WORD Translation,gwd -Hebrew Names Version,hnv -World English Bible,web -The Bible in Basic English,bbe -Young's Literal Translation,ylt -Today's New International Version,tnv -New International Reader's Version,nrv -The Darby Translation,dby -The Webster Bible,wbt -The Latin Vulgate,vul -Weymouth New Testament,wnt diff --git a/openlp/plugins/bibles/resources/httpbooks.sqlite b/openlp/plugins/bibles/resources/httpbooks.sqlite deleted file mode 100644 index 406914b63..000000000 Binary files a/openlp/plugins/bibles/resources/httpbooks.sqlite and /dev/null differ diff --git a/openlp/plugins/custom/__init__.py b/openlp/plugins/custom/__init__.py index 3a1e4461a..3446f597e 100644 --- a/openlp/plugins/custom/__init__.py +++ b/openlp/plugins/custom/__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, Armin Köhler, 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 # diff --git a/openlp/plugins/custom/customplugin.py b/openlp/plugins/custom/customplugin.py index c6c129e68..e9260f926 100644 --- a/openlp/plugins/custom/customplugin.py +++ b/openlp/plugins/custom/customplugin.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, Armin Köhler, 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 # @@ -26,8 +27,6 @@ import logging -from forms import EditCustomForm - from openlp.core.lib import Plugin, StringContent, build_icon, translate from openlp.core.lib.db import Manager from openlp.plugins.custom.lib import CustomMediaItem, CustomTab @@ -47,20 +46,19 @@ class CustomPlugin(Plugin): log.info(u'Custom Plugin loaded') def __init__(self, plugin_helpers): - Plugin.__init__(self, u'Custom', u'1.9.4', plugin_helpers, + Plugin.__init__(self, u'custom', plugin_helpers, CustomMediaItem, CustomTab) self.weight = -5 self.manager = Manager(u'custom', init_schema) - self.edit_custom_form = EditCustomForm(self) self.icon_path = u':/plugins/plugin_custom.png' self.icon = build_icon(self.icon_path) def about(self): - about_text = translate('CustomPlugin', 'Custom Plugin' - '
The custom plugin provides the ability to set up custom ' - 'text slides that can be displayed on the screen the same way ' - 'songs are. This plugin provides greater freedom over the songs ' - 'plugin.') + about_text = translate('CustomPlugin', 'Custom Slide Plugin' + '
The custom slide plugin provides the ability to ' + 'set up custom text slides that can be displayed on the screen ' + 'the same way songs are. This plugin provides greater freedom ' + 'over the songs plugin.') return about_text def usesTheme(self, theme): @@ -97,26 +95,31 @@ class CustomPlugin(Plugin): """ ## Name PluginList ## self.textStrings[StringContent.Name] = { - u'singular': translate('CustomsPlugin', 'Custom', 'name singular'), - u'plural': translate('CustomsPlugin', 'Customs', 'name plural') + u'singular': translate('CustomPlugin', 'Custom Slide', + 'name singular'), + u'plural': translate('CustomPlugin', 'Custom Slides', + 'name plural') } ## Name for MediaDockManager, SettingsManager ## self.textStrings[StringContent.VisibleName] = { - u'title': translate('CustomsPlugin', 'Custom', 'container title') + u'title': translate('CustomPlugin', 'Custom Slides', + 'container title') } # Middle Header Bar tooltips = { - u'load': translate('CustomsPlugin', 'Load a new Custom'), - u'import': translate('CustomsPlugin', 'Import a Custom'), - u'new': translate('CustomsPlugin', 'Add a new Custom'), - u'edit': translate('CustomsPlugin', 'Edit the selected Custom'), - u'delete': translate('CustomsPlugin', 'Delete the selected Custom'), - u'preview': translate('CustomsPlugin', - 'Preview the selected Custom'), - u'live': translate('CustomsPlugin', - 'Send the selected Custom live'), - u'service': translate('CustomsPlugin', - 'Add the selected Custom to the service') + u'load': translate('CustomPlugin', 'Load a new custom slide.'), + u'import': translate('CustomPlugin', 'Import a custom slide.'), + u'new': translate('CustomPlugin', 'Add a new custom slide.'), + u'edit': translate('CustomPlugin', + 'Edit the selected custom slide.'), + u'delete': translate('CustomPlugin', + 'Delete the selected custom slide.'), + u'preview': translate('CustomPlugin', + 'Preview the selected custom slide.'), + u'live': translate('CustomPlugin', + 'Send the selected custom slide live.'), + u'service': translate('CustomPlugin', + 'Add the selected custom slide to the service.') } self.setPluginUiTextStrings(tooltips) diff --git a/openlp/plugins/custom/forms/__init__.py b/openlp/plugins/custom/forms/__init__.py index c12d29c07..e901095c2 100644 --- a/openlp/plugins/custom/forms/__init__.py +++ b/openlp/plugins/custom/forms/__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, Armin Köhler, 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 # diff --git a/openlp/plugins/custom/forms/editcustomdialog.py b/openlp/plugins/custom/forms/editcustomdialog.py index 12ac3ed7e..3eee1cfd4 100644 --- a/openlp/plugins/custom/forms/editcustomdialog.py +++ b/openlp/plugins/custom/forms/editcustomdialog.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, Armin Köhler, 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 # @@ -107,11 +108,11 @@ class Ui_CustomEditDialog(object): translate('CustomPlugin.EditCustomForm', 'Edit Custom Slides')) self.titleLabel.setText( translate('CustomPlugin.EditCustomForm', '&Title:')) - self.addButton.setText(UiStrings.Add) + self.addButton.setText(UiStrings().Add) self.addButton.setToolTip( translate('CustomPlugin.EditCustomForm', 'Add a new slide at ' 'bottom.')) - self.editButton.setText(UiStrings.Edit) + self.editButton.setText(UiStrings().Edit) self.editButton.setToolTip( translate('CustomPlugin.EditCustomForm', 'Edit the selected ' 'slide.')) @@ -124,4 +125,4 @@ class Ui_CustomEditDialog(object): translate('CustomPlugin.EditCustomForm', 'The&me:')) self.creditLabel.setText( translate('CustomPlugin.EditCustomForm', '&Credits:')) - self.previewButton.setText(UiStrings.SaveAndPreview) + self.previewButton.setText(UiStrings().SaveAndPreview) diff --git a/openlp/plugins/custom/forms/editcustomform.py b/openlp/plugins/custom/forms/editcustomform.py index 232cb1e38..0eadf6021 100644 --- a/openlp/plugins/custom/forms/editcustomform.py +++ b/openlp/plugins/custom/forms/editcustomform.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, Armin Köhler, 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,7 +30,7 @@ import logging from PyQt4 import QtCore, QtGui from openlp.core.lib import Receiver, translate -from openlp.core.lib.ui import critical_error_message_box +from openlp.core.lib.ui import critical_error_message_box, find_and_set_in_combo_box from openlp.plugins.custom.lib import CustomXMLBuilder, CustomXMLParser from openlp.plugins.custom.lib.db import CustomSlide from editcustomdialog import Ui_CustomEditDialog @@ -42,13 +43,13 @@ class EditCustomForm(QtGui.QDialog, Ui_CustomEditDialog): Class documentation goes here. """ log.info(u'Custom Editor loaded') - def __init__(self, parent): + def __init__(self, mediaitem, parent, manager): """ Constructor """ - QtGui.QDialog.__init__(self) - self.parent = parent - self.manager = self.parent.manager + QtGui.QDialog.__init__(self, parent) + self.manager = manager + self.mediaitem = mediaitem self.setupUi(self) # Create other objects and forms. self.editSlideForm = EditCustomSlideForm(self) @@ -65,6 +66,9 @@ class EditCustomForm(QtGui.QDialog, Ui_CustomEditDialog): QtCore.SIGNAL(u'theme_update_list'), self.loadThemes) QtCore.QObject.connect(self.slideListView, QtCore.SIGNAL(u'currentRowChanged(int)'), self.onCurrentRowChanged) + QtCore.QObject.connect(self.slideListView, + QtCore.SIGNAL(u'doubleClicked(QModelIndex)'), + self.onEditButtonPressed) def loadThemes(self, themelist): self.themeComboBox.clear() @@ -98,15 +102,10 @@ class EditCustomForm(QtGui.QDialog, Ui_CustomEditDialog): for slide in slideList: self.slideListView.addItem(slide[1]) theme = self.customSlide.theme_name - id = self.themeComboBox.findText(theme, QtCore.Qt.MatchExactly) - # No theme match - if id == -1: - id = 0 - self.themeComboBox.setCurrentIndex(id) + find_and_set_in_combo_box(self.themeComboBox, theme) + self.titleEdit.setFocus(QtCore.Qt.OtherFocusReason) # If not preview hide the preview button. - self.previewButton.setVisible(False) - if preview: - self.previewButton.setVisible(True) + self.previewButton.setVisible(preview) def reject(self): Receiver.send_message(u'custom_edit_clear') @@ -115,7 +114,6 @@ class EditCustomForm(QtGui.QDialog, Ui_CustomEditDialog): def accept(self): log.debug(u'accept') if self.saveCustom(): - Receiver.send_message(u'custom_load_list') QtGui.QDialog.accept(self) def saveCustom(self): @@ -136,7 +134,9 @@ class EditCustomForm(QtGui.QDialog, Ui_CustomEditDialog): self.customSlide.text = unicode(sxml.extract_xml(), u'utf-8') self.customSlide.credits = unicode(self.creditEdit.text()) self.customSlide.theme_name = unicode(self.themeComboBox.currentText()) - return self.manager.save_object(self.customSlide) + success = self.manager.save_object(self.customSlide) + self.mediaitem.autoSelectId = self.customSlide.id + return success def onUpButtonClicked(self): selectedRow = self.slideListView.currentRow() @@ -173,7 +173,7 @@ class EditCustomForm(QtGui.QDialog, Ui_CustomEditDialog): item = self.slideListView.item(row) slide_list += item.text() if row != self.slideListView.count() - 1: - slide_list += u'\n[---]\n' + slide_list += u'\n[===]\n' self.editSlideForm.setText(slide_list) if self.editSlideForm.exec_(): self.updateSlideList(self.editSlideForm.getText(), True) @@ -220,10 +220,7 @@ class EditCustomForm(QtGui.QDialog, Ui_CustomEditDialog): Removes the current row from the list. """ self.slideListView.takeItem(self.slideListView.currentRow()) - if self.slideListView.currentRow() == 0: - self.upButton.setEnabled(False) - if self.slideListView.currentRow() == self.slideListView.count(): - self.downButton.setEnabled(False) + self.onCurrentRowChanged(self.slideListView.currentRow()) def onCurrentRowChanged(self, row): """ diff --git a/openlp/plugins/custom/forms/editcustomslidedialog.py b/openlp/plugins/custom/forms/editcustomslidedialog.py index 08610954f..759f6b19d 100644 --- a/openlp/plugins/custom/forms/editcustomslidedialog.py +++ b/openlp/plugins/custom/forms/editcustomslidedialog.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, Armin Köhler, 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 # @@ -26,8 +27,8 @@ from PyQt4 import QtCore, QtGui -from openlp.core.lib import translate, SpellTextEdit -from openlp.core.lib.ui import create_accept_reject_button_box +from openlp.core.lib import translate, SpellTextEdit, build_icon +from openlp.core.lib.ui import create_accept_reject_button_box, UiStrings class Ui_CustomSlideEditDialog(object): def setupUi(self, customSlideEditDialog): @@ -39,16 +40,24 @@ class Ui_CustomSlideEditDialog(object): self.dialogLayout.addWidget(self.slideTextEdit) self.buttonBox = create_accept_reject_button_box(customSlideEditDialog) self.splitButton = QtGui.QPushButton(customSlideEditDialog) + self.splitButton.setIcon(build_icon(u':/general/general_add.png')) self.splitButton.setObjectName(u'splitButton') self.buttonBox.addButton(self.splitButton, QtGui.QDialogButtonBox.ActionRole) + self.insertButton = QtGui.QPushButton(customSlideEditDialog) + self.insertButton.setIcon(build_icon(u':/general/general_add.png')) + self.insertButton.setObjectName(u'insertButton') + self.buttonBox.addButton(self.insertButton, + QtGui.QDialogButtonBox.ActionRole) self.dialogLayout.addWidget(self.buttonBox) self.retranslateUi(customSlideEditDialog) QtCore.QMetaObject.connectSlotsByName(customSlideEditDialog) def retranslateUi(self, customSlideEditDialog): - self.splitButton.setText( - translate('CustomPlugin.EditCustomForm', 'Split Slide')) - self.splitButton.setToolTip( + self.splitButton.setText(UiStrings().Split) + self.splitButton.setToolTip(UiStrings().SplitToolTip) + self.insertButton.setText( + translate('CustomPlugin.EditCustomForm', 'Insert Slide')) + self.insertButton.setToolTip( translate('CustomPlugin.EditCustomForm', 'Split a slide into two ' 'by inserting a slide splitter.')) diff --git a/openlp/plugins/custom/forms/editcustomslideform.py b/openlp/plugins/custom/forms/editcustomslideform.py index 2aea81482..71696ebc1 100644 --- a/openlp/plugins/custom/forms/editcustomslideform.py +++ b/openlp/plugins/custom/forms/editcustomslideform.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, Armin Köhler, 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 # @@ -44,8 +45,10 @@ class EditCustomSlideForm(QtGui.QDialog, Ui_CustomSlideEditDialog): QtGui.QDialog.__init__(self, parent) self.setupUi(self) # Connecting signals and slots + QtCore.QObject.connect(self.insertButton, + QtCore.SIGNAL(u'clicked()'), self.onInsertButtonPressed) QtCore.QObject.connect(self.splitButton, - QtCore.SIGNAL(u'pressed()'), self.onSplitButtonPressed) + QtCore.SIGNAL(u'clicked()'), self.onSplitButtonPressed) def setText(self, text): """ @@ -63,13 +66,22 @@ class EditCustomSlideForm(QtGui.QDialog, Ui_CustomSlideEditDialog): """ Returns a list with all slides. """ - return self.slideTextEdit.toPlainText().split(u'\n[---]\n') + return self.slideTextEdit.toPlainText().split(u'\n[===]\n') - def onSplitButtonPressed(self): + def onInsertButtonPressed(self): """ Adds a slide split at the cursor. """ if self.slideTextEdit.textCursor().columnNumber() != 0: self.slideTextEdit.insertPlainText(u'\n') - self.slideTextEdit.insertPlainText(u'[---]\n') + self.slideTextEdit.insertPlainText(u'[===]\n') + self.slideTextEdit.setFocus() + + def onSplitButtonPressed(self): + """ + Adds a virtual split at cursor. + """ + if self.slideTextEdit.textCursor().columnNumber() != 0: + self.slideTextEdit.insertPlainText(u'\n') + self.slideTextEdit.insertPlainText(u'[---]') self.slideTextEdit.setFocus() diff --git a/openlp/plugins/custom/lib/__init__.py b/openlp/plugins/custom/lib/__init__.py index 0c53505ac..0e343b6cc 100644 --- a/openlp/plugins/custom/lib/__init__.py +++ b/openlp/plugins/custom/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, Armin Köhler, 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 # diff --git a/openlp/plugins/custom/lib/customtab.py b/openlp/plugins/custom/lib/customtab.py index 9b61f8f15..8a7762f37 100644 --- a/openlp/plugins/custom/lib/customtab.py +++ b/openlp/plugins/custom/lib/customtab.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, Armin Köhler, 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 # @@ -32,8 +33,8 @@ class CustomTab(SettingsTab): """ CustomTab is the Custom settings tab in the settings dialog. """ - def __init__(self, title, visible_title): - SettingsTab.__init__(self, title, visible_title) + def __init__(self, parent, title, visible_title, icon_path): + SettingsTab.__init__(self, parent, title, visible_title, icon_path) def setupUi(self): self.setObjectName(u'CustomTab') diff --git a/openlp/plugins/custom/lib/customxmlhandler.py b/openlp/plugins/custom/lib/customxmlhandler.py index 5ea941e35..ff9fab7a7 100644 --- a/openlp/plugins/custom/lib/customxmlhandler.py +++ b/openlp/plugins/custom/lib/customxmlhandler.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, Armin Köhler, 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 # diff --git a/openlp/plugins/custom/lib/db.py b/openlp/plugins/custom/lib/db.py index 74155ad96..0cefaf012 100644 --- a/openlp/plugins/custom/lib/db.py +++ b/openlp/plugins/custom/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, Armin Köhler, 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 # diff --git a/openlp/plugins/custom/lib/mediaitem.py b/openlp/plugins/custom/lib/mediaitem.py index c0c7a7e86..59d6b4fb6 100644 --- a/openlp/plugins/custom/lib/mediaitem.py +++ b/openlp/plugins/custom/lib/mediaitem.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, Armin Köhler, 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,17 +26,29 @@ ############################################################################### import logging +import locale from PyQt4 import QtCore, QtGui +from sqlalchemy.sql import or_, func from openlp.core.lib import MediaManagerItem, Receiver, ItemCapabilities, \ - check_item_selected + check_item_selected, translate +from openlp.core.lib.searchedit import SearchEdit from openlp.core.lib.ui import UiStrings +from openlp.plugins.custom.forms import EditCustomForm from openlp.plugins.custom.lib import CustomXMLParser from openlp.plugins.custom.lib.db import CustomSlide log = logging.getLogger(__name__) +class CustomSearch(object): + """ + An enumeration for custom search methods. + """ + Titles = 1 + Themes = 2 + + class CustomMediaItem(MediaManagerItem): """ This is the custom media manager item for Custom Slides. @@ -44,26 +57,96 @@ class CustomMediaItem(MediaManagerItem): def __init__(self, parent, plugin, icon): self.IconPath = u'custom/custom' - MediaManagerItem.__init__(self, parent, self, icon) + MediaManagerItem.__init__(self, parent, plugin, icon) + self.edit_custom_form = EditCustomForm(self, self.plugin.formparent, + self.plugin.manager) self.singleServiceItem = False + self.quickPreviewAllowed = True + self.hasSearch = True # Holds information about whether the edit is remotly triggered and # which Custom is required. self.remoteCustom = -1 - self.manager = parent.manager + self.manager = plugin.manager def addEndHeaderBar(self): + self.addToolbarSeparator() + self.searchWidget = QtGui.QWidget(self) + self.searchWidget.setObjectName(u'searchWidget') + self.searchLayout = QtGui.QVBoxLayout(self.searchWidget) + self.searchLayout.setObjectName(u'searchLayout') + self.searchTextLayout = QtGui.QFormLayout() + self.searchTextLayout.setObjectName(u'searchTextLayout') + self.searchTextLabel = QtGui.QLabel(self.searchWidget) + self.searchTextLabel.setObjectName(u'searchTextLabel') + self.searchTextEdit = SearchEdit(self.searchWidget) + self.searchTextEdit.setObjectName(u'searchTextEdit') + self.searchTextLabel.setBuddy(self.searchTextEdit) + self.searchTextLayout.addRow(self.searchTextLabel, self.searchTextEdit) + self.searchLayout.addLayout(self.searchTextLayout) + self.searchButtonLayout = QtGui.QHBoxLayout() + self.searchButtonLayout.setObjectName(u'searchButtonLayout') + self.searchButtonLayout.addStretch() + self.searchTextButton = QtGui.QPushButton(self.searchWidget) + self.searchTextButton.setObjectName(u'searchTextButton') + self.searchButtonLayout.addWidget(self.searchTextButton) + self.searchLayout.addLayout(self.searchButtonLayout) + self.pageLayout.addWidget(self.searchWidget) + # Signals and slots + QtCore.QObject.connect(self.searchTextEdit, + QtCore.SIGNAL(u'returnPressed()'), self.onSearchTextButtonClick) + QtCore.QObject.connect(self.searchTextButton, + QtCore.SIGNAL(u'pressed()'), self.onSearchTextButtonClick) + QtCore.QObject.connect(self.searchTextEdit, + QtCore.SIGNAL(u'textChanged(const QString&)'), + self.onSearchTextEditChanged) + QtCore.QObject.connect(self.searchTextEdit, + QtCore.SIGNAL(u'cleared()'), self.onClearTextButtonClick) + QtCore.QObject.connect(self.searchTextEdit, + QtCore.SIGNAL(u'searchTypeChanged(int)'), + self.onSearchTextButtonClick) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'custom_edit'), self.onRemoteEdit) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'custom_edit_clear'), self.onRemoteEditClear) QtCore.QObject.connect(Receiver.get_receiver(), - QtCore.SIGNAL(u'custom_load_list'), self.initialise) + QtCore.SIGNAL(u'custom_load_list'), self.loadList) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'custom_preview'), self.onPreviewClick) + def retranslateUi(self): + self.searchTextLabel.setText(u'%s:' % UiStrings().Search) + self.searchTextButton.setText(UiStrings().Search) + def initialise(self): + self.searchTextEdit.setSearchTypes([ + (CustomSearch.Titles, u':/songs/song_search_title.png', + translate('SongsPlugin.MediaItem', 'Titles')), + (CustomSearch.Themes, u':/slides/slide_theme.png', + UiStrings().Themes) + ]) self.loadList(self.manager.get_all_objects( CustomSlide, order_by_ref=CustomSlide.title)) + self.searchTextEdit.setCurrentSearchType(QtCore.QSettings().value( + u'%s/last search type' % self.settingsSection, + QtCore.QVariant(CustomSearch.Titles)).toInt()[0]) + + def loadList(self, custom_slides): + # Sort out what custom we want to select after loading the list. + self.saveAutoSelectId() + self.listView.clear() + # Sort the customs by its title considering language specific + # characters. lower() is needed for windows! + custom_slides.sort( + cmp=locale.strcoll, key=lambda custom: custom.title.lower()) + for custom_slide in custom_slides: + custom_name = QtGui.QListWidgetItem(custom_slide.title) + custom_name.setData( + QtCore.Qt.UserRole, QtCore.QVariant(custom_slide.id)) + self.listView.addItem(custom_name) + # Auto-select the custom. + if custom_slide.id == self.autoSelectId: + self.listView.setCurrentItem(custom_name) + self.autoSelectId = -1 # Called to redisplay the custom list screen edith from a search # or from the exit of the Custom edit dialog. If remote editing is # active trigger it and clean up so it will not update again. @@ -73,73 +156,83 @@ class CustomMediaItem(MediaManagerItem): self.onPreviewClick() self.onRemoteEditClear() - def loadList(self, list): - self.listView.clear() - for customSlide in list: - custom_name = QtGui.QListWidgetItem(customSlide.title) - custom_name.setData( - QtCore.Qt.UserRole, QtCore.QVariant(customSlide.id)) - self.listView.addItem(custom_name) - def onNewClick(self): - self.parent.edit_custom_form.loadCustom(0) - self.parent.edit_custom_form.exec_() - self.initialise() + self.edit_custom_form.loadCustom(0) + self.edit_custom_form.exec_() + self.onClearTextButtonClick() + self.onSelectionChange() def onRemoteEditClear(self): self.remoteTriggered = None self.remoteCustom = -1 - def onRemoteEdit(self, customid): + def onRemoteEdit(self, message): """ Called by ServiceManager or SlideController by event passing - the Song Id in the payload along with an indicator to say which + the custom Id in the payload along with an indicator to say which type of display is required. """ - fields = customid.split(u':') - valid = self.manager.get_object(CustomSlide, fields[1]) + remote_type, custom_id = message.split(u':') + custom_id = int(custom_id) + valid = self.manager.get_object(CustomSlide, custom_id) if valid: - self.remoteCustom = fields[1] - self.remoteTriggered = fields[0] - self.parent.edit_custom_form.loadCustom(fields[1], - (fields[0] == u'P')) - self.parent.edit_custom_form.exec_() + self.remoteCustom = custom_id + self.remoteTriggered = remote_type + self.edit_custom_form.loadCustom(custom_id, (remote_type == u'P')) + self.edit_custom_form.exec_() + self.autoSelectId = -1 + self.onSearchTextButtonClick() def onEditClick(self): """ Edit a custom item """ - if check_item_selected(self.listView, UiStrings.SelectEdit): + if check_item_selected(self.listView, UiStrings().SelectEdit): item = self.listView.currentItem() item_id = (item.data(QtCore.Qt.UserRole)).toInt()[0] - self.parent.edit_custom_form.loadCustom(item_id, False) - self.parent.edit_custom_form.exec_() - self.initialise() + self.edit_custom_form.loadCustom(item_id, False) + self.edit_custom_form.exec_() + self.autoSelectId = -1 + self.onSearchTextButtonClick() def onDeleteClick(self): """ Remove a custom item from the list and database """ - if check_item_selected(self.listView, UiStrings.SelectDelete): + if check_item_selected(self.listView, UiStrings().SelectDelete): + items = self.listView.selectedIndexes() + if QtGui.QMessageBox.question(self, + UiStrings().ConfirmDelete, + translate('CustomPlugin.MediaItem', + 'Are you sure you want to delete the %n selected custom' + ' slides(s)?', '', + QtCore.QCoreApplication.CodecForTr, len(items)), + QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Yes | + QtGui.QMessageBox.No), + QtGui.QMessageBox.Yes) == QtGui.QMessageBox.No: + return row_list = [item.row() for item in self.listView.selectedIndexes()] row_list.sort(reverse=True) id_list = [(item.data(QtCore.Qt.UserRole)).toInt()[0] for item in self.listView.selectedIndexes()] for id in id_list: - self.parent.manager.delete_object(CustomSlide, id) - for row in row_list: - self.listView.takeItem(row) + self.plugin.manager.delete_object(CustomSlide, id) + self.onSearchTextButtonClick() - def generateSlideData(self, service_item, item=None, xmlVersion=False): - raw_slides = [] + def onFocus(self): + self.searchTextEdit.setFocus() + + def generateSlideData(self, service_item, item=None, xmlVersion=False, + remote=False): raw_footer = [] slide = None theme = None item_id = self._getIdOfItemToGenerate(item, self.remoteCustom) - service_item.add_capability(ItemCapabilities.AllowsEdit) - service_item.add_capability(ItemCapabilities.AllowsPreview) - service_item.add_capability(ItemCapabilities.AllowsLoop) - customSlide = self.parent.manager.get_object(CustomSlide, item_id) + service_item.add_capability(ItemCapabilities.CanEdit) + service_item.add_capability(ItemCapabilities.CanPreview) + service_item.add_capability(ItemCapabilities.CanLoop) + service_item.add_capability(ItemCapabilities.CanSoftBreak) + customSlide = self.plugin.manager.get_object(CustomSlide, item_id) title = customSlide.title credit = customSlide.credits service_item.edit_id = item_id @@ -148,8 +241,7 @@ class CustomMediaItem(MediaManagerItem): service_item.theme = theme customXML = CustomXMLParser(customSlide.text) verseList = customXML.get_verses() - for verse in verseList: - raw_slides.append(verse[1]) + raw_slides = [verse[1] for verse in verseList] service_item.title = title for slide in raw_slides: service_item.add_from_text(slide[:30], slide) @@ -160,3 +252,55 @@ class CustomMediaItem(MediaManagerItem): raw_footer.append(u'') service_item.raw_footer = raw_footer return True + + def onSearchTextButtonClick(self): + # Save the current search type to the configuration. + QtCore.QSettings().setValue(u'%s/last search type' % + self.settingsSection, + QtCore.QVariant(self.searchTextEdit.currentSearchType())) + # Reload the list considering the new search type. + search_keywords = unicode(self.searchTextEdit.displayText()) + search_results = [] + search_type = self.searchTextEdit.currentSearchType() + if search_type == CustomSearch.Titles: + log.debug(u'Titles Search') + search_results = self.plugin.manager.get_all_objects(CustomSlide, + CustomSlide.title.like(u'%' + self.whitespace.sub(u' ', + search_keywords) + u'%'), order_by_ref=CustomSlide.title) + self.loadList(search_results) + elif search_type == CustomSearch.Themes: + log.debug(u'Theme Search') + search_results = self.plugin.manager.get_all_objects(CustomSlide, + CustomSlide.theme_name.like(u'%' + self.whitespace.sub(u' ', + search_keywords) + u'%'), order_by_ref=CustomSlide.title) + self.loadList(search_results) + self.checkSearchResult() + + def onSearchTextEditChanged(self, text): + """ + If search as type enabled invoke the search on each key press. + If the Title is being searched do not start until 2 characters + have been entered. + """ + search_length = 2 + if len(text) > search_length: + self.onSearchTextButtonClick() + elif len(text) == 0: + self.onClearTextButtonClick() + + def onClearTextButtonClick(self): + """ + Clear the search text. + """ + self.searchTextEdit.clear() + self.onSearchTextButtonClick() + + def search(self, string): + search_results = self.manager.get_all_objects(CustomSlide, + or_(func.lower(CustomSlide.title).like(u'%' + + string.lower() + u'%'), + func.lower(CustomSlide.text).like(u'%' + + string.lower() + u'%')), + order_by_ref=CustomSlide.title) + return [[custom.id, custom.title] for custom in search_results] + diff --git a/openlp/plugins/images/__init__.py b/openlp/plugins/images/__init__.py index 13ea2592a..b95ac000d 100644 --- a/openlp/plugins/images/__init__.py +++ b/openlp/plugins/images/__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, Armin Köhler, 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 # diff --git a/openlp/plugins/images/imageplugin.py b/openlp/plugins/images/imageplugin.py index 84d7a71cc..4b5a6f3c0 100644 --- a/openlp/plugins/images/imageplugin.py +++ b/openlp/plugins/images/imageplugin.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, Armin Köhler, 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 # @@ -24,10 +25,13 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### +from PyQt4 import QtCore, QtGui + import logging -from openlp.core.lib import Plugin, StringContent, build_icon, translate -from openlp.plugins.images.lib import ImageMediaItem +from openlp.core.lib import Plugin, StringContent, build_icon, translate, \ + Receiver +from openlp.plugins.images.lib import ImageMediaItem, ImageTab log = logging.getLogger(__name__) @@ -35,11 +39,13 @@ class ImagePlugin(Plugin): log.info(u'Image Plugin loaded') def __init__(self, plugin_helpers): - Plugin.__init__(self, u'Images', u'1.9.4', plugin_helpers, - ImageMediaItem) + Plugin.__init__(self, u'images', plugin_helpers, ImageMediaItem, + ImageTab) self.weight = -7 self.icon_path = u':/plugins/plugin_images.png' self.icon = build_icon(self.icon_path) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'image_updated'), self.image_updated) def about(self): about_text = translate('ImagePlugin', 'Image Plugin' @@ -70,14 +76,24 @@ class ImagePlugin(Plugin): } # Middle Header Bar tooltips = { - u'load': translate('ImagePlugin', 'Load a new Image'), + u'load': translate('ImagePlugin', 'Load a new image.'), u'import': u'', - u'new': translate('ImagePlugin', 'Add a new Image'), - u'edit': translate('ImagePlugin', 'Edit the selected Image'), - u'delete': translate('ImagePlugin', 'Delete the selected Image'), - u'preview': translate('ImagePlugin', 'Preview the selected Image'), - u'live': translate('ImagePlugin', 'Send the selected Image live'), + u'new': translate('ImagePlugin', 'Add a new image.'), + u'edit': translate('ImagePlugin', 'Edit the selected image.'), + u'delete': translate('ImagePlugin', 'Delete the selected image.'), + u'preview': translate('ImagePlugin', 'Preview the selected image.'), + u'live': translate('ImagePlugin', 'Send the selected image live.'), u'service': translate('ImagePlugin', - 'Add the selected Image to the service') + 'Add the selected image to the service.') } self.setPluginUiTextStrings(tooltips) + + def image_updated(self): + """ + Triggered by saving and changing the image border. Sets the images in + image manager to require updates. Actual update is triggered by the + last part of saving the config. + """ + background = QtGui.QColor(QtCore.QSettings().value(self.settingsSection + + u'/background color', QtCore.QVariant(u'#000000'))) + self.liveController.imageManager.update_images(u'image', background) diff --git a/openlp/plugins/images/lib/__init__.py b/openlp/plugins/images/lib/__init__.py index 34c9f1ca8..e216623cd 100644 --- a/openlp/plugins/images/lib/__init__.py +++ b/openlp/plugins/images/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, Armin Köhler, 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,3 +26,4 @@ ############################################################################### from mediaitem import ImageMediaItem +from imagetab import ImageTab diff --git a/openlp/plugins/images/lib/imagetab.py b/openlp/plugins/images/lib/imagetab.py new file mode 100644 index 000000000..1aa39b63c --- /dev/null +++ b/openlp/plugins/images/lib/imagetab.py @@ -0,0 +1,100 @@ +# -*- 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 PyQt4 import QtCore, QtGui + +from openlp.core.lib import SettingsTab, translate, Receiver + +class ImageTab(SettingsTab): + """ + ImageTab is the images settings tab in the settings dialog. + """ + def __init__(self, parent, name, visible_title, icon_path): + SettingsTab.__init__(self, parent, name, visible_title, icon_path) + + def setupUi(self): + self.setObjectName(u'ImagesTab') + SettingsTab.setupUi(self) + self.bgColorGroupBox = QtGui.QGroupBox(self.leftColumn) + self.bgColorGroupBox.setObjectName(u'FontGroupBox') + self.formLayout = QtGui.QFormLayout(self.bgColorGroupBox) + self.formLayout.setObjectName(u'FormLayout') + self.colorLayout = QtGui.QHBoxLayout() + self.backgroundColorLabel = QtGui.QLabel(self.bgColorGroupBox) + self.backgroundColorLabel.setObjectName(u'BackgroundColorLabel') + self.colorLayout.addWidget(self.backgroundColorLabel) + self.backgroundColorButton = QtGui.QPushButton(self.bgColorGroupBox) + self.backgroundColorButton.setObjectName(u'BackgroundColorButton') + self.colorLayout.addWidget(self.backgroundColorButton) + self.formLayout.addRow(self.colorLayout) + self.informationLabel = QtGui.QLabel(self.bgColorGroupBox) + self.informationLabel.setObjectName(u'InformationLabel') + self.formLayout.addRow(self.informationLabel) + self.leftLayout.addWidget(self.bgColorGroupBox) + self.leftLayout.addStretch() + self.rightColumn.setSizePolicy( + QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred) + self.rightLayout.addStretch() + # Signals and slots + QtCore.QObject.connect(self.backgroundColorButton, + QtCore.SIGNAL(u'pressed()'), self.onbackgroundColorButtonClicked) + + def retranslateUi(self): + self.bgColorGroupBox.setTitle( + translate('ImagesPlugin.ImageTab', 'Background Color')) + self.backgroundColorLabel.setText( + translate('ImagesPlugin.ImageTab', 'Default Color:')) + self.informationLabel.setText( + translate('ImagesPlugin.ImageTab', 'Provides border where image ' + 'is not the correct dimensions for the screen when resized.')) + + def onbackgroundColorButtonClicked(self): + new_color = QtGui.QColorDialog.getColor( + QtGui.QColor(self.bg_color), self) + if new_color.isValid(): + self.bg_color = new_color.name() + self.backgroundColorButton.setStyleSheet( + u'background-color: %s' % self.bg_color) + + def load(self): + settings = QtCore.QSettings() + settings.beginGroup(self.settingsSection) + self.bg_color = unicode(settings.value( + u'background color', QtCore.QVariant(u'#000000')).toString()) + self.initial_color = self.bg_color + settings.endGroup() + self.backgroundColorButton.setStyleSheet( + u'background-color: %s' % self.bg_color) + + def save(self): + settings = QtCore.QSettings() + settings.beginGroup(self.settingsSection) + settings.setValue(u'background color', QtCore.QVariant(self.bg_color)) + settings.endGroup() + if self.initial_color != self.bg_color: + Receiver.send_message(u'image_updated') + diff --git a/openlp/plugins/images/lib/mediaitem.py b/openlp/plugins/images/lib/mediaitem.py index e6fdfe7db..049e2da18 100644 --- a/openlp/plugins/images/lib/mediaitem.py +++ b/openlp/plugins/images/lib/mediaitem.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, Armin Köhler, 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 # @@ -26,12 +27,13 @@ import logging import os +import locale from PyQt4 import QtCore, QtGui from openlp.core.lib import MediaManagerItem, build_icon, ItemCapabilities, \ SettingsManager, translate, check_item_selected, check_directory_exists, \ - Receiver + Receiver, create_thumb, validate_thumb from openlp.core.lib.ui import UiStrings, critical_error_message_box from openlp.core.utils import AppLocation, delete_file, get_images_filter @@ -45,20 +47,24 @@ class ImageMediaItem(MediaManagerItem): def __init__(self, parent, plugin, icon): self.IconPath = u'images/image' - MediaManagerItem.__init__(self, parent, self, icon) + MediaManagerItem.__init__(self, parent, plugin, icon) + self.quickPreviewAllowed = True + self.hasSearch = True QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'live_theme_changed'), self.liveThemeChanged) + # Allow DnD from the desktop + self.listView.activateDnD() def retranslateUi(self): self.onNewPrompt = translate('ImagePlugin.MediaItem', 'Select Image(s)') file_formats = get_images_filter() self.onNewFileMasks = u'%s;;%s (*.*) (*)' % (file_formats, - UiStrings.AllFiles) - self.replaceAction.setText(UiStrings.ReplaceBG) - self.replaceAction.setToolTip(UiStrings.ReplaceLiveBG) - self.resetAction.setText(UiStrings.ResetBG) - self.resetAction.setToolTip(UiStrings.ResetLiveBG) + UiStrings().AllFiles) + self.replaceAction.setText(UiStrings().ReplaceBG) + self.replaceAction.setToolTip(UiStrings().ReplaceLiveBG) + self.resetAction.setText(UiStrings().ResetBG) + self.resetAction.setToolTip(UiStrings().ResetLiveBG) def requiredIcons(self): MediaManagerItem.requiredIcons(self) @@ -76,7 +82,7 @@ class ImageMediaItem(MediaManagerItem): u'thumbnails') check_directory_exists(self.servicePath) self.loadList(SettingsManager.load_list( - self.settingsSection, self.settingsSection)) + self.settingsSection, u'images'), True) def addListViewToToolBar(self): MediaManagerItem.addListViewToToolBar(self) @@ -93,6 +99,8 @@ class ImageMediaItem(MediaManagerItem): """ Remove an image item from the list """ + # Turn off auto preview triggers. + self.listView.blockSignals(True) if check_item_selected(self.listView, translate('ImagePlugin.MediaItem', 'You must select an image to delete.')): row_list = [item.row() for item in self.listView.selectedIndexes()] @@ -104,77 +112,93 @@ class ImageMediaItem(MediaManagerItem): unicode(text.text()))) self.listView.takeItem(row) SettingsManager.set_list(self.settingsSection, - self.settingsSection, self.getFileList()) + u'images', self.getFileList()) + self.listView.blockSignals(False) - def loadList(self, list): - for imageFile in list: + def loadList(self, images, initialLoad=False): + if not initialLoad: + self.plugin.formparent.displayProgressBar(len(images)) + # Sort the themes by its filename considering language specific + # characters. lower() is needed for windows! + images.sort(cmp=locale.strcoll, + key=lambda filename: os.path.split(unicode(filename))[1].lower()) + for imageFile in images: + if not initialLoad: + self.plugin.formparent.incrementProgressBar() filename = os.path.split(unicode(imageFile))[1] thumb = os.path.join(self.servicePath, filename) - if os.path.exists(thumb): - if self.validate(imageFile, thumb): + if not os.path.exists(imageFile): + icon = build_icon(u':/general/general_delete.png') + else: + if validate_thumb(imageFile, thumb): icon = build_icon(thumb) else: - icon = build_icon(u':/general/general_delete.png') - else: - icon = self.iconFromFile(imageFile, thumb) + icon = create_thumb(imageFile, thumb) item_name = QtGui.QListWidgetItem(filename) item_name.setIcon(icon) + item_name.setToolTip(imageFile) item_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(imageFile)) self.listView.addItem(item_name) + if not initialLoad: + self.plugin.formparent.finishedProgressBar() - def generateSlideData(self, service_item, item=None, xmlVersion=False): - items = self.listView.selectedIndexes() - if items: - service_item.title = unicode(self.plugin.nameStrings[u'plural']) - service_item.add_capability(ItemCapabilities.AllowsMaintain) - service_item.add_capability(ItemCapabilities.AllowsPreview) - service_item.add_capability(ItemCapabilities.AllowsLoop) - service_item.add_capability(ItemCapabilities.AllowsAdditions) - # force a nonexistent theme - service_item.theme = -1 - missing_items = [] - missing_items_filenames = [] - for item in items: - bitem = self.listView.item(item.row()) - filename = unicode(bitem.data(QtCore.Qt.UserRole).toString()) - if not os.path.exists(filename): - missing_items.append(item) - missing_items_filenames.append(filename) - for item in missing_items: - items.remove(item) - # We cannot continue, as all images do not exist. + def generateSlideData(self, service_item, item=None, xmlVersion=False, + remote=False): + background = QtGui.QColor(QtCore.QSettings().value(self.settingsSection + + u'/background color', QtCore.QVariant(u'#000000'))) + if item: + items = [item] + else: + items = self.listView.selectedItems() if not items: + return False + service_item.title = unicode(self.plugin.nameStrings[u'plural']) + service_item.add_capability(ItemCapabilities.CanMaintain) + service_item.add_capability(ItemCapabilities.CanPreview) + service_item.add_capability(ItemCapabilities.CanLoop) + service_item.add_capability(ItemCapabilities.CanAppend) + # force a nonexistent theme + service_item.theme = -1 + missing_items = [] + missing_items_filenames = [] + for bitem in items: + filename = unicode(bitem.data(QtCore.Qt.UserRole).toString()) + if not os.path.exists(filename): + missing_items.append(bitem) + missing_items_filenames.append(filename) + for item in missing_items: + items.remove(item) + # We cannot continue, as all images do not exist. + if not items: + if not remote: critical_error_message_box( translate('ImagePlugin.MediaItem', 'Missing Image(s)'), unicode(translate('ImagePlugin.MediaItem', 'The following image(s) no longer exist: %s')) % u'\n'.join(missing_items_filenames)) - return False - # We have missing as well as existing images. We ask what to do. - elif missing_items and QtGui.QMessageBox.question(self, - translate('ImagePlugin.MediaItem', 'Missing Image(s)'), - unicode(translate('ImagePlugin.MediaItem', 'The following ' - 'image(s) no longer exist: %s\nDo you want to add the other ' - 'images anyway?')) % u'\n'.join(missing_items_filenames), - QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.No | - QtGui.QMessageBox.Yes)) == QtGui.QMessageBox.No: - return False - # Continue with the existing images. - for item in items: - bitem = self.listView.item(item.row()) - filename = unicode(bitem.data(QtCore.Qt.UserRole).toString()) - (path, name) = os.path.split(filename) - service_item.add_from_image(filename, name) - return True - else: return False + # We have missing as well as existing images. We ask what to do. + elif missing_items and QtGui.QMessageBox.question(self, + translate('ImagePlugin.MediaItem', 'Missing Image(s)'), + unicode(translate('ImagePlugin.MediaItem', 'The following ' + 'image(s) no longer exist: %s\nDo you want to add the other ' + 'images anyway?')) % u'\n'.join(missing_items_filenames), + QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.No | + QtGui.QMessageBox.Yes)) == QtGui.QMessageBox.No: + return False + # Continue with the existing images. + for bitem in items: + filename = unicode(bitem.data(QtCore.Qt.UserRole).toString()) + (path, name) = os.path.split(filename) + service_item.add_from_image(filename, name, background) + return True def onResetClick(self): """ Called to reset the Live backgound with the image selected, """ self.resetAction.setVisible(False) - self.parent.liveController.display.resetImage() + self.plugin.liveController.display.resetImage() def liveThemeChanged(self): """ @@ -189,15 +213,33 @@ class ImageMediaItem(MediaManagerItem): if check_item_selected(self.listView, translate('ImagePlugin.MediaItem', 'You must select an image to replace the background with.')): + background = QtGui.QColor(QtCore.QSettings().value( + self.settingsSection + u'/background color', + QtCore.QVariant(u'#000000'))) item = self.listView.selectedIndexes()[0] bitem = self.listView.item(item.row()) filename = unicode(bitem.data(QtCore.Qt.UserRole).toString()) if os.path.exists(filename): (path, name) = os.path.split(filename) - self.parent.liveController.display.directImage(name, filename) - self.resetAction.setVisible(True) + if self.plugin.liveController.display.directImage(name, + filename, background): + self.resetAction.setVisible(True) + else: + critical_error_message_box(UiStrings().LiveBGError, + translate('ImagePlugin.MediaItem', + 'There was no display item to amend.')) else: - critical_error_message_box(UiStrings.LiveBGError, + critical_error_message_box(UiStrings().LiveBGError, unicode(translate('ImagePlugin.MediaItem', 'There was a problem replacing your background, ' 'the image file "%s" no longer exists.')) % filename) + + def search(self, string): + files = SettingsManager.load_list(self.settingsSection, u'images') + results = [] + string = string.lower() + for file in files: + filename = os.path.split(unicode(file))[1] + if filename.lower().find(string) > -1: + results.append([file, filename]) + return results diff --git a/openlp/plugins/media/__init__.py b/openlp/plugins/media/__init__.py index c13c59ef6..32cff0c44 100644 --- a/openlp/plugins/media/__init__.py +++ b/openlp/plugins/media/__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, Armin Köhler, 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 # diff --git a/openlp/plugins/media/lib/__init__.py b/openlp/plugins/media/lib/__init__.py index cb4806631..25b8d531a 100644 --- a/openlp/plugins/media/lib/__init__.py +++ b/openlp/plugins/media/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, Armin Köhler, 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 # diff --git a/openlp/plugins/media/lib/mediaitem.py b/openlp/plugins/media/lib/mediaitem.py index 40ea7abb1..c8f746851 100644 --- a/openlp/plugins/media/lib/mediaitem.py +++ b/openlp/plugins/media/lib/mediaitem.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, Armin Köhler, 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 # @@ -24,18 +25,22 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### +from datetime import datetime import logging import os +import locale from PyQt4 import QtCore, QtGui - -from openlp.core.lib import MediaManagerItem, build_icon, ItemCapabilities, \ - SettingsManager, translate, check_item_selected, Receiver -from openlp.core.lib.ui import UiStrings, critical_error_message_box from PyQt4.phonon import Phonon +from openlp.core.lib import MediaManagerItem, build_icon, ItemCapabilities, \ + SettingsManager, translate, check_item_selected, Receiver, MediaType +from openlp.core.lib.ui import UiStrings, critical_error_message_box + log = logging.getLogger(__name__) +CLAPPERBOARD = QtGui.QImage(u':/media/media_video.png') + class MediaMediaItem(MediaManagerItem): """ This is the custom media manager item for Media Slides. @@ -43,30 +48,32 @@ class MediaMediaItem(MediaManagerItem): log.info(u'%s MediaMediaItem loaded', __name__) def __init__(self, parent, plugin, icon): - self.IconPath = u'images/image' + self.iconPath = u'images/image' self.background = False - self.PreviewFunction = QtGui.QPixmap( - u':/media/media_video.png').toImage() - MediaManagerItem.__init__(self, parent, self, icon) + self.previewFunction = CLAPPERBOARD + MediaManagerItem.__init__(self, parent, plugin, icon) self.singleServiceItem = False - self.mediaObject = Phonon.MediaObject(self) + self.hasSearch = True + self.mediaObject = None QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'video_background_replaced'), self.videobackgroundReplaced) - QtCore.QObject.connect(self.mediaObject, - QtCore.SIGNAL(u'stateChanged(Phonon::State, Phonon::State)'), - self.videoStart) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'openlp_phonon_creation'), + self.createPhonon) + # Allow DnD from the desktop + self.listView.activateDnD() def retranslateUi(self): self.onNewPrompt = translate('MediaPlugin.MediaItem', 'Select Media') self.onNewFileMasks = unicode(translate('MediaPlugin.MediaItem', 'Videos (%s);;Audio (%s);;%s (*)')) % ( - u' '.join(self.parent.video_extensions_list), - u' '.join(self.parent.audio_extensions_list), UiStrings.AllFiles) - self.replaceAction.setText(UiStrings.ReplaceBG) - self.replaceAction.setToolTip(UiStrings.ReplaceLiveBG) - self.resetAction.setText(UiStrings.ResetBG) - self.resetAction.setToolTip(UiStrings.ResetLiveBG) + u' '.join(self.plugin.video_extensions_list), + u' '.join(self.plugin.audio_extensions_list), UiStrings().AllFiles) + self.replaceAction.setText(UiStrings().ReplaceBG) + self.replaceAction.setToolTip(UiStrings().ReplaceLiveBG) + self.resetAction.setText(UiStrings().ResetBG) + self.resetAction.setToolTip(UiStrings().ResetLiveBG) def requiredIcons(self): MediaManagerItem.requiredIcons(self) @@ -88,14 +95,14 @@ class MediaMediaItem(MediaManagerItem): def onResetClick(self): """ - Called to reset the Live backgound with the media selected, + Called to reset the Live backgound with the media selected. """ self.resetAction.setVisible(False) - self.parent.liveController.display.resetVideo() + self.plugin.liveController.display.resetVideo() def videobackgroundReplaced(self): """ - Triggered by main display on change of serviceitem + Triggered by main display on change of serviceitem. """ self.resetAction.setVisible(False) @@ -110,55 +117,87 @@ class MediaMediaItem(MediaManagerItem): filename = unicode(item.data(QtCore.Qt.UserRole).toString()) if os.path.exists(filename): (path, name) = os.path.split(filename) - self.parent.liveController.display.video(filename, 0, True) - self.resetAction.setVisible(True) + if self.plugin.liveController.display.video(filename, 0, True): + self.resetAction.setVisible(True) + else: + critical_error_message_box(UiStrings().LiveBGError, + translate('MediaPlugin.MediaItem', + 'There was no display item to amend.')) else: - critical_error_message_box(UiStrings.LiveBGError, + critical_error_message_box(UiStrings().LiveBGError, unicode(translate('MediaPlugin.MediaItem', 'There was a problem replacing your background, ' 'the media file "%s" no longer exists.')) % filename) - def generateSlideData(self, service_item, item=None, xmlVersion=False): + def generateSlideData(self, service_item, item=None, xmlVersion=False, + remote=False): if item is None: item = self.listView.currentItem() if item is None: return False filename = unicode(item.data(QtCore.Qt.UserRole).toString()) - if os.path.exists(filename): - self.mediaState = None - self.mediaObject.stop() - self.mediaObject.clearQueue() - self.mediaObject.setCurrentSource(Phonon.MediaSource(filename)) - self.mediaObject.play() - service_item.title = unicode(self.plugin.nameStrings[u'singular']) - service_item.add_capability(ItemCapabilities.RequiresMedia) - service_item.add_capability(ItemCapabilities.AllowsVarableStartTime) - # force a nonexistent theme - service_item.theme = -1 - frame = u':/media/image_clapperboard.png' - (path, name) = os.path.split(filename) - while not self.mediaState: - Receiver.send_message(u'openlp_process_events') - service_item.media_length = self.mediaLength - service_item.add_from_command(path, name, frame) - return True - else: - # File is no longer present - critical_error_message_box( - translate('MediaPlugin.MediaItem', 'Missing Media File'), - unicode(translate('MediaPlugin.MediaItem', - 'The file %s no longer exists.')) % filename) + if not os.path.exists(filename): + if not remote: + # File is no longer present + critical_error_message_box( + translate('MediaPlugin.MediaItem', 'Missing Media File'), + unicode(translate('MediaPlugin.MediaItem', + 'The file %s no longer exists.')) % filename) return False + self.mediaObject.stop() + self.mediaObject.clearQueue() + self.mediaObject.setCurrentSource(Phonon.MediaSource(filename)) + if not self.mediaStateWait(Phonon.StoppedState): + critical_error_message_box(UiStrings().UnsupportedFile, + UiStrings().UnsupportedFile) + return False + # File too big for processing + if os.path.getsize(filename) <= 52428800: # 50MiB + self.mediaObject.play() + if not self.mediaStateWait(Phonon.PlayingState) \ + or self.mediaObject.currentSource().type() \ + == Phonon.MediaSource.Invalid: + self.mediaObject.stop() + critical_error_message_box( + translate('MediaPlugin.MediaItem', 'File Too Big'), + translate('MediaPlugin.MediaItem', 'The file you are ' + 'trying to load is too big. Please reduce it to less ' + 'than 50MiB.')) + return False + self.mediaObject.stop() + service_item.media_length = self.mediaObject.totalTime() / 1000 + service_item.add_capability( + ItemCapabilities.HasVariableStartTime) + service_item.title = unicode(self.plugin.nameStrings[u'singular']) + service_item.add_capability(ItemCapabilities.RequiresMedia) + # force a non-existent theme + service_item.theme = -1 + frame = u':/media/image_clapperboard.png' + (path, name) = os.path.split(filename) + service_item.add_from_command(path, name, frame) + return True + + def mediaStateWait(self, mediaState): + """ + Wait for the video to change its state. Wait no longer than 5 seconds. + """ + start = datetime.now() + while self.mediaObject.state() != mediaState: + if self.mediaObject.state() == Phonon.ErrorState: + return False + Receiver.send_message(u'openlp_process_events') + if (datetime.now() - start).seconds > 5: + return False + return True def initialise(self): self.listView.clear() self.listView.setIconSize(QtCore.QSize(88, 50)) - self.loadList(SettingsManager.load_list(self.settingsSection, - self.settingsSection)) + self.loadList(SettingsManager.load_list(self.settingsSection, u'media')) def onDeleteClick(self): """ - Remove a media item from the list + Remove a media item from the list. """ if check_item_selected(self.listView, translate('MediaPlugin.MediaItem', 'You must select a media file to delete.')): @@ -167,22 +206,45 @@ class MediaMediaItem(MediaManagerItem): for row in row_list: self.listView.takeItem(row) SettingsManager.set_list(self.settingsSection, - self.settingsSection, self.getFileList()) + u'media', self.getFileList()) - def loadList(self, list): - for file in list: - filename = os.path.split(unicode(file))[1] + def loadList(self, media): + # Sort the themes by its filename considering language specific + # characters. lower() is needed for windows! + media.sort(cmp=locale.strcoll, + key=lambda filename: os.path.split(unicode(filename))[1].lower()) + for track in media: + filename = os.path.split(unicode(track))[1] item_name = QtGui.QListWidgetItem(filename) - img = QtGui.QPixmap(u':/media/media_video.png').toImage() - item_name.setIcon(build_icon(img)) - item_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(file)) + item_name.setIcon(build_icon(CLAPPERBOARD)) + item_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(track)) + item_name.setToolTip(track) self.listView.addItem(item_name) - def videoStart(self, newState, oldState): - """ - Start the video at a predetermined point. - """ - if newState == Phonon.PlayingState: - self.mediaState = newState - self.mediaLength = self.mediaObject.totalTime()/1000 - self.mediaObject.stop() + def getList(self, type=MediaType.Audio): + media = SettingsManager.load_list(self.settingsSection, u'media') + media.sort(cmp=locale.strcoll, + key=lambda filename: os.path.split(unicode(filename))[1].lower()) + ext = [] + if type == MediaType.Audio: + ext = self.plugin.audio_extensions_list + else: + ext = self.plugin.video_extensions_list + ext = map(lambda x: x[1:], ext) + media = filter(lambda x: os.path.splitext(x)[1] in ext, media) + return media + + def createPhonon(self): + log.debug(u'CreatePhonon') + if not self.mediaObject: + self.mediaObject = Phonon.MediaObject(self) + + def search(self, string): + files = SettingsManager.load_list(self.settingsSection, u'media') + results = [] + string = string.lower() + for file in files: + filename = os.path.split(unicode(file))[1] + if filename.lower().find(string) > -1: + results.append([file, filename]) + return results diff --git a/openlp/plugins/media/lib/mediatab.py b/openlp/plugins/media/lib/mediatab.py index 995816860..058ea3149 100644 --- a/openlp/plugins/media/lib/mediatab.py +++ b/openlp/plugins/media/lib/mediatab.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, Armin Köhler, 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 # @@ -32,8 +33,8 @@ class MediaTab(SettingsTab): """ MediaTab is the Media settings tab in the settings dialog. """ - def __init__(self, title, visible_title): - SettingsTab.__init__(self, title, visible_title) + def __init__(self, parent, title, visible_title, icon_path): + SettingsTab.__init__(self, parent, title, visible_title, icon_path) def setupUi(self): self.setObjectName(u'MediaTab') diff --git a/openlp/plugins/media/mediaplugin.py b/openlp/plugins/media/mediaplugin.py index 3438f0279..c215b1be0 100644 --- a/openlp/plugins/media/mediaplugin.py +++ b/openlp/plugins/media/mediaplugin.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, Armin Köhler, 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 # @@ -38,13 +39,27 @@ class MediaPlugin(Plugin): log.info(u'%s MediaPlugin loaded', __name__) def __init__(self, plugin_helpers): - Plugin.__init__(self, u'Media', u'1.9.4', plugin_helpers, + Plugin.__init__(self, u'media', plugin_helpers, MediaMediaItem, MediaTab) self.weight = -6 self.icon_path = u':/plugins/plugin_media.png' self.icon = build_icon(self.icon_path) # passed with drag and drop messages self.dnd_id = u'Media' + self.additional_extensions = { + u'audio/ac3': [u'.ac3'], + u'audio/flac': [u'.flac'], + u'audio/x-m4a': [u'.m4a'], + u'audio/midi': [u'.mid', u'.midi'], + u'audio/x-mp3': [u'.mp3'], + u'audio/mpeg': [u'.mp3', u'.mp2', u'.mpga', u'.mpega', u'.m4a'], + u'audio/qcelp': [u'.qcp'], + u'audio/x-wma': [u'.wma'], + u'audio/x-ms-wma': [u'.wma'], + u'video/x-flv': [u'.flv'], + u'video/x-matroska': [u'.mpv', u'.mkv'], + u'video/x-wmv': [u'.wmv'], + u'video/x-ms-wmv': [u'.wmv']} self.audio_extensions_list = [] self.video_extensions_list = [] mimetypes.init() @@ -65,6 +80,17 @@ class MediaPlugin(Plugin): self.serviceManager.supportedSuffixes(extension[1:]) log.info(u'MediaPlugin: %s extensions: %s' % (mimetype, u' '.join(extensions))) + # Add extensions for this mimetype from self.additional_extensions. + # This hack clears mimetypes' and operating system's shortcomings + # by providing possibly missing extensions. + if mimetype in self.additional_extensions.keys(): + for extension in self.additional_extensions[mimetype]: + ext = u'*%s' % extension + if ext not in list: + list.append(ext) + self.serviceManager.supportedSuffixes(extension[1:]) + log.info(u'MediaPlugin: %s additional extensions: %s' % (mimetype, + u' '.join(self.additional_extensions[mimetype]))) def about(self): about_text = translate('MediaPlugin', 'Media Plugin' @@ -86,14 +112,14 @@ class MediaPlugin(Plugin): } # Middle Header Bar tooltips = { - u'load': translate('MediaPlugin', 'Load a new Media'), + u'load': translate('MediaPlugin', 'Load new media.'), u'import': u'', - u'new': translate('MediaPlugin', 'Add a new Media'), - u'edit': translate('MediaPlugin', 'Edit the selected Media'), - u'delete': translate('MediaPlugin', 'Delete the selected Media'), - u'preview': translate('MediaPlugin', 'Preview the selected Media'), - u'live': translate('MediaPlugin', 'Send the selected Media live'), + u'new': translate('MediaPlugin', 'Add new media.'), + u'edit': translate('MediaPlugin', 'Edit the selected media.'), + u'delete': translate('MediaPlugin', 'Delete the selected media.'), + u'preview': translate('MediaPlugin', 'Preview the selected media.'), + u'live': translate('MediaPlugin', 'Send the selected media live.'), u'service': translate('MediaPlugin', - 'Add the selected Media to the service') + 'Add the selected media to the service.') } self.setPluginUiTextStrings(tooltips) diff --git a/openlp/plugins/presentations/__init__.py b/openlp/plugins/presentations/__init__.py index 628816bcf..d7cb7afe5 100644 --- a/openlp/plugins/presentations/__init__.py +++ b/openlp/plugins/presentations/__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, Armin Köhler, 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 # diff --git a/openlp/plugins/presentations/lib/__init__.py b/openlp/plugins/presentations/lib/__init__.py index b5575268a..3c4e0499d 100644 --- a/openlp/plugins/presentations/lib/__init__.py +++ b/openlp/plugins/presentations/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, Armin Köhler, 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 # diff --git a/openlp/plugins/presentations/lib/impresscontroller.py b/openlp/plugins/presentations/lib/impresscontroller.py index 48fb85ed3..36f684ad4 100644 --- a/openlp/plugins/presentations/lib/impresscontroller.py +++ b/openlp/plugins/presentations/lib/impresscontroller.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, Armin Köhler, 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 # @@ -45,6 +46,7 @@ else: try: import uno from com.sun.star.beans import PropertyValue + from com.sun.star.task import ErrorCodeIOException uno_available = True except ImportError: uno_available = False @@ -123,7 +125,7 @@ class ImpressController(PresentationController): try: uno_instance = get_uno_instance(resolver) except: - log.exception(u'Unable to find running instance ') + log.warn(u'Unable to find running instance ') self.start_process() loop += 1 try: @@ -134,7 +136,7 @@ class ImpressController(PresentationController): "com.sun.star.frame.Desktop", uno_instance) return desktop except: - log.exception(u'Failed to get UNO desktop') + log.warn(u'Failed to get UNO desktop') return None def get_com_desktop(self): @@ -149,7 +151,7 @@ class ImpressController(PresentationController): try: desktop = self.manager.createInstance(u'com.sun.star.frame.Desktop') except AttributeError: - log.exception(u'Failure to find desktop - Impress may have closed') + log.warn(u'Failure to find desktop - Impress may have closed') return desktop if desktop else None def get_com_servicemanager(self): @@ -160,7 +162,7 @@ class ImpressController(PresentationController): try: return Dispatch(u'com.sun.star.ServiceManager') except pywintypes.com_error: - log.exception(u'Failed to get COM service manager. ' + log.warn(u'Failed to get COM service manager. ' u'Impress Controller has been disabled') return None @@ -178,7 +180,7 @@ class ImpressController(PresentationController): else: desktop = self.get_com_desktop() except: - log.exception(u'Failed to find an OpenOffice desktop to terminate') + log.warn(u'Failed to find an OpenOffice desktop to terminate') if not desktop: return docs = desktop.getComponents() @@ -189,7 +191,7 @@ class ImpressController(PresentationController): desktop.terminate() log.debug(u'OpenOffice killed') except: - log.exception(u'Failed to terminate OpenOffice') + log.warn(u'Failed to terminate OpenOffice') class ImpressDocument(PresentationDocument): @@ -219,7 +221,6 @@ class ImpressDocument(PresentationDocument): The file name of the presentatios to the run. """ log.debug(u'Load Presentation OpenOffice') - #print "s.dsk1 ", self.desktop if os.name == u'nt': desktop = self.controller.get_com_desktop() if desktop is None: @@ -234,17 +235,26 @@ class ImpressDocument(PresentationDocument): return False self.desktop = desktop properties = [] - properties.append(self.create_property(u'Minimized', True)) + if os.name != u'nt': + # Recent versions of Impress on Windows won't start the presentation + # if it starts as minimized. It seems OK on Linux though. + properties.append(self.create_property(u'Minimized', True)) properties = tuple(properties) try: self.document = desktop.loadComponentFromURL(url, u'_blank', 0, properties) except: - log.exception(u'Failed to load presentation %s' % url) + log.warn(u'Failed to load presentation %s' % url) return False + if os.name == u'nt': + # As we can't start minimized the Impress window gets in the way. + # Either window.setPosSize(0, 0, 200, 400, 12) or .setVisible(False) + window = self.document.getCurrentController().getFrame() \ + .getContainerWindow() + window.setVisible(False) self.presentation = self.document.getPresentation() self.presentation.Display = \ - self.controller.plugin.renderManager.screens.current_display + 1 + self.controller.plugin.renderer.screens.current[u'number'] + 1 self.control = None self.create_thumbnails() return True @@ -278,6 +288,9 @@ class ImpressDocument(PresentationDocument): doc.storeToURL(urlpath, props) self.convert_thumbnail(path, idx + 1) delete_file(path) + except ErrorCodeIOException, exception: + log.exception(u'ERROR! ErrorCodeIOException %d' % + exception.ErrCode) except: log.exception(u'%s - Unable to store openoffice preview' % path) @@ -310,7 +323,7 @@ class ImpressDocument(PresentationDocument): self.presentation = None self.document.dispose() except: - log.exception("Closing presentation failed") + log.warn("Closing presentation failed") self.document = None self.controller.remove_doc(self) @@ -328,7 +341,7 @@ class ImpressDocument(PresentationDocument): log.debug("getPresentation failed to find a presentation") return False except: - log.exception("getPresentation failed to find a presentation") + log.warn("getPresentation failed to find a presentation") return False return True @@ -387,14 +400,14 @@ class ImpressDocument(PresentationDocument): log.debug(u'start presentation OpenOffice') if self.control is None or not self.control.isRunning(): self.presentation.start() - # start() returns before the getCurrentComponent is ready. - # Try for 5 seconds + self.control = self.presentation.getController() + # start() returns before the Component is ready. + # Try for 15 seconds i = 1 - while self.desktop.getCurrentComponent() is None and i < 50: + while not self.control and i < 150: time.sleep(0.1) i = i + 1 - self.control = \ - self.desktop.getCurrentComponent().Presentation.getController() + self.control = self.presentation.getController() else: self.control.activate() self.goto_slide(1) diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index 2a552a480..e1dd57271 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.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, Armin Köhler, 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 # @@ -26,11 +27,13 @@ import logging import os +import locale from PyQt4 import QtCore, QtGui from openlp.core.lib import MediaManagerItem, build_icon, SettingsManager, \ - translate, check_item_selected, Receiver, ItemCapabilities + translate, check_item_selected, Receiver, ItemCapabilities, create_thumb, \ + validate_thumb from openlp.core.lib.ui import UiStrings, critical_error_message_box, \ media_item_combo_box from openlp.plugins.presentations.lib import MessageListener @@ -44,17 +47,21 @@ class PresentationMediaItem(MediaManagerItem): """ log.info(u'Presentations Media Item loaded') - def __init__(self, parent, icon, title, controllers): + def __init__(self, parent, plugin, icon, controllers): """ Constructor. Setup defaults """ self.controllers = controllers self.IconPath = u'presentations/presentation' self.Automatic = u'' - MediaManagerItem.__init__(self, parent, self, icon) + MediaManagerItem.__init__(self, parent, plugin, icon) self.message_listener = MessageListener(self) + self.hasSearch = True + self.singleServiceItem = False QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'mediaitem_presentation_rebuild'), self.rebuild) + # Allow DnD from the desktop + self.listView.activateDnD() def retranslateUi(self): """ @@ -79,7 +86,7 @@ class PresentationMediaItem(MediaManagerItem): for type in types: if fileType.find(type) == -1: fileType += u'*.%s ' % type - self.parent.serviceManager.supportedSuffixes(type) + self.plugin.serviceManager.supportedSuffixes(type) self.onNewFileMasks = unicode(translate('PresentationPlugin.MediaItem', 'Presentations (%s)')) % fileType @@ -116,9 +123,9 @@ class PresentationMediaItem(MediaManagerItem): Populate the media manager tab """ self.listView.setIconSize(QtCore.QSize(88, 50)) - list = SettingsManager.load_list( + files = SettingsManager.load_list( self.settingsSection, u'presentations') - self.loadList(list, True) + self.loadList(files, True) self.populateDisplayTypes() def rebuild(self): @@ -148,17 +155,24 @@ class PresentationMediaItem(MediaManagerItem): else: self.presentationWidget.hide() - def loadList(self, list, initialLoad=False): + def loadList(self, files, initialLoad=False): """ Add presentations into the media manager This is called both on initial load of the plugin to populate with existing files, and when the user adds new files via the media manager """ currlist = self.getFileList() - titles = [] - for file in currlist: - titles.append(os.path.split(file)[1]) - for file in list: + titles = [os.path.split(file)[1] for file in currlist] + Receiver.send_message(u'cursor_busy') + if not initialLoad: + self.plugin.formparent.displayProgressBar(len(files)) + # Sort the themes by its filename considering language specific + # characters. lower() is needed for windows! + files.sort(cmp=locale.strcoll, + key=lambda filename: os.path.split(unicode(filename))[1].lower()) + for file in files: + if not initialLoad: + self.plugin.formparent.incrementProgressBar() if currlist.count(file) > 0: continue filename = os.path.split(unicode(file))[1] @@ -180,30 +194,35 @@ class PresentationMediaItem(MediaManagerItem): doc.load_presentation() preview = doc.get_thumbnail_path(1, True) doc.close_presentation() - if preview and self.validate(preview, thumb): - icon = build_icon(thumb) - else: + if not (preview and os.path.exists(preview)): icon = build_icon(u':/general/general_delete.png') + else: + if validate_thumb(preview, thumb): + icon = build_icon(thumb) + else: + icon = create_thumb(preview, thumb) else: if initialLoad: icon = build_icon(u':/general/general_delete.png') else: - critical_error_message_box( - self, translate('PresentationPlugin.MediaItem', - 'Unsupported File'), + critical_error_message_box(UiStrings().UnsupportedFile, translate('PresentationPlugin.MediaItem', 'This type of presentation is not supported.')) continue item_name = QtGui.QListWidgetItem(filename) item_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(file)) item_name.setIcon(icon) + item_name.setToolTip(file) self.listView.addItem(item_name) + Receiver.send_message(u'cursor_normal') + if not initialLoad: + self.plugin.formparent.finishedProgressBar() def onDeleteClick(self): """ Remove a presentation item from the list """ - if check_item_selected(self.listView, UiStrings.SelectDelete): + if check_item_selected(self.listView, UiStrings().SelectDelete): items = self.listView.selectedIndexes() row_list = [item.row() for item in items] row_list.sort(reverse=True) @@ -217,25 +236,28 @@ class PresentationMediaItem(MediaManagerItem): for row in row_list: self.listView.takeItem(row) SettingsManager.set_list(self.settingsSection, - self.settingsSection, self.getFileList()) + u'presentations', self.getFileList()) - def generateSlideData(self, service_item, item=None, xmlVersion=False): + def generateSlideData(self, service_item, item=None, xmlVersion=False, + remote=False): """ Load the relevant information for displaying the presentation in the slidecontroller. In the case of powerpoints, an image for each slide """ - items = self.listView.selectedIndexes() - if len(items) > 1: - return False + if item: + items = [item] + else: + items = self.listView.selectedItems() + if len(items) > 1: + return False service_item.title = unicode(self.displayTypeComboBox.currentText()) service_item.shortname = unicode(self.displayTypeComboBox.currentText()) service_item.add_capability(ItemCapabilities.ProvidesOwnDisplay) - service_item.add_capability(ItemCapabilities.AllowsDetailedTitleDisplay) + service_item.add_capability(ItemCapabilities.HasDetailedTitleDisplay) shortname = service_item.shortname if shortname: - for item in items: - bitem = self.listView.item(item.row()) + for bitem in items: filename = unicode(bitem.data(QtCore.Qt.UserRole).toString()) if os.path.exists(filename): if shortname == self.Automatic: @@ -259,12 +281,13 @@ class PresentationMediaItem(MediaManagerItem): return True else: # File is no longer present - critical_error_message_box( - translate('PresentationPlugin.MediaItem', - 'Missing Presentation'), - unicode(translate('PresentationPlugin.MediaItem', - 'The Presentation %s is incomplete,' - ' please reload.')) % filename) + if not remote: + critical_error_message_box( + translate('PresentationPlugin.MediaItem', + 'Missing Presentation'), + unicode(translate('PresentationPlugin.MediaItem', + 'The Presentation %s is incomplete,' + ' please reload.')) % filename) return False else: # File is no longer present @@ -285,7 +308,7 @@ class PresentationMediaItem(MediaManagerItem): "supports" the extension. If none found, then look for a controller which "also supports" it instead. """ - filetype = filename.split(u'.')[1] + filetype = os.path.splitext(filename)[1][1:] if not filetype: return None for controller in self.controllers: @@ -297,3 +320,14 @@ class PresentationMediaItem(MediaManagerItem): if filetype in self.controllers[controller].alsosupports: return controller return None + + def search(self, string): + files = SettingsManager.load_list( + self.settingsSection, u'presentations') + results = [] + string = string.lower() + for file in files: + filename = os.path.split(unicode(file))[1] + if filename.lower().find(string) > -1: + results.append([file, filename]) + return results diff --git a/openlp/plugins/presentations/lib/messagelistener.py b/openlp/plugins/presentations/lib/messagelistener.py index 4db78f7a5..0f38cf02f 100644 --- a/openlp/plugins/presentations/lib/messagelistener.py +++ b/openlp/plugins/presentations/lib/messagelistener.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, Armin Köhler, 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 # @@ -49,7 +50,7 @@ class Controller(object): self.doc = None log.info(u'%s controller loaded' % live) - def add_handler(self, controller, file, is_blank): + def add_handler(self, controller, file, hide_mode, slide_no): """ Add a handler, which is an instance of a presentation and slidecontroller combination. If the slidecontroller has a display @@ -64,12 +65,21 @@ class Controller(object): # Display error message to user # Inform slidecontroller that the action failed? return + self.doc.slidenumber = slide_no if self.is_live: - self.doc.start_presentation() - if is_blank: - self.blank() - Receiver.send_message(u'maindisplay_hide', HideMode.Screen) - self.doc.slidenumber = 0 + if hide_mode == HideMode.Screen: + Receiver.send_message(u'live_display_hide', HideMode.Screen) + self.stop() + elif hide_mode == HideMode.Theme: + self.blank(hide_mode) + elif hide_mode == HideMode.Blank: + self.blank(hide_mode) + else: + self.doc.start_presentation() + Receiver.send_message(u'live_display_hide', HideMode.Screen) + self.doc.slidenumber = 0 + if slide_no > 1: + self.slide(slide_no) def activate(self): """ @@ -80,10 +90,13 @@ class Controller(object): if self.doc.is_active(): return if not self.doc.is_loaded(): - self.doc.load_presentation() + if not self.doc.load_presentation(): + return if self.is_live: self.doc.start_presentation() if self.doc.slidenumber > 1: + if self.doc.slidenumber > self.doc.get_slide_count(): + self.doc.slidenumber = self.doc.get_slide_count() self.doc.goto_slide(self.doc.slidenumber) def slide(self, slide): @@ -139,6 +152,11 @@ class Controller(object): if self.doc.slidenumber < self.doc.get_slide_count(): self.doc.slidenumber = self.doc.slidenumber + 1 return + # The "End of slideshow" screen is after the last slide + # Note, we can't just stop on the last slide, since it may + # contain animations that need to be stepped through. + if self.doc.slidenumber > self.doc.get_slide_count(): + return self.activate() self.doc.next_step() self.doc.poll_slidenumber(self.is_live) @@ -163,14 +181,10 @@ class Controller(object): Based on the handler passed at startup triggers slide show to shut down """ log.debug(u'Live = %s, shutdown' % self.is_live) - if self.is_live: - Receiver.send_message(u'maindisplay_show') self.doc.close_presentation() self.doc = None - #self.doc.slidenumber = 0 - #self.timer.stop() - def blank(self): + def blank(self, hide_mode): """ Instruct the controller to blank the presentation """ @@ -181,6 +195,8 @@ class Controller(object): return if not self.doc.is_active(): return + if hide_mode == HideMode.Theme: + Receiver.send_message(u'live_display_hide', HideMode.Theme) self.doc.blank_screen() def stop(self): @@ -208,7 +224,7 @@ class Controller(object): self.doc.slidenumber != self.doc.get_slide_number(): self.doc.goto_slide(self.doc.slidenumber) self.doc.unblank_screen() - Receiver.send_message(u'maindisplay_hide', HideMode.Screen) + Receiver.send_message(u'live_display_hide', HideMode.Screen) def poll(self): self.doc.poll_slidenumber(self.is_live) @@ -260,7 +276,7 @@ class MessageListener(object): is_live = message[1] item = message[0] log.debug(u'Startup called with message %s' % message) - is_blank = message[2] + hide_mode = message[2] file = os.path.join(item.get_frame_path(), item.get_frame_title()) self.handler = item.title @@ -272,7 +288,8 @@ class MessageListener(object): controller = self.live_handler else: controller = self.preview_handler - controller.add_handler(self.controllers[self.handler], file, is_blank) + controller.add_handler(self.controllers[self.handler], file, hide_mode, + message[3]) def slide(self, message): """ @@ -332,7 +349,6 @@ class MessageListener(object): """ is_live = message[1] if is_live: - Receiver.send_message(u'maindisplay_show') self.live_handler.shutdown() else: self.preview_handler.shutdown() @@ -350,8 +366,9 @@ class MessageListener(object): React to the message to blank the display """ is_live = message[1] + hide_mode = message[2] if is_live: - self.live_handler.blank() + self.live_handler.blank(hide_mode) def unblank(self, message): """ diff --git a/openlp/plugins/presentations/lib/powerpointcontroller.py b/openlp/plugins/presentations/lib/powerpointcontroller.py index 4dc9e8f3a..8f551e411 100644 --- a/openlp/plugins/presentations/lib/powerpointcontroller.py +++ b/openlp/plugins/presentations/lib/powerpointcontroller.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, Armin Köhler, 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 # @@ -77,7 +78,9 @@ class PowerpointController(PresentationController): """ Loads PowerPoint process """ - self.process = Dispatch(u'PowerPoint.Application') + log.debug(u'start_process') + if not self.process: + self.process = Dispatch(u'PowerPoint.Application') self.process.Visible = True self.process.WindowState = 2 @@ -120,13 +123,14 @@ class PowerpointDocument(PresentationDocument): ``presentation`` The file name of the presentations to run. """ - log.debug(u'LoadPresentation') + log.debug(u'load_presentation') if not self.controller.process or not self.controller.process.Visible: self.controller.start_process() try: self.controller.process.Presentations.Open(self.filepath, False, False, True) except pywintypes.com_error: + log.debug(u'PPT open failed') return False self.presentation = self.controller.process.Presentations( self.controller.process.Presentations.Count) @@ -145,6 +149,7 @@ class PowerpointDocument(PresentationDocument): However, for the moment, we want a physical file since it makes life easier elsewhere. """ + log.debug(u'create_thumbnails') if self.check_thumbnails(): return for num in range(0, self.presentation.Slides.Count): @@ -170,6 +175,7 @@ class PowerpointDocument(PresentationDocument): """ Returns ``True`` if a presentation is loaded. """ + log.debug(u'is_loaded') try: if not self.controller.process.Visible: return False @@ -186,6 +192,7 @@ class PowerpointDocument(PresentationDocument): """ Returns ``True`` if a presentation is currently active. """ + log.debug(u'is_active') if not self.is_loaded(): return False try: @@ -201,6 +208,7 @@ class PowerpointDocument(PresentationDocument): """ Unblanks (restores) the presentation. """ + log.debug(u'unblank_screen') self.presentation.SlideShowSettings.Run() self.presentation.SlideShowWindow.View.State = 1 self.presentation.SlideShowWindow.Activate() @@ -209,12 +217,14 @@ class PowerpointDocument(PresentationDocument): """ Blanks the screen. """ + log.debug(u'blank_screen') self.presentation.SlideShowWindow.View.State = 3 def is_blank(self): """ Returns ``True`` if screen is blank. """ + log.debug(u'is_blank') if self.is_active(): return self.presentation.SlideShowWindow.View.State == 3 else: @@ -224,6 +234,7 @@ class PowerpointDocument(PresentationDocument): """ Stops the current presentation and hides the output. """ + log.debug(u'stop_presentation') self.presentation.SlideShowWindow.View.Exit() if os.name == u'nt': @@ -231,6 +242,7 @@ class PowerpointDocument(PresentationDocument): """ Starts a presentation from the beginning. """ + log.debug(u'start_presentation') #SlideShowWindow measures its size/position by points, not pixels try: dpi = win32ui.GetActiveWindow().GetDC().GetDeviceCaps(88) @@ -240,43 +252,48 @@ class PowerpointDocument(PresentationDocument): win32ui.GetForegroundWindow().GetDC().GetDeviceCaps(88) except win32ui.error: dpi = 96 - self.presentation.SlideShowSettings.Run() - self.presentation.SlideShowWindow.View.GotoSlide(1) - rendermanager = self.controller.plugin.renderManager - rect = rendermanager.screens.current[u'size'] - self.presentation.SlideShowWindow.Top = rect.y() * 72 / dpi - self.presentation.SlideShowWindow.Height = rect.height() * 72 / dpi - self.presentation.SlideShowWindow.Left = rect.x() * 72 / dpi - self.presentation.SlideShowWindow.Width = rect.width() * 72 / dpi + renderer = self.controller.plugin.renderer + rect = renderer.screens.current[u'size'] + ppt_window = self.presentation.SlideShowSettings.Run() + ppt_window.Top = rect.y() * 72 / dpi + ppt_window.Height = rect.height() * 72 / dpi + ppt_window.Left = rect.x() * 72 / dpi + ppt_window.Width = rect.width() * 72 / dpi + def get_slide_number(self): """ Returns the current slide number. """ + log.debug(u'get_slide_number') return self.presentation.SlideShowWindow.View.CurrentShowPosition def get_slide_count(self): """ Returns total number of slides. """ + log.debug(u'get_slide_count') return self.presentation.Slides.Count def goto_slide(self, slideno): """ Moves to a specific slide in the presentation. """ + log.debug(u'goto_slide') self.presentation.SlideShowWindow.View.GotoSlide(slideno) def next_step(self): """ Triggers the next effect of slide on the running presentation. """ + log.debug(u'next_step') self.presentation.SlideShowWindow.View.Next() def previous_step(self): """ Triggers the previous slide on the running presentation. """ + log.debug(u'previous_step') self.presentation.SlideShowWindow.View.Previous() def get_slide_text(self, slide_no): diff --git a/openlp/plugins/presentations/lib/pptviewcontroller.py b/openlp/plugins/presentations/lib/pptviewcontroller.py index 0900d1d9d..cd940da5c 100644 --- a/openlp/plugins/presentations/lib/pptviewcontroller.py +++ b/openlp/plugins/presentations/lib/pptviewcontroller.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, Armin Köhler, 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 # @@ -84,7 +85,8 @@ class PptviewController(PresentationController): dllpath = os.path.join(self.plugin.pluginManager.basepath, u'presentations', u'lib', u'pptviewlib', u'pptviewlib.dll') self.process = cdll.LoadLibrary(dllpath) - #self.process.SetDebug(1) + if log.isEnabledFor(logging.DEBUG): + self.process.SetDebug(1) def kill(self): """ @@ -120,8 +122,8 @@ class PptviewDocument(PresentationDocument): The file name of the presentations to run. """ log.debug(u'LoadPresentation') - rendermanager = self.controller.plugin.renderManager - rect = rendermanager.screens.current[u'size'] + renderer = self.controller.plugin.renderer + rect = renderer.screens.current[u'size'] rect = RECT(rect.x(), rect.y(), rect.right(), rect.bottom()) filepath = str(self.filepath.replace(u'/', u'\\')) if not os.path.isdir(self.get_temp_folder()): @@ -140,8 +142,10 @@ class PptviewDocument(PresentationDocument): PPTviewLib creates large BMP's, but we want small PNG's for consistency. Convert them here. """ + log.debug(u'create_thumbnails') if self.check_thumbnails(): return + log.debug(u'create_thumbnails proceeding') for idx in range(self.get_slide_count()): path = u'%s\\slide%s.bmp' % (self.get_temp_folder(), unicode(idx + 1)) diff --git a/openlp/plugins/presentations/lib/pptviewlib/README.TXT b/openlp/plugins/presentations/lib/pptviewlib/README.TXT index 686278729..6cbf18cb0 100644 --- a/openlp/plugins/presentations/lib/pptviewlib/README.TXT +++ b/openlp/plugins/presentations/lib/pptviewlib/README.TXT @@ -1,17 +1,17 @@ PPTVIEWLIB - Control PowerPoint Viewer 2003/2007 (for openlp.org) -Copyright (C) 2008 Jonathan Corwin (j@corwin.co.uk) - -This library wrappers the free Microsoft PowerPoint Viewer (2003/2007) program, -allowing it to be more easily controlled from another program. +Copyright (C) 2008-2011 Jonathan Corwin (j@corwin.co.uk) -The PowerPoint Viewer must already be installed on the destination machine, and is +This library wrappers the free Microsoft PowerPoint Viewer (2003/2007) program, +allowing it to be more easily controlled from another program. + +The PowerPoint Viewer must already be installed on the destination machine, and is freely available at microsoft.com. The full Microsoft Office PowerPoint and PowerPoint Viewer 97 have a COM interface allowing automation. This ability was removed from the 2003+ viewer offerings. -To developers: I am not a C/C++ or Win32 API programmer as you can probably tell. +To developers: I am not a C/C++ or Win32 API programmer as you can probably tell. The code and API of this DLL could certainly do with some tidying up, and the error trapping, where it exists, is very basic. I'll happily accept patches! @@ -28,94 +28,94 @@ This project can be built with the free Microsoft Visual C++ 2008 Express Editio USAGE ----- BOOL CheckInstalled(void); - Returns TRUE if PowerPointViewer is installed. FALSE if not. + Returns TRUE if PowerPointViewer is installed. FALSE if not. int OpenPPT(char *filename, HWND hParentWnd, RECT rect, char *previewpath); - Opens the PowerPoint file, counts the number of slides, sizes and positions accordingly - and creates preview images of each slide. Note PowerPoint Viewer only allows the - slideshow to be resized whilst it is being loaded. It can be moved at any time however. + Opens the PowerPoint file, counts the number of slides, sizes and positions accordingly + and creates preview images of each slide. Note PowerPoint Viewer only allows the + slideshow to be resized whilst it is being loaded. It can be moved at any time however. - The only way to count the number of slides is to step through the entire show. Therefore - there will be a delay whilst opening large presentations for the first time. - For pre XP/2003 systems, the slideshow will flicker as the screen snapshots are taken. + The only way to count the number of slides is to step through the entire show. Therefore + there will be a delay whilst opening large presentations for the first time. + For pre XP/2003 systems, the slideshow will flicker as the screen snapshots are taken. - filename: The PowerPoint file to be opened. Full path - hParentWnd: The window which will become the parent of the slideshow window. - Can be NULL. - rect: The location/dimensions of the slideshow window. - If all properties of this structure are zero, the dimensions of the hParentWnd - are used. - previewpath If specified, the prefix to use for snapshot images of each slide, in the - form: previewpath + n + ".bmp", where n is the slide number. - A file called previewpath + "info.txt" will also be created containing information - about the PPT file, to speed up future openings of the unmodified file. - Note it is up the calling program to directly access these images if they - are required. + filename: The PowerPoint file to be opened. Full path + hParentWnd: The window which will become the parent of the slideshow window. + Can be NULL. + rect: The location/dimensions of the slideshow window. + If all properties of this structure are zero, the dimensions of the hParentWnd + are used. + previewpath If specified, the prefix to use for snapshot images of each slide, in the + form: previewpath + n + ".bmp", where n is the slide number. + A file called previewpath + "info.txt" will also be created containing information + about the PPT file, to speed up future openings of the unmodified file. + Note it is up the calling program to directly access these images if they + are required. + + RETURNS: An unique identifier to pass to other methods in this library. + If < 0, then the PPT failed to open. + If >=0, ClosePPT must be called when the PPT is no longer being used + or when the calling program is closed to release resources/hooks. - RETURNS: An unique identifier to pass to other methods in this library. - If < 0, then the PPT failed to open. - If >=0, ClosePPT must be called when the PPT is no longer being used - or when the calling program is closed to release resources/hooks. - void ClosePPT(int id); - Closes the presentation, releasing any resources and hooks. + Closes the presentation, releasing any resources and hooks. + + id: The value returned from OpenPPT. - id: The value returned from OpenPPT. - int GetCurrentSlide(int id); - Returns the current slide number (from 1) + Returns the current slide number (from 1) - id: The value returned from OpenPPT. + id: The value returned from OpenPPT. int GetSlideCount(int id); - Returns the total number of slides. + Returns the total number of slides. - id: The value returned from OpenPPT. + id: The value returned from OpenPPT. void NextStep(int id); - Advances one step (animation) through the slideshow. + Advances one step (animation) through the slideshow. - id: The value returned from OpenPPT. + id: The value returned from OpenPPT. void PrevStep(int id); - Goes backwards one step (animation) through the slideshow. + Goes backwards one step (animation) through the slideshow. - id: The value returned from OpenPPT. + id: The value returned from OpenPPT. void GotoSlide(int id, int slideno); - Goes directly to a specific slide in the slideshow + Goes directly to a specific slide in the slideshow - id: The value returned from OpenPPT. - slideno: The number of the slide (from 1) to go directly to. - - If the slide has already been displayed, then the completed slide with animations performed - will be shown. This is how the PowerPoint Viewer works so have no control over this. + id: The value returned from OpenPPT. + slideno: The number of the slide (from 1) to go directly to. + + If the slide has already been displayed, then the completed slide with animations performed + will be shown. This is how the PowerPoint Viewer works so have no control over this. void RestartShow(int id); - Restarts the show from the beginning. To reset animations, behind the scenes it - has to travel to the end and step backwards though the entire show. Therefore - for large presentations there might be a delay. + Restarts the show from the beginning. To reset animations, behind the scenes it + has to travel to the end and step backwards though the entire show. Therefore + for large presentations there might be a delay. - id: The value returned from OpenPPT. + id: The value returned from OpenPPT. void Blank(int id); - Blanks the screen, colour black. + Blanks the screen, colour black. - id: The value returned from OpenPPT. + id: The value returned from OpenPPT. void Unblank(int id) - Unblanks the screen, restoring it to it's pre-blank state. - - id: The value returned from OpenPPT. + Unblanks the screen, restoring it to it's pre-blank state. + + id: The value returned from OpenPPT. void Stop(int id) - Moves the slideshow off the screen. (There is no concept of stop show in the PowerPoint Viewer) + Moves the slideshow off the screen. (There is no concept of stop show in the PowerPoint Viewer) - id: The value returned from OpenPPT. + id: The value returned from OpenPPT. void Resume(int id) - Moves the slideshow display back onto the screen following a Stop() + Moves the slideshow display back onto the screen following a Stop() - id: The value returned from OpenPPT. + id: The value returned from OpenPPT. diff --git a/openlp/plugins/presentations/lib/pptviewlib/ppttest.py b/openlp/plugins/presentations/lib/pptviewlib/ppttest.py index 1e10def7d..50cf515dc 100644 --- a/openlp/plugins/presentations/lib/pptviewlib/ppttest.py +++ b/openlp/plugins/presentations/lib/pptviewlib/ppttest.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, Armin Köhler, 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,32 @@ from ctypes import * from ctypes.wintypes import RECT class PPTViewer(QtGui.QWidget): + """ + Standalone Test Harness for the pptviewlib library + """ def __init__(self, parent=None): QtGui.QWidget.__init__(self, parent) self.pptid = -1 self.setWindowTitle(u'PowerPoint Viewer Test') - PPTLabel = QtGui.QLabel(u'Open PowerPoint file') - slideLabel = QtGui.QLabel(u'Go to slide #') - self.PPTEdit = QtGui.QLineEdit() + ppt_label = QtGui.QLabel(u'Open PowerPoint file') + slide_label = QtGui.QLabel(u'Go to slide #') + self.pptEdit = QtGui.QLineEdit() self.slideEdit = QtGui.QLineEdit() + x_label = QtGui.QLabel(u'X pos') + y_label = QtGui.QLabel(u'Y pos') + width_label = QtGui.QLabel(u'Width') + height_label = QtGui.QLabel(u'Height') + self.xEdit = QtGui.QLineEdit(u'100') + self.yEdit = QtGui.QLineEdit(u'100') + self.widthEdit = QtGui.QLineEdit(u'900') + self.heightEdit = QtGui.QLineEdit(u'700') self.total = QtGui.QLabel() - PPTBtn = QtGui.QPushButton(u'Open') - PPTDlgBtn = QtGui.QPushButton(u'...') - slideBtn = QtGui.QPushButton(u'Go') + ppt_btn = QtGui.QPushButton(u'Open') + ppt_dlg_btn = QtGui.QPushButton(u'...') + folder_label = QtGui.QLabel(u'Slide .bmp path') + self.folderEdit = QtGui.QLineEdit(u'slide') + slide_btn = QtGui.QPushButton(u'Go') prev = QtGui.QPushButton(u'Prev') next = QtGui.QPushButton(u'Next') blank = QtGui.QPushButton(u'Blank') @@ -51,122 +65,149 @@ class PPTViewer(QtGui.QWidget): close = QtGui.QPushButton(u'Close') resume = QtGui.QPushButton(u'Resume') stop = QtGui.QPushButton(u'Stop') - pptwindow = QtGui.QWidget() - grid = QtGui.QGridLayout() - grid.addWidget(PPTLabel, 0, 0) - grid.addWidget(self.PPTEdit, 0, 1) - grid.addWidget(PPTDlgBtn, 0, 2) - grid.addWidget(PPTBtn, 0, 3) - grid.addWidget(slideLabel, 1, 0) - grid.addWidget(self.slideEdit, 1, 1) - grid.addWidget(slideBtn, 1, 3) - grid.addWidget(prev, 2, 0) - grid.addWidget(next, 2, 1) - grid.addWidget(blank, 3, 0) - grid.addWidget(unblank, 3, 1) - grid.addWidget(restart, 4, 0) - grid.addWidget(close, 4, 1) - grid.addWidget(stop, 5, 0) - grid.addWidget(resume, 5, 1) - grid.addWidget(pptwindow, 6, 0, 10, 3) - self.connect(PPTBtn, QtCore.SIGNAL(u'clicked()'), self.OpenClick) - self.connect(PPTDlgBtn, QtCore.SIGNAL(u'clicked()'), self.OpenDialog) - self.connect(slideBtn, QtCore.SIGNAL(u'clicked()'), self.GotoClick) - self.connect(prev, QtCore.SIGNAL(u'clicked()'), self.PrevClick) - self.connect(next, QtCore.SIGNAL(u'clicked()'), self.NextClick) - self.connect(blank, QtCore.SIGNAL(u'clicked()'), self.BlankClick) - self.connect(unblank, QtCore.SIGNAL(u'clicked()'), self.UnblankClick) - self.connect(restart, QtCore.SIGNAL(u'clicked()'), self.RestartClick) - self.connect(close, QtCore.SIGNAL(u'clicked()'), self.CloseClick) - self.connect(stop, QtCore.SIGNAL(u'clicked()'), self.StopClick) - self.connect(resume, QtCore.SIGNAL(u'clicked()'), self.ResumeClick) - + row = 0 + grid.addWidget(folder_label, 0, 0) + grid.addWidget(self.folderEdit, 0, 1) + row = row + 1 + grid.addWidget(x_label, row, 0) + grid.addWidget(self.xEdit, row, 1) + grid.addWidget(y_label, row, 2) + grid.addWidget(self.yEdit, row, 3) + row = row + 1 + grid.addWidget(width_label, row, 0) + grid.addWidget(self.widthEdit, row, 1) + grid.addWidget(height_label, row, 2) + grid.addWidget(self.heightEdit, row, 3) + row = row + 1 + grid.addWidget(ppt_label, row, 0) + grid.addWidget(self.pptEdit, row, 1) + grid.addWidget(ppt_dlg_btn, row, 2) + grid.addWidget(ppt_btn, row, 3) + row = row + 1 + grid.addWidget(slide_label, row, 0) + grid.addWidget(self.slideEdit, row, 1) + grid.addWidget(slide_btn, row, 2) + row = row + 1 + grid.addWidget(prev, row, 0) + grid.addWidget(next, row, 1) + row = row + 1 + grid.addWidget(blank, row, 0) + grid.addWidget(unblank, row, 1) + row = row + 1 + grid.addWidget(restart, row, 0) + grid.addWidget(close, row, 1) + row = row + 1 + grid.addWidget(stop, row, 0) + grid.addWidget(resume, row, 1) + self.connect(ppt_btn, QtCore.SIGNAL(u'clicked()'), self.openClick) + self.connect(ppt_dlg_btn, QtCore.SIGNAL(u'clicked()'), self.openDialog) + self.connect(slide_btn, QtCore.SIGNAL(u'clicked()'), self.gotoClick) + self.connect(prev, QtCore.SIGNAL(u'clicked()'), self.prevClick) + self.connect(next, QtCore.SIGNAL(u'clicked()'), self.nextClick) + self.connect(blank, QtCore.SIGNAL(u'clicked()'), self.blankClick) + self.connect(unblank, QtCore.SIGNAL(u'clicked()'), self.unblankClick) + self.connect(restart, QtCore.SIGNAL(u'clicked()'), self.restartClick) + self.connect(close, QtCore.SIGNAL(u'clicked()'), self.closeClick) + self.connect(stop, QtCore.SIGNAL(u'clicked()'), self.stopClick) + self.connect(resume, QtCore.SIGNAL(u'clicked()'), self.resumeClick) self.setLayout(grid) - self.resize(300, 150) - def PrevClick(self): - if self.pptid<0: return - pptdll.PrevStep(self.pptid) - self.UpdateCurrSlide() + def prevClick(self): + if self.pptid < 0: + return + self.pptdll.PrevStep(self.pptid) + self.updateCurrSlide() app.processEvents() - def NextClick(self): - if(self.pptid<0): return - pptdll.NextStep(self.pptid) - self.UpdateCurrSlide() + def nextClick(self): + if self.pptid < 0: + return + self.pptdll.NextStep(self.pptid) + self.updateCurrSlide() app.processEvents() - def BlankClick(self): - if(self.pptid<0): return - pptdll.Blank(self.pptid) + def blankClick(self): + if self.pptid < 0: + return + self.pptdll.Blank(self.pptid) app.processEvents() - def UnblankClick(self): - if(self.pptid<0): return - pptdll.Unblank(self.pptid) + def unblankClick(self): + if self.pptid < 0: + return + self.pptdll.Unblank(self.pptid) app.processEvents() - def RestartClick(self): - if(self.pptid<0): return - pptdll.RestartShow(self.pptid) - self.UpdateCurrSlide() + def restartClick(self): + if self.pptid < 0: + return + self.pptdll.RestartShow(self.pptid) + self.updateCurrSlide() app.processEvents() - def StopClick(self): - if(self.pptid<0): return - pptdll.Stop(self.pptid) + def stopClick(self): + if self.pptid < 0: + return + self.pptdll.Stop(self.pptid) app.processEvents() - def ResumeClick(self): - if(self.pptid<0): return - pptdll.Resume(self.pptid) + def resumeClick(self): + if self.pptid < 0: + return + self.pptdll.Resume(self.pptid) app.processEvents() - def CloseClick(self): - if(self.pptid<0): return - pptdll.ClosePPT(self.pptid) + def closeClick(self): + if self.pptid < 0: + return + self.pptdll.ClosePPT(self.pptid) self.pptid = -1 app.processEvents() - def OpenClick(self): + def openClick(self): oldid = self.pptid; - rect = RECT(100,100,900,700) - filename = unicode(self.PPTEdit.text()) - print filename - self.pptid = pptdll.OpenPPT(filename, None, rect, 'c:\\temp\\slide') - print "id: " + unicode(self.pptid) - if oldid>=0: - pptdll.ClosePPT(oldid); - slides = pptdll.GetSlideCount(self.pptid) - print "slidecount: " + unicode(slides) - self.total.setNum(pptdll.GetSlideCount(self.pptid)) - self.UpdateCurrSlide() + rect = RECT(int(self.xEdit.text()), int(self.yEdit.text()), + int(self.widthEdit.text()), int(self.heightEdit.text())) + filename = str(self.pptEdit.text().replace(u'/', u'\\')) + folder = str(self.folderEdit.text().replace(u'/', u'\\')) + print filename, folder + self.pptid = self.pptdll.OpenPPT(filename, None, rect, folder) + print u'id: ' + unicode(self.pptid) + if oldid >= 0: + self.pptdll.ClosePPT(oldid); + slides = self.pptdll.GetSlideCount(self.pptid) + print u'slidecount: ' + unicode(slides) + self.total.setNum(self.pptdll.GetSlideCount(self.pptid)) + self.updateCurrSlide() - def UpdateCurrSlide(self): - if(self.pptid<0): return - slide = unicode(pptdll.GetCurrentSlide(self.pptid)) - print "currslide: " + slide + def updateCurrSlide(self): + if self.pptid < 0: + return + slide = unicode(self.pptdll.GetCurrentSlide(self.pptid)) + print u'currslide: ' + slide self.slideEdit.setText(slide) app.processEvents() - def GotoClick(self): - if(self.pptid<0): return + def gotoClick(self): + if self.pptid < 0: + return print self.slideEdit.text() - pptdll.GotoSlide(self.pptid, int(self.slideEdit.text())) - self.UpdateCurrSlide() + self.pptdll.GotoSlide(self.pptid, int(self.slideEdit.text())) + self.updateCurrSlide() app.processEvents() - def OpenDialog(self): - self.PPTEdit.setText(QtGui.QFileDialog.getOpenFileName(self, 'Open file')) + def openDialog(self): + self.pptEdit.setText(QtGui.QFileDialog.getOpenFileName(self, + u'Open file')) if __name__ == '__main__': - #pptdll = cdll.LoadLibrary(r'C:\Documents and Settings\jonathan\Desktop\pptviewlib.dll') pptdll = cdll.LoadLibrary(r'pptviewlib.dll') pptdll.SetDebug(1) - print "Begin..." + print u'Begin...' app = QtGui.QApplication(sys.argv) - qb = PPTViewer() - qb.show() + window = PPTViewer() + window.pptdll = pptdll + window.show() sys.exit(app.exec_()) diff --git a/openlp/plugins/presentations/lib/pptviewlib/pptviewlib.cpp b/openlp/plugins/presentations/lib/pptviewlib/pptviewlib.cpp index 86876a836..a679df582 100644 --- a/openlp/plugins/presentations/lib/pptviewlib/pptviewlib.cpp +++ b/openlp/plugins/presentations/lib/pptviewlib/pptviewlib.cpp @@ -1,231 +1,262 @@ -/* - * PPTVIEWLIB - Control PowerPoint Viewer 2003/2007 (for openlp.org) - * Copyright (C) 2008 Jonathan Corwin - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ +/****************************************************************************** +* PptViewLib - PowerPoint Viewer 2003/2007 Controller * +* OpenLP - Open Source Lyrics Projection * +* --------------------------------------------------------------------------- * +* Copyright (c) 2008-2011 Raoul Snyman * +* Portions copyright (c) 2008-2011 Tim Bentley, Jonathan Corwin, Michael * +* Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, Armin Köhler, * +* Andreas Preikschat, Mattias Põldaru, Christian Richter, Philip Ridout, * +* 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 * +******************************************************************************/ - -#define WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN #include #include #include #include +#include +#include #include #include #include #include "pptviewlib.h" - -// Because of the callbacks used by SetWindowsHookEx, the memory used needs to be -// sharable across processes (the callbacks are done from a different process) -// Therefore use data_seg with RWS memory. +// Because of the callbacks used by SetWindowsHookEx, the memory used needs to +// be sharable across processes (the callbacks are done from a different +// process) Therefore use data_seg with RWS memory. // -// See http://msdn.microsoft.com/en-us/library/aa366551(VS.85).aspx for alternative -// method of holding memory, removing fixed limits which would allow dynamic number -// of items, rather than a fixed number. Use a Local\ mapping, since global has UAC -// issues in Vista. +// See http://msdn.microsoft.com/en-us/library/aa366551(VS.85).aspx for +// alternative method of holding memory, removing fixed limits which would allow +// dynamic number of items, rather than a fixed number. Use a Local\ mapping, +// since global has UAC issues in Vista. + #pragma data_seg(".PPTVIEWLIB") -PPTVIEWOBJ pptviewobj[MAX_PPTOBJS] = {NULL}; -HHOOK globalhook = NULL; +PPTVIEW pptView[MAX_PPTS] = {NULL}; +HHOOK globalHook = NULL; BOOL debug = FALSE; #pragma data_seg() #pragma comment(linker, "/SECTION:.PPTVIEWLIB,RWS") -#define DEBUG(...) if(debug) printf(__VA_ARGS__) - - HINSTANCE hInstance = NULL; -BOOL APIENTRY DllMain( HMODULE hModule, - DWORD ul_reason_for_call, - LPVOID lpReserved - ) +BOOL APIENTRY DllMain(HMODULE hModule, DWORD ulReasonForCall, + LPVOID lpReserved) { hInstance = (HINSTANCE)hModule; - switch (ul_reason_for_call) - { - case DLL_PROCESS_ATTACH: - DEBUG("PROCESS_ATTACH\n"); - break; - case DLL_THREAD_ATTACH: - DEBUG("THREAD_ATTACH\n"); - break; - case DLL_THREAD_DETACH: - DEBUG("THREAD_DETACH\n"); - break; - case DLL_PROCESS_DETACH: - // Clean up... hopefully there is only the one process attached? - // We'll find out soon enough during tests! - DEBUG("PROCESS_DETACH\n"); - for(int i = 0; i.bmp" will be appended to complete the path. E.g. "c:\temp\slide" would +// ".bmp" will be appended to complete the path. E.g. "c:\temp\slide" would // create "c:\temp\slide1.bmp" slide2.bmp, slide3.bmp etc. // It will also create a *info.txt containing information about the ppt -DllExport int OpenPPT(char *filename, HWND hParentWnd, RECT rect, char *previewpath) +DllExport int OpenPPT(char *filename, HWND hParentWnd, RECT rect, + char *previewPath) { - STARTUPINFO si; - PROCESS_INFORMATION pi; - char cmdline[MAX_PATH * 2]; - int id; + STARTUPINFO si; + PROCESS_INFORMATION pi; + char cmdLine[MAX_PATH * 2]; + int id; - DEBUG("OpenPPT start: %s; %s\n", filename, previewpath); - DEBUG("OpenPPT start: %u; %i, %i, %i, %i\n", hParentWnd, rect.top, rect.left, rect.bottom, rect.right); - if(GetPPTViewerPath(cmdline, sizeof(cmdline))==FALSE) - { - DEBUG("OpenPPT: GetPPTViewerPath failed\n"); - return -1; - } - id = -1; - for(int i = 0; ibottom-wndrect->top; - pptviewobj[id].rect.right = wndrect->right-wndrect->left; - } - else - { - pptviewobj[id].rect.top = rect.top; - pptviewobj[id].rect.left = rect.left; - pptviewobj[id].rect.bottom = rect.bottom; - pptviewobj[id].rect.right = rect.right; - } - strcat_s(cmdline, MAX_PATH * 2, "/F /S \""); - strcat_s(cmdline, MAX_PATH * 2, filename); - strcat_s(cmdline, MAX_PATH * 2, "\""); - memset(&si, 0, sizeof(si)); - memset(&pi, 0, sizeof(pi)); - BOOL gotinfo = GetPPTInfo(id); - /* - * I'd really like to just hook on the new threadid. However this always gives - * error 87. Perhaps I'm hooking to soon? No idea... however can't wait - * since I need to ensure I pick up the WM_CREATE as this is the only - * time the window can be resized in such away the content scales correctly - * - * hook = SetWindowsHookEx(WH_CBT,CbtProc,hInstance,pi.dwThreadId); - */ - if(globalhook!=NULL) - UnhookWindowsHookEx(globalhook); - globalhook = SetWindowsHookEx(WH_CBT,CbtProc,hInstance,NULL); - if(globalhook==0) - { - DEBUG("OpenPPT: SetWindowsHookEx failed\n"); - ClosePPT(id); - return -1; - } - pptviewobj[id].state = PPT_STARTED; - Sleep(10); - if(!CreateProcess(NULL, cmdline, NULL, NULL, FALSE, 0, 0, NULL, &si, &pi)) - { - DEBUG("OpenPPT: CreateProcess failed\n"); - ClosePPT(id); - return -1; - } - pptviewobj[id].dwProcessId = pi.dwProcessId; - pptviewobj[id].dwThreadId = pi.dwThreadId; - pptviewobj[id].hThread = pi.hThread; - pptviewobj[id].hProcess = pi.hProcess; - while(pptviewobj[id].state==PPT_STARTED) - Sleep(10); - if(gotinfo) - { - DEBUG("OpenPPT: Info loaded, no refresh\n"); - pptviewobj[id].state = PPT_LOADED; - Resume(id); - } - else - { - DEBUG("OpenPPT: Get info\n"); - pptviewobj[id].steps = 0; - int steps = 0; - while(pptviewobj[id].state==PPT_OPENED) - { - if(steps<=pptviewobj[id].steps) - { - Sleep(20); - DEBUG("OpenPPT: Step %d/%d\n",steps,pptviewobj[id].steps); - steps++; - NextStep(id); - } - Sleep(10); - } - DEBUG("OpenPPT: Steps %d, first slide steps %d\n",pptviewobj[id].steps,pptviewobj[id].firstSlideSteps); - SavePPTInfo(id); - if(pptviewobj[id].state==PPT_CLOSING||pptviewobj[id].slideCount<=0){ - ClosePPT(id); - id=-1; - } - else - RestartShow(id); - } - if(id>=0) - { - if(pptviewobj[id].mhook!=NULL) - UnhookWindowsHookEx(pptviewobj[id].mhook); - pptviewobj[id].mhook = NULL; - } - DEBUG("OpenPPT: Exit: id=%i\n", id); - return id; + DEBUG("OpenPPT start: %s; %s\n", filename, previewPath); + DEBUG("OpenPPT start: %u; %i, %i, %i, %i\n", hParentWnd, rect.top, + rect.left, rect.bottom, rect.right); + if (GetPPTViewerPath(cmdLine, sizeof(cmdLine)) == FALSE) + { + DEBUG("OpenPPT: GetPPTViewerPath failed\n"); + return -1; + } + id = -1; + for (int i = 0; i < MAX_PPTS; i++) + { + if (pptView[i].state == PPT_CLOSED) + { + id = i; + break; + } + } + if (id < 0) + { + DEBUG("OpenPPT: Too many PPTs\n"); + return -1; + } + memset(&pptView[id], 0, sizeof(PPTVIEW)); + strcpy_s(pptView[id].filename, MAX_PATH, filename); + strcpy_s(pptView[id].previewPath, MAX_PATH, previewPath); + pptView[id].state = PPT_CLOSED; + pptView[id].slideCount = 0; + pptView[id].currentSlide = 0; + pptView[id].firstSlideSteps = 0; + pptView[id].lastSlideSteps = 0; + pptView[id].guess = 0; + pptView[id].hParentWnd = hParentWnd; + pptView[id].hWnd = NULL; + pptView[id].hWnd2 = NULL; + for (int i = 0; i < MAX_SLIDES; i++) + { + pptView[id].slideNos[i] = 0; + } + if (hParentWnd != NULL && rect.top == 0 && rect.bottom == 0 + && rect.left == 0 && rect.right == 0) + { + LPRECT windowRect = NULL; + GetWindowRect(hParentWnd, windowRect); + pptView[id].rect.top = 0; + pptView[id].rect.left = 0; + pptView[id].rect.bottom = windowRect->bottom - windowRect->top; + pptView[id].rect.right = windowRect->right - windowRect->left; + } + else + { + pptView[id].rect.top = rect.top; + pptView[id].rect.left = rect.left; + pptView[id].rect.bottom = rect.bottom; + pptView[id].rect.right = rect.right; + } + strcat_s(cmdLine, MAX_PATH * 2, " /F /S \""); + strcat_s(cmdLine, MAX_PATH * 2, filename); + strcat_s(cmdLine, MAX_PATH * 2, "\""); + memset(&si, 0, sizeof(si)); + memset(&pi, 0, sizeof(pi)); + BOOL gotInfo = GetPPTInfo(id); + /* + * I'd really like to just hook on the new threadid. However this always + * gives error 87. Perhaps I'm hooking to soon? No idea... however can't + * wait since I need to ensure I pick up the WM_CREATE as this is the only + * time the window can be resized in such away the content scales correctly + * + * hook = SetWindowsHookEx(WH_CBT,CbtProc,hInstance,pi.dwThreadId); + */ + if (globalHook != NULL) + { + UnhookWindowsHookEx(globalHook); + } + globalHook = SetWindowsHookEx(WH_CBT, CbtProc, hInstance, NULL); + if (globalHook == 0) + { + DEBUG("OpenPPT: SetWindowsHookEx failed\n"); + ClosePPT(id); + return -1; + } + pptView[id].state = PPT_STARTED; + Sleep(10); + if (!CreateProcess(NULL, cmdLine, NULL, NULL, FALSE, 0, 0, NULL, &si, &pi)) + { + DEBUG("OpenPPT: CreateProcess failed: %s\n", cmdLine); + ClosePPT(id); + return -1; + } + pptView[id].dwProcessId = pi.dwProcessId; + pptView[id].dwThreadId = pi.dwThreadId; + pptView[id].hThread = pi.hThread; + pptView[id].hProcess = pi.hProcess; + while (pptView[id].state == PPT_STARTED) + Sleep(10); + if (gotInfo) + { + DEBUG("OpenPPT: Info loaded, no refresh\n"); + pptView[id].state = PPT_LOADED; + Resume(id); + } + else + { + DEBUG("OpenPPT: Get info\n"); + pptView[id].steps = 0; + int steps = 0; + while (pptView[id].state == PPT_OPENED) + { + if (steps <= pptView[id].steps) + { + Sleep(20); + DEBUG("OpenPPT: Step %d/%d\n", steps, pptView[id].steps); + steps++; + NextStep(id); + } + Sleep(10); + } + DEBUG("OpenPPT: Slides %d, Steps %d, first slide steps %d\n", + pptView[id].slideCount, pptView[id].steps, + pptView[id].firstSlideSteps); + for(int i = 1; i <= pptView[id].slideCount; i++) + { + DEBUG("OpenPPT: Slide %d = %d\n", i, pptView[id].slideNos[i]); + } + SavePPTInfo(id); + if (pptView[id].state == PPT_CLOSING + || pptView[id].slideCount <= 0) + { + ClosePPT(id); + id=-1; + } + else + { + RestartShow(id); + } + } + if (id >= 0) + { + if (pptView[id].msgHook != NULL) + { + UnhookWindowsHookEx(pptView[id].msgHook); + } + pptView[id].msgHook = NULL; + } + DEBUG("OpenPPT: Exit: id=%i\n", id); + return id; } // Load information about the ppt from an info.txt file. // Format: @@ -236,292 +267,370 @@ DllExport int OpenPPT(char *filename, HWND hParentWnd, RECT rect, char *previewp // first slide steps BOOL GetPPTInfo(int id) { - struct _stat filestats; - char info[MAX_PATH]; - FILE* pFile; - char buf[100]; + struct _stat fileStats; + char info[MAX_PATH]; + FILE* pFile; + char buf[100]; - DEBUG("GetPPTInfo: start\n"); - if(_stat(pptviewobj[id].filename, &filestats)!=0) - return FALSE; - sprintf_s(info, MAX_PATH, "%sinfo.txt", pptviewobj[id].previewpath); - int err = fopen_s(&pFile, info, "r"); - if(err!=0) - { - DEBUG("GetPPTInfo: file open failed - %d\n", err); - return FALSE; - } - fgets(buf, 100, pFile); // version == 1 - fgets(buf, 100, pFile); - if(filestats.st_mtime!=atoi(buf)) - { - fclose (pFile); - return FALSE; - } - fgets(buf, 100, pFile); - if(filestats.st_size!=atoi(buf)) - { - fclose (pFile); - return FALSE; - } - fgets(buf, 100, pFile); // slidecount - int slidecount = atoi(buf); - fgets(buf, 100, pFile); // first slide steps - int firstslidesteps = atoi(buf); - // check all the preview images still exist - for(int i = 1; i<=slidecount; i++) - { - sprintf_s(info, MAX_PATH, "%s%i.bmp", pptviewobj[id].previewpath, i); - if(GetFileAttributes(info)==INVALID_FILE_ATTRIBUTES) - return FALSE; - } - fclose(pFile); - pptviewobj[id].slideCount = slidecount; - pptviewobj[id].firstSlideSteps = firstslidesteps; - DEBUG("GetPPTInfo: exit ok\n"); - return TRUE; + DEBUG("GetPPTInfo: start\n"); + if (_stat(pptView[id].filename, &fileStats) != 0) + { + return FALSE; + } + sprintf_s(info, MAX_PATH, "%sinfo.txt", pptView[id].previewPath); + int err = fopen_s(&pFile, info, "r"); + if (err != 0) + { + DEBUG("GetPPTInfo: file open failed - %d\n", err); + return FALSE; + } + fgets(buf, 100, pFile); // version == 1 + fgets(buf, 100, pFile); + if (fileStats.st_mtime != atoi(buf)) + { + DEBUG("GetPPTInfo: date changed\n"); + fclose (pFile); + return FALSE; + } + fgets(buf, 100, pFile); + if (fileStats.st_size != atoi(buf)) + { + DEBUG("GetPPTInfo: size changed\n"); + fclose (pFile); + return FALSE; + } + fgets(buf, 100, pFile); // slidecount + int slideCount = atoi(buf); + fgets(buf, 100, pFile); // first slide steps + int firstSlideSteps = atoi(buf); + // check all the preview images still exist + for (int i = 1; i <= slideCount; i++) + { + sprintf_s(info, MAX_PATH, "%s%i.bmp", pptView[id].previewPath, i); + if (GetFileAttributes(info) == INVALID_FILE_ATTRIBUTES) + { + DEBUG("GetPPTInfo: bmp not found\n"); + return FALSE; + } + } + fclose(pFile); + pptView[id].slideCount = slideCount; + pptView[id].firstSlideSteps = firstSlideSteps; + DEBUG("GetPPTInfo: exit ok\n"); + return TRUE; } BOOL SavePPTInfo(int id) { - struct _stat filestats; - char info[MAX_PATH]; - FILE* pFile; + struct _stat fileStats; + char info[MAX_PATH]; + FILE* pFile; - DEBUG("SavePPTInfo: start\n"); - if(_stat(pptviewobj[id].filename, &filestats)!=0) - { - DEBUG("SavePPTInfo: stat of %s failed\n", pptviewobj[id].filename); - return FALSE; - } - sprintf_s(info, MAX_PATH, "%sinfo.txt", pptviewobj[id].previewpath); - int err = fopen_s(&pFile, info, "w"); - if(err!=0) - { - DEBUG("SavePPTInfo: fopen of %s failed%i\n", info, err); - return FALSE; - } - fprintf(pFile, "1\n"); - fprintf(pFile, "%u\n", filestats.st_mtime); - fprintf(pFile, "%u\n", filestats.st_size); - fprintf(pFile, "%u\n", pptviewobj[id].slideCount); - fprintf(pFile, "%u\n", pptviewobj[id].firstSlideSteps); - fclose (pFile); - DEBUG("SavePPTInfo: exit ok\n"); - return TRUE; + DEBUG("SavePPTInfo: start\n"); + if (_stat(pptView[id].filename, &fileStats) != 0) + { + DEBUG("SavePPTInfo: stat of %s failed\n", pptView[id].filename); + return FALSE; + } + sprintf_s(info, MAX_PATH, "%sinfo.txt", pptView[id].previewPath); + int err = fopen_s(&pFile, info, "w"); + if (err != 0) + { + DEBUG("SavePPTInfo: fopen of %s failed%i\n", info, err); + return FALSE; + } + fprintf(pFile, "1\n"); + fprintf(pFile, "%u\n", fileStats.st_mtime); + fprintf(pFile, "%u\n", fileStats.st_size); + fprintf(pFile, "%u\n", pptView[id].slideCount); + fprintf(pFile, "%u\n", pptView[id].firstSlideSteps); + fclose(pFile); + DEBUG("SavePPTInfo: exit ok\n"); + return TRUE; } // Get the path of the PowerPoint viewer from the registry -BOOL GetPPTViewerPath(char *pptviewerpath, int strsize) +BOOL GetPPTViewerPath(char *pptViewerPath, int stringSize) { - HKEY hkey; - DWORD dwtype, dwsize; - LRESULT lresult; + char cwd[MAX_PATH]; - DEBUG("GetPPTViewerPath: start\n"); - if(RegOpenKeyEx(HKEY_CLASSES_ROOT, "PowerPointViewer.Show.12\\shell\\Show\\command", 0, KEY_READ, &hkey)!=ERROR_SUCCESS) - if(RegOpenKeyEx(HKEY_CLASSES_ROOT, "Applications\\PPTVIEW.EXE\\shell\\open\\command", 0, KEY_READ, &hkey)!=ERROR_SUCCESS) - if(RegOpenKeyEx(HKEY_CLASSES_ROOT, "Applications\\PPTVIEW.EXE\\shell\\Show\\command", 0, KEY_READ, &hkey)!=ERROR_SUCCESS) - return FALSE; - dwtype = REG_SZ; - dwsize = (DWORD)strsize; - lresult = RegQueryValueEx(hkey, NULL, NULL, &dwtype, (LPBYTE)pptviewerpath, &dwsize ); - RegCloseKey(hkey); - if(lresult!=ERROR_SUCCESS) - return FALSE; - pptviewerpath[strlen(pptviewerpath)-4] = '\0'; // remove "%1" from end of key value - DEBUG("GetPPTViewerPath: exit ok\n"); - return TRUE; + DEBUG("GetPPTViewerPath: start\n"); + if(GetPPTViewerPathFromReg(pptViewerPath, stringSize)) + { + if(_access(pptViewerPath, 0) != -1) + { + DEBUG("GetPPTViewerPath: exit registry\n"); + return TRUE; + } + } + // This is where it gets ugly. PPT2007 it seems no longer stores its + // location in the registry. So we have to use the defaults which will + // upset those who like to put things somewhere else + + // Viewer 2007 in 64bit Windows: + if(_access("C:\\Program Files (x86)\\Microsoft Office\\Office12\\PPTVIEW.EXE", + 0) != -1) + { + strcpy_s( + "C:\\Program Files (x86)\\Microsoft Office\\Office12\\PPTVIEW.EXE", + stringSize, pptViewerPath); + DEBUG("GetPPTViewerPath: exit 64bit 2007\n"); + return TRUE; + } + // Viewer 2007 in 32bit Windows: + if(_access("C:\\Program Files\\Microsoft Office\\Office12\\PPTVIEW.EXE", 0) + != -1) + { + strcpy_s("C:\\Program Files\\Microsoft Office\\Office12\\PPTVIEW.EXE", + stringSize, pptViewerPath); + DEBUG("GetPPTViewerPath: exit 32bit 2007\n"); + return TRUE; + } + // Give them the opportunity to place it in the same folder as the app + _getcwd(cwd, MAX_PATH); + strcat_s(cwd, MAX_PATH, "\\PPTVIEW.EXE"); + if(_access(cwd, 0) != -1) + { + strcpy_s(pptViewerPath, stringSize, cwd); + DEBUG("GetPPTViewerPath: exit local\n"); + return TRUE; + } + DEBUG("GetPPTViewerPath: exit fail\n"); + return FALSE; +} +BOOL GetPPTViewerPathFromReg(char *pptViewerPath, int stringSize) +{ + HKEY hKey; + DWORD dwType, dwSize; + LRESULT lResult; + + // The following registry settings are for, respectively, (I think) + // PPT Viewer 2007 (older versions. Latest not in registry) & PPT Viewer 2010 + // PPT Viewer 2003 (recent versions) + // PPT Viewer 2003 (older versions) + // PPT Viewer 97 + if ((RegOpenKeyEx(HKEY_CLASSES_ROOT, + "PowerPointViewer.Show.12\\shell\\Show\\command", 0, KEY_READ, &hKey) + != ERROR_SUCCESS) + && (RegOpenKeyEx(HKEY_CLASSES_ROOT, + "PowerPointViewer.Show.11\\shell\\Show\\command", 0, KEY_READ, &hKey) + != ERROR_SUCCESS) + && (RegOpenKeyEx(HKEY_CLASSES_ROOT, + "Applications\\PPTVIEW.EXE\\shell\\open\\command", 0, KEY_READ, &hKey) + != ERROR_SUCCESS) + && (RegOpenKeyEx(HKEY_CLASSES_ROOT, + "Applications\\PPTVIEW.EXE\\shell\\Show\\command", 0, KEY_READ, &hKey) + != ERROR_SUCCESS)) + { + return FALSE; + } + dwType = REG_SZ; + dwSize = (DWORD)stringSize; + lResult = RegQueryValueEx(hKey, NULL, NULL, &dwType, (LPBYTE)pptViewerPath, + &dwSize); + RegCloseKey(hKey); + if (lResult != ERROR_SUCCESS) + { + return FALSE; + } + // remove "%1" from end of key value + pptViewerPath[strlen(pptViewerPath) - 4] = '\0'; + return TRUE; } -// Unhook the Windows hook +// Unhook the Windows hook void Unhook(int id) { - DEBUG("Unhook: start %d\n", id); - if(pptviewobj[id].hook!=NULL) - UnhookWindowsHookEx(pptviewobj[id].hook); - if(pptviewobj[id].mhook!=NULL) - UnhookWindowsHookEx(pptviewobj[id].mhook); - pptviewobj[id].hook = NULL; - pptviewobj[id].mhook = NULL; - DEBUG("Unhook: exit ok\n"); + DEBUG("Unhook: start %d\n", id); + if (pptView[id].hook != NULL) + { + UnhookWindowsHookEx(pptView[id].hook); + } + if (pptView[id].msgHook != NULL) + { + UnhookWindowsHookEx(pptView[id].msgHook); + } + pptView[id].hook = NULL; + pptView[id].msgHook = NULL; + DEBUG("Unhook: exit ok\n"); } // Close the PowerPoint viewer, release resources DllExport void ClosePPT(int id) { - DEBUG("ClosePPT: start%d\n", id); - pptviewobj[id].state = PPT_CLOSED; - Unhook(id); - if(pptviewobj[id].hWnd==0) - TerminateThread(pptviewobj[id].hThread, 0); - else - PostMessage(pptviewobj[id].hWnd, WM_CLOSE, 0, 0); - CloseHandle(pptviewobj[id].hThread); - CloseHandle(pptviewobj[id].hProcess); - memset(&pptviewobj[id], 0, sizeof(PPTVIEWOBJ)); - DEBUG("ClosePPT: exit ok\n"); - return; + DEBUG("ClosePPT: start%d\n", id); + pptView[id].state = PPT_CLOSED; + Unhook(id); + if (pptView[id].hWnd == 0) + { + TerminateThread(pptView[id].hThread, 0); + } + else + { + PostMessage(pptView[id].hWnd, WM_CLOSE, 0, 0); + } + CloseHandle(pptView[id].hThread); + CloseHandle(pptView[id].hProcess); + memset(&pptView[id], 0, sizeof(PPTVIEW)); + DEBUG("ClosePPT: exit ok\n"); + return; } // Moves the show back onto the display DllExport void Resume(int id) { - DEBUG("Resume: %d\n", id); - MoveWindow(pptviewobj[id].hWnd, pptviewobj[id].rect.left, pptviewobj[id].rect.top, - pptviewobj[id].rect.right - pptviewobj[id].rect.left, - pptviewobj[id].rect.bottom - pptviewobj[id].rect.top, TRUE); - Unblank(id); + DEBUG("Resume: %d\n", id); + MoveWindow(pptView[id].hWnd, pptView[id].rect.left, + pptView[id].rect.top, + pptView[id].rect.right - pptView[id].rect.left, + pptView[id].rect.bottom - pptView[id].rect.top, TRUE); + Unblank(id); } // Moves the show off the screen so it can't be seen DllExport void Stop(int id) { - DEBUG("Stop:%d\n", id); - MoveWindow(pptviewobj[id].hWnd, -32000, -32000, - pptviewobj[id].rect.right - pptviewobj[id].rect.left, - pptviewobj[id].rect.bottom - pptviewobj[id].rect.top, TRUE); + DEBUG("Stop:%d\n", id); + MoveWindow(pptView[id].hWnd, -32000, -32000, + pptView[id].rect.right - pptView[id].rect.left, + pptView[id].rect.bottom - pptView[id].rect.top, TRUE); } // Return the total number of slides DllExport int GetSlideCount(int id) { - DEBUG("GetSlideCount:%d\n", id); - if(pptviewobj[id].state==0) - return -1; - else - return pptviewobj[id].slideCount; + DEBUG("GetSlideCount:%d\n", id); + if (pptView[id].state == 0) + { + return -1; + } + else + { + return pptView[id].slideCount; + } } // Return the number of the slide currently viewing DllExport int GetCurrentSlide(int id) { - DEBUG("GetCurrentSlide:%d\n", id); - if(pptviewobj[id].state==0) - return -1; - else - return pptviewobj[id].currentSlide; + DEBUG("GetCurrentSlide:%d\n", id); + if (pptView[id].state == 0) + { + return -1; + } + else + { + return pptView[id].currentSlide; + } } -// Take a step forwards through the show +// Take a step forwards through the show DllExport void NextStep(int id) { - DEBUG("NextStep:%d\n", id); - if(pptviewobj[id].currentSlide>pptviewobj[id].slideCount) - return; - PostMessage(pptviewobj[id].hWnd2, WM_MOUSEWHEEL, MAKEWPARAM(0, -WHEEL_DELTA), 0); + DEBUG("NextStep:%d (%d)\n", id, pptView[id].currentSlide); + if (pptView[id].currentSlide > pptView[id].slideCount) return; + if (pptView[id].currentSlide < pptView[id].slideCount) + { + pptView[id].guess = pptView[id].currentSlide + 1; + } + PostMessage(pptView[id].hWnd2, WM_MOUSEWHEEL, MAKEWPARAM(0, -WHEEL_DELTA), + 0); } -// Take a step backwards through the show +// Take a step backwards through the show DllExport void PrevStep(int id) { - DEBUG("PrevStep:%d\n", id); - PostMessage(pptviewobj[id].hWnd2, WM_MOUSEWHEEL, MAKEWPARAM(0, WHEEL_DELTA), 0); + DEBUG("PrevStep:%d (%d)\n", id, pptView[id].currentSlide); + if (pptView[id].currentSlide > 1) + { + pptView[id].guess = pptView[id].currentSlide - 1; + } + PostMessage(pptView[id].hWnd2, WM_MOUSEWHEEL, MAKEWPARAM(0, WHEEL_DELTA), + 0); } // Blank the show (black screen) DllExport void Blank(int id) -{ - // B just toggles blank on/off. However pressing any key unblanks. - // So send random unmapped letter first (say 'A'), then we can - // better guarantee B will blank instead of trying to guess - // whether it was already blank or not. - DEBUG("Blank:%d\n", id); - HWND h1 = GetForegroundWindow(); - HWND h2 = GetFocus(); - SetForegroundWindow(pptviewobj[id].hWnd); - SetFocus(pptviewobj[id].hWnd); - Sleep(50); // slight pause, otherwise event triggering this call may grab focus back! - keybd_event((int)'A', 0, 0, 0); - keybd_event((int)'A', 0, KEYEVENTF_KEYUP, 0); - keybd_event((int)'B', 0, 0, 0); - keybd_event((int)'B', 0, KEYEVENTF_KEYUP, 0); - SetForegroundWindow(h1); - SetFocus(h2); - //PostMessage(pptviewobj[id].hWnd2, WM_KEYDOWN, 'B', 0x00300001); - //PostMessage(pptviewobj[id].hWnd2, WM_CHAR, 'b', 0x00300001); - //PostMessage(pptviewobj[id].hWnd2, WM_KEYUP, 'B', 0xC0300001); +{ + // B just toggles blank on/off. However pressing any key unblanks. + // So send random unmapped letter first (say 'A'), then we can + // better guarantee B will blank instead of trying to guess + // whether it was already blank or not. + DEBUG("Blank:%d\n", id); + HWND h1 = GetForegroundWindow(); + HWND h2 = GetFocus(); + SetForegroundWindow(pptView[id].hWnd); + SetFocus(pptView[id].hWnd); + // slight pause, otherwise event triggering this call may grab focus back! + Sleep(50); + keybd_event((int)'A', 0, 0, 0); + keybd_event((int)'A', 0, KEYEVENTF_KEYUP, 0); + keybd_event((int)'B', 0, 0, 0); + keybd_event((int)'B', 0, KEYEVENTF_KEYUP, 0); + SetForegroundWindow(h1); + SetFocus(h2); } -// Unblank the show +// Unblank the show DllExport void Unblank(int id) -{ - DEBUG("Unblank:%d\n", id); - // Pressing any key resumes. - // For some reason SendMessage works for unblanking, but not blanking. -// SendMessage(pptviewobj[id].hWnd2, WM_KEYDOWN, 'A', 0); - SendMessage(pptviewobj[id].hWnd2, WM_CHAR, 'A', 0); -// SendMessage(pptviewobj[id].hWnd2, WM_KEYUP, 'A', 0); -// HWND h1 = GetForegroundWindow(); -// HWND h2 = GetFocus(); -// Sleep(50); // slight pause, otherwise event triggering this call may grab focus back! -// SetForegroundWindow(pptviewobj[id].hWnd); -// SetFocus(pptviewobj[id].hWnd); -// keybd_event((int)'A', 0, 0, 0); -// SetForegroundWindow(h1); -// SetFocus(h2); +{ + DEBUG("Unblank:%d\n", id); + // Pressing any key resumes. + // For some reason SendMessage works for unblanking, but not blanking. + SendMessage(pptView[id].hWnd2, WM_CHAR, 'A', 0); } // Go directly to a slide -DllExport void GotoSlide(int id, int slideno) -{ - DEBUG("GotoSlide %i %i:\n", id, slideno); - // Did try WM_KEYDOWN/WM_CHAR/WM_KEYUP with SendMessage but didn't work - // perhaps I was sending to the wrong window? No idea. - // Anyway fall back to keybd_event, which is OK as long we makesure - // the slideshow has focus first - char ch[10]; +DllExport void GotoSlide(int id, int slideNo) +{ + DEBUG("GotoSlide %i %i:\n", id, slideNo); + // Did try WM_KEYDOWN/WM_CHAR/WM_KEYUP with SendMessage but didn't work + // perhaps I was sending to the wrong window? No idea. + // Anyway fall back to keybd_event, which is OK as long we makesure + // the slideshow has focus first + char ch[10]; - if(slideno<0) return; - _itoa_s(slideno, ch, 10, 10); - HWND h1 = GetForegroundWindow(); - HWND h2 = GetFocus(); - SetForegroundWindow(pptviewobj[id].hWnd); - SetFocus(pptviewobj[id].hWnd); - Sleep(50); // slight pause, otherwise event triggering this call may grab focus back! - for(int i=0;i<10;i++) - { - if(ch[i]=='\0') break; - keybd_event((BYTE)ch[i], 0, 0, 0); - keybd_event((BYTE)ch[i], 0, KEYEVENTF_KEYUP, 0); - } - keybd_event(VK_RETURN, 0, 0, 0); - keybd_event(VK_RETURN, 0, KEYEVENTF_KEYUP, 0); - SetForegroundWindow(h1); - SetFocus(h2); - - //for(int i=0;i<10;i++) - //{ - // if(ch[i]=='\0') break; - // SendMessage(pptviewobj[id].hWnd2, WM_KEYDOWN, ch[i], 0); - // SendMessage(pptviewobj[id].hWnd2, WM_CHAR, ch[i], 0); - // SendMessage(pptviewobj[id].hWnd2, WM_KEYUP, ch[i], 0); - //} - //SendMessage(pptviewobj[id].hWnd2, WM_KEYDOWN, VK_RETURN, 0); - //SendMessage(pptviewobj[id].hWnd2, WM_CHAR, VK_RETURN, 0); - //SendMessage(pptviewobj[id].hWnd2, WM_KEYUP, VK_RETURN, 0); - //keybd_event(VK_RETURN, 0, 0, 0); + if (slideNo < 0) return; + pptView[id].guess = slideNo; + _itoa_s(slideNo, ch, 10, 10); + HWND h1 = GetForegroundWindow(); + HWND h2 = GetFocus(); + SetForegroundWindow(pptView[id].hWnd); + SetFocus(pptView[id].hWnd); + // slight pause, otherwise event triggering this call may grab focus back! + Sleep(50); + for (int i=0; i<10; i++) + { + if (ch[i] == '\0') break; + keybd_event((BYTE)ch[i], 0, 0, 0); + keybd_event((BYTE)ch[i], 0, KEYEVENTF_KEYUP, 0); + } + keybd_event(VK_RETURN, 0, 0, 0); + keybd_event(VK_RETURN, 0, KEYEVENTF_KEYUP, 0); + SetForegroundWindow(h1); + SetFocus(h2); } // Restart the show from the beginning DllExport void RestartShow(int id) { - // If we just go direct to slide one, then it remembers that all other slides have - // been animated, so ends up just showing the completed slides of those slides that - // have been animated next time we advance. - // Only way I've found to get around this is to step backwards all the way through. - // Lets move the window out of the way first so the audience doesn't see this. - DEBUG("RestartShow:%d\n", id); - Stop(id); - GotoSlide(id, pptviewobj[id].slideCount); - while(pptviewobj[id].currentSlide>1) - { - PrevStep(id); - Sleep(10); - } - for(int i=0;i<=pptviewobj[id].firstSlideSteps;i++) - { - PrevStep(id); - Sleep(10); - } - Resume(id); + // If we just go direct to slide one, then it remembers that all other + // slides have been animated, so ends up just showing the completed slides + // of those slides that have been animated next time we advance. + // Only way I've found to get around this is to step backwards all the way + // through. Lets move the window out of the way first so the audience + // doesn't see this. + DEBUG("RestartShow:%d\n", id); + Stop(id); + GotoSlide(id, pptView[id].slideCount); + for (int i=0; i <= pptView[id].steps - pptView[id].lastSlideSteps; i++) + { + PrevStep(id); + Sleep(10); + } + int i = 0; + while ((pptView[id].currentSlide > 1) && (i++ < 30000)) + { + Sleep(10); + } + Resume(id); } // This hook is started with the PPTVIEW.EXE process and waits for the @@ -530,234 +639,287 @@ DllExport void RestartShow(int id) // Release the hook as soon as we're complete to free up resources LRESULT CALLBACK CbtProc(int nCode, WPARAM wParam, LPARAM lParam) { - HHOOK hook = globalhook; - if(nCode==HCBT_CREATEWND) + HHOOK hook = globalHook; + if (nCode == HCBT_CREATEWND) { - char csClassName[16]; + char csClassName[16]; HWND hCurrWnd = (HWND)wParam; - DWORD retProcId = NULL; - GetClassName(hCurrWnd, csClassName, sizeof(csClassName)); - if((strcmp(csClassName, "paneClassDC")==0) - ||(strcmp(csClassName, "screenClass")==0)) - { - int id=-1; - DWORD windowthread = GetWindowThreadProcessId(hCurrWnd,NULL); - for(int i=0; i=0) - { - if(strcmp(csClassName, "paneClassDC")==0) - pptviewobj[id].hWnd2=hCurrWnd; - else - { - pptviewobj[id].hWnd=hCurrWnd; - CBT_CREATEWND* cw = (CBT_CREATEWND*)lParam; - if(pptviewobj[id].hParentWnd!=NULL) - cw->lpcs->hwndParent = pptviewobj[id].hParentWnd; - cw->lpcs->cy=(pptviewobj[id].rect.bottom-pptviewobj[id].rect.top); - cw->lpcs->cx=(pptviewobj[id].rect.right-pptviewobj[id].rect.left); - cw->lpcs->y=-32000; - cw->lpcs->x=-32000; - } - if((pptviewobj[id].hWnd!=NULL)&&(pptviewobj[id].hWnd2!=NULL)) - { - UnhookWindowsHookEx(globalhook); - globalhook=NULL; - pptviewobj[id].hook = SetWindowsHookEx(WH_CALLWNDPROC,CwpProc,hInstance,pptviewobj[id].dwThreadId); - pptviewobj[id].mhook = SetWindowsHookEx(WH_GETMESSAGE,GetMsgProc,hInstance,pptviewobj[id].dwThreadId); - Sleep(10); - pptviewobj[id].state = PPT_OPENED; - } - } - } + DWORD retProcId = NULL; + GetClassName(hCurrWnd, csClassName, sizeof(csClassName)); + if ((strcmp(csClassName, "paneClassDC") == 0) + ||(strcmp(csClassName, "screenClass") == 0)) + { + int id = -1; + DWORD windowThread = GetWindowThreadProcessId(hCurrWnd, NULL); + for (int i=0; i < MAX_PPTS; i++) + { + if (pptView[i].dwThreadId == windowThread) + { + id = i; + break; + } + } + if (id >= 0) + { + if (strcmp(csClassName, "paneClassDC") == 0) + { + pptView[id].hWnd2 = hCurrWnd; + } + else + { + pptView[id].hWnd = hCurrWnd; + CBT_CREATEWND* cw = (CBT_CREATEWND*)lParam; + if (pptView[id].hParentWnd != NULL) + { + cw->lpcs->hwndParent = pptView[id].hParentWnd; + } + cw->lpcs->cy = pptView[id].rect.bottom + - pptView[id].rect.top; + cw->lpcs->cx = pptView[id].rect.right + - pptView[id].rect.left; + cw->lpcs->y = -32000; + cw->lpcs->x = -32000; + } + if ((pptView[id].hWnd != NULL) && (pptView[id].hWnd2 != NULL)) + { + UnhookWindowsHookEx(globalHook); + globalHook = NULL; + pptView[id].hook = SetWindowsHookEx(WH_CALLWNDPROC, + CwpProc, hInstance, pptView[id].dwThreadId); + pptView[id].msgHook = SetWindowsHookEx(WH_GETMESSAGE, + GetMsgProc, hInstance, pptView[id].dwThreadId); + Sleep(10); + pptView[id].state = PPT_OPENED; + } + } + } } - return CallNextHookEx(hook,nCode,wParam,lParam); + return CallNextHookEx(hook, nCode, wParam, lParam); } // This hook exists whilst the slideshow is loading but only listens on the // slideshows thread. It listens out for mousewheel events -LRESULT CALLBACK GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam) +LRESULT CALLBACK GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam) { - HHOOK hook = NULL; - MSG *pMSG = (MSG *)lParam; - DWORD windowthread = GetWindowThreadProcessId(pMSG->hwnd,NULL); - int id=-1; - for(int i=0; i=0&&nCode==HC_ACTION&&wParam==PM_REMOVE&&pMSG->message==WM_MOUSEWHEEL) + HHOOK hook = NULL; + MSG *pMSG = (MSG *)lParam; + DWORD windowThread = GetWindowThreadProcessId(pMSG->hwnd, NULL); + int id = -1; + for (int i = 0; i < MAX_PPTS; i++) { - if(pptviewobj[id].state!=PPT_LOADED) - { - if(pptviewobj[id].currentSlide==1) - pptviewobj[id].firstSlideSteps++; - pptviewobj[id].steps++; - } + if (pptView[i].dwThreadId == windowThread) + { + id = i; + hook = pptView[id].msgHook; + break; + } + } + if (id >= 0 && nCode == HC_ACTION && wParam == PM_REMOVE + && pMSG->message == WM_MOUSEWHEEL) + { + if (pptView[id].state != PPT_LOADED) + { + if (pptView[id].currentSlide == 1) + { + pptView[id].firstSlideSteps++; + } + pptView[id].steps++; + pptView[id].lastSlideSteps++; + } } return CallNextHookEx(hook, nCode, wParam, lParam); } // This hook exists whilst the slideshow is running but only listens on the // slideshows thread. It listens out for slide changes, message WM_USER+22. LRESULT CALLBACK CwpProc(int nCode, WPARAM wParam, LPARAM lParam){ - CWPSTRUCT *cwp; - cwp = (CWPSTRUCT *)lParam; - HHOOK hook = NULL; - char filename[MAX_PATH]; + CWPSTRUCT *cwp; + cwp = (CWPSTRUCT *)lParam; + HHOOK hook = NULL; + char filename[MAX_PATH]; - DWORD windowthread = GetWindowThreadProcessId(cwp->hwnd,NULL); - int id=-1; - for(int i=0; i=0)&&(nCode==HC_ACTION)) - { - if(cwp->message==WM_USER+22) - { - if(pptviewobj[id].state != PPT_LOADED) - { - if((pptviewobj[id].currentSlide>0) - && (pptviewobj[id].previewpath!=NULL&&strlen(pptviewobj[id].previewpath)>0)) - { - sprintf_s(filename, MAX_PATH, "%s%i.bmp", pptviewobj[id].previewpath, pptviewobj[id].currentSlide); - CaptureAndSaveWindow(cwp->hwnd, filename); - } - } - if(cwp->wParam==0) - { - if(pptviewobj[id].currentSlide>0) - { - pptviewobj[id].state = PPT_LOADED; - pptviewobj[id].currentSlide = pptviewobj[id].slideCount+1; - } - } - else - { - pptviewobj[id].currentSlide = cwp->wParam - 255; - if(pptviewobj[id].currentSlide>pptviewobj[id].slideCount) - pptviewobj[id].slideCount = pptviewobj[id].currentSlide; - } - } - if((pptviewobj[id].state != PPT_CLOSED)&&(cwp->message==WM_CLOSE||cwp->message==WM_QUIT)) - pptviewobj[id].state = PPT_CLOSING; - } - return CallNextHookEx(hook,nCode,wParam,lParam); + DWORD windowThread = GetWindowThreadProcessId(cwp->hwnd, NULL); + int id = -1; + for (int i = 0; i < MAX_PPTS; i++) + { + if (pptView[i].dwThreadId == windowThread) + { + id = i; + hook = pptView[id].hook; + break; + } + } + if ((id >= 0) && (nCode == HC_ACTION)) + { + if (cwp->message == WM_USER + 22) + { + if (pptView[id].state != PPT_LOADED) + { + if ((pptView[id].currentSlide > 0) + && (pptView[id].previewPath != NULL + && strlen(pptView[id].previewPath) > 0)) + { + sprintf_s(filename, MAX_PATH, "%s%i.bmp", + pptView[id].previewPath, + pptView[id].currentSlide); + CaptureAndSaveWindow(cwp->hwnd, filename); + } + if (((cwp->wParam == 0) + || (pptView[id].slideNos[1] == cwp->wParam)) + && (pptView[id].currentSlide > 0)) + { + pptView[id].state = PPT_LOADED; + pptView[id].currentSlide = pptView[id].slideCount + 1; + } + else + { + if (cwp->wParam > 0) + { + pptView[id].currentSlide = pptView[id].currentSlide + 1; + pptView[id].slideNos[pptView[id].currentSlide] + = cwp->wParam; + pptView[id].slideCount = pptView[id].currentSlide; + pptView[id].lastSlideSteps = 0; + } + } + } + else + { + if (cwp->wParam > 0) + { + if(pptView[id].guess > 0 + && pptView[id].slideNos[pptView[id].guess] == 0) + { + pptView[id].currentSlide = 0; + } + for(int i = 1; i <= pptView[id].slideCount; i++) + { + if(pptView[id].slideNos[i] == cwp->wParam) + { + pptView[id].currentSlide = i; + break; + } + } + if(pptView[id].currentSlide == 0) + { + pptView[id].slideNos[pptView[id].guess] = cwp->wParam; + pptView[id].currentSlide = pptView[id].guess; + } + pptView[id].guess = 0; + } + } + } + if ((pptView[id].state != PPT_CLOSED) + + &&(cwp->message == WM_CLOSE || cwp->message == WM_QUIT)) + { + pptView[id].state = PPT_CLOSING; + } + } + return CallNextHookEx(hook, nCode, wParam, lParam); } VOID CaptureAndSaveWindow(HWND hWnd, CHAR* filename) { - HBITMAP hBmp; - if ((hBmp = CaptureWindow(hWnd)) == NULL) - return; + HBITMAP hBmp; + if ((hBmp = CaptureWindow(hWnd)) == NULL) + { + return; + } + RECT client; + GetClientRect(hWnd, &client); + UINT uiBytesPerRow = 3 * client.right; // RGB takes 24 bits + UINT uiRemainderForPadding; - RECT client; - GetClientRect (hWnd, &client); - UINT uiBytesPerRow = 3 * client.right; // RGB takes 24 bits - UINT uiRemainderForPadding; + if ((uiRemainderForPadding = uiBytesPerRow % sizeof(DWORD)) > 0) + uiBytesPerRow += (sizeof(DWORD) - uiRemainderForPadding); - if ((uiRemainderForPadding = uiBytesPerRow % sizeof (DWORD)) > 0) - uiBytesPerRow += (sizeof (DWORD) - uiRemainderForPadding); + UINT uiBytesPerAllRows = uiBytesPerRow * client.bottom; + PBYTE pDataBits; - UINT uiBytesPerAllRows = uiBytesPerRow * client.bottom; - PBYTE pDataBits; + if ((pDataBits = new BYTE[uiBytesPerAllRows]) != NULL) + { + BITMAPINFOHEADER bmi = {0}; + BITMAPFILEHEADER bmf = {0}; - if ((pDataBits = new BYTE [uiBytesPerAllRows]) != NULL) - { - BITMAPINFOHEADER bmi = {0}; - BITMAPFILEHEADER bmf = {0}; + // Prepare to get the data out of HBITMAP: + bmi.biSize = sizeof(bmi); + bmi.biPlanes = 1; + bmi.biBitCount = 24; + bmi.biHeight = client.bottom; + bmi.biWidth = client.right; - // Prepare to get the data out of HBITMAP: - bmi.biSize = sizeof (bmi); - bmi.biPlanes = 1; - bmi.biBitCount = 24; - bmi.biHeight = client.bottom; - bmi.biWidth = client.right; + // Get it: + HDC hDC = GetDC(hWnd); + GetDIBits(hDC, hBmp, 0, client.bottom, pDataBits, (BITMAPINFO*) &bmi, + DIB_RGB_COLORS); + ReleaseDC(hWnd, hDC); - // Get it: - HDC hDC = GetDC (hWnd); - GetDIBits (hDC, hBmp, 0, client.bottom, pDataBits, - (BITMAPINFO*) &bmi, DIB_RGB_COLORS); - ReleaseDC (hWnd, hDC); + // Fill the file header: + bmf.bfOffBits = sizeof(bmf) + sizeof(bmi); + bmf.bfSize = bmf.bfOffBits + uiBytesPerAllRows; + bmf.bfType = 0x4D42; - // Fill the file header: - bmf.bfOffBits = sizeof (bmf) + sizeof (bmi); - bmf.bfSize = bmf.bfOffBits + uiBytesPerAllRows; - bmf.bfType = 0x4D42; - - // Writing: - FILE* pFile; - int err = fopen_s(&pFile, filename, "wb"); - if (err == 0) - { - fwrite (&bmf, sizeof (bmf), 1, pFile); - fwrite (&bmi, sizeof (bmi), 1, pFile); - fwrite (pDataBits, sizeof (BYTE), uiBytesPerAllRows, pFile); - fclose (pFile); - } - delete [] pDataBits; - } - DeleteObject (hBmp); + // Writing: + FILE* pFile; + int err = fopen_s(&pFile, filename, "wb"); + if (err == 0) + { + fwrite(&bmf, sizeof(bmf), 1, pFile); + fwrite(&bmi, sizeof(bmi), 1, pFile); + fwrite(pDataBits, sizeof(BYTE), uiBytesPerAllRows, pFile); + fclose(pFile); + } + delete [] pDataBits; + } + DeleteObject(hBmp); } -HBITMAP CaptureWindow (HWND hWnd) { - HDC hDC; - BOOL bOk = FALSE; - HBITMAP hImage = NULL; +HBITMAP CaptureWindow(HWND hWnd) +{ + HDC hDC; + BOOL bOk = FALSE; + HBITMAP hImage = NULL; - hDC = GetDC (hWnd); - RECT rcClient; - GetClientRect (hWnd, &rcClient); - if ((hImage = CreateCompatibleBitmap (hDC, rcClient.right, rcClient.bottom)) != NULL) - { - HDC hMemDC; - HBITMAP hDCBmp; + hDC = GetDC(hWnd); + RECT rcClient; + GetClientRect(hWnd, &rcClient); + if ((hImage = CreateCompatibleBitmap(hDC, rcClient.right, rcClient.bottom)) + != NULL) + { + HDC hMemDC; + HBITMAP hDCBmp; - if ((hMemDC = CreateCompatibleDC (hDC)) != NULL) - { - hDCBmp = (HBITMAP) SelectObject (hMemDC, hImage); - HMODULE hLib = LoadLibrary("User32"); - // PrintWindow works for windows outside displayable area - // but was only introduced in WinXP. BitBlt requires the window to be topmost - // and within the viewable area of the display - if(GetProcAddress(hLib, "PrintWindow")==NULL) - { - SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE); - BitBlt (hMemDC, 0, 0, rcClient.right, rcClient.bottom, hDC, 0, 0, SRCCOPY); - SetWindowPos(hWnd, HWND_NOTOPMOST, -32000, -32000, 0, 0, SWP_NOSIZE); - } - else - { - PrintWindow(hWnd, hMemDC, 0); - } - SelectObject (hMemDC, hDCBmp); - DeleteDC (hMemDC); - bOk = TRUE; - } - } - ReleaseDC (hWnd, hDC); - if (! bOk) - { - if (hImage) - { - DeleteObject (hImage); - hImage = NULL; - } - } - return hImage; + if ((hMemDC = CreateCompatibleDC(hDC)) != NULL) + { + hDCBmp = (HBITMAP)SelectObject(hMemDC, hImage); + HMODULE hLib = LoadLibrary("User32"); + // PrintWindow works for windows outside displayable area + // but was only introduced in WinXP. BitBlt requires the window to + // be topmost and within the viewable area of the display + if (GetProcAddress(hLib, "PrintWindow") == NULL) + { + SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE); + BitBlt(hMemDC, 0, 0, rcClient.right, rcClient.bottom, hDC, 0, + 0, SRCCOPY); + SetWindowPos(hWnd, HWND_NOTOPMOST, -32000, -32000, 0, 0, + SWP_NOSIZE); + } + else + { + PrintWindow(hWnd, hMemDC, 0); + } + SelectObject(hMemDC, hDCBmp); + DeleteDC(hMemDC); + bOk = TRUE; + } + } + ReleaseDC(hWnd, hDC); + if (!bOk) + { + if (hImage) + { + DeleteObject(hImage); + hImage = NULL; + } + } + return hImage; } diff --git a/openlp/plugins/presentations/lib/pptviewlib/pptviewlib.dll b/openlp/plugins/presentations/lib/pptviewlib/pptviewlib.dll deleted file mode 100644 index f8a0de0d3..000000000 Binary files a/openlp/plugins/presentations/lib/pptviewlib/pptviewlib.dll and /dev/null differ diff --git a/openlp/plugins/presentations/lib/pptviewlib/pptviewlib.h b/openlp/plugins/presentations/lib/pptviewlib/pptviewlib.h index 6012b0467..98b0a21ab 100644 --- a/openlp/plugins/presentations/lib/pptviewlib/pptviewlib.h +++ b/openlp/plugins/presentations/lib/pptviewlib/pptviewlib.h @@ -1,55 +1,85 @@ +/****************************************************************************** +* PptViewLib - PowerPoint Viewer 2003/2007 Controller * +* OpenLP - Open Source Lyrics Projection * +* --------------------------------------------------------------------------- * +* Copyright (c) 2008-2011 Raoul Snyman * +* Portions copyright (c) 2008-2011 Tim Bentley, Jonathan Corwin, Michael * +* Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, Armin Köhler, * +* Andreas Preikschat, Mattias Põldaru, Christian Richter, Philip Ridout, * +* 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 * +******************************************************************************/ -#define DllExport extern "C" __declspec( dllexport ) +#define DllExport extern "C" __declspec( dllexport ) -enum PPTVIEWSTATE { PPT_CLOSED, PPT_STARTED, PPT_OPENED, PPT_LOADED, PPT_CLOSING}; +#define DEBUG(...) if (debug) printf(__VA_ARGS__) -DllExport int OpenPPT(char *filename, HWND hParentWnd, RECT rect, char *previewpath); +enum PPTVIEWSTATE {PPT_CLOSED, PPT_STARTED, PPT_OPENED, PPT_LOADED, + PPT_CLOSING}; + +DllExport int OpenPPT(char *filename, HWND hParentWnd, RECT rect, + char *previewPath); DllExport BOOL CheckInstalled(); DllExport void ClosePPT(int id); DllExport int GetCurrentSlide(int id); DllExport int GetSlideCount(int id); DllExport void NextStep(int id); DllExport void PrevStep(int id); -DllExport void GotoSlide(int id, int slideno); +DllExport void GotoSlide(int id, int slide_no); DllExport void RestartShow(int id); DllExport void Blank(int id); DllExport void Unblank(int id); DllExport void Stop(int id); DllExport void Resume(int id); -DllExport void SetDebug(BOOL onoff); +DllExport void SetDebug(BOOL onOff); LRESULT CALLBACK CbtProc(int nCode, WPARAM wParam, LPARAM lParam); LRESULT CALLBACK CwpProc(int nCode, WPARAM wParam, LPARAM lParam); LRESULT CALLBACK GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam); -BOOL GetPPTViewerPath(char *pptviewerpath, int strsize); -HBITMAP CaptureWindow (HWND hWnd); -VOID SaveBitmap (CHAR* filename, HBITMAP hBmp) ; +BOOL GetPPTViewerPath(char *pptViewerPath, int stringSize); +BOOL GetPPTViewerPathFromReg(char *pptViewerPath, int stringSize); +HBITMAP CaptureWindow(HWND hWnd); +VOID SaveBitmap(CHAR* filename, HBITMAP hBmp) ; VOID CaptureAndSaveWindow(HWND hWnd, CHAR* filename); BOOL GetPPTInfo(int id); BOOL SavePPTInfo(int id); - - void Unhook(int id); -#define MAX_PPTOBJS 50 +#define MAX_PPTS 16 +#define MAX_SLIDES 256 -struct PPTVIEWOBJ +struct PPTVIEW { - HHOOK hook; - HHOOK mhook; - HWND hWnd; - HWND hWnd2; - HWND hParentWnd; - HANDLE hProcess; - HANDLE hThread; - DWORD dwProcessId; - DWORD dwThreadId; - RECT rect; - int slideCount; - int currentSlide; - int firstSlideSteps; - int steps; - char filename[MAX_PATH]; - char previewpath[MAX_PATH]; - PPTVIEWSTATE state; + HHOOK hook; + HHOOK msgHook; + HWND hWnd; + HWND hWnd2; + HWND hParentWnd; + HANDLE hProcess; + HANDLE hThread; + DWORD dwProcessId; + DWORD dwThreadId; + RECT rect; + int slideCount; + int currentSlide; + int firstSlideSteps; + int lastSlideSteps; + int steps; + int guess; + char filename[MAX_PATH]; + char previewPath[MAX_PATH]; + int slideNos[MAX_SLIDES]; + PPTVIEWSTATE state; }; diff --git a/openlp/plugins/presentations/lib/presentationcontroller.py b/openlp/plugins/presentations/lib/presentationcontroller.py index bd37746c1..a9d384c81 100644 --- a/openlp/plugins/presentations/lib/presentationcontroller.py +++ b/openlp/plugins/presentations/lib/presentationcontroller.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, Armin Köhler, 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,8 @@ import shutil from PyQt4 import QtCore -from openlp.core.lib import Receiver, resize_image +from openlp.core.lib import Receiver, check_directory_exists, create_thumb, \ + validate_thumb from openlp.core.utils import AppLocation log = logging.getLogger(__name__) @@ -96,8 +98,7 @@ class PresentationDocument(object): self.slidenumber = 0 self.controller = controller self.filepath = name - if not os.path.isdir(self.get_thumbnail_folder()): - os.mkdir(self.get_thumbnail_folder()) + check_directory_exists(self.get_thumbnail_folder()) def load_presentation(self): """ @@ -105,7 +106,7 @@ class PresentationDocument(object): Loads the presentation and starts it ``presentation`` - The file name of the presentations to the run. + The file name of the presentations to the run. Returns False if the file could not be opened """ @@ -144,15 +145,13 @@ class PresentationDocument(object): def check_thumbnails(self): """ - Returns true if the thumbnail images look to exist and are more - recent than the powerpoint + Returns ``True`` if the thumbnail images exist and are more recent than + the powerpoint file. """ lastimage = self.get_thumbnail_path(self.get_slide_count(), True) if not (lastimage and os.path.isfile(lastimage)): return False - imgdate = os.stat(lastimage).st_mtime - pptdate = os.stat(self.filepath).st_mtime - return imgdate >= pptdate + return validate_thumb(self.filepath, lastimage) def close_presentation(self): """ @@ -245,8 +244,8 @@ class PresentationDocument(object): if self.check_thumbnails(): return if os.path.isfile(file): - img = resize_image(file, 320, 240) - img.save(self.get_thumbnail_path(idx, False)) + thumb_path = self.get_thumbnail_path(idx, False) + create_thumb(file, thumb_path, False, QtCore.QSize(320, 240)) def get_thumbnail_path(self, slide_no, check_exists): """ @@ -386,10 +385,8 @@ class PresentationController(object): AppLocation.get_section_data_path(self.settings_section), u'thumbnails') self.thumbnail_prefix = u'slide' - if not os.path.isdir(self.thumbnail_folder): - os.makedirs(self.thumbnail_folder) - if not os.path.isdir(self.temp_folder): - os.makedirs(self.temp_folder) + check_directory_exists(self.thumbnail_folder) + check_directory_exists(self.temp_folder) def enabled(self): """ diff --git a/openlp/plugins/presentations/lib/presentationtab.py b/openlp/plugins/presentations/lib/presentationtab.py index 7b85eebb4..b0c3de7a8 100644 --- a/openlp/plugins/presentations/lib/presentationtab.py +++ b/openlp/plugins/presentations/lib/presentationtab.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, Armin Köhler, 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 # @@ -33,12 +34,12 @@ class PresentationTab(SettingsTab): """ PresentationsTab is the Presentations settings tab in the settings dialog. """ - def __init__(self, title, visible_title, controllers): + def __init__(self, parent, title, visible_title, controllers, icon_path): """ Constructor """ self.controllers = controllers - SettingsTab.__init__(self, title, visible_title) + SettingsTab.__init__(self, parent, title, visible_title, icon_path) def setupUi(self): """ @@ -86,7 +87,7 @@ class PresentationTab(SettingsTab): checkbox.setText( unicode(translate('PresentationPlugin.PresentationTab', '%s (unavailable)')) % controller.name) - self.AdvancedGroupBox.setTitle(UiStrings.Advanced) + self.AdvancedGroupBox.setTitle(UiStrings().Advanced) self.OverrideAppCheckBox.setText( translate('PresentationPlugin.PresentationTab', 'Allow presentation application to be overriden')) diff --git a/openlp/plugins/presentations/presentationplugin.py b/openlp/plugins/presentations/presentationplugin.py index eb7e714f0..643ad14ad 100644 --- a/openlp/plugins/presentations/presentationplugin.py +++ b/openlp/plugins/presentations/presentationplugin.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, Armin Köhler, 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 # @@ -51,18 +52,18 @@ class PresentationPlugin(Plugin): """ log.debug(u'Initialised') self.controllers = {} - Plugin.__init__(self, u'Presentations', u'1.9.4', plugin_helpers) + Plugin.__init__(self, u'presentations', plugin_helpers) self.weight = -8 self.icon_path = u':/plugins/plugin_presentations.png' self.icon = build_icon(self.icon_path) - def getSettingsTab(self): + def getSettingsTab(self, parent): """ Create the settings Tab """ visible_name = self.getString(StringContent.VisibleName) - return PresentationTab(self.name, visible_name[u'title'], - self.controllers) + return PresentationTab(parent, self.name, visible_name[u'title'], + self.controllers, self.icon_path) def initialise(self): """ @@ -71,13 +72,12 @@ class PresentationPlugin(Plugin): """ log.info(u'Presentations Initialising') Plugin.initialise(self) - self.insertToolboxItem() for controller in self.controllers: if self.controllers[controller].enabled(): try: self.controllers[controller].start_process() except: - log.exception(u'Failed to start controller process') + log.warn(u'Failed to start controller process') self.controllers[controller].available = False self.mediaItem.buildFileMaskString() @@ -87,7 +87,7 @@ class PresentationPlugin(Plugin): to close down their applications and release resources. """ log.info(u'Plugin Finalise') - #Ask each controller to tidy up + # Ask each controller to tidy up. for key in self.controllers: controller = self.controllers[key] if controller.enabled(): @@ -99,7 +99,7 @@ class PresentationPlugin(Plugin): Create the Media Manager List """ return PresentationMediaItem( - self, self.icon, self.name, self.controllers) + self.mediadock.media_dock, self, self.icon, self.controllers) def registerControllers(self, controller): """ @@ -128,7 +128,7 @@ class PresentationPlugin(Plugin): try: __import__(modulename, globals(), locals(), []) except ImportError: - log.exception(u'Failed to import %s on path %s', + log.warn(u'Failed to import %s on path %s', modulename, path) controller_classes = PresentationController.__subclasses__() for controller_class in controller_classes: @@ -168,17 +168,18 @@ class PresentationPlugin(Plugin): } # Middle Header Bar tooltips = { - u'load': translate('PresentationPlugin', 'Load a new Presentation'), + u'load': translate('PresentationPlugin', + 'Load a new presentation.'), u'import': u'', u'new': u'', u'edit': u'', u'delete': translate('PresentationPlugin', - 'Delete the selected Presentation'), + 'Delete the selected presentation.'), u'preview': translate('PresentationPlugin', - 'Preview the selected Presentation'), + 'Preview the selected presentation.'), u'live': translate('PresentationPlugin', - 'Send the selected Presentation live'), + 'Send the selected presentation live.'), u'service': translate('PresentationPlugin', - 'Add the selected Presentation to the service') + 'Add the selected presentation to the service.') } self.setPluginUiTextStrings(tooltips) diff --git a/openlp/plugins/remotes/__init__.py b/openlp/plugins/remotes/__init__.py index b5077f435..ab1d8adbc 100644 --- a/openlp/plugins/remotes/__init__.py +++ b/openlp/plugins/remotes/__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, Armin Köhler, 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 # @@ -26,4 +27,68 @@ """ The :mod:`remotes` plugin allows OpenLP to be controlled from another machine over a network connection. + +Routes: + +``/`` + Go to the web interface. + +``/files/{filename}`` + Serve a static file. + +``/api/poll`` + Poll to see if there are any changes. Returns a JSON-encoded dict of + any changes that occurred:: + + {"results": {"type": "controller"}} + + Or, if there were no results, False:: + + {"results": False} + +``/api/controller/{live|preview}/{action}`` + Perform ``{action}`` on the live or preview controller. Valid actions + are: + + ``next`` + Load the next slide. + + ``previous`` + Load the previous slide. + + ``jump`` + Jump to a specific slide. Requires an id return in a JSON-encoded + dict like so:: + + {"request": {"id": 1}} + + ``first`` + Load the first slide. + + ``last`` + Load the last slide. + + ``text`` + Request the text of the current slide. + +``/api/service/{action}`` + Perform ``{action}`` on the service manager (e.g. go live). Data is + passed as a json-encoded ``data`` parameter. Valid actions are: + + ``next`` + Load the next item in the service. + + ``previous`` + Load the previews item in the service. + + ``jump`` + Jump to a specific item in the service. Requires an id returned in + a JSON-encoded dict like so:: + + {"request": {"id": 1}} + + ``list`` + Request a list of items in the service. + + """ diff --git a/openlp/plugins/remotes/html/images/ajax-loader.png b/openlp/plugins/remotes/html/images/ajax-loader.png new file mode 100644 index 000000000..811a2cdd1 Binary files /dev/null and b/openlp/plugins/remotes/html/images/ajax-loader.png differ diff --git a/openlp/plugins/remotes/html/images/form-check-off.png b/openlp/plugins/remotes/html/images/form-check-off.png new file mode 100644 index 000000000..54e2fe0f8 Binary files /dev/null and b/openlp/plugins/remotes/html/images/form-check-off.png differ diff --git a/openlp/plugins/remotes/html/images/form-check-on.png b/openlp/plugins/remotes/html/images/form-check-on.png new file mode 100644 index 000000000..e6daaaf8b Binary files /dev/null and b/openlp/plugins/remotes/html/images/form-check-on.png differ diff --git a/openlp/plugins/remotes/html/images/form-radio-off.png b/openlp/plugins/remotes/html/images/form-radio-off.png new file mode 100644 index 000000000..32bd43392 Binary files /dev/null and b/openlp/plugins/remotes/html/images/form-radio-off.png differ diff --git a/openlp/plugins/remotes/html/images/form-radio-on.png b/openlp/plugins/remotes/html/images/form-radio-on.png new file mode 100644 index 000000000..ddc404970 Binary files /dev/null and b/openlp/plugins/remotes/html/images/form-radio-on.png differ diff --git a/openlp/plugins/remotes/html/images/icon-search-black.png b/openlp/plugins/remotes/html/images/icon-search-black.png new file mode 100644 index 000000000..5721120f8 Binary files /dev/null and b/openlp/plugins/remotes/html/images/icon-search-black.png differ diff --git a/openlp/plugins/remotes/html/images/icons-18-black.png b/openlp/plugins/remotes/html/images/icons-18-black.png new file mode 100644 index 000000000..3657baea8 Binary files /dev/null and b/openlp/plugins/remotes/html/images/icons-18-black.png differ diff --git a/openlp/plugins/remotes/html/images/icons-18-white.png b/openlp/plugins/remotes/html/images/icons-18-white.png new file mode 100644 index 000000000..ccca7b44b Binary files /dev/null and b/openlp/plugins/remotes/html/images/icons-18-white.png differ diff --git a/openlp/plugins/remotes/html/images/icons-36-black.png b/openlp/plugins/remotes/html/images/icons-36-black.png new file mode 100644 index 000000000..79b6d601b Binary files /dev/null and b/openlp/plugins/remotes/html/images/icons-36-black.png differ diff --git a/openlp/plugins/remotes/html/images/icons-36-white.png b/openlp/plugins/remotes/html/images/icons-36-white.png new file mode 100644 index 000000000..e1b9c04ea Binary files /dev/null and b/openlp/plugins/remotes/html/images/icons-36-white.png differ diff --git a/openlp/plugins/remotes/html/images/ui-icon-blank.png b/openlp/plugins/remotes/html/images/ui-icon-blank.png new file mode 100644 index 000000000..a685fe537 Binary files /dev/null and b/openlp/plugins/remotes/html/images/ui-icon-blank.png differ diff --git a/openlp/plugins/remotes/html/images/ui-icon-unblank.png b/openlp/plugins/remotes/html/images/ui-icon-unblank.png new file mode 100644 index 000000000..590361f44 Binary files /dev/null and b/openlp/plugins/remotes/html/images/ui-icon-unblank.png differ diff --git a/openlp/plugins/remotes/html/index.html b/openlp/plugins/remotes/html/index.html index 94bb24d32..4d3076e3a 100644 --- a/openlp/plugins/remotes/html/index.html +++ b/openlp/plugins/remotes/html/index.html @@ -1,57 +1,135 @@ - - - - - OpenLP Remote Controller - - - - + + + + + + ${app_title} + + + + + + -

OpenLP Controller

-

Quick Links: Service Manager | Slide Controller | Miscellaneous

-

Service Manager

-
-

(Click service item to go live.)

-
- Controls -
- -
-
- - -
-
-
-

Slide Controller

-
-

(Click verse to display.)

-
- Controls -
- -
-
- - -
-
-
-

Miscellaneous

-
- - +
+
+

${app_title}

+
+
+ -
- - - +
+
+
+
+ ${back} +

${service_manager}

+ ${refresh} +
+
+
    +
+
+ +
+
+
+ ${back} +

${slide_controller}

+ ${refresh} +
+
+
    +
+
+ +
+
+
+ ${back} +

${alerts}

+
+
+
+ +
-
- OpenLP website + ${show_alert} +
+
+ +
+
+

${options}

+
+ +
- diff --git a/openlp/plugins/remotes/html/jquery.js b/openlp/plugins/remotes/html/jquery.js index 7c2430802..14fd6470f 100644 --- a/openlp/plugins/remotes/html/jquery.js +++ b/openlp/plugins/remotes/html/jquery.js @@ -1,154 +1,16 @@ /*! - * jQuery JavaScript Library v1.4.2 + * jQuery JavaScript Library v1.5.1 * http://jquery.com/ * - * Copyright 2010, John Resig + * Copyright 2011, John Resig * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * Includes Sizzle.js * http://sizzlejs.com/ - * Copyright 2010, The Dojo Foundation + * Copyright 2011, The Dojo Foundation * Released under the MIT, BSD, and GPL Licenses. * - * Date: Sat Feb 13 22:33:48 2010 -0500 + * Date: Wed Feb 23 13:55:29 2011 -0500 */ -(function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/, -Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&& -(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this, -a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b=== -"find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this, -function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b
a"; -var e=d.getElementsByTagName("*"),j=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!j)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(j.getAttribute("style")),hrefNormalized:j.getAttribute("href")==="/a",opacity:/^0.55$/.test(j.style.opacity),cssFloat:!!j.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected, -parentNode:d.removeChild(d.appendChild(s.createElement("div"))).parentNode===null,deleteExpando:true,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(i){}a.insertBefore(b,a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}try{delete b.test}catch(o){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function k(){c.support.noCloneEvent= -false;d.detachEvent("onclick",k)});d.cloneNode(true).fireEvent("onclick")}d=s.createElement("div");d.innerHTML="";a=s.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var k=s.createElement("div");k.style.width=k.style.paddingLeft="1px";s.body.appendChild(k);c.boxModel=c.support.boxModel=k.offsetWidth===2;s.body.removeChild(k).style.display="none"});a=function(k){var n= -s.createElement("div");k="on"+k;var r=k in n;if(!r){n.setAttribute(k,"return;");r=typeof n[k]==="function"}return r};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=j=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ya=0,za={};c.extend({cache:{},expando:G,noData:{embed:true,object:true, -applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var f=a[G],e=c.cache;if(!f&&typeof b==="string"&&d===w)return null;f||(f=++Ya);if(typeof b==="object"){a[G]=f;e[f]=c.extend(true,{},b)}else if(!e[f]){a[G]=f;e[f]={}}a=e[f];if(d!==w)a[b]=d;return typeof b==="string"?a[b]:a}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{if(c.support.deleteExpando)delete a[c.expando]; -else a.removeAttribute&&a.removeAttribute(c.expando);delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this, -a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b=== -w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Aa=/[\n\t]/g,ca=/\s+/,Za=/\r/g,$a=/href|src|style/,ab=/(button|input)/i,bb=/(button|input|object|select|textarea)/i, -cb=/^(a|area)$/i,Ba=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(n){var r=c(this);r.addClass(a.call(this,n,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var j=b?d:0;for(d=b?d+1:e.length;j=0;else if(c.nodeName(this,"select")){var u=c.makeArray(r);c("option",this).each(function(){this.selected= -c.inArray(c(this).val(),u)>=0});if(!u.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var j=$a.test(b);if(b in a&&f&&!j){if(e){b==="type"&&ab.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed"); -a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&j?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var O=/\.(.*)$/,db=function(a){return a.replace(/[^\w\s\.\|`]/g, -function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;var e,j;if(d.handler){e=d;d=e.handler}if(!d.guid)d.guid=c.guid++;if(j=c.data(a)){var i=j.events=j.events||{},o=j.handle;if(!o)j.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,arguments):w};o.elem=a;b=b.split(" ");for(var k,n=0,r;k=b[n++];){j=e?c.extend({},e):{handler:d,data:f};if(k.indexOf(".")>-1){r=k.split("."); -k=r.shift();j.namespace=r.slice(0).sort().join(".")}else{r=[];j.namespace=""}j.type=k;j.guid=d.guid;var u=i[k],z=c.event.special[k]||{};if(!u){u=i[k]=[];if(!z.setup||z.setup.call(a,f,r,o)===false)if(a.addEventListener)a.addEventListener(k,o,false);else a.attachEvent&&a.attachEvent("on"+k,o)}if(z.add){z.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}u.push(j);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){var e,j=0,i,o,k,n,r,u,z=c.data(a), -C=z&&z.events;if(z&&C){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(e in C)c.event.remove(a,e+b)}else{for(b=b.split(" ");e=b[j++];){n=e;i=e.indexOf(".")<0;o=[];if(!i){o=e.split(".");e=o.shift();k=new RegExp("(^|\\.)"+c.map(o.slice(0).sort(),db).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(r=C[e])if(d){n=c.event.special[e]||{};for(B=f||0;B=0){a.type= -e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(j){}if(!a.isPropagationStopped()&& -f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){f=a.target;var i,o=c.nodeName(f,"a")&&e==="click",k=c.event.special[e]||{};if((!k._default||k._default.call(d,a)===false)&&!o&&!(f&&f.nodeName&&c.noData[f.nodeName.toLowerCase()])){try{if(f[e]){if(i=f["on"+e])f["on"+e]=null;c.event.triggered=true;f[e]()}}catch(n){}if(i)f["on"+e]=i;c.event.triggered=false}}},handle:function(a){var b,d,f,e;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive; -if(!b){d=a.type.split(".");a.type=d.shift();f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)")}e=c.data(this,"events");d=e[a.type];if(e&&d){d=d.slice(0);e=0;for(var j=d.length;e-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},fa=function(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Fa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data", -e);if(!(f===w||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:fa,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return fa.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return fa.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a, -"_change_data",Fa(a))}},setup:function(){if(this.type==="file")return false;for(var a in ea)c.event.add(this,a+".specialChange",ea[a]);return da.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return da.test(this.nodeName)}};ea=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a, -d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var j in d)this[b](j,f,d[j],e);return this}if(c.isFunction(f)){e=f;f=w}var i=b==="one"?c.proxy(e,function(k){c(this).unbind(k,i);return e.apply(this,arguments)}):e;if(d==="unload"&&b!=="one")this.one(d,f,e);else{j=0;for(var o=this.length;j0){y=t;break}}t=t[g]}m[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, -e=0,j=Object.prototype.toString,i=false,o=true;[0,0].sort(function(){o=false;return 0});var k=function(g,h,l,m){l=l||[];var q=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return l;for(var p=[],v,t,y,S,H=true,M=x(h),I=g;(f.exec(""),v=f.exec(I))!==null;){I=v[3];p.push(v[1]);if(v[2]){S=v[3];break}}if(p.length>1&&r.exec(g))if(p.length===2&&n.relative[p[0]])t=ga(p[0]+p[1],h);else for(t=n.relative[p[0]]?[h]:k(p.shift(),h);p.length;){g=p.shift();if(n.relative[g])g+=p.shift(); -t=ga(g,t)}else{if(!m&&p.length>1&&h.nodeType===9&&!M&&n.match.ID.test(p[0])&&!n.match.ID.test(p[p.length-1])){v=k.find(p.shift(),h,M);h=v.expr?k.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:p.pop(),set:z(m)}:k.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=v.expr?k.filter(v.expr,v.set):v.set;if(p.length>0)y=z(t);else H=false;for(;p.length;){var D=p.pop();v=D;if(n.relative[D])v=p.pop();else D="";if(v==null)v=h;n.relative[D](y,v,M)}}else y=[]}y||(y=t);y||k.error(D|| -g);if(j.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))l.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&l.push(t[g]);else l.push.apply(l,y);else z(y,l);if(S){k(S,q,l,m);k.uniqueSort(l)}return l};k.uniqueSort=function(g){if(B){i=o;g.sort(B);if(i)for(var h=1;h":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()}, -CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m, -g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)}, -text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}}, -setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return hl[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h= -h[3];l=0;for(m=h.length;l=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m=== -"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g, -h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&& -q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML=""; -if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="

";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}(); -(function(){var g=s.createElement("div");g.innerHTML="
";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}: -function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f0)for(var j=d;j0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j= -{},i;if(f&&a.length){e=0;for(var o=a.length;e-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a=== -"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode", -d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")? -a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType=== -1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/"},F={option:[1,""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div
","
"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d= -c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this}, -wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})}, -prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b, -this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild); -return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja, -""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]); -return this}else{e=0;for(var j=d.length;e0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["", -""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]===""&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e= -c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]? -c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja= -function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter= -Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a, -"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f= -a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b= -a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=//gi,ub=/select|textarea/i,vb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ka=/\?/,wb=/(\?|&)_=.*?(&|$)/,xb=/^(\w+:)?\/\/([^\/?#]+)/,yb=/%20/g,zb=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!== -"string")return zb.call(this,a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);f="POST"}var j=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(i,o){if(o==="success"||o==="notmodified")j.html(e?c("
").append(i.responseText.replace(tb,"")).find(e):i.responseText);d&&j.each(d,[i.responseText,o,i])}});return this}, -serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ub.test(this.nodeName)||vb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "), -function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href, -global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:A.XMLHttpRequest&&(A.location.protocol!=="file:"||!A.ActiveXObject)?function(){return new A.XMLHttpRequest}:function(){try{return new A.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&& -e.success.call(k,o,i,x);e.global&&f("ajaxSuccess",[x,e])}function d(){e.complete&&e.complete.call(k,x,i);e.global&&f("ajaxComplete",[x,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),j,i,o,k=a&&a.context||e,n=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(n==="GET")N.test(e.url)||(e.url+=(ka.test(e.url)? -"&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||N.test(e.url))){j=e.jsonpCallback||"jsonp"+sb++;if(e.data)e.data=(e.data+"").replace(N,"="+j+"$1");e.url=e.url.replace(N,"="+j+"$1");e.dataType="script";A[j]=A[j]||function(q){o=q;b();d();A[j]=w;try{delete A[j]}catch(p){}z&&z.removeChild(C)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache=== -false&&n==="GET"){var r=J(),u=e.url.replace(wb,"$1_="+r+"$2");e.url=u+(u===e.url?(ka.test(e.url)?"&":"?")+"_="+r:"")}if(e.data&&n==="GET")e.url+=(ka.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&c.event.trigger("ajaxStart");r=(r=xb.exec(e.url))&&(r[1]&&r[1]!==location.protocol||r[2]!==location.host);if(e.dataType==="script"&&n==="GET"&&r){var z=s.getElementsByTagName("head")[0]||s.documentElement,C=s.createElement("script");C.src=e.url;if(e.scriptCharset)C.charset=e.scriptCharset;if(!j){var B= -false;C.onload=C.onreadystatechange=function(){if(!B&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){B=true;b();d();C.onload=C.onreadystatechange=null;z&&C.parentNode&&z.removeChild(C)}}}z.insertBefore(C,z.firstChild);return w}var E=false,x=e.xhr();if(x){e.username?x.open(n,e.url,e.async,e.username,e.password):x.open(n,e.url,e.async);try{if(e.data||a&&a.contentType)x.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&x.setRequestHeader("If-Modified-Since", -c.lastModified[e.url]);c.etag[e.url]&&x.setRequestHeader("If-None-Match",c.etag[e.url])}r||x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.setRequestHeader("Accept",e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(ga){}if(e.beforeSend&&e.beforeSend.call(k,x,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");x.abort();return false}e.global&&f("ajaxSend",[x,e]);var g=x.onreadystatechange=function(q){if(!x||x.readyState===0||q==="abort"){E|| -d();E=true;if(x)x.onreadystatechange=c.noop}else if(!E&&x&&(x.readyState===4||q==="timeout")){E=true;x.onreadystatechange=c.noop;i=q==="timeout"?"timeout":!c.httpSuccess(x)?"error":e.ifModified&&c.httpNotModified(x,e.url)?"notmodified":"success";var p;if(i==="success")try{o=c.httpData(x,e.dataType,e)}catch(v){i="parsererror";p=v}if(i==="success"||i==="notmodified")j||b();else c.handleError(e,x,i,p);d();q==="timeout"&&x.abort();if(e.async)x=null}};try{var h=x.abort;x.abort=function(){x&&h.call(x); -g("abort")}}catch(l){}e.async&&e.timeout>0&&setTimeout(function(){x&&!E&&g("timeout")},e.timeout);try{x.send(n==="POST"||n==="PUT"||n==="DELETE"?e.data:null)}catch(m){c.handleError(e,x,null,m);d()}e.async||g();return x}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status=== -1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b=== -"json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(i,o){if(c.isArray(o))c.each(o,function(k,n){b||/\[\]$/.test(i)?f(i,n):d(i+"["+(typeof n==="object"||c.isArray(n)?k:"")+"]",n)});else!b&&o!=null&&typeof o==="object"?c.each(o,function(k,n){d(i+"["+k+"]",n)}):f(i,o)}function f(i,o){o=c.isFunction(o)?o():o;e[e.length]=encodeURIComponent(i)+"="+encodeURIComponent(o)}var e=[];if(b===w)b=c.ajaxSettings.traditional; -if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var j in a)d(j,a[j]);return e.join("&").replace(yb,"+")}});var la={},Ab=/toggle|show|hide/,Bb=/^([+-]=)?([\d+-.]+)(.*)$/,W,va=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();la[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration=== -"number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]|| -c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(j){return e.step(j)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start; -this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now= -this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem, -e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b
"; -a.insertBefore(b,a.firstChild);d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==j;a.removeChild(b); -c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),j=parseInt(c.curCSS(a,"top",true),10)||0,i=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a, -d,e);d={top:b.top-e.top+j,left:b.left-e.left+i};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top- -f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||s.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],j;if(!e)return null;if(f!==w)return this.each(function(){if(j=wa(this))j.scrollTo(!a?f:c(j).scrollLeft(),a?f:c(j).scrollTop());else this[d]=f});else return(j=wa(e))?"pageXOffset"in j?j[a?"pageYOffset": -"pageXOffset"]:c.support.boxModel&&j.document.documentElement[d]||j.document.body[d]:e[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(j){var i=c(this);i[d](f.call(this,j,i[d]()))});return"scrollTo"in -e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===w?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});A.jQuery=A.$=c})(window); +(function(a,b){function cg(a){return d.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cd(a){if(!bZ[a]){var b=d("<"+a+">").appendTo("body"),c=b.css("display");b.remove();if(c==="none"||c==="")c="block";bZ[a]=c}return bZ[a]}function cc(a,b){var c={};d.each(cb.concat.apply([],cb.slice(0,b)),function(){c[this]=a});return c}function bY(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function bX(){try{return new a.XMLHttpRequest}catch(b){}}function bW(){d(a).unload(function(){for(var a in bU)bU[a](0,1)})}function bQ(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var e=a.dataTypes,f={},g,h,i=e.length,j,k=e[0],l,m,n,o,p;for(g=1;g=0===c})}function N(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function F(a,b){return(a&&a!=="*"?a+".":"")+b.replace(r,"`").replace(s,"&")}function E(a){var b,c,e,f,g,h,i,j,k,l,m,n,o,q=[],r=[],s=d._data(this,"events");if(a.liveFired!==this&&s&&s.live&&!a.target.disabled&&(!a.button||a.type!=="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)")),a.liveFired=this;var t=s.live.slice(0);for(i=0;ic)break;a.currentTarget=f.elem,a.data=f.handleObj.data,a.handleObj=f.handleObj,o=f.handleObj.origHandler.apply(f.elem,arguments);if(o===!1||a.isPropagationStopped()){c=f.level,o===!1&&(b=!1);if(a.isImmediatePropagationStopped())break}}return b}}function C(a,c,e){var f=d.extend({},e[0]);f.type=a,f.originalEvent={},f.liveFired=b,d.event.handle.call(c,f),f.isDefaultPrevented()&&e[0].preventDefault()}function w(){return!0}function v(){return!1}function g(a){for(var b in a)if(b!=="toJSON")return!1;return!0}function f(a,c,f){if(f===b&&a.nodeType===1){f=a.getAttribute("data-"+c);if(typeof f==="string"){try{f=f==="true"?!0:f==="false"?!1:f==="null"?null:d.isNaN(f)?e.test(f)?d.parseJSON(f):f:parseFloat(f)}catch(g){}d.data(a,c,f)}else f=b}return f}var c=a.document,d=function(){function I(){if(!d.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(I,1);return}d.ready()}}var d=function(a,b){return new d.fn.init(a,b,g)},e=a.jQuery,f=a.$,g,h=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]+)$)/,i=/\S/,j=/^\s+/,k=/\s+$/,l=/\d/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=navigator.userAgent,w,x=!1,y,z="then done fail isResolved isRejected promise".split(" "),A,B=Object.prototype.toString,C=Object.prototype.hasOwnProperty,D=Array.prototype.push,E=Array.prototype.slice,F=String.prototype.trim,G=Array.prototype.indexOf,H={};d.fn=d.prototype={constructor:d,init:function(a,e,f){var g,i,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!e&&c.body){this.context=c,this[0]=c.body,this.selector="body",this.length=1;return this}if(typeof a==="string"){g=h.exec(a);if(!g||!g[1]&&e)return!e||e.jquery?(e||f).find(a):this.constructor(e).find(a);if(g[1]){e=e instanceof d?e[0]:e,k=e?e.ownerDocument||e:c,j=m.exec(a),j?d.isPlainObject(e)?(a=[c.createElement(j[1])],d.fn.attr.call(a,e,!0)):a=[k.createElement(j[1])]:(j=d.buildFragment([g[1]],[k]),a=(j.cacheable?d.clone(j.fragment):j.fragment).childNodes);return d.merge(this,a)}i=c.getElementById(g[2]);if(i&&i.parentNode){if(i.id!==g[2])return f.find(a);this.length=1,this[0]=i}this.context=c,this.selector=a;return this}if(d.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return d.makeArray(a,this)},selector:"",jquery:"1.5.1",length:0,size:function(){return this.length},toArray:function(){return E.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var e=this.constructor();d.isArray(a)?D.apply(e,a):d.merge(e,a),e.prevObject=this,e.context=this.context,b==="find"?e.selector=this.selector+(this.selector?" ":"")+c:b&&(e.selector=this.selector+"."+b+"("+c+")");return e},each:function(a,b){return d.each(this,a,b)},ready:function(a){d.bindReady(),y.done(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(E.apply(this,arguments),"slice",E.call(arguments).join(","))},map:function(a){return this.pushStack(d.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:D,sort:[].sort,splice:[].splice},d.fn.init.prototype=d.fn,d.extend=d.fn.extend=function(){var a,c,e,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i==="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!=="object"&&!d.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;y.resolveWith(c,[d]),d.fn.trigger&&d(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!x){x=!0;if(c.readyState==="complete")return setTimeout(d.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",A,!1),a.addEventListener("load",d.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",A),a.attachEvent("onload",d.ready);var b=!1;try{b=a.frameElement==null}catch(e){}c.documentElement.doScroll&&b&&I()}}},isFunction:function(a){return d.type(a)==="function"},isArray:Array.isArray||function(a){return d.type(a)==="array"},isWindow:function(a){return a&&typeof a==="object"&&"setInterval"in a},isNaN:function(a){return a==null||!l.test(a)||isNaN(a)},type:function(a){return a==null?String(a):H[B.call(a)]||"object"},isPlainObject:function(a){if(!a||d.type(a)!=="object"||a.nodeType||d.isWindow(a))return!1;if(a.constructor&&!C.call(a,"constructor")&&!C.call(a.constructor.prototype,"isPrototypeOf"))return!1;var c;for(c in a){}return c===b||C.call(a,c)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!=="string"||!b)return null;b=d.trim(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return a.JSON&&a.JSON.parse?a.JSON.parse(b):(new Function("return "+b))();d.error("Invalid JSON: "+b)},parseXML:function(b,c,e){a.DOMParser?(e=new DOMParser,c=e.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b)),e=c.documentElement,(!e||!e.nodeName||e.nodeName==="parsererror")&&d.error("Invalid XML: "+b);return c},noop:function(){},globalEval:function(a){if(a&&i.test(a)){var b=c.head||c.getElementsByTagName("head")[0]||c.documentElement,e=c.createElement("script");d.support.scriptEval()?e.appendChild(c.createTextNode(a)):e.text=a,b.insertBefore(e,b.firstChild),b.removeChild(e)}},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,e){var f,g=0,h=a.length,i=h===b||d.isFunction(a);if(e){if(i){for(f in a)if(c.apply(a[f],e)===!1)break}else for(;g1){var f=E.call(arguments,0),g=b,h=function(a){return function(b){f[a]=arguments.length>1?E.call(arguments,0):b,--g||c.resolveWith(e,f)}};while(b--)a=f[b],a&&d.isFunction(a.promise)?a.promise().then(h(b),c.reject):--g;g||c.resolveWith(e,f)}else c!==a&&c.resolve(a);return e},uaMatch:function(a){a=a.toLowerCase();var b=r.exec(a)||s.exec(a)||t.exec(a)||a.indexOf("compatible")<0&&u.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}d.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.subclass=this.subclass,a.fn.init=function b(b,c){c&&c instanceof d&&!(c instanceof a)&&(c=a(c));return d.fn.init.call(this,b,c,e)},a.fn.init.prototype=a.fn;var e=a(c);return a},browser:{}}),y=d._Deferred(),d.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){H["[object "+b+"]"]=b.toLowerCase()}),w=d.uaMatch(v),w.browser&&(d.browser[w.browser]=!0,d.browser.version=w.version),d.browser.webkit&&(d.browser.safari=!0),G&&(d.inArray=function(a,b){return G.call(b,a)}),i.test(" ")&&(j=/^[\s\xA0]+/,k=/[\s\xA0]+$/),g=d(c),c.addEventListener?A=function(){c.removeEventListener("DOMContentLoaded",A,!1),d.ready()}:c.attachEvent&&(A=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",A),d.ready())});return d}();(function(){d.support={};var b=c.createElement("div");b.style.display="none",b.innerHTML="
a";var e=b.getElementsByTagName("*"),f=b.getElementsByTagName("a")[0],g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=b.getElementsByTagName("input")[0];if(e&&e.length&&f){d.support={leadingWhitespace:b.firstChild.nodeType===3,tbody:!b.getElementsByTagName("tbody").length,htmlSerialize:!!b.getElementsByTagName("link").length,style:/red/.test(f.getAttribute("style")),hrefNormalized:f.getAttribute("href")==="/a",opacity:/^0.55$/.test(f.style.opacity),cssFloat:!!f.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,deleteExpando:!0,optDisabled:!1,checkClone:!1,noCloneEvent:!0,noCloneChecked:!0,boxModel:null,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableHiddenOffsets:!0},i.checked=!0,d.support.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,d.support.optDisabled=!h.disabled;var j=null;d.support.scriptEval=function(){if(j===null){var b=c.documentElement,e=c.createElement("script"),f="script"+d.now();try{e.appendChild(c.createTextNode("window."+f+"=1;"))}catch(g){}b.insertBefore(e,b.firstChild),a[f]?(j=!0,delete a[f]):j=!1,b.removeChild(e),b=e=f=null}return j};try{delete b.test}catch(k){d.support.deleteExpando=!1}!b.addEventListener&&b.attachEvent&&b.fireEvent&&(b.attachEvent("onclick",function l(){d.support.noCloneEvent=!1,b.detachEvent("onclick",l)}),b.cloneNode(!0).fireEvent("onclick")),b=c.createElement("div"),b.innerHTML="";var m=c.createDocumentFragment();m.appendChild(b.firstChild),d.support.checkClone=m.cloneNode(!0).cloneNode(!0).lastChild.checked,d(function(){var a=c.createElement("div"),b=c.getElementsByTagName("body")[0];if(b){a.style.width=a.style.paddingLeft="1px",b.appendChild(a),d.boxModel=d.support.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,d.support.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="
",d.support.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="
t
";var e=a.getElementsByTagName("td");d.support.reliableHiddenOffsets=e[0].offsetHeight===0,e[0].style.display="",e[1].style.display="none",d.support.reliableHiddenOffsets=d.support.reliableHiddenOffsets&&e[0].offsetHeight===0,a.innerHTML="",b.removeChild(a).style.display="none",a=e=null}});var n=function(a){var b=c.createElement("div");a="on"+a;if(!b.attachEvent)return!0;var d=a in b;d||(b.setAttribute(a,"return;"),d=typeof b[a]==="function"),b=null;return d};d.support.submitBubbles=n("submit"),d.support.changeBubbles=n("change"),b=e=f=null}})();var e=/^(?:\{.*\}|\[.*\])$/;d.extend({cache:{},uuid:0,expando:"jQuery"+(d.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?d.cache[a[d.expando]]:a[d.expando];return!!a&&!g(a)},data:function(a,c,e,f){if(d.acceptData(a)){var g=d.expando,h=typeof c==="string",i,j=a.nodeType,k=j?d.cache:a,l=j?a[d.expando]:a[d.expando]&&d.expando;if((!l||f&&l&&!k[l][g])&&h&&e===b)return;l||(j?a[d.expando]=l=++d.uuid:l=d.expando),k[l]||(k[l]={},j||(k[l].toJSON=d.noop));if(typeof c==="object"||typeof c==="function")f?k[l][g]=d.extend(k[l][g],c):k[l]=d.extend(k[l],c);i=k[l],f&&(i[g]||(i[g]={}),i=i[g]),e!==b&&(i[c]=e);if(c==="events"&&!i[c])return i[g]&&i[g].events;return h?i[c]:i}},removeData:function(b,c,e){if(d.acceptData(b)){var f=d.expando,h=b.nodeType,i=h?d.cache:b,j=h?b[d.expando]:d.expando;if(!i[j])return;if(c){var k=e?i[j][f]:i[j];if(k){delete k[c];if(!g(k))return}}if(e){delete i[j][f];if(!g(i[j]))return}var l=i[j][f];d.support.deleteExpando||i!=a?delete i[j]:i[j]=null,l?(i[j]={},h||(i[j].toJSON=d.noop),i[j][f]=l):h&&(d.support.deleteExpando?delete b[d.expando]:b.removeAttribute?b.removeAttribute(d.expando):b[d.expando]=null)}},_data:function(a,b,c){return d.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=d.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),d.fn.extend({data:function(a,c){var e=null;if(typeof a==="undefined"){if(this.length){e=d.data(this[0]);if(this[0].nodeType===1){var g=this[0].attributes,h;for(var i=0,j=g.length;i-1)return!0;return!1},val:function(a){if(!arguments.length){var c=this[0];if(c){if(d.nodeName(c,"option")){var e=c.attributes.value;return!e||e.specified?c.value:c.text}if(d.nodeName(c,"select")){var f=c.selectedIndex,g=[],h=c.options,i=c.type==="select-one";if(f<0)return null;for(var k=i?f:0,l=i?f+1:h.length;k=0;else if(d.nodeName(this,"select")){var f=d.makeArray(e);d("option",this).each(function(){this.selected=d.inArray(d(this).val(),f)>=0}),f.length||(this.selectedIndex=-1)}else this.value=e}})}}),d.extend({attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,e,f){if(!a||a.nodeType===3||a.nodeType===8||a.nodeType===2)return b;if(f&&c in d.attrFn)return d(a)[c](e);var g=a.nodeType!==1||!d.isXMLDoc(a),h=e!==b;c=g&&d.props[c]||c;if(a.nodeType===1){var i=k.test(c);if(c==="selected"&&!d.support.optSelected){var j=a.parentNode;j&&(j.selectedIndex,j.parentNode&&j.parentNode.selectedIndex)}if((c in a||a[c]!==b)&&g&&!i){h&&(c==="type"&&l.test(a.nodeName)&&a.parentNode&&d.error("type property can't be changed"),e===null?a.nodeType===1&&a.removeAttribute(c):a[c]=e);if(d.nodeName(a,"form")&&a.getAttributeNode(c))return a.getAttributeNode(c).nodeValue;if(c==="tabIndex"){var o=a.getAttributeNode("tabIndex");return o&&o.specified?o.value:m.test(a.nodeName)||n.test(a.nodeName)&&a.href?0:b}return a[c]}if(!d.support.style&&g&&c==="style"){h&&(a.style.cssText=""+e);return a.style.cssText}h&&a.setAttribute(c,""+e);if(!a.attributes[c]&&(a.hasAttribute&&!a.hasAttribute(c)))return b;var p=!d.support.hrefNormalized&&g&&i?a.getAttribute(c,2):a.getAttribute(c);return p===null?b:p}h&&(a[c]=e);return a[c]}});var p=/\.(.*)$/,q=/^(?:textarea|input|select)$/i,r=/\./g,s=/ /g,t=/[^\w\s.|`]/g,u=function(a){return a.replace(t,"\\$&")};d.event={add:function(c,e,f,g){if(c.nodeType!==3&&c.nodeType!==8){try{d.isWindow(c)&&(c!==a&&!c.frameElement)&&(c=a)}catch(h){}if(f===!1)f=v;else if(!f)return;var i,j;f.handler&&(i=f,f=i.handler),f.guid||(f.guid=d.guid++);var k=d._data(c);if(!k)return;var l=k.events,m=k.handle;l||(k.events=l={}),m||(k.handle=m=function(){return typeof d!=="undefined"&&!d.event.triggered?d.event.handle.apply(m.elem,arguments):b}),m.elem=c,e=e.split(" ");var n,o=0,p;while(n=e[o++]){j=i?d.extend({},i):{handler:f,data:g},n.indexOf(".")>-1?(p=n.split("."),n=p.shift(),j.namespace=p.slice(0).sort().join(".")):(p=[],j.namespace=""),j.type=n,j.guid||(j.guid=f.guid);var q=l[n],r=d.event.special[n]||{};if(!q){q=l[n]=[];if(!r.setup||r.setup.call(c,g,p,m)===!1)c.addEventListener?c.addEventListener(n,m,!1):c.attachEvent&&c.attachEvent("on"+n,m)}r.add&&(r.add.call(c,j),j.handler.guid||(j.handler.guid=f.guid)),q.push(j),d.event.global[n]=!0}c=null}},global:{},remove:function(a,c,e,f){if(a.nodeType!==3&&a.nodeType!==8){e===!1&&(e=v);var g,h,i,j,k=0,l,m,n,o,p,q,r,s=d.hasData(a)&&d._data(a),t=s&&s.events;if(!s||!t)return;c&&c.type&&(e=c.handler,c=c.type);if(!c||typeof c==="string"&&c.charAt(0)==="."){c=c||"";for(h in t)d.event.remove(a,h+c);return}c=c.split(" ");while(h=c[k++]){r=h,q=null,l=h.indexOf(".")<0,m=[],l||(m=h.split("."),h=m.shift(),n=new RegExp("(^|\\.)"+d.map(m.slice(0).sort(),u).join("\\.(?:.*\\.)?")+"(\\.|$)")),p=t[h];if(!p)continue;if(!e){for(j=0;j=0&&(a.type=f=f.slice(0,-1),a.exclusive=!0),e||(a.stopPropagation(),d.event.global[f]&&d.each(d.cache,function(){var b=d.expando,e=this[b];e&&e.events&&e.events[f]&&d.event.trigger(a,c,e.handle.elem)}));if(!e||e.nodeType===3||e.nodeType===8)return b;a.result=b,a.target=e,c=d.makeArray(c),c.unshift(a)}a.currentTarget=e;var h=d._data(e,"handle");h&&h.apply(e,c);var i=e.parentNode||e.ownerDocument;try{e&&e.nodeName&&d.noData[e.nodeName.toLowerCase()]||e["on"+f]&&e["on"+f].apply(e,c)===!1&&(a.result=!1,a.preventDefault())}catch(j){}if(!a.isPropagationStopped()&&i)d.event.trigger(a,c,i,!0);else if(!a.isDefaultPrevented()){var k,l=a.target,m=f.replace(p,""),n=d.nodeName(l,"a")&&m==="click",o=d.event.special[m]||{};if((!o._default||o._default.call(e,a)===!1)&&!n&&!(l&&l.nodeName&&d.noData[l.nodeName.toLowerCase()])){try{l[m]&&(k=l["on"+m],k&&(l["on"+m]=null),d.event.triggered=!0,l[m]())}catch(q){}k&&(l["on"+m]=k),d.event.triggered=!1}}},handle:function(c){var e,f,g,h,i,j=[],k=d.makeArray(arguments);c=k[0]=d.event.fix(c||a.event),c.currentTarget=this,e=c.type.indexOf(".")<0&&!c.exclusive,e||(g=c.type.split("."),c.type=g.shift(),j=g.slice(0).sort(),h=new RegExp("(^|\\.)"+j.join("\\.(?:.*\\.)?")+"(\\.|$)")),c.namespace=c.namespace||j.join("."),i=d._data(this,"events"),f=(i||{})[c.type];if(i&&f){f=f.slice(0);for(var l=0,m=f.length;l-1?d.map(a.options,function(a){return a.selected}).join("-"):"":a.nodeName.toLowerCase()==="select"&&(c=a.selectedIndex);return c},B=function B(a){var c=a.target,e,f;if(q.test(c.nodeName)&&!c.readOnly){e=d._data(c,"_change_data"),f=A(c),(a.type!=="focusout"||c.type!=="radio")&&d._data(c,"_change_data",f);if(e===b||f===e)return;if(e!=null||f)a.type="change",a.liveFired=b,d.event.trigger(a,arguments[1],c)}};d.event.special.change={filters:{focusout:B,beforedeactivate:B,click:function(a){var b=a.target,c=b.type;(c==="radio"||c==="checkbox"||b.nodeName.toLowerCase()==="select")&&B.call(this,a)},keydown:function(a){var b=a.target,c=b.type;(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(c==="checkbox"||c==="radio")||c==="select-multiple")&&B.call(this,a)},beforeactivate:function(a){var b=a.target;d._data(b,"_change_data",A(b))}},setup:function(a,b){if(this.type==="file")return!1;for(var c in z)d.event.add(this,c+".specialChange",z[c]);return q.test(this.nodeName)},teardown:function(a){d.event.remove(this,".specialChange");return q.test(this.nodeName)}},z=d.event.special.change.filters,z.focus=z.beforeactivate}c.addEventListener&&d.each({focus:"focusin",blur:"focusout"},function(a,b){function c(a){a=d.event.fix(a),a.type=b;return d.event.handle.call(this,a)}d.event.special[b]={setup:function(){this.addEventListener(a,c,!0)},teardown:function(){this.removeEventListener(a,c,!0)}}}),d.each(["bind","one"],function(a,c){d.fn[c]=function(a,e,f){if(typeof a==="object"){for(var g in a)this[c](g,e,a[g],f);return this}if(d.isFunction(e)||e===!1)f=e,e=b;var h=c==="one"?d.proxy(f,function(a){d(this).unbind(a,h);return f.apply(this,arguments)}):f;if(a==="unload"&&c!=="one")this.one(a,e,f);else for(var i=0,j=this.length;i0?this.bind(b,a,c):this.trigger(b)},d.attrFn&&(d.attrFn[b]=!0)}),function(){function u(a,b,c,d,e,f){for(var g=0,h=d.length;g0){j=i;break}}i=i[a]}d[g]=j}}}function t(a,b,c,d,e,f){for(var g=0,h=d.length;g+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,e=0,f=Object.prototype.toString,g=!1,h=!0,i=/\\/g,j=/\W/;[0,0].sort(function(){h=!1;return 0});var k=function(b,d,e,g){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!=="string")return e;var i,j,n,o,q,r,s,t,u=!0,w=k.isXML(d),x=[],y=b;do{a.exec(""),i=a.exec(y);if(i){y=i[3],x.push(i[1]);if(i[2]){o=i[3];break}}}while(i);if(x.length>1&&m.exec(b))if(x.length===2&&l.relative[x[0]])j=v(x[0]+x[1],d);else{j=l.relative[x[0]]?[d]:k(x.shift(),d);while(x.length)b=x.shift(),l.relative[b]&&(b+=x.shift()),j=v(b,j)}else{!g&&x.length>1&&d.nodeType===9&&!w&&l.match.ID.test(x[0])&&!l.match.ID.test(x[x.length-1])&&(q=k.find(x.shift(),d,w),d=q.expr?k.filter(q.expr,q.set)[0]:q.set[0]);if(d){q=g?{expr:x.pop(),set:p(g)}:k.find(x.pop(),x.length===1&&(x[0]==="~"||x[0]==="+")&&d.parentNode?d.parentNode:d,w),j=q.expr?k.filter(q.expr,q.set):q.set,x.length>0?n=p(j):u=!1;while(x.length)r=x.pop(),s=r,l.relative[r]?s=x.pop():r="",s==null&&(s=d),l.relative[r](n,s,w)}else n=x=[]}n||(n=j),n||k.error(r||b);if(f.call(n)==="[object Array]")if(u)if(d&&d.nodeType===1)for(t=0;n[t]!=null;t++)n[t]&&(n[t]===!0||n[t].nodeType===1&&k.contains(d,n[t]))&&e.push(j[t]);else for(t=0;n[t]!=null;t++)n[t]&&n[t].nodeType===1&&e.push(j[t]);else e.push.apply(e,n);else p(n,e);o&&(k(o,h,e,g),k.uniqueSort(e));return e};k.uniqueSort=function(a){if(r){g=h,a.sort(r);if(g)for(var b=1;b0},k.find=function(a,b,c){var d;if(!a)return[];for(var e=0,f=l.order.length;e":function(a,b){var c,d=typeof b==="string",e=0,f=a.length;if(d&&!j.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(i,"")},TAG:function(a,b){return a[1].replace(i,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||k.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&k.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(i,"");!f&&l.attrMap[g]&&(a[1]=l.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(i,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=k(b[3],null,null,c);else{var g=k.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(l.match.POS.test(b[0])||l.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!k(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){return"text"===a.getAttribute("type")},radio:function(a){return"radio"===a.type},checkbox:function(a){return"checkbox"===a.type},file:function(a){return"file"===a.type},password:function(a){return"password"===a.type},submit:function(a){return"submit"===a.type},image:function(a){return"image"===a.type},reset:function(a){return"reset"===a.type},button:function(a){return"button"===a.type||a.nodeName.toLowerCase()==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=l.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||k.getText([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=l.attrHandle[c]?l.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=l.setFilters[e];if(f)return f(a,c,b,d)}}},m=l.match.POS,n=function(a,b){return"\\"+(b-0+1)};for(var o in l.match)l.match[o]=new RegExp(l.match[o].source+/(?![^\[]*\])(?![^\(]*\))/.source),l.leftMatch[o]=new RegExp(/(^(?:.|\r|\n)*?)/.source+l.match[o].source.replace(/\\(\d+)/g,n));var p=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(q){p=function(a,b){var c=0,d=b||[];if(f.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length==="number")for(var e=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(l.find.ID=function(a,c,d){if(typeof c.getElementById!=="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!=="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},l.filter.ID=function(a,b){var c=typeof a.getAttributeNode!=="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(l.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!=="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(l.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=k,b=c.createElement("div"),d="__sizzle__";b.innerHTML="

";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){k=function(b,e,f,g){e=e||c;if(!g&&!k.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return p(e.getElementsByTagName(b),f);if(h[2]&&l.find.CLASS&&e.getElementsByClassName)return p(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return p([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return p([],f);if(i.id===h[3])return p([i],f)}try{return p(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var m=e,n=e.getAttribute("id"),o=n||d,q=e.parentNode,r=/^\s*[+~]/.test(b);n?o=o.replace(/'/g,"\\$&"):e.setAttribute("id",o),r&&q&&(e=e.parentNode);try{if(!r||q)return p(e.querySelectorAll("[id='"+o+"'] "+b),f)}catch(s){}finally{n||m.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)k[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector,d=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(e){d=!0}b&&(k.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!k.isXML(a))try{if(d||!l.match.PSEUDO.test(c)&&!/!=/.test(c))return b.call(a,c)}catch(e){}return k(c,null,null,[a]).length>0})}(),function(){var a=c.createElement("div");a.innerHTML="
";if(a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;l.order.splice(1,0,"CLASS"),l.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!=="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?k.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?k.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:k.contains=function(){return!1},k.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var v=function(a,b){var c,d=[],e="",f=b.nodeType?[b]:b;while(c=l.match.PSEUDO.exec(a))e+=c[0],a=a.replace(l.match.PSEUDO,"");a=l.relative[a]?a+"*":a;for(var g=0,h=f.length;g0)for(var g=c;g0},closest:function(a,b){var c=[],e,f,g=this[0];if(d.isArray(a)){var h,i,j={},k=1;if(g&&a.length){for(e=0,f=a.length;e-1:d(g).is(h))&&c.push({selector:i,elem:g,level:k});g=g.parentNode,k++}}return c}var l=L.test(a)?d(a,b||this.context):null;for(e=0,f=this.length;e-1:d.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b)break}}c=c.length>1?d.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a||typeof a==="string")return d.inArray(this[0],a?d(a):this.parent().children());return d.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a==="string"?d(a,b):d.makeArray(a),e=d.merge(this.get(),c);return this.pushStack(N(c[0])||N(e[0])?e:d.unique(e))},andSelf:function(){return this.add(this.prevObject)}}),d.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return d.dir(a,"parentNode")},parentsUntil:function(a,b,c){return d.dir(a,"parentNode",c)},next:function(a){return d.nth(a,2,"nextSibling")},prev:function(a){return d.nth(a,2,"previousSibling")},nextAll:function(a){return d.dir(a,"nextSibling")},prevAll:function(a){return d.dir(a,"previousSibling")},nextUntil:function(a,b,c){return d.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return d.dir(a,"previousSibling",c)},siblings:function(a){return d.sibling(a.parentNode.firstChild,a)},children:function(a){return d.sibling(a.firstChild)},contents:function(a){return d.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:d.makeArray(a.childNodes)}},function(a,b){d.fn[a]=function(c,e){var f=d.map(this,b,c),g=K.call(arguments);G.test(a)||(e=c),e&&typeof e==="string"&&(f=d.filter(e,f)),f=this.length>1&&!M[a]?d.unique(f):f,(this.length>1||I.test(e))&&H.test(a)&&(f=f.reverse());return this.pushStack(f,a,g.join(","))}}),d.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?d.find.matchesSelector(b[0],a)?[b[0]]:[]:d.find.matches(a,b)},dir:function(a,c,e){var f=[],g=a[c];while(g&&g.nodeType!==9&&(e===b||g.nodeType!==1||!d(g).is(e)))g.nodeType===1&&f.push(g),g=g[c];return f},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var P=/ jQuery\d+="(?:\d+|null)"/g,Q=/^\s+/,R=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,S=/<([\w:]+)/,T=/",""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]};X.optgroup=X.option,X.tbody=X.tfoot=X.colgroup=X.caption=X.thead,X.th=X.td,d.support.htmlSerialize||(X._default=[1,"div
","
"]),d.fn.extend({text:function(a){if(d.isFunction(a))return this.each(function(b){var c=d(this);c.text(a.call(this,b,c.text()))});if(typeof a!=="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return d.text(this)},wrapAll:function(a){if(d.isFunction(a))return this.each(function(b){d(this).wrapAll(a.call(this,b))});if(this[0]){var b=d(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(d.isFunction(a))return this.each(function(b){d(this).wrapInner(a.call(this,b))});return this.each(function(){var b=d(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){d(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){d.nodeName(this,"body")||d(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=d(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,d(arguments[0]).toArray());return a}},remove:function(a,b){for(var c=0,e;(e=this[c])!=null;c++)if(!a||d.filter(a,[e]).length)!b&&e.nodeType===1&&(d.cleanData(e.getElementsByTagName("*")),d.cleanData([e])),e.parentNode&&e.parentNode.removeChild(e);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&d.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return d.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(P,""):null;if(typeof a!=="string"||V.test(a)||!d.support.leadingWhitespace&&Q.test(a)||X[(S.exec(a)||["",""])[1].toLowerCase()])d.isFunction(a)?this.each(function(b){var c=d(this);c.html(a.call(this,b,c.html()))}):this.empty().append(a);else{a=a.replace(R,"<$1>");try{for(var c=0,e=this.length;c1&&l0?this.clone(!0):this).get();d(f[h])[b](j),e=e.concat(j)}return this.pushStack(e,a,f.selector)}}),d.extend({clone:function(a,b,c){var e=a.cloneNode(!0),f,g,h;if((!d.support.noCloneEvent||!d.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!d.isXMLDoc(a)){$(a,e),f=_(a),g=_(e);for(h=0;f[h];++h)$(f[h],g[h])}if(b){Z(a,e);if(c){f=_(a),g=_(e);for(h=0;f[h];++h)Z(f[h],g[h])}}return e},clean:function(a,b,e,f){b=b||c,typeof b.createElement==="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var g=[];for(var h=0,i;(i=a[h])!=null;h++){typeof i==="number"&&(i+="");if(!i)continue;if(typeof i!=="string"||U.test(i)){if(typeof i==="string"){i=i.replace(R,"<$1>");var j=(S.exec(i)||["",""])[1].toLowerCase(),k=X[j]||X._default,l=k[0],m=b.createElement("div");m.innerHTML=k[1]+i+k[2];while(l--)m=m.lastChild;if(!d.support.tbody){var n=T.test(i),o=j==="table"&&!n?m.firstChild&&m.firstChild.childNodes:k[1]===""&&!n?m.childNodes:[];for(var p=o.length-1;p>=0;--p)d.nodeName(o[p],"tbody")&&!o[p].childNodes.length&&o[p].parentNode.removeChild(o[p])}!d.support.leadingWhitespace&&Q.test(i)&&m.insertBefore(b.createTextNode(Q.exec(i)[0]),m.firstChild),i=m.childNodes}}else i=b.createTextNode(i);i.nodeType?g.push(i):g=d.merge(g,i)}if(e)for(h=0;g[h];h++)!f||!d.nodeName(g[h],"script")||g[h].type&&g[h].type.toLowerCase()!=="text/javascript"?(g[h].nodeType===1&&g.splice.apply(g,[h+1,0].concat(d.makeArray(g[h].getElementsByTagName("script")))),e.appendChild(g[h])):f.push(g[h].parentNode?g[h].parentNode.removeChild(g[h]):g[h]);return g},cleanData:function(a){var b,c,e=d.cache,f=d.expando,g=d.event.special,h=d.support.deleteExpando;for(var i=0,j;(j=a[i])!=null;i++){if(j.nodeName&&d.noData[j.nodeName.toLowerCase()])continue;c=j[d.expando];if(c){b=e[c]&&e[c][f];if(b&&b.events){for(var k in b.events)g[k]?d.event.remove(j,k):d.removeEvent(j,k,b.handle);b.handle&&(b.handle.elem=null)}h?delete j[d.expando]:j.removeAttribute&&j.removeAttribute(d.expando),delete e[c]}}}});var bb=/alpha\([^)]*\)/i,bc=/opacity=([^)]*)/,bd=/-([a-z])/ig,be=/([A-Z])/g,bf=/^-?\d+(?:px)?$/i,bg=/^-?\d/,bh={position:"absolute",visibility:"hidden",display:"block"},bi=["Left","Right"],bj=["Top","Bottom"],bk,bl,bm,bn=function(a,b){return b.toUpperCase()};d.fn.css=function(a,c){if(arguments.length===2&&c===b)return this;return d.access(this,a,c,!0,function(a,c,e){return e!==b?d.style(a,c,e):d.css(a,c)})},d.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bk(a,"opacity","opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{zIndex:!0,fontWeight:!0,opacity:!0,zoom:!0,lineHeight:!0},cssProps:{"float":d.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,e,f){if(a&&a.nodeType!==3&&a.nodeType!==8&&a.style){var g,h=d.camelCase(c),i=a.style,j=d.cssHooks[h];c=d.cssProps[h]||h;if(e===b){if(j&&"get"in j&&(g=j.get(a,!1,f))!==b)return g;return i[c]}if(typeof e==="number"&&isNaN(e)||e==null)return;typeof e==="number"&&!d.cssNumber[h]&&(e+="px");if(!j||!("set"in j)||(e=j.set(a,e))!==b)try{i[c]=e}catch(k){}}},css:function(a,c,e){var f,g=d.camelCase(c),h=d.cssHooks[g];c=d.cssProps[g]||g;if(h&&"get"in h&&(f=h.get(a,!0,e))!==b)return f;if(bk)return bk(a,c,g)},swap:function(a,b,c){var d={};for(var e in b)d[e]=a.style[e],a.style[e]=b[e];c.call(a);for(e in b)a.style[e]=d[e]},camelCase:function(a){return a.replace(bd,bn)}}),d.curCSS=d.css,d.each(["height","width"],function(a,b){d.cssHooks[b]={get:function(a,c,e){var f;if(c){a.offsetWidth!==0?f=bo(a,b,e):d.swap(a,bh,function(){f=bo(a,b,e)});if(f<=0){f=bk(a,b,b),f==="0px"&&bm&&(f=bm(a,b,b));if(f!=null)return f===""||f==="auto"?"0px":f}if(f<0||f==null){f=a.style[b];return f===""||f==="auto"?"0px":f}return typeof f==="string"?f:f+"px"}},set:function(a,b){if(!bf.test(b))return b;b=parseFloat(b);if(b>=0)return b+"px"}}}),d.support.opacity||(d.cssHooks.opacity={get:function(a,b){return bc.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style;c.zoom=1;var e=d.isNaN(b)?"":"alpha(opacity="+b*100+")",f=c.filter||"";c.filter=bb.test(f)?f.replace(bb,e):c.filter+" "+e}}),c.defaultView&&c.defaultView.getComputedStyle&&(bl=function(a,c,e){var f,g,h;e=e.replace(be,"-$1").toLowerCase();if(!(g=a.ownerDocument.defaultView))return b;if(h=g.getComputedStyle(a,null))f=h.getPropertyValue(e),f===""&&!d.contains(a.ownerDocument.documentElement,a)&&(f=d.style(a,e));return f}),c.documentElement.currentStyle&&(bm=function(a,b){var c,d=a.currentStyle&&a.currentStyle[b],e=a.runtimeStyle&&a.runtimeStyle[b],f=a.style;!bf.test(d)&&bg.test(d)&&(c=f.left,e&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":d||0,d=f.pixelLeft+"px",f.left=c,e&&(a.runtimeStyle.left=e));return d===""?"auto":d}),bk=bl||bm,d.expr&&d.expr.filters&&(d.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!d.support.reliableHiddenOffsets&&(a.style.display||d.css(a,"display"))==="none"},d.expr.filters.visible=function(a){return!d.expr.filters.hidden(a)});var bp=/%20/g,bq=/\[\]$/,br=/\r?\n/g,bs=/#.*$/,bt=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bu=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bv=/(?:^file|^widget|\-extension):$/,bw=/^(?:GET|HEAD)$/,bx=/^\/\//,by=/\?/,bz=/)<[^<]*)*<\/script>/gi,bA=/^(?:select|textarea)/i,bB=/\s+/,bC=/([?&])_=[^&]*/,bD=/(^|\-)([a-z])/g,bE=function(a,b,c){return b+c.toUpperCase()},bF=/^([\w\+\.\-]+:)\/\/([^\/?#:]*)(?::(\d+))?/,bG=d.fn.load,bH={},bI={},bJ,bK;try{bJ=c.location.href}catch(bL){bJ=c.createElement("a"),bJ.href="",bJ=bJ.href}bK=bF.exec(bJ.toLowerCase()),d.fn.extend({load:function(a,c,e){if(typeof a!=="string"&&bG)return bG.apply(this,arguments);if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var g=a.slice(f,a.length);a=a.slice(0,f)}var h="GET";c&&(d.isFunction(c)?(e=c,c=b):typeof c==="object"&&(c=d.param(c,d.ajaxSettings.traditional),h="POST"));var i=this;d.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?d("
").append(c.replace(bz,"")).find(g):c)),e&&i.each(e,[c,b,a])}});return this},serialize:function(){return d.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?d.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bA.test(this.nodeName)||bu.test(this.type))}).map(function(a,b){var c=d(this).val();return c==null?null:d.isArray(c)?d.map(c,function(a,c){return{name:b.name,value:a.replace(br,"\r\n")}}):{name:b.name,value:c.replace(br,"\r\n")}}).get()}}),d.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){d.fn[b]=function(a){return this.bind(b,a)}}),d.each(["get","post"],function(a,c){d[c]=function(a,e,f,g){d.isFunction(e)&&(g=g||f,f=e,e=b);return d.ajax({type:c,url:a,data:e,success:f,dataType:g})}}),d.extend({getScript:function(a,c){return d.get(a,b,c,"script")},getJSON:function(a,b,c){return d.get(a,b,c,"json")},ajaxSetup:function(a,b){b?d.extend(!0,a,d.ajaxSettings,b):(b=a,a=d.extend(!0,d.ajaxSettings,b));for(var c in {context:1,url:1})c in b?a[c]=b[c]:c in d.ajaxSettings&&(a[c]=d.ajaxSettings[c]);return a},ajaxSettings:{url:bJ,isLocal:bv.test(bK[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":"*/*"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":d.parseJSON,"text xml":d.parseXML}},ajaxPrefilter:bM(bH),ajaxTransport:bM(bI),ajax:function(a,c){function v(a,c,l,n){if(r!==2){r=2,p&&clearTimeout(p),o=b,m=n||"",u.readyState=a?4:0;var q,t,v,w=l?bP(e,u,l):b,x,y;if(a>=200&&a<300||a===304){if(e.ifModified){if(x=u.getResponseHeader("Last-Modified"))d.lastModified[k]=x;if(y=u.getResponseHeader("Etag"))d.etag[k]=y}if(a===304)c="notmodified",q=!0;else try{t=bQ(e,w),c="success",q=!0}catch(z){c="parsererror",v=z}}else{v=c;if(!c||a)c="error",a<0&&(a=0)}u.status=a,u.statusText=c,q?h.resolveWith(f,[t,c,u]):h.rejectWith(f,[u,c,v]),u.statusCode(j),j=b,s&&g.trigger("ajax"+(q?"Success":"Error"),[u,e,q?t:v]),i.resolveWith(f,[u,c]),s&&(g.trigger("ajaxComplete",[u,e]),--d.active||d.event.trigger("ajaxStop"))}}typeof a==="object"&&(c=a,a=b),c=c||{};var e=d.ajaxSetup({},c),f=e.context||e,g=f!==e&&(f.nodeType||f instanceof d)?d(f):d.event,h=d.Deferred(),i=d._Deferred(),j=e.statusCode||{},k,l={},m,n,o,p,q,r=0,s,t,u={readyState:0,setRequestHeader:function(a,b){r||(l[a.toLowerCase().replace(bD,bE)]=b);return this},getAllResponseHeaders:function(){return r===2?m:null},getResponseHeader:function(a){var c;if(r===2){if(!n){n={};while(c=bt.exec(m))n[c[1].toLowerCase()]=c[2]}c=n[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){r||(e.mimeType=a);return this},abort:function(a){a=a||"abort",o&&o.abort(a),v(0,a);return this}};h.promise(u),u.success=u.done,u.error=u.fail,u.complete=i.done,u.statusCode=function(a){if(a){var b;if(r<2)for(b in a)j[b]=[j[b],a[b]];else b=a[u.status],u.then(b,b)}return this},e.url=((a||e.url)+"").replace(bs,"").replace(bx,bK[1]+"//"),e.dataTypes=d.trim(e.dataType||"*").toLowerCase().split(bB),e.crossDomain||(q=bF.exec(e.url.toLowerCase()),e.crossDomain=q&&(q[1]!=bK[1]||q[2]!=bK[2]||(q[3]||(q[1]==="http:"?80:443))!=(bK[3]||(bK[1]==="http:"?80:443)))),e.data&&e.processData&&typeof e.data!=="string"&&(e.data=d.param(e.data,e.traditional)),bN(bH,e,c,u);if(r===2)return!1;s=e.global,e.type=e.type.toUpperCase(),e.hasContent=!bw.test(e.type),s&&d.active++===0&&d.event.trigger("ajaxStart");if(!e.hasContent){e.data&&(e.url+=(by.test(e.url)?"&":"?")+e.data),k=e.url;if(e.cache===!1){var w=d.now(),x=e.url.replace(bC,"$1_="+w);e.url=x+(x===e.url?(by.test(e.url)?"&":"?")+"_="+w:"")}}if(e.data&&e.hasContent&&e.contentType!==!1||c.contentType)l["Content-Type"]=e.contentType;e.ifModified&&(k=k||e.url,d.lastModified[k]&&(l["If-Modified-Since"]=d.lastModified[k]),d.etag[k]&&(l["If-None-Match"]=d.etag[k])),l.Accept=e.dataTypes[0]&&e.accepts[e.dataTypes[0]]?e.accepts[e.dataTypes[0]]+(e.dataTypes[0]!=="*"?", */*; q=0.01":""):e.accepts["*"];for(t in e.headers)u.setRequestHeader(t,e.headers[t]);if(e.beforeSend&&(e.beforeSend.call(f,u,e)===!1||r===2)){u.abort();return!1}for(t in {success:1,error:1,complete:1})u[t](e[t]);o=bN(bI,e,c,u);if(o){u.readyState=1,s&&g.trigger("ajaxSend",[u,e]),e.async&&e.timeout>0&&(p=setTimeout(function(){u.abort("timeout")},e.timeout));try{r=1,o.send(l,v)}catch(y){status<2?v(-1,y):d.error(y)}}else v(-1,"No Transport");return u},param:function(a,c){var e=[],f=function(a,b){b=d.isFunction(b)?b():b,e[e.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=d.ajaxSettings.traditional);if(d.isArray(a)||a.jquery&&!d.isPlainObject(a))d.each(a,function(){f(this.name,this.value)});else for(var g in a)bO(g,a[g],c,f);return e.join("&").replace(bp,"+")}}),d.extend({active:0,lastModified:{},etag:{}});var bR=d.now(),bS=/(\=)\?(&|$)|()\?\?()/i;d.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return d.expando+"_"+bR++}}),d.ajaxPrefilter("json jsonp",function(b,c,e){var f=typeof b.data==="string";if(b.dataTypes[0]==="jsonp"||c.jsonpCallback||c.jsonp!=null||b.jsonp!==!1&&(bS.test(b.url)||f&&bS.test(b.data))){var g,h=b.jsonpCallback=d.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2",m=function(){a[h]=i,g&&d.isFunction(i)&&a[h](g[0])};b.jsonp!==!1&&(j=j.replace(bS,l),b.url===j&&(f&&(k=k.replace(bS,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},e.then(m,m),b.converters["script json"]=function(){g||d.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),d.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){d.globalEval(a);return a}}}),d.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),d.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var bT=d.now(),bU,bV;d.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&bX()||bY()}:bX,bV=d.ajaxSettings.xhr(),d.support.ajax=!!bV,d.support.cors=bV&&"withCredentials"in bV,bV=b,d.support.ajax&&d.ajaxTransport(function(a){if(!a.crossDomain||d.support.cors){var c;return{send:function(e,f){var g=a.xhr(),h,i;a.username?g.open(a.type,a.url,a.async,a.username,a.password):g.open(a.type,a.url,a.async);if(a.xhrFields)for(i in a.xhrFields)g[i]=a.xhrFields[i];a.mimeType&&g.overrideMimeType&&g.overrideMimeType(a.mimeType),(!a.crossDomain||a.hasContent)&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(i in e)g.setRequestHeader(i,e[i])}catch(j){}g.send(a.hasContent&&a.data||null),c=function(e,i){var j,k,l,m,n;try{if(c&&(i||g.readyState===4)){c=b,h&&(g.onreadystatechange=d.noop,delete bU[h]);if(i)g.readyState!==4&&g.abort();else{j=g.status,l=g.getAllResponseHeaders(),m={},n=g.responseXML,n&&n.documentElement&&(m.xml=n),m.text=g.responseText;try{k=g.statusText}catch(o){k=""}j||!a.isLocal||a.crossDomain?j===1223&&(j=204):j=m.text?200:404}}}catch(p){i||f(-1,p)}m&&f(j,k,m,l)},a.async&&g.readyState!==4?(bU||(bU={},bW()),h=bT++,g.onreadystatechange=bU[h]=c):c()},abort:function(){c&&c(0,1)}}}});var bZ={},b$=/^(?:toggle|show|hide)$/,b_=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,ca,cb=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];d.fn.extend({show:function(a,b,c){var e,f;if(a||a===0)return this.animate(cc("show",3),a,b,c);for(var g=0,h=this.length;g=0;a--)c[a].elem===this&&(b&&c[a](!0),c.splice(a,1))}),b||this.dequeue();return this}}),d.each({slideDown:cc("show",1),slideUp:cc("hide",1),slideToggle:cc("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){d.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),d.extend({speed:function(a,b,c){var e=a&&typeof a==="object"?d.extend({},a):{complete:c||!c&&b||d.isFunction(a)&&a,duration:a,easing:c&&b||b&&!d.isFunction(b)&&b};e.duration=d.fx.off?0:typeof e.duration==="number"?e.duration:e.duration in d.fx.speeds?d.fx.speeds[e.duration]:d.fx.speeds._default,e.old=e.complete,e.complete=function(){e.queue!==!1&&d(this).dequeue(),d.isFunction(e.old)&&e.old.call(this)};return e},easing:{linear:function(a,b,c,d){return c+d*a},swing:function(a,b,c,d){return(-Math.cos(a*Math.PI)/2+.5)*d+c}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig||(b.orig={})}}),d.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(d.fx.step[this.prop]||d.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=d.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,b,c){function g(a){return e.step(a)}var e=this,f=d.fx;this.startTime=d.now(),this.start=a,this.end=b,this.unit=c||this.unit||(d.cssNumber[this.prop]?"":"px"),this.now=this.start,this.pos=this.state=0,g.elem=this.elem,g()&&d.timers.push(g)&&!ca&&(ca=setInterval(f.tick,f.interval))},show:function(){this.options.orig[this.prop]=d.style(this.elem,this.prop),this.options.show=!0,this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),d(this.elem).show()},hide:function(){this.options.orig[this.prop]=d.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b=d.now(),c=!0;if(a||b>=this.options.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),this.options.curAnim[this.prop]=!0;for(var e in this.options.curAnim)this.options.curAnim[e]!==!0&&(c=!1);if(c){if(this.options.overflow!=null&&!d.support.shrinkWrapBlocks){var f=this.elem,g=this.options;d.each(["","X","Y"],function(a,b){f.style["overflow"+b]=g.overflow[a]})}this.options.hide&&d(this.elem).hide();if(this.options.hide||this.options.show)for(var h in this.options.curAnim)d.style(this.elem,h,this.options.orig[h]);this.options.complete.call(this.elem)}return!1}var i=b-this.startTime;this.state=i/this.options.duration;var j=this.options.specialEasing&&this.options.specialEasing[this.prop],k=this.options.easing||(d.easing.swing?"swing":"linear");this.pos=d.easing[j||k](this.state,i,0,1,this.options.duration),this.now=this.start+(this.end-this.start)*this.pos,this.update();return!0}},d.extend(d.fx,{tick:function(){var a=d.timers;for(var b=0;b
";d.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"}),b.innerHTML=j,a.insertBefore(b,a.firstChild),e=b.firstChild,f=e.firstChild,h=e.nextSibling.firstChild.firstChild,this.doesNotAddBorder=f.offsetTop!==5,this.doesAddBorderForTableAndCells=h.offsetTop===5,f.style.position="fixed",f.style.top="20px",this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15,f.style.position=f.style.top="",e.style.overflow="hidden",e.style.position="relative",this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5,this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==i,a.removeChild(b),a=b=e=f=g=h=null,d.offset.initialize=d.noop},bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;d.offset.initialize(),d.offset.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(d.css(a,"marginTop"))||0,c+=parseFloat(d.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var e=d.css(a,"position");e==="static"&&(a.style.position="relative");var f=d(a),g=f.offset(),h=d.css(a,"top"),i=d.css(a,"left"),j=e==="absolute"&&d.inArray("auto",[h,i])>-1,k={},l={},m,n;j&&(l=f.position()),m=j?l.top:parseInt(h,10)||0,n=j?l.left:parseInt(i,10)||0,d.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):f.css(k)}},d.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),e=cf.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(d.css(a,"marginTop"))||0,c.left-=parseFloat(d.css(a,"marginLeft"))||0,e.top+=parseFloat(d.css(b[0],"borderTopWidth"))||0,e.left+=parseFloat(d.css(b[0],"borderLeftWidth"))||0;return{top:c.top-e.top,left:c.left-e.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&(!cf.test(a.nodeName)&&d.css(a,"position")==="static"))a=a.offsetParent;return a})}}),d.each(["Left","Top"],function(a,c){var e="scroll"+c;d.fn[e]=function(c){var f=this[0],g;if(!f)return null;if(c!==b)return this.each(function(){g=cg(this),g?g.scrollTo(a?d(g).scrollLeft():c,a?c:d(g).scrollTop()):this[e]=c});g=cg(f);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:d.support.boxModel&&g.document.documentElement[e]||g.document.body[e]:f[e]}}),d.each(["Height","Width"],function(a,c){var e=c.toLowerCase();d.fn["inner"+c]=function(){return this[0]?parseFloat(d.css(this[0],e,"padding")):null},d.fn["outer"+c]=function(a){return this[0]?parseFloat(d.css(this[0],e,a?"margin":"border")):null},d.fn[e]=function(a){var f=this[0];if(!f)return a==null?null:this;if(d.isFunction(a))return this.each(function(b){var c=d(this);c[e](a.call(this,b,c[e]()))});if(d.isWindow(f)){var g=f.document.documentElement["client"+c];return f.document.compatMode==="CSS1Compat"&&g||f.document.body["client"+c]||g}if(f.nodeType===9)return Math.max(f.documentElement["client"+c],f.body["scroll"+c],f.documentElement["scroll"+c],f.body["offset"+c],f.documentElement["offset"+c]);if(a===b){var h=d.css(f,e),i=parseFloat(h);return d.isNaN(i)?h:i}return this.css(e,typeof a==="string"?a:a+"px")}}),a.jQuery=a.$=d})(window); \ No newline at end of file diff --git a/openlp/plugins/remotes/html/jquery.mobile.css b/openlp/plugins/remotes/html/jquery.mobile.css new file mode 100644 index 000000000..6324793f8 --- /dev/null +++ b/openlp/plugins/remotes/html/jquery.mobile.css @@ -0,0 +1,16 @@ +/*! + * jQuery Mobile v1.0a3 + * http://jquerymobile.com/ + * + * Copyright 2010, jQuery Project + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + */ +/*! + * jQuery Mobile v1.0a3 + * http://jquerymobile.com/ + * + * Copyright 2010, jQuery Project + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + */.ui-bar-a{border:1px solid #2a2a2a;background:#111;color:#fff;font-weight:bold;text-shadow:0 -1px 1px #000;background-image:-moz-linear-gradient(top,#3c3c3c,#111);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#3c3c3c),color-stop(1,#111));-msfilter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#3c3c3c', EndColorStr='#111111')"}.ui-bar-a,.ui-bar-a input,.ui-bar-a select,.ui-bar-a textarea,.ui-bar-a button{font-family:Helvetica,Arial,sans-serif}.ui-bar-a .ui-link-inherit{color:#fff}.ui-bar-a .ui-link{color:#7cc4e7;font-weight:bold}.ui-body-a{border:1px solid #2a2a2a;background:#222;color:#fff;text-shadow:0 1px 0 #000;font-weight:normal;background-image:-moz-linear-gradient(top,#666,#222);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#666),color-stop(1,#222));-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#666666', EndColorStr='#222222)')"}.ui-body-a,.ui-body-a input,.ui-body-a select,.ui-body-a textarea,.ui-body-a button{font-family:Helvetica,Arial,sans-serif}.ui-body-a .ui-link-inherit{color:#fff}.ui-body-a .ui-link{color:#2489ce;font-weight:bold}.ui-br{border-bottom:1px solid rgba(130,130,130,.3)}.ui-btn-up-a{border:1px solid #222;background:#333;font-weight:bold;color:#fff;cursor:pointer;text-shadow:0 -1px 1px #000;text-decoration:none;background-image:-moz-linear-gradient(top,#555,#333);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#555),color-stop(1,#333));-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#555555', EndColorStr='#333333')"}.ui-btn-up-a a.ui-link-inherit{color:#fff}.ui-btn-hover-a{border:1px solid #000;background:#444;font-weight:bold;color:#fff;text-shadow:0 -1px 1px #000;text-decoration:none;background-image:-moz-linear-gradient(top,#666,#444);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#666),color-stop(1,#444));-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#666666', EndColorStr='#444444')"}.ui-btn-hover-a a.ui-link-inherit{color:#fff}.ui-btn-down-a{border:1px solid #000;background:#3d3d3d;font-weight:bold;color:#fff;text-shadow:0 -1px 1px #000;background-image:-moz-linear-gradient(top,#333,#5a5a5a);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#333),color-stop(1,#5a5a5a));-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#333333', EndColorStr='#5a5a5a')"}.ui-btn-down-a a.ui-link-inherit{color:#fff}.ui-btn-up-a,.ui-btn-hover-a,.ui-btn-down-a{font-family:Helvetica,Arial,sans-serif}.ui-bar-b{border:1px solid #456f9a;background:#5e87b0;color:#fff;font-weight:bold;text-shadow:0 -1px 1px #254f7a;background-image:-moz-linear-gradient(top,#81a8ce,#5e87b0);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#81a8ce),color-stop(1,#5e87b0));-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#81a8ce', EndColorStr='#5e87b0')"}.ui-bar-b,.ui-bar-b input,.ui-bar-b select,.ui-bar-b textarea,.ui-bar-b button{font-family:Helvetica,Arial,sans-serif}.ui-bar-b .ui-link-inherit{color:#fff}.ui-bar-b .ui-link{color:#7cc4e7;font-weight:bold}.ui-body-b{border:1px solid #c6c6c6;background:#ccc;color:#333;text-shadow:0 1px 0 #fff;font-weight:normal;background-image:-moz-linear-gradient(top,#e6e6e6,#ccc);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#e6e6e6),color-stop(1,#ccc));-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#e6e6e6', EndColorStr='#cccccc')"}.ui-body-b,.ui-body-b input,.ui-body-b select,.ui-body-b textarea,.ui-body-b button{font-family:Helvetica,Arial,sans-serif}.ui-body-b .ui-link-inherit{color:#333}.ui-body-b .ui-link{color:#2489ce;font-weight:bold}.ui-btn-up-b{border:1px solid #145072;background:#2567ab;font-weight:bold;color:#fff;cursor:pointer;text-shadow:0 -1px 1px #145072;text-decoration:none;background-image:-moz-linear-gradient(top,#4e89c5,#2567ab);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#5f9cc5),color-stop(1,#396b9e));-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#4e89c5', EndColorStr='#2567ab')"}.ui-btn-up-b a.ui-link-inherit{color:#fff}.ui-btn-hover-b{border:1px solid #00516e;background:#4b88b6;font-weight:bold;color:#fff;text-shadow:0 -1px 1px #014d68;background-image:-moz-linear-gradient(top,#72b0d4,#4b88b6);text-decoration:none;background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#72b0d4),color-stop(1,#4b88b6));-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#72b0d4', EndColorStr='#4b88b6')"}.ui-btn-hover-b a.ui-link-inherit{color:#fff}.ui-btn-down-b{border:1px solid #225377;background:#4e89c5;font-weight:bold;color:#fff;text-shadow:0 -1px 1px #225377;background-image:-moz-linear-gradient(top,#396b9e,#4e89c5);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#396b9e),color-stop(1,#4e89c5));-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#396b9e', EndColorStr='#4e89c5')"}.ui-btn-down-b a.ui-link-inherit{color:#fff}.ui-btn-up-b,.ui-btn-hover-b,.ui-btn-down-b{font-family:Helvetica,Arial,sans-serif}.ui-bar-c{border:1px solid #b3b3b3;background:#e9eaeb;color:#3e3e3e;font-weight:bold;text-shadow:0 1px 1px #fff;background-image:-moz-linear-gradient(top,#f0f0f0,#e9eaeb);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#f0f0f0),color-stop(1,#e9eaeb));-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#f0f0f0', EndColorStr='#e9eaeb')"}.ui-bar-c,.ui-bar-c input,.ui-bar-c select,.ui-bar-c textarea,.ui-bar-c button{font-family:Helvetica,Arial,sans-serif}.ui-body-c{border:1px solid #b3b3b3;color:#333;text-shadow:0 1px 0 #fff;background:#f0f0f0;background-image:-moz-linear-gradient(top,#eee,#ddd);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#eee),color-stop(1,#ddd));-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#eeeeee', EndColorStr='#dddddd')"}.ui-body-c,.ui-body-c input,.ui-body-c select,.ui-body-c textarea,.ui-body-c button{font-family:Helvetica,Arial,sans-serif}.ui-body-c .ui-link-inherit{color:#333}.ui-body-c .ui-link{color:#2489ce;font-weight:bold}.ui-btn-up-c{border:1px solid #ccc;background:#eee;font-weight:bold;color:#444;cursor:pointer;text-shadow:0 1px 1px #f6f6f6;text-decoration:none;background-image:-moz-linear-gradient(top,#fefefe,#eee);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fdfdfd),color-stop(1,#eee));-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#fdfdfd', EndColorStr='#eeeeee')"}.ui-btn-up-c a.ui-link-inherit{color:#2f3e46}.ui-btn-hover-c{border:1px solid #bbb;background:#dadada;font-weight:bold;color:#101010;text-decoration:none;text-shadow:0 1px 1px #fff;background-image:-moz-linear-gradient(top,#ededed,#dadada);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#ededed),color-stop(1,#dadada));-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#ededed', EndColorStr='#dadada')"}.ui-btn-hover-c a.ui-link-inherit{color:#2f3e46}.ui-btn-down-c{border:1px solid #808080;background:#fdfdfd;font-weight:bold;color:#111;text-shadow:0 1px 1px #fff;background-image:-moz-linear-gradient(top,#eee,#fdfdfd);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#eee),color-stop(1,#fdfdfd));-msfilter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#eeeeee', EndColorStr='#fdfdfd')"}.ui-btn-down-c a.ui-link-inherit{color:#2f3e46}.ui-btn-up-c,.ui-btn-hover-c,.ui-btn-down-c{font-family:Helvetica,Arial,sans-serif}.ui-bar-d{border:1px solid #ccc;background:#bbb;color:#333;text-shadow:0 1px 0 #eee;background-image:-moz-linear-gradient(top,#ddd,#bbb);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#ddd),color-stop(1,#bbb));-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#dddddd', EndColorStr='#bbbbbb')"}.ui-bar-d,.ui-bar-d input,.ui-bar-d select,.ui-bar-d textarea,.ui-bar-d button{font-family:Helvetica,Arial,sans-serif}.ui-bar-d .ui-link-inherit{color:#333}.ui-bar-d .ui-link{color:#2489ce;font-weight:bold}.ui-body-d{border:1px solid #ccc;color:#333;text-shadow:0 1px 0 #fff;background:#fff}.ui-body-d,.ui-body-d input,.ui-body-d select,.ui-body-d textarea,.ui-body-d button{font-family:Helvetica,Arial,sans-serif}.ui-body-d .ui-link-inherit{color:#333}.ui-body-d .ui-link{color:#2489ce;font-weight:bold}.ui-btn-up-d{border:1px solid #ccc;background:#fff;font-weight:bold;color:#444;text-decoration:none;text-shadow:0 1px 1px #fff}.ui-btn-up-d a.ui-link-inherit{color:#333}.ui-btn-hover-d{border:1px solid #aaa;background:#eee;font-weight:bold;color:#222;cursor:pointer;text-shadow:0 1px 1px #fff;text-decoration:none;background-image:-moz-linear-gradient(top,#fdfdfd,#eee);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fdfdfd),color-stop(1,#eee));-msfilter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#fdfdfd', EndColorStr='#eeeeee')"}.ui-btn-hover-d a.ui-link-inherit{color:#222}.ui-btn-down-d{border:1px solid #aaa;background:#fff;font-weight:bold;color:#111;text-shadow:0 1px 1px #fff;background-image:-moz-linear-gradient(top,#eee,#fff);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#eee),color-stop(1,#fff));-msfilter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#eeeeee', EndColorStr='#ffffff')"}.ui-btn-down-d a.ui-link-inherit{border:1px solid #808080;background:#ced0d2;font-weight:bold;color:#111;text-shadow:none;background-image:-moz-linear-gradient(top,#ccc,#eee);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#ccc),color-stop(1,#eee));-msfilter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#cccccc', EndColorStr='#eeeeee')"}.ui-btn-up-d,.ui-btn-hover-d,.ui-btn-down-d{font-family:Helvetica,Arial,sans-serif}.ui-bar-e{border:1px solid #f7c942;background:#fadb4e;color:#333;text-shadow:0 1px 0 #fff;background-image:-moz-linear-gradient(top,#fceda7,#fadb4e);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fceda7),color-stop(1,#fadb4e));-msfilter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#fceda7', EndColorStr='#fadb4e')"}.ui-bar-e,.ui-bar-e input,.ui-bar-e select,.ui-bar-e textarea,.ui-bar-d button{font-family:Helvetica,Arial,sans-serif}.ui-bar-e .ui-link-inherit{color:#333}.ui-bar-e .ui-link{color:#2489ce;font-weight:bold}.ui-body-e{border:1px solid #f7c942;color:#333;text-shadow:0 1px 0 #fff;background:#faeb9e;background-image:-moz-linear-gradient(top,#fff,#faeb9e);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fff),color-stop(1,#faeb9e));-msfilter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#ffffff', EndColorStr='#faeb9e')"}.ui-body-e,.ui-body-e input,.ui-body-e select,.ui-body-e textarea,.ui-body-e button{font-family:Helvetica,Arial,sans-serif}.ui-body-e .ui-link-inherit{color:#333}.ui-body-e .ui-link{color:#2489ce;font-weight:bold}.ui-btn-up-e{border:1px solid #f7c942;background:#fadb4e;font-weight:bold;color:#333;cursor:pointer;text-shadow:0 1px 1px #fe3;text-decoration:none;text-shadow:0 1px 0 #fff;background-image:-moz-linear-gradient(top,#fceda7,#fadb4e);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fceda7),color-stop(1,#fadb4e));-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#fceda7', EndColorStr='#fadb4e')"}.ui-btn-up-e a.ui-link-inherit{color:#333}.ui-btn-hover-e{border:1px solid #e79952;background:#fbe26f;font-weight:bold;color:#111;text-decoration:none;text-shadow:0 1px 1px #fff;background-image:-moz-linear-gradient(top,#fcf0b5,#fbe26f);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fcf0b5),color-stop(1,#fbe26f));-msfilter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#fcf0b5', EndColorStr='#fbe26f')"}.ui-btn-hover-e a.ui-link-inherit{color:#333}.ui-btn-down-e{border:1px solid #f7c942;background:#fceda7;font-weight:bold;color:#111;text-shadow:0 1px 1px #fff;background-image:-moz-linear-gradient(top,#fadb4e,#fceda7);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fadb4e),color-stop(1,#fceda7));-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#fadb4e', EndColorStr='#fceda7')"}.ui-btn-down-e a.ui-link-inherit{color:#333}.ui-btn-up-e,.ui-btn-hover-e,.ui-btn-down-e{font-family:Helvetica,Arial,sans-serif}a.ui-link-inherit{text-decoration:none!important}.ui-btn-active{border:1px solid #155678;background:#4596ce;font-weight:bold;color:#fff;cursor:pointer;text-shadow:0 -1px 1px #145072;text-decoration:none;background-image:-moz-linear-gradient(top,#85bae4,#5393c5);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#85bae4),color-stop(1,#5393c5));-msfilter:"progid:DXImageTransform.Microsoft.gradient(startColorStr='#85bae4', EndColorStr='#5393c5')";outline:0}.ui-btn-active a.ui-link-inherit{color:#fff}.ui-btn-inner{border-top:1px solid #fff;border-color:rgba(255,255,255,.3)}.ui-corner-tl{-moz-border-radius-topleft:.6em;-webkit-border-top-left-radius:.6em;border-top-left-radius:.6em}.ui-corner-tr{-moz-border-radius-topright:.6em;-webkit-border-top-right-radius:.6em;border-top-right-radius:.6em}.ui-corner-bl{-moz-border-radius-bottomleft:.6em;-webkit-border-bottom-left-radius:.6em;border-bottom-left-radius:.6em}.ui-corner-br{-moz-border-radius-bottomright:.6em;-webkit-border-bottom-right-radius:.6em;border-bottom-right-radius:.6em}.ui-corner-top{-moz-border-radius-topleft:.6em;-webkit-border-top-left-radius:.6em;border-top-left-radius:.6em;-moz-border-radius-topright:.6em;-webkit-border-top-right-radius:.6em;border-top-right-radius:.6em}.ui-corner-bottom{-moz-border-radius-bottomleft:.6em;-webkit-border-bottom-left-radius:.6em;border-bottom-left-radius:.6em;-moz-border-radius-bottomright:.6em;-webkit-border-bottom-right-radius:.6em;border-bottom-right-radius:.6em}.ui-corner-right{-moz-border-radius-topright:.6em;-webkit-border-top-right-radius:.6em;border-top-right-radius:.6em;-moz-border-radius-bottomright:.6em;-webkit-border-bottom-right-radius:.6em;border-bottom-right-radius:.6em}.ui-corner-left{-moz-border-radius-topleft:.6em;-webkit-border-top-left-radius:.6em;border-top-left-radius:.6em;-moz-border-radius-bottomleft:.6em;-webkit-border-bottom-left-radius:.6em;border-bottom-left-radius:.6em}.ui-corner-all{-moz-border-radius:.6em;-webkit-border-radius:.6em;border-radius:.6em}.ui-disabled{opacity:.3}.ui-disabled,.ui-disabled a{cursor:default!important}.ui-icon{background-image:url(images/icons-18-white.png);background-repeat:no-repeat;background-color:#666;background-color:rgba(0,0,0,.4);-moz-border-radius:9px;-webkit-border-radius:9px;border-radius:9px}.ui-icon-disc{background-color:#666;background-color:rgba(0,0,0,.3);-moz-border-radius:9px;-webkit-border-radius:9px;border-radius:9px}.ui-icon-black{background-image:url(images/icons-18-black.png)}.ui-icon-black-disc{background-color:#fff;background-color:rgba(255,255,255,.3);-moz-border-radius:9px;-webkit-border-radius:9px;border-radius:9px}@media screen and (-webkit-min-device-pixel-ratio:2),screen and (max--moz-device-pixel-ratio:2){.ui-icon{background-image:url(images/icons-36-white.png);background-size:630px 18px}.ui-icon-black{background-image:url(images/icons-36-black.png)}}.ui-icon-plus{background-position:-0 0}.ui-icon-minus{background-position:-36px 0}.ui-icon-delete{background-position:-72px 0}.ui-icon-arrow-r{background-position:-108px 0}.ui-icon-arrow-l{background-position:-144px 0}.ui-icon-arrow-u{background-position:-180px 0}.ui-icon-arrow-d{background-position:-216px 0}.ui-icon-check{background-position:-252px 0}.ui-icon-gear{background-position:-288px 0}.ui-icon-refresh{background-position:-324px 0}.ui-icon-forward{background-position:-360px 0}.ui-icon-back{background-position:-396px 0}.ui-icon-grid{background-position:-432px 0}.ui-icon-star{background-position:-468px 0}.ui-icon-alert{background-position:-504px 0}.ui-icon-info{background-position:-540px 0}.ui-icon-home{background-position:-576px 0}.ui-icon-search{background-position:-612px 0}.ui-icon-checkbox-off,.ui-icon-checkbox-on,.ui-icon-radio-off,.ui-icon-radio-on{background-color:transparent;-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;background-size:20px 20px}.ui-icon-checkbox-off{background-image:url(images/form-check-off.png)}.ui-icon-checkbox-on{background-image:url(images/form-check-on.png)}.ui-icon-radio-off{background-image:url(images/form-radio-off.png)}.ui-icon-radio-on{background-image:url(images/form-radio-on.png)}.ui-icon-searchfield{background-image:url(images/icon-search-black.png);background-size:16px 16px}.ui-icon-loading{background-image:url(images/ajax-loader.png);width:40px;height:40px;-moz-border-radius:20px;-webkit-border-radius:20px;border-radius:20px;background-size:35px 35px}.ui-btn-corner-tl{-moz-border-radius-topleft:1em;-webkit-border-top-left-radius:1em;border-top-left-radius:1em}.ui-btn-corner-tr{-moz-border-radius-topright:1em;-webkit-border-top-right-radius:1em;border-top-right-radius:1em}.ui-btn-corner-bl{-moz-border-radius-bottomleft:1em;-webkit-border-bottom-left-radius:1em;border-bottom-left-radius:1em}.ui-btn-corner-br{-moz-border-radius-bottomright:1em;-webkit-border-bottom-right-radius:1em;border-bottom-right-radius:1em}.ui-btn-corner-top{-moz-border-radius-topleft:1em;-webkit-border-top-left-radius:1em;border-top-left-radius:1em;-moz-border-radius-topright:1em;-webkit-border-top-right-radius:1em;border-top-right-radius:1em}.ui-btn-corner-bottom{-moz-border-radius-bottomleft:1em;-webkit-border-bottom-left-radius:1em;border-bottom-left-radius:1em;-moz-border-radius-bottomright:1em;-webkit-border-bottom-right-radius:1em;border-bottom-right-radius:1em}.ui-btn-corner-right{-moz-border-radius-topright:1em;-webkit-border-top-right-radius:1em;border-top-right-radius:1em;-moz-border-radius-bottomright:1em;-webkit-border-bottom-right-radius:1em;border-bottom-right-radius:1em}.ui-btn-corner-left{-moz-border-radius-topleft:1em;-webkit-border-top-left-radius:1em;border-top-left-radius:1em;-moz-border-radius-bottomleft:1em;-webkit-border-bottom-left-radius:1em;border-bottom-left-radius:1em}.ui-btn-corner-all{-moz-border-radius:1em;-webkit-border-radius:1em;border-radius:1em}.ui-corner-tl,.ui-corner-tr,.ui-corner-bl,.ui-corner-br,.ui-corner-top,.ui-corner-bottom,.ui-corner-right,.ui-corner-left,.ui-corner-all,.ui-btn-corner-tl,.ui-btn-corner-tr,.ui-btn-corner-bl,.ui-btn-corner-br,.ui-btn-corner-top,.ui-btn-corner-bottom,.ui-btn-corner-right,.ui-btn-corner-left,.ui-btn-corner-all{-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box}.ui-overlay{background:#666;opacity:.5;filter:Alpha(Opacity=50);position:absolute;width:100%;height:100%}.ui-overlay-shadow{-moz-box-shadow:0 0 12px rgba(0,0,0,.6);-webkit-box-shadow:0 0 12px rgba(0,0,0,.6);box-shadow:0 0 12px rgba(0,0,0,.6)}.ui-shadow{-moz-box-shadow:0 1px 4px rgba(0,0,0,.3);-webkit-box-shadow:0 1px 4px rgba(0,0,0,.3);box-shadow:0 1px 4px rgba(0,0,0,.3)}.ui-bar-a .ui-shadow,.ui-bar-b .ui-shadow,.ui-bar-c .ui-shadow{-moz-box-shadow:0 1px 0 rgba(255,255,255,.3);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.3);box-shadow:0 1px 0 rgba(255,255,255,.3)}.ui-shadow-inset{-moz-box-shadow:inset 0 1px 4px rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 4px rgba(0,0,0,.2);box-shadow:inset 0 1px 4px rgba(0,0,0,.2)}.ui-icon-shadow{-moz-box-shadow:0 1px 0 rgba(255,255,255,.4);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.4);box-shadow:0 1px 0 rgba(255,255,255,.4)}.ui-focus{-moz-box-shadow:0 0 12px #387bbe;-webkit-box-shadow:0 0 12px #387bbe;box-shadow:0 0 12px #387bbe}.ui-mobile-nosupport-boxshadow *{-moz-box-shadow:none!important;-webkit-box-shadow:none!important;box-shadow:none!important}.ui-mobile-nosupport-boxshadow .ui-focus{outline-width:2px}.ui-mobile fieldset,.ui-page{padding:0;margin:0}.ui-mobile a img,.ui-mobile fieldset{border:0}.ui-mobile-viewport{margin:0;overflow-x:hidden;-webkit-text-size-adjust:none;-ms-text-size-adjust:none;-webkit-tap-highlight-color:rgba(0,0,0,0)}.ui-mobile [data-role=page],.ui-mobile [data-role=dialog],.ui-page{top:0;left:0;width:100%;min-height:100%;position:absolute;display:none;border:0}.ui-mobile .ui-page-active{display:block;overflow:visible}.portrait,.portrait .ui-page{min-height:480px}.landscape,.landscape .ui-page{min-height:320px}.ui-loading .ui-mobile-viewport{overflow:hidden!important}.ui-loading .ui-loader{display:block}.ui-loading .ui-page{overflow:hidden}.ui-loader{display:none;position:absolute;opacity:.85;z-index:10;left:50%;width:200px;margin-left:-130px;margin-top:-35px;padding:10px 30px}.ui-loader h1{font-size:15px;text-align:center}.ui-loader .ui-icon{position:static;display:block;opacity:.9;margin:0 auto;width:35px;height:35px;background-color:transparent}.ui-mobile-rendering>*{visibility:hidden}.ui-bar,.ui-body{position:relative;padding:.4em 15px;overflow:hidden;display:block;clear:both}.ui-bar{font-size:16px;margin:0}.ui-bar h1,.ui-bar h2,.ui-bar h3,.ui-bar h4,.ui-bar h5,.ui-bar h6{margin:0;padding:0;font-size:16px;display:inline-block}.ui-header,.ui-footer{display:block}.ui-page .ui-header,.ui-page .ui-footer{position:relative}.ui-header .ui-btn-left{position:absolute;left:10px;top:.4em}.ui-header .ui-title,.ui-footer .ui-title{text-align:center;font-size:16px;display:block;margin:.6em 90px .8em;padding:0;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;outline:0!important}.ui-header .ui-btn-right{position:absolute;right:10px;top:.4em}.ui-content{border-width:0;overflow:visible;overflow-x:hidden;padding:15px}.ui-page-fullscreen .ui-content{padding:0}.ui-icon{width:18px;height:18px}.ui-fullscreen img{max-width:100%}.ui-nojs{position:absolute;left:-9999px}.spin{-webkit-transform:rotate(360deg);-webkit-animation-name:spin;-webkit-animation-duration:1s;-webkit-animation-iteration-count:infinite}@-webkit-keyframes spin{from{-webkit-transform:rotate(0deg)}to{-webkit-transform:rotate(360deg)}}.in,.out{-webkit-animation-timing-function:ease-in-out;-webkit-animation-duration:350ms}.slide.in{-webkit-transform:translateX(0);-webkit-animation-name:slideinfromright}.slide.out{-webkit-transform:translateX(-100%);-webkit-animation-name:slideouttoleft}.slide.in.reverse{-webkit-transform:translateX(0);-webkit-animation-name:slideinfromleft}.slide.out.reverse{-webkit-transform:translateX(100%);-webkit-animation-name:slideouttoright}.slideup.in{-webkit-transform:translateY(0);-webkit-animation-name:slideinfrombottom;z-index:10}.slideup.out{-webkit-animation-name:dontmove;z-index:0}.slideup.out.reverse{-webkit-transform:translateY(100%);z-index:10;-webkit-animation-name:slideouttobottom}.slideup.in.reverse{z-index:0;-webkit-animation-name:dontmove}.slidedown.in{-webkit-transform:translateY(0);-webkit-animation-name:slideinfromtop;z-index:10}.slidedown.out{-webkit-animation-name:dontmove;z-index:0}.slidedown.out.reverse{-webkit-transform:translateY(-100%);z-index:10;-webkit-animation-name:slideouttotop}.slidedown.in.reverse{z-index:0;-webkit-animation-name:dontmove}@-webkit-keyframes slideinfromright{from{-webkit-transform:translateX(100%)}to{-webkit-transform:translateX(0)}}@-webkit-keyframes slideinfromleft{from{-webkit-transform:translateX(-100%)}to{-webkit-transform:translateX(0)}}@-webkit-keyframes slideouttoleft{from{-webkit-transform:translateX(0)}to{-webkit-transform:translateX(-100%)}}@-webkit-keyframes slideouttoright{from{-webkit-transform:translateX(0)}to{-webkit-transform:translateX(100%)}}@-webkit-keyframes slideinfromtop{from{-webkit-transform:translateY(-100%)}to{-webkit-transform:translateY(0)}}@-webkit-keyframes slideinfrombottom{from{-webkit-transform:translateY(100%)}to{-webkit-transform:translateY(0)}}@-webkit-keyframes slideouttobottom{from{-webkit-transform:translateY(0)}to{-webkit-transform:translateY(100%)}}@-webkit-keyframes slideouttotop{from{-webkit-transform:translateY(0)}to{-webkit-transform:translateY(-100%)}}@-webkit-keyframes fadein{from{opacity:0}to{opacity:1}}@-webkit-keyframes fadeout{from{opacity:1}to{opacity:0}}.fade.in{opacity:1;z-index:10;-webkit-animation-name:fadein}.fade.out{z-index:0;-webkit-animation-name:fadeout}.ui-mobile-viewport-perspective{-webkit-perspective:1000;position:absolute}.ui-mobile-viewport-transitioning,.ui-mobile-viewport-transitioning .ui-page{width:100%;height:100%;overflow:hidden}.flip{-webkit-animation-duration:.65s;-webkit-backface-visibility:hidden;-webkit-transform:translateX(0)}.flip.in{-webkit-transform:rotateY(0) scale(1);-webkit-animation-name:flipinfromleft}.flip.out{-webkit-transform:rotateY(-180deg) scale(.8);-webkit-animation-name:flipouttoleft}.flip.in.reverse{-webkit-transform:rotateY(0) scale(1);-webkit-animation-name:flipinfromright}.flip.out.reverse{-webkit-transform:rotateY(180deg) scale(.8);-webkit-animation-name:flipouttoright}@-webkit-keyframes flipinfromright{from{-webkit-transform:rotateY(-180deg) scale(.8)}to{-webkit-transform:rotateY(0) scale(1)}}@-webkit-keyframes flipinfromleft{from{-webkit-transform:rotateY(180deg) scale(.8)}to{-webkit-transform:rotateY(0) scale(1)}}@-webkit-keyframes flipouttoleft{from{-webkit-transform:rotateY(0) scale(1)}to{-webkit-transform:rotateY(-180deg) scale(.8)}}@-webkit-keyframes flipouttoright{from{-webkit-transform:rotateY(0) scale(1)}to{-webkit-transform:rotateY(180deg) scale(.8)}}@-webkit-keyframes dontmove{from{opacity:1}to{opacity:1}}.pop{-webkit-transform-origin:50% 50%}.pop.in{-webkit-transform:scale(1);opacity:1;-webkit-animation-name:popin;z-index:10}.pop.out.reverse{-webkit-transform:scale(.2);opacity:0;-webkit-animation-name:popout;z-index:10}.pop.in.reverse{z-index:0;-webkit-animation-name:dontmove}@-webkit-keyframes popin{from{-webkit-transform:scale(.2);opacity:0}to{-webkit-transform:scale(1);opacity:1}}@-webkit-keyframes popout{from{-webkit-transform:scale(1);opacity:1}to{-webkit-transform:scale(.2);opacity:0}}.ui-grid-a,.ui-grid-b,.ui-grid-c,.ui-grid-d{overflow:hidden}.ui-block-a,.ui-block-b,.ui-block-c,.ui-block-d,.ui-block-e{margin:0;padding:0;border:0;float:left}.ui-grid-a .ui-block-a,.ui-grid-a .ui-block-b{width:50%}.ui-grid-a .ui-block-a{clear:left}.ui-grid-b .ui-block-a,.ui-grid-b .ui-block-b,.ui-grid-b .ui-block-c{width:33.333%}.ui-grid-b .ui-block-a{clear:left}.ui-grid-c .ui-block-a,.ui-grid-c .ui-block-b,.ui-grid-c .ui-block-c,.ui-grid-c .ui-block-d{width:25%}.ui-grid-c .ui-block-a{clear:left}.ui-grid-d .ui-block-a,.ui-grid-d .ui-block-b,.ui-grid-d .ui-block-c,.ui-grid-d .ui-block-d,.ui-grid-d .ui-block-e{width:20%}.ui-grid-d .ui-block-a{clear:left}.ui-header,.ui-footer,.ui-page-fullscreen .ui-header,.ui-page-fullscreen .ui-footer{position:absolute;overflow:hidden;width:100%;border-left-width:0;border-right-width:0}.ui-header-fixed,.ui-footer-fixed{z-index:1000;-webkit-transform:translateZ(0)}.ui-footer-duplicate,.ui-page-fullscreen .ui-fixed-inline{display:none}.ui-page-fullscreen .ui-header,.ui-page-fullscreen .ui-footer{opacity:.9}.ui-navbar{overflow:hidden}.ui-navbar ul,.ui-navbar-expanded ul{list-style:none;padding:0;margin:0;position:relative;display:block;border:0}.ui-navbar-collapsed ul{float:left;width:75%;margin-right:-2px}.ui-navbar-collapsed .ui-navbar-toggle{float:left;width:25%}.ui-navbar li.ui-navbar-truncate{position:absolute;left:-9999px;top:-9999px}.ui-navbar li .ui-btn,.ui-navbar .ui-navbar-toggle .ui-btn{display:block;font-size:12px;text-align:center;margin:0;border-right-width:0}.ui-navbar li .ui-btn{margin-right:-1px}.ui-navbar li .ui-btn:last-child{margin-right:0}.ui-header .ui-navbar li .ui-btn,.ui-header .ui-navbar .ui-navbar-toggle .ui-btn,.ui-footer .ui-navbar li .ui-btn,.ui-footer .ui-navbar .ui-navbar-toggle .ui-btn{border-top-width:0;border-bottom-width:0}.ui-navbar .ui-btn-inner{padding-left:2px;padding-right:2px}.ui-navbar-noicons li .ui-btn .ui-btn-inner,.ui-navbar-noicons .ui-navbar-toggle .ui-btn-inner{padding-top:.8em;padding-bottom:.9em}.ui-navbar-expanded .ui-btn{margin:0;font-size:14px}.ui-navbar-expanded .ui-btn-inner{padding-left:5px;padding-right:5px}.ui-navbar-expanded .ui-btn-icon-top .ui-btn-inner{padding:45px 5px 15px;text-align:center}.ui-navbar-expanded .ui-btn-icon-top .ui-icon{top:15px}.ui-navbar-expanded .ui-btn-icon-bottom .ui-btn-inner{padding:15px 5px 45px;text-align:center}.ui-navbar-expanded .ui-btn-icon-bottom .ui-icon{bottom:15px}.ui-navbar-expanded li .ui-btn .ui-btn-inner{min-height:2.5em}.ui-navbar-expanded .ui-navbar-noicons .ui-btn .ui-btn-inner{padding-top:1.8em;padding-bottom:1.9em}.ui-btn{display:block;text-align:center;cursor:pointer;position:relative;margin:.5em 5px;padding:0}.ui-btn:focus,.ui-btn:active{outline:0}.ui-header .ui-btn,.ui-footer .ui-btn,.ui-bar .ui-btn{display:inline-block;font-size:13px;margin:0}.ui-btn-inline{display:inline-block}.ui-btn-inner{padding:.6em 25px;display:block;height:100%;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;position:relative}.ui-header .ui-btn-inner,.ui-footer .ui-btn-inner,.ui-bar .ui-btn-inner{padding:.4em 8px .5em}.ui-btn-icon-notext{display:inline-block;width:20px;height:20px;padding:2px 1px 2px 3px;text-indent:-9999px}.ui-btn-icon-notext .ui-btn-inner{padding:0}.ui-btn-icon-notext .ui-btn-text{position:absolute;left:-999px}.ui-btn-icon-left .ui-btn-inner{padding-left:33px}.ui-header .ui-btn-icon-left .ui-btn-inner,.ui-footer .ui-btn-icon-left .ui-btn-inner,.ui-bar .ui-btn-icon-left .ui-btn-inner{padding-left:27px}.ui-btn-icon-right .ui-btn-inner{padding-right:33px}.ui-header .ui-btn-icon-right .ui-btn-inner,.ui-footer .ui-btn-icon-right .ui-btn-inner,.ui-bar .ui-btn-icon-right .ui-btn-inner{padding-right:27px}.ui-btn-icon-top .ui-btn-inner{padding-top:33px}.ui-header .ui-btn-icon-top .ui-btn-inner,.ui-footer .ui-btn-icon-top .ui-btn-inner,.ui-bar .ui-btn-icon-top .ui-btn-inner{padding-top:27px}.ui-btn-icon-bottom .ui-btn-inner{padding-bottom:33px}.ui-header .ui-btn-icon-bottom .ui-btn-inner,.ui-footer .ui-btn-icon-bottom .ui-btn-inner,.ui-bar .ui-btn-icon-bottom .ui-btn-inner{padding-bottom:27px}.ui-btn-icon-notext .ui-icon{display:block}.ui-btn-icon-left .ui-icon,.ui-btn-icon-right .ui-icon{position:absolute;top:50%;margin-top:-9px}.ui-btn-icon-top .ui-icon,.ui-btn-icon-bottom .ui-icon{position:absolute;left:50%;margin-left:-9px}.ui-btn-icon-left .ui-icon{left:10px}.ui-btn-icon-right .ui-icon{right:10px}.ui-header .ui-btn-icon-left .ui-icon,.ui-footer .ui-btn-icon-left .ui-icon,.ui-bar .ui-btn-icon-left .ui-icon{left:4px}.ui-header .ui-btn-icon-right .ui-icon,.ui-footer .ui-btn-icon-right .ui-icon,.ui-bar .ui-btn-icon-right .ui-icon{right:4px}.ui-header .ui-btn-icon-top .ui-icon,.ui-footer .ui-btn-icon-top .ui-icon,.ui-bar .ui-btn-icon-top .ui-icon{top:4px}.ui-header .ui-btn-icon-bottom .ui-icon,.ui-footer .ui-btn-icon-bottom .ui-icon,.ui-bar .ui-btn-icon-bottom .ui-icon{bottom:4px}.ui-btn-icon-top .ui-icon{top:5px}.ui-btn-icon-bottom .ui-icon{bottom:5px}.ui-btn-hidden{position:absolute;top:0;left:0;width:100%;height:100%;-webkit-appearance:button;opacity:0;cursor:pointer}.ui-collapsible-contain{margin:.5em 0}.ui-collapsible-heading{font-size:16px;display:block;margin:0 -8px;padding:0;border-width:0 0 1px 0;position:relative}.ui-collapsible-heading a{text-align:left;margin:0}.ui-collapsible-heading a .ui-btn-inner{padding-left:40px}.ui-collapsible-heading a span.ui-btn{position:absolute;left:6px;top:50%;margin:-12px 0 0 0;width:20px;height:20px;padding:1px 0 1px 2px;text-indent:-9999px}.ui-collapsible-heading a span.ui-btn .ui-btn-inner{padding:0}.ui-collapsible-heading a span.ui-btn .ui-icon{left:0;margin-top:-10px}.ui-collapsible-heading-status{position:absolute;left:-9999px}.ui-collapsible-content{display:block;padding:10px 0 10px 8px}.ui-collapsible-content-collapsed{display:none}.ui-collapsible-set{margin:.5em 0}.ui-collapsible-set .ui-collapsible-contain{margin:-1px 0 0}.ui-controlgroup,fieldset.ui-controlgroup{padding:0;margin:.5em 0 1em}.ui-bar .ui-controlgroup{margin:0 .3em}.ui-controlgroup-label{font-size:16px;line-height:1.4;font-weight:normal;margin:0 0 .3em}.ui-controlgroup-controls{display:block;width:95%}.ui-controlgroup li{list-style:none}.ui-controlgroup-vertical .ui-btn,.ui-controlgroup-vertical .ui-checkbox,.ui-controlgroup-vertical .ui-radio{margin:0;border-bottom-width:0}.ui-controlgroup-vertical .ui-controlgroup-last{border-bottom-width:1px}.ui-controlgroup-horizontal{padding:0}.ui-controlgroup-horizontal .ui-btn,.ui-controlgroup-horizontal .ui-checkbox,.ui-controlgroup-horizontal .ui-radio{margin:0 -5px 0 0;display:inline-block}.ui-controlgroup-horizontal .ui-checkbox .ui-btn,.ui-controlgroup-horizontal .ui-radio .ui-btn,.ui-controlgroup-horizontal .ui-checkbox:last-child,.ui-controlgroup-horizontal .ui-radio:last-child{margin-right:0}.ui-controlgroup-horizontal .ui-controlgroup-last{margin-right:0}.ui-controlgroup .ui-checkbox label,.ui-controlgroup .ui-radio label{font-size:16px}.min-width-480px .ui-controlgroup-label{vertical-align:top;display:inline-block;width:20%;margin:0 2% 0 0}.min-width-480px .ui-controlgroup-controls{width:60%;display:inline-block}.ui-dialog{min-height:480px}.ui-dialog .ui-header,.ui-dialog .ui-content,.ui-dialog .ui-footer{margin:15px;position:relative}.ui-dialog .ui-header,.ui-dialog .ui-footer{z-index:10;width:auto}.ui-dialog .ui-content,.ui-dialog .ui-footer{margin-top:-15px}.ui-checkbox,.ui-radio{position:relative;margin:.2em 0 .5em;z-index:1}.ui-checkbox .ui-btn,.ui-radio .ui-btn{margin:0;text-align:left;z-index:2}.ui-checkbox .ui-btn-icon-left .ui-btn-inner,.ui-radio .ui-btn-icon-left .ui-btn-inner{padding-left:45px}.ui-checkbox .ui-btn-icon-right .ui-btn-inner,.ui-radio .ui-btn-icon-right .ui-btn-inner{padding-right:45px}.ui-checkbox .ui-btn-icon-left .ui-icon,.ui-radio .ui-btn-icon-left .ui-icon{left:15px}.ui-checkbox .ui-btn-icon-right .ui-icon,.ui-radio .ui-btn-icon-right .ui-icon{right:15px}.ui-checkbox input,.ui-radio input{position:absolute;left:20px;top:50%;width:10px;height:10px;margin:-5px 0 0 0;outline:0!important;z-index:1}.ui-field-contain{background:0;padding:1.5em 0;margin:0;border-bottom-width:1px;overflow:visible}.ui-field-contain:first-child{border-top-width:0}.min-width-480px .ui-field-contain{border-width:0;padding:0;margin:1em 0}.ui-select{display:block;position:relative}.ui-select select{position:absolute;left:-9999px;top:-9999px}.ui-select .ui-btn select{cursor:pointer;-webkit-appearance:button;left:0;top:0;width:100%;height:100%;opacity:.001}.ui-select .ui-btn select.ui-select-nativeonly{opacity:1}.ui-select .ui-btn-icon-right .ui-btn-inner{padding-right:45px}.ui-select .ui-btn-icon-right .ui-icon{right:15px}label.ui-select{font-size:16px;line-height:1.4;font-weight:normal;margin:0 0 .3em;display:block}.ui-select .ui-btn-text,.ui-selectmenu .ui-btn-text{display:inline-block;min-height:1em}.ui-select .ui-btn-text{text-overflow:ellipsis;overflow:hidden;width:85%}.ui-selectmenu{position:absolute;padding:0;z-index:100!important;width:80%;max-width:350px;padding:6px}.ui-selectmenu .ui-listview{margin:0}.ui-selectmenu .ui-btn.ui-li-divider{cursor:default}.ui-selectmenu-hidden{top:-9999px;left:-9999px}.ui-selectmenu-screen{position:absolute;top:0;left:0;width:100%;height:100%;z-index:99}.ui-screen-hidden,.ui-selectmenu-list .ui-li .ui-icon{display:none}.ui-selectmenu-list .ui-li .ui-icon{display:block}.ui-li.ui-selectmenu-placeholder{display:none}.min-width-480px label.ui-select{display:inline-block;width:20%;margin:0 2% 0 0}.min-width-480px .ui-select{width:60%;display:inline-block}.ui-selectmenu .ui-header h1:after{content:'.';visibility:hidden}label.ui-input-text{font-size:16px;line-height:1.4;display:block;font-weight:normal;margin:0 0 .3em}input.ui-input-text,textarea.ui-input-text{background-image:none;padding:.4em;line-height:1.4;font-size:16px;display:block;width:95%}input.ui-input-text{-webkit-appearance:none}textarea.ui-input-text{height:50px;-webkit-transition:height 200ms linear;-moz-transition:height 200ms linear;-o-transition:height 200ms linear;transition:height 200ms linear}.ui-input-search{padding:0 30px;width:77%;background-position:8px 50%;background-repeat:no-repeat;position:relative}.ui-input-search input.ui-input-text{border:0;width:98%;padding:.4em 0;margin:0;display:block;background:transparent none;outline:0!important}.ui-input-search .ui-input-clear{position:absolute;right:0;top:50%;margin-top:-14px}.ui-input-search .ui-input-clear-hidden{display:none}.min-width-480px label.ui-input-text{vertical-align:top}.min-width-480px label.ui-input-text{display:inline-block;width:20%;margin:0 2% 0 0}.min-width-480px input.ui-input-text,.min-width-480px textarea.ui-input-text,.min-width-480px .ui-input-search{width:60%;display:inline-block}.min-width-480px .ui-input-search{width:50%}.ui-listview{margin:0;counter-reset:listnumbering}.ui-content .ui-listview{margin:-15px}.ui-content .ui-listview-inset{margin:1em 0}.ui-listview,.ui-li{list-style:none;padding:0;zoom:1}.ui-li{display:block;margin:0;position:relative;overflow:hidden;text-align:left;border-width:0;border-top-width:1px}.ui-li .ui-btn-text{text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.ui-li-divider,.ui-li-static{padding:.5em 15px;font-size:14px;font-weight:bold;counter-reset:listnumbering}ol.ui-listview .ui-link-inherit:before,.ui-li-dec{font-size:.8em;display:inline-block;padding-right:.3em;font-weight:normal;counter-increment:listnumbering;content:counter(listnumbering) ". "}ol.ui-listview .ui-li-jsnumbering:before{content:""!important}.ui-listview-inset .ui-li{border-right-width:1px;border-left-width:1px}.ui-li:last-child{border-bottom-width:1px}.ui-li .ui-btn-inner{display:block;position:relative;padding:.7em 75px .7em 15px}.ui-li-has-thumb .ui-btn-inner{min-height:60px;padding-left:100px}.ui-li-has-icon .ui-btn-inner{min-height:20px;padding-left:40px}.ui-li-heading{font-size:16px;font-weight:bold;display:block;margin:.6em 0;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.ui-li-desc{font-size:12px;font-weight:normal;display:block;margin:-.5em 0 .6em;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.ui-li-thumb,.ui-li-icon{position:absolute;left:1px;top:0;max-height:80px;max-width:80px}.ui-li-icon{max-height:40px;max-width:40px;left:10px;top:.9em}.ui-li-thumb,.ui-li-icon,.ui-li-content{float:left;margin-right:10px}.ui-li-aside{float:right;width:50%;text-align:right;margin:.3em 0}.min-width-480px .ui-li-aside{width:45%}.ui-li-has-alt .ui-btn-inner{padding-right:95px}.ui-li-count{position:absolute;font-size:11px;font-weight:bold;padding:.2em .5em;top:50%;margin-top:-.9em;right:38px}.ui-li-divider .ui-li-count{right:10px}.ui-li-has-alt .ui-li-count{right:55px}.ui-li-link-alt{position:absolute;width:40px;height:100%;border-width:0;border-left-width:1px;top:0;right:0;margin:0;padding:0}.ui-li-link-alt .ui-btn{overflow:hidden;position:absolute;right:8px;top:50%;margin:-11px 0 0 0;border-bottom-width:1px}.ui-li-link-alt .ui-btn-inner{padding:0;position:static}.ui-li-link-alt .ui-btn .ui-icon{right:50%;margin-right:-9px}.ui-listview-filter{border-width:0;overflow:hidden;margin:-15px -15px 15px -15px}.ui-listview-filter .ui-input-search{margin:5px;width:auto;display:block}@media only screen and (min-device-width:768px) and (max-device-width:1024px){.ui-li .ui-btn-text{overflow:visible}}label.ui-slider{display:block}input.ui-slider-input,.min-width-480px input.ui-slider-input{display:inline-block;width:50px}select.ui-slider-switch{display:none}div.ui-slider{position:relative;display:inline-block;overflow:visible;height:15px;padding:0;margin:0 2% 0 20px;top:4px;width:66%}a.ui-slider-handle{position:absolute;z-index:10;top:50%;width:28px;height:28px;margin-top:-15px;margin-left:-15px}a.ui-slider-handle .ui-btn-inner{padding-left:0;padding-right:0}.min-width-480px label.ui-slider{display:inline-block;width:20%;margin:0 2% 0 0}.min-width-480px div.ui-slider{width:45%}div.ui-slider-switch{height:32px;overflow:hidden;margin-left:0}div.ui-slider-inneroffset{margin-left:50%;position:absolute;top:1px;height:100%;width:50%}div.ui-slider-handle-snapping{-webkit-transition:left 100ms linear}div.ui-slider-labelbg{position:absolute;top:0;margin:0;border-width:0}div.ui-slider-switch div.ui-slider-labelbg-a{width:60%;height:100%;left:0}div.ui-slider-switch div.ui-slider-labelbg-b{width:60%;height:100%;right:0}.ui-slider-switch-a div.ui-slider-labelbg-a,.ui-slider-switch-b div.ui-slider-labelbg-b{z-index:1}.ui-slider-switch-a div.ui-slider-labelbg-b,.ui-slider-switch-b div.ui-slider-labelbg-a{z-index:10}div.ui-slider-switch a.ui-slider-handle{z-index:20;width:101%;height:32px;margin-top:-18px;margin-left:-101%}span.ui-slider-label{width:100%;position:absolute;height:32px;font-size:16px;text-align:center;line-height:2;background:0;border-color:transparent}span.ui-slider-label-a{left:-100%;margin-right:-1px}span.ui-slider-label-b{right:-100%;margin-left:-1px} \ No newline at end of file diff --git a/openlp/plugins/remotes/html/jquery.mobile.js b/openlp/plugins/remotes/html/jquery.mobile.js new file mode 100644 index 000000000..44cc49e71 --- /dev/null +++ b/openlp/plugins/remotes/html/jquery.mobile.js @@ -0,0 +1,121 @@ +/*! + * jQuery Mobile v1.0a3 + * http://jquerymobile.com/ + * + * Copyright 2010, jQuery Project + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + */ +(function(a,d){if(a.cleanData){var c=a.cleanData;a.cleanData=function(b){for(var g=0,e;(e=b[g])!=null;g++)a(e).triggerHandler("remove");c(b)}}else{var f=a.fn.remove;a.fn.remove=function(b,g){return this.each(function(){if(!g)if(!b||a.filter(b,[this]).length)a("*",this).add([this]).each(function(){a(this).triggerHandler("remove")});return f.call(a(this),b,g)})}}a.widget=function(b,g,e){var i=b.split(".")[0],h;b=b.split(".")[1];h=i+"-"+b;if(!e){e=g;g=a.Widget}a.expr[":"][h]=function(k){return!!a.data(k, +b)};a[i]=a[i]||{};a[i][b]=function(k,j){arguments.length&&this._createWidget(k,j)};g=new g;g.options=a.extend(true,{},g.options);a[i][b].prototype=a.extend(true,g,{namespace:i,widgetName:b,widgetEventPrefix:a[i][b].prototype.widgetEventPrefix||b,widgetBaseClass:h},e);a.widget.bridge(b,a[i][b])};a.widget.bridge=function(b,g){a.fn[b]=function(e){var i=typeof e==="string",h=Array.prototype.slice.call(arguments,1),k=this;e=!i&&h.length?a.extend.apply(null,[true,e].concat(h)):e;if(i&&e.charAt(0)==="_")return k; +i?this.each(function(){var j=a.data(this,b);if(!j)throw"cannot call methods on "+b+" prior to initialization; attempted to call method '"+e+"'";if(!a.isFunction(j[e]))throw"no such method '"+e+"' for "+b+" widget instance";var o=j[e].apply(j,h);if(o!==j&&o!==d){k=o;return false}}):this.each(function(){var j=a.data(this,b);j?j.option(e||{})._init():a.data(this,b,new g(e,this))});return k}};a.Widget=function(b,g){arguments.length&&this._createWidget(b,g)};a.Widget.prototype={widgetName:"widget",widgetEventPrefix:"", +options:{disabled:false},_createWidget:function(b,g){a.data(g,this.widgetName,this);this.element=a(g);this.options=a.extend(true,{},this.options,this._getCreateOptions(),b);var e=this;this.element.bind("remove."+this.widgetName,function(){e.destroy()});this._create();this._trigger("create");this._init()},_getCreateOptions:function(){var b={};if(a.metadata)b=a.metadata.get(element)[this.widgetName];return b},_create:function(){},_init:function(){},destroy:function(){this.element.unbind("."+this.widgetName).removeData(this.widgetName); +this.widget().unbind("."+this.widgetName).removeAttr("aria-disabled").removeClass(this.widgetBaseClass+"-disabled ui-state-disabled")},widget:function(){return this.element},option:function(b,g){var e=b;if(arguments.length===0)return a.extend({},this.options);if(typeof b==="string"){if(g===d)return this.options[b];e={};e[b]=g}this._setOptions(e);return this},_setOptions:function(b){var g=this;a.each(b,function(e,i){g._setOption(e,i)});return this},_setOption:function(b,g){this.options[b]=g;if(b=== +"disabled")this.widget()[g?"addClass":"removeClass"](this.widgetBaseClass+"-disabled ui-state-disabled").attr("aria-disabled",g);return this},enable:function(){return this._setOption("disabled",false)},disable:function(){return this._setOption("disabled",true)},_trigger:function(b,g,e){var i=this.options[b];g=a.Event(g);g.type=(b===this.widgetEventPrefix?b:this.widgetEventPrefix+b).toLowerCase();e=e||{};if(g.originalEvent){b=a.event.props.length;for(var h;b;){h=a.event.props[--b];g[h]=g.originalEvent[h]}}this.element.trigger(g, +e);return!(a.isFunction(i)&&i.call(this.element[0],g,e)===false||g.isDefaultPrevented())}}})(jQuery);(function(a,d){a.widget("mobile.widget",{_getCreateOptions:function(){var c=this.element,f={};a.each(this.options,function(b){var g=c.data(b.replace(/[A-Z]/g,function(e){return"-"+e.toLowerCase()}));if(g!==d)f[b]=g});return f}})})(jQuery); +(function(a){function d(){var g=c.width(),e=[],i=[],h;f.removeClass("min-width-"+b.join("px min-width-")+"px max-width-"+b.join("px max-width-")+"px");a.each(b,function(k,j){g>=j&&e.push("min-width-"+j+"px");g<=j&&i.push("max-width-"+j+"px")});if(e.length)h=e.join(" ");if(i.length)h+=" "+i.join(" ");f.addClass(h)}var c=a(window),f=a("html"),b=[320,480,768,1024];a.mobile.media=function(){var g={},e=a("
"),i=a("").append(e);return function(h){if(!(h in g)){var k=document.createElement("style"), +j="@media "+h+" { #jquery-mediatest { position:absolute; } }";k.type="text/css";if(k.styleSheet)k.styleSheet.cssText=j;else k.appendChild(document.createTextNode(j));f.prepend(i).prepend(k);g[h]=e.css("position")==="absolute";i.add(k).remove()}return g[h]}}();a.mobile.addResolutionBreakpoints=function(g){if(a.type(g)==="array")b=b.concat(g);else b.push(g);b.sort(function(e,i){return e-i});d()};a(document).bind("mobileinit.htmlclass",function(){c.bind("orientationchange.htmlclass resize.htmlclass", +function(g){g.orientation&&f.removeClass("portrait landscape").addClass(g.orientation);d()})});a(function(){c.trigger("orientationchange.htmlclass")})})(jQuery); +(function(a,d){function c(h){var k=h.charAt(0).toUpperCase()+h.substr(1);h=(h+" "+g.join(k+" ")+k).split(" ");for(var j in h)if(b[j]!==d)return true}var f=a("").prependTo("html"),b=f[0].style,g=["webkit","moz","o"],e=window.palmGetResource||window.PalmServiceBridge,i=window.blackberry;a.extend(a.support,{orientation:"orientation"in window,touch:"ontouchend"in document,cssTransitions:"WebKitTransitionEvent"in window,pushState:!!history.pushState,mediaquery:a.mobile.media("only all"),cssPseudoElement:!!c("content"), +boxShadow:!!c("boxShadow")&&!i,scrollTop:("pageXOffset"in window||"scrollTop"in document.documentElement||"scrollTop"in f[0])&&!e,dynamicBaseTag:function(){var h=location.protocol+"//"+location.host+location.pathname+"ui-dir/",k=a("head base"),j=null,o="";if(k.length)o=k.attr("href");else k=j=a("",{href:h}).appendTo("head");var p=a("").prependTo(f)[0].href;k[0].href=o?o:location.pathname;j&&j.remove();return p.indexOf(h)===0}()});f.remove();a.support.boxShadow||a("html").addClass("ui-mobile-nosupport-boxshadow")})(jQuery); +(function(a,d){a.each("touchstart touchmove touchend orientationchange tap taphold swipe swipeleft swiperight scrollstart scrollstop".split(" "),function(e,i){a.fn[i]=function(h){return h?this.bind(i,h):this.trigger(i)};a.attrFn[i]=true});var c=a.support.touch,f=c?"touchstart":"mousedown",b=c?"touchend":"mouseup",g=c?"touchmove":"mousemove";a.event.special.scrollstart={enabled:true,setup:function(){function e(j,o){h=o;var p=j.type;j.type=h?"scrollstart":"scrollstop";a.event.handle.call(i,j);j.type= +p}var i=this,h,k;a(i).bind("touchmove scroll",function(j){if(a.event.special.scrollstart.enabled){h||e(j,true);clearTimeout(k);k=setTimeout(function(){e(j,false)},50)}})}};a.event.special.tap={setup:function(){var e=this,i=a(e);i.bind("mousedown touchstart",function(h){function k(n){if(n.type=="scroll")j=true;else{n=n.type=="touchmove"?n.originalEvent.touches[0]:n;if(Math.abs(v[0]-n.pageX)>10||Math.abs(v[1]-n.pageY)>10)j=true}}if(h.which&&h.which!==1||i.data("prevEvent")&&i.data("prevEvent")!==h.type)return false; +i.data("prevEvent",h.type);setTimeout(function(){i.removeData("prevEvent")},800);var j=false,o=true,p=h.target,t=h.originalEvent,v=h.type=="touchstart"?[t.touches[0].pageX,t.touches[0].pageY]:[h.pageX,h.pageY],m,r;r=setTimeout(function(){if(o&&!j){m=h.type;h.type="taphold";a.event.handle.call(e,h);h.type=m}},750);a(window).one("scroll",k);i.bind("mousemove touchmove",k).one("mouseup touchend",function(n){i.unbind("mousemove touchmove",k);a(window).unbind("scroll",k);clearTimeout(r);o=false;if(!j&& +p==n.target){m=n.type;n.type="tap";a.event.handle.call(e,n);n.type=m}})})}};a.event.special.swipe={setup:function(){var e=a(this);e.bind(f,function(i){function h(p){if(j){var t=p.originalEvent.touches?p.originalEvent.touches[0]:p;o={time:(new Date).getTime(),coords:[t.pageX,t.pageY]};Math.abs(j.coords[0]-o.coords[0])>10&&p.preventDefault()}}var k=i.originalEvent.touches?i.originalEvent.touches[0]:i,j={time:(new Date).getTime(),coords:[k.pageX,k.pageY],origin:a(i.target)},o;e.bind(g,h).one(b,function(){e.unbind(g, +h);if(j&&o)if(o.time-j.time<1E3&&Math.abs(j.coords[0]-o.coords[0])>30&&Math.abs(j.coords[1]-o.coords[1])<75)j.origin.trigger("swipe").trigger(j.coords[0]>o.coords[0]?"swipeleft":"swiperight");j=o=d})})}};(function(e){function i(){var o=k();if(o!==j){j=o;h.trigger("orientationchange")}}var h=e(window),k,j;e.event.special.orientationchange={setup:function(){if(e.support.orientation)return false;j=k();h.bind("resize",i)},teardown:function(){if(e.support.orientation)return false;h.unbind("resize",i)}, +add:function(o){var p=o.handler;o.handler=function(t){t.orientation=k();return p.apply(this,arguments)}}};k=function(){var o=document.documentElement;return o&&o.clientWidth/o.clientHeight<1.1?"portrait":"landscape"}})(jQuery);a.each({scrollstop:"scrollstart",taphold:"tap",swipeleft:"swipe",swiperight:"swipe"},function(e,i){a.event.special[e]={setup:function(){a(this).bind(i,a.noop)}}})})(jQuery); +(function(a,d,c){function f(j){j=j||location.href;return"#"+j.replace(/^[^#]*#?(.*)$/,"$1")}var b="hashchange",g=document,e,i=a.event.special,h=g.documentMode,k="on"+b in d&&(h===c||h>7);a.fn[b]=function(j){return j?this.bind(b,j):this.trigger(b)};a.fn[b].delay=50;i[b]=a.extend(i[b],{setup:function(){if(k)return false;a(e.start)},teardown:function(){if(k)return false;a(e.stop)}});e=function(){function j(){var n=f(),u=r(t);if(n!==t){m(t=n,u);a(d).trigger(b)}else if(u!==t)location.href=location.href.replace(/#.*/, +"")+u;p=setTimeout(j,a.fn[b].delay)}var o={},p,t=f(),v=function(n){return n},m=v,r=v;o.start=function(){p||j()};o.stop=function(){p&&clearTimeout(p);p=c};a.browser.msie&&!k&&function(){var n,u;o.start=function(){if(!n){u=(u=a.fn[b].src)&&u+f();n=a('