forked from openlp/openlp
trunk
This commit is contained in:
commit
d9c6e2a228
@ -6,6 +6,8 @@
|
|||||||
*.ropeproject
|
*.ropeproject
|
||||||
*.e4*
|
*.e4*
|
||||||
.eric4project
|
.eric4project
|
||||||
|
.komodotools
|
||||||
|
*.komodoproject
|
||||||
list
|
list
|
||||||
openlp.org 2.0.e4*
|
openlp.org 2.0.e4*
|
||||||
documentation/build/html
|
documentation/build/html
|
||||||
@ -30,3 +32,4 @@ tests.kdev4
|
|||||||
*.orig
|
*.orig
|
||||||
__pycache__
|
__pycache__
|
||||||
*.dll
|
*.dll
|
||||||
|
.directory
|
||||||
|
@ -5,6 +5,8 @@ recursive-include openlp *.html
|
|||||||
recursive-include openlp *.js
|
recursive-include openlp *.js
|
||||||
recursive-include openlp *.css
|
recursive-include openlp *.css
|
||||||
recursive-include openlp *.png
|
recursive-include openlp *.png
|
||||||
|
recursive-include openlp *.ps
|
||||||
|
recursive-include openlp *.json
|
||||||
recursive-include documentation *
|
recursive-include documentation *
|
||||||
recursive-include resources *
|
recursive-include resources *
|
||||||
recursive-include scripts *
|
recursive-include scripts *
|
||||||
|
11
README.txt
11
README.txt
@ -1,16 +1,15 @@
|
|||||||
OpenLP 2.0
|
OpenLP
|
||||||
==========
|
======
|
||||||
|
|
||||||
You're probably reading this because you've just downloaded the source code for
|
You're probably reading this because you've just downloaded the source code for
|
||||||
OpenLP 2.0. If you are looking for the installer file, please go to the download
|
OpenLP. If you are looking for the installer file, please go to the download
|
||||||
page on the web site::
|
page on the web site::
|
||||||
|
|
||||||
http://openlp.org/en/download.html
|
http://openlp.org/download
|
||||||
|
|
||||||
If you're looking for how to contribute to OpenLP, then please look at the
|
If you're looking for how to contribute to OpenLP, then please look at the
|
||||||
OpenLP wiki::
|
OpenLP wiki::
|
||||||
|
|
||||||
http://wiki.openlp.org/
|
http://wiki.openlp.org/
|
||||||
|
|
||||||
Thanks for downloading OpenLP 2.0!
|
Thanks for downloading OpenLP!
|
||||||
|
|
||||||
|
@ -110,7 +110,7 @@ class AppLocation(object):
|
|||||||
:param extension:
|
:param extension:
|
||||||
Defaults to *None*. The extension to search for. For example::
|
Defaults to *None*. The extension to search for. For example::
|
||||||
|
|
||||||
u'.png'
|
'.png'
|
||||||
"""
|
"""
|
||||||
path = AppLocation.get_data_path()
|
path = AppLocation.get_data_path()
|
||||||
if section:
|
if section:
|
||||||
|
@ -68,8 +68,7 @@ class Settings(QtCore.QSettings):
|
|||||||
``__obsolete_settings__``
|
``__obsolete_settings__``
|
||||||
Each entry is structured in the following way::
|
Each entry is structured in the following way::
|
||||||
|
|
||||||
(u'general/enable slide loop', u'advanced/slide limits',
|
('general/enable slide loop', 'advanced/slide limits', [(SlideLimits.Wrap, True), (SlideLimits.End, False)])
|
||||||
[(SlideLimits.Wrap, True), (SlideLimits.End, False)])
|
|
||||||
|
|
||||||
The first entry is the *old key*; it will be removed.
|
The first entry is the *old key*; it will be removed.
|
||||||
|
|
||||||
|
@ -296,8 +296,7 @@ def create_separated_list(string_list):
|
|||||||
|
|
||||||
:param string_list: List of unicode strings
|
:param string_list: List of unicode strings
|
||||||
"""
|
"""
|
||||||
if LooseVersion(Qt.PYQT_VERSION_STR) >= LooseVersion('4.9') and \
|
if LooseVersion(Qt.PYQT_VERSION_STR) >= LooseVersion('4.9') and LooseVersion(Qt.qVersion()) >= LooseVersion('4.8'):
|
||||||
LooseVersion(Qt.qVersion()) >= LooseVersion('4.8'):
|
|
||||||
return QtCore.QLocale().createSeparatedList(string_list)
|
return QtCore.QLocale().createSeparatedList(string_list)
|
||||||
if not string_list:
|
if not string_list:
|
||||||
return ''
|
return ''
|
||||||
|
@ -194,6 +194,7 @@ class Manager(object):
|
|||||||
db_ver, up_ver = upgrade_db(self.db_url, upgrade_mod)
|
db_ver, up_ver = upgrade_db(self.db_url, upgrade_mod)
|
||||||
except (SQLAlchemyError, DBAPIError):
|
except (SQLAlchemyError, DBAPIError):
|
||||||
log.exception('Error loading database: %s', self.db_url)
|
log.exception('Error loading database: %s', self.db_url)
|
||||||
|
return
|
||||||
if db_ver > up_ver:
|
if db_ver > up_ver:
|
||||||
critical_error_message_box(
|
critical_error_message_box(
|
||||||
translate('OpenLP.Manager', 'Database Error'),
|
translate('OpenLP.Manager', 'Database Error'),
|
||||||
|
@ -129,7 +129,7 @@ class Plugin(QtCore.QObject, RegistryProperties):
|
|||||||
|
|
||||||
class MyPlugin(Plugin):
|
class MyPlugin(Plugin):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(MyPlugin, self).__init__('MyPlugin', version=u'0.1')
|
super(MyPlugin, self).__init__('MyPlugin', version='0.1')
|
||||||
|
|
||||||
:param name: Defaults to *None*. The name of the plugin.
|
:param name: Defaults to *None*. The name of the plugin.
|
||||||
:param default_settings: A dict containing the plugin's settings. The value to each key is the default value
|
:param default_settings: A dict containing the plugin's settings. The value to each key is the default value
|
||||||
|
@ -248,6 +248,9 @@ class Renderer(OpenLPMixin, RegistryMixin, RegistryProperties):
|
|||||||
elif item.is_capable(ItemCapabilities.CanSoftBreak):
|
elif item.is_capable(ItemCapabilities.CanSoftBreak):
|
||||||
pages = []
|
pages = []
|
||||||
if '[---]' in text:
|
if '[---]' in text:
|
||||||
|
# Remove two or more option slide breaks next to each other (causing infinite loop).
|
||||||
|
while '\n[---]\n[---]\n' in text:
|
||||||
|
text = text.replace('\n[---]\n[---]\n', '\n[---]\n')
|
||||||
while True:
|
while True:
|
||||||
slides = text.split('\n[---]\n', 2)
|
slides = text.split('\n[---]\n', 2)
|
||||||
# If there are (at least) two occurrences of [---] we use the first two slides (and neglect the last
|
# If there are (at least) two occurrences of [---] we use the first two slides (and neglect the last
|
||||||
@ -392,7 +395,7 @@ class Renderer(OpenLPMixin, RegistryMixin, RegistryProperties):
|
|||||||
off when displayed.
|
off when displayed.
|
||||||
|
|
||||||
:param lines: The text to be fitted on the slide split into lines.
|
:param lines: The text to be fitted on the slide split into lines.
|
||||||
:param line_end: The text added after each line. Either ``u' '`` or ``u'<br>``.
|
:param line_end: The text added after each line. Either ``' '`` or ``'<br>``.
|
||||||
"""
|
"""
|
||||||
formatted = []
|
formatted = []
|
||||||
previous_html = ''
|
previous_html = ''
|
||||||
@ -416,7 +419,7 @@ class Renderer(OpenLPMixin, RegistryMixin, RegistryProperties):
|
|||||||
processed word by word. This is sometimes need for **bible** verses.
|
processed word by word. This is sometimes need for **bible** verses.
|
||||||
|
|
||||||
:param lines: The text to be fitted on the slide split into lines.
|
:param lines: The text to be fitted on the slide split into lines.
|
||||||
:param line_end: The text added after each line. Either ``u' '`` or ``u'<br>``. This is needed for **bibles**.
|
:param line_end: The text added after each line. Either ``' '`` or ``'<br>``. This is needed for **bibles**.
|
||||||
"""
|
"""
|
||||||
formatted = []
|
formatted = []
|
||||||
previous_html = ''
|
previous_html = ''
|
||||||
@ -453,7 +456,7 @@ class Renderer(OpenLPMixin, RegistryMixin, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
Tests the given text for not closed formatting tags and returns a tuple consisting of three unicode strings::
|
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'<strong><span style="-webkit-text-fill-color:red">')
|
('{st}{r}Text text text{/r}{/st}', '{st}{r}', '<strong><span style="-webkit-text-fill-color:red">')
|
||||||
|
|
||||||
The first unicode string is the text, with correct closing tags. The second unicode string are OpenLP's opening
|
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.
|
formatting tags and the third unicode string the html opening formatting tags.
|
||||||
@ -500,8 +503,8 @@ class Renderer(OpenLPMixin, RegistryMixin, RegistryProperties):
|
|||||||
The text contains html.
|
The text contains html.
|
||||||
:param raw_list: The elements which do not fit on a slide and needs to be processed using the binary chop.
|
:param 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.
|
The elements can contain formatting tags.
|
||||||
:param separator: The separator for the elements. For lines this is ``u'<br>'`` and for words this is ``u' '``.
|
:param separator: The separator for the elements. For lines this is ``'<br>'`` and for words this is ``' '``.
|
||||||
:param line_end: The text added after each "element line". Either ``u' '`` or ``u'<br>``. This is needed for
|
:param line_end: The text added after each "element line". Either ``' '`` or ``'<br>``. This is needed for
|
||||||
bibles.
|
bibles.
|
||||||
"""
|
"""
|
||||||
smallest_index = 0
|
smallest_index = 0
|
||||||
|
@ -63,8 +63,7 @@ class ScreenList(object):
|
|||||||
"""
|
"""
|
||||||
Initialise the screen list.
|
Initialise the screen list.
|
||||||
|
|
||||||
``desktop``
|
:param desktop: A QDesktopWidget object.
|
||||||
A ``QDesktopWidget`` object.
|
|
||||||
"""
|
"""
|
||||||
screen_list = cls()
|
screen_list = cls()
|
||||||
screen_list.desktop = desktop
|
screen_list.desktop = desktop
|
||||||
@ -136,7 +135,7 @@ class ScreenList(object):
|
|||||||
Returns a list with the screens. This should only be used to display
|
Returns a list with the screens. This should only be used to display
|
||||||
available screens to the user::
|
available screens to the user::
|
||||||
|
|
||||||
[u'Screen 1 (primary)', u'Screen 2']
|
['Screen 1 (primary)', 'Screen 2']
|
||||||
"""
|
"""
|
||||||
screen_list = []
|
screen_list = []
|
||||||
for screen in self.screen_list:
|
for screen in self.screen_list:
|
||||||
@ -153,9 +152,9 @@ class ScreenList(object):
|
|||||||
:param screen: A dict with the screen properties::
|
:param screen: A dict with the screen properties::
|
||||||
|
|
||||||
{
|
{
|
||||||
u'primary': True,
|
'primary': True,
|
||||||
u'number': 0,
|
'number': 0,
|
||||||
u'size': PyQt4.QtCore.QRect(0, 0, 1024, 768)
|
'size': PyQt4.QtCore.QRect(0, 0, 1024, 768)
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
log.info('Screen %d found with resolution %s' % (screen['number'], screen['size']))
|
log.info('Screen %d found with resolution %s' % (screen['number'], screen['size']))
|
||||||
|
@ -44,7 +44,7 @@ class Ui_AboutDialog(object):
|
|||||||
Set up the UI for the dialog.
|
Set up the UI for the dialog.
|
||||||
"""
|
"""
|
||||||
about_dialog.setObjectName('about_dialog')
|
about_dialog.setObjectName('about_dialog')
|
||||||
about_dialog.setWindowIcon(build_icon(':/icon/openlp-logo-16x16.png'))
|
about_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg'))
|
||||||
self.about_dialog_layout = QtGui.QVBoxLayout(about_dialog)
|
self.about_dialog_layout = QtGui.QVBoxLayout(about_dialog)
|
||||||
self.about_dialog_layout.setObjectName('about_dialog_layout')
|
self.about_dialog_layout.setObjectName('about_dialog_layout')
|
||||||
self.logo_label = QtGui.QLabel(about_dialog)
|
self.logo_label = QtGui.QLabel(about_dialog)
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
The About dialog.
|
The About dialog.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from PyQt4 import QtCore, QtGui
|
from PyQt4 import QtGui
|
||||||
|
|
||||||
from .aboutdialog import Ui_AboutDialog
|
from .aboutdialog import Ui_AboutDialog
|
||||||
from openlp.core.lib import translate
|
from openlp.core.lib import translate
|
||||||
|
@ -30,9 +30,9 @@
|
|||||||
The GUI widgets of the exception dialog.
|
The GUI widgets of the exception dialog.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from PyQt4 import QtCore, QtGui
|
from PyQt4 import QtGui
|
||||||
|
|
||||||
from openlp.core.lib import translate
|
from openlp.core.lib import translate, build_icon
|
||||||
from openlp.core.lib.ui import create_button, create_button_box
|
from openlp.core.lib.ui import create_button, create_button_box
|
||||||
|
|
||||||
|
|
||||||
@ -45,6 +45,7 @@ class Ui_ExceptionDialog(object):
|
|||||||
Set up the UI.
|
Set up the UI.
|
||||||
"""
|
"""
|
||||||
exception_dialog.setObjectName('exception_dialog')
|
exception_dialog.setObjectName('exception_dialog')
|
||||||
|
exception_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
|
||||||
self.exception_layout = QtGui.QVBoxLayout(exception_dialog)
|
self.exception_layout = QtGui.QVBoxLayout(exception_dialog)
|
||||||
self.exception_layout.setObjectName('exception_layout')
|
self.exception_layout.setObjectName('exception_layout')
|
||||||
self.message_layout = QtGui.QHBoxLayout()
|
self.message_layout = QtGui.QHBoxLayout()
|
||||||
|
@ -31,7 +31,7 @@ The UI widgets for the rename dialog
|
|||||||
"""
|
"""
|
||||||
from PyQt4 import QtCore, QtGui
|
from PyQt4 import QtCore, QtGui
|
||||||
|
|
||||||
from openlp.core.lib import translate
|
from openlp.core.lib import translate, build_icon
|
||||||
from openlp.core.lib.ui import create_button_box
|
from openlp.core.lib.ui import create_button_box
|
||||||
|
|
||||||
|
|
||||||
@ -44,6 +44,7 @@ class Ui_FileRenameDialog(object):
|
|||||||
Set up the UI
|
Set up the UI
|
||||||
"""
|
"""
|
||||||
file_rename_dialog.setObjectName('file_rename_dialog')
|
file_rename_dialog.setObjectName('file_rename_dialog')
|
||||||
|
file_rename_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
|
||||||
file_rename_dialog.resize(300, 10)
|
file_rename_dialog.resize(300, 10)
|
||||||
self.dialog_layout = QtGui.QGridLayout(file_rename_dialog)
|
self.dialog_layout = QtGui.QGridLayout(file_rename_dialog)
|
||||||
self.dialog_layout.setObjectName('dialog_layout')
|
self.dialog_layout.setObjectName('dialog_layout')
|
||||||
|
@ -114,10 +114,10 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
Run the wizard.
|
Run the wizard.
|
||||||
"""
|
"""
|
||||||
self.setDefaults()
|
self.set_defaults()
|
||||||
return QtGui.QWizard.exec_(self)
|
return QtGui.QWizard.exec_(self)
|
||||||
|
|
||||||
def setDefaults(self):
|
def set_defaults(self):
|
||||||
"""
|
"""
|
||||||
Set up display at start of theme edit.
|
Set up display at start of theme edit.
|
||||||
"""
|
"""
|
||||||
@ -199,8 +199,8 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard, RegistryProperties):
|
|||||||
self.no_internet_label.setText(self.no_internet_text + self.cancelWizardText)
|
self.no_internet_label.setText(self.no_internet_text + self.cancelWizardText)
|
||||||
elif page_id == FirstTimePage.Defaults:
|
elif page_id == FirstTimePage.Defaults:
|
||||||
self.theme_combo_box.clear()
|
self.theme_combo_box.clear()
|
||||||
for iter in range(self.themes_list_widget.count()):
|
for index in range(self.themes_list_widget.count()):
|
||||||
item = self.themes_list_widget.item(iter)
|
item = self.themes_list_widget.item(index)
|
||||||
if item.checkState() == QtCore.Qt.Checked:
|
if item.checkState() == QtCore.Qt.Checked:
|
||||||
self.theme_combo_box.addItem(item.text())
|
self.theme_combo_box.addItem(item.text())
|
||||||
if self.has_run_wizard:
|
if self.has_run_wizard:
|
||||||
@ -292,13 +292,9 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
themes = self.config.get('themes', 'files')
|
themes = self.config.get('themes', 'files')
|
||||||
themes = themes.split(',')
|
themes = themes.split(',')
|
||||||
for theme in themes:
|
for index, theme in enumerate(themes):
|
||||||
filename = self.config.get('theme_%s' % theme, 'filename')
|
|
||||||
screenshot = self.config.get('theme_%s' % theme, 'screenshot')
|
screenshot = self.config.get('theme_%s' % theme, 'screenshot')
|
||||||
for index in range(self.themes_list_widget.count()):
|
|
||||||
item = self.themes_list_widget.item(index)
|
item = self.themes_list_widget.item(index)
|
||||||
if item.data(QtCore.Qt.UserRole) == filename:
|
|
||||||
break
|
|
||||||
item.setIcon(build_icon(os.path.join(gettempdir(), 'openlp', screenshot)))
|
item.setIcon(build_icon(os.path.join(gettempdir(), 'openlp', screenshot)))
|
||||||
|
|
||||||
def _get_file_size(self, url):
|
def _get_file_size(self, url):
|
||||||
|
@ -32,6 +32,7 @@ The UI widgets of the language selection dialog.
|
|||||||
from PyQt4 import QtGui
|
from PyQt4 import QtGui
|
||||||
|
|
||||||
from openlp.core.common import translate
|
from openlp.core.common import translate
|
||||||
|
from openlp.core.lib import build_icon
|
||||||
from openlp.core.lib.ui import create_button_box
|
from openlp.core.lib.ui import create_button_box
|
||||||
|
|
||||||
|
|
||||||
@ -44,6 +45,7 @@ class Ui_FirstTimeLanguageDialog(object):
|
|||||||
Set up the UI.
|
Set up the UI.
|
||||||
"""
|
"""
|
||||||
language_dialog.setObjectName('language_dialog')
|
language_dialog.setObjectName('language_dialog')
|
||||||
|
language_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
|
||||||
language_dialog.resize(300, 50)
|
language_dialog.resize(300, 50)
|
||||||
self.dialog_layout = QtGui.QVBoxLayout(language_dialog)
|
self.dialog_layout = QtGui.QVBoxLayout(language_dialog)
|
||||||
self.dialog_layout.setContentsMargins(8, 8, 8, 8)
|
self.dialog_layout.setContentsMargins(8, 8, 8, 8)
|
||||||
|
@ -34,6 +34,7 @@ from PyQt4 import QtCore, QtGui
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
from openlp.core.common import translate
|
from openlp.core.common import translate
|
||||||
|
from openlp.core.lib import build_icon
|
||||||
from openlp.core.lib.ui import add_welcome_page
|
from openlp.core.lib.ui import add_welcome_page
|
||||||
|
|
||||||
|
|
||||||
@ -60,6 +61,7 @@ class Ui_FirstTimeWizard(object):
|
|||||||
Set up the UI.
|
Set up the UI.
|
||||||
"""
|
"""
|
||||||
first_time_wizard.setObjectName('first_time_wizard')
|
first_time_wizard.setObjectName('first_time_wizard')
|
||||||
|
first_time_wizard.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
|
||||||
first_time_wizard.resize(550, 386)
|
first_time_wizard.resize(550, 386)
|
||||||
first_time_wizard.setModal(True)
|
first_time_wizard.setModal(True)
|
||||||
first_time_wizard.setWizardStyle(QtGui.QWizard.ModernStyle)
|
first_time_wizard.setWizardStyle(QtGui.QWizard.ModernStyle)
|
||||||
|
@ -45,6 +45,7 @@ class Ui_FormattingTagDialog(object):
|
|||||||
Set up the UI
|
Set up the UI
|
||||||
"""
|
"""
|
||||||
formatting_tag_dialog.setObjectName('formatting_tag_dialog')
|
formatting_tag_dialog.setObjectName('formatting_tag_dialog')
|
||||||
|
formatting_tag_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
|
||||||
formatting_tag_dialog.resize(725, 548)
|
formatting_tag_dialog.resize(725, 548)
|
||||||
self.list_data_grid_layout = QtGui.QVBoxLayout(formatting_tag_dialog)
|
self.list_data_grid_layout = QtGui.QVBoxLayout(formatting_tag_dialog)
|
||||||
self.list_data_grid_layout.setMargin(8)
|
self.list_data_grid_layout.setMargin(8)
|
||||||
|
@ -89,7 +89,7 @@ class Ui_MainWindow(object):
|
|||||||
Set up the user interface
|
Set up the user interface
|
||||||
"""
|
"""
|
||||||
main_window.setObjectName('MainWindow')
|
main_window.setObjectName('MainWindow')
|
||||||
main_window.setWindowIcon(build_icon(':/icon/openlp-logo-64x64.png'))
|
main_window.setWindowIcon(build_icon(':/icon/openlp-logo.svg'))
|
||||||
main_window.setDockNestingEnabled(True)
|
main_window.setDockNestingEnabled(True)
|
||||||
# Set up the main container, which contains all the other form widgets.
|
# Set up the main container, which contains all the other form widgets.
|
||||||
self.main_content = QtGui.QWidget(main_window)
|
self.main_content = QtGui.QWidget(main_window)
|
||||||
@ -320,12 +320,12 @@ class Ui_MainWindow(object):
|
|||||||
# i18n add Language Actions
|
# i18n add Language Actions
|
||||||
add_actions(self.settings_language_menu, (self.auto_language_item, None))
|
add_actions(self.settings_language_menu, (self.auto_language_item, None))
|
||||||
add_actions(self.settings_language_menu, self.language_group.actions())
|
add_actions(self.settings_language_menu, self.language_group.actions())
|
||||||
# Order things differently in OS X so that Preferences menu item in the
|
# Qt on OS X looks for keywords in the menu items title to determine which menu items get added to the main
|
||||||
# app menu is correct (this gets picked up automatically by Qt).
|
# menu. If we are running on Mac OS X the menu items whose title contains those keywords but don't belong in the
|
||||||
|
# main menu need to be marked as such with QAction.NoRole.
|
||||||
if sys.platform == 'darwin':
|
if sys.platform == 'darwin':
|
||||||
add_actions(self.settings_menu, (self.settings_plugin_list_item, self.settings_language_menu.menuAction(),
|
self.settings_shortcuts_item.setMenuRole(QtGui.QAction.NoRole)
|
||||||
None, self.settings_configure_item, self.settings_shortcuts_item, self.formatting_tag_item))
|
self.formatting_tag_item.setMenuRole(QtGui.QAction.NoRole)
|
||||||
else:
|
|
||||||
add_actions(self.settings_menu, (self.settings_plugin_list_item, self.settings_language_menu.menuAction(),
|
add_actions(self.settings_menu, (self.settings_plugin_list_item, self.settings_language_menu.menuAction(),
|
||||||
None, self.formatting_tag_item, self.settings_shortcuts_item, self.settings_configure_item))
|
None, self.formatting_tag_item, self.settings_shortcuts_item, self.settings_configure_item))
|
||||||
add_actions(self.tools_menu, (self.tools_add_tool_item, None))
|
add_actions(self.tools_menu, (self.tools_add_tool_item, None))
|
||||||
@ -1334,7 +1334,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow, RegistryProperties):
|
|||||||
if self.copy_data:
|
if self.copy_data:
|
||||||
log.info('Copying data to new path')
|
log.info('Copying data to new path')
|
||||||
try:
|
try:
|
||||||
self.showStatusMessage(
|
self.show_status_message(
|
||||||
translate('OpenLP.MainWindow', 'Copying OpenLP data to new data directory location - %s '
|
translate('OpenLP.MainWindow', 'Copying OpenLP data to new data directory location - %s '
|
||||||
'- Please wait for copy to finish').replace('%s', self.new_data_path))
|
'- Please wait for copy to finish').replace('%s', self.new_data_path))
|
||||||
dir_util.copy_tree(old_data_path, self.new_data_path)
|
dir_util.copy_tree(old_data_path, self.new_data_path)
|
||||||
@ -1364,8 +1364,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow, RegistryProperties):
|
|||||||
args = []
|
args = []
|
||||||
for a in self.arguments:
|
for a in self.arguments:
|
||||||
args.extend([a])
|
args.extend([a])
|
||||||
for arg in args:
|
for filename in args:
|
||||||
filename = arg
|
|
||||||
if not isinstance(filename, str):
|
if not isinstance(filename, str):
|
||||||
filename = str(filename, sys.getfilesystemencoding())
|
filename = str(filename, sys.getfilesystemencoding())
|
||||||
if filename.endswith(('.osz', '.oszl')):
|
if filename.endswith(('.osz', '.oszl')):
|
||||||
|
@ -29,8 +29,6 @@
|
|||||||
"""
|
"""
|
||||||
The :mod:`~openlp.core.ui.media.mediaplayer` module contains the MediaPlayer class.
|
The :mod:`~openlp.core.ui.media.mediaplayer` module contains the MediaPlayer class.
|
||||||
"""
|
"""
|
||||||
import os
|
|
||||||
|
|
||||||
from openlp.core.common import RegistryProperties
|
from openlp.core.common import RegistryProperties
|
||||||
from openlp.core.ui.media import MediaState
|
from openlp.core.ui.media import MediaState
|
||||||
|
|
||||||
|
@ -33,10 +33,8 @@ import logging
|
|||||||
import mimetypes
|
import mimetypes
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from PyQt4 import QtGui
|
|
||||||
from PyQt4.phonon import Phonon
|
from PyQt4.phonon import Phonon
|
||||||
|
|
||||||
from openlp.core.common import Settings
|
|
||||||
from openlp.core.lib import translate
|
from openlp.core.lib import translate
|
||||||
|
|
||||||
from openlp.core.ui.media import MediaState
|
from openlp.core.ui.media import MediaState
|
||||||
|
@ -34,6 +34,7 @@ from distutils.version import LooseVersion
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import threading
|
||||||
|
|
||||||
from PyQt4 import QtGui
|
from PyQt4 import QtGui
|
||||||
|
|
||||||
@ -206,7 +207,9 @@ class VlcPlayer(MediaPlayer):
|
|||||||
controller = display.controller
|
controller = display.controller
|
||||||
start_time = 0
|
start_time = 0
|
||||||
log.debug('vlc play')
|
log.debug('vlc play')
|
||||||
display.vlc_media_player.play()
|
if self.state != MediaState.Paused and controller.media_info.start_time > 0:
|
||||||
|
start_time = controller.media_info.start_time
|
||||||
|
threading.Thread(target=display.vlc_media_player.play).start()
|
||||||
if not self.media_state_wait(display, vlc.State.Playing):
|
if not self.media_state_wait(display, vlc.State.Playing):
|
||||||
return False
|
return False
|
||||||
if self.state != MediaState.Paused and controller.media_info.start_time > 0:
|
if self.state != MediaState.Paused and controller.media_info.start_time > 0:
|
||||||
@ -254,7 +257,7 @@ class VlcPlayer(MediaPlayer):
|
|||||||
"""
|
"""
|
||||||
Stop the current item
|
Stop the current item
|
||||||
"""
|
"""
|
||||||
display.vlc_media_player.stop()
|
threading.Thread(target=display.vlc_media_player.stop).start()
|
||||||
self.state = MediaState.Stopped
|
self.state = MediaState.Stopped
|
||||||
|
|
||||||
def volume(self, display, vol):
|
def volume(self, display, vol):
|
||||||
|
@ -32,6 +32,7 @@ The UI widgets of the plugin view dialog
|
|||||||
from PyQt4 import QtCore, QtGui
|
from PyQt4 import QtCore, QtGui
|
||||||
|
|
||||||
from openlp.core.common import UiStrings, translate
|
from openlp.core.common import UiStrings, translate
|
||||||
|
from openlp.core.lib import build_icon
|
||||||
from openlp.core.lib.ui import create_button_box
|
from openlp.core.lib.ui import create_button_box
|
||||||
|
|
||||||
|
|
||||||
@ -44,6 +45,7 @@ class Ui_PluginViewDialog(object):
|
|||||||
Set up the UI
|
Set up the UI
|
||||||
"""
|
"""
|
||||||
pluginViewDialog.setObjectName('pluginViewDialog')
|
pluginViewDialog.setObjectName('pluginViewDialog')
|
||||||
|
pluginViewDialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
|
||||||
pluginViewDialog.setWindowModality(QtCore.Qt.ApplicationModal)
|
pluginViewDialog.setWindowModality(QtCore.Qt.ApplicationModal)
|
||||||
self.plugin_layout = QtGui.QVBoxLayout(pluginViewDialog)
|
self.plugin_layout = QtGui.QVBoxLayout(pluginViewDialog)
|
||||||
self.plugin_layout.setObjectName('plugin_layout')
|
self.plugin_layout.setObjectName('plugin_layout')
|
||||||
|
@ -30,7 +30,6 @@
|
|||||||
The actual plugin view form
|
The actual plugin view form
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
|
|
||||||
from PyQt4 import QtGui
|
from PyQt4 import QtGui
|
||||||
|
|
||||||
|
@ -56,6 +56,7 @@ class Ui_PrintServiceDialog(object):
|
|||||||
Set up the UI
|
Set up the UI
|
||||||
"""
|
"""
|
||||||
print_service_dialog.setObjectName('print_service_dialog')
|
print_service_dialog.setObjectName('print_service_dialog')
|
||||||
|
print_service_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
|
||||||
print_service_dialog.resize(664, 594)
|
print_service_dialog.resize(664, 594)
|
||||||
self.main_layout = QtGui.QVBoxLayout(print_service_dialog)
|
self.main_layout = QtGui.QVBoxLayout(print_service_dialog)
|
||||||
self.main_layout.setSpacing(0)
|
self.main_layout.setSpacing(0)
|
||||||
|
@ -242,7 +242,7 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog, RegistryProperties)
|
|||||||
Creates a html element. If ``text`` is given, the element's text will set and if a ``parent`` is given,
|
Creates a html element. If ``text`` is given, the element's text will set and if a ``parent`` is given,
|
||||||
the element is appended.
|
the element is appended.
|
||||||
|
|
||||||
:param tag: The html tag, e. g. ``u'span'``. Defaults to ``None``.
|
:param tag: The html tag, e. g. ``'span'``. Defaults to ``None``.
|
||||||
:param text: The text for the tag. Defaults to ``None``.
|
:param text: The text for the tag. Defaults to ``None``.
|
||||||
:param parent: The parent element. Defaults to ``None``.
|
:param parent: The parent element. Defaults to ``None``.
|
||||||
:param classId: Value for the class attribute
|
:param classId: Value for the class attribute
|
||||||
|
@ -32,6 +32,7 @@ The UI widgets for the service item edit dialog
|
|||||||
from PyQt4 import QtGui
|
from PyQt4 import QtGui
|
||||||
|
|
||||||
from openlp.core.common import translate
|
from openlp.core.common import translate
|
||||||
|
from openlp.core.lib import build_icon
|
||||||
from openlp.core.lib.ui import create_button_box, create_button
|
from openlp.core.lib.ui import create_button_box, create_button
|
||||||
|
|
||||||
|
|
||||||
@ -44,6 +45,7 @@ class Ui_ServiceItemEditDialog(object):
|
|||||||
Set up the UI
|
Set up the UI
|
||||||
"""
|
"""
|
||||||
serviceItemEditDialog.setObjectName('serviceItemEditDialog')
|
serviceItemEditDialog.setObjectName('serviceItemEditDialog')
|
||||||
|
serviceItemEditDialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
|
||||||
self.dialog_layout = QtGui.QGridLayout(serviceItemEditDialog)
|
self.dialog_layout = QtGui.QGridLayout(serviceItemEditDialog)
|
||||||
self.dialog_layout.setContentsMargins(8, 8, 8, 8)
|
self.dialog_layout.setContentsMargins(8, 8, 8, 8)
|
||||||
self.dialog_layout.setSpacing(8)
|
self.dialog_layout.setSpacing(8)
|
||||||
|
@ -45,8 +45,8 @@ class Ui_SettingsDialog(object):
|
|||||||
Set up the UI
|
Set up the UI
|
||||||
"""
|
"""
|
||||||
settings_dialog.setObjectName('settings_dialog')
|
settings_dialog.setObjectName('settings_dialog')
|
||||||
|
settings_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
|
||||||
settings_dialog.resize(800, 500)
|
settings_dialog.resize(800, 500)
|
||||||
settings_dialog.setWindowIcon(build_icon(':/system/system_settings.png'))
|
|
||||||
self.dialog_layout = QtGui.QGridLayout(settings_dialog)
|
self.dialog_layout = QtGui.QGridLayout(settings_dialog)
|
||||||
self.dialog_layout.setObjectName('dialog_layout')
|
self.dialog_layout.setObjectName('dialog_layout')
|
||||||
self.dialog_layout.setMargin(8)
|
self.dialog_layout.setMargin(8)
|
||||||
|
@ -66,6 +66,7 @@ class Ui_ShortcutListDialog(object):
|
|||||||
Set up the UI
|
Set up the UI
|
||||||
"""
|
"""
|
||||||
shortcutListDialog.setObjectName('shortcutListDialog')
|
shortcutListDialog.setObjectName('shortcutListDialog')
|
||||||
|
shortcutListDialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
|
||||||
shortcutListDialog.resize(500, 438)
|
shortcutListDialog.resize(500, 438)
|
||||||
self.shortcut_list_layout = QtGui.QVBoxLayout(shortcutListDialog)
|
self.shortcut_list_layout = QtGui.QVBoxLayout(shortcutListDialog)
|
||||||
self.shortcut_list_layout.setObjectName('shortcut_list_layout')
|
self.shortcut_list_layout.setObjectName('shortcut_list_layout')
|
||||||
|
@ -244,10 +244,10 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog, RegistryProperties)
|
|||||||
self.primary_push_button.setChecked(False)
|
self.primary_push_button.setChecked(False)
|
||||||
self.alternate_push_button.setChecked(False)
|
self.alternate_push_button.setChecked(False)
|
||||||
else:
|
else:
|
||||||
if action.defaultShortcuts:
|
if action.default_shortcuts:
|
||||||
primary_label_text = action.defaultShortcuts[0].toString()
|
primary_label_text = action.default_shortcuts[0].toString()
|
||||||
if len(action.defaultShortcuts) == 2:
|
if len(action.default_shortcuts) == 2:
|
||||||
alternate_label_text = action.defaultShortcuts[1].toString()
|
alternate_label_text = action.default_shortcuts[1].toString()
|
||||||
shortcuts = self._action_shortcuts(action)
|
shortcuts = self._action_shortcuts(action)
|
||||||
# We do not want to loose pending changes, that is why we have to keep the text when, this function has not
|
# 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.
|
# been triggered by a signal.
|
||||||
@ -292,7 +292,7 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog, RegistryProperties)
|
|||||||
self._adjust_button(self.alternate_push_button, False, text='')
|
self._adjust_button(self.alternate_push_button, False, text='')
|
||||||
for category in self.action_list.categories:
|
for category in self.action_list.categories:
|
||||||
for action in category.actions:
|
for action in category.actions:
|
||||||
self.changed_actions[action] = action.defaultShortcuts
|
self.changed_actions[action] = action.default_shortcuts
|
||||||
self.refresh_shortcut_list()
|
self.refresh_shortcut_list()
|
||||||
|
|
||||||
def on_default_radio_button_clicked(self, toggled):
|
def on_default_radio_button_clicked(self, toggled):
|
||||||
@ -306,7 +306,7 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog, RegistryProperties)
|
|||||||
if action is None:
|
if action is None:
|
||||||
return
|
return
|
||||||
temp_shortcuts = self._action_shortcuts(action)
|
temp_shortcuts = self._action_shortcuts(action)
|
||||||
self.changed_actions[action] = action.defaultShortcuts
|
self.changed_actions[action] = action.default_shortcuts
|
||||||
self.refresh_shortcut_list()
|
self.refresh_shortcut_list()
|
||||||
primary_button_text = ''
|
primary_button_text = ''
|
||||||
alternate_button_text = ''
|
alternate_button_text = ''
|
||||||
@ -357,8 +357,8 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog, RegistryProperties)
|
|||||||
return
|
return
|
||||||
shortcuts = self._action_shortcuts(action)
|
shortcuts = self._action_shortcuts(action)
|
||||||
new_shortcuts = []
|
new_shortcuts = []
|
||||||
if action.defaultShortcuts:
|
if action.default_shortcuts:
|
||||||
new_shortcuts.append(action.defaultShortcuts[0])
|
new_shortcuts.append(action.default_shortcuts[0])
|
||||||
# We have to check if the primary default shortcut is available. But we only have to check, if the action
|
# We have to check if the primary default shortcut is available. But we only have to check, if the action
|
||||||
# has a default primary shortcut (an "empty" shortcut is always valid and if the action does not have a
|
# has a default primary shortcut (an "empty" shortcut is always valid and if the action does not have a
|
||||||
# default primary shortcut, then the alternative shortcut (not the default one) will become primary
|
# default primary shortcut, then the alternative shortcut (not the default one) will become primary
|
||||||
@ -383,8 +383,8 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog, RegistryProperties)
|
|||||||
new_shortcuts = []
|
new_shortcuts = []
|
||||||
if shortcuts:
|
if shortcuts:
|
||||||
new_shortcuts.append(shortcuts[0])
|
new_shortcuts.append(shortcuts[0])
|
||||||
if len(action.defaultShortcuts) == 2:
|
if len(action.default_shortcuts) == 2:
|
||||||
new_shortcuts.append(action.defaultShortcuts[1])
|
new_shortcuts.append(action.default_shortcuts[1])
|
||||||
if len(new_shortcuts) == 2:
|
if len(new_shortcuts) == 2:
|
||||||
if not self._validiate_shortcut(action, new_shortcuts[1]):
|
if not self._validiate_shortcut(action, new_shortcuts[1]):
|
||||||
return
|
return
|
||||||
|
@ -32,6 +32,7 @@ The UI widgets for the time dialog
|
|||||||
from PyQt4 import QtCore, QtGui
|
from PyQt4 import QtCore, QtGui
|
||||||
|
|
||||||
from openlp.core.common import UiStrings, translate
|
from openlp.core.common import UiStrings, translate
|
||||||
|
from openlp.core.lib import build_icon
|
||||||
from openlp.core.lib.ui import create_button_box
|
from openlp.core.lib.ui import create_button_box
|
||||||
|
|
||||||
|
|
||||||
@ -44,6 +45,7 @@ class Ui_StartTimeDialog(object):
|
|||||||
Set up the UI
|
Set up the UI
|
||||||
"""
|
"""
|
||||||
StartTimeDialog.setObjectName('StartTimeDialog')
|
StartTimeDialog.setObjectName('StartTimeDialog')
|
||||||
|
StartTimeDialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
|
||||||
StartTimeDialog.resize(350, 10)
|
StartTimeDialog.resize(350, 10)
|
||||||
self.dialog_layout = QtGui.QGridLayout(StartTimeDialog)
|
self.dialog_layout = QtGui.QGridLayout(StartTimeDialog)
|
||||||
self.dialog_layout.setObjectName('dialog_layout')
|
self.dialog_layout.setObjectName('dialog_layout')
|
||||||
|
@ -90,7 +90,7 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard, RegistryProperties):
|
|||||||
self.footer_font_combo_box.activated.connect(self.update_theme)
|
self.footer_font_combo_box.activated.connect(self.update_theme)
|
||||||
self.footer_size_spin_box.valueChanged.connect(self.update_theme)
|
self.footer_size_spin_box.valueChanged.connect(self.update_theme)
|
||||||
|
|
||||||
def setDefaults(self):
|
def set_defaults(self):
|
||||||
"""
|
"""
|
||||||
Set up display at start of theme edit.
|
Set up display at start of theme edit.
|
||||||
"""
|
"""
|
||||||
@ -261,7 +261,7 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard, RegistryProperties):
|
|||||||
log.debug('Editing theme %s' % self.theme.theme_name)
|
log.debug('Editing theme %s' % self.theme.theme_name)
|
||||||
self.temp_background_filename = ''
|
self.temp_background_filename = ''
|
||||||
self.update_theme_allowed = False
|
self.update_theme_allowed = False
|
||||||
self.setDefaults()
|
self.set_defaults()
|
||||||
self.update_theme_allowed = True
|
self.update_theme_allowed = True
|
||||||
self.theme_name_label.setVisible(not edit)
|
self.theme_name_label.setVisible(not edit)
|
||||||
self.theme_name_edit.setVisible(not edit)
|
self.theme_name_edit.setVisible(not edit)
|
||||||
|
@ -32,6 +32,7 @@ The layout of the theme
|
|||||||
from PyQt4 import QtGui
|
from PyQt4 import QtGui
|
||||||
|
|
||||||
from openlp.core.common import translate
|
from openlp.core.common import translate
|
||||||
|
from openlp.core.lib import build_icon
|
||||||
from openlp.core.lib.ui import create_button_box
|
from openlp.core.lib.ui import create_button_box
|
||||||
|
|
||||||
|
|
||||||
@ -44,6 +45,7 @@ class Ui_ThemeLayoutDialog(object):
|
|||||||
Set up the UI
|
Set up the UI
|
||||||
"""
|
"""
|
||||||
themeLayoutDialog.setObjectName('themeLayoutDialogDialog')
|
themeLayoutDialog.setObjectName('themeLayoutDialogDialog')
|
||||||
|
themeLayoutDialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
|
||||||
self.preview_layout = QtGui.QVBoxLayout(themeLayoutDialog)
|
self.preview_layout = QtGui.QVBoxLayout(themeLayoutDialog)
|
||||||
self.preview_layout.setObjectName('preview_layout')
|
self.preview_layout.setObjectName('preview_layout')
|
||||||
self.preview_area = QtGui.QWidget(themeLayoutDialog)
|
self.preview_area = QtGui.QWidget(themeLayoutDialog)
|
||||||
|
@ -190,7 +190,7 @@ class ThemesTab(SettingsTab):
|
|||||||
|
|
||||||
:param theme_list: The list of available themes::
|
:param theme_list: The list of available themes::
|
||||||
|
|
||||||
[u'Bible Theme', u'Song Theme']
|
['Bible Theme', 'Song Theme']
|
||||||
"""
|
"""
|
||||||
# Reload as may have been triggered by the ThemeManager.
|
# Reload as may have been triggered by the ThemeManager.
|
||||||
self.global_theme = Settings().value(self.settings_section + '/global theme')
|
self.global_theme = Settings().value(self.settings_section + '/global theme')
|
||||||
|
@ -46,6 +46,7 @@ class Ui_ThemeWizard(object):
|
|||||||
Set up the UI
|
Set up the UI
|
||||||
"""
|
"""
|
||||||
themeWizard.setObjectName('OpenLP.ThemeWizard')
|
themeWizard.setObjectName('OpenLP.ThemeWizard')
|
||||||
|
themeWizard.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
|
||||||
themeWizard.setModal(True)
|
themeWizard.setModal(True)
|
||||||
themeWizard.setWizardStyle(QtGui.QWizard.ModernStyle)
|
themeWizard.setWizardStyle(QtGui.QWizard.ModernStyle)
|
||||||
themeWizard.setOptions(QtGui.QWizard.IndependentPages |
|
themeWizard.setOptions(QtGui.QWizard.IndependentPages |
|
||||||
|
@ -118,6 +118,7 @@ class OpenLPWizard(QtGui.QWizard, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
Set up the wizard UI.
|
Set up the wizard UI.
|
||||||
"""
|
"""
|
||||||
|
self.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
|
||||||
self.setModal(True)
|
self.setModal(True)
|
||||||
self.setWizardStyle(QtGui.QWizard.ModernStyle)
|
self.setWizardStyle(QtGui.QWizard.ModernStyle)
|
||||||
self.setOptions(QtGui.QWizard.IndependentPages |
|
self.setOptions(QtGui.QWizard.IndependentPages |
|
||||||
@ -197,7 +198,7 @@ class OpenLPWizard(QtGui.QWizard, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
Run the wizard.
|
Run the wizard.
|
||||||
"""
|
"""
|
||||||
self.setDefaults()
|
self.set_defaults()
|
||||||
return QtGui.QWizard.exec_(self)
|
return QtGui.QWizard.exec_(self)
|
||||||
|
|
||||||
def reject(self):
|
def reject(self):
|
||||||
@ -279,7 +280,7 @@ class OpenLPWizard(QtGui.QWizard, RegistryProperties):
|
|||||||
:param filters: The file extension filters. It should contain the file description
|
:param filters: The file extension filters. It should contain the file description
|
||||||
as well as the file extension. For example::
|
as well as the file extension. For example::
|
||||||
|
|
||||||
u'OpenLP 2.0 Databases (*.sqlite)'
|
'OpenLP 2.0 Databases (*.sqlite)'
|
||||||
"""
|
"""
|
||||||
if filters:
|
if filters:
|
||||||
filters += ';;'
|
filters += ';;'
|
||||||
|
@ -113,7 +113,7 @@ def get_application_version():
|
|||||||
"""
|
"""
|
||||||
Returns the application version of the running instance of OpenLP::
|
Returns the application version of the running instance of OpenLP::
|
||||||
|
|
||||||
{u'full': u'1.9.4-bzr1249', u'version': u'1.9.4', u'build': u'bzr1249'}
|
{'full': '1.9.4-bzr1249', 'version': '1.9.4', 'build': 'bzr1249'}
|
||||||
"""
|
"""
|
||||||
global APPLICATION_VERSION
|
global APPLICATION_VERSION
|
||||||
if APPLICATION_VERSION:
|
if APPLICATION_VERSION:
|
||||||
|
@ -65,20 +65,14 @@ class CategoryActionList(object):
|
|||||||
self.index = 0
|
self.index = 0
|
||||||
self.actions = []
|
self.actions = []
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __contains__(self, key):
|
||||||
"""
|
|
||||||
Implement the __getitem__() method to make this class a dictionary type
|
|
||||||
"""
|
|
||||||
for weight, action in self.actions:
|
|
||||||
if action.text() == key:
|
|
||||||
return action
|
|
||||||
raise KeyError('Action "%s" does not exist.' % key)
|
|
||||||
|
|
||||||
def __contains__(self, item):
|
|
||||||
"""
|
"""
|
||||||
Implement the __contains__() method to make this class a dictionary type
|
Implement the __contains__() method to make this class a dictionary type
|
||||||
"""
|
"""
|
||||||
return item in self
|
for weight, action in self.actions:
|
||||||
|
if action == key:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
"""
|
"""
|
||||||
@ -103,23 +97,14 @@ class CategoryActionList(object):
|
|||||||
self.index += 1
|
self.index += 1
|
||||||
return self.actions[self.index - 1][1]
|
return self.actions[self.index - 1][1]
|
||||||
|
|
||||||
def has_key(self, key):
|
def append(self, action):
|
||||||
"""
|
|
||||||
Implement the has_key() method to make this class a dictionary type
|
|
||||||
"""
|
|
||||||
for weight, action in self.actions:
|
|
||||||
if action.text() == key:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def append(self, name):
|
|
||||||
"""
|
"""
|
||||||
Append an action
|
Append an action
|
||||||
"""
|
"""
|
||||||
weight = 0
|
weight = 0
|
||||||
if self.actions:
|
if self.actions:
|
||||||
weight = self.actions[-1][0] + 1
|
weight = self.actions[-1][0] + 1
|
||||||
self.add(name, weight)
|
self.add(action, weight)
|
||||||
|
|
||||||
def add(self, action, weight=0):
|
def add(self, action, weight=0):
|
||||||
"""
|
"""
|
||||||
@ -128,14 +113,15 @@ class CategoryActionList(object):
|
|||||||
self.actions.append((weight, action))
|
self.actions.append((weight, action))
|
||||||
self.actions.sort(key=lambda act: act[0])
|
self.actions.sort(key=lambda act: act[0])
|
||||||
|
|
||||||
def remove(self, remove_action):
|
def remove(self, action):
|
||||||
"""
|
"""
|
||||||
Remove an action
|
Remove an action
|
||||||
"""
|
"""
|
||||||
for action in self.actions:
|
for item in self.actions:
|
||||||
if action[1] == remove_action:
|
if item[1] == action:
|
||||||
self.actions.remove(action)
|
self.actions.remove(item)
|
||||||
return
|
return
|
||||||
|
raise ValueError('Action "%s" does not exist.' % action)
|
||||||
|
|
||||||
|
|
||||||
class CategoryList(object):
|
class CategoryList(object):
|
||||||
@ -184,9 +170,9 @@ class CategoryList(object):
|
|||||||
self.index += 1
|
self.index += 1
|
||||||
return self.categories[self.index - 1]
|
return self.categories[self.index - 1]
|
||||||
|
|
||||||
def has_key(self, key):
|
def __contains__(self, key):
|
||||||
"""
|
"""
|
||||||
Implement the has_key() method to make this class like a dictionary
|
Implement the __contains__() method to make this class like a dictionary
|
||||||
"""
|
"""
|
||||||
for category in self.categories:
|
for category in self.categories:
|
||||||
if category.name == key:
|
if category.name == key:
|
||||||
@ -200,10 +186,7 @@ class CategoryList(object):
|
|||||||
weight = 0
|
weight = 0
|
||||||
if self.categories:
|
if self.categories:
|
||||||
weight = self.categories[-1].weight + 1
|
weight = self.categories[-1].weight + 1
|
||||||
if actions:
|
|
||||||
self.add(name, weight, actions)
|
self.add(name, weight, actions)
|
||||||
else:
|
|
||||||
self.add(name, weight)
|
|
||||||
|
|
||||||
def add(self, name, weight=0, actions=None):
|
def add(self, name, weight=0, actions=None):
|
||||||
"""
|
"""
|
||||||
@ -226,6 +209,8 @@ class CategoryList(object):
|
|||||||
for category in self.categories:
|
for category in self.categories:
|
||||||
if category.name == name:
|
if category.name == name:
|
||||||
self.categories.remove(category)
|
self.categories.remove(category)
|
||||||
|
return
|
||||||
|
raise ValueError('Category "%s" does not exist.' % name)
|
||||||
|
|
||||||
|
|
||||||
class ActionList(object):
|
class ActionList(object):
|
||||||
@ -270,7 +255,7 @@ class ActionList(object):
|
|||||||
settings = Settings()
|
settings = Settings()
|
||||||
settings.beginGroup('shortcuts')
|
settings.beginGroup('shortcuts')
|
||||||
# Get the default shortcut from the config.
|
# Get the default shortcut from the config.
|
||||||
action.defaultShortcuts = settings.get_default_value(action.objectName())
|
action.default_shortcuts = settings.get_default_value(action.objectName())
|
||||||
if weight is None:
|
if weight is None:
|
||||||
self.categories[category].actions.append(action)
|
self.categories[category].actions.append(action)
|
||||||
else:
|
else:
|
||||||
|
@ -32,7 +32,7 @@ other class holds all the functional code, like slots and loading and saving.
|
|||||||
|
|
||||||
The first class, commonly known as the **Dialog** class, is typically named ``Ui_<name>Dialog``. It is a slightly
|
The first class, commonly known as the **Dialog** class, is typically named ``Ui_<name>Dialog``. It is a slightly
|
||||||
modified version of the class that the ``pyuic4`` command produces from Qt4's .ui file. Typical modifications will be
|
modified version of the class that the ``pyuic4`` command produces from Qt4's .ui file. Typical modifications will be
|
||||||
converting most strings from "" to u'' and using OpenLP's ``translate()`` function for translating strings.
|
converting most strings from "" to '' and using OpenLP's ``translate()`` function for translating strings.
|
||||||
|
|
||||||
The second class, commonly known as the **Form** class, is typically named ``<name>Form``. This class is the one which
|
The second class, commonly known as the **Form** class, is typically named ``<name>Form``. This class is the one which
|
||||||
is instantiated and used. It uses dual inheritance to inherit from (usually) QtGui.QDialog and the Ui class mentioned
|
is instantiated and used. It uses dual inheritance to inherit from (usually) QtGui.QDialog and the Ui class mentioned
|
||||||
|
@ -46,7 +46,7 @@ class Ui_AlertDialog(object):
|
|||||||
"""
|
"""
|
||||||
alert_dialog.setObjectName('alert_dialog')
|
alert_dialog.setObjectName('alert_dialog')
|
||||||
alert_dialog.resize(400, 300)
|
alert_dialog.resize(400, 300)
|
||||||
alert_dialog.setWindowIcon(build_icon(':/icon/openlp-logo-16x16.png'))
|
alert_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
|
||||||
self.alert_dialog_layout = QtGui.QGridLayout(alert_dialog)
|
self.alert_dialog_layout = QtGui.QGridLayout(alert_dialog)
|
||||||
self.alert_dialog_layout.setObjectName('alert_dialog_layout')
|
self.alert_dialog_layout.setObjectName('alert_dialog_layout')
|
||||||
self.alert_text_layout = QtGui.QFormLayout()
|
self.alert_text_layout = QtGui.QFormLayout()
|
||||||
|
@ -33,7 +33,7 @@ other class holds all the functional code, like slots and loading and saving.
|
|||||||
|
|
||||||
The first class, commonly known as the **Dialog** class, is typically named ``Ui_<name>Dialog``. It is a slightly
|
The first class, commonly known as the **Dialog** class, is typically named ``Ui_<name>Dialog``. It is a slightly
|
||||||
modified version of the class that the ``pyuic4`` command produces from Qt4's .ui file. Typical modifications will be
|
modified version of the class that the ``pyuic4`` command produces from Qt4's .ui file. Typical modifications will be
|
||||||
converting most strings from "" to u'' and using OpenLP's ``translate()`` function for translating strings.
|
converting most strings from "" to '' and using OpenLP's ``translate()`` function for translating strings.
|
||||||
|
|
||||||
The second class, commonly known as the **Form** class, is typically named ``<name>Form``. This class is the one which
|
The second class, commonly known as the **Form** class, is typically named ``<name>Form``. This class is the one which
|
||||||
is instantiated and used. It uses dual inheritance to inherit from (usually) QtGui.QDialog and the Ui class mentioned
|
is instantiated and used. It uses dual inheritance to inherit from (usually) QtGui.QDialog and the Ui class mentioned
|
||||||
|
@ -465,7 +465,7 @@ class BibleImportForm(OpenLPWizard):
|
|||||||
self.license_details_page.registerField('license_copyright', self.copyright_edit)
|
self.license_details_page.registerField('license_copyright', self.copyright_edit)
|
||||||
self.license_details_page.registerField('license_permissions', self.permissions_edit)
|
self.license_details_page.registerField('license_permissions', self.permissions_edit)
|
||||||
|
|
||||||
def setDefaults(self):
|
def set_defaults(self):
|
||||||
"""
|
"""
|
||||||
Set default values for the wizard pages.
|
Set default values for the wizard pages.
|
||||||
"""
|
"""
|
||||||
|
@ -307,7 +307,7 @@ class BibleUpgradeForm(OpenLPWizard):
|
|||||||
if self.currentPage() == self.progress_page:
|
if self.currentPage() == self.progress_page:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def setDefaults(self):
|
def set_defaults(self):
|
||||||
"""
|
"""
|
||||||
Set default values for the wizard pages.
|
Set default values for the wizard pages.
|
||||||
"""
|
"""
|
||||||
|
@ -30,12 +30,14 @@
|
|||||||
from PyQt4 import QtCore, QtGui
|
from PyQt4 import QtCore, QtGui
|
||||||
|
|
||||||
from openlp.core.common import translate
|
from openlp.core.common import translate
|
||||||
|
from openlp.core.lib import build_icon
|
||||||
from openlp.core.lib.ui import create_button_box
|
from openlp.core.lib.ui import create_button_box
|
||||||
|
|
||||||
|
|
||||||
class Ui_BookNameDialog(object):
|
class Ui_BookNameDialog(object):
|
||||||
def setupUi(self, book_name_dialog):
|
def setupUi(self, book_name_dialog):
|
||||||
book_name_dialog.setObjectName('book_name_dialog')
|
book_name_dialog.setObjectName('book_name_dialog')
|
||||||
|
book_name_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
|
||||||
book_name_dialog.resize(400, 271)
|
book_name_dialog.resize(400, 271)
|
||||||
self.book_name_layout = QtGui.QVBoxLayout(book_name_dialog)
|
self.book_name_layout = QtGui.QVBoxLayout(book_name_dialog)
|
||||||
self.book_name_layout.setSpacing(8)
|
self.book_name_layout.setSpacing(8)
|
||||||
|
@ -39,8 +39,8 @@ from openlp.plugins.bibles.lib.db import BiblesResourcesDB
|
|||||||
class Ui_EditBibleDialog(object):
|
class Ui_EditBibleDialog(object):
|
||||||
def setupUi(self, edit_bible_dialog):
|
def setupUi(self, edit_bible_dialog):
|
||||||
edit_bible_dialog.setObjectName('edit_bible_dialog')
|
edit_bible_dialog.setObjectName('edit_bible_dialog')
|
||||||
|
edit_bible_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
|
||||||
edit_bible_dialog.resize(520, 400)
|
edit_bible_dialog.resize(520, 400)
|
||||||
edit_bible_dialog.setWindowIcon(build_icon(':/icon/openlp-logo-16x16.png'))
|
|
||||||
edit_bible_dialog.setModal(True)
|
edit_bible_dialog.setModal(True)
|
||||||
self.dialog_layout = QtGui.QVBoxLayout(edit_bible_dialog)
|
self.dialog_layout = QtGui.QVBoxLayout(edit_bible_dialog)
|
||||||
self.dialog_layout.setSpacing(8)
|
self.dialog_layout.setSpacing(8)
|
||||||
|
@ -30,12 +30,14 @@
|
|||||||
from PyQt4 import QtGui
|
from PyQt4 import QtGui
|
||||||
|
|
||||||
from openlp.core.common import translate
|
from openlp.core.common import translate
|
||||||
|
from openlp.core.lib import build_icon
|
||||||
from openlp.core.lib.ui import create_button_box
|
from openlp.core.lib.ui import create_button_box
|
||||||
|
|
||||||
|
|
||||||
class Ui_LanguageDialog(object):
|
class Ui_LanguageDialog(object):
|
||||||
def setupUi(self, language_dialog):
|
def setupUi(self, language_dialog):
|
||||||
language_dialog.setObjectName('language_dialog')
|
language_dialog.setObjectName('language_dialog')
|
||||||
|
language_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
|
||||||
language_dialog.resize(400, 165)
|
language_dialog.resize(400, 165)
|
||||||
self.language_layout = QtGui.QVBoxLayout(language_dialog)
|
self.language_layout = QtGui.QVBoxLayout(language_dialog)
|
||||||
self.language_layout.setSpacing(8)
|
self.language_layout.setSpacing(8)
|
||||||
|
@ -262,7 +262,7 @@ def parse_reference(reference, bible, language_selection, book_ref_id=False):
|
|||||||
|
|
||||||
For example::
|
For example::
|
||||||
|
|
||||||
[(u'John', 3, 16, 18), (u'John', 4, 1, 1)]
|
[('John', 3, 16, 18), ('John', 4, 1, 1)]
|
||||||
|
|
||||||
**Reference string details:**
|
**Reference string details:**
|
||||||
|
|
||||||
@ -311,7 +311,7 @@ def parse_reference(reference, bible, language_selection, book_ref_id=False):
|
|||||||
``(?P<to_verse>[0-9]+)``
|
``(?P<to_verse>[0-9]+)``
|
||||||
The ``to_verse`` reference is equivalent to group 2.
|
The ``to_verse`` reference is equivalent to group 2.
|
||||||
|
|
||||||
The full reference is matched against get_reference_match(u'full'). This regular expression looks like this:
|
The full reference is matched against get_reference_match('full'). This regular expression looks like this:
|
||||||
|
|
||||||
``^\s*(?!\s)(?P<book>[\d]*[^\d]+)(?<!\s)\s*``
|
``^\s*(?!\s)(?P<book>[\d]*[^\d]+)(?<!\s)\s*``
|
||||||
The ``book`` group starts with the first non-whitespace character. There are optional leading digits followed by
|
The ``book`` group starts with the first non-whitespace character. There are optional leading digits followed by
|
||||||
|
@ -405,7 +405,7 @@ class BiblesTab(SettingsTab):
|
|||||||
:param theme_list:
|
:param theme_list:
|
||||||
The list of available themes::
|
The list of available themes::
|
||||||
|
|
||||||
[u'Bible Theme', u'Song Theme']
|
['Bible Theme', 'Song Theme']
|
||||||
"""
|
"""
|
||||||
self.bible_theme_combo_box.clear()
|
self.bible_theme_combo_box.clear()
|
||||||
self.bible_theme_combo_box.addItem('')
|
self.bible_theme_combo_box.addItem('')
|
||||||
|
@ -370,17 +370,16 @@ class BibleDB(QtCore.QObject, Manager, RegistryProperties):
|
|||||||
This is probably the most used function. It retrieves the list of
|
This is probably the most used function. It retrieves the list of
|
||||||
verses based on the user's query.
|
verses based on the user's query.
|
||||||
|
|
||||||
:param reference_list: This is the list of references the media manager item wants. It is
|
:param reference_list: This is the list of references the media manager item wants. It is a list of tuples, with
|
||||||
a list of tuples, with the following format::
|
the following format::
|
||||||
|
|
||||||
(book_reference_id, chapter, start_verse, end_verse)
|
(book_reference_id, chapter, start_verse, end_verse)
|
||||||
|
|
||||||
Therefore, when you are looking for multiple items, simply break
|
Therefore, when you are looking for multiple items, simply break them up into references like this, bundle
|
||||||
them up into references like this, bundle them into a list. This
|
them into a list. This function then runs through the list, and returns an amalgamated list of ``Verse``
|
||||||
function then runs through the list, and returns an amalgamated
|
objects. For example::
|
||||||
list of ``Verse`` objects. For example::
|
|
||||||
|
|
||||||
[(u'35', 1, 1, 1), (u'35', 2, 2, 3)]
|
[('35', 1, 1, 1), ('35', 2, 2, 3)]
|
||||||
:param show_error:
|
:param show_error:
|
||||||
"""
|
"""
|
||||||
log.debug('BibleDB.get_verses("%s")' % reference_list)
|
log.debug('BibleDB.get_verses("%s")' % reference_list)
|
||||||
|
@ -534,7 +534,7 @@ class HTTPBible(BibleDB, RegistryProperties):
|
|||||||
them into a list. This function then runs through the list, and returns an amalgamated list of ``Verse``
|
them into a list. This function then runs through the list, and returns an amalgamated list of ``Verse``
|
||||||
objects. For example::
|
objects. For example::
|
||||||
|
|
||||||
[(u'35', 1, 1, 1), (u'35', 2, 2, 3)]
|
[('35', 1, 1, 1), ('35', 2, 2, 3)]
|
||||||
"""
|
"""
|
||||||
log.debug('HTTPBible.get_verses("%s")', reference_list)
|
log.debug('HTTPBible.get_verses("%s")', reference_list)
|
||||||
for reference in reference_list:
|
for reference in reference_list:
|
||||||
|
@ -54,19 +54,19 @@ class BibleFormat(object):
|
|||||||
WebDownload = 3
|
WebDownload = 3
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_class(format):
|
def get_class(bible_format):
|
||||||
"""
|
"""
|
||||||
Return the appropriate implementation class.
|
Return the appropriate implementation class.
|
||||||
|
|
||||||
:param format: The Bible format.
|
:param bible_format: The Bible format.
|
||||||
"""
|
"""
|
||||||
if format == BibleFormat.OSIS:
|
if bible_format == BibleFormat.OSIS:
|
||||||
return OSISBible
|
return OSISBible
|
||||||
elif format == BibleFormat.CSV:
|
elif bible_format == BibleFormat.CSV:
|
||||||
return CSVBible
|
return CSVBible
|
||||||
elif format == BibleFormat.OpenSong:
|
elif bible_format == BibleFormat.OpenSong:
|
||||||
return OpenSongBible
|
return OpenSongBible
|
||||||
elif format == BibleFormat.WebDownload:
|
elif bible_format == BibleFormat.WebDownload:
|
||||||
return HTTPBible
|
return HTTPBible
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
@ -60,7 +60,6 @@ class BibleMediaItem(MediaManagerItem):
|
|||||||
log.info('Bible Media Item loaded')
|
log.info('Bible Media Item loaded')
|
||||||
|
|
||||||
def __init__(self, parent, plugin):
|
def __init__(self, parent, plugin):
|
||||||
self.icon_path = 'songs/song'
|
|
||||||
self.lock_icon = build_icon(':/bibles/bibles_search_lock.png')
|
self.lock_icon = build_icon(':/bibles/bibles_search_lock.png')
|
||||||
self.unlock_icon = build_icon(':/bibles/bibles_search_unlock.png')
|
self.unlock_icon = build_icon(':/bibles/bibles_search_unlock.png')
|
||||||
MediaManagerItem.__init__(self, parent, plugin)
|
MediaManagerItem.__init__(self, parent, plugin)
|
||||||
@ -172,6 +171,7 @@ class BibleMediaItem(MediaManagerItem):
|
|||||||
self.page_layout.addWidget(tab)
|
self.page_layout.addWidget(tab)
|
||||||
tab.setVisible(False)
|
tab.setVisible(False)
|
||||||
lock_button.toggled.connect(self.on_lock_button_toggled)
|
lock_button.toggled.connect(self.on_lock_button_toggled)
|
||||||
|
second_combo_box.currentIndexChanged.connect(self.on_second_bible_combobox_index_changed)
|
||||||
setattr(self, prefix + 'VersionLabel', version_label)
|
setattr(self, prefix + 'VersionLabel', version_label)
|
||||||
setattr(self, prefix + 'VersionComboBox', version_combo_box)
|
setattr(self, prefix + 'VersionComboBox', version_combo_box)
|
||||||
setattr(self, prefix + 'SecondLabel', second_label)
|
setattr(self, prefix + 'SecondLabel', second_label)
|
||||||
@ -263,11 +263,15 @@ class BibleMediaItem(MediaManagerItem):
|
|||||||
def config_update(self):
|
def config_update(self):
|
||||||
log.debug('config_update')
|
log.debug('config_update')
|
||||||
if Settings().value(self.settings_section + '/second bibles'):
|
if Settings().value(self.settings_section + '/second bibles'):
|
||||||
|
self.quickSecondLabel.setVisible(True)
|
||||||
|
self.quickSecondComboBox.setVisible(True)
|
||||||
self.advancedSecondLabel.setVisible(True)
|
self.advancedSecondLabel.setVisible(True)
|
||||||
self.advancedSecondComboBox.setVisible(True)
|
self.advancedSecondComboBox.setVisible(True)
|
||||||
self.quickSecondLabel.setVisible(True)
|
self.quickSecondLabel.setVisible(True)
|
||||||
self.quickSecondComboBox.setVisible(True)
|
self.quickSecondComboBox.setVisible(True)
|
||||||
else:
|
else:
|
||||||
|
self.quickSecondLabel.setVisible(False)
|
||||||
|
self.quickSecondComboBox.setVisible(False)
|
||||||
self.advancedSecondLabel.setVisible(False)
|
self.advancedSecondLabel.setVisible(False)
|
||||||
self.advancedSecondComboBox.setVisible(False)
|
self.advancedSecondComboBox.setVisible(False)
|
||||||
self.quickSecondLabel.setVisible(False)
|
self.quickSecondLabel.setVisible(False)
|
||||||
@ -360,8 +364,7 @@ class BibleMediaItem(MediaManagerItem):
|
|||||||
combo boxes on the 'Advanced Search' Tab. This is not of any importance of the 'Quick Search' Tab.
|
combo boxes on the 'Advanced Search' Tab. This is not of any importance of the 'Quick Search' Tab.
|
||||||
|
|
||||||
:param bible: The bible to initialise (unicode).
|
:param bible: The bible to initialise (unicode).
|
||||||
:param last_book_id: The "book reference id" of the book which is chosen at the moment.
|
:param last_book_id: The "book reference id" of the book which is chosen at the moment. (int)
|
||||||
(int)
|
|
||||||
"""
|
"""
|
||||||
log.debug('initialise_advanced_bible %s, %s', bible, last_book_id)
|
log.debug('initialise_advanced_bible %s, %s', bible, last_book_id)
|
||||||
book_data = self.plugin.manager.get_books(bible)
|
book_data = self.plugin.manager.get_books(bible)
|
||||||
@ -421,9 +424,8 @@ class BibleMediaItem(MediaManagerItem):
|
|||||||
|
|
||||||
def update_auto_completer(self):
|
def update_auto_completer(self):
|
||||||
"""
|
"""
|
||||||
This updates the bible book completion list for the search field. The
|
This updates the bible book completion list for the search field. The completion depends on the bible. It is
|
||||||
completion depends on the bible. It is only updated when we are doing a
|
only updated when we are doing a reference search, otherwise the auto completion list is removed.
|
||||||
reference search, otherwise the auto completion list is removed.
|
|
||||||
"""
|
"""
|
||||||
log.debug('update_auto_completer')
|
log.debug('update_auto_completer')
|
||||||
# Save the current search type to the configuration.
|
# Save the current search type to the configuration.
|
||||||
@ -461,6 +463,17 @@ class BibleMediaItem(MediaManagerItem):
|
|||||||
books.sort(key=get_locale_key)
|
books.sort(key=get_locale_key)
|
||||||
set_case_insensitive_completer(books, self.quick_search_edit)
|
set_case_insensitive_completer(books, self.quick_search_edit)
|
||||||
|
|
||||||
|
def on_second_bible_combobox_index_changed(self, selection):
|
||||||
|
"""
|
||||||
|
Activate the style combobox only when no second bible is selected
|
||||||
|
"""
|
||||||
|
if selection == 0:
|
||||||
|
self.quickStyleComboBox.setEnabled(True)
|
||||||
|
self.advancedStyleComboBox.setEnabled(True)
|
||||||
|
else:
|
||||||
|
self.quickStyleComboBox.setEnabled(False)
|
||||||
|
self.advancedStyleComboBox.setEnabled(False)
|
||||||
|
|
||||||
def on_import_click(self):
|
def on_import_click(self):
|
||||||
if not hasattr(self, 'import_wizard'):
|
if not hasattr(self, 'import_wizard'):
|
||||||
self.import_wizard = BibleImportForm(self, self.plugin.manager, self.plugin)
|
self.import_wizard = BibleImportForm(self, self.plugin.manager, self.plugin)
|
||||||
@ -593,8 +606,7 @@ class BibleMediaItem(MediaManagerItem):
|
|||||||
:param range_from: The first number of the range (int).
|
:param range_from: The first number of the range (int).
|
||||||
:param range_to: The last number of the range (int).
|
:param range_to: The last number of the range (int).
|
||||||
:param combo: The combo box itself (QComboBox).
|
:param combo: The combo box itself (QComboBox).
|
||||||
:param restore: If True, then the combo's currentText will be restored after
|
:param restore: If True, then the combo's currentText will be restored after adjusting (if possible).
|
||||||
adjusting (if possible).
|
|
||||||
"""
|
"""
|
||||||
log.debug('adjust_combo_box %s, %s, %s', combo, range_from, range_to)
|
log.debug('adjust_combo_box %s, %s, %s', combo, range_from, range_to)
|
||||||
if restore:
|
if restore:
|
||||||
@ -640,8 +652,8 @@ class BibleMediaItem(MediaManagerItem):
|
|||||||
|
|
||||||
def on_quick_search_button(self):
|
def on_quick_search_button(self):
|
||||||
"""
|
"""
|
||||||
Does a quick search and saves the search results. Quick search can
|
Does a quick search and saves the search results. Quick search can either be "Reference Search" or
|
||||||
either be "Reference Search" or "Text Search".
|
"Text Search".
|
||||||
"""
|
"""
|
||||||
log.debug('Quick Search Button clicked')
|
log.debug('Quick Search Button clicked')
|
||||||
self.quickSearchButton.setEnabled(False)
|
self.quickSearchButton.setEnabled(False)
|
||||||
@ -696,8 +708,7 @@ class BibleMediaItem(MediaManagerItem):
|
|||||||
|
|
||||||
def display_results(self, bible, second_bible=''):
|
def display_results(self, bible, second_bible=''):
|
||||||
"""
|
"""
|
||||||
Displays the search results in the media manager. All data needed for
|
Displays the search results in the media manager. All data needed for further action is saved for/in each row.
|
||||||
further action is saved for/in each row.
|
|
||||||
"""
|
"""
|
||||||
items = self.build_display_results(bible, second_bible, self.search_results)
|
items = self.build_display_results(bible, second_bible, self.search_results)
|
||||||
for bible_verse in items:
|
for bible_verse in items:
|
||||||
@ -708,8 +719,7 @@ class BibleMediaItem(MediaManagerItem):
|
|||||||
|
|
||||||
def build_display_results(self, bible, second_bible, search_results):
|
def build_display_results(self, bible, second_bible, search_results):
|
||||||
"""
|
"""
|
||||||
Displays the search results in the media manager. All data needed for
|
Displays the search results in the media manager. All data needed for further action is saved for/in each row.
|
||||||
further action is saved for/in each row.
|
|
||||||
"""
|
"""
|
||||||
verse_separator = get_reference_separator('sep_v_display')
|
verse_separator = get_reference_separator('sep_v_display')
|
||||||
version = self.plugin.manager.get_meta_data(bible, 'name').value
|
version = self.plugin.manager.get_meta_data(bible, 'name').value
|
||||||
@ -837,7 +847,6 @@ class BibleMediaItem(MediaManagerItem):
|
|||||||
# If there are no more items we check whether we have to add bible_text.
|
# If there are no more items we check whether we have to add bible_text.
|
||||||
if bible_text:
|
if bible_text:
|
||||||
raw_slides.append(bible_text.lstrip())
|
raw_slides.append(bible_text.lstrip())
|
||||||
bible_text = ''
|
|
||||||
# Service Item: Capabilities
|
# Service Item: Capabilities
|
||||||
if self.settings.layout_style == LayoutStyle.Continuous and not second_bible:
|
if self.settings.layout_style == LayoutStyle.Continuous and not second_bible:
|
||||||
# Split the line but do not replace line breaks in renderer.
|
# Split the line but do not replace line breaks in renderer.
|
||||||
@ -859,9 +868,8 @@ class BibleMediaItem(MediaManagerItem):
|
|||||||
|
|
||||||
def format_title(self, start_bitem, old_bitem):
|
def format_title(self, start_bitem, old_bitem):
|
||||||
"""
|
"""
|
||||||
This method 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
|
||||||
we are at the end of a verse range. E. g. if we want to add
|
want to add Genesis 1:1-6 as well as Daniel 2:14.
|
||||||
Genesis 1:1-6 as well as Daniel 2:14.
|
|
||||||
|
|
||||||
:param start_bitem: The first item of a range.
|
:param start_bitem: The first item of a range.
|
||||||
:param old_bitem: The last item of a range.
|
:param old_bitem: The last item of a range.
|
||||||
@ -891,10 +899,8 @@ class BibleMediaItem(MediaManagerItem):
|
|||||||
|
|
||||||
def check_title(self, bitem, old_bitem):
|
def check_title(self, bitem, old_bitem):
|
||||||
"""
|
"""
|
||||||
This method 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.
|
||||||
the case, we return True, otherwise False. E. g. if we added
|
E. g. if we added Genesis 1:1-6, but the next verse is Daniel 2:14, we return True.
|
||||||
|
|
||||||
Genesis 1:1-6, but the next verse is Daniel 2:14, we return True.
|
|
||||||
|
|
||||||
:param bitem: The item we are dealing with at the moment.
|
:param bitem: The item we are dealing with at the moment.
|
||||||
:param old_bitem: The item we were previously dealing with.
|
:param old_bitem: The item we were previously dealing with.
|
||||||
@ -918,20 +924,17 @@ class BibleMediaItem(MediaManagerItem):
|
|||||||
return True
|
return True
|
||||||
elif old_chapter + 1 == chapter and (verse != 1 or old_verse !=
|
elif old_chapter + 1 == chapter and (verse != 1 or old_verse !=
|
||||||
self.plugin.manager.get_verse_count(old_bible, old_book, old_chapter)):
|
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
|
# We are in the following chapter, but the last verse was not the last verse of the chapter or the current
|
||||||
# last verse of the chapter or the current verse is not the
|
# verse is not the first one of the chapter.
|
||||||
# first one of the chapter.
|
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def format_verse(self, old_chapter, chapter, verse):
|
def format_verse(self, old_chapter, chapter, verse):
|
||||||
"""
|
"""
|
||||||
Formats and returns the text, each verse starts with, for the given
|
Formats and returns the text, each verse starts with, for the given chapter and verse. The text is either
|
||||||
chapter and verse. The text is either surrounded by round, square,
|
surrounded by round, square, curly brackets or no brackets at all. For example::
|
||||||
|
|
||||||
curly brackets or no brackets at all. For example::
|
'{su}1:1{/su}'
|
||||||
|
|
||||||
u'{su}1:1{/su}'
|
|
||||||
|
|
||||||
:param old_chapter: The previous verse's chapter number (int).
|
:param old_chapter: The previous verse's chapter number (int).
|
||||||
:param chapter: The chapter number (int).
|
:param chapter: The chapter number (int).
|
||||||
|
@ -41,8 +41,8 @@ class Ui_CustomEditDialog(object):
|
|||||||
:param custom_edit_dialog: The Dialog
|
:param custom_edit_dialog: The Dialog
|
||||||
"""
|
"""
|
||||||
custom_edit_dialog.setObjectName('custom_edit_dialog')
|
custom_edit_dialog.setObjectName('custom_edit_dialog')
|
||||||
|
custom_edit_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
|
||||||
custom_edit_dialog.resize(450, 350)
|
custom_edit_dialog.resize(450, 350)
|
||||||
custom_edit_dialog.setWindowIcon(build_icon(':/icon/openlp-logo-16x16.png'))
|
|
||||||
self.dialog_layout = QtGui.QVBoxLayout(custom_edit_dialog)
|
self.dialog_layout = QtGui.QVBoxLayout(custom_edit_dialog)
|
||||||
self.dialog_layout.setObjectName('dialog_layout')
|
self.dialog_layout.setObjectName('dialog_layout')
|
||||||
self.title_layout = QtGui.QHBoxLayout()
|
self.title_layout = QtGui.QHBoxLayout()
|
||||||
|
@ -197,7 +197,6 @@ class EditCustomForm(QtGui.QDialog, Ui_CustomEditDialog):
|
|||||||
self.slide_list_view.clear()
|
self.slide_list_view.clear()
|
||||||
self.slide_list_view.addItems(slides)
|
self.slide_list_view.addItems(slides)
|
||||||
else:
|
else:
|
||||||
old_slides = []
|
|
||||||
old_row = self.slide_list_view.currentRow()
|
old_row = self.slide_list_view.currentRow()
|
||||||
# Create a list with all (old/unedited) slides.
|
# Create a list with all (old/unedited) slides.
|
||||||
old_slides = [self.slide_list_view.item(row).text() for row in range(self.slide_list_view.count())]
|
old_slides = [self.slide_list_view.item(row).text() for row in range(self.slide_list_view.count())]
|
||||||
|
@ -30,13 +30,14 @@
|
|||||||
from PyQt4 import QtGui
|
from PyQt4 import QtGui
|
||||||
|
|
||||||
from openlp.core.common import UiStrings, translate
|
from openlp.core.common import UiStrings, translate
|
||||||
from openlp.core.lib import SpellTextEdit
|
from openlp.core.lib import SpellTextEdit, build_icon
|
||||||
from openlp.core.lib.ui import create_button, create_button_box
|
from openlp.core.lib.ui import create_button, create_button_box
|
||||||
|
|
||||||
|
|
||||||
class Ui_CustomSlideEditDialog(object):
|
class Ui_CustomSlideEditDialog(object):
|
||||||
def setupUi(self, custom_slide_edit_dialog):
|
def setupUi(self, custom_slide_edit_dialog):
|
||||||
custom_slide_edit_dialog.setObjectName('custom_slide_edit_dialog')
|
custom_slide_edit_dialog.setObjectName('custom_slide_edit_dialog')
|
||||||
|
custom_slide_edit_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
|
||||||
custom_slide_edit_dialog.resize(350, 300)
|
custom_slide_edit_dialog.resize(350, 300)
|
||||||
self.dialog_layout = QtGui.QVBoxLayout(custom_slide_edit_dialog)
|
self.dialog_layout = QtGui.QVBoxLayout(custom_slide_edit_dialog)
|
||||||
self.slide_text_edit = SpellTextEdit(self)
|
self.slide_text_edit = SpellTextEdit(self)
|
||||||
|
@ -32,7 +32,7 @@ other class holds all the functional code, like slots and loading and saving.
|
|||||||
|
|
||||||
The first class, commonly known as the **Dialog** class, is typically named ``Ui_<name>Dialog``. It is a slightly
|
The first class, commonly known as the **Dialog** class, is typically named ``Ui_<name>Dialog``. It is a slightly
|
||||||
modified version of the class that the ``pyuic4`` command produces from Qt4's .ui file. Typical modifications will be
|
modified version of the class that the ``pyuic4`` command produces from Qt4's .ui file. Typical modifications will be
|
||||||
converting most strings from "" to u'' and using OpenLP's ``translate()`` function for translating strings.
|
converting most strings from "" to '' and using OpenLP's ``translate()`` function for translating strings.
|
||||||
|
|
||||||
The second class, commonly known as the **Form** class, is typically named ``<name>Form``. This class is the one which
|
The second class, commonly known as the **Form** class, is typically named ``<name>Form``. This class is the one which
|
||||||
is instantiated and used. It uses dual inheritance to inherit from (usually) QtGui.QDialog and the Ui class mentioned
|
is instantiated and used. It uses dual inheritance to inherit from (usually) QtGui.QDialog and the Ui class mentioned
|
||||||
|
@ -31,7 +31,7 @@ The :mod:`db` module provides the database and schema that is the backend for th
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from sqlalchemy import Column, ForeignKey, Table, types
|
from sqlalchemy import Column, ForeignKey, Table, types
|
||||||
from sqlalchemy.orm import mapper, relation, reconstructor
|
from sqlalchemy.orm import mapper
|
||||||
|
|
||||||
from openlp.core.lib.db import BaseModel, init_db
|
from openlp.core.lib.db import BaseModel, init_db
|
||||||
|
|
||||||
|
@ -353,7 +353,7 @@ class ImageMediaItem(MediaManagerItem):
|
|||||||
icon = build_icon(thumb)
|
icon = build_icon(thumb)
|
||||||
else:
|
else:
|
||||||
icon = create_thumb(imageFile.filename, thumb)
|
icon = create_thumb(imageFile.filename, thumb)
|
||||||
item_name = QtGui.QTreeWidgetItem(filename)
|
item_name = QtGui.QTreeWidgetItem([filename])
|
||||||
item_name.setText(0, filename)
|
item_name.setText(0, filename)
|
||||||
item_name.setIcon(0, icon)
|
item_name.setIcon(0, icon)
|
||||||
item_name.setToolTip(0, imageFile.filename)
|
item_name.setToolTip(0, imageFile.filename)
|
||||||
|
@ -379,7 +379,6 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
media = Settings().value(self.settings_section + '/media files')
|
media = Settings().value(self.settings_section + '/media files')
|
||||||
media.sort(key=lambda filename: get_locale_key(os.path.split(str(filename))[1]))
|
media.sort(key=lambda filename: get_locale_key(os.path.split(str(filename))[1]))
|
||||||
extension = []
|
|
||||||
if type == MediaType.Audio:
|
if type == MediaType.Audio:
|
||||||
extension = self.media_controller.audio_extensions_list
|
extension = self.media_controller.audio_extensions_list
|
||||||
else:
|
else:
|
||||||
|
@ -354,7 +354,7 @@ class PresentationController(object):
|
|||||||
class MyPresentationController(PresentationController):
|
class MyPresentationController(PresentationController):
|
||||||
def __init__(self, plugin):
|
def __init__(self, plugin):
|
||||||
PresentationController.__init(
|
PresentationController.__init(
|
||||||
self, plugin, u'My Presenter App')
|
self, plugin, 'My Presenter App')
|
||||||
|
|
||||||
:param plugin: Defaults to *None*. The presentationplugin object
|
:param plugin: Defaults to *None*. The presentationplugin object
|
||||||
:param name: Name of the application, to appear in the application
|
:param name: Name of the application, to appear in the application
|
||||||
|
@ -149,11 +149,11 @@ class HttpRouter(RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
Initialise the router stack and any other variables.
|
Initialise the router stack and any other variables.
|
||||||
"""
|
"""
|
||||||
authcode = "%s:%s" % (Settings().value('remotes/user id'), Settings().value('remotes/password'))
|
auth_code = "%s:%s" % (Settings().value('remotes/user id'), Settings().value('remotes/password'))
|
||||||
try:
|
try:
|
||||||
self.auth = base64.b64encode(authcode)
|
self.auth = base64.b64encode(auth_code)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
self.auth = base64.b64encode(authcode.encode()).decode()
|
self.auth = base64.b64encode(auth_code.encode()).decode()
|
||||||
self.routes = [
|
self.routes = [
|
||||||
('^/$', {'function': self.serve_file, 'secure': False}),
|
('^/$', {'function': self.serve_file, 'secure': False}),
|
||||||
('^/(stage)$', {'function': self.serve_file, 'secure': False}),
|
('^/(stage)$', {'function': self.serve_file, 'secure': False}),
|
||||||
@ -376,7 +376,6 @@ class HttpRouter(RegistryProperties):
|
|||||||
Examines the extension of the file and determines what the content_type should be, defaults to text/plain
|
Examines the extension of the file and determines what the content_type should be, defaults to text/plain
|
||||||
Returns the extension and the content_type
|
Returns the extension and the content_type
|
||||||
"""
|
"""
|
||||||
content_type = 'text/plain'
|
|
||||||
ext = os.path.splitext(file_name)[1]
|
ext = os.path.splitext(file_name)[1]
|
||||||
content_type = FILE_TYPES.get(ext, 'text/plain')
|
content_type = FILE_TYPES.get(ext, 'text/plain')
|
||||||
return ext, content_type
|
return ext, content_type
|
||||||
@ -439,7 +438,7 @@ class HttpRouter(RegistryProperties):
|
|||||||
if plugin.status == PluginStatus.Active:
|
if plugin.status == PluginStatus.Active:
|
||||||
try:
|
try:
|
||||||
text = json.loads(self.request_data)['request']['text']
|
text = json.loads(self.request_data)['request']['text']
|
||||||
except KeyError as ValueError:
|
except KeyError:
|
||||||
return self.do_http_error()
|
return self.do_http_error()
|
||||||
text = urllib.parse.unquote(text)
|
text = urllib.parse.unquote(text)
|
||||||
self.alerts_manager.emit(QtCore.SIGNAL('alerts_text'), [text])
|
self.alerts_manager.emit(QtCore.SIGNAL('alerts_text'), [text])
|
||||||
@ -453,6 +452,7 @@ class HttpRouter(RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
Perform an action on the slide controller.
|
Perform an action on the slide controller.
|
||||||
"""
|
"""
|
||||||
|
log.debug("controller_text var = %s" % var)
|
||||||
current_item = self.live_controller.service_item
|
current_item = self.live_controller.service_item
|
||||||
data = []
|
data = []
|
||||||
if current_item:
|
if current_item:
|
||||||
@ -488,7 +488,7 @@ class HttpRouter(RegistryProperties):
|
|||||||
if self.request_data:
|
if self.request_data:
|
||||||
try:
|
try:
|
||||||
data = json.loads(self.request_data)['request']['id']
|
data = json.loads(self.request_data)['request']['id']
|
||||||
except KeyError as ValueError:
|
except KeyError:
|
||||||
return self.do_http_error()
|
return self.do_http_error()
|
||||||
log.info(data)
|
log.info(data)
|
||||||
# This slot expects an int within a list.
|
# This slot expects an int within a list.
|
||||||
@ -547,7 +547,7 @@ class HttpRouter(RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
text = json.loads(self.request_data)['request']['text']
|
text = json.loads(self.request_data)['request']['text']
|
||||||
except KeyError as ValueError:
|
except KeyError:
|
||||||
return self.do_http_error()
|
return self.do_http_error()
|
||||||
text = urllib.parse.unquote(text)
|
text = urllib.parse.unquote(text)
|
||||||
plugin = self.plugin_manager.get_plugin_by_name(plugin_name)
|
plugin = self.plugin_manager.get_plugin_by_name(plugin_name)
|
||||||
@ -563,12 +563,12 @@ class HttpRouter(RegistryProperties):
|
|||||||
Go live on an item of type ``plugin``.
|
Go live on an item of type ``plugin``.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
id = json.loads(self.request_data)['request']['id']
|
request_id = json.loads(self.request_data)['request']['id']
|
||||||
except KeyError as ValueError:
|
except KeyError:
|
||||||
return self.do_http_error()
|
return self.do_http_error()
|
||||||
plugin = self.plugin_manager.get_plugin_by_name(plugin_name)
|
plugin = self.plugin_manager.get_plugin_by_name(plugin_name)
|
||||||
if plugin.status == PluginStatus.Active and plugin.media_item:
|
if plugin.status == PluginStatus.Active and plugin.media_item:
|
||||||
plugin.media_item.emit(QtCore.SIGNAL('%s_go_live' % plugin_name), [id, True])
|
plugin.media_item.emit(QtCore.SIGNAL('%s_go_live' % plugin_name), [request_id, True])
|
||||||
return self.do_http_success()
|
return self.do_http_success()
|
||||||
|
|
||||||
def add_to_service(self, plugin_name):
|
def add_to_service(self, plugin_name):
|
||||||
@ -576,11 +576,11 @@ class HttpRouter(RegistryProperties):
|
|||||||
Add item of type ``plugin_name`` to the end of the service.
|
Add item of type ``plugin_name`` to the end of the service.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
id = json.loads(self.request_data)['request']['id']
|
request_id = json.loads(self.request_data)['request']['id']
|
||||||
except KeyError as ValueError:
|
except KeyError:
|
||||||
return self.do_http_error()
|
return self.do_http_error()
|
||||||
plugin = self.plugin_manager.get_plugin_by_name(plugin_name)
|
plugin = self.plugin_manager.get_plugin_by_name(plugin_name)
|
||||||
if plugin.status == PluginStatus.Active and plugin.media_item:
|
if plugin.status == PluginStatus.Active and plugin.media_item:
|
||||||
item_id = plugin.media_item.create_item_from_id(id)
|
item_id = plugin.media_item.create_item_from_id(request_id)
|
||||||
plugin.media_item.emit(QtCore.SIGNAL('%s_add_to_service' % plugin_name), [item_id, True])
|
plugin.media_item.emit(QtCore.SIGNAL('%s_add_to_service' % plugin_name), [item_id, True])
|
||||||
self.do_http_success()
|
self.do_http_success()
|
||||||
|
@ -40,7 +40,7 @@ import time
|
|||||||
|
|
||||||
from PyQt4 import QtCore
|
from PyQt4 import QtCore
|
||||||
|
|
||||||
from openlp.core.common import AppLocation, Settings
|
from openlp.core.common import AppLocation, Settings, RegistryProperties
|
||||||
|
|
||||||
from openlp.plugins.remotes.lib import HttpRouter
|
from openlp.plugins.remotes.lib import HttpRouter
|
||||||
|
|
||||||
@ -94,13 +94,18 @@ class HttpThread(QtCore.QThread):
|
|||||||
"""
|
"""
|
||||||
self.http_server.start_server()
|
self.http_server.start_server()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
log.debug("stop called")
|
||||||
|
self.http_server.stop = True
|
||||||
|
|
||||||
class OpenLPServer():
|
|
||||||
|
class OpenLPServer(RegistryProperties):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""
|
"""
|
||||||
Initialise the http server, and start the server of the correct type http / https
|
Initialise the http server, and start the server of the correct type http / https
|
||||||
"""
|
"""
|
||||||
log.debug('Initialise httpserver')
|
super(OpenLPServer, self).__init__()
|
||||||
|
log.debug('Initialise OpenLP')
|
||||||
self.settings_section = 'remotes'
|
self.settings_section = 'remotes'
|
||||||
self.http_thread = HttpThread(self)
|
self.http_thread = HttpThread(self)
|
||||||
self.http_thread.start()
|
self.http_thread.start()
|
||||||
@ -110,32 +115,49 @@ class OpenLPServer():
|
|||||||
Start the correct server and save the handler
|
Start the correct server and save the handler
|
||||||
"""
|
"""
|
||||||
address = Settings().value(self.settings_section + '/ip address')
|
address = Settings().value(self.settings_section + '/ip address')
|
||||||
if Settings().value(self.settings_section + '/https enabled'):
|
self.address = address
|
||||||
|
self.is_secure = Settings().value(self.settings_section + '/https enabled')
|
||||||
|
self.needs_authentication = Settings().value(self.settings_section + '/authentication enabled')
|
||||||
|
if self.is_secure:
|
||||||
port = Settings().value(self.settings_section + '/https port')
|
port = Settings().value(self.settings_section + '/https port')
|
||||||
self.httpd = HTTPSServer((address, port), CustomHandler)
|
self.port = port
|
||||||
log.debug('Started ssl httpd...')
|
self.start_server_instance(address, port, HTTPSServer)
|
||||||
else:
|
else:
|
||||||
port = Settings().value(self.settings_section + '/port')
|
port = Settings().value(self.settings_section + '/port')
|
||||||
loop = 1
|
self.port = port
|
||||||
while loop < 3:
|
self.start_server_instance(address, port, ThreadingHTTPServer)
|
||||||
try:
|
|
||||||
self.httpd = ThreadingHTTPServer((address, port), CustomHandler)
|
|
||||||
except OSError:
|
|
||||||
loop += 1
|
|
||||||
time.sleep(0.1)
|
|
||||||
except:
|
|
||||||
log.error('Failed to start server ')
|
|
||||||
log.debug('Started non ssl httpd...')
|
|
||||||
if hasattr(self, 'httpd') and self.httpd:
|
if hasattr(self, 'httpd') and self.httpd:
|
||||||
self.httpd.serve_forever()
|
self.httpd.serve_forever()
|
||||||
else:
|
else:
|
||||||
log.debug('Failed to start server')
|
log.debug('Failed to start server')
|
||||||
|
|
||||||
|
def start_server_instance(self, address, port, server_class):
|
||||||
|
"""
|
||||||
|
Start the server
|
||||||
|
|
||||||
|
:param address: The server address
|
||||||
|
:param port: The run port
|
||||||
|
:param server_class: the class to start
|
||||||
|
"""
|
||||||
|
loop = 1
|
||||||
|
while loop < 4:
|
||||||
|
try:
|
||||||
|
self.httpd = server_class((address, port), CustomHandler)
|
||||||
|
log.debug("Server started for class %s %s %d" % (server_class, address, port))
|
||||||
|
except OSError:
|
||||||
|
log.debug("failed to start http server thread state %d %s" %
|
||||||
|
(loop, self.http_thread.isRunning()))
|
||||||
|
loop += 1
|
||||||
|
time.sleep(0.1)
|
||||||
|
except:
|
||||||
|
log.error('Failed to start server ')
|
||||||
|
|
||||||
def stop_server(self):
|
def stop_server(self):
|
||||||
"""
|
"""
|
||||||
Stop the server
|
Stop the server
|
||||||
"""
|
"""
|
||||||
self.http_thread.exit(0)
|
if self.http_thread.isRunning():
|
||||||
|
self.http_thread.stop()
|
||||||
self.httpd = None
|
self.httpd = None
|
||||||
log.debug('Stopped the server.')
|
log.debug('Stopped the server.')
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ import os.path
|
|||||||
from PyQt4 import QtCore, QtGui, QtNetwork
|
from PyQt4 import QtCore, QtGui, QtNetwork
|
||||||
|
|
||||||
from openlp.core.common import AppLocation, Settings, translate
|
from openlp.core.common import AppLocation, Settings, translate
|
||||||
from openlp.core.lib import SettingsTab
|
from openlp.core.lib import SettingsTab, build_icon
|
||||||
|
|
||||||
ZERO_URL = '0.0.0.0'
|
ZERO_URL = '0.0.0.0'
|
||||||
|
|
||||||
@ -234,6 +234,7 @@ class RemoteTab(SettingsTab):
|
|||||||
"""
|
"""
|
||||||
Load the configuration and update the server configuration if necessary
|
Load the configuration and update the server configuration if necessary
|
||||||
"""
|
"""
|
||||||
|
self.is_secure = Settings().value(self.settings_section + '/https enabled')
|
||||||
self.port_spin_box.setValue(Settings().value(self.settings_section + '/port'))
|
self.port_spin_box.setValue(Settings().value(self.settings_section + '/port'))
|
||||||
self.https_port_spin_box.setValue(Settings().value(self.settings_section + '/https port'))
|
self.https_port_spin_box.setValue(Settings().value(self.settings_section + '/https port'))
|
||||||
self.address_edit.setText(Settings().value(self.settings_section + '/ip address'))
|
self.address_edit.setText(Settings().value(self.settings_section + '/ip address'))
|
||||||
@ -263,9 +264,7 @@ class RemoteTab(SettingsTab):
|
|||||||
Settings().value(self.settings_section + '/port') != self.port_spin_box.value() or \
|
Settings().value(self.settings_section + '/port') != self.port_spin_box.value() or \
|
||||||
Settings().value(self.settings_section + '/https port') != self.https_port_spin_box.value() or \
|
Settings().value(self.settings_section + '/https port') != self.https_port_spin_box.value() or \
|
||||||
Settings().value(self.settings_section + '/https enabled') != \
|
Settings().value(self.settings_section + '/https enabled') != \
|
||||||
self.https_settings_group_box.isChecked() or \
|
self.https_settings_group_box.isChecked():
|
||||||
Settings().value(self.settings_section + '/authentication enabled') != \
|
|
||||||
self.user_login_group_box.isChecked():
|
|
||||||
self.settings_form.register_post_process('remotes_config_updated')
|
self.settings_form.register_post_process('remotes_config_updated')
|
||||||
Settings().setValue(self.settings_section + '/port', self.port_spin_box.value())
|
Settings().setValue(self.settings_section + '/port', self.port_spin_box.value())
|
||||||
Settings().setValue(self.settings_section + '/https port', self.https_port_spin_box.value())
|
Settings().setValue(self.settings_section + '/https port', self.https_port_spin_box.value())
|
||||||
@ -275,6 +274,7 @@ class RemoteTab(SettingsTab):
|
|||||||
Settings().setValue(self.settings_section + '/authentication enabled', self.user_login_group_box.isChecked())
|
Settings().setValue(self.settings_section + '/authentication enabled', self.user_login_group_box.isChecked())
|
||||||
Settings().setValue(self.settings_section + '/user id', self.user_id.text())
|
Settings().setValue(self.settings_section + '/user id', self.user_id.text())
|
||||||
Settings().setValue(self.settings_section + '/password', self.password.text())
|
Settings().setValue(self.settings_section + '/password', self.password.text())
|
||||||
|
self.generate_icon()
|
||||||
|
|
||||||
def on_twelve_hour_check_box_changed(self, check_state):
|
def on_twelve_hour_check_box_changed(self, check_state):
|
||||||
"""
|
"""
|
||||||
@ -290,3 +290,25 @@ class RemoteTab(SettingsTab):
|
|||||||
Invert the HTTP group box based on Https group settings
|
Invert the HTTP group box based on Https group settings
|
||||||
"""
|
"""
|
||||||
self.http_settings_group_box.setEnabled(not self.https_settings_group_box.isChecked())
|
self.http_settings_group_box.setEnabled(not self.https_settings_group_box.isChecked())
|
||||||
|
|
||||||
|
def generate_icon(self):
|
||||||
|
"""
|
||||||
|
Generate icon for main window
|
||||||
|
"""
|
||||||
|
self.remote_server_icon.hide()
|
||||||
|
icon = QtGui.QImage(':/remote/network_server.png')
|
||||||
|
icon = icon.scaled(80, 80, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
|
||||||
|
if self.is_secure:
|
||||||
|
overlay = QtGui.QImage(':/remote/network_ssl.png')
|
||||||
|
overlay = overlay.scaled(60, 60, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
|
||||||
|
painter = QtGui.QPainter(icon)
|
||||||
|
painter.drawImage(0, 0, overlay)
|
||||||
|
painter.end()
|
||||||
|
if Settings().value(self.settings_section + '/authentication enabled'):
|
||||||
|
overlay = QtGui.QImage(':/remote/network_auth.png')
|
||||||
|
overlay = overlay.scaled(60, 60, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
|
||||||
|
painter = QtGui.QPainter(icon)
|
||||||
|
painter.drawImage(20, 0, overlay)
|
||||||
|
painter.end()
|
||||||
|
self.remote_server_icon.setPixmap(QtGui.QPixmap.fromImage(icon))
|
||||||
|
self.remote_server_icon.show()
|
||||||
|
@ -28,7 +28,8 @@
|
|||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import time
|
|
||||||
|
from PyQt4 import QtGui
|
||||||
|
|
||||||
from openlp.core.lib import Plugin, StringContent, translate, build_icon
|
from openlp.core.lib import Plugin, StringContent, translate, build_icon
|
||||||
from openlp.plugins.remotes.lib import RemoteTab, OpenLPServer
|
from openlp.plugins.remotes.lib import RemoteTab, OpenLPServer
|
||||||
@ -67,6 +68,21 @@ class RemotesPlugin(Plugin):
|
|||||||
log.debug('initialise')
|
log.debug('initialise')
|
||||||
super(RemotesPlugin, self).initialise()
|
super(RemotesPlugin, self).initialise()
|
||||||
self.server = OpenLPServer()
|
self.server = OpenLPServer()
|
||||||
|
if not hasattr(self, 'remote_server_icon'):
|
||||||
|
self.remote_server_icon = QtGui.QLabel(self.main_window.status_bar)
|
||||||
|
size_policy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
|
||||||
|
size_policy.setHorizontalStretch(0)
|
||||||
|
size_policy.setVerticalStretch(0)
|
||||||
|
size_policy.setHeightForWidth(self.remote_server_icon.sizePolicy().hasHeightForWidth())
|
||||||
|
self.remote_server_icon.setSizePolicy(size_policy)
|
||||||
|
self.remote_server_icon.setFrameShadow(QtGui.QFrame.Plain)
|
||||||
|
self.remote_server_icon.setLineWidth(1)
|
||||||
|
self.remote_server_icon.setScaledContents(True)
|
||||||
|
self.remote_server_icon.setFixedSize(20, 20)
|
||||||
|
self.remote_server_icon.setObjectName('remote_server_icon')
|
||||||
|
self.main_window.status_bar.insertPermanentWidget(2, self.remote_server_icon)
|
||||||
|
self.settings_tab.remote_server_icon = self.remote_server_icon
|
||||||
|
self.settings_tab.generate_icon()
|
||||||
|
|
||||||
def finalise(self):
|
def finalise(self):
|
||||||
"""
|
"""
|
||||||
@ -104,9 +120,11 @@ class RemotesPlugin(Plugin):
|
|||||||
|
|
||||||
def config_update(self):
|
def config_update(self):
|
||||||
"""
|
"""
|
||||||
Called when Config is changed to restart the server on new address or port
|
Called when Config is changed to requests a restart with the server on new address or port
|
||||||
"""
|
"""
|
||||||
log.debug('remote config changed')
|
log.debug('remote config changed')
|
||||||
self.finalise()
|
QtGui.QMessageBox.information(self.main_window,
|
||||||
time.sleep(0.5)
|
translate('RemotePlugin', 'Server Config Change'),
|
||||||
self.initialise()
|
translate('RemotePlugin', 'Server configuration changes will require a restart '
|
||||||
|
'to take effect.'),
|
||||||
|
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok))
|
||||||
|
@ -34,7 +34,7 @@ code, like slots and loading and saving.
|
|||||||
The first class, commonly known as the **Dialog** class, is typically named
|
The first class, commonly known as the **Dialog** class, is typically named
|
||||||
``Ui_<name>Dialog``. It is a slightly modified version of the class that the
|
``Ui_<name>Dialog``. It is a slightly modified version of the class that the
|
||||||
``pyuic4`` command produces from Qt4's .ui file. Typical modifications will be
|
``pyuic4`` command produces from Qt4's .ui file. Typical modifications will be
|
||||||
converting most strings from "" to u'' and using OpenLP's ``translate()``
|
converting most strings from "" to '' and using OpenLP's ``translate()``
|
||||||
function for translating strings.
|
function for translating strings.
|
||||||
|
|
||||||
The second class, commonly known as the **Form** class, is typically named
|
The second class, commonly known as the **Form** class, is typically named
|
||||||
|
@ -43,8 +43,8 @@ class Ui_AuthorsDialog(object):
|
|||||||
Set up the UI for the dialog.
|
Set up the UI for the dialog.
|
||||||
"""
|
"""
|
||||||
authors_dialog.setObjectName('authors_dialog')
|
authors_dialog.setObjectName('authors_dialog')
|
||||||
|
authors_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
|
||||||
authors_dialog.resize(300, 10)
|
authors_dialog.resize(300, 10)
|
||||||
authors_dialog.setWindowIcon(build_icon(':/icon/openlp-logo-16x16.png'))
|
|
||||||
authors_dialog.setModal(True)
|
authors_dialog.setModal(True)
|
||||||
self.dialog_layout = QtGui.QVBoxLayout(authors_dialog)
|
self.dialog_layout = QtGui.QVBoxLayout(authors_dialog)
|
||||||
self.dialog_layout.setObjectName('dialog_layout')
|
self.dialog_layout.setObjectName('dialog_layout')
|
||||||
|
@ -264,7 +264,7 @@ class DuplicateSongRemovalForm(OpenLPWizard, RegistryProperties):
|
|||||||
self.break_search = True
|
self.break_search = True
|
||||||
self.plugin.media_item.on_search_text_button_clicked()
|
self.plugin.media_item.on_search_text_button_clicked()
|
||||||
|
|
||||||
def setDefaults(self):
|
def set_defaults(self):
|
||||||
"""
|
"""
|
||||||
Set default form values for the song import wizard.
|
Set default form values for the song import wizard.
|
||||||
"""
|
"""
|
||||||
|
@ -43,8 +43,8 @@ class Ui_EditSongDialog(object):
|
|||||||
"""
|
"""
|
||||||
def setupUi(self, edit_song_dialog):
|
def setupUi(self, edit_song_dialog):
|
||||||
edit_song_dialog.setObjectName('edit_song_dialog')
|
edit_song_dialog.setObjectName('edit_song_dialog')
|
||||||
|
edit_song_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
|
||||||
edit_song_dialog.resize(650, 400)
|
edit_song_dialog.resize(650, 400)
|
||||||
edit_song_dialog.setWindowIcon(build_icon(':/icon/openlp-logo-16x16.png'))
|
|
||||||
edit_song_dialog.setModal(True)
|
edit_song_dialog.setModal(True)
|
||||||
self.dialog_layout = QtGui.QVBoxLayout(edit_song_dialog)
|
self.dialog_layout = QtGui.QVBoxLayout(edit_song_dialog)
|
||||||
self.dialog_layout.setSpacing(8)
|
self.dialog_layout.setSpacing(8)
|
||||||
@ -118,13 +118,18 @@ class Ui_EditSongDialog(object):
|
|||||||
self.authors_group_box.setObjectName('authors_group_box')
|
self.authors_group_box.setObjectName('authors_group_box')
|
||||||
self.authors_layout = QtGui.QVBoxLayout(self.authors_group_box)
|
self.authors_layout = QtGui.QVBoxLayout(self.authors_group_box)
|
||||||
self.authors_layout.setObjectName('authors_layout')
|
self.authors_layout.setObjectName('authors_layout')
|
||||||
self.author_add_layout = QtGui.QHBoxLayout()
|
self.author_add_layout = QtGui.QVBoxLayout()
|
||||||
self.author_add_layout.setObjectName('author_add_layout')
|
self.author_add_layout.setObjectName('author_add_layout')
|
||||||
|
self.author_type_layout = QtGui.QHBoxLayout()
|
||||||
|
self.author_type_layout.setObjectName('author_type_layout')
|
||||||
self.authors_combo_box = create_combo_box(self.authors_group_box, 'authors_combo_box')
|
self.authors_combo_box = create_combo_box(self.authors_group_box, 'authors_combo_box')
|
||||||
self.author_add_layout.addWidget(self.authors_combo_box)
|
self.author_add_layout.addWidget(self.authors_combo_box)
|
||||||
|
self.author_types_combo_box = create_combo_box(self.authors_group_box, 'author_types_combo_box', editable=False)
|
||||||
|
self.author_type_layout.addWidget(self.author_types_combo_box)
|
||||||
self.author_add_button = QtGui.QPushButton(self.authors_group_box)
|
self.author_add_button = QtGui.QPushButton(self.authors_group_box)
|
||||||
self.author_add_button.setObjectName('author_add_button')
|
self.author_add_button.setObjectName('author_add_button')
|
||||||
self.author_add_layout.addWidget(self.author_add_button)
|
self.author_type_layout.addWidget(self.author_add_button)
|
||||||
|
self.author_add_layout.addLayout(self.author_type_layout)
|
||||||
self.authors_layout.addLayout(self.author_add_layout)
|
self.authors_layout.addLayout(self.author_add_layout)
|
||||||
self.authors_list_view = QtGui.QListWidget(self.authors_group_box)
|
self.authors_list_view = QtGui.QListWidget(self.authors_group_box)
|
||||||
self.authors_list_view.setAlternatingRowColors(True)
|
self.authors_list_view.setAlternatingRowColors(True)
|
||||||
@ -330,7 +335,7 @@ class Ui_EditSongDialog(object):
|
|||||||
translate('SongsPlugin.EditSongForm', '<strong>Warning:</strong> You have not entered a verse order.')
|
translate('SongsPlugin.EditSongForm', '<strong>Warning:</strong> You have not entered a verse order.')
|
||||||
|
|
||||||
|
|
||||||
def create_combo_box(parent, name):
|
def create_combo_box(parent, name, editable=True):
|
||||||
"""
|
"""
|
||||||
Utility method to generate a standard combo box for this dialog.
|
Utility method to generate a standard combo box for this dialog.
|
||||||
|
|
||||||
@ -340,7 +345,7 @@ def create_combo_box(parent, name):
|
|||||||
combo_box = QtGui.QComboBox(parent)
|
combo_box = QtGui.QComboBox(parent)
|
||||||
combo_box.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToMinimumContentsLength)
|
combo_box.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToMinimumContentsLength)
|
||||||
combo_box.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
|
combo_box.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
|
||||||
combo_box.setEditable(True)
|
combo_box.setEditable(editable)
|
||||||
combo_box.setInsertPolicy(QtGui.QComboBox.NoInsert)
|
combo_box.setInsertPolicy(QtGui.QComboBox.NoInsert)
|
||||||
combo_box.setObjectName(name)
|
combo_box.setObjectName(name)
|
||||||
return combo_box
|
return combo_box
|
||||||
|
@ -42,7 +42,7 @@ from openlp.core.common import Registry, RegistryProperties, AppLocation, UiStri
|
|||||||
from openlp.core.lib import FileDialog, PluginStatus, MediaType, create_separated_list
|
from openlp.core.lib import FileDialog, PluginStatus, MediaType, create_separated_list
|
||||||
from openlp.core.lib.ui import set_case_insensitive_completer, critical_error_message_box, find_and_set_in_combo_box
|
from openlp.core.lib.ui import set_case_insensitive_completer, critical_error_message_box, find_and_set_in_combo_box
|
||||||
from openlp.plugins.songs.lib import VerseType, clean_song
|
from openlp.plugins.songs.lib import VerseType, clean_song
|
||||||
from openlp.plugins.songs.lib.db import Book, Song, Author, Topic, MediaFile
|
from openlp.plugins.songs.lib.db import Book, Song, Author, AuthorSong, AuthorType, Topic, MediaFile
|
||||||
from openlp.plugins.songs.lib.ui import SongStrings
|
from openlp.plugins.songs.lib.ui import SongStrings
|
||||||
from openlp.plugins.songs.lib.xml import SongXML
|
from openlp.plugins.songs.lib.xml import SongXML
|
||||||
from openlp.plugins.songs.forms.editsongdialog import Ui_EditSongDialog
|
from openlp.plugins.songs.forms.editsongdialog import Ui_EditSongDialog
|
||||||
@ -107,6 +107,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties):
|
|||||||
self.audio_list_widget.setAlternatingRowColors(True)
|
self.audio_list_widget.setAlternatingRowColors(True)
|
||||||
self.find_verse_split = re.compile('---\[\]---\n', re.UNICODE)
|
self.find_verse_split = re.compile('---\[\]---\n', re.UNICODE)
|
||||||
self.whitespace = re.compile(r'\W+', re.UNICODE)
|
self.whitespace = re.compile(r'\W+', re.UNICODE)
|
||||||
|
self.find_tags = re.compile(u'\{/?\w+\}', re.UNICODE)
|
||||||
|
|
||||||
def _load_objects(self, cls, combo, cache):
|
def _load_objects(self, cls, combo, cache):
|
||||||
"""
|
"""
|
||||||
@ -122,12 +123,12 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties):
|
|||||||
combo.setItemData(row, obj.id)
|
combo.setItemData(row, obj.id)
|
||||||
set_case_insensitive_completer(cache, combo)
|
set_case_insensitive_completer(cache, combo)
|
||||||
|
|
||||||
def _add_author_to_list(self, author):
|
def _add_author_to_list(self, author, author_type):
|
||||||
"""
|
"""
|
||||||
Add an author to the author list.
|
Add an author to the author list.
|
||||||
"""
|
"""
|
||||||
author_item = QtGui.QListWidgetItem(str(author.display_name))
|
author_item = QtGui.QListWidgetItem(author.get_display_name(author_type))
|
||||||
author_item.setData(QtCore.Qt.UserRole, author.id)
|
author_item.setData(QtCore.Qt.UserRole, (author.id, author_type))
|
||||||
self.authors_list_view.addItem(author_item)
|
self.authors_list_view.addItem(author_item)
|
||||||
|
|
||||||
def _extract_verse_order(self, verse_order):
|
def _extract_verse_order(self, verse_order):
|
||||||
@ -217,8 +218,8 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties):
|
|||||||
if self.authors_list_view.count() == 0:
|
if self.authors_list_view.count() == 0:
|
||||||
self.song_tab_widget.setCurrentIndex(1)
|
self.song_tab_widget.setCurrentIndex(1)
|
||||||
self.authors_list_view.setFocus()
|
self.authors_list_view.setFocus()
|
||||||
critical_error_message_box(
|
critical_error_message_box(message=translate('SongsPlugin.EditSongForm',
|
||||||
message=translate('SongsPlugin.EditSongForm', 'You need to have an author for this song.'))
|
'You need to have an author for this song.'))
|
||||||
return False
|
return False
|
||||||
if self.verse_order_edit.text():
|
if self.verse_order_edit.text():
|
||||||
result = self._validate_verse_list(self.verse_order_edit.text(), self.verse_list_widget.rowCount())
|
result = self._validate_verse_list(self.verse_order_edit.text(), self.verse_list_widget.rowCount())
|
||||||
@ -234,8 +235,57 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties):
|
|||||||
self.manager.save_object(book)
|
self.manager.save_object(book)
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
# Validate tags (lp#1199639)
|
||||||
|
misplaced_tags = []
|
||||||
|
verse_tags = []
|
||||||
|
for i in range(self.verse_list_widget.rowCount()):
|
||||||
|
item = self.verse_list_widget.item(i, 0)
|
||||||
|
tags = self.find_tags.findall(item.text())
|
||||||
|
field = item.data(QtCore.Qt.UserRole)
|
||||||
|
verse_tags.append(field)
|
||||||
|
if not self._validate_tags(tags):
|
||||||
|
misplaced_tags.append('%s %s' % (VerseType.translated_name(field[0]), field[1:]))
|
||||||
|
if misplaced_tags:
|
||||||
|
critical_error_message_box(
|
||||||
|
message=translate('SongsPlugin.EditSongForm',
|
||||||
|
'There are misplaced formatting tags in the following verses:\n\n%s\n\n'
|
||||||
|
'Please correct these tags before continuing.' % ', '.join(misplaced_tags)))
|
||||||
|
return False
|
||||||
|
for tag in verse_tags:
|
||||||
|
if verse_tags.count(tag) > 26:
|
||||||
|
# lp#1310523: OpenLyrics allows only a-z variants of one verse:
|
||||||
|
# http://openlyrics.info/dataformat.html#verse-name
|
||||||
|
critical_error_message_box(message=translate(
|
||||||
|
'SongsPlugin.EditSongForm', 'You have %(count)s verses named %(name)s %(number)s. '
|
||||||
|
'You can have at most 26 verses with the same name' %
|
||||||
|
{'count': verse_tags.count(tag),
|
||||||
|
'name': VerseType.translated_name(tag[0]),
|
||||||
|
'number': tag[1:]}))
|
||||||
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def _validate_tags(self, tags):
|
||||||
|
"""
|
||||||
|
Validates a list of tags
|
||||||
|
Deletes the first affiliated tag pair which is located side by side in the list
|
||||||
|
and call itself recursively with the shortened tag list.
|
||||||
|
If there is any misplaced tag in the list, either the length of the tag list is not even,
|
||||||
|
or the function won't find any tag pairs side by side.
|
||||||
|
If there is no misplaced tag, the length of the list will be zero on any recursive run.
|
||||||
|
|
||||||
|
:param tags: A list of tags
|
||||||
|
:return: True if the function can't find any mismatched tags. Else False.
|
||||||
|
"""
|
||||||
|
if len(tags) == 0:
|
||||||
|
return True
|
||||||
|
if len(tags) % 2 != 0:
|
||||||
|
return False
|
||||||
|
for i in range(len(tags)-1):
|
||||||
|
if tags[i+1] == "{/" + tags[i][1:]:
|
||||||
|
del tags[i:i+2]
|
||||||
|
return self._validate_tags(tags)
|
||||||
|
return False
|
||||||
|
|
||||||
def _process_lyrics(self):
|
def _process_lyrics(self):
|
||||||
"""
|
"""
|
||||||
Process the lyric data entered by the user into the OpenLP XML format.
|
Process the lyric data entered by the user into the OpenLP XML format.
|
||||||
@ -302,6 +352,15 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties):
|
|||||||
self.authors.append(author.display_name)
|
self.authors.append(author.display_name)
|
||||||
set_case_insensitive_completer(self.authors, self.authors_combo_box)
|
set_case_insensitive_completer(self.authors, self.authors_combo_box)
|
||||||
|
|
||||||
|
# Types
|
||||||
|
self.author_types_combo_box.clear()
|
||||||
|
self.author_types_combo_box.addItem('')
|
||||||
|
# Don't iterate over the dictionary to give them this specific order
|
||||||
|
self.author_types_combo_box.addItem(AuthorType.Types[AuthorType.Words], AuthorType.Words)
|
||||||
|
self.author_types_combo_box.addItem(AuthorType.Types[AuthorType.Music], AuthorType.Music)
|
||||||
|
self.author_types_combo_box.addItem(AuthorType.Types[AuthorType.WordsAndMusic], AuthorType.WordsAndMusic)
|
||||||
|
self.author_types_combo_box.addItem(AuthorType.Types[AuthorType.Translation], AuthorType.Translation)
|
||||||
|
|
||||||
def load_topics(self):
|
def load_topics(self):
|
||||||
"""
|
"""
|
||||||
Load the topics into the combobox.
|
Load the topics into the combobox.
|
||||||
@ -454,10 +513,8 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties):
|
|||||||
self.tag_rows()
|
self.tag_rows()
|
||||||
# clear the results
|
# clear the results
|
||||||
self.authors_list_view.clear()
|
self.authors_list_view.clear()
|
||||||
for author in self.song.authors:
|
for author_song in self.song.authors_songs:
|
||||||
author_name = QtGui.QListWidgetItem(str(author.display_name))
|
self._add_author_to_list(author_song.author, author_song.author_type)
|
||||||
author_name.setData(QtCore.Qt.UserRole, author.id)
|
|
||||||
self.authors_list_view.addItem(author_name)
|
|
||||||
# clear the results
|
# clear the results
|
||||||
self.topics_list_view.clear()
|
self.topics_list_view.clear()
|
||||||
for topic in self.song.topics:
|
for topic in self.song.topics:
|
||||||
@ -496,6 +553,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
item = int(self.authors_combo_box.currentIndex())
|
item = int(self.authors_combo_box.currentIndex())
|
||||||
text = self.authors_combo_box.currentText().strip(' \r\n\t')
|
text = self.authors_combo_box.currentText().strip(' \r\n\t')
|
||||||
|
author_type = self.author_types_combo_box.itemData(self.author_types_combo_box.currentIndex())
|
||||||
# This if statement is for OS X, which doesn't seem to work well with
|
# This if statement is for OS X, which doesn't seem to work well with
|
||||||
# the QCompleter auto-completion class. See bug #812628.
|
# the QCompleter auto-completion class. See bug #812628.
|
||||||
if text in self.authors:
|
if text in self.authors:
|
||||||
@ -513,7 +571,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties):
|
|||||||
author = Author.populate(first_name=text.rsplit(' ', 1)[0], last_name=text.rsplit(' ', 1)[1],
|
author = Author.populate(first_name=text.rsplit(' ', 1)[0], last_name=text.rsplit(' ', 1)[1],
|
||||||
display_name=text)
|
display_name=text)
|
||||||
self.manager.save_object(author)
|
self.manager.save_object(author)
|
||||||
self._add_author_to_list(author)
|
self._add_author_to_list(author, author_type)
|
||||||
self.load_authors()
|
self.load_authors()
|
||||||
self.authors_combo_box.setCurrentIndex(0)
|
self.authors_combo_box.setCurrentIndex(0)
|
||||||
else:
|
else:
|
||||||
@ -521,11 +579,11 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties):
|
|||||||
elif item > 0:
|
elif item > 0:
|
||||||
item_id = (self.authors_combo_box.itemData(item))
|
item_id = (self.authors_combo_box.itemData(item))
|
||||||
author = self.manager.get_object(Author, item_id)
|
author = self.manager.get_object(Author, item_id)
|
||||||
if self.authors_list_view.findItems(str(author.display_name), QtCore.Qt.MatchExactly):
|
if self.authors_list_view.findItems(author.get_display_name(author_type), QtCore.Qt.MatchExactly):
|
||||||
critical_error_message_box(
|
critical_error_message_box(
|
||||||
message=translate('SongsPlugin.EditSongForm', 'This author is already in the list.'))
|
message=translate('SongsPlugin.EditSongForm', 'This author is already in the list.'))
|
||||||
else:
|
else:
|
||||||
self._add_author_to_list(author)
|
self._add_author_to_list(author, author_type)
|
||||||
self.authors_combo_box.setCurrentIndex(0)
|
self.authors_combo_box.setCurrentIndex(0)
|
||||||
else:
|
else:
|
||||||
QtGui.QMessageBox.warning(
|
QtGui.QMessageBox.warning(
|
||||||
@ -905,13 +963,13 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties):
|
|||||||
else:
|
else:
|
||||||
self.song.theme_name = None
|
self.song.theme_name = None
|
||||||
self._process_lyrics()
|
self._process_lyrics()
|
||||||
self.song.authors = []
|
self.song.authors_songs = []
|
||||||
for row in range(self.authors_list_view.count()):
|
for row in range(self.authors_list_view.count()):
|
||||||
item = self.authors_list_view.item(row)
|
item = self.authors_list_view.item(row)
|
||||||
author_id = (item.data(QtCore.Qt.UserRole))
|
author_song = AuthorSong()
|
||||||
author = self.manager.get_object(Author, author_id)
|
author_song.author_id = item.data(QtCore.Qt.UserRole)[0]
|
||||||
if author is not None:
|
author_song.author_type = item.data(QtCore.Qt.UserRole)[1]
|
||||||
self.song.authors.append(author)
|
self.song.authors_songs.append(author_song)
|
||||||
self.song.topics = []
|
self.song.topics = []
|
||||||
for row in range(self.topics_list_view.count()):
|
for row in range(self.topics_list_view.count()):
|
||||||
item = self.topics_list_view.item(row)
|
item = self.topics_list_view.item(row)
|
||||||
|
@ -37,6 +37,7 @@ from openlp.plugins.songs.lib import VerseType
|
|||||||
class Ui_EditVerseDialog(object):
|
class Ui_EditVerseDialog(object):
|
||||||
def setupUi(self, edit_verse_dialog):
|
def setupUi(self, edit_verse_dialog):
|
||||||
edit_verse_dialog.setObjectName('edit_verse_dialog')
|
edit_verse_dialog.setObjectName('edit_verse_dialog')
|
||||||
|
edit_verse_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
|
||||||
edit_verse_dialog.resize(400, 400)
|
edit_verse_dialog.resize(400, 400)
|
||||||
edit_verse_dialog.setModal(True)
|
edit_verse_dialog.setModal(True)
|
||||||
self.dialog_layout = QtGui.QVBoxLayout(edit_verse_dialog)
|
self.dialog_layout = QtGui.QVBoxLayout(edit_verse_dialog)
|
||||||
|
@ -122,8 +122,6 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog):
|
|||||||
text = text[:position + 4]
|
text = text[:position + 4]
|
||||||
match = VERSE_REGEX.match(text)
|
match = VERSE_REGEX.match(text)
|
||||||
if match:
|
if match:
|
||||||
# TODO: Not used, remove?
|
|
||||||
# verse_tag = match.group(1)
|
|
||||||
try:
|
try:
|
||||||
verse_num = int(match.group(2)) + 1
|
verse_num = int(match.group(2)) + 1
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
@ -42,10 +42,10 @@ class Ui_MediaFilesDialog(object):
|
|||||||
Set up the user interface.
|
Set up the user interface.
|
||||||
"""
|
"""
|
||||||
media_files_dialog.setObjectName('media_files_dialog')
|
media_files_dialog.setObjectName('media_files_dialog')
|
||||||
|
media_files_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
|
||||||
media_files_dialog.setWindowModality(QtCore.Qt.ApplicationModal)
|
media_files_dialog.setWindowModality(QtCore.Qt.ApplicationModal)
|
||||||
media_files_dialog.resize(400, 300)
|
media_files_dialog.resize(400, 300)
|
||||||
media_files_dialog.setModal(True)
|
media_files_dialog.setModal(True)
|
||||||
media_files_dialog.setWindowIcon(build_icon(':/icon/openlp-logo-16x16.png'))
|
|
||||||
self.files_vertical_layout = QtGui.QVBoxLayout(media_files_dialog)
|
self.files_vertical_layout = QtGui.QVBoxLayout(media_files_dialog)
|
||||||
self.files_vertical_layout.setSpacing(8)
|
self.files_vertical_layout.setSpacing(8)
|
||||||
self.files_vertical_layout.setMargin(8)
|
self.files_vertical_layout.setMargin(8)
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
from PyQt4 import QtGui
|
from PyQt4 import QtGui
|
||||||
|
|
||||||
from openlp.core.lib import translate
|
from openlp.core.lib import translate, build_icon
|
||||||
from openlp.core.lib.ui import create_button_box
|
from openlp.core.lib.ui import create_button_box
|
||||||
|
|
||||||
|
|
||||||
@ -42,6 +42,7 @@ class Ui_SongBookDialog(object):
|
|||||||
Set up the user interface.
|
Set up the user interface.
|
||||||
"""
|
"""
|
||||||
song_book_dialog.setObjectName('song_book_dialog')
|
song_book_dialog.setObjectName('song_book_dialog')
|
||||||
|
song_book_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
|
||||||
song_book_dialog.resize(300, 10)
|
song_book_dialog.resize(300, 10)
|
||||||
self.dialog_layout = QtGui.QVBoxLayout(song_book_dialog)
|
self.dialog_layout = QtGui.QVBoxLayout(song_book_dialog)
|
||||||
self.dialog_layout.setObjectName('dialog_layout')
|
self.dialog_layout.setObjectName('dialog_layout')
|
||||||
|
@ -231,11 +231,11 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
Opens a QFileDialog and writes the filenames to the given listbox.
|
Opens a QFileDialog and writes the filenames to the given listbox.
|
||||||
|
|
||||||
:param title: The title of the dialog (unicode).
|
:param title: The title of the dialog (str).
|
||||||
:param listbox: A listbox (QListWidget).
|
:param listbox: A listbox (QListWidget).
|
||||||
:param filters: The file extension filters. It should contain the file descriptions
|
:param filters: The file extension filters. It should contain the file descriptions as well as the file
|
||||||
as well as the file extensions. For example::
|
extensions. For example::
|
||||||
u'SongBeamer Files (*.sng)'
|
'SongBeamer Files (*.sng)'
|
||||||
"""
|
"""
|
||||||
if filters:
|
if filters:
|
||||||
filters += ';;'
|
filters += ';;'
|
||||||
@ -304,7 +304,7 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
self.source_page.emit(QtCore.SIGNAL('completeChanged()'))
|
self.source_page.emit(QtCore.SIGNAL('completeChanged()'))
|
||||||
|
|
||||||
def setDefaults(self):
|
def set_defaults(self):
|
||||||
"""
|
"""
|
||||||
Set default form values for the song import wizard.
|
Set default form values for the song import wizard.
|
||||||
"""
|
"""
|
||||||
|
@ -44,6 +44,7 @@ class Ui_SongMaintenanceDialog(object):
|
|||||||
Set up the user interface for the song maintenance dialog
|
Set up the user interface for the song maintenance dialog
|
||||||
"""
|
"""
|
||||||
song_maintenance_dialog.setObjectName('song_maintenance_dialog')
|
song_maintenance_dialog.setObjectName('song_maintenance_dialog')
|
||||||
|
song_maintenance_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
|
||||||
song_maintenance_dialog.setWindowModality(QtCore.Qt.ApplicationModal)
|
song_maintenance_dialog.setWindowModality(QtCore.Qt.ApplicationModal)
|
||||||
song_maintenance_dialog.resize(10, 350)
|
song_maintenance_dialog.resize(10, 350)
|
||||||
self.dialog_layout = QtGui.QGridLayout(song_maintenance_dialog)
|
self.dialog_layout = QtGui.QGridLayout(song_maintenance_dialog)
|
||||||
|
@ -319,8 +319,6 @@ class SongSelectForm(QtGui.QDialog, Ui_SongSelectDialog):
|
|||||||
def on_search_finished(self):
|
def on_search_finished(self):
|
||||||
"""
|
"""
|
||||||
Slot which is called when the search is completed.
|
Slot which is called when the search is completed.
|
||||||
|
|
||||||
:param songs:
|
|
||||||
"""
|
"""
|
||||||
self.application.process_events()
|
self.application.process_events()
|
||||||
self.search_progress_bar.setVisible(False)
|
self.search_progress_bar.setVisible(False)
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
from PyQt4 import QtGui
|
from PyQt4 import QtGui
|
||||||
|
|
||||||
from openlp.core.lib import translate
|
from openlp.core.lib import translate, build_icon
|
||||||
from openlp.core.lib.ui import create_button_box
|
from openlp.core.lib.ui import create_button_box
|
||||||
|
|
||||||
|
|
||||||
@ -42,6 +42,7 @@ class Ui_TopicsDialog(object):
|
|||||||
Set up the user interface for the topics dialog.
|
Set up the user interface for the topics dialog.
|
||||||
"""
|
"""
|
||||||
topics_dialog.setObjectName('topics_dialog')
|
topics_dialog.setObjectName('topics_dialog')
|
||||||
|
topics_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
|
||||||
topics_dialog.resize(300, 10)
|
topics_dialog.resize(300, 10)
|
||||||
self.dialog_layout = QtGui.QVBoxLayout(topics_dialog)
|
self.dialog_layout = QtGui.QVBoxLayout(topics_dialog)
|
||||||
self.dialog_layout.setObjectName('dialog_layout')
|
self.dialog_layout.setObjectName('dialog_layout')
|
||||||
|
@ -206,14 +206,14 @@ class VerseType(object):
|
|||||||
Return the VerseType for a given tag
|
Return the VerseType for a given tag
|
||||||
|
|
||||||
:param verse_tag: The string to return a VerseType for
|
:param verse_tag: The string to return a VerseType for
|
||||||
:param default: Default return value if no matching tag is found
|
:param default: Default return value if no matching tag is found (a valid VerseType or None)
|
||||||
:return: A VerseType of the tag
|
:return: A VerseType of the tag
|
||||||
"""
|
"""
|
||||||
verse_tag = verse_tag[0].lower()
|
verse_tag = verse_tag[0].lower()
|
||||||
for num, tag in enumerate(VerseType.tags):
|
for num, tag in enumerate(VerseType.tags):
|
||||||
if verse_tag == tag:
|
if verse_tag == tag:
|
||||||
return num
|
return num
|
||||||
if len(VerseType.names) > default:
|
if default in range(0, len(VerseType.names)) or default is None:
|
||||||
return default
|
return default
|
||||||
else:
|
else:
|
||||||
return VerseType.Other
|
return VerseType.Other
|
||||||
@ -231,7 +231,7 @@ class VerseType(object):
|
|||||||
for num, tag in enumerate(VerseType.translated_tags):
|
for num, tag in enumerate(VerseType.translated_tags):
|
||||||
if verse_tag == tag:
|
if verse_tag == tag:
|
||||||
return num
|
return num
|
||||||
if len(VerseType.names) > default:
|
if default in range(0, len(VerseType.names)) or default is None:
|
||||||
return default
|
return default
|
||||||
else:
|
else:
|
||||||
return VerseType.Other
|
return VerseType.Other
|
||||||
@ -390,7 +390,7 @@ def clean_song(manager, song):
|
|||||||
verses = SongXML().get_verses(song.lyrics)
|
verses = SongXML().get_verses(song.lyrics)
|
||||||
song.search_lyrics = ' '.join([clean_string(verse[1]) for verse in verses])
|
song.search_lyrics = ' '.join([clean_string(verse[1]) for verse in verses])
|
||||||
# The song does not have any author, add one.
|
# The song does not have any author, add one.
|
||||||
if not song.authors:
|
if not song.authors and not song.authors_songs: # Need to check both relations
|
||||||
name = SongStrings.AuthorUnknown
|
name = SongStrings.AuthorUnknown
|
||||||
author = manager.get_object_filtered(Author, Author.display_name == name)
|
author = manager.get_object_filtered(Author, Author.display_name == name)
|
||||||
if author is None:
|
if author is None:
|
||||||
@ -434,7 +434,7 @@ def strip_rtf(text, default_encoding=None):
|
|||||||
# Current font is the font tag we last met.
|
# Current font is the font tag we last met.
|
||||||
font = ''
|
font = ''
|
||||||
# Character encoding is defined inside fonttable.
|
# Character encoding is defined inside fonttable.
|
||||||
# font_table could contain eg u'0': u'cp1252'
|
# font_table could contain eg '0': u'cp1252'
|
||||||
font_table = {'': ''}
|
font_table = {'': ''}
|
||||||
# Stack of things to keep track of when entering/leaving groups.
|
# Stack of things to keep track of when entering/leaving groups.
|
||||||
stack = []
|
stack = []
|
||||||
|
@ -35,19 +35,52 @@ import re
|
|||||||
|
|
||||||
from sqlalchemy import Column, ForeignKey, Table, types
|
from sqlalchemy import Column, ForeignKey, Table, types
|
||||||
from sqlalchemy.orm import mapper, relation, reconstructor
|
from sqlalchemy.orm import mapper, relation, reconstructor
|
||||||
from sqlalchemy.sql.expression import func
|
from sqlalchemy.sql.expression import func, text
|
||||||
|
|
||||||
from openlp.core.lib.db import BaseModel, init_db
|
from openlp.core.lib.db import BaseModel, init_db
|
||||||
from openlp.core.utils import get_natural_key
|
from openlp.core.utils import get_natural_key
|
||||||
|
from openlp.core.lib import translate
|
||||||
|
|
||||||
|
|
||||||
class Author(BaseModel):
|
class Author(BaseModel):
|
||||||
"""
|
"""
|
||||||
Author model
|
Author model
|
||||||
"""
|
"""
|
||||||
|
def get_display_name(self, author_type=None):
|
||||||
|
if author_type:
|
||||||
|
return "%s (%s)" % (self.display_name, AuthorType.Types[author_type])
|
||||||
|
return self.display_name
|
||||||
|
|
||||||
|
|
||||||
|
class AuthorSong(BaseModel):
|
||||||
|
"""
|
||||||
|
Relationship between Authors and Songs (many to many).
|
||||||
|
Need to define this relationship table explicit to get access to the
|
||||||
|
Association Object (author_type).
|
||||||
|
http://docs.sqlalchemy.org/en/latest/orm/relationships.html#association-object
|
||||||
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class AuthorType(object):
|
||||||
|
"""
|
||||||
|
Enumeration for Author types.
|
||||||
|
They are defined by OpenLyrics: http://openlyrics.info/dataformat.html#authors
|
||||||
|
|
||||||
|
The 'words+music' type is not an official type, but is provided for convenience.
|
||||||
|
"""
|
||||||
|
Words = 'words'
|
||||||
|
Music = 'music'
|
||||||
|
WordsAndMusic = 'words+music'
|
||||||
|
Translation = 'translation'
|
||||||
|
Types = {
|
||||||
|
Words: translate('OpenLP.Ui', 'Words'),
|
||||||
|
Music: translate('OpenLP.Ui', 'Music'),
|
||||||
|
WordsAndMusic: translate('OpenLP.Ui', 'Words and Music'),
|
||||||
|
Translation: translate('OpenLP.Ui', 'Translation')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class Book(BaseModel):
|
class Book(BaseModel):
|
||||||
"""
|
"""
|
||||||
Book model
|
Book model
|
||||||
@ -67,6 +100,7 @@ class Song(BaseModel):
|
|||||||
"""
|
"""
|
||||||
Song model
|
Song model
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.sort_key = []
|
self.sort_key = []
|
||||||
|
|
||||||
@ -120,6 +154,7 @@ def init_schema(url):
|
|||||||
|
|
||||||
* author_id
|
* author_id
|
||||||
* song_id
|
* song_id
|
||||||
|
* author_type
|
||||||
|
|
||||||
**media_files Table**
|
**media_files Table**
|
||||||
* id
|
* id
|
||||||
@ -230,7 +265,8 @@ def init_schema(url):
|
|||||||
authors_songs_table = Table(
|
authors_songs_table = Table(
|
||||||
'authors_songs', metadata,
|
'authors_songs', metadata,
|
||||||
Column('author_id', types.Integer(), ForeignKey('authors.id'), primary_key=True),
|
Column('author_id', types.Integer(), ForeignKey('authors.id'), primary_key=True),
|
||||||
Column('song_id', types.Integer(), ForeignKey('songs.id'), primary_key=True)
|
Column('song_id', types.Integer(), ForeignKey('songs.id'), primary_key=True),
|
||||||
|
Column('author_type', types.String(), primary_key=True, nullable=False, server_default=text('""'))
|
||||||
)
|
)
|
||||||
|
|
||||||
# Definition of the "songs_topics" table
|
# Definition of the "songs_topics" table
|
||||||
@ -241,10 +277,15 @@ def init_schema(url):
|
|||||||
)
|
)
|
||||||
|
|
||||||
mapper(Author, authors_table)
|
mapper(Author, authors_table)
|
||||||
|
mapper(AuthorSong, authors_songs_table, properties={
|
||||||
|
'author': relation(Author)
|
||||||
|
})
|
||||||
mapper(Book, song_books_table)
|
mapper(Book, song_books_table)
|
||||||
mapper(MediaFile, media_files_table)
|
mapper(MediaFile, media_files_table)
|
||||||
mapper(Song, songs_table, properties={
|
mapper(Song, songs_table, properties={
|
||||||
'authors': relation(Author, backref='songs', secondary=authors_songs_table, lazy=False),
|
# Use the authors_songs relation when you need access to the 'author_type' attribute.
|
||||||
|
'authors_songs': relation(AuthorSong, cascade="all, delete-orphan"),
|
||||||
|
'authors': relation(Author, secondary=authors_songs_table),
|
||||||
'book': relation(Book, backref='songs'),
|
'book': relation(Book, backref='songs'),
|
||||||
'media_files': relation(MediaFile, backref='songs', order_by=media_files_table.c.weight),
|
'media_files': relation(MediaFile, backref='songs', order_by=media_files_table.c.weight),
|
||||||
'topics': relation(Topic, backref='songs', secondary=songs_topics_table)
|
'topics': relation(Topic, backref='songs', secondary=songs_topics_table)
|
||||||
|
@ -292,7 +292,7 @@ class EasySlidesImport(SongImport):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def _extract_region(self, line):
|
def _extract_region(self, line):
|
||||||
# this was true already: line[0:7] == u'[region':
|
# this was true already: line[0:7] == '[region':
|
||||||
"""
|
"""
|
||||||
Extract the region from text
|
Extract the region from text
|
||||||
|
|
||||||
|
@ -34,13 +34,13 @@ EasyWorship song databases into the current installation database.
|
|||||||
import os
|
import os
|
||||||
import struct
|
import struct
|
||||||
import re
|
import re
|
||||||
|
import zlib
|
||||||
|
|
||||||
from openlp.core.lib import translate
|
from openlp.core.lib import translate
|
||||||
from openlp.plugins.songs.lib import VerseType
|
from openlp.plugins.songs.lib import VerseType
|
||||||
from openlp.plugins.songs.lib import retrieve_windows_encoding, strip_rtf
|
from openlp.plugins.songs.lib import retrieve_windows_encoding, strip_rtf
|
||||||
from .songimport import SongImport
|
from .songimport import SongImport
|
||||||
|
|
||||||
RTF_STRIPPING_REGEX = re.compile(r'\{\\tx[^}]*\}')
|
|
||||||
# regex: at least two newlines, can have spaces between them
|
# regex: at least two newlines, can have spaces between them
|
||||||
SLIDE_BREAK_REGEX = re.compile(r'\n *?\n[\n ]*')
|
SLIDE_BREAK_REGEX = re.compile(r'\n *?\n[\n ]*')
|
||||||
NUMBER_REGEX = re.compile(r'[0-9]+')
|
NUMBER_REGEX = re.compile(r'[0-9]+')
|
||||||
@ -74,12 +74,130 @@ class EasyWorshipSongImport(SongImport):
|
|||||||
"""
|
"""
|
||||||
def __init__(self, manager, **kwargs):
|
def __init__(self, manager, **kwargs):
|
||||||
super(EasyWorshipSongImport, self).__init__(manager, **kwargs)
|
super(EasyWorshipSongImport, self).__init__(manager, **kwargs)
|
||||||
|
self.entry_error_log = ''
|
||||||
|
|
||||||
def do_import(self):
|
def do_import(self):
|
||||||
"""
|
"""
|
||||||
Import the songs
|
Determines the type of file to import and calls the appropiate method
|
||||||
|
"""
|
||||||
|
if self.import_source.lower().endswith('ews'):
|
||||||
|
self.import_ews()
|
||||||
|
else:
|
||||||
|
self.import_db()
|
||||||
|
|
||||||
:return:
|
def import_ews(self):
|
||||||
|
"""
|
||||||
|
Import the songs from service file
|
||||||
|
The full spec of the ews files can be found here:
|
||||||
|
https://github.com/meinders/lithium-ews/blob/master/docs/ews%20file%20format.md
|
||||||
|
or here: http://wiki.openlp.org/Development:EasyWorship_EWS_Format
|
||||||
|
"""
|
||||||
|
# Open ews file if it exists
|
||||||
|
if not os.path.isfile(self.import_source):
|
||||||
|
log.debug('Given ews file does not exists.')
|
||||||
|
return
|
||||||
|
# Make sure there is room for at least a header and one entry
|
||||||
|
if os.path.getsize(self.import_source) < 892:
|
||||||
|
log.debug('Given ews file is to small to contain valid data.')
|
||||||
|
return
|
||||||
|
# Take a stab at how text is encoded
|
||||||
|
self.encoding = 'cp1252'
|
||||||
|
self.encoding = retrieve_windows_encoding(self.encoding)
|
||||||
|
if not self.encoding:
|
||||||
|
log.debug('No encoding set.')
|
||||||
|
return
|
||||||
|
self.ews_file = open(self.import_source, 'rb')
|
||||||
|
# EWS header, version '1.6'/' 3'/' 5':
|
||||||
|
# Offset Field Data type Length Details
|
||||||
|
# --------------------------------------------------------------------------------------------------
|
||||||
|
# 0 Filetype string 38 Specifies the file type and version.
|
||||||
|
# "EasyWorship Schedule File Version 1.6" or
|
||||||
|
# "EasyWorship Schedule File Version 3" or
|
||||||
|
# "EasyWorship Schedule File Version 5"
|
||||||
|
# 40/48/56 Entry count int32le 4 Number of items in the schedule
|
||||||
|
# 44/52/60 Entry length int16le 2 Length of schedule entries: 0x0718 = 1816
|
||||||
|
# Get file version
|
||||||
|
type, = struct.unpack('<38s', self.ews_file.read(38))
|
||||||
|
version = type.decode()[-3:]
|
||||||
|
# Set fileposition based on filetype/version
|
||||||
|
file_pos = 0
|
||||||
|
if version == ' 5':
|
||||||
|
file_pos = 56
|
||||||
|
elif version == ' 3':
|
||||||
|
file_pos = 48
|
||||||
|
elif version == '1.6':
|
||||||
|
file_pos = 40
|
||||||
|
else:
|
||||||
|
log.debug('Given ews file is of unknown version.')
|
||||||
|
return
|
||||||
|
entry_count = self.get_i32(file_pos)
|
||||||
|
entry_length = self.get_i16(file_pos+4)
|
||||||
|
file_pos += 6
|
||||||
|
self.import_wizard.progress_bar.setMaximum(entry_count)
|
||||||
|
# Loop over songs
|
||||||
|
for i in range(entry_count):
|
||||||
|
# Load EWS entry metadata:
|
||||||
|
# Offset Field Data type Length Details
|
||||||
|
# ------------------------------------------------------------------------------------------------
|
||||||
|
# 0 Title cstring 50
|
||||||
|
# 307 Author cstring 50
|
||||||
|
# 358 Copyright cstring 100
|
||||||
|
# 459 Administrator cstring 50
|
||||||
|
# 800 Content pointer int32le 4 Position of the content for this entry.
|
||||||
|
# 820 Content type int32le 4 0x01 = Song, 0x02 = Scripture, 0x03 = Presentation,
|
||||||
|
# 0x04 = Video, 0x05 = Live video, 0x07 = Image,
|
||||||
|
# 0x08 = Audio, 0x09 = Web
|
||||||
|
# 1410 Song number cstring 10
|
||||||
|
self.set_defaults()
|
||||||
|
self.title = self.get_string(file_pos + 0, 50)
|
||||||
|
authors = self.get_string(file_pos + 307, 50)
|
||||||
|
copyright = self.get_string(file_pos + 358, 100)
|
||||||
|
admin = self.get_string(file_pos + 459, 50)
|
||||||
|
cont_ptr = self.get_i32(file_pos + 800)
|
||||||
|
cont_type = self.get_i32(file_pos + 820)
|
||||||
|
self.ccli_number = self.get_string(file_pos + 1410, 10)
|
||||||
|
# Only handle content type 1 (songs)
|
||||||
|
if cont_type != 1:
|
||||||
|
file_pos += entry_length
|
||||||
|
continue
|
||||||
|
# Load song content
|
||||||
|
# Offset Field Data type Length Details
|
||||||
|
# ------------------------------------------------------------------------------------------------
|
||||||
|
# 0 Length int32le 4 Length (L) of content, including the compressed content
|
||||||
|
# and the following fields (14 bytes total).
|
||||||
|
# 4 Content string L-14 Content compressed with deflate.
|
||||||
|
# Checksum int32be 4 Alder-32 checksum.
|
||||||
|
# (unknown) 4 0x51 0x4b 0x03 0x04
|
||||||
|
# Content length int32le 4 Length of content after decompression
|
||||||
|
content_length = self.get_i32(cont_ptr)
|
||||||
|
deflated_content = self.get_bytes(cont_ptr + 4, content_length - 10)
|
||||||
|
deflated_length = self.get_i32(cont_ptr + 4 + content_length - 6)
|
||||||
|
inflated_content = zlib.decompress(deflated_content, 15, deflated_length)
|
||||||
|
if copyright:
|
||||||
|
self.copyright = copyright
|
||||||
|
if admin:
|
||||||
|
if copyright:
|
||||||
|
self.copyright += ', '
|
||||||
|
self.copyright += translate('SongsPlugin.EasyWorshipSongImport',
|
||||||
|
'Administered by %s') % admin
|
||||||
|
# Set the SongImport object members.
|
||||||
|
self.set_song_import_object(authors, inflated_content)
|
||||||
|
if self.stop_import_flag:
|
||||||
|
break
|
||||||
|
if self.entry_error_log:
|
||||||
|
self.log_error(self.import_source,
|
||||||
|
translate('SongsPlugin.EasyWorshipSongImport', '"%s" could not be imported. %s')
|
||||||
|
% (self.title, self.entry_error_log))
|
||||||
|
self.entry_error_log = ''
|
||||||
|
elif not self.finish():
|
||||||
|
self.log_error(self.import_source)
|
||||||
|
# Set file_pos for next entry
|
||||||
|
file_pos += entry_length
|
||||||
|
self.ews_file.close()
|
||||||
|
|
||||||
|
def import_db(self):
|
||||||
|
"""
|
||||||
|
Import the songs from the database
|
||||||
"""
|
"""
|
||||||
# Open the DB and MB files if they exist
|
# Open the DB and MB files if they exist
|
||||||
import_source_mb = self.import_source.replace('.DB', '.MB')
|
import_source_mb = self.import_source.replace('.DB', '.MB')
|
||||||
@ -169,36 +287,71 @@ class EasyWorshipSongImport(SongImport):
|
|||||||
raw_record = db_file.read(record_size)
|
raw_record = db_file.read(record_size)
|
||||||
self.fields = self.record_structure.unpack(raw_record)
|
self.fields = self.record_structure.unpack(raw_record)
|
||||||
self.set_defaults()
|
self.set_defaults()
|
||||||
self.title = self.get_field(fi_title).decode()
|
self.title = self.get_field(fi_title).decode('unicode-escape')
|
||||||
# Get remaining fields.
|
# Get remaining fields.
|
||||||
copy = self.get_field(fi_copy)
|
copy = self.get_field(fi_copy)
|
||||||
admin = self.get_field(fi_admin)
|
admin = self.get_field(fi_admin)
|
||||||
ccli = self.get_field(fi_ccli)
|
ccli = self.get_field(fi_ccli)
|
||||||
authors = self.get_field(fi_author)
|
authors = self.get_field(fi_author)
|
||||||
words = self.get_field(fi_words)
|
words = self.get_field(fi_words)
|
||||||
# Set the SongImport object members.
|
|
||||||
if copy:
|
if copy:
|
||||||
self.copyright = copy.decode()
|
self.copyright = copy.decode('unicode-escape')
|
||||||
if admin:
|
if admin:
|
||||||
if copy:
|
if copy:
|
||||||
self.copyright += ', '
|
self.copyright += ', '
|
||||||
self.copyright += translate('SongsPlugin.EasyWorshipSongImport',
|
self.copyright += translate('SongsPlugin.EasyWorshipSongImport',
|
||||||
'Administered by %s') % admin.decode()
|
'Administered by %s') % admin.decode('unicode-escape')
|
||||||
if ccli:
|
if ccli:
|
||||||
self.ccli_number = ccli.decode()
|
self.ccli_number = ccli.decode('unicode-escape')
|
||||||
|
if authors:
|
||||||
|
authors = authors.decode('unicode-escape')
|
||||||
|
else:
|
||||||
|
authors = ''
|
||||||
|
# Set the SongImport object members.
|
||||||
|
self.set_song_import_object(authors, words)
|
||||||
|
if self.stop_import_flag:
|
||||||
|
break
|
||||||
|
if self.entry_error_log:
|
||||||
|
self.log_error(self.import_source,
|
||||||
|
translate('SongsPlugin.EasyWorshipSongImport', '"%s" could not be imported. %s')
|
||||||
|
% (self.title, self.entry_error_log))
|
||||||
|
self.entry_error_log = ''
|
||||||
|
elif not self.finish():
|
||||||
|
self.log_error(self.import_source)
|
||||||
|
db_file.close()
|
||||||
|
self.memo_file.close()
|
||||||
|
|
||||||
|
def set_song_import_object(self, authors, words):
|
||||||
|
"""
|
||||||
|
Set the SongImport object members.
|
||||||
|
|
||||||
|
:param authors: String with authons
|
||||||
|
:param words: Bytes with rtf-encoding
|
||||||
|
"""
|
||||||
if authors:
|
if authors:
|
||||||
# Split up the authors
|
# Split up the authors
|
||||||
author_list = authors.split(b'/')
|
author_list = authors.split('/')
|
||||||
if len(author_list) < 2:
|
if len(author_list) < 2:
|
||||||
author_list = authors.split(b';')
|
author_list = authors.split(';')
|
||||||
if len(author_list) < 2:
|
if len(author_list) < 2:
|
||||||
author_list = authors.split(b',')
|
author_list = authors.split(',')
|
||||||
for author_name in author_list:
|
for author_name in author_list:
|
||||||
self.add_author(author_name.decode().strip())
|
self.add_author(author_name.strip())
|
||||||
if words:
|
if words:
|
||||||
# Format the lyrics
|
# Format the lyrics
|
||||||
result = strip_rtf(words.decode(), self.encoding)
|
result = None
|
||||||
|
decoded_words = None
|
||||||
|
try:
|
||||||
|
decoded_words = words.decode()
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
# The unicode chars in the rtf was not escaped in the expected manor
|
||||||
|
self.entry_error_log = translate('SongsPlugin.EasyWorshipSongImport',
|
||||||
|
'Unexpected data formatting.')
|
||||||
|
return
|
||||||
|
result = strip_rtf(decoded_words, self.encoding)
|
||||||
if result is None:
|
if result is None:
|
||||||
|
self.entry_error_log = translate('SongsPlugin.EasyWorshipSongImport',
|
||||||
|
'No song text found.')
|
||||||
return
|
return
|
||||||
words, self.encoding = result
|
words, self.encoding = result
|
||||||
verse_type = VerseType.tags[VerseType.Verse]
|
verse_type = VerseType.tags[VerseType.Verse]
|
||||||
@ -236,19 +389,12 @@ class EasyWorshipSongImport(SongImport):
|
|||||||
if len(self.comments) > 5:
|
if len(self.comments) > 5:
|
||||||
self.comments += str(translate('SongsPlugin.EasyWorshipSongImport',
|
self.comments += str(translate('SongsPlugin.EasyWorshipSongImport',
|
||||||
'\n[above are Song Tags with notes imported from EasyWorship]'))
|
'\n[above are Song Tags with notes imported from EasyWorship]'))
|
||||||
if self.stop_import_flag:
|
|
||||||
break
|
|
||||||
if not self.finish():
|
|
||||||
self.log_error(self.import_source)
|
|
||||||
db_file.close()
|
|
||||||
self.memo_file.close()
|
|
||||||
|
|
||||||
def find_field(self, field_name):
|
def find_field(self, field_name):
|
||||||
"""
|
"""
|
||||||
Find a field in the descriptions
|
Find a field in the descriptions
|
||||||
|
|
||||||
:param field_name: field to find
|
:param field_name: field to find
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
return [i for i, x in enumerate(self.field_descriptions) if x.name == field_name][0]
|
return [i for i, x in enumerate(self.field_descriptions) if x.name == field_name][0]
|
||||||
|
|
||||||
@ -285,7 +431,7 @@ class EasyWorshipSongImport(SongImport):
|
|||||||
Extract the field
|
Extract the field
|
||||||
|
|
||||||
:param field_desc_index: Field index value
|
:param field_desc_index: Field index value
|
||||||
:return:
|
:return: The field value
|
||||||
"""
|
"""
|
||||||
field = self.fields[field_desc_index]
|
field = self.fields[field_desc_index]
|
||||||
field_desc = self.field_descriptions[field_desc_index]
|
field_desc = self.field_descriptions[field_desc_index]
|
||||||
@ -323,3 +469,52 @@ class EasyWorshipSongImport(SongImport):
|
|||||||
return self.memo_file.read(blob_size)
|
return self.memo_file.read(blob_size)
|
||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
def get_bytes(self, pos, length):
|
||||||
|
"""
|
||||||
|
Get bytes from ews_file
|
||||||
|
|
||||||
|
:param pos: Position to read from
|
||||||
|
:param length: Bytes to read
|
||||||
|
:return: Bytes read
|
||||||
|
"""
|
||||||
|
self.ews_file.seek(pos)
|
||||||
|
return self.ews_file.read(length)
|
||||||
|
|
||||||
|
def get_string(self, pos, length):
|
||||||
|
"""
|
||||||
|
Get string from ews_file
|
||||||
|
|
||||||
|
:param pos: Position to read from
|
||||||
|
:param length: Characters to read
|
||||||
|
:return: String read
|
||||||
|
"""
|
||||||
|
bytes = self.get_bytes(pos, length)
|
||||||
|
mask = '<' + str(length) + 's'
|
||||||
|
byte_str, = struct.unpack(mask, bytes)
|
||||||
|
return byte_str.decode('unicode-escape').replace('\0', '').strip()
|
||||||
|
|
||||||
|
def get_i16(self, pos):
|
||||||
|
"""
|
||||||
|
Get short int from ews_file
|
||||||
|
|
||||||
|
:param pos: Position to read from
|
||||||
|
:return: Short integer read
|
||||||
|
"""
|
||||||
|
|
||||||
|
bytes = self.get_bytes(pos, 2)
|
||||||
|
mask = '<h'
|
||||||
|
number, = struct.unpack(mask, bytes)
|
||||||
|
return number
|
||||||
|
|
||||||
|
def get_i32(self, pos):
|
||||||
|
"""
|
||||||
|
Get long int from ews_file
|
||||||
|
|
||||||
|
:param pos: Position to read from
|
||||||
|
:return: Long integer read
|
||||||
|
"""
|
||||||
|
bytes = self.get_bytes(pos, 4)
|
||||||
|
mask = '<i'
|
||||||
|
number, = struct.unpack(mask, bytes)
|
||||||
|
return number
|
||||||
|
@ -153,19 +153,20 @@ class SongFormat(object):
|
|||||||
CCLI = 3
|
CCLI = 3
|
||||||
DreamBeam = 4
|
DreamBeam = 4
|
||||||
EasySlides = 5
|
EasySlides = 5
|
||||||
EasyWorship = 6
|
EasyWorshipDB = 6
|
||||||
FoilPresenter = 7
|
EasyWorshipService = 7
|
||||||
MediaShout = 8
|
FoilPresenter = 8
|
||||||
OpenSong = 9
|
MediaShout = 9
|
||||||
PowerSong = 10
|
OpenSong = 10
|
||||||
SongBeamer = 11
|
PowerSong = 11
|
||||||
SongPro = 12
|
SongBeamer = 12
|
||||||
SongShowPlus = 13
|
SongPro = 13
|
||||||
SongsOfFellowship = 14
|
SongShowPlus = 14
|
||||||
SundayPlus = 15
|
SongsOfFellowship = 15
|
||||||
WordsOfWorship = 16
|
SundayPlus = 16
|
||||||
WorshipCenterPro = 17
|
WordsOfWorship = 17
|
||||||
ZionWorx = 18
|
WorshipCenterPro = 18
|
||||||
|
ZionWorx = 19
|
||||||
|
|
||||||
# Set optional attribute defaults
|
# Set optional attribute defaults
|
||||||
__defaults__ = {
|
__defaults__ = {
|
||||||
@ -224,13 +225,20 @@ class SongFormat(object):
|
|||||||
'selectMode': SongFormatSelect.SingleFile,
|
'selectMode': SongFormatSelect.SingleFile,
|
||||||
'filter': '%s (*.xml)' % translate('SongsPlugin.ImportWizardForm', 'EasySlides XML File')
|
'filter': '%s (*.xml)' % translate('SongsPlugin.ImportWizardForm', 'EasySlides XML File')
|
||||||
},
|
},
|
||||||
EasyWorship: {
|
EasyWorshipDB: {
|
||||||
'class': EasyWorshipSongImport,
|
'class': EasyWorshipSongImport,
|
||||||
'name': 'EasyWorship',
|
'name': 'EasyWorship Song Database',
|
||||||
'prefix': 'ew',
|
'prefix': 'ew',
|
||||||
'selectMode': SongFormatSelect.SingleFile,
|
'selectMode': SongFormatSelect.SingleFile,
|
||||||
'filter': '%s (*.db)' % translate('SongsPlugin.ImportWizardForm', 'EasyWorship Song Database')
|
'filter': '%s (*.db)' % translate('SongsPlugin.ImportWizardForm', 'EasyWorship Song Database')
|
||||||
},
|
},
|
||||||
|
EasyWorshipService: {
|
||||||
|
'class': EasyWorshipSongImport,
|
||||||
|
'name': 'EasyWorship Service File',
|
||||||
|
'prefix': 'ew',
|
||||||
|
'selectMode': SongFormatSelect.SingleFile,
|
||||||
|
'filter': '%s (*.ews)' % translate('SongsPlugin.ImportWizardForm', 'EasyWorship Service File')
|
||||||
|
},
|
||||||
FoilPresenter: {
|
FoilPresenter: {
|
||||||
'class': FoilPresenterImport,
|
'class': FoilPresenterImport,
|
||||||
'name': 'Foilpresenter',
|
'name': 'Foilpresenter',
|
||||||
@ -341,7 +349,8 @@ class SongFormat(object):
|
|||||||
SongFormat.CCLI,
|
SongFormat.CCLI,
|
||||||
SongFormat.DreamBeam,
|
SongFormat.DreamBeam,
|
||||||
SongFormat.EasySlides,
|
SongFormat.EasySlides,
|
||||||
SongFormat.EasyWorship,
|
SongFormat.EasyWorshipDB,
|
||||||
|
SongFormat.EasyWorshipService,
|
||||||
SongFormat.FoilPresenter,
|
SongFormat.FoilPresenter,
|
||||||
SongFormat.MediaShout,
|
SongFormat.MediaShout,
|
||||||
SongFormat.OpenSong,
|
SongFormat.OpenSong,
|
||||||
|
@ -36,15 +36,15 @@ from PyQt4 import QtCore, QtGui
|
|||||||
from sqlalchemy.sql import or_
|
from sqlalchemy.sql import or_
|
||||||
|
|
||||||
from openlp.core.common import Registry, AppLocation, Settings, check_directory_exists, UiStrings, translate
|
from openlp.core.common import Registry, AppLocation, Settings, check_directory_exists, UiStrings, translate
|
||||||
from openlp.core.lib import MediaManagerItem, ItemCapabilities, PluginStatus, ServiceItemContext, check_item_selected, \
|
from openlp.core.lib import MediaManagerItem, ItemCapabilities, PluginStatus, ServiceItem, ServiceItemContext, \
|
||||||
create_separated_list
|
check_item_selected, create_separated_list
|
||||||
from openlp.core.lib.ui import create_widget_action
|
from openlp.core.lib.ui import create_widget_action
|
||||||
from openlp.plugins.songs.forms.editsongform import EditSongForm
|
from openlp.plugins.songs.forms.editsongform import EditSongForm
|
||||||
from openlp.plugins.songs.forms.songmaintenanceform import SongMaintenanceForm
|
from openlp.plugins.songs.forms.songmaintenanceform import SongMaintenanceForm
|
||||||
from openlp.plugins.songs.forms.songimportform import SongImportForm
|
from openlp.plugins.songs.forms.songimportform import SongImportForm
|
||||||
from openlp.plugins.songs.forms.songexportform import SongExportForm
|
from openlp.plugins.songs.forms.songexportform import SongExportForm
|
||||||
from openlp.plugins.songs.lib import VerseType, clean_string, delete_song
|
from openlp.plugins.songs.lib import VerseType, clean_string, delete_song
|
||||||
from openlp.plugins.songs.lib.db import Author, Song, Book, MediaFile
|
from openlp.plugins.songs.lib.db import Author, AuthorType, Song, Book, MediaFile
|
||||||
from openlp.plugins.songs.lib.ui import SongStrings
|
from openlp.plugins.songs.lib.ui import SongStrings
|
||||||
from openlp.plugins.songs.lib.xml import OpenLyrics, SongXML
|
from openlp.plugins.songs.lib.xml import OpenLyrics, SongXML
|
||||||
|
|
||||||
@ -124,7 +124,8 @@ class SongMediaItem(MediaManagerItem):
|
|||||||
log.debug('config_updated')
|
log.debug('config_updated')
|
||||||
self.search_as_you_type = Settings().value(self.settings_section + '/search as type')
|
self.search_as_you_type = Settings().value(self.settings_section + '/search as type')
|
||||||
self.update_service_on_edit = Settings().value(self.settings_section + '/update service on edit')
|
self.update_service_on_edit = Settings().value(self.settings_section + '/update service on edit')
|
||||||
self.add_song_from_service = Settings().value(self.settings_section + '/add song from service',)
|
self.add_song_from_service = Settings().value(self.settings_section + '/add song from service')
|
||||||
|
self.display_songbook = Settings().value(self.settings_section + '/display songbook')
|
||||||
|
|
||||||
def retranslateUi(self):
|
def retranslateUi(self):
|
||||||
self.search_text_label.setText('%s:' % UiStrings().Search)
|
self.search_text_label.setText('%s:' % UiStrings().Search)
|
||||||
@ -234,8 +235,7 @@ class SongMediaItem(MediaManagerItem):
|
|||||||
if song.temporary:
|
if song.temporary:
|
||||||
continue
|
continue
|
||||||
author_list = [author.display_name for author in song.authors]
|
author_list = [author.display_name for author in song.authors]
|
||||||
song_title = str(song.title)
|
song_detail = '%s (%s)' % (song.title, create_separated_list(author_list)) if author_list else song.title
|
||||||
song_detail = '%s (%s)' % (song_title, create_separated_list(author_list))
|
|
||||||
song_name = QtGui.QListWidgetItem(song_detail)
|
song_name = QtGui.QListWidgetItem(song_detail)
|
||||||
song_name.setData(QtCore.Qt.UserRole, song.id)
|
song_name.setData(QtCore.Qt.UserRole, song.id)
|
||||||
self.list_view.addItem(song_name)
|
self.list_view.addItem(song_name)
|
||||||
@ -464,23 +464,55 @@ class SongMediaItem(MediaManagerItem):
|
|||||||
def generate_footer(self, item, song):
|
def generate_footer(self, item, song):
|
||||||
"""
|
"""
|
||||||
Generates the song footer based on a song and adds details to a service item.
|
Generates the song footer based on a song and adds details to a service item.
|
||||||
author_list is only required for initial song generation.
|
|
||||||
|
|
||||||
:param item: The service item to be amended
|
:param item: The service item to be amended
|
||||||
:param song: The song to be used to generate the footer
|
:param song: The song to be used to generate the footer
|
||||||
|
:return: List of all authors (only required for initial song generation)
|
||||||
"""
|
"""
|
||||||
author_list = [str(author.display_name) for author in song.authors]
|
authors_words = []
|
||||||
|
authors_music = []
|
||||||
|
authors_words_music = []
|
||||||
|
authors_translation = []
|
||||||
|
authors_none = []
|
||||||
|
for author_song in song.authors_songs:
|
||||||
|
if author_song.author_type == AuthorType.Words:
|
||||||
|
authors_words.append(author_song.author.display_name)
|
||||||
|
elif author_song.author_type == AuthorType.Music:
|
||||||
|
authors_music.append(author_song.author.display_name)
|
||||||
|
elif author_song.author_type == AuthorType.WordsAndMusic:
|
||||||
|
authors_words_music.append(author_song.author.display_name)
|
||||||
|
elif author_song.author_type == AuthorType.Translation:
|
||||||
|
authors_translation.append(author_song.author.display_name)
|
||||||
|
else:
|
||||||
|
authors_none.append(author_song.author.display_name)
|
||||||
|
authors_all = authors_words_music + authors_words + authors_music + authors_translation + authors_none
|
||||||
item.audit = [
|
item.audit = [
|
||||||
song.title, author_list, song.copyright, str(song.ccli_number)
|
song.title, authors_all, song.copyright, str(song.ccli_number)
|
||||||
]
|
]
|
||||||
item.raw_footer = []
|
item.raw_footer = []
|
||||||
item.raw_footer.append(song.title)
|
item.raw_footer.append(song.title)
|
||||||
item.raw_footer.append(create_separated_list(author_list))
|
if authors_none:
|
||||||
|
item.raw_footer.append("%s: %s" % (translate('OpenLP.Ui', 'Written by'),
|
||||||
|
create_separated_list(authors_none)))
|
||||||
|
if authors_words_music:
|
||||||
|
item.raw_footer.append("%s: %s" % (AuthorType.Types[AuthorType.WordsAndMusic],
|
||||||
|
create_separated_list(authors_words_music)))
|
||||||
|
if authors_words:
|
||||||
|
item.raw_footer.append("%s: %s" % (AuthorType.Types[AuthorType.Words],
|
||||||
|
create_separated_list(authors_words)))
|
||||||
|
if authors_music:
|
||||||
|
item.raw_footer.append("%s: %s" % (AuthorType.Types[AuthorType.Music],
|
||||||
|
create_separated_list(authors_music)))
|
||||||
|
if authors_translation:
|
||||||
|
item.raw_footer.append("%s: %s" % (AuthorType.Types[AuthorType.Translation],
|
||||||
|
create_separated_list(authors_translation)))
|
||||||
item.raw_footer.append(song.copyright)
|
item.raw_footer.append(song.copyright)
|
||||||
|
if self.display_songbook and song.book:
|
||||||
|
item.raw_footer.append("%s #%s" % (song.book.name, song.song_number))
|
||||||
if Settings().value('core/ccli number'):
|
if Settings().value('core/ccli number'):
|
||||||
item.raw_footer.append(translate('SongsPlugin.MediaItem',
|
item.raw_footer.append(translate('SongsPlugin.MediaItem',
|
||||||
'CCLI License: ') + Settings().value('core/ccli number'))
|
'CCLI License: ') + Settings().value('core/ccli number'))
|
||||||
return author_list
|
return authors_all
|
||||||
|
|
||||||
def service_load(self, item):
|
def service_load(self, item):
|
||||||
"""
|
"""
|
||||||
@ -489,14 +521,6 @@ class SongMediaItem(MediaManagerItem):
|
|||||||
log.debug('service_load')
|
log.debug('service_load')
|
||||||
if self.plugin.status != PluginStatus.Active or not item.data_string:
|
if self.plugin.status != PluginStatus.Active or not item.data_string:
|
||||||
return
|
return
|
||||||
if item.data_string['title'].find('@') == -1:
|
|
||||||
# FIXME: This file seems to be an old one (prior to 1.9.5), which means, that the search title
|
|
||||||
# (data_string[u'title']) is probably wrong. We add "@" to search title and hope that we do not add any
|
|
||||||
# duplicate. This should work for songs without alternate title.
|
|
||||||
temp = (re.compile(r'\W+', re.UNICODE).sub(' ', item.data_string['title'].strip()) + '@').strip().lower()
|
|
||||||
search_results = \
|
|
||||||
self.plugin.manager.get_all_objects(Song, Song.search_title == temp, Song.search_title.asc())
|
|
||||||
else:
|
|
||||||
search_results = self.plugin.manager.get_all_objects(
|
search_results = self.plugin.manager.get_all_objects(
|
||||||
Song, Song.search_title == item.data_string['title'], Song.search_title.asc())
|
Song, Song.search_title == item.data_string['title'], Song.search_title.asc())
|
||||||
edit_id = 0
|
edit_id = 0
|
||||||
|
@ -171,7 +171,7 @@ class SongBeamerImport(SongImport):
|
|||||||
|
|
||||||
:param line: The line in the file. It should consist of a tag and a value for this tag (unicode)::
|
:param line: The line in the file. It should consist of a tag and a value for this tag (unicode)::
|
||||||
|
|
||||||
u'#Title=Nearer my God to Thee'
|
'#Title=Nearer my God to Thee'
|
||||||
"""
|
"""
|
||||||
tag_val = line.split('=', 1)
|
tag_val = line.split('=', 1)
|
||||||
if len(tag_val) == 1:
|
if len(tag_val) == 1:
|
||||||
|
@ -146,14 +146,14 @@ class SongSelectImport(object):
|
|||||||
try:
|
try:
|
||||||
song_page = BeautifulSoup(self.opener.open(song['link']).read(), 'lxml')
|
song_page = BeautifulSoup(self.opener.open(song['link']).read(), 'lxml')
|
||||||
except (TypeError, HTTPError) as e:
|
except (TypeError, HTTPError) as e:
|
||||||
log.exception(u'Could not get song from SongSelect, %s', e)
|
log.exception('Could not get song from SongSelect, %s', e)
|
||||||
return None
|
return None
|
||||||
if callback:
|
if callback:
|
||||||
callback()
|
callback()
|
||||||
try:
|
try:
|
||||||
lyrics_page = BeautifulSoup(self.opener.open(song['link'] + '/lyrics').read(), 'lxml')
|
lyrics_page = BeautifulSoup(self.opener.open(song['link'] + '/lyrics').read(), 'lxml')
|
||||||
except (TypeError, HTTPError):
|
except (TypeError, HTTPError):
|
||||||
log.exception(u'Could not get lyrics from SongSelect')
|
log.exception('Could not get lyrics from SongSelect')
|
||||||
return None
|
return None
|
||||||
if callback:
|
if callback:
|
||||||
callback()
|
callback()
|
||||||
|
@ -121,7 +121,7 @@ class SongShowPlusImport(SongImport):
|
|||||||
null, verse_no, = struct.unpack("BB", song_data.read(2))
|
null, verse_no, = struct.unpack("BB", song_data.read(2))
|
||||||
elif block_key == CUSTOM_VERSE:
|
elif block_key == CUSTOM_VERSE:
|
||||||
null, verse_name_length, = struct.unpack("BB", song_data.read(2))
|
null, verse_name_length, = struct.unpack("BB", song_data.read(2))
|
||||||
verse_name = song_data.read(verse_name_length)
|
verse_name = self.decode(song_data.read(verse_name_length))
|
||||||
length_descriptor_size, = struct.unpack("B", song_data.read(1))
|
length_descriptor_size, = struct.unpack("B", song_data.read(1))
|
||||||
log.debug(length_descriptor_size)
|
log.debug(length_descriptor_size)
|
||||||
# Detect if/how long the length descriptor is
|
# Detect if/how long the length descriptor is
|
||||||
@ -147,7 +147,12 @@ class SongShowPlusImport(SongImport):
|
|||||||
elif block_key == COPYRIGHT:
|
elif block_key == COPYRIGHT:
|
||||||
self.add_copyright(self.decode(data))
|
self.add_copyright(self.decode(data))
|
||||||
elif block_key == CCLI_NO:
|
elif block_key == CCLI_NO:
|
||||||
self.ccli_number = int(data)
|
# Try to get the CCLI number even if the field contains additional text
|
||||||
|
match = re.search(r'\d+', self.decode(data))
|
||||||
|
if match:
|
||||||
|
self.ccli_number = int(match.group())
|
||||||
|
else:
|
||||||
|
log.warn("Can't parse CCLI Number from string: %s" % self.decode(data))
|
||||||
elif block_key == VERSE:
|
elif block_key == VERSE:
|
||||||
self.add_verse(self.decode(data), "%s%s" % (VerseType.tags[VerseType.Verse], verse_no))
|
self.add_verse(self.decode(data), "%s%s" % (VerseType.tags[VerseType.Verse], verse_no))
|
||||||
elif block_key == CHORUS:
|
elif block_key == CHORUS:
|
||||||
|
@ -59,6 +59,9 @@ class SongsTab(SettingsTab):
|
|||||||
self.add_from_service_check_box = QtGui.QCheckBox(self.mode_group_box)
|
self.add_from_service_check_box = QtGui.QCheckBox(self.mode_group_box)
|
||||||
self.add_from_service_check_box.setObjectName('add_from_service_check_box')
|
self.add_from_service_check_box.setObjectName('add_from_service_check_box')
|
||||||
self.mode_layout.addWidget(self.add_from_service_check_box)
|
self.mode_layout.addWidget(self.add_from_service_check_box)
|
||||||
|
self.display_songbook_check_box = QtGui.QCheckBox(self.mode_group_box)
|
||||||
|
self.display_songbook_check_box.setObjectName('songbook_check_box')
|
||||||
|
self.mode_layout.addWidget(self.display_songbook_check_box)
|
||||||
self.left_layout.addWidget(self.mode_group_box)
|
self.left_layout.addWidget(self.mode_group_box)
|
||||||
self.left_layout.addStretch()
|
self.left_layout.addStretch()
|
||||||
self.right_layout.addStretch()
|
self.right_layout.addStretch()
|
||||||
@ -66,6 +69,7 @@ class SongsTab(SettingsTab):
|
|||||||
self.tool_bar_active_check_box.stateChanged.connect(self.on_tool_bar_active_check_box_changed)
|
self.tool_bar_active_check_box.stateChanged.connect(self.on_tool_bar_active_check_box_changed)
|
||||||
self.update_on_edit_check_box.stateChanged.connect(self.on_update_on_edit_check_box_changed)
|
self.update_on_edit_check_box.stateChanged.connect(self.on_update_on_edit_check_box_changed)
|
||||||
self.add_from_service_check_box.stateChanged.connect(self.on_add_from_service_check_box_changed)
|
self.add_from_service_check_box.stateChanged.connect(self.on_add_from_service_check_box_changed)
|
||||||
|
self.display_songbook_check_box.stateChanged.connect(self.on_songbook_check_box_changed)
|
||||||
|
|
||||||
def retranslateUi(self):
|
def retranslateUi(self):
|
||||||
self.mode_group_box.setTitle(translate('SongsPlugin.SongsTab', 'Songs Mode'))
|
self.mode_group_box.setTitle(translate('SongsPlugin.SongsTab', 'Songs Mode'))
|
||||||
@ -75,6 +79,7 @@ class SongsTab(SettingsTab):
|
|||||||
self.update_on_edit_check_box.setText(translate('SongsPlugin.SongsTab', 'Update service from song edit'))
|
self.update_on_edit_check_box.setText(translate('SongsPlugin.SongsTab', 'Update service from song edit'))
|
||||||
self.add_from_service_check_box.setText(translate('SongsPlugin.SongsTab',
|
self.add_from_service_check_box.setText(translate('SongsPlugin.SongsTab',
|
||||||
'Import missing songs from service files'))
|
'Import missing songs from service files'))
|
||||||
|
self.display_songbook_check_box.setText(translate('SongsPlugin.SongsTab', 'Display songbook in footer'))
|
||||||
|
|
||||||
def on_search_as_type_check_box_changed(self, check_state):
|
def on_search_as_type_check_box_changed(self, check_state):
|
||||||
self.song_search = (check_state == QtCore.Qt.Checked)
|
self.song_search = (check_state == QtCore.Qt.Checked)
|
||||||
@ -88,6 +93,9 @@ class SongsTab(SettingsTab):
|
|||||||
def on_add_from_service_check_box_changed(self, check_state):
|
def on_add_from_service_check_box_changed(self, check_state):
|
||||||
self.update_load = (check_state == QtCore.Qt.Checked)
|
self.update_load = (check_state == QtCore.Qt.Checked)
|
||||||
|
|
||||||
|
def on_songbook_check_box_changed(self, check_state):
|
||||||
|
self.display_songbook = (check_state == QtCore.Qt.Checked)
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
settings = Settings()
|
settings = Settings()
|
||||||
settings.beginGroup(self.settings_section)
|
settings.beginGroup(self.settings_section)
|
||||||
@ -95,10 +103,12 @@ class SongsTab(SettingsTab):
|
|||||||
self.tool_bar = settings.value('display songbar')
|
self.tool_bar = settings.value('display songbar')
|
||||||
self.update_edit = settings.value('update service on edit')
|
self.update_edit = settings.value('update service on edit')
|
||||||
self.update_load = settings.value('add song from service')
|
self.update_load = settings.value('add song from service')
|
||||||
|
self.display_songbook = settings.value('display songbook')
|
||||||
self.search_as_type_check_box.setChecked(self.song_search)
|
self.search_as_type_check_box.setChecked(self.song_search)
|
||||||
self.tool_bar_active_check_box.setChecked(self.tool_bar)
|
self.tool_bar_active_check_box.setChecked(self.tool_bar)
|
||||||
self.update_on_edit_check_box.setChecked(self.update_edit)
|
self.update_on_edit_check_box.setChecked(self.update_edit)
|
||||||
self.add_from_service_check_box.setChecked(self.update_load)
|
self.add_from_service_check_box.setChecked(self.update_load)
|
||||||
|
self.display_songbook_check_box.setChecked(self.display_songbook)
|
||||||
settings.endGroup()
|
settings.endGroup()
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
@ -108,6 +118,7 @@ class SongsTab(SettingsTab):
|
|||||||
settings.setValue('display songbar', self.tool_bar)
|
settings.setValue('display songbar', self.tool_bar)
|
||||||
settings.setValue('update service on edit', self.update_edit)
|
settings.setValue('update service on edit', self.update_edit)
|
||||||
settings.setValue('add song from service', self.update_load)
|
settings.setValue('add song from service', self.update_load)
|
||||||
|
settings.setValue('display songbook', self.display_songbook)
|
||||||
settings.endGroup()
|
settings.endGroup()
|
||||||
if self.tab_visited:
|
if self.tab_visited:
|
||||||
self.settings_form.register_post_process('songs_config_updated')
|
self.settings_form.register_post_process('songs_config_updated')
|
||||||
|
@ -68,7 +68,7 @@ class SundayPlusImport(SongImport):
|
|||||||
for filename in self.import_source:
|
for filename in self.import_source:
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
return
|
return
|
||||||
song_file = open(filename)
|
song_file = open(filename, 'rb')
|
||||||
self.do_import_file(song_file)
|
self.do_import_file(song_file)
|
||||||
song_file.close()
|
song_file.close()
|
||||||
|
|
||||||
@ -103,7 +103,7 @@ class SundayPlusImport(SongImport):
|
|||||||
# Now we are looking for the name.
|
# Now we are looking for the name.
|
||||||
if data[i:i + 1] == '#':
|
if data[i:i + 1] == '#':
|
||||||
name_end = data.find(':', i + 1)
|
name_end = data.find(':', i + 1)
|
||||||
name = data[i + 1:name_end]
|
name = data[i + 1:name_end].upper()
|
||||||
i = name_end + 1
|
i = name_end + 1
|
||||||
while data[i:i + 1] == ' ':
|
while data[i:i + 1] == ' ':
|
||||||
i += 1
|
i += 1
|
||||||
@ -129,13 +129,13 @@ class SundayPlusImport(SongImport):
|
|||||||
value = data[i:end]
|
value = data[i:end]
|
||||||
# If we are in the main group.
|
# If we are in the main group.
|
||||||
if not cell:
|
if not cell:
|
||||||
if name == 'title':
|
if name == 'TITLE':
|
||||||
self.title = self.decode(self.unescape(value))
|
self.title = self.decode(self.unescape(value))
|
||||||
elif name == 'Author':
|
elif name == 'AUTHOR':
|
||||||
author = self.decode(self.unescape(value))
|
author = self.decode(self.unescape(value))
|
||||||
if len(author):
|
if len(author):
|
||||||
self.add_author(author)
|
self.add_author(author)
|
||||||
elif name == 'Copyright':
|
elif name == 'COPYRIGHT':
|
||||||
self.copyright = self.decode(self.unescape(value))
|
self.copyright = self.decode(self.unescape(value))
|
||||||
elif name[0:4] == 'CELL':
|
elif name[0:4] == 'CELL':
|
||||||
self.parse(value, cell=name[4:])
|
self.parse(value, cell=name[4:])
|
||||||
@ -147,12 +147,12 @@ class SundayPlusImport(SongImport):
|
|||||||
verse_type = VerseType.tags[VerseType.from_loose_input(value[0])]
|
verse_type = VerseType.tags[VerseType.from_loose_input(value[0])]
|
||||||
if len(value) >= 2 and value[-1] in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']:
|
if len(value) >= 2 and value[-1] in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']:
|
||||||
verse_type = "%s%s" % (verse_type, value[-1])
|
verse_type = "%s%s" % (verse_type, value[-1])
|
||||||
elif name == 'Hotkey':
|
elif name == 'HOTKEY':
|
||||||
# Hotkey always appears after MARKER_NAME, so it
|
# HOTKEY always appears after MARKER_NAME, so it
|
||||||
# effectively overrides MARKER_NAME, if present.
|
# effectively overrides MARKER_NAME, if present.
|
||||||
if len(value) and value in list(HOTKEY_TO_VERSE_TYPE.keys()):
|
if len(value) and value in list(HOTKEY_TO_VERSE_TYPE.keys()):
|
||||||
verse_type = HOTKEY_TO_VERSE_TYPE[value]
|
verse_type = HOTKEY_TO_VERSE_TYPE[value]
|
||||||
if name == 'rtf':
|
if name == 'RTF':
|
||||||
value = self.unescape(value)
|
value = self.unescape(value)
|
||||||
result = strip_rtf(value, self.encoding)
|
result = strip_rtf(value, self.encoding)
|
||||||
if result is None:
|
if result is None:
|
||||||
|
@ -40,7 +40,7 @@ class SongStrings(object):
|
|||||||
# These strings should need a good reason to be retranslated elsewhere.
|
# These strings should need a good reason to be retranslated elsewhere.
|
||||||
Author = translate('OpenLP.Ui', 'Author', 'Singular')
|
Author = translate('OpenLP.Ui', 'Author', 'Singular')
|
||||||
Authors = translate('OpenLP.Ui', 'Authors', 'Plural')
|
Authors = translate('OpenLP.Ui', 'Authors', 'Plural')
|
||||||
AuthorUnknown = 'Author Unknown' # Used to populate the database.
|
AuthorUnknown = translate('OpenLP.Ui', 'Author Unknown') # Used to populate the database.
|
||||||
CopyrightSymbol = translate('OpenLP.Ui', '\xa9', 'Copyright symbol.')
|
CopyrightSymbol = translate('OpenLP.Ui', '\xa9', 'Copyright symbol.')
|
||||||
SongBook = translate('OpenLP.Ui', 'Song Book', 'Singular')
|
SongBook = translate('OpenLP.Ui', 'Song Book', 'Singular')
|
||||||
SongBooks = translate('OpenLP.Ui', 'Song Books', 'Plural')
|
SongBooks = translate('OpenLP.Ui', 'Song Books', 'Plural')
|
||||||
|
@ -32,14 +32,14 @@ backend for the Songs plugin
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from sqlalchemy import Column, types
|
from sqlalchemy import Column, ForeignKey, types
|
||||||
from sqlalchemy.exc import OperationalError
|
from sqlalchemy.exc import OperationalError
|
||||||
from sqlalchemy.sql.expression import func, false, null, text
|
from sqlalchemy.sql.expression import func, false, null, text
|
||||||
|
|
||||||
from openlp.core.lib.db import get_upgrade_op
|
from openlp.core.lib.db import get_upgrade_op
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
__version__ = 3
|
__version__ = 4
|
||||||
|
|
||||||
|
|
||||||
def upgrade_1(session, metadata):
|
def upgrade_1(session, metadata):
|
||||||
@ -97,3 +97,25 @@ def upgrade_3(session, metadata):
|
|||||||
op.add_column('songs', Column('temporary', types.Boolean(), server_default=false()))
|
op.add_column('songs', Column('temporary', types.Boolean(), server_default=false()))
|
||||||
except OperationalError:
|
except OperationalError:
|
||||||
log.info('Upgrade 3 has already been run')
|
log.info('Upgrade 3 has already been run')
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade_4(session, metadata):
|
||||||
|
"""
|
||||||
|
Version 4 upgrade.
|
||||||
|
|
||||||
|
This upgrade adds a column for author type to the authors_songs table
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Since SQLite doesn't support changing the primary key of a table, we need to recreate the table
|
||||||
|
# and copy the old values
|
||||||
|
op = get_upgrade_op(session)
|
||||||
|
op.create_table('authors_songs_tmp',
|
||||||
|
Column('author_id', types.Integer(), ForeignKey('authors.id'), primary_key=True),
|
||||||
|
Column('song_id', types.Integer(), ForeignKey('songs.id'), primary_key=True),
|
||||||
|
Column('author_type', types.String(), primary_key=True,
|
||||||
|
nullable=False, server_default=text('""')))
|
||||||
|
op.execute('INSERT INTO authors_songs_tmp SELECT author_id, song_id, "" FROM authors_songs')
|
||||||
|
op.drop_table('authors_songs')
|
||||||
|
op.rename_table('authors_songs_tmp', 'authors_songs')
|
||||||
|
except OperationalError:
|
||||||
|
log.info('Upgrade 4 has already been run')
|
||||||
|
@ -71,7 +71,7 @@ from lxml import etree, objectify
|
|||||||
from openlp.core.common import translate
|
from openlp.core.common import translate
|
||||||
from openlp.core.lib import FormattingTags
|
from openlp.core.lib import FormattingTags
|
||||||
from openlp.plugins.songs.lib import VerseType, clean_song
|
from openlp.plugins.songs.lib import VerseType, clean_song
|
||||||
from openlp.plugins.songs.lib.db import Author, Book, Song, Topic
|
from openlp.plugins.songs.lib.db import Author, AuthorSong, AuthorType, Book, Song, Topic
|
||||||
from openlp.core.utils import get_application_version
|
from openlp.core.utils import get_application_version
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@ -166,7 +166,7 @@ class OpenLyrics(object):
|
|||||||
supported by the :class:`OpenLyrics` class:
|
supported by the :class:`OpenLyrics` class:
|
||||||
|
|
||||||
``<authors>``
|
``<authors>``
|
||||||
OpenLP does not support the attribute *type* and *lang*.
|
OpenLP does not support the attribute *lang*.
|
||||||
|
|
||||||
``<chord>``
|
``<chord>``
|
||||||
This property is not supported.
|
This property is not supported.
|
||||||
@ -269,10 +269,18 @@ class OpenLyrics(object):
|
|||||||
'verseOrder', properties, song.verse_order.lower())
|
'verseOrder', properties, song.verse_order.lower())
|
||||||
if song.ccli_number:
|
if song.ccli_number:
|
||||||
self._add_text_to_element('ccliNo', properties, song.ccli_number)
|
self._add_text_to_element('ccliNo', properties, song.ccli_number)
|
||||||
if song.authors:
|
if song.authors_songs:
|
||||||
authors = etree.SubElement(properties, 'authors')
|
authors = etree.SubElement(properties, 'authors')
|
||||||
for author in song.authors:
|
for author_song in song.authors_songs:
|
||||||
self._add_text_to_element('author', authors, author.display_name)
|
element = self._add_text_to_element('author', authors, author_song.author.display_name)
|
||||||
|
if author_song.author_type:
|
||||||
|
# Handle the special case 'words+music': Need to create two separate authors for that
|
||||||
|
if author_song.author_type == AuthorType.WordsAndMusic:
|
||||||
|
element.set('type', AuthorType.Words)
|
||||||
|
element = self._add_text_to_element('author', authors, author_song.author.display_name)
|
||||||
|
element.set('type', AuthorType.Music)
|
||||||
|
else:
|
||||||
|
element.set('type', author_song.author_type)
|
||||||
book = self.manager.get_object_filtered(Book, Book.id == song.song_book_id)
|
book = self.manager.get_object_filtered(Book, Book.id == song.song_book_id)
|
||||||
if book is not None:
|
if book is not None:
|
||||||
book = book.name
|
book = book.name
|
||||||
@ -302,9 +310,9 @@ class OpenLyrics(object):
|
|||||||
verse_tag = verse[0]['type'][0].lower()
|
verse_tag = verse[0]['type'][0].lower()
|
||||||
verse_number = verse[0]['label']
|
verse_number = verse[0]['label']
|
||||||
verse_def = verse_tag + verse_number
|
verse_def = verse_tag + verse_number
|
||||||
verse_tags.append(verse_def)
|
|
||||||
# Create the letter from the number of duplicates
|
# Create the letter from the number of duplicates
|
||||||
verse[0]['suffix'] = chr(96 + verse_tags.count(verse_def))
|
verse[0][u'suffix'] = chr(97 + (verse_tags.count(verse_def) % 26))
|
||||||
|
verse_tags.append(verse_def)
|
||||||
# If the verse tag is a duplicate use the suffix letter
|
# If the verse tag is a duplicate use the suffix letter
|
||||||
for verse in verse_list:
|
for verse in verse_list:
|
||||||
verse_tag = verse[0]['type'][0].lower()
|
verse_tag = verse[0]['type'][0].lower()
|
||||||
@ -336,7 +344,7 @@ class OpenLyrics(object):
|
|||||||
"""
|
"""
|
||||||
Tests the given text for not closed formatting tags and returns a tuple consisting of two unicode strings::
|
Tests the given text for not closed formatting tags and returns a tuple consisting of two unicode strings::
|
||||||
|
|
||||||
(u'{st}{r}', u'{/r}{/st}')
|
('{st}{r}', '{/r}{/st}')
|
||||||
|
|
||||||
The first unicode string are the start tags (for the next slide). The second unicode string are the end tags.
|
The first unicode string are the start tags (for the next slide). The second unicode string are the end tags.
|
||||||
|
|
||||||
@ -501,16 +509,20 @@ class OpenLyrics(object):
|
|||||||
if hasattr(properties, 'authors'):
|
if hasattr(properties, 'authors'):
|
||||||
for author in properties.authors.author:
|
for author in properties.authors.author:
|
||||||
display_name = self._text(author)
|
display_name = self._text(author)
|
||||||
|
author_type = author.get('type', '')
|
||||||
if display_name:
|
if display_name:
|
||||||
authors.append(display_name)
|
authors.append((display_name, author_type))
|
||||||
for display_name in authors:
|
for (display_name, author_type) in authors:
|
||||||
author = self.manager.get_object_filtered(Author, Author.display_name == display_name)
|
author = self.manager.get_object_filtered(Author, Author.display_name == display_name)
|
||||||
if author is None:
|
if author is None:
|
||||||
# We need to create a new author, as the author does not exist.
|
# We need to create a new author, as the author does not exist.
|
||||||
author = Author.populate(display_name=display_name,
|
author = Author.populate(display_name=display_name,
|
||||||
last_name=display_name.split(' ')[-1],
|
last_name=display_name.split(' ')[-1],
|
||||||
first_name=' '.join(display_name.split(' ')[:-1]))
|
first_name=' '.join(display_name.split(' ')[:-1]))
|
||||||
song.authors.append(author)
|
author_song = AuthorSong()
|
||||||
|
author_song.author = author
|
||||||
|
author_song.author_type = author_type
|
||||||
|
song.authors_songs.append(author_song)
|
||||||
|
|
||||||
def _process_cclinumber(self, properties, song):
|
def _process_cclinumber(self, properties, song):
|
||||||
"""
|
"""
|
||||||
|
@ -63,6 +63,7 @@ __default_settings__ = {
|
|||||||
'songs/search as type': False,
|
'songs/search as type': False,
|
||||||
'songs/add song from service': True,
|
'songs/add song from service': True,
|
||||||
'songs/display songbar': True,
|
'songs/display songbar': True,
|
||||||
|
'songs/display songbook': False,
|
||||||
'songs/last directory import': '',
|
'songs/last directory import': '',
|
||||||
'songs/last directory export': '',
|
'songs/last directory export': '',
|
||||||
'songs/songselect username': '',
|
'songs/songselect username': '',
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
from PyQt4 import QtCore, QtGui
|
from PyQt4 import QtCore, QtGui
|
||||||
|
|
||||||
from openlp.core.common import translate
|
from openlp.core.common import translate
|
||||||
|
from openlp.core.lib import build_icon
|
||||||
from openlp.core.lib.ui import create_button_box
|
from openlp.core.lib.ui import create_button_box
|
||||||
|
|
||||||
|
|
||||||
@ -44,6 +45,7 @@ class Ui_SongUsageDeleteDialog(object):
|
|||||||
:param song_usage_delete_dialog:
|
:param song_usage_delete_dialog:
|
||||||
"""
|
"""
|
||||||
song_usage_delete_dialog.setObjectName('song_usage_delete_dialog')
|
song_usage_delete_dialog.setObjectName('song_usage_delete_dialog')
|
||||||
|
song_usage_delete_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
|
||||||
song_usage_delete_dialog.resize(291, 243)
|
song_usage_delete_dialog.resize(291, 243)
|
||||||
self.vertical_layout = QtGui.QVBoxLayout(song_usage_delete_dialog)
|
self.vertical_layout = QtGui.QVBoxLayout(song_usage_delete_dialog)
|
||||||
self.vertical_layout.setSpacing(8)
|
self.vertical_layout.setSpacing(8)
|
||||||
|
@ -45,6 +45,7 @@ class Ui_SongUsageDetailDialog(object):
|
|||||||
:param song_usage_detail_dialog:
|
:param song_usage_detail_dialog:
|
||||||
"""
|
"""
|
||||||
song_usage_detail_dialog.setObjectName('song_usage_detail_dialog')
|
song_usage_detail_dialog.setObjectName('song_usage_detail_dialog')
|
||||||
|
song_usage_detail_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
|
||||||
song_usage_detail_dialog.resize(609, 413)
|
song_usage_detail_dialog.resize(609, 413)
|
||||||
self.vertical_layout = QtGui.QVBoxLayout(song_usage_detail_dialog)
|
self.vertical_layout = QtGui.QVBoxLayout(song_usage_detail_dialog)
|
||||||
self.vertical_layout.setSpacing(8)
|
self.vertical_layout.setSpacing(8)
|
||||||
|
BIN
resources/images/network_auth.png
Normal file
BIN
resources/images/network_auth.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 608 B |
BIN
resources/images/network_server.png
Normal file
BIN
resources/images/network_server.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
BIN
resources/images/network_ssl.png
Normal file
BIN
resources/images/network_ssl.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 577 B |
@ -150,6 +150,11 @@
|
|||||||
<file>messagebox_info.png</file>
|
<file>messagebox_info.png</file>
|
||||||
<file>messagebox_warning.png</file>
|
<file>messagebox_warning.png</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
|
<qresource prefix="remote">
|
||||||
|
<file>network_server.png</file>
|
||||||
|
<file>network_ssl.png</file>
|
||||||
|
<file>network_auth.png</file>
|
||||||
|
</qresource>
|
||||||
<qresource prefix="songusage">
|
<qresource prefix="songusage">
|
||||||
<file>song_usage_active.png</file>
|
<file>song_usage_active.png</file>
|
||||||
<file>song_usage_inactive.png</file>
|
<file>song_usage_inactive.png</file>
|
||||||
|
@ -148,7 +148,7 @@ class JenkinsTrigger(object):
|
|||||||
|
|
||||||
def get_repo_name():
|
def get_repo_name():
|
||||||
"""
|
"""
|
||||||
This returns the name of branch of the wokring directory. For example it returns *lp:~googol/openlp/render*.
|
This returns the name of branch of the working directory. For example it returns *lp:~googol/openlp/render*.
|
||||||
"""
|
"""
|
||||||
# Run the bzr command.
|
# Run the bzr command.
|
||||||
bzr = Popen(('bzr', 'info'), stdout=PIPE, stderr=PIPE)
|
bzr = Popen(('bzr', 'info'), stdout=PIPE, stderr=PIPE)
|
||||||
@ -198,7 +198,7 @@ def main():
|
|||||||
jenkins_trigger = JenkinsTrigger(token)
|
jenkins_trigger = JenkinsTrigger(token)
|
||||||
try:
|
try:
|
||||||
jenkins_trigger.trigger_build()
|
jenkins_trigger.trigger_build()
|
||||||
except HTTPError as e:
|
except HTTPError:
|
||||||
print('Wrong token.')
|
print('Wrong token.')
|
||||||
return
|
return
|
||||||
# Open the browser before printing the output.
|
# Open the browser before printing the output.
|
||||||
|
@ -53,8 +53,8 @@ class TestFileDialog(TestCase):
|
|||||||
self.mocked_os.rest()
|
self.mocked_os.rest()
|
||||||
self.mocked_qt_gui.reset()
|
self.mocked_qt_gui.reset()
|
||||||
|
|
||||||
# GIVEN: A List of known values as a return value from QFileDialog.getOpenFileNames and a list of valid
|
# GIVEN: A List of known values as a return value from QFileDialog.getOpenFileNames and a list of valid file
|
||||||
# file names.
|
# names.
|
||||||
self.mocked_qt_gui.QFileDialog.getOpenFileNames.return_value = [
|
self.mocked_qt_gui.QFileDialog.getOpenFileNames.return_value = [
|
||||||
'/Valid File', '/url%20encoded%20file%20%231', '/non-existing']
|
'/Valid File', '/url%20encoded%20file%20%231', '/non-existing']
|
||||||
self.mocked_os.path.exists.side_effect = lambda file_name: file_name in [
|
self.mocked_os.path.exists.side_effect = lambda file_name: file_name in [
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user