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/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 0ef481f2b..df5563844 100644 --- a/copyright.txt +++ b/copyright.txt @@ -5,9 +5,10 @@ # 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, # +# 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 # 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 0bb05b8b9..000000000 --- a/documentation/api/source/plugins/remotes.rst +++ /dev/null @@ -1,25 +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: - -.. autoclass:: openlp.plugins.remotes.lib.httpserver.HttpConnection - :members: - -.. autoclass:: openlp.plugins.remotes.lib.httpserver.HttpResponse - :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 20bff1ee7..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/alert.rst b/documentation/manual/source/alert.rst deleted file mode 100644 index 6877cef6f..000000000 --- a/documentation/manual/source/alert.rst +++ /dev/null @@ -1,57 +0,0 @@ -===== -Alert -===== - -From time to time it may be necessary to display a small unobtrusive message to -the congregation. In OpenLP this is known as an `Alert`. Examples could be a car -with the headlights left on, a parent needed in nursery, or anything else -somebody may need notified about. This is easily accomplished using the Alert, -which is accessible from the top Menu under :menuselection:`Tools --> Alert`. - -.. image:: pics/alert.png - -:guilabel:`Alert text:` Enter the message you want displayed in this box. - -:guilabel:`Parameter:` This box is used for words you want to insert into the -alert text. - -You will add the Parameter text into the alert text using “<>” (without -quotations). Anywhere in the Alert text that you add these two symbols, <> side -by side, will insert any text you have in the parameter box into the Alert -message. - -Examples of use ---------------- - -:guilabel:`Alert text:` The owner of the vehicle with license plate number <> -your lights are on. - -:guilabel:`Parameter:` HNN432 - -These two settings will display like this: - -The owner of the vehicle with the license plate number HNN432 your lights are on. - -You could also reverse this example: - -:guilabel:`Alert text:` HNN432 <> - -:guilabel:`Parameter:` left their lights on. - -`Will display like this:` HNN432 left their lights on. - -If you use the same alerts on a regular basis, Save your Alert and you will have -access to the alert with a click of the mouse. You may also click on `New` to -make a new alert or `Delete` an alert you do not need. - -When you are ready to Display your Alert you have two options. Clicking on -Display will display the Alert and the Alert Message window will remain open. -Clicking Display & Close will display the alert and close the Alert Message -window. - -All details of the display, font, color, size and position are adjusted from the -top Menu item :menuselection:`Settings --> Configure OpenLP`, Alert tab and the -instructions here. :doc:`configure` - -**Please note:** Alert Message is a Plugin that needs to be Active in the Plugin -List for use. \ No newline at end of file diff --git a/documentation/manual/source/bibles.rst b/documentation/manual/source/bibles.rst deleted file mode 100644 index 005ab72db..000000000 --- a/documentation/manual/source/bibles.rst +++ /dev/null @@ -1,158 +0,0 @@ -====== -Bibles -====== - -Managing Bibles in OpenLP is a relatively simple process. There are also -converters provided to get data from other formats into OpenLP. - -.. _bibleimporter: - -Bible 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 Bible Importer :menuselection:`File --> Import --> Bible`. -You may also enter the Bible Importer by clicking the :guilabel:`Import Icon:` - -.. image:: pics/themeimportexport.png - -You will see the Bible Importer window, click :guilabel:`Next`. - -.. image:: pics/bibleimport01.png - -After clicking :guilabel:`Next` you can select from the various types of -software that OpenLP will convert Bibles from. Click on the file folder icon to -choose the file(s) of the Bible database you want to import. See the sections -below for more information on the different formats that OpenLP will import. -Click :guilabel:`Next` to continue. - -.. image:: pics/bibleimport02.png - -After selecting your file(s), you'll be asked to fill in the details of the -Bible you are importing. Remember to check what information you need to display -for your Bible's translation, as some of them have strict rules around the -copyright notice. Click :guilabel:`Next` to continue. - -.. image:: pics/bibleimportdetails1.png - -After filling in the copyright details, OpenLP will start to import your Bible. -It may take some time to import your Bible so please be patient. - -.. image:: pics/bibleimportfinished1.png - -When the import has finished click :guilabel:`Finish` and you should be -ready to use your Bible in OpenLP. - -Importing from openlp.org 1.x -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Importing Bibles from openlp.org 1.x is a simple process. First you will need to -locate your version 1.x Bibles. Version 1.x Bibles have the `.bible` file -extension. - -Windows XP:: - - C:\Documents and Settings\All Users\Application Data\openlp.org\Data\Bibles\ - -Windows Vista / Windows 7:: - - C:\ProgramData\openlp.org\Data\Bibles\ - -After selecting all of the openlp.org 1.x Bibles you want to convert, click -:guilabel:`Next` to continue the import process. - -Importing OSIS Bibles -^^^^^^^^^^^^^^^^^^^^^ - -Importing OSIS files is very simple. Select OSIS as your import source, select -your OSIS Bible file and continue the import process. - -**About OSIS Formatted Bibles** - -The OSIS XML standard was designed to provide a common format for distribution -of electronic Bibles. More information can be found out at the `Bible Technologies website -`_. - -If you have any software installed that is part of the `Sword Project -`_ it can be easily converted. - -You can use the commands below convert Bibles from that software to OSIS format. - -The following commands are used in all platforms and the commands are case -sensitive across all platforms. To convert a Bible using the command prompt in -Windows or a terminal in Linux or Mac OS X you would type:: - - mod2osis biblename > biblename.osis - -For example: if I wanted to convert a King James Version Bible I would type -something similar to this:: - - mod2osis KJV > kjv.osis - -You may also wish to dictate a file location for the conversion to place the -osis file for example:: - - mod2osis KJV > /home/user/bibles/kjv.osis - -Importing OpenSong Bibles -^^^^^^^^^^^^^^^^^^^^^^^^^ - -Converting from OpenSong you will need to locate your bibles 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:`Bibles`. This folder should -contain files with all your bibles in them without a file extension. (file.xmms). -When you have located this folder you will need to select the bible from the -folder. - -You may also import downloaded bibles from OpenSong. The process is the same, -except you will need to extract the bible from a zip file. This is usually done -by right clicking on the downloaded file and select `Extract` or `Extract Here`. - -After selecting the OpenSong Bibles you want to import, follow the rest of the -import process. When the import has finished you should be ready to use your -OpenSong Bibles. - -Importing Web Download Bibles -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -**About Web Download** - -OpenLP provides a Web Download method to import Bibles when you do not have a -locally installed Bible available. The Web Download method registers the Bible -in OpenLP like the other bibles only it downloads the verses as you need them. -This import is not meant to be used as your sole source for Bibles, but rather -as another option and does require an internet connection. - -To use the web download feature select web download from the import wizard. - -You can select from several options of location to download from and also -what Bible translation you need. You will probably want to choose the location -from where you get the best performance or has the translation you need. - -.. image:: pics/webbible1.png - -You can also select a proxy server if needed from the `Proxy Server` tab. Your -network administrator will know if this is necessary, in most cases this will -not be needed. - -.. image:: pics/webbibleproxy1.png - -After selecting your download location and the Bible you wish to use, click -:guilabel:`Next` to continue the import process. When your import is completed -you should now be ready to use the web bible. - -Importing CSV formatted Bibles -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -If you have a Bible in .csv format OpenLP can import it. CSV Bibles will -consist of two files a `books` file and a `verse` file. Select CSV from the list -of Bible types to import. - -You are now ready to select your .csv files. You will need to select both your -books and verse file location. - -.. image:: pics/csvimport1.png - -After you have selected the file locations you can continue with the import -process. Once it is complete you should be ready to use your imported CSV Bible. diff --git a/documentation/manual/source/conf.py b/documentation/manual/source/conf.py deleted file mode 100644 index 2e9c0a88f..000000000 --- a/documentation/manual/source/conf.py +++ /dev/null @@ -1,228 +0,0 @@ -# -*- coding: utf-8 -*- -# -# OpenLP documentation build configuration file, created by -# sphinx-quickstart on Thu Sep 30 21:24:54 2010. -# -# 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 sys -import os - -# 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('.')) - -# -- General configuration ----------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = [] - -# 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-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'OpenLP' -copyright = u'2004 - 2011, 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 = '2.0' - -# 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 patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# 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 = True - -# 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. See the documentation for -# a list of builtin themes. -if sys.argv[2] == 'qthelp' or sys.argv[2] == 'htmlhelp': - html_theme = 'openlp_qthelp' -else: - 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 sys.argv[2] == 'html': - 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 = [os.path.join(os.path.abspath('..'), 'themes')] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -html_title = u'OpenLP 2.0 Reference 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 = 'pics/logo.png' - -# 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 = 'pics/openlp.ico' - -# 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_domain_indices = 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, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = 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 = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'OpenLP' - - -# -- 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 Reference Manual', - u'Wesley Stout', '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 - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = 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_domain_indices = True - - -# -- Options for manual page output -------------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'openlp', u'OpenLP Reference Manual', - [u'Wesley Stout'], 1) -] diff --git a/documentation/manual/source/configure.rst b/documentation/manual/source/configure.rst deleted file mode 100644 index b12ada062..000000000 --- a/documentation/manual/source/configure.rst +++ /dev/null @@ -1,376 +0,0 @@ -================== -Configuring OpenLP -================== - -OpenLP has many options you can configure to suit your needs. Most options are -self-explanatory and we will quickly review them. - -To configure OpenLP, click on :menuselection:`Settings --> Configure OpenLP...` - -The plugins you have activated will have configure options. If all the plugins -are activated there will be 9 tabs across the top you can configure. - -General Tab -=========== - -.. image:: pics/configuregeneral.png - -Monitors -^^^^^^^^ -To select the monitor you want to display OpenLP on, click the drop-down box -and choose. - -Display if a single screen -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -When this box is selected, you will be able to see your display on a separate -window on the monitor you are using. Click the display and push the Esc key -on your keyboard to close the display window. - -Application Startup -^^^^^^^^^^^^^^^^^^^ - -**Show blank screen warning:** - -When this box is selected, you will get a warning when opening OpenLP that the -output display has been blanked. You may have blanked it and shut down the -program and this will warn you it is still blanked. - -**Automatically open the last service:** - -When this box is selected, OpenLP will remember the last service you were -working on when you closed the program. - -**Show the splash screen:** - -The OpenLP logo is displayed while OpenLP loads when this checkbox is checked. -This is useful to give some indication that the program is loading. - -**Check for updates to OpenLP** - -OpenLP will check to see if there is a newer version available on a regular -basis when this checkbox is checked. Please note that this requires Internet -access. - -Application Settings -^^^^^^^^^^^^^^^^^^^^ - -**Prompt to save before starting a new service** - -When this box is selected, OpenLP will prompt you to save the service you are -working on before starting a new service. - -**Automatically preview next item in service** - -When this box is selected, the next item in the Service Manager will show in the -Preview pane. - -**Unblank display when adding new live item** - -When using the :guilabel:`blank to` button with this checkbox checked, on going -live with the next item, the screen will be automatically re-enabled. If this -checkbox is not checked you will need to click the :guilabel:`blank to` button -again to reverse the action. - - -**Slide loop delay** - -This setting is the time delay in seconds if you want to continuously loop -images, verses, or lyrics. This control timer is also accessible on the "live -toolbar. - -CCLI Details -^^^^^^^^^^^^ - -**CCLI number** - -If you subscribe to CCLI, this box is for your License number. This number is -also displayed in the Song Footer box. - -Display Position -^^^^^^^^^^^^^^^^ -This setting will default to your computer monitor. It will override the output -display combo box. If your projector display is different, select the Override -display position and make the changes here to match your projector display. This -option also comes in handy when you have the "Display if a single screen" box -selected. You can make the display smaller so it does not cover your whole -screen. - -Themes Tab -========== - -.. image:: pics/configurethemes.png - -Global Theme -^^^^^^^^^^^^ - -Choose the theme you would like to use as your default global theme from the -drop down box. The theme selected appears below. The global theme use is -determined by the Theme Level you have selected. - -Theme Level -^^^^^^^^^^^ - -Choose from one of three options for the default use of your theme. - -**Song Level:** - -With this level selected, your theme is associated with the song. The theme is -controlled by adding or editing a song in the Song editor and your song theme -takes priority. If your song does not have a theme associated with it, OpenLP -will use the theme set in the Service Manager. - -**Service Level:** - -With this level selected, your theme is controlled at the top of the Service -Manager. Select your default service theme there. This setting will override -your Song theme. - -**Global Level:** - -With this level selected, all songs and verses will use the theme selected on -the left in the Global Theme drop down. - -Advanced Tab -============ - -.. image:: pics/configureadvanced.png - - -UI Settings (user interface) -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -**Number of recent files to display:** - -Set this number for OpenLP to remember your last files open. These will show -under File. - -**Remember active media manager tab on startup:** - -With this box selected OpenLP media manager will open on the same tab that it -was closed on. - -**Double-click to send items straight to live:** - -With this box selected, double-clicking on anything in the Media Manager will -immediately send it live instead of to Preview. - -**Expand new service items on creation:** - -With this box selected, everything you add to the Service Manager will be -expanded so you can see all the verses, lyrics and presentations, line by line. -When you open OpenLP, everything will automatically be expanded in the Service -Manager. - -Songs Tab -========= - -.. image:: pics/configuresongs.png - -Songs Mode -^^^^^^^^^^ - -**Enable search as you type:** - -With this box selected, Media Manager/Songs will display the song you are -searching for as you are typing. If this box is not selected, you need to type -in your search box and then click on the Search button. - -**Display verses on live tool bar:** - -With this box selected, a Go To drop down box is available on the live toolbar -to select any part of the verse type you want displayed live. - -**Update service from song edit:** - -With this box selected and you edit a song in the media manager, the results -will also change the song if it is added to the Service Manager. If this box -is not selected, your song edit changes will only be available in the Service -Manager if you add it again. - -**Add missing songs when opening service:** - -With this box selected, when you open an order of service created on another -computer, or if one of the songs are no longer in your Media Manager, it will -automatically enter the song in your Songs Media Manager. If this box is not -checked, the song is available in the service but will not be added to the -Media Manager. - -Bibles Tab -========== - -.. image:: pics/configurebibles.png - -Verse Display -^^^^^^^^^^^^^ - -**Only show new chapter numbers:** - -With this box selected, the live display of the verse will only show the -chapter number and verse for the first verse, and just the verse numbers after -that. If the chapter changes, the new chapter number will be displayed with the -verse number for the first line, and only the verse number displayed thereafter. - -**Display style:** - -This option will put brackets around the chapter and verse numbers. You may -select No Brackets or your bracket style from the drop down menu. - -**Layout style:** - -There are three options to determine how your Bible verses are displayed. - -`Verse Per Slide` will display one verse per slide. -`Verse Per Line` will start each verse on a new line until the slide is full. -`Continuous` will run all verses together separated by verse number and chapter -if chapter is selected to show above. - -**Note: Changes do not affect verses already in the service.** - -**Display second Bible verses:** - -OpenLP has the ability to display the same verse in two different Bible -versions for comparison. With this option selected, there will be a Second -choice in the Bible Media Manager to use this option. Verses will display with -one verse per slide with the second Bible verse below. - -**Bible theme:** - -You may select your default Bible theme from this drop down box. This selected -theme will only be used if your `Theme Level` is set at `Song Level`. - -**Note: Changes do not affect verses already in the service.** - - -Presentations Tab -================= - -.. image:: pics/configurepresentations.png - -Available Controllers -^^^^^^^^^^^^^^^^^^^^^ - -OpenLP has the ability to import OpenOffice Impress or Microsoft PowerPoint -presentations, and use Impress, PowerPoint, or PowerPoint Viewer to display -them and they are controlled from within OpenLP. Please remember that in order -to use this function, you must have Impress, PowerPoint or PowerPoint Viewer -installed on your computer because OpenLP uses these programs to open and run -the presentation. You may select your default controllers here in this tab. - -Advanced -^^^^^^^^ - -**Allow presentation application to be overridden** - -With this option selected, you will see `Present using` area with a dropdown -box on the Presentations toolbar in Media Manager which gives you the option -to select the presentation program you want to use. - -Media Tab -========= - -.. image:: pics/configuremedia.png - -Media Display -^^^^^^^^^^^^^ - -**Use Phonon for video playback** - -If you are having trouble displaying media, selecting this box could help. - -Custom Tab: -=========== - -.. image:: pics/configurecustom.png - -Custom Display -^^^^^^^^^^^^^^ - -**Display Footer** - -With this option selected, your Custom slide Title will be displayed in the -footer. - -**Note: If you have an entry in the Credits box of your custom slide, title and -credits will always be displayed.** - -Alerts Tab -========== - -.. image:: pics/configurealerts.png - -Font -^^^^ - -**Font name:** - -Choose your desired font from the drop down menu - -**Font color:** - -Choose your font color here. - -**Background color:** - -Choose the background color the font will be displayed on. - -**Font size:** - -This will adjust the size of the font. - -**Alert timeout:** - -This setting will determine how long your Alert will be displayed on the screen, -in seconds. - -**Location:** - -Choose the location where you want the alert displayed on the -screen, Top, Middle or Bottom. - -**Preview:** - -Your choices will be displayed here. - -Remote Tab -=========== - -.. image:: pics/configureremotes.png - -OpenLP gives you the ability to control your Service Manager from a remote -computer through a web browser. This was written actually for a nursery or day -care where a "come and get YYYY" message could be triggered remotely. It has -now become an interface to control the whole service remotely. - -An example of one use for this would be if you have a missionary with a -PowerPoint presentation, it may be easier for that missionary to remotely -connect to your projection computer and change the slides when he wants to. - -To use this feature you will need to be on a network, wired or wireless, know -the IP address of the projection computer and enter that IP address and port -number in the remote computer's web browser. - -To find your projection computer's IP address for Windows, open Command Prompt -and type in “ipconfig” (without quotations), press Enter key and your IP -address will show. In Linux, open Terminal and type “ifconfig” (without -quotations), and use the IP address after “inet addr:” The IP address will -always have a format of xxx.xxx.xxx.xxx where x is one to three digits long. - -Server Settings -^^^^^^^^^^^^^^^ - -Serve on IP address: Put your projection computer's IP address here. - -Port Number -^^^^^^^^^^^ - -You can use the default port number or change it to another number. - -With these two settings written down, open a web browser in the remote computer -and enter the IP address followed by a colon and then the port number, ie: -192.168.1.104:4316 then press enter. You should now have access to the OpenLP -Controller. If it does not come up, you either entered the wrong IP address, -port number or one or both computer's are not connected to the network. - - - diff --git a/documentation/manual/source/dualmonitors.rst b/documentation/manual/source/dualmonitors.rst deleted file mode 100644 index 7e39edb45..000000000 --- a/documentation/manual/source/dualmonitors.rst +++ /dev/null @@ -1,231 +0,0 @@ -.. _dualmonitors: - -================== -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 -will vary depending on operating system. - -Most modern computers 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 the outputs pictured above since your laptop 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 setup consist of your normal single monitor, with your -projector hooked up to your computer as the second monitor. With the option of -extending your desktop across the second monitor, or your operating system's -equivalent. - -**Special Note For Projectors Using USB Connections** - -Users have reported experiencing difficulties when using a projector with a USB -connection, as third party software is often required to properly configure -dual monitors. If possible, it is best to use a direct output (VGA, DVI, HDMI, -S-Video) from your machine's video card. If a USB connection is your only option -please consult the manufacturer's manual for instructions on a proper setup. - -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 may also bypass this step by a 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`. Click on the monitor 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. -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 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 - -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 - -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. - -Linux Systems With Intel Video -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Generally systems with Intel video cards work very well. They are well supported -by open source drivers. There are, however, a couple of issues that may require -some work arounds. - -**Resolution Issue** - -There is a limitation with certain cards which limits the total resolution to -2048x2048, so both monitors can not have a total resolution totaling more than -that. To work around this it may be necessary to position your monitor as a top -or bottom monitor as opposed to the typical side by side setup. This can easily -be accomplished through your desktop's control of monitors. Please see the -sections on dual monitors with KDE and GNOME above. - -**Primary Monitor Issues** - -With certain cards your system may get confused on what is the primary display. -For example many users will be using a laptop. You will want your laptop screen -to be the primary screen, and your projector to be the secondary monitor. -Certain Intel cards reverse this. To work around this you will need to know the -name of your monitor. If you are a KDE user this info is given to you in the -display settings. If you are not using KDE enter the following in a terminal -without your projector connected to your computer:: - - user@linux:~ $ xrandr -q - -This will give you a long string of output. Screen names will be something along -the lines of LVDM, VGA-0 or some convention similar to that. Without your -projector connected to your computer only one monitor will show as being -connected. That will be the monitor you will need to use as the primary. Now -connect your projector and enter:: - - user@linux:~ $ xrandr --output LVDM --primary - -**Note** it has been reported that when this issue is occurring you will not -want to connect your projector until your desktop is running. diff --git a/documentation/manual/source/faq.rst b/documentation/manual/source/faq.rst deleted file mode 100644 index abba7a553..000000000 --- a/documentation/manual/source/faq.rst +++ /dev/null @@ -1,572 +0,0 @@ -========================== -Frequently Asked Questions -========================== - -General Questions -================= - -When is the release date for OpenLP 2.0? ----------------------------------------- - -*It will be ready when it's ready!* We do not have fixed dates, but we have -set some `targets for the releases `_. -If you take part in the `development `_, -start to `test OpenLP `_ and -`provide feedback `_ this will speed up -the progress. - -Can I help with OpenLP? ------------------------ - -OpenLP is possible because of the commitment of individuals. If you would like -to help there are several things that you can get involved with. Please see: -`Contributing `_ -for more information. - -I use and like OpenLP and would like to tell others online. Where can I do this? --------------------------------------------------------------------------------- - -A variety of places! - -* Are you on facebook? Then `become a fan `_ -* Are you on twitter? Then `follow openlp `_, and - retweet the announcements. -* Give us a thumbs up on the - `SourceForge project page `_ -* If you have a website or blog, then link to our site http://www.openlp.org - with a few words saying what the software is and why you like it. -* Add a placemark on our `Worldwide Usage map `_, - so others in your locality can see someone close by is using it. -* If you are a member of any Christian Forums or websites, and their rules allow - it, then perhaps review the software or ask others to review it. - -What operating systems will OpenLP 2.0 support? ------------------------------------------------ - -OpenLP 2.0 is designed to be cross platform. Currently it has been known to run -on Windows (XP, Vista, 7), Linux (Ubuntu/Kubuntu, Fedora), FreeBSD & Mac OSX. -`Please let us know `_ if you've -successfully run it on something else. - -Which programming language is 2.0 developed in? ------------------------------------------------ - -OpenLP 2.0 is written in `Python `_ and uses the -`Qt4 toolkit `_. Both are cross-platform which allows the -software to run on different types of machine and so allow more people access to -free worship software. Python is one of the easier programming languages to -learn, so this helps us develop and `find bugs `_ -quicker, and also allows more developers to contribute with the project. - -Which written languages does OpenLP support? --------------------------------------------- - -The beta now has support for a few languages which can be seen on the -:menuselection:`Settings -->Translate` menu. However some of these translations -are incomplete. If you would like to help complete or start to translate OpenLP -into your language then see the `Getting started page `_. - -What is a beta release? ------------------------ - -A beta release is a release which is almost feature complete and is fairly -stable. However there may still be a few `features `_ -to complete, and `bugs `_ we've not yet fixed. -It is used by several people without serious problems. However there is a small -possibility that it could still crash occasionally or do unexpected things. It -is intended for those who want the latest version, and are prepared to give the -program a good test before using it in a live situation to ensure they won't -encounter any unexpected problems. If you want to have a look at the latest beta -release then just `download it `_. - -Should I use this beta release at Church in my Sunday services? ---------------------------------------------------------------- - -As long as you have taken the time to run through your service a couple of times -on your target machine, the answer to this question is **yes**. The OpenLP -team believes that OpenLP 2.0 beta 1 is stable enough to be used in Sunday -services. As of beta 1, there are a good number of churches already using -version 2.0 successfully. The OpenLP team works hard to make sure each release -is solid, but cannot yet guarantee that everything works perfectly, or even -correctly. - -If however your congregation is made up of 85 year old women who snarl when you -suggest replacing the gas lamps with electric light bulbs and consider the pipe -organ too loud and modern, then we recommend sticking with version 1.2 for now. - -As of beta 1, version 1.2 of OpenLP is "put out to pasture" - no more -development or even bugfixes will be performed on that version. - -Upgrading -========= - -Does 2.0 replace 1.2, or can they be run side by side? ------------------------------------------------------- - -It is perfectly safe to install 2.0 on a system with 1.2. Both versions are -installed in separate places, so you can still go back to 1.2. You can even run -them at the same time! - -2.0 stores its data in a separate folder to 1.2, so your data is perfectly safe, -and whatever you do in 2.0 will not damage 1.2 - -Are 1.2 and 2.0 compatible? ---------------------------- - -No. However imports exist to transfer your data to the new version. - -I have a computer that is quite old, should I upgrade? ------------------------------------------------------- - -2.0 does require significantly more resources than v1.2. Therefore if your -computer does not have much memory you may find 2.0 will struggle, `especially` -when changing between slides. - -Why can I not see my 1.2 songs, bibles and themes in 2.0? ---------------------------------------------------------- - -This is an beta release, which means it is not finished and one of the things we -haven't completely finished yet is importing 1.2 data automatically. We plan to -do this `Version 1.9.6 (beta 2) `_. - -How do I transfer my 1.2 song database? ---------------------------------------- - -In OpenLP 2.0, go to the :menuselection:`File --> Import --> Song` menu. -In the Wizard that appears, click Next and choose "openlp.org v1.x" from the -Format list. Click the search button on the Filename prompt, and at the bottom -of the dialog, copy the following into the File name prompt:: - - %ALLUSERSPROFILE%\Application Data\openlp.org\Data\songs.olp - -*(This must be in the popup file chooser dialog. Don't enter it directly into -the wizard).* - -Click Open, then in the wizard just click Next and wait for the import to complete. - -How do I transfer my 1.2 Bibles? --------------------------------- - -In OpenLP 2.0, go to the :menuselection:`File --> Import --> Bible` menu. -In the Wizard that appears, click Next and choose "openlp.org v1.x" from the -Format list. -Click the search button on the Filename prompt, and at the bottom of the dialog, -copy the following into the File name prompt:: - - %ALLUSERSPROFILE%\Application Data\openlp.org\Data\Bibles - -*(This must be in the popup file chooser dialog. Don't enter it directly into -the wizard).* - -Choose the Bible, Click Open, then in the wizard just click Next, enter the -License details, and wait for the import to complete. - -How do I transfer my 1.2 Themes? --------------------------------- - -In openlp.org v1, export each theme by selecting it in the Theme Manager, and -then clicking the picture of a blue folder with red arrow on the Theme Managers -toolbox. This theme file can then be imported into V2 using the -:menuselection:`File --> Import --> Theme` menu. - -I can't get my 2.0 theme to look the same as 1.2 ------------------------------------------------- - -OpenLP 2.0 is a complete rewrite using a completely different programming -language so it would work on different types of system. There are differences in -how the old and new languages draw text on the screen, and therefore it is -unlikely you'll get an exact match. - -Using OpenLP -============ - -Is there a manual or any documentation for 2.0? ------------------------------------------------ - -Some folks are working on a brand new manual for OpenLP 2.0. You can find the -latest version of this manual at http://manual.openlp.org. If you need help, -use the live chat feature or ask in the forums. If you would like to help write -the manual, please let us know - we are always happy for new volunteers to join -the team and contribute to the project. - -I've started OpenLP, but I can't see the songs or bibles section in the Media Manager -------------------------------------------------------------------------------------- - -When you installed OpenLP, the first time wizard would have asked which plugins -you wanted, and songs and bibles should have been selected. If for some reason -they were not, then you will need to activate them yourself. See -`How do I activate / deactivate a plugin `_ -for instructions. - -How do I activate / deactivate a plugin? ----------------------------------------- - -Plugins can be turned on and off from the Plugin List Screen. Select the plugin -you wish to start/stop and change it's status. You should not need to restart -OpenLP. - -What are these plugins that I keep seeing mentioned? ----------------------------------------------------- - -The plugins allow OpenLP to be extend easily. A number have been written -(Songs, Bibles, Presentations) etc but it is possible for the application to be -extended with functionality only you require. If this is the case then go for -it but lets us know as we can help and it may be something someone else wants. - -How do I enable PowerPoint/Impress/PowerPoint Viewer? ------------------------------------------------------ - -First of all ensure that the presentation plugin is enabled (see above). -Then to enable a presentation application, go to the `Settings` dialog, switch -to the `Presentations` tab and check one of the enabled checkboxes. OpenLP will -automatically detect which of the three you have installed, and enable the -appropriate checkbox(es). Check the applications you require, and then restart -OpenLP for the change to be detected. -Note, PowerPoint Viewer 2010 is not yet supported, use 2003 or 2007. - -See also `I'm on Windows and PowerPoint is installed, but it doesn't appear as an option `_ -and `Why is there no presentations plugin available on OS X? `_ - -Why is there no presentations plugin available on OS X? -------------------------------------------------------- - -Currently the presentations plugin is not bundled with OpenLP on OS X. The -reason for that is that the OpenOffice.org version on Mac OS X does not contain -the (more exact: does only contain a broken) interoperability component (the so -called pyuno bridge) which could be used by OpenLP. As soon as the -interoperability component works on OS X we can re-enable the plugin and bundle -it. We are really sorry for that. - -Is it possible to get Bible x? How? ------------------------------------ - -The Bible plugin has a much improved `Import Wizard` which can import Bibles -from a variety of sources. The following sources are supported: - -CSV - The same format as documented for `openlp.org 1.x `_. - -OSIS - An XML format for Bible. You can export Bibles from the `Sword Project `_ - into OSIS using the ``mod2osis`` tool. After using the Sword software Media - Manager (or other Sword frontend, like BibleTime or Xiphos) to download the - required Bible, run the following command from the command line (works on - Windows and Linux):: - - mod2osis > .osis - - The ```` parameter is the name of your Bible, as you see it in Sword. - Note that the ```` is case sensitive on all environments. Once you - have exported your Bible to OSIS, the Bible import wizard will the read - ``.osis`` file and import your Bible. - -OpenSong - OpenSong have a good selection of Bibles on their - `download page `_. - -Web Download - OpenLP can download Bibles on demand from the following 3 sites: - - * `Crosswalk `_ - * `BibleGateway `_ - * `BibleServer `_ - -Why do my Bible verses take a long time to load? ------------------------------------------------- - -In order to better conform to copyright law, the Web Download Bibles are not -downloaded when you import them, but on the fly as you search for them. As a -result, the search takes a little longer if you need to download those -particular verses. Having said that, the Web Download Bibles cache downloaded -verses so that you don't need to download them again. - -My Bible is on the Web Download sites, but my Church isn't on the internet. What options do I have? ---------------------------------------------------------------------------------------------------- - -When you create and save a service, all the items in the service are saved with -it. That means any images, presentations, songs and media items are saved. This -is also true for bibles. What this means is you can create the service on your -home computer, insert a Bible passage from the web, save it and then open the -service using your church computer and voila, the Bible passage should be there! -Note this can also be done with songs, etc! - -(Advanced) Where do I find the configuration file? -================================================== - -Linux, FreeBSD & PC-BSD ------------------------ - -If your distribution supports the XDG standard, you'll find OpenLP's -configuration file in:: - - /home//.config/OpenLP/OpenLP.conf - -If that file and/or directory does not exist, look for:: - - /home//.openlp/openlp.conf - -```` is your username. - -OS X ----- - -You'll find your configuration file here:: - - /Users//Library/Preferences/org.openlp.OpenLP.plist - -```` is your username. - -Windows -------- - -On Windows, OpenLP does not use a configuration file, it uses the Windows -registry. You can find the settings here:: - - HKEY_CURRENT_USER\Software\OpenLP\OpenLP - -Troubleshooting -=============== - -Something has gone wrong, what should I do to help get it fixed? ----------------------------------------------------------------- - -If you have found an error in the program (what we call a bug) you should report -this to us so that OpenLP can be improved. Before reporting any bugs please -first make sure that there isn't already a bug report about your problem: - -#. Check the `Launchpad bug list `_ -#. `OpenLP support System `_ -#. Check the `bug reports `_ forum - -If there **is already a bug report**, you may be able to help by providing -further information. However, **if no one else has reported** it yet, then -please post a new bug report. - -#. The **preferred place** for reporting bugs is the - `bugs list `_ on Launchpad. -#. Alternatively, if you don't have a Launchpad account and don't want to sign - up for one, you can post in the - `bug reports forum `_. -#. If none of these ways suits you, you can send an email to - ``bugs (at) openlp.org``. - -What information should I include in a bug report? --------------------------------------------------- - -Since OpenLP 1.9.4, there is a bug report dialog which automatically opens when -OpenLP hits a serious bug. However, this doesn't appear all the time, and in -some behavioural bugs, you'll have to file a bug report yourself. The following -items are information the developers need in order to reproduce the bug. - -Operating System - Include information such as the version of your operating system, the - distribution (e.g. Ubuntu, Fedora, etc.) if you're using Linux, or the - edition (e.g. Home, Basic, Business, etc.) if you're using Windows. - -Version of OpenLP - You can find out the version of OpenLP by going to :menuselection:`Help --> About` - -Steps to Reproduce - The exact steps the developers need to follow in order to reproduce the bug. - -Version of MS Office or OpenOffice - If you're using the song imports or the presentation plugin, you'll need to - supply the version of Office, OpenOffice.org or LibreOffice. - -Bible Translation and Source - If the bug occurred while you were working with Bibles, specify the - translation of the Bible, and the source format if you imported it yourself. - -**Any** Other Information - Often bugs are caused by something that might not seem to be directly - related to the bug itself. If you have any other information with regards to - actions you performed or other activities when the bug occurred, it would be - welcomed by the developers. - -The more information you give us, the better we can help you. - -I've been asked to email a debug log, where do I find this? ------------------------------------------------------------ - -We may need a debug log to help pin-point the issue. A new log file is created -each time you start OpenLP so copy the file before you run the software a second -time. On Windows a Debug option is available in the start menu. On other systems, -you will need to run OpenLP from the command line, with the following -option: ```-l debug```. Please note, that is a lowercase **L**. - -If you haven't been given a specific email address to send it to, then please do -not paste the log contents straight into a forum post. Instead, open the log -file in a text editor (such as notepad on Windows) and copy and paste the -contents into somewhere like `pastebin.com `_. Then give us -the link to the page that is created. - -Windows -^^^^^^^ - -Find the OpenLP 2.0 folder in your Start menu. Choose the "OpenLP (Debug)" option. - -OpenLP will start up. Go to the :menuselection:`Tools --> Open Data Folder` menu -option, and an Explorer window will appear containing folders such as alerts, -bibles, custom etc. Keep this Explorer window open. - -Now repeat the steps you need to take in OpenLP to reproduce the problem you had, -and then close down OpenLP. - -In the Explorer window you left open, navigate up one level into the openlp -folder. You will see the ``openlp.log`` file. This is the file to e-mail. - -Linux/FreeBSD -^^^^^^^^^^^^^ - -If you installed OpenLP from a package:: - - @:~$ openlp -l debug - -Alternately, if you're running OpenLP from source:: - - @:~$ ./openlp.pyw -l debug - -If your Linux distribution supports the XDG standard, you'll find the log in:: - - ~/.cache/openlp/openlp.log - -Otherwise, you'll find the log file in:: - - ~/.openlp/openlp.log - -Mac OS X -^^^^^^^^ - -Open Terminal.app and navigate to where you installed OpenLP, usually -``/Applications``:: - - @:~$ cd /Applications - -Then go into the OpenLP.app directory, down to the OpenLP executable:: - - @:~$ cd OpenLP.app/Contents/MacOS - -And then run OpenLP in debug mode:: - - @:~$ ./openlp -l debug - -Once you've done that, you need to get the log file. In your home directory, -open the Library directory, and the Application Support directory within that. -Then open the openlp directory, and you should find the openlp.log file in that -directory:: - - /Users//Library/Application Support/openlp/openlp.log - -```` is your username. - -I'm on Windows and PowerPoint is installed, but it doesn't appear as an option ------------------------------------------------------------------------------- - -Try installing the `Visual C++ Runtime Redistributable `_. - -The command line shows many error messages ------------------------------------------- - -When running OpenLP from the command line, you might get something like this:: - - WARNING: bool Phonon::FactoryPrivate::createBackend() phonon backend plugin could not be loaded - WARNING: bool Phonon::FactoryPrivate::createBackend() phonon backend plugin could not be loaded - WARNING: Phonon::createPath: Cannot connect Phonon::MediaObject ( no objectName ) to VideoDisplay ( no objectName ). - WARNING: Phonon::createPath: Cannot connect Phonon::MediaObject ( no objectName ) to Phonon::AudioOutput ( no objectName ). - WARNING: bool Phonon::FactoryPrivate::createBackend() phonon backend plugin could not be loaded - -These error messages indicate that you need to install an appropriate backend -for Phonon. - -Linux/FreeBSD -^^^^^^^^^^^^^ - -If you're using Gnome, you need to install the GStreamer backend for Phonon. On -Ubuntu you would install the ```phonon-backend-gstreamer``` package:: - - @:~$ sudo aptitude install phonon-backend-gstreamer - -If you're using KDE, you need to install the Xine backend for Phonon. On Kubuntu -you would install the ```phonon-backend-xine``` package:: - - @:~$ sudo aptitude install phonon-backend-xine - -If you know which audiovisual system you're using, then install the appropriate -backend. - -phonon-backend-vlc may also be worth trying on some systems. - -Windows & Mac OS X -^^^^^^^^^^^^^^^^^^ - -Phonon should already be set up properly. If you're still having issues, let the -developers know. - -I've upgraded from 1.9.2 to a newer version, and now OpenLP crashes on load ---------------------------------------------------------------------------- - -You need to upgrade your song database. See this `blog post `_ -for information on how to do this. - -I've upgraded to 1.9.5, and now OpenLP has duplicates of many songs in the Media Manager ----------------------------------------------------------------------------------------- - -You need to run :menuselection:`Tools --> Re-index Songs`. - -There are no menu icons in OpenLP ---------------------------------- - -This may affect (only) Linux users with XFCE or Gnome. To solve the problem, -follow the instructions on `this bug report `_. - -JPEG images don't work ----------------------- - -This is a known issue on some Mac OS X 10.5 systems, and has also been seen on -Windows XP too. The solution is to convert the image into another format such as -PNG. - -MP3's and other audio formats don't work ----------------------------------------- - -This is a known issue on some systems, including some XP machines, and we have -no solution at the moment. - -Videos can be slow or pixelated. Background Videos are very slow ----------------------------------------------------------------- - -If you are just playing videos from the Media plugin, try selecting the -:guilabel:`Use Phonon for Video playback` option in the Media configuration, -accessible by going to :menuselection:`Settings --> Configure OpenLP --> Media`. -As for text over video, we have no solution for speeding this up. Reducing the -monitor resolution and avoiding shadows and outline text will help. We are -hoping a future release of the toolkit we are using (QtWebKit) will help improve -this, but there is no timeframe at present. - -Features -======== - -What new features will I find in 2.0? -------------------------------------- - -Since 2.0 was a rewrite from the ground up, you won't find a great deal of new -features since initially we want to ensure all the 1.2 features are included. -However the developers have managed to sneak a few in. Take a look at the -`complete list `_. - -Why hasn't popular feature request X been implemented? ------------------------------------------------------- - -We made a decision to first implement 1.2 features, before going wild on new -features. There are only a handful of developers working in their spare time. If -we were to try and include everything we wanted to implement, then 2.0 would not -likely ever get released. - -I have a great idea for a new feature, where should I suggest it? ------------------------------------------------------------------ - -First of all check it isn't on the `Feature Requests `_ -page. If it is, then you need to say no more, it's already been suggested! If it -isn't on the list, then head to the -`feature request forum `_ -and post the idea there. diff --git a/documentation/manual/source/glossary.rst b/documentation/manual/source/glossary.rst deleted file mode 100644 index fd00f4842..000000000 --- a/documentation/manual/source/glossary.rst +++ /dev/null @@ -1,76 +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 the 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. - -Platform --------- - -When the word platform is used, it is usually referring to your operating system, -Windows, Linux or MAC OS. - -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 service in OpenLP. -The service file consist of **Service Items** - -Service Item ------------- - -Service items 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 where your media items go live. You can also save, open, and edit -services files from here. - -.. 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 and 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 6ed9c7258..000000000 --- a/documentation/manual/source/index.rst +++ /dev/null @@ -1,25 +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 - wizard - dualmonitors - configure - bibles - themes - mediamanager - songs - alert - faq - troubleshooting diff --git a/documentation/manual/source/introduction.rst b/documentation/manual/source/introduction.rst deleted file mode 100644 index b14ef3944..000000000 --- a/documentation/manual/source/introduction.rst +++ /dev/null @@ -1,43 +0,0 @@ -============= -Introduction -============= - -About ------ - -OpenLP stands for "Open Source Lyrics Projection" and is presentation software -developed for churches to provide a single easy to use interface for the -projection needs of a typical worship service. First created in 2004, it has -steadily grown in features and maturity such that it is now a mainstay in -hundreds of churches around the world. - -OpenLP has searchable databases of songs and Bible verses allowing them to be -projected instantly or saved in a pre-prepared order of service file. Themes -allow for a variety of presentation options and allow you to add attractive -visuals to enhance your presentations. PowerPoint and OpenOffice presentations, -videos and audio files can be run from within the program removing the need to -switch between different programs. Alert messages can be displayed so the -nursery or car park stewards can notify the congregation easily. Remote -capability allows the worship leader to change songs, or for alert messages to -be sent from anywhere on the network, even via a smart phone or tablet. - -Being free, this software can be installed on as many PCs as required, -including the home PCs of worship leader(s) at no additional cost. Compared to -the expensive site licenses and restrictions of commercial software we believe -OpenLP is the perfect choice for quality and value. Still in active development -by a growing team of enthusiastic developers, features are being added all the -time resulting in continual improvement of the software. - -OpenLP is licensed under the GNU Generic Public License, which means -that it is free to use, distribute, modify, and it stays free. - -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 `view the license on-line `_. diff --git a/documentation/manual/source/mediamanager.rst b/documentation/manual/source/mediamanager.rst deleted file mode 100644 index a3cd6bbe3..000000000 --- a/documentation/manual/source/mediamanager.rst +++ /dev/null @@ -1,251 +0,0 @@ -============= -Media Manager -============= - -Once you get your system set up for OpenLP you will be ready to add content to -your Service Manager. 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 :kbd:`Alt+F7`. You will want to click on the plugin to the left that you -would like to enable and select **active** from the drop down box to the right. - -.. image:: pics/plugins.png - - -You are now ready to add content for your service. - -Adding Media Content --------------------- - -This section will describe how to add the different types of media OpenLP is -capable of displaying. - -Songs -^^^^^ -Clicking on Songs in the Media Manager will display all of the songs you have -added by Title and Author/Authors. - -Using the buttons you can: - -`Add a song:` Brings up the dialog box to add a new song - -`Edit the selected song:` Brings up the dialog box to make changes to the song - -`Delete the selected song:` Removes the song from your song list - -`Preview the selected song:` Lets you see what your song will look like -displayed live - -`Send the selected song live:` This option will immediately display your song -live. - -`Add the selected Song to the service:` This will enter your song in the Service -Manager. You may also drag your song over to the Service Manager. - -`Maintain the list of authors, topics and books:` Brings up a dialog box to edit -Authors, Topics or Song Books. -Note: Right clicking on a song file will bring up some of the same options. - -Bibles -^^^^^^ -Clicking on Bibles in the Media Manager will display your options for searching -and adding chapters and verses to the Service Manager. - -Using the buttons you can: - -`Import a Bible:` This is discussed in detail on the Bible Importer page in the -manual. - -`Preview the selected Bible:` Sends your selected verses to the Preview window - -`Send the selected Bible live:` This option will immediately display your -selected verses live. - -`Add the selected Bible to the service:` This will enter your verses into the -Service Manager. You may also click and drag your verses over to the Service -Manager. - -**Quick tab** - -`Version:` Once you have imported a Bible, it will be displayed in the Version -drop down box. Select the version of the Bible you want to use here. - -**Second** - -If you have “Display second Bible verses” selected in Configure OpenLP, Bibles -tab, this choice will be visible. This option is for displaying another version -of the Bible for comparison. Both versions will be displayed. If there is not -enough space on one slide, the Second version will be displayed on the next -slide. - -Use the `Search` button to display your results in the box below. - -**Find** - -You will type your search query in this box for the following two searches. - -`Search type:` You may search for a specific verse using this format below. - -Book Chapter -Book Chapter-Chapter -Book Chapter:Verse-Verse -Book Chapter:Verse-End (this will display verses to the end of the chapter) -Book Chapter:Verse-Verse, Verse-Verse -Book Chapter:Verse-Verse,Chapter:Verse-Verse -Book Chapter:Verse-Chapter:Verse - -`Text Search:` - -You may also search by a keyword or words. The more words you use for the -search, the more you will narrow down your results. - -**Results** `Clear and Keep.` - -Each search you make will display your verses below. If you would like to -display verses that are out of sequence you may select `Keep` in the drop down -box and continue your search for the next verse or verses. All searches will be -shown and kept below. -If you select `Clear` from the drop down box, each new search clears the -previous search from the list below. - -**Advanced tab** - -This tab is convenient for selecting book, chapter and verse by name and number. -Version and Second are the same as in Quick tab above. Click on each box and -select the version you wish to display and Second version if you wish to display -an alternative version. - -Use the `Search` button to display your results in the box below. - -`Book:` Click on the drop down box and select the book you want to display. -`Chapter: Verse:` Select your chapter From and To and Verse From and To - -Results will work the same as the Quick tab above. - -Presentations -^^^^^^^^^^^^^ -Using the buttons you can: - -`Load a new presentation:` This brings up a dialog box to find your presentation -and list it in OpenLP. - -`Delete the selected Presentation:` This removes your Presentation from the list. -Please note: this will not delete the presentation from your computer, only from -the OpenLP list. - -`Preview the selected Presentation:` Sends your selected Presentation to the -Preview window - -`Send the selected Presentation live:` This option will immediately display your -selected Presentation live. - -`Add the selected Presentation to the service:` This will enter your -Presentation into the Service Manager. You may also click and drag your -Presentation over to the Service Manager. - -Right clicking on a Presentation file will bring up some of the same options. - -Images -^^^^^^ -Using the buttons you can: - -`Load a new Image:` This brings up a dialog box to find your Image and list it -in OpenLP. - -`Delete the selected Image:` This removes your Image from the list. Please note: -this will not delete the Image from your computer, just the OpenLP list. - -`Preview the selected Image:` Sends your selected Image to the Preview window - -`Send the selected Image live:` This option will immediately display your -selected Image live. - -`Add the selected Image to the service:` This will enter your Image into the -Service Manager. You may also click and drag your Image over to the Service -Manager. - -`Replace Live Background:` With an Image selected, clicking this button will -immediately replace the live background being displayed with your selection. -The Image will replace the theme background until the theme changes or the -"Remove Background" button is pressed. - - -Right clicking on an Image file will bring up some of the same options. - -Media -^^^^^ - -Media is an audio or video file. Generally if you can play or view your media -on your computer without OpenLP, you can also play it in OpenLP. - -Using the buttons you can: -`Load a new Media:` This brings up a dialog box to find your Media and list it -in OpenLP. - -`Delete the selected Media:` This removes your Media from the list. Please note: -this will not delete the Media from your computer, just the OpenLP list. - -`Preview the selected Media:` Sends your selected Media to the Preview window - -`Send the selected Media live:` This option will immediately display your -selected Media live. - -`Add the selected Media to the service:` This will enter your Media into the -Service Manager. You may also click and drag your Media over to the Service -Manager. - -`Replace Live Background:` With a Media file selected, clicking this button will -immediately replace the live background being displayed with your selection. - -Right clicking on a Media file will bring up some of the same options. - -Custom -^^^^^^ - -Custom gives you the option of creating your own slide. This could be useful for -displaying readings, liturgy or any text that may not be found in Songs or -Bibles. - -`Add a new Custom:` Brings up the dialog box to add a new Custom display. -`Edit the selected Custom:` Brings up the dialog box to make changes to the -Custom display. - -`Delete the selected Custom:` Remove the Custom from your list - -`Preview the selected Custom:` Lets you see what your Custom will look like -displayed live - -`Send the selected Custom live:` This option will immediately display your -Custom live - -`Add the selected Custom to the service:` This will enter your Custom in the -Service Manager. You may also drag your Custom over to the Service Manager. - -Right clicking on a Custom file will bring up some of the same options. - -When you Add a new Custom slide a dialog box will appear. - -`Title:` Name of your Custom slide. - -`Add:` After clicking on Add you will enter your text you want to display in -this box. To create multiple slides, click the Split Slide button. When you have -finished adding your text, click on the Save button. - -`Theme:` Select the theme you want to use for your Custom slide from this drop -down box. -`Credits:` Anything typed in this box will be displayed in the footer -information on the display. When you are finished, click the Save button. - -To Edit your slide, click on the Edit button to edit part of it or the Edit All -if you need to make multiple changes. Use the Up and Down arrows to change the -arrangement of your Custom slide. diff --git a/documentation/manual/source/pics/001-first-time-language.png b/documentation/manual/source/pics/001-first-time-language.png deleted file mode 100644 index 52e12e1b1..000000000 Binary files a/documentation/manual/source/pics/001-first-time-language.png and /dev/null differ diff --git a/documentation/manual/source/pics/002-first-time-wizard-welcome.png b/documentation/manual/source/pics/002-first-time-wizard-welcome.png deleted file mode 100644 index 9faad00a0..000000000 Binary files a/documentation/manual/source/pics/002-first-time-wizard-welcome.png and /dev/null differ diff --git a/documentation/manual/source/pics/003-first-time-wizard-plugins.png b/documentation/manual/source/pics/003-first-time-wizard-plugins.png deleted file mode 100644 index bf0dc9ec3..000000000 Binary files a/documentation/manual/source/pics/003-first-time-wizard-plugins.png and /dev/null differ diff --git a/documentation/manual/source/pics/004-first-time-wizard-songs.png b/documentation/manual/source/pics/004-first-time-wizard-songs.png deleted file mode 100644 index 7a6822fa5..000000000 Binary files a/documentation/manual/source/pics/004-first-time-wizard-songs.png and /dev/null differ diff --git a/documentation/manual/source/pics/005-first-time-wizard-bibles.png b/documentation/manual/source/pics/005-first-time-wizard-bibles.png deleted file mode 100644 index 235d24f4e..000000000 Binary files a/documentation/manual/source/pics/005-first-time-wizard-bibles.png and /dev/null differ diff --git a/documentation/manual/source/pics/006-first-time-wizard-themes.png b/documentation/manual/source/pics/006-first-time-wizard-themes.png deleted file mode 100644 index ea6debdd2..000000000 Binary files a/documentation/manual/source/pics/006-first-time-wizard-themes.png and /dev/null differ diff --git a/documentation/manual/source/pics/007-first-time-wizard-settings.png b/documentation/manual/source/pics/007-first-time-wizard-settings.png deleted file mode 100644 index afaeefe0e..000000000 Binary files a/documentation/manual/source/pics/007-first-time-wizard-settings.png and /dev/null differ diff --git a/documentation/manual/source/pics/009-first-time-wizard-progress.png b/documentation/manual/source/pics/009-first-time-wizard-progress.png deleted file mode 100644 index 7ffd64982..000000000 Binary files a/documentation/manual/source/pics/009-first-time-wizard-progress.png and /dev/null differ diff --git a/documentation/manual/source/pics/010-first-time-wizard-finished.png b/documentation/manual/source/pics/010-first-time-wizard-finished.png deleted file mode 100644 index ff6014a2e..000000000 Binary files a/documentation/manual/source/pics/010-first-time-wizard-finished.png and /dev/null differ diff --git a/documentation/manual/source/pics/011-first-time-wizard-song-import.png b/documentation/manual/source/pics/011-first-time-wizard-song-import.png deleted file mode 100644 index 30d29b2e5..000000000 Binary files a/documentation/manual/source/pics/011-first-time-wizard-song-import.png and /dev/null differ diff --git a/documentation/manual/source/pics/012-openlp-main-window.png b/documentation/manual/source/pics/012-openlp-main-window.png deleted file mode 100644 index 7e7fb9fa6..000000000 Binary files a/documentation/manual/source/pics/012-openlp-main-window.png and /dev/null differ diff --git a/documentation/manual/source/pics/addsong.png b/documentation/manual/source/pics/addsong.png deleted file mode 100644 index 3bcdfc088..000000000 Binary files a/documentation/manual/source/pics/addsong.png and /dev/null differ diff --git a/documentation/manual/source/pics/addsongservice1.png b/documentation/manual/source/pics/addsongservice1.png deleted file mode 100644 index 1a01edadb..000000000 Binary files a/documentation/manual/source/pics/addsongservice1.png and /dev/null differ diff --git a/documentation/manual/source/pics/addsongservice2.png b/documentation/manual/source/pics/addsongservice2.png deleted file mode 100644 index 4be2728d0..000000000 Binary files a/documentation/manual/source/pics/addsongservice2.png and /dev/null differ diff --git a/documentation/manual/source/pics/alert.png b/documentation/manual/source/pics/alert.png deleted file mode 100644 index 9df135d4f..000000000 Binary files a/documentation/manual/source/pics/alert.png and /dev/null differ diff --git a/documentation/manual/source/pics/authorstopicsbooks.png b/documentation/manual/source/pics/authorstopicsbooks.png deleted file mode 100644 index dd7aa4862..000000000 Binary files a/documentation/manual/source/pics/authorstopicsbooks.png and /dev/null differ diff --git a/documentation/manual/source/pics/bibleimport01.png b/documentation/manual/source/pics/bibleimport01.png deleted file mode 100644 index be8bdfbf3..000000000 Binary files a/documentation/manual/source/pics/bibleimport01.png and /dev/null differ diff --git a/documentation/manual/source/pics/bibleimport02.png b/documentation/manual/source/pics/bibleimport02.png deleted file mode 100644 index 23c4b7b2a..000000000 Binary files a/documentation/manual/source/pics/bibleimport02.png and /dev/null differ diff --git a/documentation/manual/source/pics/bibleimportdetails1.png b/documentation/manual/source/pics/bibleimportdetails1.png deleted file mode 100644 index 519c36922..000000000 Binary files a/documentation/manual/source/pics/bibleimportdetails1.png and /dev/null differ diff --git a/documentation/manual/source/pics/bibleimportfinished1.png b/documentation/manual/source/pics/bibleimportfinished1.png deleted file mode 100644 index 15fbd7bae..000000000 Binary files a/documentation/manual/source/pics/bibleimportfinished1.png and /dev/null differ diff --git a/documentation/manual/source/pics/biblewebcomplete.png b/documentation/manual/source/pics/biblewebcomplete.png deleted file mode 100644 index a928f8021..000000000 Binary files a/documentation/manual/source/pics/biblewebcomplete.png and /dev/null differ diff --git a/documentation/manual/source/pics/configureadvanced.png b/documentation/manual/source/pics/configureadvanced.png deleted file mode 100644 index 44908e828..000000000 Binary files a/documentation/manual/source/pics/configureadvanced.png and /dev/null differ diff --git a/documentation/manual/source/pics/configurealerts.png b/documentation/manual/source/pics/configurealerts.png deleted file mode 100644 index a15de9e5d..000000000 Binary files a/documentation/manual/source/pics/configurealerts.png and /dev/null differ diff --git a/documentation/manual/source/pics/configurebibles.png b/documentation/manual/source/pics/configurebibles.png deleted file mode 100644 index ab8bc1930..000000000 Binary files a/documentation/manual/source/pics/configurebibles.png and /dev/null differ diff --git a/documentation/manual/source/pics/configurecustom.png b/documentation/manual/source/pics/configurecustom.png deleted file mode 100644 index 33514de18..000000000 Binary files a/documentation/manual/source/pics/configurecustom.png and /dev/null differ diff --git a/documentation/manual/source/pics/configuregeneral.png b/documentation/manual/source/pics/configuregeneral.png deleted file mode 100644 index 18a89474e..000000000 Binary files a/documentation/manual/source/pics/configuregeneral.png and /dev/null differ diff --git a/documentation/manual/source/pics/configuremedia.png b/documentation/manual/source/pics/configuremedia.png deleted file mode 100644 index 9179873ea..000000000 Binary files a/documentation/manual/source/pics/configuremedia.png and /dev/null differ diff --git a/documentation/manual/source/pics/configurepresentations.png b/documentation/manual/source/pics/configurepresentations.png deleted file mode 100644 index 4f8a98bc7..000000000 Binary files a/documentation/manual/source/pics/configurepresentations.png and /dev/null differ diff --git a/documentation/manual/source/pics/configureremotes.png b/documentation/manual/source/pics/configureremotes.png deleted file mode 100644 index d8801aa78..000000000 Binary files a/documentation/manual/source/pics/configureremotes.png and /dev/null differ diff --git a/documentation/manual/source/pics/configuresongs.png b/documentation/manual/source/pics/configuresongs.png deleted file mode 100644 index 9da70deb2..000000000 Binary files a/documentation/manual/source/pics/configuresongs.png and /dev/null differ diff --git a/documentation/manual/source/pics/createthemeicon.png b/documentation/manual/source/pics/createthemeicon.png deleted file mode 100644 index d1fefc0d2..000000000 Binary files a/documentation/manual/source/pics/createthemeicon.png and /dev/null differ diff --git a/documentation/manual/source/pics/csvimport1.png b/documentation/manual/source/pics/csvimport1.png deleted file mode 100644 index 9a4214aec..000000000 Binary files a/documentation/manual/source/pics/csvimport1.png and /dev/null differ diff --git a/documentation/manual/source/pics/csvimport2.png b/documentation/manual/source/pics/csvimport2.png deleted file mode 100644 index d0c8e569b..000000000 Binary files a/documentation/manual/source/pics/csvimport2.png and /dev/null differ 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/logo.png b/documentation/manual/source/pics/logo.png deleted file mode 100644 index a232b59a9..000000000 Binary files a/documentation/manual/source/pics/logo.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 183e0defb..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/openlp.ico b/documentation/manual/source/pics/openlp.ico deleted file mode 100644 index e53fce8d7..000000000 Binary files a/documentation/manual/source/pics/openlp.ico and /dev/null differ diff --git a/documentation/manual/source/pics/phononcheckbox.png b/documentation/manual/source/pics/phononcheckbox.png deleted file mode 100644 index 2830bd8be..000000000 Binary files a/documentation/manual/source/pics/phononcheckbox.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 647c1de7d..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/previewsong1.png b/documentation/manual/source/pics/previewsong1.png deleted file mode 100644 index 23a6b2a07..000000000 Binary files a/documentation/manual/source/pics/previewsong1.png and /dev/null differ diff --git a/documentation/manual/source/pics/previewsong2.png b/documentation/manual/source/pics/previewsong2.png deleted file mode 100644 index 6202e0d14..000000000 Binary files a/documentation/manual/source/pics/previewsong2.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/sendsonglive1.png b/documentation/manual/source/pics/sendsonglive1.png deleted file mode 100644 index abef56775..000000000 Binary files a/documentation/manual/source/pics/sendsonglive1.png and /dev/null differ diff --git a/documentation/manual/source/pics/sendsonglive2.png b/documentation/manual/source/pics/sendsonglive2.png deleted file mode 100644 index 5700495f3..000000000 Binary files a/documentation/manual/source/pics/sendsonglive2.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/song_edit_author_maintenance.png b/documentation/manual/source/pics/song_edit_author_maintenance.png deleted file mode 100644 index 56b5550d2..000000000 Binary files a/documentation/manual/source/pics/song_edit_author_maintenance.png and /dev/null differ diff --git a/documentation/manual/source/pics/song_edit_authors.png b/documentation/manual/source/pics/song_edit_authors.png deleted file mode 100644 index 4a4a3a773..000000000 Binary files a/documentation/manual/source/pics/song_edit_authors.png and /dev/null differ diff --git a/documentation/manual/source/pics/song_edit_lyrics.png b/documentation/manual/source/pics/song_edit_lyrics.png deleted file mode 100644 index 10727376c..000000000 Binary files a/documentation/manual/source/pics/song_edit_lyrics.png and /dev/null differ diff --git a/documentation/manual/source/pics/song_edit_maintenance.png b/documentation/manual/source/pics/song_edit_maintenance.png deleted file mode 100644 index 23bcb62df..000000000 Binary files a/documentation/manual/source/pics/song_edit_maintenance.png and /dev/null differ diff --git a/documentation/manual/source/pics/song_edit_songbook_maintenance.png b/documentation/manual/source/pics/song_edit_songbook_maintenance.png deleted file mode 100644 index d8634ba31..000000000 Binary files a/documentation/manual/source/pics/song_edit_songbook_maintenance.png and /dev/null differ diff --git a/documentation/manual/source/pics/song_edit_theme_copyright.png b/documentation/manual/source/pics/song_edit_theme_copyright.png deleted file mode 100644 index 5234ccc13..000000000 Binary files a/documentation/manual/source/pics/song_edit_theme_copyright.png and /dev/null differ diff --git a/documentation/manual/source/pics/song_edit_topic_maintenance.png b/documentation/manual/source/pics/song_edit_topic_maintenance.png deleted file mode 100644 index 70446b819..000000000 Binary files a/documentation/manual/source/pics/song_edit_topic_maintenance.png and /dev/null differ diff --git a/documentation/manual/source/pics/song_edit_verse_error.png b/documentation/manual/source/pics/song_edit_verse_error.png deleted file mode 100644 index 4fa10fec3..000000000 Binary files a/documentation/manual/source/pics/song_edit_verse_error.png and /dev/null differ diff --git a/documentation/manual/source/pics/song_edit_verse_type.png b/documentation/manual/source/pics/song_edit_verse_type.png deleted file mode 100644 index e4cdfa1a6..000000000 Binary files a/documentation/manual/source/pics/song_edit_verse_type.png and /dev/null differ diff --git a/documentation/manual/source/pics/songeditor1.png b/documentation/manual/source/pics/songeditor1.png deleted file mode 100644 index d34e10165..000000000 Binary files a/documentation/manual/source/pics/songeditor1.png and /dev/null differ diff --git a/documentation/manual/source/pics/songeditor11.png b/documentation/manual/source/pics/songeditor11.png deleted file mode 100644 index 0d25eb097..000000000 Binary files a/documentation/manual/source/pics/songeditor11.png and /dev/null differ diff --git a/documentation/manual/source/pics/songeditor2.png b/documentation/manual/source/pics/songeditor2.png deleted file mode 100644 index e9f02fd16..000000000 Binary files a/documentation/manual/source/pics/songeditor2.png and /dev/null differ diff --git a/documentation/manual/source/pics/songeditor3.png b/documentation/manual/source/pics/songeditor3.png deleted file mode 100644 index 4c567ab29..000000000 Binary files a/documentation/manual/source/pics/songeditor3.png and /dev/null differ diff --git a/documentation/manual/source/pics/songeditor4.png b/documentation/manual/source/pics/songeditor4.png deleted file mode 100644 index b8fe6be9c..000000000 Binary files a/documentation/manual/source/pics/songeditor4.png and /dev/null differ diff --git a/documentation/manual/source/pics/songeditor5.png b/documentation/manual/source/pics/songeditor5.png deleted file mode 100644 index bfc9aba75..000000000 Binary files a/documentation/manual/source/pics/songeditor5.png and /dev/null differ diff --git a/documentation/manual/source/pics/songeditor6.png b/documentation/manual/source/pics/songeditor6.png deleted file mode 100644 index e727b3828..000000000 Binary files a/documentation/manual/source/pics/songeditor6.png and /dev/null differ diff --git a/documentation/manual/source/pics/songeditor7.png b/documentation/manual/source/pics/songeditor7.png deleted file mode 100644 index eeb6f0ff1..000000000 Binary files a/documentation/manual/source/pics/songeditor7.png and /dev/null differ diff --git a/documentation/manual/source/pics/songeditor8.png b/documentation/manual/source/pics/songeditor8.png deleted file mode 100644 index fd57d79b4..000000000 Binary files a/documentation/manual/source/pics/songeditor8.png and /dev/null differ diff --git a/documentation/manual/source/pics/songeditor9.png b/documentation/manual/source/pics/songeditor9.png deleted file mode 100644 index cf067a7dc..000000000 Binary files a/documentation/manual/source/pics/songeditor9.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/songs12.png b/documentation/manual/source/pics/songs12.png deleted file mode 100644 index d9637a4d0..000000000 Binary files a/documentation/manual/source/pics/songs12.png and /dev/null differ diff --git a/documentation/manual/source/pics/songs13.png b/documentation/manual/source/pics/songs13.png deleted file mode 100644 index 2d084954c..000000000 Binary files a/documentation/manual/source/pics/songs13.png and /dev/null differ diff --git a/documentation/manual/source/pics/songs14.png b/documentation/manual/source/pics/songs14.png deleted file mode 100644 index 917bb6bf6..000000000 Binary files a/documentation/manual/source/pics/songs14.png and /dev/null differ diff --git a/documentation/manual/source/pics/songs15.png b/documentation/manual/source/pics/songs15.png deleted file mode 100644 index 74deea0ec..000000000 Binary files a/documentation/manual/source/pics/songs15.png and /dev/null differ diff --git a/documentation/manual/source/pics/songs16.png b/documentation/manual/source/pics/songs16.png deleted file mode 100644 index e72026839..000000000 Binary files a/documentation/manual/source/pics/songs16.png and /dev/null differ diff --git a/documentation/manual/source/pics/songs17.png b/documentation/manual/source/pics/songs17.png deleted file mode 100644 index c5b56d1eb..000000000 Binary files a/documentation/manual/source/pics/songs17.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/themeeditbutton.png b/documentation/manual/source/pics/themeeditbutton.png deleted file mode 100644 index b3e6a768b..000000000 Binary files a/documentation/manual/source/pics/themeeditbutton.png and /dev/null differ diff --git a/documentation/manual/source/pics/themeimportexport.png b/documentation/manual/source/pics/themeimportexport.png deleted file mode 100644 index 189a2a435..000000000 Binary files a/documentation/manual/source/pics/themeimportexport.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/thememanager1.png b/documentation/manual/source/pics/thememanager1.png deleted file mode 100644 index 24dd6bec0..000000000 Binary files a/documentation/manual/source/pics/thememanager1.png and /dev/null differ diff --git a/documentation/manual/source/pics/themewizard1.png b/documentation/manual/source/pics/themewizard1.png deleted file mode 100644 index 1e37687fb..000000000 Binary files a/documentation/manual/source/pics/themewizard1.png and /dev/null differ diff --git a/documentation/manual/source/pics/themewizard2.png b/documentation/manual/source/pics/themewizard2.png deleted file mode 100644 index ba1654daf..000000000 Binary files a/documentation/manual/source/pics/themewizard2.png and /dev/null differ diff --git a/documentation/manual/source/pics/themewizard3.png b/documentation/manual/source/pics/themewizard3.png deleted file mode 100644 index 508e80c1c..000000000 Binary files a/documentation/manual/source/pics/themewizard3.png and /dev/null differ diff --git a/documentation/manual/source/pics/themewizard4.png b/documentation/manual/source/pics/themewizard4.png deleted file mode 100644 index 9cd50ed67..000000000 Binary files a/documentation/manual/source/pics/themewizard4.png and /dev/null differ diff --git a/documentation/manual/source/pics/themewizard5.png b/documentation/manual/source/pics/themewizard5.png deleted file mode 100644 index cb5c0813c..000000000 Binary files a/documentation/manual/source/pics/themewizard5.png and /dev/null differ diff --git a/documentation/manual/source/pics/themewizard6.png b/documentation/manual/source/pics/themewizard6.png deleted file mode 100644 index 9958d43f0..000000000 Binary files a/documentation/manual/source/pics/themewizard6.png and /dev/null differ diff --git a/documentation/manual/source/pics/themewizard7.png b/documentation/manual/source/pics/themewizard7.png deleted file mode 100644 index 92d9e47ac..000000000 Binary files a/documentation/manual/source/pics/themewizard7.png and /dev/null differ diff --git a/documentation/manual/source/pics/themewizard8.png b/documentation/manual/source/pics/themewizard8.png deleted file mode 100644 index a25cf0d8c..000000000 Binary files a/documentation/manual/source/pics/themewizard8.png and /dev/null differ diff --git a/documentation/manual/source/pics/themewizard9.png b/documentation/manual/source/pics/themewizard9.png deleted file mode 100644 index 2bde33ac7..000000000 Binary files a/documentation/manual/source/pics/themewizard9.png and /dev/null differ diff --git a/documentation/manual/source/pics/themewizardwelcome.png b/documentation/manual/source/pics/themewizardwelcome.png deleted file mode 100644 index f58e7c8f3..000000000 Binary files a/documentation/manual/source/pics/themewizardwelcome.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/webbible1.png b/documentation/manual/source/pics/webbible1.png deleted file mode 100644 index 6ff1b6560..000000000 Binary files a/documentation/manual/source/pics/webbible1.png and /dev/null differ diff --git a/documentation/manual/source/pics/webbibleproxy1.png b/documentation/manual/source/pics/webbibleproxy1.png deleted file mode 100644 index 23ba9ad24..000000000 Binary files a/documentation/manual/source/pics/webbibleproxy1.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/quickstart.rst b/documentation/manual/source/quickstart.rst deleted file mode 100644 index 81e262125..000000000 --- a/documentation/manual/source/quickstart.rst +++ /dev/null @@ -1,11 +0,0 @@ -======================== -OpenLP Quick Start Guide -======================== - -Thank you for choosing OpenLP. The developers of OpenLP have attempted to make -OpenLP intuitive and easy to use. This Quick Start Guide will help you to get -going quickly. - -Bible Importer -============== - diff --git a/documentation/manual/source/songs.rst b/documentation/manual/source/songs.rst deleted file mode 100644 index 44cda2f80..000000000 --- a/documentation/manual/source/songs.rst +++ /dev/null @@ -1,232 +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 click :menuselection:`File --> Import --> Song`. -You will see the Song Importer window, then click :guilabel:`Next`. - -.. image:: pics/songimporter.png - -After choosing :guilabel:`Next` you can select from the various types of -software that OpenLP will convert songs from. - -.. image:: pics/songimporterchoices.png - -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 simple process. First you will -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 will be complete. - -.. image:: pics/finishedimport.png - -Press :guilabel:`Finish` and you will now be ready to use your OpenLP -version 1 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 will -contain files with all your songs in them, without a file extension. (file.xxx). -When you have located this folder you will 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 list, press the shift key, and select the last song in the list. After -this press :guilabel:`Next` and you will see that your import has been -successful. - -.. image:: pics/finishedimport.png - -Press :guilabel:`Finish` and OpenLP will be ready to use your songs that you -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 more info check out the `CCLI website. -`_ - -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 will take you to a -page displaying the lyrics and copyright information for your song. - -.. image:: pics/songselectlyrics.png - -Next, hover over the :guilabel:`Lyrics` menu from the upper right corner. -Choose either the .txt or .usr file. You will 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 the shift key and select the -last item in the list. When finished, you will see that your import has -completed. - -.. image:: pics/finishedimport.png - -Press :guilabel:`Finish` and OpenLP will be ready to use your songs imported -from CCLI SongSelect. - -Creating or editing a song slide -================================ - -If you want to create a new song slide or, once you have a song imported, you -want to edit and rearrange the Title & Lyrics, Author, Topics & Song Book, -assign a Theme, or edit Copyright Info & Comments, you will do this through the -`Song Editor`. - -**Edit:** To edit an existing song you can either click on a song in the -`Media Manager` and then click the button to :guilabel:`Edit the selected song` -or right click a song from either the `Media Manager` or additionally from the -`Service Manager` and click :guilabel:`Edit item`. If you are adding a new song -click :guilabel:`Add a new Song` in the `Media Manager`. - -.. image:: pics/song_edit_lyrics.png - -**Title:** This is where you would name your song or edit a song name. - -**Alternate title:**Alternate Title was for songs with two names -"Lord the Light" - "Shine Jesus Shine". You can also add a name in this box that -will bring up the song in Titles search. **Example:** You could use an alternate -title of "hymn" on all your hymn song titles for grouping. When you search "hymn" -it will show all the hymns that have "hymn" for the Alternate title. - -**Lyrics:** The *Lyrics* window shows all lyrics imported or added. On the left -side of the lyrics you will see a capital letter followed by a number. A V1 -would represent verse 1, C1 would be Chorus 1. You will use these letters and -numbers for the order to display the lyrics. - -**Verse Order:** After you entered or edited your song, you will want OpenLP to -display the verses in the correct order you want them displayed. On the left side -of your lyrics you will see C1, V1, V2 etc. the way they were imported or added. -To put your lyrics in the correct order is as simple as typing in the -:guilabel:`Verse order box` at the bottom, the correct order you want them -displayed, with only a blank space in between each entry. The correct format will -look like this: V1 C1 V2 C1 V3 C1. If you forget to put a space in between the -order, or if you do not have the corresponding verse number, OpenLP will politely -tell you with a pop-up error message what is wrong so you can correct your -mistake and save it. Verse order is optional and if left blank the verses will -display in the order seen in *Lyrics*. - -.. image:: pics/song_edit_verse_error.png - -Adding or editing the lyrics -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -**Add:** To Add a new verse, click on :guilabel:`Add`. The main window is where -you will type your lyrics. OpenLP is packaged with a spell checker for most -languages. If you misspell a word it will be underlined. Right click the -underlined word and left click *Spelling Suggestions* or you can ignore it and -continue typing. You also have the ability to format the font using *Formatting -Tags*. Highlight the word/words you want to format and right click the highlight. -Left click *Formatting Tags* and choose the format you want to apply to the font -and the format tags will be entered with your lyrics. These tags are not visible -when displayed. To remove the format, delete the tag on each end of the word or -sentence. - -**Edit:** To edit an existing verse, click on the verse you wish to *Edit* then click on -:guilabel:`Edit`, make your changes and click :guilabel:`Save`. - -**Edit All:** To edit the whole song at once, click on :guilabel:`Edit All`. - -**Delete:** To delete a verse, click on the verse you want to delete and it will -highlight, click on the :guilabel:`Delete` button and it will be deleted. -**Warning:**, once you click the :guilabel:`Delete` button, you will not be -asked again, it will be deleted immediately. - -.. image:: pics/song_edit_verse_type.png - -**Verse type:** gives you 7 ways to classify your lyrics. Verse, Chorus, Bridge, -Pre-Chorus, Intro, Ending, Other. - -If you have more than one verse, you would number them Verse 1, 2, 3 as needed. -If you find the verse has too many lines for your screen, you can edit and -shorten the verse and :guilabel:`Add` another slide. - -Authors, Topics & Song Book -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Once your *Title & Lyrics* are added or edited the way you want them you must -add or enter the author or authors of the song. OpenLP requires all songs to -have an author entered. You can add a blank space for the author name. - -.. image:: pics/song_edit_authors.png - -**Authors:** Click the drop down arrow to view all authors or start typing a name -in the box and a list will appear. If the authors name has not been added, type -the authors name in the box and click :guilabel:`Add to Song`. The authors name -will appear below and will also be added to your database. If you accidently add -the wrong author you can click on the authors name and click :guilabel:`Remove`. - -:guilabel:`Manage Authors, Topics, Song Books`: Clicking this button will bring -up your complete list of authors. - -.. image:: pics/song_edit_maintenance.png - -**Add:** Clicking the :guilabel:`Add` button will bring up a box where you will -add the Authors First name, Last name and Display name. Click :guilabel:`Save` -when you are finished. - -.. image:: pics/song_edit_author_maintenance.png - -**Edit:** The :guilabel:`Edit` button will bring up window where you can edit -the info that is already there. - -**Delete:** The :guilabel:`Delete` button will remove the author you have -highlighted. Note: You cannot delete an author that is assigned to a song. -Authors names are displayed in the footer. - -Theme, Copyright info & Comments -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -On this tab you can assign a *Theme* to a song, enter the *Copyright information* -and add the *CCLI number*. If you imported a song from SongSelect this -information will usually be entered. - -.. image:: pics/song_edit_theme_copyright.png - -**Theme:** Click the drop down arrow to display your list of themes or start -typing a theme name in the box and the list will appear. You can also create a -new theme by clicking the :guilabel:`New Theme` button. - -**Copyright information:** Add or edit the copyright information in this box. If -you would like to use the © symbol click "guilabel:`©` button. This information -is displayed in the footer. - -**CCLI number:** Enter the CCLI number in this box. Note: this is the CCLI number -of the song, not your contract number. This number is not displayed in the footer - -**Comments:** You can add comments in this box. This information is not -dispayed in the footer. diff --git a/documentation/manual/source/themes.rst b/documentation/manual/source/themes.rst deleted file mode 100644 index 7e041cea2..000000000 --- a/documentation/manual/source/themes.rst +++ /dev/null @@ -1,198 +0,0 @@ -.. _themes: - -====== -Themes -====== - - -The `Theme Manager` is where you can set backgrounds, fonts, and colors to the -style you desire. From the theme manager you can create a new theme, Edit a -theme, Delete a theme, Import a theme, and Export a theme. - -.. image:: /pics/thememanager1.png - -Creating New Themes -=================== -Click the :guilabel:`Create Theme Icon` to Create a new theme - -.. image:: /pics/createthemeicon.png - -This will bring up the `Theme Wizard` - -.. image:: /pics/themewizardwelcome.png - -Click :guilabel:`Next`. You have 3 choices in the drop down menu for Background -type: Solid Color, Gradient, or Image. - -.. image:: /pics/themewizard1.png - -Solid color: select solid color and click on the black button next to Color. -You will have the option of choosing among the colors you see or entering your -own. - -.. image:: /pics/themewizard2.png - -Gradient: choose the two colors, First and Second, you want to fade together -and the Gradient drop down will let you determine the directions of the fade. - -.. image:: /pics/themewizard3.png - -Image: Click on the folder to find and select your image. OpenLP accepts a -variety of image types. - -.. image:: /pics/themewizard4.png - -**Note:** If possible, try to use the same size image as your projector is -displaying. - -When finished with your selection for background, click the :guilabel:`Next` -button. - -This is the area where you will select and define your font characteristics for -the Display text. - -.. image:: /pics/themewizard5.png - -**Font:** Choose the font you would like to use from the drop down. - -**Color:** Choose the color of your font. - -**Size:** The size of your font determines how many lines are shown per slide. -As you change the font size, the lines per slide will change. - -**Line Spacing:** This setting determines how much space you want between -lines. This setting will also change the lines per slide. - -**Outline:** If you desire an outline around your font, select the Outline box, -choose your color and size of the outline. - -**Shadow:** If you desire a shadow around your font, select the Shadow box and -choose your color and size of the shadow. - -**Bold Display:** select the box for Bold font - -**Italic Display:** select the box for Italic font - -When you are finished selecting your font details click the :guilabel:`Next` -button. - -**Footer Area Font Details** - -This page determines the Font, Font Color, and size of the font for the footer. -The footer is where the Title of the song, Author or Authors, Copyright and -CCLI License are displayed. - -.. image:: /pics/themewizard6.png - -When you are finished setting your footer font details, click :guilabel:`Next`. - -**Text Formatting Details** - -This page determines the alignment of the text on your slide and the transition -from one slide to the next. - -.. image:: /pics/themewizard7.png - -**Horizontal Align** the text to the Left, Right or Center of the screen. - -**Vertical Align** the text to the Top, Middle or bottom of the screen. - -**Transitions** - -When this box is selected, switching slides will fade out from one and fade in -to the next. When the box is not selected, slide changing will be instant. - -When you are finished setting your Text Formatting Details, click :guilabel:`Next`. - -**Output Area Locations** - -This page gives you the ability to position your Main area or Footer area to a -specific area of the screen using the x and y positions. ie: if you do not want -your footer on the bottom left, you can make the adjustment here. -You can resize the Width and the Height of the Main Area and the Footer Area. -ie: If you have a temporary or permanent obstacle in one part of the viewing -area, you can resize the Main or Footer area and use x and y positions to -display in a different position on the screen. - -.. image:: /pics/themewizard8.png - -You can also change the Width and the Height of the Main Area of the Footer Area. - -When you are finished setting your Output Area Locations, click :guilabel:`Next`. - -Save and Preview - -.. image:: /pics/themewizard9.png - -**Theme Name:** Enter your theme name here. - -**Preview** -The Preview shows the choices you made when setting up the previous pages plus, -shows all the edit effects possible so you can see what the impact is on all -possible font colors and characteristics. - -If you are satisfied with your selections, click :guilabel:`Finish`. If you -want to make a change, use the :guilabel:`Back` button. - -Editing Themes -============== -Now that you created your theme, and you show it on the projector and there is -something you don't like, you can easily Edit your theme either by clicking the -Theme Edit Button: - -.. image:: /pics/themeeditbutton.png - -Or by right-clicking your theme and selecting the appropriate action. - -Deleting Themes -=============== - -The Delete Button: - -.. image:: /pics/songs17.png - -will delete a selected theme or by right-clicking your theme and selecting -the appropriate action. - -**Note:** deleting the currently selected global theme or the -default theme is not possible. - -Exporting Themes -================ -If you would like to transfer a theme from one computer to another, click on -the theme you want to Export, click the last button in the Theme Manager: - -.. image:: /pics/themeimportexport.png - -choose the folder you want to save your theme and click the OK button. - -Importing Themes -================ - -The fourth button in the Theme Manager: - -.. image:: /pics/themeimportexport.png - -will allow you to Import an Exported theme. Click the Import button, select the -folder and the theme file, and click OK. Your imported theme will be in the -Theme Manager. Import Theme will also handle version 1 Exports. You will need to -check your imported theme since many of the values will have been defaulted. - -Rename Theme -============ - -If you created a theme and want to change the name of it, right-click your -theme and click Rename theme and enter the new name. - -Copy Theme -========== - -Now that you created a theme with all the attributes you like, you can -right-click the theme, click on Copy theme, choose your new name and click OK. -You now have a duplicate of your first theme that you can edit the way you want. - -Set as Global default -===================== - -If you right-click your theme, you have the option to set the theme as Global -default. This option is covered in greater detail under “Configure OpenLP. diff --git a/documentation/manual/source/troubleshooting.rst b/documentation/manual/source/troubleshooting.rst deleted file mode 100644 index 6f5f21a74..000000000 --- a/documentation/manual/source/troubleshooting.rst +++ /dev/null @@ -1,197 +0,0 @@ -=============== -Troubleshooting -=============== - -I can not play videos or other media ------------------------------------- - -If you can not play video or audio through openlp, there are several areas that -could be an issue. First thing is to make sure you can play the media file -through your default media player. OpenLP should be able to play any file that -you can play through your default media player. - -If you can play a file through your media player but not on OpenLP it may help -to enable Phonon for multimedia playback. Go to the OpenLP configuration -:menuselection:`Settings --> Configure OpenLP...` and select the Media tab. -Make sure the check box for `Use Phonon for video playback` is checked. - -.. image:: pics/phononcheckbox.png - -Codecs -^^^^^^ - -You may need to install codecs for certain files to play. Most newer versions -of Windows and OS X will support most media types. Most Linux distributions -will require a little more help to get certain media types to play. - -Microsoft Windows -^^^^^^^^^^^^^^^^^ - -Later versions of Microsoft Windows (Vista, Windows 7) generally come with -everything you need to play most media formats. If for some reason you need -additional codecs we have seen success from the `Combined Community Codec Pack -(CCCP) `_. You might also wish to check out the -K-Lite Codec Pack. If you are having issues, results do seem to vary with the -different options. What works for some may not for others, so some trial and -error may be required. - -Mac -^^^ - -If you are using a Mac. You may wish to play Windows formats. flip4mac enables -you to use popular Windows formats such as .wmv on your Mac. You can get it -`from here `_. - -Ubuntu Linux (and variants) -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -If you are using Ubuntu Linux, or one of its variants (Kubuntu, Edubuntu, etc...) -it is a fairly quick and easy process to get all the codecs you need to make -things work. You will need to install two meta-packages that contain all the -multimedia codecs that you will generally need. From the Software Center install -ubuntu-restricted-extras and Kubuntu-restricted-extras, or from the terminal:: - - user@linux:~ $ sudo apt-get install ubuntu-restricted-extras kubuntu-restricted-extras - -**Note** if you are running Kubuntu there is no need to install the -ubuntu-restricted-extras meta-package - -For more information on Ubuntu and multimedia issues please check out the -`community documentation `_. - -Arch Linux -^^^^^^^^^^ - -The following command provides the most complete solution for codecs on Arch -Linux:: - - root@linux:~ # pacman -S gstreamer0.10-{base,good,bad,ugly}-plugins gstreamer0.10-ffmpeg - -If you need more help with Arch Linux and multimedia please see the `Arch Linux -documentation `_. - -Debian Linux -^^^^^^^^^^^^ - -You will need to add the Debian Multimedia Repository. So add the folowing to -/etc/apt/sources.list:: - - deb http://www.debian-multimedia.org testing main non-free - -Then update the repository info:: - - root@linux:~ # apt-get update - -Then install the following packages:: - - root@linux:~ # apt-get install gstreamer0.10-ffmpeg gstreamer0.10-lame gstreamer0.10-plugins-really-bad gstreamer0.10-plugins-bad gstreamer0.10-plugins-ugly gstreamer0.10-plugins-good gstreamer0.10-x264 - -Fedora Linux -^^^^^^^^^^^^ -You will need to set up Fedora to play most media formats. This is relatively -simple using the rpmfusion repository. - -**Note** the following commands will enable a third party repository to your -system. Please check out `the RPM Fusion site `_ for more information. - -To enable both the free and nonfree components for any Fedora official release -enter the following commands:: - - su -c 'yum localinstall --nogpgcheck http://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-stable.noarch.rpm http://download1.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-stable.noarch.rpm' - -After enabling the rpmfusion repository you will want to refresh your package -list, perform any updates and search for gstreamer-good, bad, and ugly and -install. - -Follow the tutorial `using the rpmfusion repository `_ -to enable extra audio and video formats on Fedora - -The Media Manager appears to be missing some features ------------------------------------------------------ - -If you do not see all the features listed in the Media Manager, you may need -to enable them. - -To enable the plugins navigate to :menuselection:`Settings --> Plugins` or -press :kbd:`Alt+F7`. You will want to click on the plugin to the left that you -would like to enable and select **active** from the drop down box to the right. - -.. image:: pics/plugins.png - -By default all plugins should be enabled during the first run wizard except the -remotes plugin, unless you specify differently. - -I can not see the book, chapter, and verse when I display scripture -------------------------------------------------------------------- - -The book, chapter, and verse should be displayed when you display scripture. If -you can not see this your theme probably has the text size too small for the -info to be seen. See the section of the manual on :ref:`themes` if you need more info -on text sizes in themes. - -I am running Mac OS X and I do not have a presentations plugin --------------------------------------------------------------- - -Due to software limitations with Keynote and OpenOffice Impress, the -presentations plugin on OS X is not currently available. - -I am using PowerPoint 2010 or PowerPoint Viewer 2010 and presentations do not work ----------------------------------------------------------------------------------- - -Currently OpenLP does not support PowerPoint Viewer 2010. PowerPoint 2010 should -work correctly, although some users have reported problems. If you have issues -with PowerPoint 2010 or PowerPoint Viewer 2010 try the PowerPoint 2003 or 2007 -Viewers. `Download the PowerPoint 2007 viewer for free -`_. - -I have PowerPoint installed but it does not show as a presentation option -------------------------------------------------------------------------- - -Installing the `Visual C++ Runtime Redistributable. `_ -has fixed this problem according to some of our users. - -I have JPG images and they will not work on my system ------------------------------------------------------ - -This is a known issue on some Mac OS X 10.5 systems and on a few Windows XP -systems. Currently the only solution is to convert your images into another -format. We would suggest using PNG images when possible with OpenLP. - -I am running a Linux Distro and can not see the menus ------------------------------------------------------ - -This seems to be a problem with XFCE and some versions of Gnome too. To correct -this problem, open a terminal and type in the following commands:: - - gconftool-2 --type boolean --set /desktop/gnome/interface/buttons_have_icons true - - gconftool-2 --type boolean --set /desktop/gnome/interface/menus_have_icons true - -I chose to use a web Bible but it did not download the entire Bible -------------------------------------------------------------------- - -Due to copyright restrictions OpenLP cannot download an entire Bible. It can -only download the section you search for. If you do not have an internet -connection where you intend to use OpenLP you will need another scripture -source. For more information about acquiring Bibles please see :ref:`bibleimporter`. - -OpenLP is using a large amount of RAM when showing a presentation ------------------------------------------------------------------ - -OpenLP uses a large amount of RAM when showing a presentation due to the way it -handles presentations. OpenLP itself is unable to show those presentations or -load the presentation files, so it interacts with the presentation through -either Microsoft PowerPoint or LibreOffice Impress. In order to show the slides -in the slide controller, OpenLP requests that the presentation application -export the slides to images, and then uses those images as slides. This results -in a large amount of RAM being used, especially in presentations with more than -about 20 slides. - -OpenLP is not displaying correctly, or is not on the correct screen -------------------------------------------------------------------- - -Please read the documentation on :ref:`dualmonitors`. It is very important to -have dual monitors setup properly for OpenLP to function as expected. - - - diff --git a/documentation/manual/source/wizard.rst b/documentation/manual/source/wizard.rst deleted file mode 100644 index fb658b266..000000000 --- a/documentation/manual/source/wizard.rst +++ /dev/null @@ -1,102 +0,0 @@ -================ -First Run Wizard -================ - -When using OpenLP for the first time, the **First Run Wizard** will help you -with setting up your installation. This wizard is not intended to be a -comprehensive setup but will help you with the basics. - -Select Translation ------------------- - -.. image:: pics/001-first-time-language.png - -You can choose the translation you want to use or let OpenLP -automatically select it based on your operating system locale. Click -:guilabel:`OK` to continue. - -The "Welcome to the First Time Wizard" dialog box will appear next. Click -:guilabel:`Next`. - -.. image:: pics/002-first-time-wizard-welcome.png - -Activate required Plugins -------------------------- - -.. image:: pics/003-first-time-wizard-plugins.png - -OpenLP has several plugins to choose from. By default, all plugins are enabled, -except the *Remote Access* plugin. For more information on these plugins, please -read the :doc:`mediamanager` section in the manual. If you are not sure of which -plugins to enable or disable, leave the selection as is. You can easily activate -or deactivate plugins later, when OpenLP is running, by going to -:menuselection:`Settings --> Plugin List`. Click :guilabel:`Next` to continue. - -Sample Songs ------------- - -.. image:: pics/004-first-time-wizard-songs.png - -OpenLP provides some sample songs in a few languages for downloading and -importing into your new song database. This is convenient for new users who do -not have any songs yet. If you already have songs in your database, OpenLP will -simply add these sample songs to your database, leaving your existing songs -intact. Once you are happy with which songs you'd like, click :guilabel:`Next`. - -Sample Bibles -------------- - -.. image:: pics/005-first-time-wizard-bibles.png - -There are also a number of free Bibles that you can download and install. Using -the check box next to each Bible, select each Bible that you would like -installed. If you do not wish to install any Bibles, simply leave them all -unchecked. Once you are happy with your selection, click :guilabel:`Next` to -continue. - -Sample Themes -------------- - -.. image:: pics/006-first-time-wizard-themes.png - -Some sample themes are also available for download and installation into OpenLP. -As with the Bibles, simply check the check box next to each theme to select it. -If you are a new user, these themes can help you understand how themes work. If -you are happy with your selected themes, click :guilabel:`Next` to continue. - -Default Settings ----------------- - -.. image:: pics/007-first-time-wizard-settings.png - -Default Display Monitor -^^^^^^^^^^^^^^^^^^^^^^^ - -Screen 2. If you are installing OpenLP but are not connected to a second output -at the time, you can set this up later by going to -:menuselection:`Settings --> Configure OpenLP`. - -Default Theme -^^^^^^^^^^^^^ - -If you have selected one or more themes on the themes page of the wizard, you -can select which theme you'd like to be the default theme. - -If you are happy with the selections you have made, click :guilabel:`Next` to -continue to the next page. - -.. image:: pics/009-first-time-wizard-progress.png - -Making progress. - -.. image:: pics/010-first-time-wizard-finished.png - -On the last page of the wizard, OpenLP will download the sample songs, Bibles -and themes, and set up OpenLP for you. Click :guilabel:`Finish` and OpenLP will -start. - -.. image:: pics/011-first-time-wizard-song-import.png - -The main window. - -.. image:: pics/012-openlp-main-window.png 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 425d3c874..64ffb3321 100755 --- a/openlp.pyw +++ b/openlp.pyw @@ -6,9 +6,10 @@ # 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, # +# 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 # @@ -24,230 +25,18 @@ # 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 + # 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 optparse import OptionParser -from traceback import format_exception -from PyQt4 import QtCore, QtGui +from openlp.core import main -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 - -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. - """ - - 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): - """ - Run the OpenLP application. - """ - # 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(screens, self.clipboard(), - self.arguments()) - self.mainWindow.show() - if show_splash: - # now kill the splashscreen - self.splash.finish(self.mainWindow) - log.debug(u'Splashscreen closed') - 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() - 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 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) - # Instance check - if app.isAlreadyRunning(): - sys.exit() - app.setOrganizationName(u'OpenLP') - app.setOrganizationDomain(u'openlp.org') - app.setApplicationName(u'OpenLP') - app.setApplicationVersion(get_application_version()[u'version']) - # 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() - 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__': """ Instantiate and run the application. """ - main() \ No newline at end of file + main() diff --git a/openlp/__init__.py b/openlp/__init__.py index ac3dac24c..5f7608770 100644 --- a/openlp/__init__.py +++ b/openlp/__init__.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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 1cef928bc..82a30b19f 100644 --- a/openlp/core/__init__.py +++ b/openlp/core/__init__.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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 89eeb6ad4..e104f4022 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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): """ @@ -184,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. @@ -199,26 +211,36 @@ def resize_image(image, width, height, background=QtCore.Qt.black): The new image height. ``background`` - The background colour defaults to black. + 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 @@ -243,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 @@ -253,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 @@ -272,23 +295,20 @@ def check_directory_exists(dir): except IOError: pass -from listwidgetwithdnd import ListWidgetWithDnD -from displaytags import DisplayTags from eventreceiver import Receiver +from listwidgetwithdnd import ListWidgetWithDnD +from formattingtags import FormattingTags from spelltextedit import SpellTextEdit -from imagemanager import ImageManager 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 42dab1e42..cafc867b3 100644 --- a/openlp/core/lib/db.py +++ b/openlp/core/lib/db.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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 9b4661fee..8c942171c 100644 --- a/openlp/core/lib/dockwidget.py +++ b/openlp/core/lib/dockwidget.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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 bee195b0f..14b6126bc 100644 --- a/openlp/core/lib/eventreceiver.py +++ b/openlp/core/lib/eventreceiver.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -34,204 +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 + 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 1f2d2498d..5dfe00d36 100644 --- a/openlp/core/lib/htmlbuilder.py +++ b/openlp/core/lib/htmlbuilder.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -34,6 +35,7 @@ from openlp.core.lib.theme import BackgroundType, BackgroundGradientType, \ log = logging.getLogger(__name__) HTMLSRC = u""" + OpenLP Display @@ -55,44 +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; + 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 65f2b355c..000000000 --- a/openlp/core/lib/rendermanager.py +++ /dev/null @@ -1,261 +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, 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 # -############################################################################### - -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: - # Images have a theme of -1 - if theme and theme != -1: - 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 94152ef2f..c172ba21d 100644 --- a/openlp/core/lib/searchedit.py +++ b/openlp/core/lib/searchedit.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -62,6 +63,7 @@ class SearchEdit(QtGui.QLineEdit): self._onSearchEditTextChanged ) self._updateStyleSheet() + self.setAcceptDrops(False) def _updateStyleSheet(self): """ @@ -74,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( @@ -115,7 +117,7 @@ class SearchEdit(QtGui.QLineEdit): Set a new current search type. ``identifier`` - The search type identifier (int). + The search type identifier (int). """ menu = self.menuButton.menu() for action in menu.actions(): diff --git a/openlp/core/lib/serviceitem.py b/openlp/core/lib/serviceitem.py index 5de25f6aa..b12a27f1d 100644 --- a/openlp/core/lib/serviceitem.py +++ b/openlp/core/lib/serviceitem.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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 - AllowsVariableStartTime = 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'' @@ -112,13 +116,15 @@ class ServiceItem(object): 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()) @@ -151,39 +157,37 @@ 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 @@ -193,7 +197,7 @@ class ServiceItem(object): 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. @@ -203,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): @@ -218,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( @@ -264,15 +273,15 @@ class ServiceItem(object): u'xml_version': self.xml_version, u'start_time': self.start_time, u'end_time': self.end_time, - u'media_length': self.media_length + u'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( @@ -314,6 +323,9 @@ class ServiceItem(object): 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) @@ -335,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 @@ -347,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): """ @@ -441,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) \ No newline at end of file + 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 bb83371e6..9bcbf2f07 100644 --- a/openlp/core/lib/settingsmanager.py +++ b/openlp/core/lib/settingsmanager.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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 e1396d984..46263efca 100644 --- a/openlp/core/lib/settingstab.py +++ b/openlp/core/lib/settingstab.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -50,7 +51,6 @@ class SettingsTab(QtGui.QWidget): self.setupUi() self.retranslateUi() self.initialise() - self.preLoad() self.load() def setupUi(self): @@ -86,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. @@ -118,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 a99539775..25e4e24ae 100644 --- a/openlp/core/lib/spelltextedit.py +++ b/openlp/core/lib/spelltextedit.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -28,6 +29,7 @@ import re try: import enchant from enchant import DictNotFoundError + from enchant.errors import Error ENCHANT_AVAILABLE = True except ImportError: ENCHANT_AVAILABLE = False @@ -37,7 +39,7 @@ except ImportError: 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__) @@ -46,16 +48,17 @@ class SpellTextEdit(QtGui.QPlainTextEdit): """ Spell checking widget based on QPlanTextEdit. """ - def __init__(self, *args): + def __init__(self, parent=None, formattingTagsAllowed=True): global ENCHANT_AVAILABLE - QtGui.QPlainTextEdit.__init__(self, *args) + QtGui.QPlainTextEdit.__init__(self, parent) + self.formattingTagsAllowed = formattingTagsAllowed # Default dictionary based on the current locale. if ENCHANT_AVAILABLE: try: self.dictionary = enchant.Dict() self.highlighter = Highlighter(self.document()) self.highlighter.spellingDictionary = self.dictionary - except DictNotFoundError: + except (Error, DictNotFoundError): ENCHANT_AVAILABLE = False log.debug(u'Could not load default dictionary') @@ -108,16 +111,17 @@ 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()): + 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): @@ -146,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 7f2204c67..34a3b9d98 100644 --- a/openlp/core/lib/theme.py +++ b/openlp/core/lib/theme.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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 d2b37df51..cf550d875 100644 --- a/openlp/core/lib/toolbar.py +++ b/openlp/core/lib/toolbar.py @@ -5,9 +5,10 @@ # 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, # +# 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 # diff --git a/openlp/core/lib/ui.py b/openlp/core/lib/ui.py index b544efab9..756df36c3 100644 --- a/openlp/core/lib/ui.py +++ b/openlp/core/lib/ui.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -63,10 +64,12 @@ class UiStrings(object): 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') @@ -80,10 +83,8 @@ class UiStrings(object): self.Image = translate('OpenLP.Ui', 'Image') self.Import = translate('OpenLP.Ui', 'Import') self.LayoutStyle = translate('OpenLP.Ui', 'Layout style:') - self.LengthTime = unicode(translate('OpenLP.Ui', 'Length %s')) self.Live = translate('OpenLP.Ui', 'Live') self.LiveBGError = translate('OpenLP.Ui', 'Live Background Error') - self.LivePanel = translate('OpenLP.Ui', 'Live Panel') self.LiveToolbar = translate('OpenLP.Ui', 'Live Toolbar') self.Load = translate('OpenLP.Ui', 'Load') self.Minutes = translate('OpenLP.Ui', 'm', @@ -100,14 +101,15 @@ class UiStrings(object): 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.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.PreviewPanel = translate('OpenLP.Ui', 'Preview Panel') - self.PrintServiceOrder = translate('OpenLP.Ui', 'Print Service Order') + self.PrintService = translate('OpenLP.Ui', 'Print Service') self.ReplaceBG = translate('OpenLP.Ui', 'Replace Background') - self.ReplaceLiveBG = translate('OpenLP.Ui', 'Replace Live 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.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') @@ -119,16 +121,24 @@ class UiStrings(object): 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 Model') + self.ViewMode = translate('OpenLP.Ui', 'View Mode') def add_welcome_page(parent, image): """ @@ -319,17 +329,18 @@ def shortcut_action(parent, name, shortcuts, function, icon=None, checked=None, if checked is not None: action.setCheckable(True) action.setChecked(checked) - action.setShortcuts(shortcuts) - action.setShortcutContext(context) + 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.WindowShortcut): + context=QtCore.Qt.WidgetShortcut): """ - Utility method to help build context menus for plugins + Utility method to help build context menus. ``base`` The parent menu to add this menu item to @@ -348,7 +359,7 @@ def context_menu_action(base, icon, text, slot, shortcuts=None, category=None, ``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. + left to ``None``, then the action will be hidden in the shortcut dialog. ``context`` The context the shortcut is valid. @@ -362,11 +373,12 @@ def context_menu_action(base, icon, text, slot, shortcuts=None, category=None, 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 for plugins + Utility method to help build context menus. ``base`` The parent object to add this menu to @@ -390,6 +402,7 @@ def context_menu_separator(base): """ action = QtGui.QAction(u'', base) action.setSeparator(True) + base.addAction(action) return action def add_widget_completer(cache, widget): diff --git a/openlp/core/theme/__init__.py b/openlp/core/theme/__init__.py index bd5ba899d..44a937608 100644 --- a/openlp/core/theme/__init__.py +++ b/openlp/core/theme/__init__.py @@ -5,9 +5,10 @@ # 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, # +# 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 # diff --git a/openlp/core/theme/theme.py b/openlp/core/theme/theme.py index 78e9cb7e7..48e364dbd 100644 --- a/openlp/core/theme/theme.py +++ b/openlp/core/theme/theme.py @@ -5,9 +5,10 @@ # 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, # +# 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 # diff --git a/openlp/core/ui/__init__.py b/openlp/core/ui/__init__.py index a2985c0b8..6a04a080b 100644 --- a/openlp/core/ui/__init__.py +++ b/openlp/core/ui/__init__.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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 d4ea463ea..8216d3e7a 100644 --- a/openlp/core/ui/aboutdialog.py +++ b/openlp/core/ui/aboutdialog.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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' @@ -111,18 +112,20 @@ class Ui_AboutDialog(object): 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', + contributors = [u'Gerald "jerryb" Britton', + u'Scott "sguerrieri" Guerrieri', u'Matthias "matthub" Hub', u'Meinert "m2j" Jordan', - u'Armin "orangeshirt" K\xf6hler', u'Mattias "mahfiaz" P\xf5ldaru', + 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'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'Tim "TRB143" Bentley (Fedora and Android)', u'Matthias "matthub" Hub (Mac OS X)', - u'Stevan "StevanP" Pettit (Windows)', + u'Stevan "ElderP" Pettit (Windows)', u'Raoul "superfly" Snyman (Ubuntu)'] translators = { u'af': [u'Johan "nuvolari" Mynhardt'], @@ -137,7 +140,8 @@ class Ui_AboutDialog(object): 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'Gustavo Bim'], + 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, Carsten\n' - '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 ' @@ -615,4 +621,4 @@ class Ui_AboutDialog(object): self.aboutNotebook.indexOf(self.licenseTab), translate('OpenLP.AboutForm', 'License')) self.contributeButton.setText(translate('OpenLP.AboutForm', - 'Contribute')) \ No newline at end of file + 'Contribute')) diff --git a/openlp/core/ui/aboutform.py b/openlp/core/ui/aboutform.py index 4112cfd8f..4e031656c 100644 --- a/openlp/core/ui/aboutform.py +++ b/openlp/core/ui/aboutform.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -60,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 15f9553fc..d6d28f053 100644 --- a/openlp/core/ui/advancedtab.py +++ b/openlp/core/ui/advancedtab.py @@ -5,9 +5,10 @@ # 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, # +# 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 # diff --git a/openlp/core/ui/exceptiondialog.py b/openlp/core/ui/exceptiondialog.py index 4aa01f776..6ed45c5d8 100644 --- a/openlp/core/ui/exceptiondialog.py +++ b/openlp/core/ui/exceptiondialog.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -38,6 +39,8 @@ class Ui_ExceptionDialog(object): self.messageLayout.addSpacing(12) self.bugLabel = QtGui.QLabel(exceptionDialog) self.bugLabel.setPixmap(QtGui.QPixmap(u':/graphics/exception.png')) + self.bugLabel.setSizePolicy(QtGui.QSizePolicy.Fixed, + QtGui.QSizePolicy.Fixed) self.bugLabel.setObjectName(u'bugLabel') self.messageLayout.addWidget(self.bugLabel) self.messageLayout.addSpacing(12) diff --git a/openlp/core/ui/exceptionform.py b/openlp/core/ui/exceptionform.py index 622d60f79..0cda4bd60 100644 --- a/openlp/core/ui/exceptionform.py +++ b/openlp/core/ui/exceptionform.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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,9 +59,26 @@ 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 @@ -89,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' @@ -105,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 ' @@ -121,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): """ @@ -149,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())) @@ -185,4 +216,5 @@ class ExceptionForm(QtGui.QDialog, Ui_ExceptionDialog): def __buttonState(self, state): self.saveReportButton.setEnabled(state) - self.sendReportButton.setEnabled(state) \ No newline at end of file + self.sendReportButton.setEnabled(state) + diff --git a/openlp/core/ui/filerenamedialog.py b/openlp/core/ui/filerenamedialog.py index ec0f0e2dd..6611a9df7 100644 --- a/openlp/core/ui/filerenamedialog.py +++ b/openlp/core/ui/filerenamedialog.py @@ -5,9 +5,10 @@ # 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, # +# 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 # diff --git a/openlp/core/ui/filerenameform.py b/openlp/core/ui/filerenameform.py index 049b68336..0bdbdf892 100644 --- a/openlp/core/ui/filerenameform.py +++ b/openlp/core/ui/filerenameform.py @@ -5,9 +5,10 @@ # 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, # +# 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 # diff --git a/openlp/core/ui/firsttimeform.py b/openlp/core/ui/firsttimeform.py index dade26cf9..bfa4bf6b1 100644 --- a/openlp/core/ui/firsttimeform.py +++ b/openlp/core/ui/firsttimeform.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -27,7 +28,9 @@ import io import logging import os +import sys import urllib +import urllib2 from tempfile import gettempdir from ConfigParser import SafeConfigParser @@ -59,8 +62,13 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard): files = self.webAccess.read() self.config.readfp(io.BytesIO(files)) self.updateScreenListCombo() + self.downloadCanceled = False self.downloading = unicode(translate('OpenLP.FirstTimeWizard', '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.QObject.connect(Receiver.get_receiver(), @@ -79,6 +87,10 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard): """ 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: songs = self.config.get(u'songs', u'languages') @@ -119,7 +131,7 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard): 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), + 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, @@ -128,11 +140,13 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard): 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 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 @@ -149,16 +163,50 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard): """ Detects Page changes and updates as approprate. """ - if pageId == FirstTimePage.Defaults: + # 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 updateScreenListCombo(self): """ @@ -169,6 +217,53 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard): 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') + + 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() @@ -178,7 +273,7 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard): 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. @@ -199,15 +294,16 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard): """ Prepare the UI for the process. """ - # We start on 2 for plugins status setting plus a "finished" point. - max_progress = 2 + 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)) - max_progress += size + self.max_progress += size # Loop through the Bibles list and increase for each selected item iterator = QtGui.QTreeWidgetItemIterator(self.biblesTreeWidget) while iterator.value(): @@ -215,7 +311,7 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard): 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)) - max_progress += size + self.max_progress += size iterator += 1 # Loop through the themes list and increase for each selected item for i in xrange(self.themesListWidget.count()): @@ -223,23 +319,49 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard): if item.checkState() == QtCore.Qt.Checked: filename = item.data(QtCore.Qt.UserRole).toString() size = self._getFileSize(u'%s%s' % (self.web, filename)) - max_progress += size - self.finishButton.setVisible(False) - self.progressBar.setValue(0) - self.progressBar.setMinimum(0) - self.progressBar.setMaximum(max_progress) + 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. """ - self.progressBar.setValue(self.progressBar.maximum()) + 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) - self.progressLabel.setText(translate('OpenLP.FirstTimeWizard', - 'Download complete. Click the finish button to start OpenLP.')) Receiver.send_message(u'openlp_process_events') def _performWizard(self): @@ -259,42 +381,42 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard): self._setPluginStatus(self.customCheckBox, u'custom/status') self._setPluginStatus(self.songUsageCheckBox, u'songusage/status') self._setPluginStatus(self.alertCheckBox, u'alerts/status') - # 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)) - urllib.urlretrieve(u'%s%s' % (self.web, filename), destination, - self._downloadProgress) - # 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 - urllib.urlretrieve(u'%s%s' % (self.web, bible), - os.path.join(bibles_destination, bible), - self._downloadProgress) - 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 - urllib.urlretrieve(u'%s%s' % (self.web, theme), - os.path.join(themes_destination, theme), - self._downloadProgress) + 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', diff --git a/openlp/core/ui/firsttimelanguagedialog.py b/openlp/core/ui/firsttimelanguagedialog.py index bdc03048a..172fd3e0f 100644 --- a/openlp/core/ui/firsttimelanguagedialog.py +++ b/openlp/core/ui/firsttimelanguagedialog.py @@ -5,9 +5,10 @@ # 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, # +# 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 # diff --git a/openlp/core/ui/firsttimelanguageform.py b/openlp/core/ui/firsttimelanguageform.py index f6ffafb8f..dcde212eb 100644 --- a/openlp/core/ui/firsttimelanguageform.py +++ b/openlp/core/ui/firsttimelanguageform.py @@ -5,9 +5,10 @@ # 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, # +# 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 # diff --git a/openlp/core/ui/firsttimewizard.py b/openlp/core/ui/firsttimewizard.py index 71b3f5dfc..0f152a396 100644 --- a/openlp/core/ui/firsttimewizard.py +++ b/openlp/core/ui/firsttimewizard.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -48,10 +49,12 @@ class Ui_FirstTimeWizard(object): FirstTimeWizard.resize(550, 386) FirstTimeWizard.setModal(True) FirstTimeWizard.setWizardStyle(QtGui.QWizard.ModernStyle) - FirstTimeWizard.setOptions(QtGui.QWizard.IndependentPages| + FirstTimeWizard.setOptions(QtGui.QWizard.IndependentPages | QtGui.QWizard.NoBackButtonOnStartPage | - QtGui.QWizard.NoBackButtonOnLastPage) + 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) @@ -188,9 +191,7 @@ class Ui_FirstTimeWizard(object): 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( @@ -201,15 +202,14 @@ class Ui_FirstTimeWizard(object): '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. ')) + ' Click the next button below to start.')) self.pluginPage.setTitle(translate('OpenLP.FirstTimeWizard', 'Activate required Plugins')) 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')) @@ -230,14 +230,17 @@ class Ui_FirstTimeWizard(object): self.noInternetPage.setSubTitle(translate( 'OpenLP.FirstTimeWizard', 'Unable to detect an Internet connection.')) - self.noInternetLabel.setText(translate('OpenLP.FirstTimeWizard', + 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.\n\nTo re-run the First Time Wizard and ' - 'import this sample data at a later stage, press the cancel ' - 'button now, check your Internet connection, and restart OpenLP.' - '\n\nTo cancel the First Time Wizard completely, press the finish ' - 'button now.')) + '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', @@ -254,13 +257,11 @@ class Ui_FirstTimeWizard(object): 'Default Settings')) self.defaultsPage.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Set up default settings to be used by OpenLP.')) - self.progressPage.setTitle(translate('OpenLP.FirstTimeWizard', - 'Setting Up And Importing')) - self.progressPage.setSubTitle(translate('OpenLP.FirstTimeWizard', - 'Please wait while OpenLP is set up and your data is imported.')) 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 70% rename from openlp/core/ui/displaytagdialog.py rename to openlp/core/ui/formattingtagdialog.py index 8e71a60ff..186f4739b 100644 --- a/openlp/core/ui/displaytagdialog.py +++ b/openlp/core/ui/formattingtagdialog.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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')) + translate('OpenLP.FormattingTagDialog', 'End tag')) self.deletePushButton.setText(UiStrings().Delete) - self.defaultPushButton.setText( - translate('OpenLP.DisplayTagDialog', 'Default')) 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) \ No newline at end of file + 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 63% rename from openlp/core/ui/displaytagform.py rename to openlp/core/ui/formattingtagform.py index 24cd14bd0..e7435e5b7 100644 --- a/openlp/core/ui/displaytagform.py +++ b/openlp/core/ui/formattingtagform.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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 7dac2fe9d..be02b3caa 100644 --- a/openlp/core/ui/generaltab.py +++ b/openlp/core/ui/generaltab.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -29,6 +30,7 @@ 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__) @@ -36,33 +38,15 @@ class GeneralTab(SettingsTab): """ GeneralTab is the general settings tab in the settings dialog. """ - def __init__(self, parent, 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 + self.screens = ScreenList.get_instance() self.icon_path = u':/icon/openlp-logo-16x16.png' - generalTranslated = translate('GeneralTab', 'General') + generalTranslated = translate('OpenLP.GeneralTab', 'General') SettingsTab.__init__(self, parent, u'General', generalTranslated) - 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() - def setupUi(self): """ Create the user interface for the general settings tab @@ -113,11 +97,15 @@ class GeneralTab(SettingsTab): 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() @@ -158,14 +146,14 @@ class GeneralTab(SettingsTab): self.displayLayout.addWidget(self.customXLabel, 3, 0) self.customXValueEdit = QtGui.QSpinBox(self.displayGroupBox) self.customXValueEdit.setObjectName(u'customXValueEdit') - self.customXValueEdit.setMaximum(9999) + 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 = QtGui.QSpinBox(self.displayGroupBox) self.customYValueEdit.setObjectName(u'customYValueEdit') - self.customYValueEdit.setMaximum(9999) + self.customYValueEdit.setRange(-9999, 9999) self.displayLayout.addWidget(self.customYValueEdit, 4, 1) self.customWidthLabel = QtGui.QLabel(self.displayGroupBox) self.customWidthLabel.setObjectName(u'customWidthLabel') @@ -182,18 +170,29 @@ class GeneralTab(SettingsTab): 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'valueChanged(int)'), self.onDisplayPositionChanged) + QtCore.SIGNAL(u'valueChanged(int)'), self.onDisplayChanged) QtCore.QObject.connect(self.customWidthValueEdit, - QtCore.SIGNAL(u'valueChanged(int)'), self.onDisplayPositionChanged) + QtCore.SIGNAL(u'valueChanged(int)'), self.onDisplayChanged) QtCore.QObject.connect(self.customYValueEdit, - QtCore.SIGNAL(u'valueChanged(int)'), self.onDisplayPositionChanged) + QtCore.SIGNAL(u'valueChanged(int)'), self.onDisplayChanged) QtCore.QObject.connect(self.customXValueEdit, - QtCore.SIGNAL(u'valueChanged(int)'), 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) @@ -203,7 +202,6 @@ class GeneralTab(SettingsTab): self.passwordLabel.setVisible(False) self.passwordEdit.setVisible(False) - def retranslateUi(self): """ Translate the general settings tab to the currently selected language @@ -233,8 +231,10 @@ class GeneralTab(SettingsTab): '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:')) + 'Timed slide interval:')) self.timeoutSpinBox.setSuffix(translate('OpenLP.GeneralTab', ' sec')) self.ccliGroupBox.setTitle( translate('OpenLP.GeneralTab', 'CCLI Details')) @@ -252,6 +252,10 @@ class GeneralTab(SettingsTab): self.customYLabel.setText(translate('OpenLP.GeneralTab', 'Y')) 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): """ @@ -261,6 +265,9 @@ class GeneralTab(SettingsTab): settings.beginGroup(self.settingsSection) self.monitorComboBox.clear() 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( @@ -271,7 +278,6 @@ class GeneralTab(SettingsTab): QtCore.QVariant(False)).toBool()) self.autoUnblankCheckBox.setChecked(settings.value(u'auto unblank', QtCore.QVariant(False)).toBool()) - self.monitorComboBox.setCurrentIndex(self.monitorNumber) self.displayOnMonitorCheck.setChecked(self.screens.display) self.warningCheckBox.setChecked(settings.value(u'blank warning', QtCore.QVariant(False)).toBool()) @@ -283,6 +289,8 @@ 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.overrideCheckBox.setChecked(settings.value(u'override position', @@ -295,20 +303,23 @@ class GeneralTab(SettingsTab): 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', @@ -325,6 +336,8 @@ class GeneralTab(SettingsTab): 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', @@ -343,16 +356,11 @@ class GeneralTab(SettingsTab): 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): """ @@ -361,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( - self.customXValueEdit.value(), - self.customYValueEdit.value(), - self.customWidthValueEdit.value(), - self.customHeightValueEdit.value()) + # 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): """ @@ -388,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 \ No newline at end of file + self.display_changed = True diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index 7843284b3..2a16867e5 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -34,50 +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, \ - 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 = {} 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): """ @@ -89,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 @@ -101,7 +125,64 @@ 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) + 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) + self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.frame.setScrollBarPolicy(QtCore.Qt.Vertical, + QtCore.Qt.ScrollBarAlwaysOff) + self.frame.setScrollBarPolicy(QtCore.Qt.Horizontal, + QtCore.Qt.ScrollBarAlwaysOff) + if self.isLive: + # 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() + 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) + 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(self.initialFrame) + painter_image.fillRect(self.initialFrame.rect(), background_color) + painter_image.drawImage( + (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(self.initialFrame) + self.webView.setHtml(build_html(serviceItem, self.screen, + self.isLive, None, plugins=self.plugins)) + self.__hideMouse() + # To display or not to display? + if not self.screen[u'primary']: + self.primary = False + else: + self.primary = True + 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) @@ -115,71 +196,13 @@ class MainDisplay(DisplayWidget): QtCore.QObject.connect(self.mediaObject, QtCore.SIGNAL(u'tick(qint64)'), self.videoTick) - log.debug(u'Setup webView for monitor %s' % self.screens.monitor_number) - self.webView = QtWebKit.QWebView(self) - self.webView.setGeometry(0, 0, - self.screen[u'size'].width(), self.screen[u'size'].height()) - self.page = self.webView.page() - self.frame = self.page.mainFrame() - QtCore.QObject.connect(self.webView, - QtCore.SIGNAL(u'loadFinished(bool)'), self.isWebLoaded) - self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - self.frame.setScrollBarPolicy(QtCore.Qt.Vertical, - QtCore.Qt.ScrollBarAlwaysOff) - 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() - 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) - self.initialFrame = 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.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) - serviceItem = ServiceItem() - serviceItem.bg_image_bytes = image_to_byte(self.initialFrame) - self.webView.setHtml(build_html(serviceItem, self.screen, - self.alertTab, self.isLive, None)) - 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'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') @@ -187,26 +210,27 @@ class MainDisplay(DisplayWidget): while not self.webLoaded: Receiver.send_message(u'openlp_process_events') self.setGeometry(self.screen[u'size']) - self.frame.evaluateJavaScript(u'show_text("%s")' % \ + 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: @@ -217,39 +241,43 @@ class MainDisplay(DisplayWidget): alert_height = int(height.toString()) shrinkItem.resize(self.width(), alert_height) shrinkItem.setVisible(True) - if self.alertTab.location == 1: + if location == AlertLocation.Middle: shrinkItem.move(self.screen[u'size'].left(), (self.screen[u'size'].height() - alert_height) / 2) - elif self.alertTab.location == 2: + elif location == AlertLocation.Bottom: shrinkItem.move(self.screen[u'size'].left(), self.screen[u'size'].height() - alert_height) else: shrinkItem.setVisible(False) 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): """ @@ -261,14 +289,11 @@ class MainDisplay(DisplayWidget): 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'): @@ -277,9 +302,6 @@ class MainDisplay(DisplayWidget): self.displayImage(None) # clear the cache self.override = {} - # Update the preview frame. - if self.isLive: - Receiver.send_message(u'maindisplay_active') def resetVideo(self): """ @@ -295,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): """ @@ -348,6 +367,11 @@ 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']) @@ -372,10 +396,7 @@ class MainDisplay(DisplayWidget): 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 videoState(self, newState, oldState): """ @@ -436,16 +457,15 @@ class MainDisplay(DisplayWidget): self.hideDisplay(self.hideMode) else: # Single screen active - if self.screens.monitor_number == 0: + 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.QImage(self.screen[u'size'].width(), - self.screen[u'size'].height(), - QtGui.QImage.Format_ARGB32_Premultiplied) + 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) @@ -483,8 +503,8 @@ class MainDisplay(DisplayWidget): image_bytes = self.imageManager.get_image_bytes(image) else: image_bytes = None - html = build_html(self.serviceItem, self.screen, self.alertTab, - self.isLive, background, image_bytes) + 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') @@ -550,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 @@ -579,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 cb01e37a1..47570cd8a 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -26,20 +27,26 @@ 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, 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, \ - get_application_version + 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__) @@ -63,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-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') @@ -164,157 +177,193 @@ class Ui_MainWindow(object): # Create the menu items action_list = ActionList.get_instance() action_list.add_category(UiStrings().File, CategoryOrder.standardMenu) - self.FileNewItem = shortcut_action(mainWindow, u'FileNewItem', + self.fileNewItem = shortcut_action(mainWindow, u'fileNewItem', [QtGui.QKeySequence(u'Ctrl+N')], - self.ServiceManagerContents.onNewServiceClicked, + self.serviceManagerContents.onNewServiceClicked, u':/general/general_new.png', category=UiStrings().File) - self.FileOpenItem = shortcut_action(mainWindow, u'FileOpenItem', + self.fileOpenItem = shortcut_action(mainWindow, u'fileOpenItem', [QtGui.QKeySequence(u'Ctrl+O')], - self.ServiceManagerContents.onLoadServiceClicked, + self.serviceManagerContents.onLoadServiceClicked, u':/general/general_open.png', category=UiStrings().File) - self.FileSaveItem = shortcut_action(mainWindow, u'FileSaveItem', + self.fileSaveItem = shortcut_action(mainWindow, u'fileSaveItem', [QtGui.QKeySequence(u'Ctrl+S')], - self.ServiceManagerContents.saveFile, + self.serviceManagerContents.saveFile, u':/general/general_save.png', category=UiStrings().File) - self.FileSaveAsItem = shortcut_action(mainWindow, u'FileSaveAsItem', + self.fileSaveAsItem = shortcut_action(mainWindow, u'fileSaveAsItem', [QtGui.QKeySequence(u'Ctrl+Shift+S')], - self.ServiceManagerContents.saveFileAs, category=UiStrings().File) + self.serviceManagerContents.saveFileAs, category=UiStrings().File) self.printServiceOrderItem = shortcut_action(mainWindow, u'printServiceItem', [QtGui.QKeySequence(u'Ctrl+P')], - self.ServiceManagerContents.printServiceOrder, + self.serviceManagerContents.printServiceOrder, category=UiStrings().File) - self.FileExitItem = shortcut_action(mainWindow, u'FileExitItem', + 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) + 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) + 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.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.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.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.viewPreviewPanel = shortcut_action(mainWindow, + u'viewPreviewPanel', [QtGui.QKeySequence(u'F11')], self.setPreviewPanelVisibility, checked=previewVisible, category=UiStrings().View) - self.ViewLivePanel = shortcut_action(mainWindow, u'ViewLivePanel', + self.viewLivePanel = shortcut_action(mainWindow, u'viewLivePanel', [QtGui.QKeySequence(u'F12')], self.setLivePanelVisibility, checked=liveVisible, category=UiStrings().View) - action_list.add_category(UiStrings().ViewMode, CategoryOrder.standardMenu) - self.ModeDefaultItem = checkable_action( - mainWindow, u'ModeDefaultItem', category=UiStrings().ViewMode) - self.ModeSetupItem = checkable_action( - mainWindow, u'ModeLiveItem', 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) + 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', + 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', + self.toolsOpenDataFolder = icon_action(mainWindow, + u'toolsOpenDataFolder', u':/general/general_open.png', category=UiStrings().Tools) - action_list.add_category(UiStrings().Settings, CategoryOrder.standardMenu) + 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) - self.LanguageGroup = QtGui.QActionGroup(mainWindow) - self.LanguageGroup.setExclusive(True) - self.LanguageGroup.setObjectName(u'LanguageGroup') - add_actions(self.LanguageGroup, [self.AutoLanguageItem]) + 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', + add_actions(self.languageGroup, [languageItem]) + self.settingsShortcutsItem = icon_action(mainWindow, + u'settingsShortcutsItem', u':/system/system_configure_shortcuts.png', category=UiStrings().Settings) - self.DisplayTagItem = icon_action(mainWindow, - u'DisplayTagItem', u':/system/tag_editor.png', + # 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', + 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.HelpDocumentationItem = icon_action(mainWindow, - u'HelpDocumentationItem', u':/system/system_help_contents.png', - category=None)#UiStrings().Help) - self.HelpDocumentationItem.setEnabled(False) - self.HelpAboutItem = shortcut_action(mainWindow, u'HelpAboutItem', - [QtGui.QKeySequence(u'Ctrl+F1')], self.onHelpAboutItemClicked, + self.aboutItem = shortcut_action(mainWindow, u'aboutItem', + [QtGui.QKeySequence(u'Ctrl+F1')], self.onAboutItemClicked, u':/system/system_about.png', category=UiStrings().Help) - self.HelpOnlineHelpItem = base_action( - mainWindow, u'HelpOnlineHelpItem', category=UiStrings().Help) - self.helpWebSiteItem = base_action( - mainWindow, u'helpWebSiteItem', category=UiStrings().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)) + 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.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.HelpDocumentationItem.setVisible(False) + self.toolsAddToolItem.setVisible(False) + self.importLanguageItem.setVisible(False) + self.exportLanguageItem.setVisible(False) + self.setLockPanel(panelLocked) + self.settingsImported = False def retranslateUi(self, mainWindow): """ @@ -322,131 +371,153 @@ class Ui_MainWindow(object): """ 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.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.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.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.printServiceOrderItem.setText(UiStrings().PrintServiceOrder) + self.printServiceOrderItem.setText(UiStrings().PrintService) self.printServiceOrderItem.setStatusTip(translate('OpenLP.MainWindow', - 'Print the current Service Order.')) - 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.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.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.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.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.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.settingsPluginListItem.setText(translate('OpenLP.MainWindow', '&Plugin List')) self.settingsPluginListItem.setStatusTip( translate('OpenLP.MainWindow', 'List the Plugins')) - 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.HelpOnlineHelpItem.setText( + if os.name == u'nt': + self.offlineHelpItem.setText( + translate('OpenLP.MainWindow', '&User Guide')) + self.onlineHelpItem.setText( translate('OpenLP.MainWindow', '&Online Help')) - # Uncomment after 1.9.5 beta string freeze - #self.HelpOnlineHelpItem.setShortcut( - # translate('OpenLP.MainWindow', 'F1')) - self.helpWebSiteItem.setText( + self.webSiteItem.setText( translate('OpenLP.MainWindow', '&Web Site')) - 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.AutoLanguageItem.setText( + self.autoLanguageItem.setText( translate('OpenLP.MainWindow', '&Autodetect')) - self.AutoLanguageItem.setStatusTip(translate('OpenLP.MainWindow', + self.autoLanguageItem.setStatusTip(translate('OpenLP.MainWindow', 'Use the system language, if available.')) - self.ToolsAddToolItem.setText( + 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.')) @@ -456,85 +527,98 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): """ log.info(u'MainWindow loaded') - def __init__(self, screens, clipboard, arguments): + 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.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) - self.settingsForm = SettingsForm(self.screens, self, self) - self.displayTagForm = DisplayTagForm(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.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.HelpOnlineHelpItem, - QtCore.SIGNAL(u'triggered()'), self.onHelpOnLineHelpClicked) - QtCore.QObject.connect(self.ToolsOpenDataFolder, + QtCore.QObject.connect(self.toolsOpenDataFolder, QtCore.SIGNAL(u'triggered()'), self.onToolsOpenDataFolderClicked) - 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.settingsImportItem, + QtCore.SIGNAL(u'triggered()'), self.onSettingsImportItemClicked) + QtCore.QObject.connect(self.settingsExportItem, + QtCore.SIGNAL(u'triggered()'), self.onSettingsExportItemClicked) # i18n set signals for languages - 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(), @@ -545,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 @@ -572,31 +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() + # 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') 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): """ @@ -619,29 +713,35 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): if self.liveController.display.isVisible(): self.liveController.display.setFocus() self.activateWindow() - # On Windows, arguments contains the entire commandline - # So args[0]=='python' args[1]=='openlp.pyw' - # Therefore this approach is not going to work - # Bypass for now. - if len(self.arguments) and os.name != u'nt': + if len(self.arguments): args = [] for a in self.arguments: args.extend([a]) - self.ServiceManagerContents.loadFile(unicode(args[0])) + 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 @@ -652,20 +752,59 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): plugin.firstTime() Receiver.send_message(u'openlp_process_events') temp_dir = os.path.join(unicode(gettempdir()), u'openlp') - for filename in os.listdir(temp_dir): - os.remove(os.path.join(temp_dir, filename)) - os.removedirs(temp_dir) + 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', @@ -674,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): @@ -689,14 +831,20 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): import webbrowser webbrowser.open_new(u'http://openlp.org/') - def onHelpOnLineHelpClicked(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 onHelpAboutItemClicked(self): + def onAboutItemClicked(self): """ Show the About form """ @@ -716,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): """ @@ -742,6 +896,171 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): 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): """ Put OpenLP into "Default" view mode. @@ -777,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: @@ -827,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() @@ -880,10 +1207,10 @@ 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) @@ -891,7 +1218,8 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): self.mediaManagerDock.setVisible(not self.mediaManagerDock.isVisible()) def toggleServiceManager(self): - self.serviceManagerDock.setVisible(not self.serviceManagerDock.isVisible()) + self.serviceManagerDock.setVisible( + not self.serviceManagerDock.isVisible()) def toggleThemeManager(self): self.themeManagerDock.setVisible(not self.themeManagerDock.isVisible()) @@ -909,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): """ @@ -924,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): """ @@ -932,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() @@ -941,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) @@ -961,32 +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 = 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.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): """ @@ -1008,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 ca4f4d442..cc6cc92bc 100644 --- a/openlp/core/ui/mediadockmanager.py +++ b/openlp/core/ui/mediadockmanager.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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(): + 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 84fb845c6..3fc4bf34b 100644 --- a/openlp/core/ui/plugindialog.py +++ b/openlp/core/ui/plugindialog.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -85,4 +86,4 @@ class Ui_PluginViewDialog(object): self.statusComboBox.setItemText(0, translate('OpenLP.PluginForm', 'Active')) self.statusComboBox.setItemText(1, - translate('OpenLP.PluginForm', 'Inactive')) \ No newline at end of file + translate('OpenLP.PluginForm', 'Inactive')) diff --git a/openlp/core/ui/pluginform.py b/openlp/core/ui/pluginform.py index bfac5f3e0..c529248a9 100644 --- a/openlp/core/ui/pluginform.py +++ b/openlp/core/ui/pluginform.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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 889bc4f7d..8287ef02a 100644 --- a/openlp/core/ui/printservicedialog.py +++ b/openlp/core/ui/printservicedialog.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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,18 +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(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,24 +72,18 @@ class Ui_PrintServiceDialog(object): self.toolbar.addSeparator() self.zoomInButton = QtGui.QToolButton(self.toolbar) self.zoomInButton.setIcon(build_icon(u':/general/general_zoom_in.png')) - self.zoomInButton.setToolTip(translate('OpenLP.PrintServiceForm', - 'Zoom In')) 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( build_icon(u':/general/general_zoom_out.png')) - self.zoomOutButton.setToolTip(translate('OpenLP.PrintServiceForm', - 'Zoom Out')) 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( build_icon(u':/general/general_zoom_original.png')) - self.zoomOriginalButton.setToolTip(translate('OpenLP.PrintServiceForm', - 'Zoom Original')) self.zoomOriginalButton.setObjectName(u'zoomOriginalButton') self.zoomOriginalButton.setIconSize(QtCore.QSize(22, 22)) self.toolbar.addWidget(self.zoomOriginalButton) @@ -115,20 +101,17 @@ 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) @@ -148,20 +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.')) + '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]) \ No newline at end of file + '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 bb87cf32f..c08b6293e 100644 --- a/openlp/core/ui/printserviceform.py +++ b/openlp/core/ui/printserviceform.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -23,13 +24,14 @@ # 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, get_text_file_string +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 @@ -41,42 +43,44 @@ http://doc.trolltech.com/4.7/richtext-html-subset.html#css-properties */ .serviceTitle { - font-weight:600; - font-size:x-large; - color:black; + font-weight: 600; + font-size: x-large; + color: black; } .item { - color:black; + color: black; } .itemTitle { - font-weight:600; - font-size:large; + font-weight: 600; + font-size: large; } -.itemText {} +.itemText { + margin-top: 10px; +} .itemFooter { - font-size:8px; + font-size: 8px; } .itemNotes {} .itemNotesTitle { - font-weight:bold; - font-size:12px; + font-weight: bold; + font-size: 12px; } .itemNotesText { - font-size:11px; + font-size: 11px; } .media {} .mediaTitle { - font-weight:bold; - font-size:11px; + font-weight: bold; + font-size: 11px; } .mediaText {} @@ -88,16 +92,16 @@ http://doc.trolltech.com/4.7/richtext-html-subset.html#css-properties } .customNotesTitle { - font-weight:bold; - font-size:11px; + font-weight: bold; + font-size: 11px; } .customNotesText { - font-size:11px; + font-size: 11px; } .newPage { - page-break-before:always; + page-break-before: always; } """ @@ -134,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, @@ -182,7 +184,7 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog): 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', unicode(self.titleLineEdit.text()), + 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) @@ -192,8 +194,9 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog): classId=u'customNotes') self._addElement(u'span', translate('OpenLP.ServiceManager', 'Custom Service Notes: '), div, classId=u'customNotesTitle') - self._addElement(u'span', self.footerTextEdit.toPlainText(), div, - classId=u'customNotesText') + self._addElement(u'span', + cgi.escape(self.footerTextEdit.toPlainText()), + div, classId=u'customNotesText') self.document.setHtml(html.tostring(html_data)) self.previewWidget.updatePreview() @@ -203,19 +206,19 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog): 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' ' + item.get_display_title(), - item_title) + 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']: - p = self._addElement(u'div', parent=div, + text_div = self._addElement(u'div', parent=div, classId=u'itemText') else: - self._addElement(u'br', parent=p) - self._addElement(u'p', slide[u'html'], p) + 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(): @@ -229,8 +232,9 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog): foot_text = item.foot_text foot_text = foot_text.partition(u'
')[2] if foot_text: - foot = self._addElement(u'div', foot_text, parent=div, - classId=u'itemFooter') + 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: @@ -238,8 +242,8 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog): self._addElement(u'span', translate('OpenLP.ServiceManager', 'Notes: '), p, classId=u'itemNotesTitle') - notes = self._addElement(u'span', - item.notes.replace(u'\n', u'
'), p, + 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(): @@ -323,13 +327,14 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog): """ Copies the display text to the clipboard as plain text """ - self.mainWindow.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.update_song_usage() self.mainWindow.clipboard.setText(self.document.toHtml()) def printServiceOrder(self): @@ -338,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) @@ -394,3 +400,9 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog): 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 15dbd9883..5b81c8be8 100644 --- a/openlp/core/ui/screen.py +++ b/openlp/core/ui/screen.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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 4e966a38b..d821430b2 100644 --- a/openlp/core/ui/serviceitemeditdialog.py +++ b/openlp/core/ui/serviceitemeditdialog.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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 400566099..974133c3d 100644 --- a/openlp/core/ui/serviceitemeditform.py +++ b/openlp/core/ui/serviceitemeditform.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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: diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index fed2efa3d..8ad2db9c2 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -23,10 +24,13 @@ # 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__) @@ -36,7 +40,7 @@ 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, \ - context_menu_action, find_and_set_in_combo_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, \ @@ -47,18 +51,18 @@ 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 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: @@ -73,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) @@ -115,11 +122,11 @@ class ServiceManager(QtGui.QWidget): UiStrings().CreateService, self.onNewServiceClicked) self.toolbar.addToolbarButton( UiStrings().OpenService, u':/general/general_open.png', - translate('OpenLP.ServiceManager', 'Load an existing service'), + 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'), + translate('OpenLP.ServiceManager', 'Save this service.'), self.saveFile) self.toolbar.addSeparator() self.themeLabel = QtGui.QLabel(u'%s:' % UiStrings().Theme, self) @@ -128,7 +135,7 @@ class ServiceManager(QtGui.QWidget): 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( @@ -274,8 +281,6 @@ class ServiceManager(QtGui.QWidget): 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(), @@ -287,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 @@ -300,31 +305,34 @@ 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) @@ -365,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): """ @@ -403,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, - UiStrings.OpenFile, - 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) @@ -442,29 +464,56 @@ 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() + 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 = os.path.splitext(file_name)[0] - service_file_name = basename + '.osd' - log.debug(u'ServiceManager.saveFile - %s' % path_file_name) - SettingsManager.set_last_dir(self.mainwindow.serviceSettingsSection, + 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: - service.append({u'serviceitem': - item[u'service_item'].get_service_repr()}) + 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'] @@ -473,25 +522,29 @@ class ServiceManager(QtGui.QWidget): # Only write a file once if path_from in write_list: continue - file_size = os.path.getsize(path_from) - #size_limit = 52428800 # 50MiB - #if file_size > size_limit: - # # File exeeds size_limit bytes, ask user - # message = unicode(translate('OpenLP.ServiceManager', - # 'Do you want to include \n%.1f MB file "%s"\n' - # 'into the service file?\nThis may take some time.\n\n' - # 'Please note that you need to\ntake care of that file' - # ' yourself,\nif you leave it out.')) % \ - # (file_size/1048576, os.path.split(path_from)[1]) - # ans = QtGui.QMessageBox.question(self.mainwindow, - # translate('OpenLP.ServiceManager', 'Including Large ' - # 'File'), message, QtGui.QMessageBox.StandardButtons( - # QtGui.QMessageBox.Ok|QtGui.QMessageBox.Cancel), - # QtGui.QMessageBox.Ok) - # if ans == QtGui.QMessageBox.Cancel: - # continue - write_list.append(path_from) - total_size += file_size + 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) @@ -501,26 +554,54 @@ class ServiceManager(QtGui.QWidget): log.debug(u'ServiceManager.saveFile - allowZip64 is %s' % allow_zip_64) zip = None success = True + self.mainwindow.incrementProgressBar() try: - zip = zipfile.ZipFile(path_file_name, 'w', zipfile.ZIP_STORED, + 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 path_from in write_list: - zip.write(path_from, path_from.encode(u'utf-8')) + 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') + 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) - else: - delete_file(path_file_name) + try: + delete_file(temp_file_name) + except: + pass return success def saveFileAs(self): @@ -531,7 +612,7 @@ class ServiceManager(QtGui.QWidget): fileName = unicode(QtGui.QFileDialog.getSaveFileName(self.mainwindow, UiStrings().SaveService, SettingsManager.get_last_dir( - self.mainwindow.serviceSettingsSection), + self.mainwindow.servicemanagerSettingsSection), translate('OpenLP.ServiceManager', 'OpenLP Service Files (*.osz)'))) if not fileName: return False @@ -553,8 +634,8 @@ class ServiceManager(QtGui.QWidget): 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', @@ -562,47 +643,50 @@ 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.from_service = True - 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) self.setFileName(fileName) self.mainwindow.addRecentFile(fileName) self.setModified(False) QtCore.QSettings().setValue( - 'service/last file', QtCore.QVariant(fileName)) - Receiver.send_message(u'cursor_normal') + '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, 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.')) - log.exception(u'Problem loading service file %s' % fileName) except zipfile.BadZipfile: if os.path.getsize(fileName) == 0: log.exception(u'Service file is zero sized: %s' % fileName) @@ -616,13 +700,16 @@ class ServiceManager(QtGui.QWidget): QtGui.QMessageBox.information(self, translate('OpenLP.ServiceManager', 'Corrupt File'), translate('OpenLP.ServiceManager', 'This file is either ' - 'corrupt or not an OpenLP 2.0 service file.')) + 'corrupt or it is not an OpenLP 2.0 service file.')) return finally: if fileTo: fileTo.close() if zip: zip.close() + self.mainwindow.finishedProgressBar() + Receiver.send_message(u'cursor_normal') + self.repaintServiceList(-1, -1) def loadLastFile(self): """ @@ -631,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) @@ -648,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.AllowsVariableStartTime): + .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] @@ -689,6 +772,9 @@ class ServiceManager(QtGui.QWidget): 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_(): @@ -756,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): """ @@ -769,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() + 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() + item = self.serviceManagerList.currentItem() + itemAfter = self.serviceManagerList.itemBelow(item) + if itemAfter is None: + return + self.serviceManagerList.setCurrentItem(itemAfter) def onCollapseAll(self): """ @@ -820,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): """ @@ -828,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): """ @@ -836,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): """ @@ -844,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() + self.setModified() def onServiceUp(self): """ @@ -868,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() + 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() + 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() + self.setModified() def onDeleteFromService(self): """ @@ -902,7 +963,7 @@ class ServiceManager(QtGui.QWidget): if item != -1: self.serviceItems.remove(self.serviceItems[item]) self.repaintServiceList(item - 1, -1) - self.setModified() + self.setModified() def repaintServiceList(self, serviceItem, serviceItemChild): """ @@ -944,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.AllowsVariableStartTime): - 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) @@ -979,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): """ @@ -989,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() @@ -1001,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: @@ -1016,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() + # 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 = int(editId) - self.setModified() + 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() + 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 @@ -1069,7 +1160,6 @@ 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() @@ -1078,22 +1168,24 @@ 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: @@ -1105,6 +1197,7 @@ class ServiceManager(QtGui.QWidget): """ 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( @@ -1114,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): """ @@ -1146,6 +1240,7 @@ class ServiceManager(QtGui.QWidget): 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) @@ -1155,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() @@ -1165,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): """ @@ -1172,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) @@ -1219,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 @@ -1248,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: @@ -1272,19 +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, - self.onThemeChangeAction, context=QtCore.Qt.WidgetShortcut) - self.themeMenu.addAction(action) + themeAction = context_menu_action(self.themeMenu, None, theme, + self.onThemeChangeAction) + themeAction.setObjectName(theme) + themeAction.setCheckable(True) + themeGroup.addAction(themeAction) find_and_set_in_combo_box(self.themeComboBox, self.service_theme) - self.mainwindow.renderManager.set_service_theme(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): @@ -1294,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 ef1c627d4..ddfcb3381 100644 --- a/openlp/core/ui/servicenoteform.py +++ b/openlp/core/ui/servicenoteform.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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 50bcca4e2..296337f0f 100644 --- a/openlp/core/ui/settingsdialog.py +++ b/openlp/core/ui/settingsdialog.py @@ -5,9 +5,10 @@ # 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, # +# 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 # diff --git a/openlp/core/ui/settingsform.py b/openlp/core/ui/settingsform.py index 1967412f5..37da93b5b 100644 --- a/openlp/core/ui/settingsform.py +++ b/openlp/core/ui/settingsform.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -28,7 +29,7 @@ The :mod:`settingsform` provides a user interface for the OpenLP settings """ import logging -from PyQt4 import QtGui, QtCore +from PyQt4 import QtGui from openlp.core.lib import Receiver, build_icon, PluginStatus from openlp.core.ui import AdvancedTab, GeneralTab, ThemesTab @@ -40,14 +41,14 @@ 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 - self.generalTab = GeneralTab(self, screens) + self.generalTab = GeneralTab(self) # Themes tab self.themesTab = ThemesTab(self, mainWindow) # Advanced tab @@ -57,7 +58,7 @@ class SettingsForm(QtGui.QDialog, Ui_SettingsDialog): # load all the settings self.settingListWidget.clear() for tabIndex in range(0, self.stackedLayout.count() + 1): - # take at 0 and the rest shuffell up. + # 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) diff --git a/openlp/core/ui/shortcutlistdialog.py b/openlp/core/ui/shortcutlistdialog.py index 288086cba..a9b9b22cf 100644 --- a/openlp/core/ui/shortcutlistdialog.py +++ b/openlp/core/ui/shortcutlistdialog.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -28,6 +29,24 @@ 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') @@ -36,7 +55,7 @@ class Ui_ShortcutListDialog(object): self.shortcutListLayout.setObjectName(u'shortcutListLayout') self.descriptionLabel = QtGui.QLabel(shortcutListDialog) self.descriptionLabel.setObjectName(u'descriptionLabel') - self.descriptionLabel.setWordWrap(True) + self.descriptionLabel.setWordWrap(True) self.shortcutListLayout.addWidget(self.descriptionLabel) self.treeWidget = QtGui.QTreeWidget(shortcutListDialog) self.treeWidget.setObjectName(u'treeWidget') @@ -56,12 +75,11 @@ class Ui_ShortcutListDialog(object): self.detailsLayout.addWidget(self.customRadioButton, 1, 0, 1, 1) self.primaryLayout = QtGui.QHBoxLayout() self.primaryLayout.setObjectName(u'primaryLayout') - self.primaryPushButton = QtGui.QPushButton(shortcutListDialog) + 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.primaryPushButton.setCheckable(True) self.primaryLayout.addWidget(self.primaryPushButton) self.clearPrimaryButton = QtGui.QToolButton(shortcutListDialog) self.clearPrimaryButton.setObjectName(u'clearPrimaryButton') @@ -72,9 +90,8 @@ class Ui_ShortcutListDialog(object): self.detailsLayout.addLayout(self.primaryLayout, 1, 1, 1, 1) self.alternateLayout = QtGui.QHBoxLayout() self.alternateLayout.setObjectName(u'alternateLayout') - self.alternatePushButton = QtGui.QPushButton(shortcutListDialog) + self.alternatePushButton = CaptureShortcutButton(shortcutListDialog) self.alternatePushButton.setObjectName(u'alternatePushButton') - self.alternatePushButton.setCheckable(True) self.alternatePushButton.setIcon( build_icon(u':/system/system_configure_shortcuts.png')) self.alternateLayout.addWidget(self.alternatePushButton) @@ -106,7 +123,7 @@ 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.')) diff --git a/openlp/core/ui/shortcutlistform.py b/openlp/core/ui/shortcutlistform.py index 3136c9b1f..1eccddc95 100644 --- a/openlp/core/ui/shortcutlistform.py +++ b/openlp/core/ui/shortcutlistform.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -71,7 +72,9 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog): QtCore.SIGNAL(u'clicked(bool)'), self.onCustomRadioButtonClicked) def keyPressEvent(self, event): - if self.primaryPushButton.isChecked() or \ + 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: @@ -95,6 +98,9 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog): 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) if self._validiate_shortcut(self._currentItemAction(), key_sequence): if self.primaryPushButton.isChecked(): @@ -163,6 +169,7 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog): self.customRadioButton.setChecked(True) if toggled: self.alternatePushButton.setChecked(False) + self.primaryPushButton.setText(u'') return action = self._currentItemAction() if action is None: @@ -181,6 +188,7 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog): self.customRadioButton.setChecked(True) if toggled: self.primaryPushButton.setChecked(False) + self.alternatePushButton.setText(u'') return action = self._currentItemAction() if action is None: @@ -195,7 +203,7 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog): 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. + # why we have to adjust the primary button's text. self.primaryPushButton.setText(self.alternatePushButton.text()) self.alternatePushButton.setText(u'') self.refreshShortcutList() @@ -211,10 +219,11 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog): 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) - self.onCurrentItemChanged(item) def onCurrentItemChanged(self, item=None, previousItem=None): """ @@ -238,7 +247,8 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog): 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. + # 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() @@ -247,6 +257,12 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog): 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) @@ -265,7 +281,8 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog): """ Restores all default shortcuts. """ - if self.buttonBox.buttonRole(button) != QtGui.QDialogButtonBox.ResetRole: + if self.buttonBox.buttonRole(button) != \ + QtGui.QDialogButtonBox.ResetRole: return if QtGui.QMessageBox.question(self, translate('OpenLP.ShortcutListDialog', 'Restore Default Shortcuts'), diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index dfa313cc1..d871dc474 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -26,14 +27,16 @@ 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 import OpenLPToolbar, Receiver, ItemCapabilities, \ + translate, build_icon from openlp.core.lib.ui import UiStrings, shortcut_action -from openlp.core.ui import HideMode, MainDisplay +from openlp.core.ui import HideMode, MainDisplay, ScreenList from openlp.core.utils.actions import ActionList, CategoryOrder log = logging.getLogger(__name__) @@ -45,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): @@ -53,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' ] @@ -78,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 @@ -117,7 +117,7 @@ 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.setObjectName(u'previewListWidget') self.previewListWidget.setSelectionBehavior( QtGui.QAbstractItemView.SelectRows) self.previewListWidget.setSelectionMode( @@ -140,19 +140,20 @@ 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'), + 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'), + 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) @@ -180,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().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 @@ -234,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) @@ -260,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, @@ -286,29 +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.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)'), @@ -318,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( @@ -327,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) @@ -358,11 +454,90 @@ 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): self.previousItem.setObjectName(u'previousItemPreview') @@ -403,9 +578,11 @@ class SlideController(QtGui.QWidget): 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): @@ -414,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) @@ -459,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) @@ -490,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): """ @@ -548,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): @@ -558,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) + 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() - return - self._processItem(item, slideno) + else: + self._processItem(item, slidenum) def _processItem(self, serviceItem, slideno): """ @@ -576,19 +771,36 @@ class SlideController(QtGui.QWidget): log.debug(u'processManagerItem live = %s' % self.isLive) self.onStopLoop() old_item = self.serviceItem - self.serviceItem = 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, self.hideMode(), slideno]) self.slideList = {} - width = self.parent.controlSplitter.sizes()[self.split] + 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: @@ -674,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.slideSelected() - def onSlideSelectedIndex(self, message): """ Go to the requested slide @@ -730,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 @@ -738,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): """ @@ -766,11 +946,11 @@ class SlideController(QtGui.QWidget): self.desktopScreen.setChecked(False) if checked: QtCore.QSettings().setValue( - self.parent.generalSettingsSection + u'/screen blank', + self.parent().generalSettingsSection + u'/screen blank', QtCore.QVariant(u'blanked')) else: QtCore.QSettings().remove( - self.parent.generalSettingsSection + u'/screen blank') + self.parent().generalSettingsSection + u'/screen blank') self.blankPlugin() self.updatePreview() @@ -787,11 +967,11 @@ class SlideController(QtGui.QWidget): self.desktopScreen.setChecked(False) if checked: QtCore.QSettings().setValue( - self.parent.generalSettingsSection + u'/screen blank', + self.parent().generalSettingsSection + u'/screen blank', QtCore.QVariant(u'themed')) else: QtCore.QSettings().remove( - self.parent.generalSettingsSection + u'/screen blank') + self.parent().generalSettingsSection + u'/screen blank') self.blankPlugin() self.updatePreview() @@ -808,11 +988,11 @@ class SlideController(QtGui.QWidget): self.desktopScreen.setChecked(checked) if checked: QtCore.QSettings().setValue( - self.parent.generalSettingsSection + u'/screen blank', + self.parent().generalSettingsSection + u'/screen blank', QtCore.QVariant(u'hidden')) else: QtCore.QSettings().remove( - self.parent.generalSettingsSection + u'/screen blank') + self.parent().generalSettingsSection + u'/screen blank') self.hidePlugin(checked) self.updatePreview() @@ -825,16 +1005,21 @@ class SlideController(QtGui.QWidget): if self.serviceItem is not None: if hide_mode: if not self.serviceItem.is_command(): - Receiver.send_message(u'maindisplay_hide', hide_mode) + Receiver.send_message(u'live_display_hide', hide_mode) Receiver.send_message(u'%s_blank' % self.serviceItem.name.lower(), [self.serviceItem, self.isLive, hide_mode]) else: if not self.serviceItem.is_command(): - Receiver.send_message(u'maindisplay_show') + 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): """ @@ -843,16 +1028,21 @@ class SlideController(QtGui.QWidget): log.debug(u'hidePlugin %s ', hide) if self.serviceItem is not None: if hide: - Receiver.send_message(u'maindisplay_hide', HideMode.Screen) + 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'maindisplay_show') + 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, start=False): """ @@ -873,20 +1063,18 @@ class SlideController(QtGui.QWidget): 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: if start: self.display.buildHtml(self.serviceItem, toDisplay) - frame = self.display.preview() else: - frame = self.display.image(toDisplay) + 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, @@ -914,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): """ @@ -927,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. """ @@ -943,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.slideSelected() - def onSlideSelectedPreviousNoloop(self): - self.onSlideSelectedPrevious(False) - - def onSlideSelectedPrevious(self, loop=True): + def onSlideSelectedPrevious(self): """ Go to the previous slide. """ @@ -967,7 +1151,8 @@ 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 @@ -980,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.slideSelected() + self.onStopLoop() def onStartLoop(self): """ @@ -1002,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): """ @@ -1012,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): """ @@ -1030,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): @@ -1048,7 +1292,7 @@ class SlideController(QtGui.QWidget): Receiver.send_message('servicemanager_preview_live', u'%s:%s' % (self.serviceItem._uuid, row)) else: - self.parent.liveController.addServiceManagerItem( + self.parent().liveController.addServiceManagerItem( self.serviceItem, row) def onMediaStart(self, item): @@ -1154,4 +1398,3 @@ class SlideController(QtGui.QWidget): return HideMode.Screen else: return None - diff --git a/openlp/core/ui/splashscreen.py b/openlp/core/ui/splashscreen.py index 84aa1d7df..036daf968 100644 --- a/openlp/core/ui/splashscreen.py +++ b/openlp/core/ui/splashscreen.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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 2d1711231..61e4eb662 100644 --- a/openlp/core/ui/starttimedialog.py +++ b/openlp/core/ui/starttimedialog.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -118,4 +119,4 @@ class Ui_StartTimeDialog(object): 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')) \ No newline at end of file + self.lengthLabel.setText(translate('OpenLP.StartTimeForm', 'Length')) diff --git a/openlp/core/ui/starttimeform.py b/openlp/core/ui/starttimeform.py index 956b01a9d..45c0b70b7 100644 --- a/openlp/core/ui/starttimeform.py +++ b/openlp/core/ui/starttimeform.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -73,14 +74,14 @@ class StartTimeForm(QtGui.QDialog, Ui_StartTimeDialog): title=translate('OpenLP.StartTimeForm', 'Time Validation Error'), message=translate('OpenLP.StartTimeForm', - 'End time is set after the end of the media item')) + '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 End Time of the media item')) + '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 diff --git a/openlp/core/ui/themeform.py b/openlp/core/ui/themeform.py index 019ab5bfe..9ccd91d08 100644 --- a/openlp/core/ui/themeform.py +++ b/openlp/core/ui/themeform.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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 @@ -57,6 +59,7 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): 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) @@ -65,6 +68,8 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): self.onGradientComboBoxCurrentIndexChanged) QtCore.QObject.connect(self.colorButton, 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.QObject.connect(self.gradientEndButton, @@ -85,6 +90,9 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): self.onShadowCheckCheckBoxStateChanged) QtCore.QObject.connect(self.footerColorButton, 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) @@ -201,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): """ @@ -226,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 @@ -329,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 == \ @@ -463,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. @@ -563,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 335b9b174..596bce5d9 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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,8 +58,6 @@ 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) @@ -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) @@ -170,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 @@ -196,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): """ @@ -297,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): """ @@ -362,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: @@ -373,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: @@ -459,7 +455,10 @@ class ThemeManager(QtGui.QWidget): QtCore.QVariant(theme.theme_name)) self.configUpdated() files = SettingsManager.get_files(self.settingsSection, u'.png') - files.sort() + # 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 @@ -473,15 +472,12 @@ class ThemeManager(QtGui.QWidget): name = textName thumb = os.path.join(self.thumbPath, u'%s.png' % textName) item_name = QtGui.QListWidgetItem(name) - if os.path.exists(thumb): + if validate_thumb(theme, thumb): icon = build_icon(thumb) else: - icon = build_icon(theme) - pixmap = icon.pixmap(QtCore.QSize(88, 50)) - pixmap.save(thumb, u'png') + icon = create_thumb(theme, thumb) item_name.setIcon(icon) - item_name.setData(QtCore.Qt.UserRole, - QtCore.QVariant(textName)) + item_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(textName)) self.themeListWidget.addItem(item_name) self.themelist.append(textName) self._pushThemes() @@ -610,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): @@ -623,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 @@ -653,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. @@ -669,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): @@ -748,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): """ @@ -806,4 +818,5 @@ class ThemeManager(QtGui.QWidget): vAlignCorrection = VerticalType.Bottom newtheme.display_horizontal_align = theme.HorizontalAlign newtheme.display_vertical_align = vAlignCorrection - return newtheme.extract_xml() \ No newline at end of file + return newtheme.extract_xml() + diff --git a/openlp/core/ui/themestab.py b/openlp/core/ui/themestab.py index fccef1244..572efdf4b 100644 --- a/openlp/core/ui/themestab.py +++ b/openlp/core/ui/themestab.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -36,7 +37,7 @@ class ThemesTab(SettingsTab): """ def __init__(self, parent, mainwindow): self.mainwindow = mainwindow - generalTranslated = translate('ThemeTab', 'Themes') + generalTranslated = translate('OpenLP.ThemesTab', 'Themes') SettingsTab.__init__(self, parent, u'Themes', generalTranslated) self.icon_path = u':/themes/theme_new.png' @@ -144,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.mainwindow.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) @@ -167,7 +166,7 @@ class ThemesTab(SettingsTab): def onDefaultComboBoxChanged(self, value): self.global_theme = unicode(self.DefaultComboBox.currentText()) - self.mainwindow.renderManager.set_global_theme( + self.mainwindow.renderer.set_global_theme( self.global_theme, self.theme_level) self.__previewGlobalTheme() @@ -185,10 +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) + self.DefaultComboBox.addItems(theme_list) find_and_set_in_combo_box(self.DefaultComboBox, self.global_theme) - self.mainwindow.renderManager.set_global_theme( + self.mainwindow.renderer.set_global_theme( self.global_theme, self.theme_level) if self.global_theme is not u'': self.__previewGlobalTheme() @@ -203,4 +201,4 @@ class ThemesTab(SettingsTab): if not preview.isNull(): preview = preview.scaled(300, 255, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) - self.DefaultListView.setPixmap(preview) \ No newline at end of file + self.DefaultListView.setPixmap(preview) diff --git a/openlp/core/ui/themewizard.py b/openlp/core/ui/themewizard.py index 759b36101..1135db274 100644 --- a/openlp/core/ui/themewizard.py +++ b/openlp/core/ui/themewizard.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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) @@ -442,6 +449,8 @@ class Ui_ThemeWizard(object): translate('OpenLP.ThemeWizard', 'Top Left - Bottom Right')) self.gradientComboBox.setItemText(BackgroundGradientType.LeftBottom, translate('OpenLP.ThemeWizard', 'Bottom Left - Top Right')) + 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')) @@ -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( @@ -537,4 +551,4 @@ class Ui_ThemeWizard(object): labelWidth = max(self.backgroundLabel.minimumSizeHint().width(), self.horizontalLabel.minimumSizeHint().width()) self.spacer.changeSize(labelWidth, 0, - QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) \ No newline at end of file + QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) diff --git a/openlp/core/ui/wizard.py b/openlp/core/ui/wizard.py index 9d1147638..9d8a106ed 100644 --- a/openlp/core/ui/wizard.py +++ b/openlp/core/ui/wizard.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -189,6 +190,14 @@ 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): """ diff --git a/openlp/core/utils/__init__.py b/openlp/core/utils/__init__.py index 1bc7037ae..fbf185474 100644 --- a/openlp/core/utils/__init__.py +++ b/openlp/core/utils/__init__.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -52,6 +53,7 @@ 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): """ @@ -60,20 +62,17 @@ class VersionThread(QtCore.QThread): """ def __init__(self, parent): QtCore.QThread.__init__(self, parent) - 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') 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)) @@ -82,7 +81,7 @@ class VersionThread(QtCore.QThread): remote_version[u'revision'] = int(match.group(4)) else: return - match = self.version_splitter.match(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)) @@ -101,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 @@ -114,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): """ @@ -139,6 +155,8 @@ class AppLocation(object): os.path.abspath(os.path.split(sys.argv[0])[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) @@ -328,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: @@ -373,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. @@ -446,25 +475,6 @@ 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. @@ -497,5 +507,5 @@ from actions import ActionList __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'string_is_unicode', - u'get_uno_command', u'get_uno_instance', u'delete_file'] + 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 0c4eee655..86ee69a48 100644 --- a/openlp/core/utils/actions.py +++ b/openlp/core/utils/actions.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -202,7 +203,8 @@ class ActionList(object): Add an action to the list of actions. ``action`` - The action to add (QAction). + 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 diff --git a/openlp/core/utils/languagemanager.py b/openlp/core/utils/languagemanager.py index 9dbf9a779..7b57ee2bc 100644 --- a/openlp/core/utils/languagemanager.py +++ b/openlp/core/utils/languagemanager.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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,8 +57,14 @@ 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(): @@ -69,6 +77,8 @@ class LanguageManager(object): 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 @@ -115,7 +125,7 @@ class LanguageManager(object): language = u'en' if action: action_name = unicode(action.objectName()) - if action_name == u'AutoLanguageItem': + if action_name == u'autoLanguageItem': LanguageManager.auto_language = True else: LanguageManager.auto_language = False diff --git a/openlp/plugins/__init__.py b/openlp/plugins/__init__.py index 7a160a316..48fe8cb77 100644 --- a/openlp/plugins/__init__.py +++ b/openlp/plugins/__init__.py @@ -5,9 +5,10 @@ # 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, # +# 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 # diff --git a/openlp/plugins/alerts/__init__.py b/openlp/plugins/alerts/__init__.py index 3a0892d49..006db9361 100644 --- a/openlp/plugins/alerts/__init__.py +++ b/openlp/plugins/alerts/__init__.py @@ -5,9 +5,10 @@ # 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, # +# 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 # diff --git a/openlp/plugins/alerts/alertsplugin.py b/openlp/plugins/alerts/alertsplugin.py index 0cfa9c60a..493f08ba0 100644 --- a/openlp/plugins/alerts/alertsplugin.py +++ b/openlp/plugins/alerts/alertsplugin.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -26,11 +27,12 @@ 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 @@ -38,11 +40,81 @@ 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', plugin_helpers, + Plugin.__init__(self, u'alerts', plugin_helpers, settings_tab_class=AlertsTab) self.weight = -3 self.icon_path = u':/plugins/plugin_alerts.png' @@ -67,7 +139,7 @@ class AlertsPlugin(Plugin): 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) @@ -78,7 +150,6 @@ class AlertsPlugin(Plugin): self.toolsAlertItem.setVisible(True) action_list = ActionList.get_instance() action_list.add_action(self.toolsAlertItem, UiStrings().Tools) - self.liveController.alertTab = self.settings_tab def finalise(self): """ @@ -103,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): @@ -118,4 +189,37 @@ class AlertsPlugin(Plugin): ## Name for MediaDockManager, SettingsManager ## self.textStrings[StringContent.VisibleName] = { u'title': translate('AlertsPlugin', 'Alerts', 'container title') - } \ No newline at end of file + } + + 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 bb4a9940f..3b11d7c92 100644 --- a/openlp/plugins/alerts/forms/__init__.py +++ b/openlp/plugins/alerts/forms/__init__.py @@ -5,9 +5,10 @@ # 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, # +# 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 # diff --git a/openlp/plugins/alerts/forms/alertdialog.py b/openlp/plugins/alerts/forms/alertdialog.py index da788f2bd..e42680165 100644 --- a/openlp/plugins/alerts/forms/alertdialog.py +++ b/openlp/plugins/alerts/forms/alertdialog.py @@ -5,9 +5,10 @@ # 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, # +# 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 # diff --git a/openlp/plugins/alerts/forms/alertform.py b/openlp/plugins/alerts/forms/alertform.py index 6f6311392..45d283f28 100644 --- a/openlp/plugins/alerts/forms/alertform.py +++ b/openlp/plugins/alerts/forms/alertform.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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) @@ -175,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: @@ -186,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 39cbbfe59..780f295a8 100644 --- a/openlp/plugins/alerts/lib/__init__.py +++ b/openlp/plugins/alerts/lib/__init__.py @@ -5,9 +5,10 @@ # 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, # +# 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 # diff --git a/openlp/plugins/alerts/lib/alertsmanager.py b/openlp/plugins/alerts/lib/alertsmanager.py index d12fb41ec..e73273fe7 100644 --- a/openlp/plugins/alerts/lib/alertsmanager.py +++ b/openlp/plugins/alerts/lib/alertsmanager.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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 2ec2be24b..bd879fef3 100644 --- a/openlp/plugins/alerts/lib/alertstab.py +++ b/openlp/plugins/alerts/lib/alertstab.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -26,7 +27,8 @@ 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): @@ -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().Seconds) - 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.bg_color, self.font_color)) \ No newline at end of file + 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 72c671620..b5c4f8d60 100644 --- a/openlp/plugins/alerts/lib/db.py +++ b/openlp/plugins/alerts/lib/db.py @@ -5,9 +5,10 @@ # 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, # +# 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 # diff --git a/openlp/plugins/bibles/__init__.py b/openlp/plugins/bibles/__init__.py index 5a2035e13..d468ae0dc 100644 --- a/openlp/plugins/bibles/__init__.py +++ b/openlp/plugins/bibles/__init__.py @@ -5,9 +5,10 @@ # 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, # +# 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 # diff --git a/openlp/plugins/bibles/bibleplugin.py b/openlp/plugins/bibles/bibleplugin.py index 5a631bf00..619581b17 100644 --- a/openlp/plugins/bibles/bibleplugin.py +++ b/openlp/plugins/bibles/bibleplugin.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -32,6 +33,7 @@ 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__) @@ -39,7 +41,7 @@ class BiblePlugin(Plugin): log.info(u'Bible Plugin loaded') def __init__(self, plugin_helpers): - Plugin.__init__(self, u'Bibles', plugin_helpers, + Plugin.__init__(self, u'bibles', plugin_helpers, BibleMediaItem, BiblesTab) self.weight = -9 self.icon_path = u':/plugins/plugin_bibles.png' @@ -58,6 +60,8 @@ class BiblePlugin(Plugin): #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): """ @@ -72,6 +76,19 @@ class BiblePlugin(Plugin): #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 = base_action(import_menu, u'importBibleItem') self.importBibleItem.setText(translate('BiblesPlugin', '&Bible')) @@ -87,13 +104,46 @@ class BiblePlugin(Plugin): 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 @@ -137,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) \ No newline at end of file + self.setPluginUiTextStrings(tooltips) diff --git a/openlp/plugins/bibles/forms/__init__.py b/openlp/plugins/bibles/forms/__init__.py index e6897e53f..5b19d9f24 100644 --- a/openlp/plugins/bibles/forms/__init__.py +++ b/openlp/plugins/bibles/forms/__init__.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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 439724b66..7577e86a3 100644 --- a/openlp/plugins/bibles/forms/bibleimportform.py +++ b/openlp/plugins/bibles/forms/bibleimportform.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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__) @@ -123,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) @@ -186,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() @@ -382,8 +369,6 @@ class BibleImportForm(OpenLPWizard): 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( @@ -434,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(), @@ -456,14 +440,6 @@ class BibleImportForm(OpenLPWizard): 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, translate('BiblesPlugin.ImportWizardForm', @@ -485,6 +461,11 @@ class BibleImportForm(OpenLPWizard): 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, @@ -496,6 +477,7 @@ 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, translate('BiblesPlugin.ImportWizardForm', @@ -517,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 @@ -531,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): @@ -541,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. @@ -587,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) @@ -617,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('')) @@ -644,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): """ @@ -694,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') @@ -718,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()) ) @@ -750,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) @@ -765,4 +725,4 @@ class BibleImportForm(OpenLPWizard): self.progressLabel.setText(translate( 'BiblesPlugin.ImportWizardForm', 'Your Bible import failed.')) del self.manager.db_cache[importer.name] - delete_database(self.plugin.settingsSection, importer.file) \ No newline at end of file + delete_database(self.plugin.settingsSection, importer.file) 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/plugins/bibles/forms/languageform.py b/openlp/plugins/bibles/forms/languageform.py new file mode 100644 index 000000000..c5069815b --- /dev/null +++ b/openlp/plugins/bibles/forms/languageform.py @@ -0,0 +1,71 @@ +# -*- 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 LanguageForm. +""" +import logging + +from PyQt4.QtGui import QDialog + +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): + """ + Class to manage a dialog which ask the user for a language. + """ + log.info(u'LanguageForm loaded') + + def __init__(self, parent=None): + """ + Constructor + """ + QDialog.__init__(self, parent) + self.setupUi(self) + + 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) + + 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 e219fbc00..7e40b6230 100644 --- a/openlp/plugins/bibles/lib/__init__.py +++ b/openlp/plugins/bibles/lib/__init__.py @@ -5,9 +5,10 @@ # 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, # +# 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 # diff --git a/openlp/plugins/bibles/lib/biblestab.py b/openlp/plugins/bibles/lib/biblestab.py index 33c2c1f9f..4a24c146d 100644 --- a/openlp/plugins/bibles/lib/biblestab.py +++ b/openlp/plugins/bibles/lib/biblestab.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -207,4 +208,4 @@ class BiblesTab(SettingsTab): self.bibleThemeComboBox.addItem(u'') for theme in theme_list: self.bibleThemeComboBox.addItem(theme) - find_and_set_in_combo_box(self.bibleThemeComboBox, self.bible_theme) \ No newline at end of file + 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 9ff7394d1..6735a7344 100644 --- a/openlp/plugins/bibles/lib/csvbible.py +++ b/openlp/plugins/bibles/lib/csvbible.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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 ec63dc02f..243e7f967 100644 --- a/openlp/plugins/bibles/lib/db.py +++ b/openlp/plugins/bibles/lib/db.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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) @@ -180,19 +171,6 @@ class BibleDB(QtCore.QObject, Manager): 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 @@ -205,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. @@ -249,7 +231,7 @@ 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( @@ -299,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): @@ -309,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. @@ -332,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)\ @@ -359,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): @@ -397,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. @@ -418,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: @@ -428,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. @@ -439,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,)) + try: + 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] + } + except (IndexError, TypeError): + 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)) + try: + 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] + } + except (IndexError, TypeError): + 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 6a6ef131a..228d4758c 100644 --- a/openlp/plugins/bibles/lib/http.py +++ b/openlp/plugins/bibles/lib/http.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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,22 +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: + 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 @@ -242,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 @@ -263,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) @@ -286,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: @@ -296,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') @@ -308,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) @@ -323,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 @@ -331,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 = {} @@ -377,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__) @@ -399,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: @@ -406,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: @@ -423,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 @@ -437,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(): @@ -480,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) @@ -500,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. @@ -519,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): @@ -574,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 67469e063..934aa2d90 100644 --- a/openlp/plugins/bibles/lib/manager.py +++ b/openlp/plugins/bibles/lib/manager.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -30,6 +31,7 @@ import os from PyQt4 import QtCore from openlp.core.lib import Receiver, SettingsManager, translate +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 @@ -139,15 +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. @@ -210,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() ] @@ -218,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): @@ -229,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. @@ -252,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): @@ -285,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. @@ -294,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 e0e06b02f..31effe189 100644 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -25,6 +26,7 @@ ############################################################################### import logging +import locale from PyQt4 import QtCore, QtGui @@ -32,7 +34,8 @@ 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, find_and_set_in_combo_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, \ VerseReferenceList, get_reference_match @@ -55,15 +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 @@ -72,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.quickLayout.addRow(self.quickSearchLabel, self.quickSearchEdit) - self.quickLayoutLabel = QtGui.QLabel(self.quickTab) - self.quickLayoutLabel.setObjectName(u'quickClearLabel') - self.quickLayoutComboBox = media_item_combo_box(self.quickTab, - u'quickLayoutComboBox') - self.quickLayoutComboBox.addItems([u'', u'', u'']) - self.quickLayout.addRow(self.quickLayoutLabel, self.quickLayoutComboBox) - 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, @@ -212,8 +261,11 @@ class BibleMediaItem(MediaManagerItem): QtCore.QObject.connect(self.quickVersionComboBox, QtCore.SIGNAL(u'activated(int)'), self.updateAutoCompleter) QtCore.QObject.connect( - self.quickLayoutComboBox, QtCore.SIGNAL(u'activated(int)'), - self.onlayoutStyleComboBoxChanged) + 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) @@ -224,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') @@ -238,21 +299,26 @@ class BibleMediaItem(MediaManagerItem): self.advancedSecondComboBox.setVisible(False) self.quickSecondLabel.setVisible(False) self.quickSecondComboBox.setVisible(False) - self.quickLayoutComboBox.setCurrentIndex(self.settings.layout_style) + self.quickStyleComboBox.setCurrentIndex(self.settings.layout_style) + self.advancedStyleComboBox.setCurrentIndex(self.settings.layout_style) def retranslateUi(self): log.debug(u'retranslateUi') + self.quickSearchLabel.setText( + translate('BiblesPlugin.MediaItem', 'Find:')) self.quickVersionLabel.setText(u'%s:' % UiStrings().Version) self.quickSecondLabel.setText( translate('BiblesPlugin.MediaItem', 'Second:')) - self.quickSearchLabel.setText( - translate('BiblesPlugin.MediaItem', 'Find:')) + 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.quickClearLabel.setText( - translate('BiblesPlugin.MediaItem', 'Results:')) - self.advancedVersionLabel.setText(u'%s:' % UiStrings().Version) - self.advancedSecondLabel.setText( - translate('BiblesPlugin.MediaItem', 'Second:')) self.advancedBookLabel.setText( translate('BiblesPlugin.MediaItem', 'Book:')) self.advancedChapterLabel.setText( @@ -263,28 +329,23 @@ 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.quickLayoutLabel.setText(UiStrings().LayoutStyle) - self.quickLayoutComboBox.setItemText(LayoutStyle.VersePerSlide, + 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.quickLayoutComboBox.setItemText(LayoutStyle.VersePerLine, + self.advancedStyleComboBox.setItemText(LayoutStyle.VersePerLine, UiStrings().VersePerLine) - self.quickLayoutComboBox.setItemText(LayoutStyle.Continuous, + 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() bible = QtCore.QSettings().value( self.settingsSection + u'/quick bible', QtCore.QVariant( @@ -302,14 +363,6 @@ class BibleMediaItem(MediaManagerItem): 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() @@ -319,8 +372,8 @@ 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. for bible in bibles: if bible: @@ -338,10 +391,14 @@ class BibleMediaItem(MediaManagerItem): 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 initialiseAdvancedBible(self, bible): """ @@ -354,7 +411,17 @@ class BibleMediaItem(MediaManagerItem): The bible to initialise (unicode). """ log.debug(u'initialiseAdvancedBible %s', bible) - book_data = self.parent.manager.get_books(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: @@ -370,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) @@ -399,20 +466,82 @@ class BibleMediaItem(MediaManagerItem): 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() + 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() + 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): 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): item = int(self.advancedBookComboBox.currentIndex()) self.initialiseChapterVerse( @@ -427,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) @@ -439,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) @@ -451,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: @@ -487,7 +616,7 @@ class BibleMediaItem(MediaManagerItem): if restore: old_text = unicode(combo.currentText()) combo.clear() - combo.addItems([unicode(i) for i in range(range_from, range_to + 1)]) + combo.addItems(map(unicode, range(range_from, range_to + 1))) if restore and combo.findText(old_text) != -1: combo.setCurrentIndex(combo.findText(old_text)) @@ -511,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): @@ -538,72 +668,92 @@ 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. + """ + 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 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.parent.manager.get_meta_data(bible, u'Version').value - copyright = self.parent.manager.get_meta_data(bible, u'Copyright').value + version = self.plugin.manager.get_meta_data(bible, u'Version').value + copyright = self.plugin.manager.get_meta_data(bible, u'Copyright').value permissions = \ - self.parent.manager.get_meta_data(bible, u'Permissions').value + 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.parent.manager.get_meta_data( + second_version = self.plugin.manager.get_meta_data( second_bible, u'Version').value - second_copyright = self.parent.manager.get_meta_data( + second_copyright = self.plugin.manager.get_meta_data( second_bible, u'Copyright').value - second_permissions = self.parent.manager.get_meta_data( + second_permissions = self.plugin.manager.get_meta_data( second_bible, u'Permissions').value - for count, verse in enumerate(self.search_results): + items = [] + for count, verse in enumerate(search_results): data = { 'book': QtCore.QVariant(verse.book.name), 'chapter': QtCore.QVariant(verse.chapter), @@ -627,7 +777,7 @@ class BibleMediaItem(MediaManagerItem): 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, + bible_text = u'%s %d%s%d (%s, %s)' % (verse.book.name, verse.chapter, verse_separator, verse.verse, version, second_version) else: @@ -635,27 +785,20 @@ class BibleMediaItem(MediaManagerItem): verse.chapter, verse_separator, verse.verse, version) bible_verse = QtGui.QListWidgetItem(bible_text) bible_verse.setData(QtCore.Qt.UserRole, QtCore.QVariant(data)) - self.listView.addItem(bible_verse) - self.listView.selectAll() - self.search_results = {} - self.second_search_results = {} + items.append(bible_verse) + return items - 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 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'' @@ -664,8 +807,7 @@ class BibleMediaItem(MediaManagerItem): raw_slides = [] raw_title = [] verses = VerseReferenceList() - for item in items: - bitem = self.listView.item(item.row()) + for bitem in items: book = self._decodeQtObject(bitem, 'book') chapter = int(self._decodeQtObject(bitem, 'chapter')) verse = int(self._decodeQtObject(bitem, 'verse')) @@ -694,16 +836,17 @@ 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 # Add footer service_item.raw_footer.append(verses.format_verses()) @@ -711,7 +854,7 @@ class BibleMediaItem(MediaManagerItem): verses.add_version(second_version, second_copyright, second_permissions) service_item.raw_footer.append(verses.format_versions()) - raw_title.append(self.formatTitle(start_item, item)) + 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()) @@ -721,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 @@ -730,12 +874,13 @@ class BibleMediaItem(MediaManagerItem): service_item.theme = None else: service_item.theme = self.settings.bible_theme - [service_item.add_from_text(slide[:30], slide) for slide in raw_slides] + for slide in raw_slides: + 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. @@ -747,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') @@ -771,9 +914,9 @@ class BibleMediaItem(MediaManagerItem): range_separator + old_chapter + verse_separator + old_verse 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. @@ -784,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')) @@ -804,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 @@ -843,10 +984,21 @@ class BibleMediaItem(MediaManagerItem): return u'{su}[%s]{/su}' % verse_text return u'{su}%s{/su}' % verse_text - def onlayoutStyleComboBoxChanged(self): - self.settings.layout_style = self.quickLayoutComboBox.currentIndex() - self.settings.layoutStyleComboBox.setCurrentIndex( - self.settings.layout_style) - QtCore.QSettings().setValue( - self.settingsSection + u'/verse layout style', - QtCore.QVariant(self.settings.layout_style)) \ No newline at end of file + 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 e43417c02..b822b9b9d 100644 --- a/openlp/plugins/bibles/lib/openlp1.py +++ b/openlp/plugins/bibles/lib/openlp1.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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 585ecf9c7..16820229c 100644 --- a/openlp/plugins/bibles/lib/opensong.py +++ b/openlp/plugins/bibles/lib/opensong.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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 a080524eb..b802cda85 100644 --- a/openlp/plugins/bibles/lib/osis.py +++ b/openlp/plugins/bibles/lib/osis.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -33,7 +34,7 @@ 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__) @@ -85,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. """ @@ -95,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)...')) @@ -108,6 +108,11 @@ 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 @@ -122,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) diff --git a/openlp/plugins/bibles/lib/versereferencelist.py b/openlp/plugins/bibles/lib/versereferencelist.py index bab6d7e11..471fc6a66 100644 --- a/openlp/plugins/bibles/lib/versereferencelist.py +++ b/openlp/plugins/bibles/lib/versereferencelist.py @@ -5,9 +5,10 @@ # 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, # +# 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 # 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 5171155d2..3446f597e 100644 --- a/openlp/plugins/custom/__init__.py +++ b/openlp/plugins/custom/__init__.py @@ -5,9 +5,10 @@ # 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, # +# 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 # diff --git a/openlp/plugins/custom/customplugin.py b/openlp/plugins/custom/customplugin.py index 0e88202ca..e9260f926 100644 --- a/openlp/plugins/custom/customplugin.py +++ b/openlp/plugins/custom/customplugin.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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', 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 fb3cf975b..e901095c2 100644 --- a/openlp/plugins/custom/forms/__init__.py +++ b/openlp/plugins/custom/forms/__init__.py @@ -5,9 +5,10 @@ # 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, # +# 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 # diff --git a/openlp/plugins/custom/forms/editcustomdialog.py b/openlp/plugins/custom/forms/editcustomdialog.py index 7a6c1f07b..3eee1cfd4 100644 --- a/openlp/plugins/custom/forms/editcustomdialog.py +++ b/openlp/plugins/custom/forms/editcustomdialog.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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) \ No newline at end of file + self.previewButton.setText(UiStrings().SaveAndPreview) diff --git a/openlp/plugins/custom/forms/editcustomform.py b/openlp/plugins/custom/forms/editcustomform.py index 9312b5ddd..0eadf6021 100644 --- a/openlp/plugins/custom/forms/editcustomform.py +++ b/openlp/plugins/custom/forms/editcustomform.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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() @@ -99,10 +103,9 @@ class EditCustomForm(QtGui.QDialog, Ui_CustomEditDialog): self.slideListView.addItem(slide[1]) theme = self.customSlide.theme_name 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') @@ -111,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): @@ -132,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() @@ -169,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) @@ -261,4 +265,4 @@ class EditCustomForm(QtGui.QDialog, Ui_CustomEditDialog): message=translate('CustomPlugin.EditCustomForm', 'You need to add at least one slide')) return False - return True \ No newline at end of file + return True diff --git a/openlp/plugins/custom/forms/editcustomslidedialog.py b/openlp/plugins/custom/forms/editcustomslidedialog.py index d6ebf23ec..759f6b19d 100644 --- a/openlp/plugins/custom/forms/editcustomslidedialog.py +++ b/openlp/plugins/custom/forms/editcustomslidedialog.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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 7d4e32968..71696ebc1 100644 --- a/openlp/plugins/custom/forms/editcustomslideform.py +++ b/openlp/plugins/custom/forms/editcustomslideform.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -44,6 +45,8 @@ 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'clicked()'), self.onSplitButtonPressed) @@ -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 25abdd38b..0e343b6cc 100644 --- a/openlp/plugins/custom/lib/__init__.py +++ b/openlp/plugins/custom/lib/__init__.py @@ -5,9 +5,10 @@ # 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, # +# 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 # diff --git a/openlp/plugins/custom/lib/customtab.py b/openlp/plugins/custom/lib/customtab.py index 9de294418..8a7762f37 100644 --- a/openlp/plugins/custom/lib/customtab.py +++ b/openlp/plugins/custom/lib/customtab.py @@ -5,9 +5,10 @@ # 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, # +# 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 # diff --git a/openlp/plugins/custom/lib/customxmlhandler.py b/openlp/plugins/custom/lib/customxmlhandler.py index 2babe3d12..ff9fab7a7 100644 --- a/openlp/plugins/custom/lib/customxmlhandler.py +++ b/openlp/plugins/custom/lib/customxmlhandler.py @@ -5,9 +5,10 @@ # 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, # +# 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 # diff --git a/openlp/plugins/custom/lib/db.py b/openlp/plugins/custom/lib/db.py index 35c24413a..0cefaf012 100644 --- a/openlp/plugins/custom/lib/db.py +++ b/openlp/plugins/custom/lib/db.py @@ -5,9 +5,10 @@ # 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, # +# 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 # diff --git a/openlp/plugins/custom/lib/mediaitem.py b/openlp/plugins/custom/lib/mediaitem.py index b508104c4..59d6b4fb6 100644 --- a/openlp/plugins/custom/lib/mediaitem.py +++ b/openlp/plugins/custom/lib/mediaitem.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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,27 +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. @@ -74,37 +156,32 @@ 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): """ @@ -113,34 +190,49 @@ class CustomMediaItem(MediaManagerItem): 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): + 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 @@ -149,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,4 +251,56 @@ class CustomMediaItem(MediaManagerItem): else: raw_footer.append(u'') service_item.raw_footer = raw_footer - return True \ No newline at end of file + 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 a375f863f..b95ac000d 100644 --- a/openlp/plugins/images/__init__.py +++ b/openlp/plugins/images/__init__.py @@ -5,9 +5,10 @@ # 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, # +# 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 # diff --git a/openlp/plugins/images/imageplugin.py b/openlp/plugins/images/imageplugin.py index 5d009ad65..4b5a6f3c0 100644 --- a/openlp/plugins/images/imageplugin.py +++ b/openlp/plugins/images/imageplugin.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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,10 +39,13 @@ class ImagePlugin(Plugin): log.info(u'Image Plugin loaded') def __init__(self, plugin_helpers): - Plugin.__init__(self, u'Images', 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' @@ -69,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 6a9e364f9..e216623cd 100644 --- a/openlp/plugins/images/lib/__init__.py +++ b/openlp/plugins/images/lib/__init__.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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 1b5a55c61..049e2da18 100644 --- a/openlp/plugins/images/lib/mediaitem.py +++ b/openlp/plugins/images/lib/mediaitem.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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,10 +47,13 @@ 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', @@ -77,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) @@ -94,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()] @@ -105,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()) - filepath = unicode(bitem.data(QtCore.Qt.UserRole).toString()) - filename = os.path.split(filepath)[1] - service_item.add_from_image(filepath, filename) - 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): """ @@ -190,16 +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()) - filepath = unicode(bitem.data(QtCore.Qt.UserRole).toString()) - if os.path.exists(filepath): - filename = os.path.split(filepath)[1] - self.parent.liveController.display.directImage(filename, - filepath) - self.resetAction.setVisible(True) + filename = unicode(bitem.data(QtCore.Qt.UserRole).toString()) + if os.path.exists(filename): + (path, name) = os.path.split(filename) + 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, unicode(translate('ImagePlugin.MediaItem', 'There was a problem replacing your background, ' - 'the image file "%s" no longer exists.')) % filepath) + '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 9271a0936..32cff0c44 100644 --- a/openlp/plugins/media/__init__.py +++ b/openlp/plugins/media/__init__.py @@ -5,9 +5,10 @@ # 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, # +# 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 # diff --git a/openlp/plugins/media/lib/__init__.py b/openlp/plugins/media/lib/__init__.py index 7f63c8108..25b8d531a 100644 --- a/openlp/plugins/media/lib/__init__.py +++ b/openlp/plugins/media/lib/__init__.py @@ -5,9 +5,10 @@ # 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, # +# 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 # diff --git a/openlp/plugins/media/lib/mediaitem.py b/openlp/plugins/media/lib/mediaitem.py index 36ecfe162..c8f746851 100644 --- a/openlp/plugins/media/lib/mediaitem.py +++ b/openlp/plugins/media/lib/mediaitem.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -27,16 +28,19 @@ 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. @@ -44,23 +48,28 @@ 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(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) + 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) @@ -86,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) @@ -107,37 +116,40 @@ class MediaMediaItem(MediaManagerItem): item = self.listView.currentItem() filename = unicode(item.data(QtCore.Qt.UserRole).toString()) if os.path.exists(filename): - self.parent.liveController.display.video(filename, 0, True) - self.resetAction.setVisible(True) + (path, name) = os.path.split(filename) + 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, 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 not os.path.exists(filename): - # 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 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): - # Due to string freeze, borrow a message from presentations - # This will be corrected in 1.9.6 - critical_error_message_box( - translate('PresentationPlugin.MediaItem', 'Unsupported File'), - unicode(translate('PresentationPlugin.MediaItem', - 'Unsupported File'))) + critical_error_message_box(UiStrings().UnsupportedFile, + UiStrings().UnsupportedFile) return False # File too big for processing if os.path.getsize(filename) <= 52428800: # 50MiB @@ -145,20 +157,17 @@ class MediaMediaItem(MediaManagerItem): if not self.mediaStateWait(Phonon.PlayingState) \ or self.mediaObject.currentSource().type() \ == Phonon.MediaSource.Invalid: - # Due to string freeze, borrow a message from presentations - # This will be corrected in 1.9.6 self.mediaObject.stop() critical_error_message_box( - translate('PresentationPlugin.MediaItem', - 'Unsupported File'), - unicode(translate('PresentationPlugin.MediaItem', - 'Unsupported File'))) + 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.mediaLength = self.mediaObject.totalTime() / 1000 self.mediaObject.stop() - service_item.media_length = self.mediaLength + service_item.media_length = self.mediaObject.totalTime() / 1000 service_item.add_capability( - ItemCapabilities.AllowsVariableStartTime) + ItemCapabilities.HasVariableStartTime) service_item.title = unicode(self.plugin.nameStrings[u'singular']) service_item.add_capability(ItemCapabilities.RequiresMedia) # force a non-existent theme @@ -170,8 +179,7 @@ class MediaMediaItem(MediaManagerItem): def mediaStateWait(self, mediaState): """ - Wait for the video to change its state - Wait no longer than 5 seconds. + Wait for the video to change its state. Wait no longer than 5 seconds. """ start = datetime.now() while self.mediaObject.state() != mediaState: @@ -185,12 +193,11 @@ class MediaMediaItem(MediaManagerItem): 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.')): @@ -199,13 +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)) - self.listView.addItem(item_name) \ No newline at end of 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 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 f54aa02fa..058ea3149 100644 --- a/openlp/plugins/media/lib/mediatab.py +++ b/openlp/plugins/media/lib/mediatab.py @@ -5,9 +5,10 @@ # 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, # +# 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 # diff --git a/openlp/plugins/media/mediaplugin.py b/openlp/plugins/media/mediaplugin.py index e01e6846d..c215b1be0 100644 --- a/openlp/plugins/media/mediaplugin.py +++ b/openlp/plugins/media/mediaplugin.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -38,7 +39,7 @@ class MediaPlugin(Plugin): log.info(u'%s MediaPlugin loaded', __name__) def __init__(self, plugin_helpers): - Plugin.__init__(self, u'Media', plugin_helpers, + Plugin.__init__(self, u'media', plugin_helpers, MediaMediaItem, MediaTab) self.weight = -6 self.icon_path = u':/plugins/plugin_media.png' @@ -111,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 bf077953a..d7cb7afe5 100644 --- a/openlp/plugins/presentations/__init__.py +++ b/openlp/plugins/presentations/__init__.py @@ -5,9 +5,10 @@ # 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, # +# 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 # diff --git a/openlp/plugins/presentations/lib/__init__.py b/openlp/plugins/presentations/lib/__init__.py index b41c2f2f6..3c4e0499d 100644 --- a/openlp/plugins/presentations/lib/__init__.py +++ b/openlp/plugins/presentations/lib/__init__.py @@ -5,9 +5,10 @@ # 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, # +# 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 # diff --git a/openlp/plugins/presentations/lib/impresscontroller.py b/openlp/plugins/presentations/lib/impresscontroller.py index 8166a2258..36f684ad4 100644 --- a/openlp/plugins/presentations/lib/impresscontroller.py +++ b/openlp/plugins/presentations/lib/impresscontroller.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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 74ff3fea8..e1dd57271 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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,24 +194,29 @@ 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( - 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): """ @@ -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: @@ -296,4 +319,15 @@ class PresentationMediaItem(MediaManagerItem): if self.controllers[controller].enabled(): if filetype in self.controllers[controller].alsosupports: return controller - return None \ No newline at end of file + 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 94cd2bfa4..0f38cf02f 100644 --- a/openlp/plugins/presentations/lib/messagelistener.py +++ b/openlp/plugins/presentations/lib/messagelistener.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -67,7 +68,7 @@ class Controller(object): self.doc.slidenumber = slide_no if self.is_live: if hide_mode == HideMode.Screen: - Receiver.send_message(u'maindisplay_hide', HideMode.Screen) + Receiver.send_message(u'live_display_hide', HideMode.Screen) self.stop() elif hide_mode == HideMode.Theme: self.blank(hide_mode) @@ -75,7 +76,7 @@ class Controller(object): self.blank(hide_mode) else: self.doc.start_presentation() - Receiver.send_message(u'maindisplay_hide', HideMode.Screen) + Receiver.send_message(u'live_display_hide', HideMode.Screen) self.doc.slidenumber = 0 if slide_no > 1: self.slide(slide_no) @@ -94,6 +95,8 @@ class Controller(object): 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): @@ -149,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) @@ -188,7 +196,7 @@ class Controller(object): if not self.doc.is_active(): return if hide_mode == HideMode.Theme: - Receiver.send_message(u'maindisplay_hide', HideMode.Theme) + Receiver.send_message(u'live_display_hide', HideMode.Theme) self.doc.blank_screen() def stop(self): @@ -216,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) diff --git a/openlp/plugins/presentations/lib/powerpointcontroller.py b/openlp/plugins/presentations/lib/powerpointcontroller.py index a02bc841a..8f551e411 100644 --- a/openlp/plugins/presentations/lib/powerpointcontroller.py +++ b/openlp/plugins/presentations/lib/powerpointcontroller.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -251,14 +252,15 @@ class PowerpointDocument(PresentationDocument): win32ui.GetForegroundWindow().GetDC().GetDeviceCaps(88) except win32ui.error: dpi = 96 - rendermanager = self.controller.plugin.renderManager - rect = rendermanager.screens.current[u'size'] + 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. diff --git a/openlp/plugins/presentations/lib/pptviewcontroller.py b/openlp/plugins/presentations/lib/pptviewcontroller.py index 94fd05f31..cd940da5c 100644 --- a/openlp/plugins/presentations/lib/pptviewcontroller.py +++ b/openlp/plugins/presentations/lib/pptviewcontroller.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -121,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()): diff --git a/openlp/plugins/presentations/lib/pptviewlib/ppttest.py b/openlp/plugins/presentations/lib/pptviewlib/ppttest.py index 337bdb09f..50cf515dc 100644 --- a/openlp/plugins/presentations/lib/pptviewlib/ppttest.py +++ b/openlp/plugins/presentations/lib/pptviewlib/ppttest.py @@ -5,9 +5,10 @@ # 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, # +# 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 # diff --git a/openlp/plugins/presentations/lib/presentationcontroller.py b/openlp/plugins/presentations/lib/presentationcontroller.py index 8d48d98a3..a9d384c81 100644 --- a/openlp/plugins/presentations/lib/presentationcontroller.py +++ b/openlp/plugins/presentations/lib/presentationcontroller.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -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): """ @@ -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 bba2b469e..b0c3de7a8 100644 --- a/openlp/plugins/presentations/lib/presentationtab.py +++ b/openlp/plugins/presentations/lib/presentationtab.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -131,4 +132,4 @@ class PresentationTab(SettingsTab): QtCore.QVariant(self.OverrideAppCheckBox.checkState())) changed = True if changed: - Receiver.send_message(u'mediaitem_presentation_rebuild') \ No newline at end of file + Receiver.send_message(u'mediaitem_presentation_rebuild') diff --git a/openlp/plugins/presentations/presentationplugin.py b/openlp/plugins/presentations/presentationplugin.py index f217e6023..643ad14ad 100644 --- a/openlp/plugins/presentations/presentationplugin.py +++ b/openlp/plugins/presentations/presentationplugin.py @@ -5,9 +5,10 @@ # 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, # +# 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 # @@ -51,7 +52,7 @@ class PresentationPlugin(Plugin): """ log.debug(u'Initialised') self.controllers = {} - Plugin.__init__(self, u'Presentations', 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) @@ -76,7 +77,7 @@ class PresentationPlugin(Plugin): 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() @@ -86,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(): @@ -98,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): """ @@ -127,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: @@ -167,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 6dc3d5cce..ab1d8adbc 100644 --- a/openlp/plugins/remotes/__init__.py +++ b/openlp/plugins/remotes/__init__.py @@ -5,9 +5,10 @@ # 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, # +# 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 # diff --git a/openlp/plugins/remotes/html/images/icons-18-black.png b/openlp/plugins/remotes/html/images/icons-18-black.png index 3657baea8..1ecfd26fb 100644 Binary files a/openlp/plugins/remotes/html/images/icons-18-black.png 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 index ccca7b44b..0c70831ac 100644 Binary files a/openlp/plugins/remotes/html/images/icons-18-white.png 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 index 79b6d601b..4c72adf1e 100644 Binary files a/openlp/plugins/remotes/html/images/icons-36-black.png 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 index e1b9c04ea..84ea9fb31 100644 Binary files a/openlp/plugins/remotes/html/images/icons-36-white.png and b/openlp/plugins/remotes/html/images/icons-36-white.png differ diff --git a/openlp/plugins/remotes/html/index.html b/openlp/plugins/remotes/html/index.html index 3708db654..1339e9dda 100644 --- a/openlp/plugins/remotes/html/index.html +++ b/openlp/plugins/remotes/html/index.html @@ -8,7 +8,8 @@ # 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 # +# 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,71 +27,136 @@ --> - OpenLP 2.0 Remote + ${app_title} +
- Back -

Service Manager

+ ${back} +

${service_manager}

+ ${refresh} +
+
+ +
- Back -

Slide Controller

+ ${back} +

${slide_controller}

+ ${refresh} +
+
+ +
- Back -

Alerts

+ ${back} +

${alerts}

- +
- Show Alert + ${show_alert} +
+
+ +
+
+

${options}

+
+
diff --git a/openlp/plugins/remotes/html/jquery.js b/openlp/plugins/remotes/html/jquery.js index 14fd6470f..628ed9b31 100644 --- a/openlp/plugins/remotes/html/jquery.js +++ b/openlp/plugins/remotes/html/jquery.js @@ -1,16 +1,4 @@ -/*! - * jQuery JavaScript Library v1.5.1 - * http://jquery.com/ - * - * 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 2011, The Dojo Foundation - * Released under the MIT, BSD, and GPL Licenses. - * - * Date: Wed Feb 23 13:55:29 2011 -0500 - */ -(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 +/*! jQuery v1.6.4 http://jquery.com/ | http://jquery.org/license */ +(function(a,b){function cu(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cr(a){if(!cg[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ch||(ch=c.createElement("iframe"),ch.frameBorder=ch.width=ch.height=0),b.appendChild(ch);if(!ci||!ch.createElement)ci=(ch.contentWindow||ch.contentDocument).document,ci.write((c.compatMode==="CSS1Compat"?"":"")+""),ci.close();d=ci.createElement(a),ci.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ch)}cg[a]=e}return cg[a]}function cq(a,b){var c={};f.each(cm.concat.apply([],cm.slice(0,b)),function(){c[this]=a});return c}function cp(){cn=b}function co(){setTimeout(cp,0);return cn=f.now()}function cf(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ce(){try{return new a.XMLHttpRequest}catch(b){}}function b$(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){c!=="border"&&f.each(e,function(){c||(d-=parseFloat(f.css(a,"padding"+this))||0),c==="margin"?d+=parseFloat(f.css(a,c+this))||0:d-=parseFloat(f.css(a,"border"+this+"Width"))||0});return d+"px"}d=bv(a,b,b);if(d<0||d==null)d=a.style[b]||0;d=parseFloat(d)||0,c&&f.each(e,function(){d+=parseFloat(f.css(a,"padding"+this))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+this+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+this))||0)});return d+"px"}function bl(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(bd,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bk(a){f.nodeName(a,"input")?bj(a):"getElementsByTagName"in a&&f.grep(a.getElementsByTagName("input"),bj)}function bj(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bi(a){return"getElementsByTagName"in a?a.getElementsByTagName("*"):"querySelectorAll"in a?a.querySelectorAll("*"):[]}function bh(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bg(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c=f.expando,d=f.data(a),e=f.data(b,d);if(d=d[c]){var g=d.events;e=e[c]=f.extend({},d);if(g){delete e.handle,e.events={};for(var h in g)for(var i=0,j=g[h].length;i=0===c})}function U(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function M(a,b){return(a&&a!=="*"?a+".":"")+b.replace(y,"`").replace(z,"&")}function L(a){var b,c,d,e,g,h,i,j,k,l,m,n,o,p=[],q=[],r=f._data(this,"events");if(!(a.liveFired===this||!r||!r.live||a.target.disabled||a.button&&a.type==="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)")),a.liveFired=this;var s=r.live.slice(0);for(i=0;ic)break;a.currentTarget=e.elem,a.data=e.handleObj.data,a.handleObj=e.handleObj,o=e.handleObj.origHandler.apply(e.elem,arguments);if(o===!1||a.isPropagationStopped()){c=e.level,o===!1&&(b=!1);if(a.isImmediatePropagationStopped())break}}return b}}function J(a,c,d){var e=f.extend({},d[0]);e.type=a,e.originalEvent={},e.liveFired=b,f.event.handle.call(c,e),e.isDefaultPrevented()&&d[0].preventDefault()}function D(){return!0}function C(){return!1}function m(a,c,d){var e=c+"defer",g=c+"queue",h=c+"mark",i=f.data(a,e,b,!0);i&&(d==="queue"||!f.data(a,g,b,!0))&&(d==="mark"||!f.data(a,h,b,!0))&&setTimeout(function(){!f.data(a,g,b,!0)&&!f.data(a,h,b,!0)&&(f.removeData(a,e,!0),i.resolve())},0)}function l(a){for(var b in a)if(b!=="toJSON")return!1;return!0}function k(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(j,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNaN(d)?i.test(d)?f.parseJSON(d):d:parseFloat(d)}catch(g){}f.data(a,c,d)}else d=b}return d}var c=a.document,d=a.navigator,e=a.location,f=function(){function K(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(K,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/\d/,n=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,o=/^[\],:{}\s]*$/,p=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,q=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,r=/(?:^|:|,)(?:\s*\[)+/g,s=/(webkit)[ \/]([\w.]+)/,t=/(opera)(?:.*version)?[ \/]([\w.]+)/,u=/(msie) ([\w.]+)/,v=/(mozilla)(?:.*? rv:([\w.]+))?/,w=/-([a-z]|[0-9])/ig,x=/^-ms-/,y=function(a,b){return(b+"").toUpperCase()},z=d.userAgent,A,B,C,D=Object.prototype.toString,E=Object.prototype.hasOwnProperty,F=Array.prototype.push,G=Array.prototype.slice,H=String.prototype.trim,I=Array.prototype.indexOf,J={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=n.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.6.4",length:0,size:function(){return this.length},toArray:function(){return G.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 d=this.constructor();e.isArray(a)?F.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),B.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(G.apply(this,arguments),"slice",G.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:F,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,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"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;B.resolveWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!B){B=e._Deferred();if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",C,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",C),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&K()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNaN:function(a){return a==null||!m.test(a)||isNaN(a)},type:function(a){return a==null?String(a):J[D.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!E.call(a,"constructor")&&!E.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||E.call(a,d)},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=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(o.test(b.replace(p,"@").replace(q,"]").replace(r,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(x,"ms-").replace(w,y)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?h.call(arguments,0):c,--e||g.resolveWith(g,h.call(b,0))}}var b=arguments,c=0,d=b.length,e=d,g=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred();if(d>1){for(;c
a",d=a.getElementsByTagName("*"),e=a.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=a.getElementsByTagName("input")[0],k={leadingWhitespace:a.firstChild.nodeType===3,tbody:!a.getElementsByTagName("tbody").length,htmlSerialize:!!a.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55$/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:a.className!=="t",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,k.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,k.optDisabled=!h.disabled;try{delete a.test}catch(v){k.deleteExpando=!1}!a.addEventListener&&a.attachEvent&&a.fireEvent&&(a.attachEvent("onclick",function(){k.noCloneEvent=!1}),a.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),k.radioValue=i.value==="t",i.setAttribute("checked","checked"),a.appendChild(i),l=c.createDocumentFragment(),l.appendChild(a.firstChild),k.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,a.innerHTML="",a.style.width=a.style.paddingLeft="1px",m=c.getElementsByTagName("body")[0],o=c.createElement(m?"div":"body"),p={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},m&&f.extend(p,{position:"absolute",left:"-1000px",top:"-1000px"});for(t in p)o.style[t]=p[t];o.appendChild(a),n=m||b,n.insertBefore(o,n.firstChild),k.appendChecked=i.checked,k.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,k.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="
",k.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="
t
",q=a.getElementsByTagName("td"),u=q[0].offsetHeight===0,q[0].style.display="",q[1].style.display="none",k.reliableHiddenOffsets=u&&q[0].offsetHeight===0,a.innerHTML="",c.defaultView&&c.defaultView.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",a.appendChild(j),k.reliableMarginRight=(parseInt((c.defaultView.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0),o.innerHTML="",n.removeChild(o);if(a.attachEvent)for(t in{submit:1,change:1,focusin:1})s="on"+t,u=s in a,u||(a.setAttribute(s,"return;"),u=typeof a[s]=="function"),k[t+"Bubbles"]=u;o=l=g=h=m=j=a=i=null;return k}(),f.boxModel=f.support.boxModel;var i=/^(?:\{.*\}|\[.*\])$/,j=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.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?f.cache[a[f.expando]]:a[f.expando];return!!a&&!l(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i=f.expando,j=typeof c=="string",k=a.nodeType,l=k?f.cache:a,m=k?a[f.expando]:a[f.expando]&&f.expando;if((!m||e&&m&&l[m]&&!l[m][i])&&j&&d===b)return;m||(k?a[f.expando]=m=++f.uuid:m=f.expando),l[m]||(l[m]={},k||(l[m].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?l[m][i]=f.extend(l[m][i],c):l[m]=f.extend(l[m],c);g=l[m],e&&(g[i]||(g[i]={}),g=g[i]),d!==b&&(g[f.camelCase(c)]=d);if(c==="events"&&!g[c])return g[i]&&g[i].events;j?(h=g[c],h==null&&(h=g[f.camelCase(c)])):h=g;return h}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e=f.expando,g=a.nodeType,h=g?f.cache:a,i=g?a[f.expando]:f.expando;if(!h[i])return;if(b){d=c?h[i][e]:h[i];if(d){d[b]||(b=f.camelCase(b)),delete d[b];if(!l(d))return}}if(c){delete h[i][e];if(!l(h[i]))return}var j=h[i][e];f.support.deleteExpando||!h.setInterval?delete h[i]:h[i]=null,j?(h[i]={},g||(h[i].toJSON=f.noop),h[i][e]=j):g&&(f.support.deleteExpando?delete a[f.expando]:a.removeAttribute?a.removeAttribute(f.expando):a[f.expando]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d=null;if(typeof a=="undefined"){if(this.length){d=f.data(this[0]);if(this[0].nodeType===1){var e=this[0].attributes,g;for(var h=0,i=e.length;h-1)return!0;return!1},val:function(a){var c,d,e=this[0];if(!arguments.length){if(e){c=f.valHooks[e.nodeName.toLowerCase()]||f.valHooks[e.type];if(c&&"get"in c&&(d=c.get(e,"value"))!==b)return d;d=e.value;return typeof d=="string"?d.replace(p,""):d==null?"":d}return b}var g=f.isFunction(a);return this.each(function(d){var e=f(this),h;if(this.nodeType===1){g?h=a.call(this,d,e.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c=a.selectedIndex,d=[],e=a.options,g=a.type==="select-one";if(c<0)return null;for(var h=g?c:0,i=g?c+1:e.length;h=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attrFix:{tabindex:"tabIndex"},attr:function(a,c,d,e){var g=a.nodeType;if(!a||g===3||g===8||g===2)return b;if(e&&c in f.attrFn)return f(a)[c](d);if(!("getAttribute"in a))return f.prop(a,c,d);var h,i,j=g!==1||!f.isXMLDoc(a);j&&(c=f.attrFix[c]||c,i=f.attrHooks[c],i||(t.test(c)?i=v:u&&(i=u)));if(d!==b){if(d===null){f.removeAttr(a,c);return b}if(i&&"set"in i&&j&&(h=i.set(a,d,c))!==b)return h;a.setAttribute(c,""+d);return d}if(i&&"get"in i&&j&&(h=i.get(a,c))!==null)return h;h=a.getAttribute(c);return h===null?b:h},removeAttr:function(a,b){var c;a.nodeType===1&&(b=f.attrFix[b]||b,f.attr(a,b,""),a.removeAttribute(b),t.test(b)&&(c=f.propFix[b]||b)in a&&(a[c]=!1))},attrHooks:{type:{set:function(a,b){if(q.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},value:{get:function(a,b){if(u&&f.nodeName(a,"button"))return u.get(a,b);return b in a?a.value:null},set:function(a,b,c){if(u&&f.nodeName(a,"button"))return u.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e=a.nodeType;if(!a||e===3||e===8||e===2)return b;var g,h,i=e!==1||!f.isXMLDoc(a);i&&(c=f.propFix[c]||c,h=f.propHooks[c]);return d!==b?h&&"set"in h&&(g=h.set(a,d,c))!==b?g:a[c]=d:h&&"get"in h&&(g=h.get(a,c))!==null?g:a[c]},propHooks:{tabIndex:{get:function(a){var c=a.getAttributeNode("tabindex");return c&&c.specified?parseInt(c.value,10):r.test(a.nodeName)||s.test(a.nodeName)&&a.href?0:b}}}}),f.attrHooks.tabIndex=f.propHooks.tabIndex,v={get:function(a,c){var d;return f.prop(a,c)===!0||(d=a.getAttributeNode(c))&&d.nodeValue!==!1?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase()));return c}},f.support.getSetAttribute||(u=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&d.nodeValue!==""?d.nodeValue:b},set:function(a,b,d){var e=a.getAttributeNode(d);e||(e=c.createAttribute(d),a.setAttributeNode(e));return e.nodeValue=b+""}},f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})})),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex);return null}})),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var w=/\.(.*)$/,x=/^(?:textarea|input|select)$/i,y=/\./g,z=/ /g,A=/[^\w\s.|`]/g,B=function(a){return a.replace(A,"\\$&")};f.event={add:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){if(d===!1)d=C;else if(!d)return;var g,h;d.handler&&(g=d,d=g.handler),d.guid||(d.guid=f.guid++);var i=f._data(a);if(!i)return;var j=i.events,k=i.handle;j||(i.events=j={}),k||(i.handle=k=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.handle.apply(k.elem,arguments):b}),k.elem=a,c=c.split(" ");var l,m=0,n;while(l=c[m++]){h=g?f.extend({},g):{handler:d,data:e},l.indexOf(".")>-1?(n=l.split("."),l=n.shift(),h.namespace=n.slice(0).sort().join(".")):(n=[],h.namespace=""),h.type=l,h.guid||(h.guid=d.guid);var o=j[l],p=f.event.special[l]||{};if(!o){o=j[l]=[];if(!p.setup||p.setup.call(a,e,n,k)===!1)a.addEventListener?a.addEventListener(l,k,!1):a.attachEvent&&a.attachEvent("on"+l,k)}p.add&&(p.add.call(a,h),h.handler.guid||(h.handler.guid=d.guid)),o.push(h),f.event.global[l]=!0}a=null}},global:{},remove:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){d===!1&&(d=C);var g,h,i,j,k=0,l,m,n,o,p,q,r,s=f.hasData(a)&&f._data(a),t=s&&s.events;if(!s||!t)return;c&&c.type&&(d=c.handler,c=c.type);if(!c||typeof c=="string"&&c.charAt(0)==="."){c=c||"";for(h in t)f.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("(^|\\.)"+f.map(m.slice(0).sort(),B).join("\\.(?:.*\\.)?")+"(\\.|$)")),p=t[h];if(!p)continue;if(!d){for(j=0;j=0&&(h=h.slice(0,-1),j=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if(!!e&&!f.event.customEvent[h]||!!f.event.global[h]){c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.exclusive=j,c.namespace=i.join("."),c.namespace_re=new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)");if(g||!e)c.preventDefault(),c.stopPropagation();if(!e){f.each(f.cache,function(){var a=f.expando,b=this[a];b&&b.events&&b.events[h]&&f.event.trigger(c,d,b.handle.elem)});return}if(e.nodeType===3||e.nodeType===8)return;c.result=b,c.target=e,d=d!=null?f.makeArray(d):[],d.unshift(c);var k=e,l=h.indexOf(":")<0?"on"+h:"";do{var m=f._data(k,"handle");c.currentTarget=k,m&&m.apply(k,d),l&&f.acceptData(k)&&k[l]&&k[l].apply(k,d)===!1&&(c.result=!1,c.preventDefault()),k=k.parentNode||k.ownerDocument||k===c.target.ownerDocument&&a}while(k&&!c.isPropagationStopped());if(!c.isDefaultPrevented()){var n,o=f.event.special[h]||{};if((!o._default||o._default.call(e.ownerDocument,c)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)){try{l&&e[h]&&(n=e[l],n&&(e[l]=null),f.event.triggered=h,e[h]())}catch(p){}n&&(e[l]=n),f.event.triggered=b}}return c.result}},handle:function(c){c=f.event.fix(c||a.event);var d=((f._data(this,"events")||{})[c.type]||[]).slice(0),e=!c.exclusive&&!c.namespace,g=Array.prototype.slice.call(arguments,0);g[0]=c,c.currentTarget=this;for(var h=0,i=d.length;h-1?f.map(a.options,function(a){return a.selected}).join("-"):"":f.nodeName(a,"select")&&(c=a.selectedIndex);return c},I=function(c){var d=c.target,e,g;if(!!x.test(d.nodeName)&&!d.readOnly){e=f._data(d,"_change_data"),g=H(d),(c.type!=="focusout"||d.type!=="radio")&&f._data(d,"_change_data",g);if(e===b||g===e)return;if(e!=null||g)c.type="change",c.liveFired=b,f.event.trigger(c,arguments[1],d)}};f.event.special.change={filters:{focusout:I,beforedeactivate:I,click:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(c==="radio"||c==="checkbox"||f.nodeName(b,"select"))&&I.call(this,a)},keydown:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(a.keyCode===13&&!f.nodeName(b,"textarea")||a.keyCode===32&&(c==="checkbox"||c==="radio")||c==="select-multiple")&&I.call(this,a)},beforeactivate:function(a){var b=a.target;f._data(b,"_change_data",H(b))}},setup:function(a,b){if(this.type==="file")return!1;for(var c in G)f.event.add(this,c+".specialChange",G[c]);return x.test(this.nodeName)},teardown:function(a){f.event.remove(this,".specialChange");return x.test(this.nodeName)}},G=f.event.special.change.filters,G.focus=G.beforeactivate}f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){function e(a){var c=f.event.fix(a);c.type=b,c.originalEvent={},f.event.trigger(c,null,c.target),c.isDefaultPrevented()&&a.preventDefault()}var d=0;f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.each(["bind","one"],function(a,c){f.fn[c]=function(a,d,e){var g;if(typeof a=="object"){for(var h in a)this[c](h,d,a[h],e);return this}if(arguments.length===2||d===!1)e=d,d=b;c==="one"?(g=function(a){f(this).unbind(a,g);return e.apply(this,arguments)},g.guid=e.guid||f.guid++):g=e;if(a==="unload"&&c!=="one")this.one(a,d,e);else for(var i=0,j=this.length;i0?this.bind(b,a,c):this.trigger(b)},f.attrFn&&(f.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,d=0,e=Object.prototype.toString,g=!1,h=!0,i=/\\/g,j=/\W/;[0,0].sort(function(){h=!1;return 0});var k=function(b,d,f,g){f=f||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return f;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(e.call(n)==="[object Array]")if(!u)f.push.apply(f,n);else 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]))&&f.push(j[t]);else for(t=0;n[t]!=null;t++)n[t]&&n[t].nodeType===1&&f.push(j[t]);else p(n,f);o&&(k(o,h,f,g),k.uniqueSort(f));return f};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]=d++;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){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},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(e.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var f=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;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}k.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!k.isXML(a))try{if(e||!l.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}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(h=g;h0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h,i,j={},k=1;if(g&&a.length){for(d=0,e=a.length;d-1:f(g).is(h))&&c.push({selector:i,elem:g,level:k});g=g.parentNode,k++}}return c}var l=S.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(U(c[0])||U(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c),g=R.call(arguments);N.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!T[a]?f.unique(e):e,(this.length>1||P.test(d))&&O.test(a)&&(e=e.reverse());return this.pushStack(e,a,g.join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},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 W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/",""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]};be.optgroup=be.option,be.tbody=be.tfoot=be.colgroup=be.caption=be.thead,be.th=be.td,f.support.htmlSerialize||(be._default=[1,"div
","
"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(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 f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(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(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){f(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(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=f(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,f(arguments[0]).toArray());return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.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 f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!be[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1>");try{for(var c=0,d=this.length;c1&&l0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d=a.cloneNode(!0),e,g,h;if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bh(a,d),e=bi(a),g=bi(d);for(h=0;e[h];++h)g[h]&&bh(e[h],g[h])}if(b){bg(a,d);if(c){e=bi(a),g=bi(d);for(h=0;e[h];++h)bg(e[h],g[h])}}e=g=null;return d},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!_.test(k))k=b.createTextNode(k);else{k=k.replace(Y,"<$1>");var l=(Z.exec(k)||["",""])[1].toLowerCase(),m=be[l]||be._default,n=m[0],o=b.createElement("div");o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=$.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]===""&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&X.test(k)&&o.insertBefore(b.createTextNode(X.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bn.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNaN(b)?"":"alpha(opacity="+b*100+")",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bm,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bm.test(g)?g.replace(bm,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bv(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bw=function(a,c){var d,e,g;c=c.replace(bo,"-$1").toLowerCase();if(!(e=a.ownerDocument.defaultView))return b;if(g=e.getComputedStyle(a,null))d=g.getPropertyValue(c),d===""&&!f.contains(a.ownerDocument.documentElement,a)&&(d=f.style(a,c));return d}),c.documentElement.currentStyle&&(bx=function(a,b){var c,d=a.currentStyle&&a.currentStyle[b],e=a.runtimeStyle&&a.runtimeStyle[b],f=a.style;!bp.test(d)&&bq.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}),bv=bw||bx,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bz=/%20/g,bA=/\[\]$/,bB=/\r?\n/g,bC=/#.*$/,bD=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bE=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bF=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bG=/^(?:GET|HEAD)$/,bH=/^\/\//,bI=/\?/,bJ=/)<[^<]*)*<\/script>/gi,bK=/^(?:select|textarea)/i,bL=/\s+/,bM=/([?&])_=[^&]*/,bN=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bO=f.fn.load,bP={},bQ={},bR,bS,bT=["*/"]+["*"];try{bR=e.href}catch(bU){bR=c.createElement("a"),bR.href="",bR=bR.href}bS=bN.exec(bR.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bO)return bO.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.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?f("
").append(c.replace(bJ,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bK.test(this.nodeName)||bE.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bB,"\r\n")}}):{name:b.name,value:c.replace(bB,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.bind(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?bX(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),bX(a,b);return a},ajaxSettings:{url:bR,isLocal:bF.test(bS[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","*":bT},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bV(bP),ajaxTransport:bV(bQ),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?bZ(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=b$(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.resolveWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f._Deferred(),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bD.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.done,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bC,"").replace(bH,bS[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bL),d.crossDomain==null&&(r=bN.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bS[1]&&r[2]==bS[2]&&(r[3]||(r[1]==="http:"?80:443))==(bS[3]||(bS[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bW(bP,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bG.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bI.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bM,"$1_="+x);d.url=y+(y===d.url?(bI.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bT+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bW(bQ,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){s<2?w(-1,z):f.error(z)}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)bY(g,a[g],c,e);return d.join("&").replace(bz,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var b_=f.now(),ca=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+b_++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(ca.test(b.url)||e&&ca.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(ca,l),b.url===j&&(e&&(k=k.replace(ca,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.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(c||!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 cb=a.ActiveXObject?function(){for(var a in cd)cd[a](0,1)}:!1,cc=0,cd;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ce()||cf()}:ce,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cb&&delete cd[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cc,cb&&(cd||(cd={},f(a).unload(cb)),cd[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cg={},ch,ci,cj=/^(?:toggle|show|hide)$/,ck=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cl,cm=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cn;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cq("show",3),a,b,c);for(var g=0,h=this.length;g=e.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),e.animatedProperties[this.prop]=!0;for(g in e.animatedProperties)e.animatedProperties[g]!==!0&&(c=!1);if(c){e.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){d.style["overflow"+b]=e.overflow[a]}),e.hide&&f(d).hide();if(e.hide||e.show)for(var i in e.animatedProperties)f.style(d,i,e.orig[i]);e.complete.call(d)}return!1}e.duration==Infinity?this.now=b:(h=b-this.startTime,this.state=h/e.duration,this.pos=f.easing[e.animatedProperties[this.prop]](this.state,h,0,1,e.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){for(var a=f.timers,b=0;b
";f.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),d=b.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,this.doesNotAddBorder=e.offsetTop!==5,this.doesAddBorderForTableAndCells=h.offsetTop===5,e.style.position="fixed",e.style.top="20px",this.supportsFixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",this.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==i,a.removeChild(b),f.offset.initialize=f.noop},bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.offset.initialize(),f.offset.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.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):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=ct.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!ct.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cu(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cu(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a&&a.style?parseFloat(f.css(a,d,"padding")):null},f.fn["outer"+c]=function(a){var b=this[0];return b&&b.style?parseFloat(f.css(b,d,a?"margin":"border")):null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c],h=e.document.body;return e.document.compatMode==="CSS1Compat"&&g||h&&h["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var i=f.css(e,d),j=parseFloat(i);return f.isNaN(j)?i:j}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f})(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 index 6324793f8..0ebf6e3cf 100644 --- a/openlp/plugins/remotes/html/jquery.mobile.css +++ b/openlp/plugins/remotes/html/jquery.mobile.css @@ -1,16 +1,12 @@ /*! - * jQuery Mobile v1.0a3 + * jQuery Mobile v1.0rc2 * 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 + *//*! +* jQuery Mobile Framework +* Copyright (c) jQuery Project +* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses. +*/.ui-bar-a{border:1px solid #2a2a2a;background:#111;color:#fff;font-weight:bold;text-shadow:0 -1px 1px #000;background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#111));background-image:-webkit-linear-gradient(top,#3c3c3c,#111);background-image:-moz-linear-gradient(top,#3c3c3c,#111);background-image:-ms-linear-gradient(top,#3c3c3c,#111);background-image:-o-linear-gradient(top,#3c3c3c,#111);background-image:linear-gradient(top,#3c3c3c,#111)}.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-bar-a .ui-link:hover{color:#2489ce}.ui-bar-a .ui-link:active{color:#2489ce}.ui-bar-a .ui-link:visited{color:#2489ce}.ui-body-a{border:1px solid #2a2a2a;background:#222;color:#fff;text-shadow:0 1px 0 #000;font-weight:normal;background-image:-webkit-gradient(linear,left top,left bottom,from(#666),to(#222));background-image:-webkit-linear-gradient(top,#666,#222);background-image:-moz-linear-gradient(top,#666,#222);background-image:-ms-linear-gradient(top,#666,#222);background-image:-o-linear-gradient(top,#666,#222);background-image:linear-gradient(top,#666,#222)}.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-body-a .ui-link:hover{color:#2489ce}.ui-body-a .ui-link:active{color:#2489ce}.ui-body-a .ui-link:visited{color:#2489ce}.ui-br{border-bottom:#828282;border-bottom:rgba(130,130,130,.3);border-bottom-width:1px;border-bottom-style:solid}.ui-btn-up-a{border:1px solid #222;background:#333;font-weight:bold;color:#fff;text-shadow:0 -1px 1px #000;background-image:-webkit-gradient(linear,left top,left bottom,from(#555),to(#333));background-image:-webkit-linear-gradient(top,#555,#333);background-image:-moz-linear-gradient(top,#555,#333);background-image:-ms-linear-gradient(top,#555,#333);background-image:-o-linear-gradient(top,#555,#333);background-image:linear-gradient(top,#555,#333)}.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;background-image:-webkit-gradient(linear,left top,left bottom,from(#666),to(#444));background-image:-webkit-linear-gradient(top,#666,#444);background-image:-moz-linear-gradient(top,#666,#444);background-image:-ms-linear-gradient(top,#666,#444);background-image:-o-linear-gradient(top,#666,#444);background-image:linear-gradient(top,#666,#444)}.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:-webkit-gradient(linear,left top,left bottom,from(#333),to(#5a5a5a));background-image:-webkit-linear-gradient(top,#333,#5a5a5a);background-image:-moz-linear-gradient(top,#333,#5a5a5a);background-image:-ms-linear-gradient(top,#333,#5a5a5a);background-image:-o-linear-gradient(top,#333,#5a5a5a);background-image:linear-gradient(top,#333,#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;text-decoration:none}.ui-bar-b{border:1px solid #456f9a;background:#5e87b0;color:#fff;font-weight:bold;text-shadow:0 -1px 1px #254f7a;background-image:-webkit-gradient(linear,left top,left bottom,from(#81a8ce),to(#5e87b0));background-image:-webkit-linear-gradient(top,#81a8ce,#5e87b0);background-image:-moz-linear-gradient(top,#81a8ce,#5e87b0);background-image:-ms-linear-gradient(top,#81a8ce,#5e87b0);background-image:-o-linear-gradient(top,#81a8ce,#5e87b0);background-image:linear-gradient(top,#81a8ce,#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:#ddf0f8;font-weight:bold}.ui-bar-b .ui-link:hover{color:#ddf0f8}.ui-bar-b .ui-link:active{color:#ddf0f8}.ui-bar-b .ui-link:visited{color:#ddf0f8}.ui-body-b{border:1px solid #c6c6c6;background:#ccc;color:#333;text-shadow:0 1px 0 #fff;font-weight:normal;background-image:-webkit-gradient(linear,left top,left bottom,from(#e6e6e6),to(#ccc));background-image:-webkit-linear-gradient(top,#e6e6e6,#ccc);background-image:-moz-linear-gradient(top,#e6e6e6,#ccc);background-image:-ms-linear-gradient(top,#e6e6e6,#ccc);background-image:-o-linear-gradient(top,#e6e6e6,#ccc);background-image:linear-gradient(top,#e6e6e6,#ccc)}.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-body-b .ui-link:hover{color:#2489ce}.ui-body-b .ui-link:active{color:#2489ce}.ui-body-b .ui-link:visited{color:#2489ce}.ui-btn-up-b{border:1px solid #145072;background:#2567ab;font-weight:bold;color:#fff;text-shadow:0 -1px 1px #145072;background-image:-webkit-gradient(linear,left top,left bottom,from(#5f9cc5),to(#396b9e));background-image:-webkit-linear-gradient(top,#5f9cc5,#396b9e);background-image:-moz-linear-gradient(top,#5f9cc5,#396b9e);background-image:-ms-linear-gradient(top,#5f9cc5,#396b9e);background-image:-o-linear-gradient(top,#5f9cc5,#396b9e);background-image:linear-gradient(top,#5f9cc5,#396b9e)}.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:-webkit-gradient(linear,left top,left bottom,from(#72b0d4),to(#4b88b6));background-image:-webkit-linear-gradient(top,#72b0d4,#4b88b6);background-image:-moz-linear-gradient(top,#72b0d4,#4b88b6);background-image:-ms-linear-gradient(top,#72b0d4,#4b88b6);background-image:-o-linear-gradient(top,#72b0d4,#4b88b6);background-image:linear-gradient(top,#72b0d4,#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:-webkit-gradient(linear,left top,left bottom,from(#396b9e),to(#4e89c5));background-image:-webkit-linear-gradient(top,#396b9e,#4e89c5);background-image:-moz-linear-gradient(top,#396b9e,#4e89c5);background-image:-ms-linear-gradient(top,#396b9e,#4e89c5);background-image:-o-linear-gradient(top,#396b9e,#4e89c5);background-image:linear-gradient(top,#396b9e,#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;text-decoration:none}.ui-bar-c{border:1px solid #b3b3b3;background:#e9eaeb;color:#3e3e3e;font-weight:bold;text-shadow:0 1px 1px #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#f0f0f0),to(#e9eaeb));background-image:-webkit-linear-gradient(top,#f0f0f0,#e9eaeb);background-image:-moz-linear-gradient(top,#f0f0f0,#e9eaeb);background-image:-ms-linear-gradient(top,#f0f0f0,#e9eaeb);background-image:-o-linear-gradient(top,#f0f0f0,#e9eaeb);background-image:linear-gradient(top,#f0f0f0,#e9eaeb)}.ui-bar-c .ui-link-inherit{color:#3e3e3e}.ui-bar-c .ui-link{color:#7cc4e7;font-weight:bold}.ui-bar-c .ui-link:hover{color:#2489ce}.ui-bar-c .ui-link:active{color:#2489ce}.ui-bar-c .ui-link:visited{color:#2489ce}.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:-webkit-gradient(linear,left top,left bottom,from(#eee),to(#ddd));background-image:-webkit-linear-gradient(top,#eee,#ddd);background-image:-moz-linear-gradient(top,#eee,#ddd);background-image:-ms-linear-gradient(top,#eee,#ddd);background-image:-o-linear-gradient(top,#eee,#ddd);background-image:linear-gradient(top,#eee,#ddd)}.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-body-c .ui-link:hover{color:#2489ce}.ui-body-c .ui-link:active{color:#2489ce}.ui-body-c .ui-link:visited{color:#2489ce}.ui-btn-up-c{border:1px solid #ccc;background:#eee;font-weight:bold;color:#444;text-shadow:0 1px 1px #f6f6f6;background-image:-webkit-gradient(linear,left top,left bottom,from(#fdfdfd),to(#eee));background-image:-webkit-linear-gradient(top,#fdfdfd,#eee);background-image:-moz-linear-gradient(top,#fdfdfd,#eee);background-image:-ms-linear-gradient(top,#fdfdfd,#eee);background-image:-o-linear-gradient(top,#fdfdfd,#eee);background-image:linear-gradient(top,#fdfdfd,#eee)}.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-shadow:0 1px 1px #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#ededed),to(#dadada));background-image:-webkit-linear-gradient(top,#ededed,#dadada);background-image:-moz-linear-gradient(top,#ededed,#dadada);background-image:-ms-linear-gradient(top,#ededed,#dadada);background-image:-o-linear-gradient(top,#ededed,#dadada);background-image:linear-gradient(top,#ededed,#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:-webkit-gradient(linear,left top,left bottom,from(#eee),to(#fdfdfd));background-image:-webkit-linear-gradient(top,#eee,#fdfdfd);background-image:-moz-linear-gradient(top,#eee,#fdfdfd);background-image:-ms-linear-gradient(top,#eee,#fdfdfd);background-image:-o-linear-gradient(top,#eee,#fdfdfd);background-image:linear-gradient(top,#eee,#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;text-decoration:none}.ui-bar-d{border:1px solid #ccc;background:#bbb;color:#333;text-shadow:0 1px 0 #eee;background-image:-webkit-gradient(linear,left top,left bottom,from(#ddd),to(#bbb));background-image:-webkit-linear-gradient(top,#ddd,#bbb);background-image:-moz-linear-gradient(top,#ddd,#bbb);background-image:-ms-linear-gradient(top,#ddd,#bbb);background-image:-o-linear-gradient(top,#ddd,#bbb);background-image:linear-gradient(top,#ddd,#bbb)}.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-bar-d .ui-link:hover{color:#2489ce}.ui-bar-d .ui-link:active{color:#2489ce}.ui-bar-d .ui-link:visited{color:#2489ce}.ui-body-d{border:1px solid #ccc;color:#333;text-shadow:0 1px 0 #fff;background:#fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#fff));background-image:-webkit-linear-gradient(top,#fff,#fff);background-image:-moz-linear-gradient(top,#fff,#fff);background-image:-ms-linear-gradient(top,#fff,#fff);background-image:-o-linear-gradient(top,#fff,#fff);background-image:linear-gradient(top,#fff,#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-body-d .ui-link:hover{color:#2489ce}.ui-body-d .ui-link:active{color:#2489ce}.ui-body-d .ui-link:visited{color:#2489ce}.ui-btn-up-d{border:1px solid #ccc;background:#fff;font-weight:bold;color:#444;text-shadow:0 1px 1px #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#fff));background-image:-webkit-linear-gradient(top,#fff,#fff);background-image:-moz-linear-gradient(top,#fff,#fff);background-image:-ms-linear-gradient(top,#fff,#fff);background-image:-o-linear-gradient(top,#fff,#fff);background-image:linear-gradient(top,#fff,#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;background-image:-webkit-gradient(linear,left top,left bottom,from(#fdfdfd),to(#eee));background-image:-webkit-linear-gradient(top,#fdfdfd,#eee);background-image:-moz-linear-gradient(top,#fdfdfd,#eee);background-image:-ms-linear-gradient(top,#fdfdfd,#eee);background-image:-o-linear-gradient(top,#fdfdfd,#eee);background-image:linear-gradient(top,#fdfdfd,#eee)}.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:-webkit-gradient(linear,left top,left bottom,from(#eee),to(#fff));background-image:-webkit-linear-gradient(top,#eee,#fff);background-image:-moz-linear-gradient(top,#eee,#fff);background-image:-ms-linear-gradient(top,#eee,#fff);background-image:-o-linear-gradient(top,#eee,#fff);background-image:linear-gradient(top,#eee,#fff)}.ui-btn-down-d a.ui-link-inherit{color:#111}.ui-btn-up-d,.ui-btn-hover-d,.ui-btn-down-d{font-family:Helvetica,Arial,sans-serif;text-decoration:none}.ui-bar-e{border:1px solid #f7c942;background:#fadb4e;color:#333;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#fceda7),to(#fadb4e));background-image:-webkit-linear-gradient(top,#fceda7,#fadb4e);background-image:-moz-linear-gradient(top,#fceda7,#fadb4e);background-image:-ms-linear-gradient(top,#fceda7,#fadb4e);background-image:-o-linear-gradient(top,#fceda7,#fadb4e);background-image:linear-gradient(top,#fceda7,#fadb4e)}.ui-bar-e,.ui-bar-e input,.ui-bar-e select,.ui-bar-e textarea,.ui-bar-e 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-bar-e .ui-link:hover{color:#2489ce}.ui-bar-e .ui-link:active{color:#2489ce}.ui-bar-e .ui-link:visited{color:#2489ce}.ui-body-e{border:1px solid #f7c942;color:#333;text-shadow:0 1px 0 #fff;background:#faeb9e;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#faeb9e));background-image:-webkit-linear-gradient(top,#fff,#faeb9e);background-image:-moz-linear-gradient(top,#fff,#faeb9e);background-image:-ms-linear-gradient(top,#fff,#faeb9e);background-image:-o-linear-gradient(top,#fff,#faeb9e);background-image:linear-gradient(top,#fff,#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-body-e .ui-link:hover{color:#2489ce}.ui-body-e .ui-link:active{color:#2489ce}.ui-body-e .ui-link:visited{color:#2489ce}.ui-btn-up-e{border:1px solid #f7c942;background:#fadb4e;font-weight:bold;color:#333;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#fceda7),to(#fadb4e));background-image:-webkit-linear-gradient(top,#fceda7,#fadb4e);background-image:-moz-linear-gradient(top,#fceda7,#fadb4e);background-image:-ms-linear-gradient(top,#fceda7,#fadb4e);background-image:-o-linear-gradient(top,#fceda7,#fadb4e);background-image:linear-gradient(top,#fceda7,#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-shadow:0 1px 1px #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf0b5),to(#fbe26f));background-image:-webkit-linear-gradient(top,#fcf0b5,#fbe26f);background-image:-moz-linear-gradient(top,#fcf0b5,#fbe26f);background-image:-ms-linear-gradient(top,#fcf0b5,#fbe26f);background-image:-o-linear-gradient(top,#fcf0b5,#fbe26f);background-image:linear-gradient(top,#fcf0b5,#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:-webkit-gradient(linear,left top,left bottom,from(#fadb4e),to(#fceda7));background-image:-webkit-linear-gradient(top,#fadb4e,#fceda7);background-image:-moz-linear-gradient(top,#fadb4e,#fceda7);background-image:-ms-linear-gradient(top,#fadb4e,#fceda7);background-image:-o-linear-gradient(top,#fadb4e,#fceda7);background-image:linear-gradient(top,#fadb4e,#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;text-decoration:none}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:-webkit-gradient(linear,left top,left bottom,from(#85bae4),to(#5393c5));background-image:-webkit-linear-gradient(top,#85bae4,#5393c5);background-image:-moz-linear-gradient(top,#85bae4,#5393c5);background-image:-ms-linear-gradient(top,#85bae4,#5393c5);background-image:-o-linear-gradient(top,#85bae4,#5393c5);background-image:linear-gradient(top,#85bae4,#5393c5);font-family:Helvetica,Arial,sans-serif}.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-corner-none{-moz-border-radius:0;-webkit-border-radius:0;border-radius:0}.ui-disabled{opacity:.3}.ui-disabled,.ui-disabled a{pointer-events:none;cursor:default}.ui-icon,.ui-icon-searchfield:after{background:#666;background:rgba(0,0,0,.4);background-image:url(images/icons-18-white.png);background-repeat:no-repeat;-moz-border-radius:9px;-webkit-border-radius:9px;border-radius:9px}.ui-icon-alt{background:#fff;background:rgba(255,255,255,.3);background-image:url(images/icons-18-black.png);background-repeat:no-repeat}@media only screen and (-webkit-min-device-pixel-ratio:1.5),only screen and (min--moz-device-pixel-ratio:1.5),only screen and (min-resolution:240dpi){.ui-icon-plus,.ui-icon-minus,.ui-icon-delete,.ui-icon-arrow-r,.ui-icon-arrow-l,.ui-icon-arrow-u,.ui-icon-arrow-d,.ui-icon-check,.ui-icon-gear,.ui-icon-refresh,.ui-icon-forward,.ui-icon-back,.ui-icon-grid,.ui-icon-star,.ui-icon-alert,.ui-icon-info,.ui-icon-home,.ui-icon-search,.ui-icon-searchfield:after,.ui-icon-checkbox-off,.ui-icon-checkbox-on,.ui-icon-radio-off,.ui-icon-radio-on{background-image:url(images/icons-36-white.png);-moz-background-size:776px 18px;-o-background-size:776px 18px;-webkit-background-size:776px 18px;background-size:776px 18px}.ui-icon-alt{background-image:url(images/icons-36-black.png)}}.ui-icon-plus{background-position:-0 50%}.ui-icon-minus{background-position:-36px 50%}.ui-icon-delete{background-position:-72px 50%}.ui-icon-arrow-r{background-position:-108px 50%}.ui-icon-arrow-l{background-position:-144px 50%}.ui-icon-arrow-u{background-position:-180px 50%}.ui-icon-arrow-d{background-position:-216px 50%}.ui-icon-check{background-position:-252px 50%}.ui-icon-gear{background-position:-288px 50%}.ui-icon-refresh{background-position:-324px 50%}.ui-icon-forward{background-position:-360px 50%}.ui-icon-back{background-position:-396px 50%}.ui-icon-grid{background-position:-432px 50%}.ui-icon-star{background-position:-468px 50%}.ui-icon-alert{background-position:-504px 50%}.ui-icon-info{background-position:-540px 50%}.ui-icon-home{background-position:-576px 50%}.ui-icon-search,.ui-icon-searchfield:after{background-position:-612px 50%}.ui-icon-checkbox-off{background-position:-684px 50%}.ui-icon-checkbox-on{background-position:-648px 50%}.ui-icon-radio-off{background-position:-756px 50%}.ui-icon-radio-on{background-position:-720px 50%}.ui-checkbox .ui-icon{-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px}.ui-icon-checkbox-off,.ui-icon-radio-off{background-color:transparent}.ui-checkbox-on .ui-icon,.ui-radio-on .ui-icon{background-color:#4596ce}.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;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,.ui-mobile body{height:100%}.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}.ui-page{outline:0}@media screen and (orientation:portrait){.ui-mobile,.ui-mobile .ui-page{min-height:420px}}@media screen and (orientation:landscape){.ui-mobile,.ui-mobile .ui-page{min-height:300px}}.ui-page.ui-mobile-touch-overflow,.ui-mobile-touch-overflow.ui-native-fixed .ui-content{overflow:auto;height:100%;-webkit-overflow-scrolling:touch;-moz-overflow-scrolling:touch;-o-overflow-scrolling:touch;-ms-overflow-scrolling:touch;overflow-scrolling:touch}.ui-page.ui-mobile-touch-overflow,.ui-page.ui-mobile-touch-overflow *{-webkit-transform:rotateY(0)}.ui-page.ui-mobile-pre-transition{display:block}.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:100;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-btn-right{position:absolute;right:10px;top:.4em}.ui-header .ui-title,.ui-footer .ui-title{min-height:1.1em;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-footer .ui-title{margin:.6em 15px .8em}.ui-content{border-width:0;overflow:visible;overflow-x:hidden;padding:15px}.ui-page-fullscreen .ui-content{padding:0}.ui-mobile-touch-overflow.ui-page.ui-native-fixed,.ui-mobile-touch-overflow.ui-page.ui-native-fullscreen{overflow:visible}.ui-mobile-touch-overflow.ui-native-fixed .ui-header,.ui-mobile-touch-overflow.ui-native-fixed .ui-footer{position:fixed;left:0;right:0;top:0;z-index:200}.ui-mobile-touch-overflow.ui-page.ui-native-fixed .ui-footer{top:auto;bottom:0}.ui-mobile-touch-overflow.ui-native-fixed .ui-content{padding-top:2.5em;padding-bottom:3em;top:0;bottom:0;height:auto;position:absolute}.ui-mobile-touch-overflow.ui-native-fullscreen .ui-content{padding-top:0;padding-bottom:0}.ui-mobile-touch-overflow.ui-native-fullscreen .ui-header,.ui-mobile-touch-overflow.ui-native-fullscreen .ui-footer{opacity:.9}.ui-native-bars-hidden{display:none}.ui-icon{width:18px;height:18px}.ui-fullscreen img{max-width:100%}.ui-nojs{position:absolute;left:-9999px}.ui-hide-label label,.ui-hidden-accessible{position:absolute!important;left:-9999px;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}.spin{-webkit-transform:rotate(360deg);-webkit-animation-name:spin;-webkit-animation-duration:1s;-webkit-animation-iteration-count:infinite;-webkit-animation-timing-function:linear}@-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.out{-webkit-transform:translateX(-100%);-webkit-animation-name:slideouttoleft}.slide.in{-webkit-transform:translateX(0);-webkit-animation-name:slideinfromright}.slide.out.reverse{-webkit-transform:translateX(100%);-webkit-animation-name:slideouttoright}.slide.in.reverse{-webkit-transform:translateX(0);-webkit-animation-name:slideinfromleft}.slideup.out{-webkit-animation-name:dontmove;z-index:0}.slideup.in{-webkit-transform:translateY(0);-webkit-animation-name:slideinfrombottom;z-index:10}.slideup.in.reverse{z-index:0;-webkit-animation-name:dontmove}.slideup.out.reverse{-webkit-transform:translateY(100%);z-index:10;-webkit-animation-name:slideouttobottom}.slidedown.out{-webkit-animation-name:dontmove;z-index:0}.slidedown.in{-webkit-transform:translateY(0);-webkit-animation-name:slideinfromtop;z-index:10}.slidedown.in.reverse{z-index:0;-webkit-animation-name:dontmove}.slidedown.out.reverse{-webkit-transform:translateY(-100%);z-index:10;-webkit-animation-name:slideouttotop}@-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.out{z-index:0;-webkit-animation-name:fadeout}.fade.in{opacity:1;z-index:10;-webkit-animation-name:fadein}.viewport-flip{-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.out{-webkit-transform:rotateY(-180deg) scale(.8);-webkit-animation-name:flipouttoleft}.flip.in{-webkit-transform:rotateY(0) scale(1);-webkit-animation-name:flipinfromleft}.flip.out.reverse{-webkit-transform:rotateY(180deg) scale(.8);-webkit-animation-name:flipouttoright}.flip.in.reverse{-webkit-transform:rotateY(0) scale(1);-webkit-animation-name:flipinfromright}@-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.in.reverse{z-index:0;-webkit-animation-name:dontmove}.pop.out.reverse{-webkit-transform:scale(.2);opacity:0;-webkit-animation-name:popout;z-index:10}@-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;min-height:1px}.ui-grid-solo .ui-block-a{width:100%;float:none}.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-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;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;position:relative;zoom:1}.ui-btn input,.ui-btn button{z-index:2}.ui-header .ui-btn-inner,.ui-footer .ui-btn-inner,.ui-bar .ui-btn-inner{padding:.4em 8px .5em}.ui-btn-icon-notext{width:24px;height:24px}.ui-btn-icon-notext .ui-btn-inner{padding:2px 1px 2px 3px}.ui-btn-text{position:relative;z-index:1}.ui-btn-icon-notext .ui-btn-text{position:absolute;left:-9999px}.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;z-index:0}.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-btn-icon-top .ui-icon{top:10px}.ui-btn-icon-bottom .ui-icon{bottom: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-hidden{position:absolute;top:0;left:0;width:100%;height:100%;-webkit-appearance:button;opacity:.1;cursor:pointer;background:transparent;font-size:1px;border:0;line-height:999px}.ui-collapsible{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:10px 0}.ui-collapsible-heading a span.ui-btn .ui-icon{left:0;margin-top:-10px}.ui-collapsible-heading-status{position:fixed;left:-9999px}.ui-collapsible-content{display:block;margin:0 -8px;padding:10px 16px;border-top:0;background-image:none;font-weight:normal}.ui-collapsible-content-collapsed{display:none}.ui-collapsible-set{margin:.5em 0}.ui-collapsible-set .ui-collapsible{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:100%}.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-controls label.ui-select{position:absolute;left:-9999px}.ui-controlgroup-vertical .ui-controlgroup-last{border-bottom-width:1px}.ui-controlgroup-horizontal{padding:0}.ui-controlgroup-horizontal .ui-btn,.ui-controlgroup-horizontal .ui-select{display:inline-block;margin:0 -5px 0 0}.ui-controlgroup-horizontal .ui-checkbox,.ui-controlgroup-horizontal .ui-radio{float:left;margin:0 -1px 0 0}.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}@media all and (min-width:450px){.ui-field-contain .ui-controlgroup-label{vertical-align:top;display:inline-block;width:20%;margin:0 2% 0 0}.ui-field-contain .ui-controlgroup-controls{width:60%;display:inline-block}.ui-field-contain .ui-controlgroup .ui-select{width:100%}.ui-field-contain .ui-controlgroup-horizontal .ui-select{width:auto}}.ui-dialog{min-height:480px}.ui-dialog .ui-header,.ui-dialog .ui-content,.ui-dialog .ui-footer{max-width:500px;margin:10% auto 0 auto;padding:15px;width:85%;position:relative}.ui-dialog .ui-header{padding:0 15px}.ui-dialog .ui-header,.ui-dialog .ui-footer{z-index:10}.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-inner,.ui-radio .ui-btn-inner{white-space:normal}.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-icon,.ui-radio .ui-icon{top:1.1em}.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{padding:1.5em 0;margin:0;border-bottom-width:1px;overflow:visible}.ui-field-contain:first-child{border-top-width:0}@media all and (min-width:450px){.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{overflow:hidden}.ui-select .ui-btn select{cursor:pointer;-webkit-appearance:button;left:0;top:0;width:100%;min-height:1.5em;min-height:100%;height:3em;max-height:100%;opacity:0;-ms-filter:"alpha(opacity=0)";filter:alpha(opacity=0);z-index:2}@-moz-document url-prefix(){.ui-select .ui-btn select{opacity:.0001}}.ui-select .ui-btn select.ui-select-nativeonly{opacity:1;text-indent:0}.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:block;min-height:1em;overflow:hidden}.ui-select .ui-btn-text{text-overflow:ellipsis}.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}.ui-selectmenu .ui-header .ui-title{margin:.6em 46px .8em}@media all and (min-width:450px){.ui-field-contain label.ui-select{vertical-align:top;display:inline-block;width:20%;margin:0 2% 0 0}.ui-field-contain .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 1.5%;line-height:1.4;font-size:16px;display:block;width:97%}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;background-image:none;position:relative}.ui-icon-searchfield:after{position:absolute;left:7px;top:50%;margin-top:-9px;content:"";width:18px;height:18px;opacity:.5}.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:-13px}.ui-input-search .ui-input-clear-hidden{display:none}@media all and (min-width:450px){.ui-field-contain label.ui-input-text{vertical-align:top;display:inline-block;width:20%;margin:0 2% 0 0}.ui-field-contain input.ui-input-text,.ui-field-contain textarea.ui-input-text,.ui-field-contain .ui-input-search{width:60%;display:inline-block}.ui-field-contain .ui-input-search{width:50%}.ui-hide-label input.ui-input-text,.ui-hide-label textarea.ui-input-text,.ui-hide-label .ui-input-search{padding:.4em 1.5%;width:97%}.ui-input-search input.ui-input-text{width:98%}}.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}.ui-li,.ui-li.ui-field-contain{display:block;margin:0;position:relative;overflow:visible;text-align:left;border-width:0;border-top-width:1px}.ui-li .ui-btn-text a.ui-link-inherit{text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.ui-li-divider,.ui-li-static{padding:.5em 15px;font-size:14px;font-weight:bold}.ui-li-divider{counter-reset:listnumbering}ol.ui-listview .ui-link-inherit:before,ol.ui-listview .ui-li-static: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,.ui-li.ui-field-contain:last-child{border-bottom-width:1px}.ui-li>.ui-btn-inner{display:block;position:relative;padding:0}.ui-li .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li{padding:.7em 15px .7em 15px;display:block}.ui-li-has-thumb .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-thumb{min-height:60px;padding-left:100px}.ui-li-has-icon .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-icon{min-height:20px;padding-left:40px}.ui-li-has-count .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-count{padding-right:45px}.ui-li-has-arrow .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-arrow{padding-right:30px}.ui-li-has-arrow.ui-li-has-count .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-arrow.ui-li-has-count{padding-right:75px}.ui-li-has-count .ui-btn-text{padding-right:15px}.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}@media all and (min-width:480px){.ui-li-aside{width:45%}}.ui-li-divider{cursor:default}.ui-li-has-alt .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-alt{padding-right:95px}.ui-li-has-count .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,.ui-li-static .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;z-index:2}.ui-li-link-alt .ui-btn{overflow:hidden;position:absolute;right:8px;top:50%;margin:-11px 0 0 0;border-bottom-width:1px;z-index:-1}.ui-li-link-alt .ui-btn-inner{padding:0;height:100%;position:absolute;width:100%;top:0;left:0}.ui-li-link-alt .ui-btn .ui-icon{right:50%;margin-right:-9px}.ui-listview * .ui-btn-inner>.ui-btn>.ui-btn-inner{border-top:0}.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}.ui-listview-filter-inset{margin:-15px -5px -15px -5px;background:transparent}.ui-li.ui-screen-hidden{display:none}@media only screen and (min-device-width:768px) and (max-device-width:1024px){.ui-li .ui-btn-text{overflow:visible}}label.ui-slider{font-size:16px;line-height:1.4;font-weight:normal;margin:0 0 .3em;display:block}input.ui-slider-input,.ui-field-contain 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%}div.ui-slider-switch{width:99.8%}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}@media all and (min-width:480px){.ui-field-contain label.ui-slider{vertical-align:top;display:inline-block;width:20%;margin:0 2% 0 0}.ui-field-contain div.ui-slider{width:43%}}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%}a.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:0}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 index 44cc49e71..a99df1903 100644 --- a/openlp/plugins/remotes/html/jquery.mobile.js +++ b/openlp/plugins/remotes/html/jquery.mobile.js @@ -1,121 +1,175 @@ /*! - * jQuery Mobile v1.0a3 + * jQuery Mobile v1.0rc2 * 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('