This commit is contained in:
Tomas Groth 2014-05-21 17:56:57 +02:00
commit d9c6e2a228
125 changed files with 1411 additions and 476 deletions

View File

@ -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

View File

@ -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 *

View File

@ -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!

View File

@ -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:

View File

@ -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.

View File

@ -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 ''

View File

@ -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'),

View File

@ -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

View File

@ -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

View File

@ -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']))

View File

@ -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)

View File

@ -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

View File

@ -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()

View File

@ -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')

View File

@ -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):

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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')):

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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')

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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')

View File

@ -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

View File

@ -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')

View File

@ -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)

View File

@ -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)

View File

@ -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')

View File

@ -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 |

View File

@ -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 += ';;'

View File

@ -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:

View File

@ -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:

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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.
""" """

View File

@ -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.
""" """

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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('')

View File

@ -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)

View File

@ -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:

View File

@ -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

View File

@ -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).

View File

@ -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()

View File

@ -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())]

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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:

View File

@ -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

View File

@ -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()

View File

@ -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.')

View File

@ -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()

View File

@ -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))

View File

@ -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

View File

@ -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')

View File

@ -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.
""" """

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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:

View File

@ -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)

View File

@ -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')

View File

@ -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.
""" """

View File

@ -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)

View File

@ -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)

View File

@ -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')

View File

@ -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 = []

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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:

View File

@ -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()

View File

@ -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:

View File

@ -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')

View File

@ -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:

View File

@ -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')

View File

@ -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')

View File

@ -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):
""" """

View File

@ -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': '',

View File

@ -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)

View File

@ -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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 608 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 577 B

View File

@ -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>

View 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.

View File

@ -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