forked from openlp/openlp
Cleanup code style and add bible tests
bzr-revno: 2335
This commit is contained in:
commit
ff78a99fc1
@ -82,11 +82,8 @@ def upgrade_db(url, upgrade):
|
||||
"""
|
||||
Upgrade a database.
|
||||
|
||||
``url``
|
||||
The url of the database to upgrade.
|
||||
|
||||
``upgrade``
|
||||
The python module that contains the upgrade instructions.
|
||||
:param url: The url of the database to upgrade.
|
||||
:param upgrade: The python module that contains the upgrade instructions.
|
||||
"""
|
||||
session, metadata = init_db(url)
|
||||
|
||||
@ -99,7 +96,7 @@ def upgrade_db(url, upgrade):
|
||||
metadata_table = Table('metadata', metadata,
|
||||
Column('key', types.Unicode(64), primary_key=True),
|
||||
Column('value', types.UnicodeText(), default=None)
|
||||
)
|
||||
)
|
||||
metadata_table.create(checkfirst=True)
|
||||
mapper(Metadata, metadata_table)
|
||||
version_meta = session.query(Metadata).get('version')
|
||||
@ -137,11 +134,8 @@ def delete_database(plugin_name, db_file_name=None):
|
||||
"""
|
||||
Remove a database file from the system.
|
||||
|
||||
``plugin_name``
|
||||
The name of the plugin to remove the database for
|
||||
|
||||
``db_file_name``
|
||||
The database file name. Defaults to None resulting in the plugin_name being used.
|
||||
:param plugin_name: The name of the plugin to remove the database for
|
||||
:param db_file_name: The database file name. Defaults to None resulting in the plugin_name being used.
|
||||
"""
|
||||
db_file_path = None
|
||||
if db_file_name:
|
||||
@ -175,17 +169,11 @@ class Manager(object):
|
||||
Runs the initialisation process that includes creating the connection to the database and the tables if they do
|
||||
not exist.
|
||||
|
||||
``plugin_name``
|
||||
The name to setup paths and settings section names
|
||||
|
||||
``init_schema``
|
||||
The init_schema function for this database
|
||||
|
||||
``upgrade_schema``
|
||||
The upgrade_schema function for this database
|
||||
|
||||
``db_file_name``
|
||||
The file name to use for this database. Defaults to None resulting in the plugin_name being used.
|
||||
:param plugin_name: The name to setup paths and settings section names
|
||||
:param init_schema: The init_schema function for this database
|
||||
:param db_file_name: The upgrade_schema function for this database
|
||||
:param upgrade_mod: The file name to use for this database. Defaults to None resulting in the plugin_name
|
||||
being used.
|
||||
"""
|
||||
settings = Settings()
|
||||
settings.beginGroup(plugin_name)
|
||||
@ -208,7 +196,10 @@ class Manager(object):
|
||||
self.db_url += '?charset=%s' % urlquote(db_encoding)
|
||||
settings.endGroup()
|
||||
if upgrade_mod:
|
||||
db_ver, up_ver = upgrade_db(self.db_url, upgrade_mod)
|
||||
try:
|
||||
db_ver, up_ver = upgrade_db(self.db_url, upgrade_mod)
|
||||
except (SQLAlchemyError, DBAPIError):
|
||||
log.exception('Error loading database: %s', self.db_url)
|
||||
if db_ver > up_ver:
|
||||
critical_error_message_box(
|
||||
translate('OpenLP.Manager', 'Database Error'),
|
||||
@ -229,11 +220,8 @@ class Manager(object):
|
||||
"""
|
||||
Save an object to the database
|
||||
|
||||
``object_instance``
|
||||
The object to save
|
||||
|
||||
``commit``
|
||||
Commit the session with this object
|
||||
:param object_instance: The object to save
|
||||
:param commit: Commit the session with this object
|
||||
"""
|
||||
for try_count in range(3):
|
||||
try:
|
||||
@ -262,11 +250,8 @@ class Manager(object):
|
||||
"""
|
||||
Save a list of objects to the database
|
||||
|
||||
``object_list``
|
||||
The list of objects to save
|
||||
|
||||
``commit``
|
||||
Commit the session with this object
|
||||
:param object_list: The list of objects to save
|
||||
:param commit: Commit the session with this object
|
||||
"""
|
||||
for try_count in range(3):
|
||||
try:
|
||||
@ -295,11 +280,8 @@ class Manager(object):
|
||||
"""
|
||||
Return the details of an object
|
||||
|
||||
``object_class``
|
||||
The type of object to return
|
||||
|
||||
``key``
|
||||
The unique reference or primary key for the instance to return
|
||||
:param object_class: The type of object to return
|
||||
:param key: The unique reference or primary key for the instance to return
|
||||
"""
|
||||
if not key:
|
||||
return object_class()
|
||||
@ -319,11 +301,8 @@ class Manager(object):
|
||||
"""
|
||||
Returns an object matching specified criteria
|
||||
|
||||
``object_class``
|
||||
The type of object to return
|
||||
|
||||
``filter_clause``
|
||||
The criteria to select the object by
|
||||
:param object_class: The type of object to return
|
||||
:param filter_clause: The criteria to select the object by
|
||||
"""
|
||||
for try_count in range(3):
|
||||
try:
|
||||
@ -340,14 +319,9 @@ class Manager(object):
|
||||
"""
|
||||
Returns all the objects from the database
|
||||
|
||||
``object_class``
|
||||
The type of objects to return
|
||||
|
||||
``filter_clause``
|
||||
The filter governing selection of objects to return. Defaults to None.
|
||||
|
||||
``order_by_ref``
|
||||
Any parameters to order the returned objects by. Defaults to None.
|
||||
:param object_class: The type of objects to return
|
||||
:param filter_clause: The filter governing selection of objects to return. Defaults to None.
|
||||
:param order_by_ref: Any parameters to order the returned objects by. Defaults to None.
|
||||
"""
|
||||
query = self.session.query(object_class)
|
||||
if filter_clause is not None:
|
||||
@ -371,11 +345,8 @@ class Manager(object):
|
||||
"""
|
||||
Returns a count of the number of objects in the database.
|
||||
|
||||
``object_class``
|
||||
The type of objects to return.
|
||||
|
||||
``filter_clause``
|
||||
The filter governing selection of objects to return. Defaults to None.
|
||||
:param object_class: The type of objects to return.
|
||||
:param filter_clause: The filter governing selection of objects to return. Defaults to None.
|
||||
"""
|
||||
query = self.session.query(object_class)
|
||||
if filter_clause is not None:
|
||||
@ -395,11 +366,8 @@ class Manager(object):
|
||||
"""
|
||||
Delete an object from the database
|
||||
|
||||
``object_class``
|
||||
The type of object to delete
|
||||
|
||||
``key``
|
||||
The unique reference or primary key for the instance to be deleted
|
||||
:param object_class: The type of object to delete
|
||||
:param key: The unique reference or primary key for the instance to be deleted
|
||||
"""
|
||||
if key != 0:
|
||||
object_instance = self.get_object(object_class, key)
|
||||
@ -432,11 +400,8 @@ class Manager(object):
|
||||
Delete all object records. This method should only be used for simple tables and **not** ones with
|
||||
relationships. The relationships are not deleted from the database and this will lead to database corruptions.
|
||||
|
||||
``object_class``
|
||||
The type of object to delete
|
||||
|
||||
``filter_clause``
|
||||
The filter governing selection of objects to return. Defaults to None.
|
||||
:param object_class: The type of object to delete
|
||||
:param filter_clause: The filter governing selection of objects to return. Defaults to None.
|
||||
"""
|
||||
for try_count in range(3):
|
||||
try:
|
||||
|
@ -116,20 +116,20 @@ class BiblePlugin(Plugin):
|
||||
if QtGui.QMessageBox.information(self.main_window,
|
||||
translate('OpenLP', 'Information'),
|
||||
translate('OpenLP', 'Bible format has changed.\nYou have to upgrade your existing Bibles.\n'
|
||||
'Should OpenLP upgrade now?'),
|
||||
'Should OpenLP upgrade now?'),
|
||||
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No)) == \
|
||||
QtGui.QMessageBox.Yes:
|
||||
self.on_tools_upgrade_Item_triggered()
|
||||
|
||||
def add_import_menu_item(self, import_menu):
|
||||
self.import_bible_item = create_action(import_menu, 'importBibleItem',
|
||||
text=translate('BiblesPlugin', '&Bible'), visible=False,
|
||||
triggers=self.on_bible_import_click)
|
||||
text=translate('BiblesPlugin', '&Bible'), visible=False,
|
||||
triggers=self.on_bible_import_click)
|
||||
import_menu.addAction(self.import_bible_item)
|
||||
|
||||
def add_export_menu_Item(self, export_menu):
|
||||
self.export_bible_item = create_action(export_menu, 'exportBibleItem',
|
||||
text=translate('BiblesPlugin', '&Bible'), visible=False)
|
||||
text=translate('BiblesPlugin', '&Bible'), visible=False)
|
||||
export_menu.addAction(self.export_bible_item)
|
||||
|
||||
def add_tools_menu_item(self, tools_menu):
|
||||
@ -140,7 +140,8 @@ class BiblePlugin(Plugin):
|
||||
The actual **Tools** menu item, so that your actions can use it as their parent.
|
||||
"""
|
||||
log.debug('add tools menu')
|
||||
self.tools_upgrade_item = create_action(tools_menu, 'toolsUpgradeItem',
|
||||
self.tools_upgrade_item = create_action(
|
||||
tools_menu, 'toolsUpgradeItem',
|
||||
text=translate('BiblesPlugin', '&Upgrade older Bibles'),
|
||||
statustip=translate('BiblesPlugin', 'Upgrade the Bible databases to the latest format.'),
|
||||
visible=False, triggers=self.on_tools_upgrade_Item_triggered)
|
||||
@ -162,14 +163,16 @@ class BiblePlugin(Plugin):
|
||||
|
||||
def about(self):
|
||||
about_text = translate('BiblesPlugin', '<strong>Bible Plugin</strong>'
|
||||
'<br />The Bible plugin provides the ability to display Bible '
|
||||
'verses from different sources during the service.')
|
||||
'<br />The Bible plugin provides the ability to display Bible '
|
||||
'verses from different sources during the service.')
|
||||
return about_text
|
||||
|
||||
def uses_theme(self, theme):
|
||||
"""
|
||||
Called to find out if the bible plugin is currently using a theme. Returns ``True`` if the theme is being used,
|
||||
otherwise returns ``False``.
|
||||
|
||||
:param theme: The theme
|
||||
"""
|
||||
return str(self.settings_tab.bible_theme) == theme
|
||||
|
||||
@ -178,11 +181,8 @@ class BiblePlugin(Plugin):
|
||||
Rename the theme the bible plugin is using making the plugin use the
|
||||
new name.
|
||||
|
||||
``old_theme``
|
||||
The name of the theme the plugin should stop using. Unused for this particular plugin.
|
||||
|
||||
``new_theme``
|
||||
The new name the plugin should now use.
|
||||
:param old_theme: The name of the theme the plugin should stop using. Unused for this particular plugin.
|
||||
:param new_theme: The new name the plugin should now use.
|
||||
"""
|
||||
self.settings_tab.bible_theme = new_theme
|
||||
self.settings_tab.save()
|
||||
@ -207,8 +207,7 @@ class BiblePlugin(Plugin):
|
||||
'new': translate('BiblesPlugin', 'Add a new Bible.'),
|
||||
'edit': translate('BiblesPlugin', 'Edit the selected Bible.'),
|
||||
'delete': translate('BiblesPlugin', 'Delete the selected Bible.'),
|
||||
'preview': translate('BiblesPlugin',
|
||||
'Preview the selected Bible.'),
|
||||
'preview': translate('BiblesPlugin', 'Preview the selected Bible.'),
|
||||
'live': translate('BiblesPlugin', 'Send the selected Bible live.'),
|
||||
'service': translate('BiblesPlugin', 'Add the selected Bible to the service.')
|
||||
}
|
||||
|
@ -182,9 +182,10 @@ def update_reference_separators():
|
||||
"""
|
||||
Updates separators and matches for parsing and formating scripture references.
|
||||
"""
|
||||
default_separators = translate('BiblesPlugin',
|
||||
':|v|V|verse|verses;;-|to;;,|and;;end Double-semicolon delimited separators for parsing references. '
|
||||
'Consult the developers for further information.').split(';;')
|
||||
default_separators = \
|
||||
translate('BiblesPlugin',
|
||||
':|v|V|verse|verses;;-|to;;,|and;;end Double-semicolon delimited separators for parsing references. '
|
||||
'Consult the developers for further information.').split(';;')
|
||||
settings = Settings()
|
||||
settings.beginGroup('bibles')
|
||||
custom_separators = [
|
||||
@ -206,8 +207,7 @@ def update_reference_separators():
|
||||
for character in '\\.^$*+?{}[]()':
|
||||
source_string = source_string.replace(character, '\\' + character)
|
||||
# add various unicode alternatives
|
||||
source_string = source_string.replace('-',
|
||||
'(?:[-\u00AD\u2010\u2011\u2012\u2014\u2014\u2212\uFE63\uFF0D])')
|
||||
source_string = source_string.replace('-', '(?:[-\u00AD\u2010\u2011\u2012\u2014\u2014\u2212\uFE63\uFF0D])')
|
||||
source_string = source_string.replace(',', '(?:[,\u201A])')
|
||||
REFERENCE_SEPARATORS['sep_%s' % role] = '\s*(?:%s)\s*' % source_string
|
||||
REFERENCE_SEPARATORS['sep_%s_default' % role] = default_separators[index]
|
||||
@ -227,8 +227,7 @@ def get_reference_separator(separator_type):
|
||||
"""
|
||||
Provides separators for parsing and formatting scripture references.
|
||||
|
||||
``separator_type``
|
||||
The role and format of the separator.
|
||||
:param separator_type: The role and format of the separator.
|
||||
"""
|
||||
if not REFERENCE_SEPARATORS:
|
||||
update_reference_separators()
|
||||
@ -239,8 +238,7 @@ def get_reference_match(match_type):
|
||||
"""
|
||||
Provides matches for parsing scripture references strings.
|
||||
|
||||
``match_type``
|
||||
The type of match is ``range_separator``, ``range`` or ``full``.
|
||||
:param match_type: The type of match is ``range_separator``, ``range`` or ``full``.
|
||||
"""
|
||||
if not REFERENCE_MATCHES:
|
||||
update_reference_separators()
|
||||
@ -252,19 +250,10 @@ def parse_reference(reference, bible, language_selection, book_ref_id=False):
|
||||
This is the next generation über-awesome function that takes a person's typed in string and converts it to a list
|
||||
of references to be queried from the Bible database files.
|
||||
|
||||
``reference``
|
||||
A string. The Bible reference to parse.
|
||||
|
||||
``bible``
|
||||
A object. The Bible database object.
|
||||
|
||||
``language_selection``
|
||||
An int. The language selection the user has choosen in settings section.
|
||||
|
||||
``book_ref_id``
|
||||
A string. The book reference id.
|
||||
|
||||
Returns ``None`` or a reference list.
|
||||
:param reference: A string. The Bible reference to parse.
|
||||
:param bible: A object. The Bible database object.
|
||||
:param language_selection: An int. The language selection the user has chosen in settings section.
|
||||
:param book_ref_id: A string. The book reference id.
|
||||
|
||||
The reference list is a list of tuples, with each tuple structured like this::
|
||||
|
||||
@ -410,15 +399,9 @@ class SearchResults(object):
|
||||
"""
|
||||
Create the search result object.
|
||||
|
||||
``book``
|
||||
The book of the Bible.
|
||||
|
||||
``chapter``
|
||||
The chapter of the book.
|
||||
|
||||
``verse_list``
|
||||
The list of verses for this reading.
|
||||
|
||||
:param book: The book of the Bible.
|
||||
:param chapter: The chapter of the book.
|
||||
:param verse_list: The list of verses for this reading.
|
||||
"""
|
||||
self.book = book
|
||||
self.chapter = chapter
|
||||
|
@ -120,7 +120,7 @@ class BiblesTab(SettingsTab):
|
||||
self.end_separator_line_edit = QtGui.QLineEdit(self.scripture_reference_group_box)
|
||||
self.end_separator_line_edit.setObjectName('end_separator_line_edit')
|
||||
self.end_separator_line_edit.setValidator(QtGui.QRegExpValidator(QtCore.QRegExp(r'[^0-9]*'),
|
||||
self.end_separator_line_edit))
|
||||
self.end_separator_line_edit))
|
||||
self.scripture_reference_layout.addWidget(self.end_separator_line_edit, 3, 1)
|
||||
self.left_layout.addWidget(self.scripture_reference_group_box)
|
||||
self.right_column.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred)
|
||||
@ -142,7 +142,7 @@ class BiblesTab(SettingsTab):
|
||||
self.new_chapters_check_box.stateChanged.connect(self.on_new_chapters_check_box_changed)
|
||||
self.display_style_combo_box.activated.connect(self.on_display_style_combo_box_changed)
|
||||
self.bible_theme_combo_box.activated.connect(self.on_bible_theme_combo_box_changed)
|
||||
self.layout_style_combo_box.activated.connect(self.on_layout_style_combo_boxChanged)
|
||||
self.layout_style_combo_box.activated.connect(self.on_layout_style_combo_box_changed)
|
||||
self.bible_second_check_box.stateChanged.connect(self.on_bible_second_check_box)
|
||||
self.verse_separator_check_box.clicked.connect(self.on_verse_separator_check_box_clicked)
|
||||
self.verse_separator_line_edit.textEdited.connect(self.on_verse_separator_line_edit_edited)
|
||||
@ -170,15 +170,15 @@ class BiblesTab(SettingsTab):
|
||||
self.layout_style_combo_box.setItemText(LayoutStyle.VersePerLine, UiStrings().VersePerLine)
|
||||
self.layout_style_combo_box.setItemText(LayoutStyle.Continuous, UiStrings().Continuous)
|
||||
self.display_style_combo_box.setItemText(DisplayStyle.NoBrackets,
|
||||
translate('BiblesPlugin.BiblesTab', 'No Brackets'))
|
||||
translate('BiblesPlugin.BiblesTab', 'No Brackets'))
|
||||
self.display_style_combo_box.setItemText(DisplayStyle.Round,
|
||||
translate('BiblesPlugin.BiblesTab', '( And )'))
|
||||
translate('BiblesPlugin.BiblesTab', '( And )'))
|
||||
self.display_style_combo_box.setItemText(DisplayStyle.Curly,
|
||||
translate('BiblesPlugin.BiblesTab', '{ And }'))
|
||||
translate('BiblesPlugin.BiblesTab', '{ And }'))
|
||||
self.display_style_combo_box.setItemText(DisplayStyle.Square,
|
||||
translate('BiblesPlugin.BiblesTab', '[ And ]'))
|
||||
translate('BiblesPlugin.BiblesTab', '[ And ]'))
|
||||
self.change_note_label.setText(translate('BiblesPlugin.BiblesTab',
|
||||
'Note:\nChanges do not affect verses already in the service.'))
|
||||
'Note:\nChanges do not affect verses already in the service.'))
|
||||
self.bible_second_check_box.setText(translate('BiblesPlugin.BiblesTab', 'Display second Bible verses'))
|
||||
self.scripture_reference_group_box.setTitle(translate('BiblesPlugin.BiblesTab', 'Custom Scripture References'))
|
||||
self.verse_separator_check_box.setText(translate('BiblesPlugin.BiblesTab', 'Verse Separator:'))
|
||||
@ -186,21 +186,21 @@ class BiblesTab(SettingsTab):
|
||||
self.list_separator_check_box.setText(translate('BiblesPlugin.BiblesTab', 'List Separator:'))
|
||||
self.end_separator_check_box.setText(translate('BiblesPlugin.BiblesTab', 'End Mark:'))
|
||||
tip_text = translate('BiblesPlugin.BiblesTab',
|
||||
'Multiple alternative verse separators may be defined.\nThey have to be separated by a vertical bar "|".'
|
||||
'\nPlease clear this edit line to use the default value.')
|
||||
'Multiple alternative verse separators may be defined.\nThey have to be separated by a '
|
||||
'vertical bar "|".\nPlease clear this edit line to use the default value.')
|
||||
self.verse_separator_line_edit.setToolTip(tip_text)
|
||||
self.range_separator_line_edit.setToolTip(tip_text)
|
||||
self.list_separator_line_edit.setToolTip(tip_text)
|
||||
self.end_separator_line_edit.setToolTip(tip_text)
|
||||
self.language_selection_group_box.setTitle(translate('BiblesPlugin.BiblesTab', 'Default Bible Language'))
|
||||
self.language_selection_label.setText(translate('BiblesPlugin.BiblesTab',
|
||||
'Book name language in search field,\nsearch results and on display:'))
|
||||
self.language_selection_combo_box.setItemText(LanguageSelection.Bible,
|
||||
translate('BiblesPlugin.BiblesTab', 'Bible Language'))
|
||||
self.language_selection_combo_box.setItemText(LanguageSelection.Application,
|
||||
translate('BiblesPlugin.BiblesTab', 'Application Language'))
|
||||
self.language_selection_combo_box.setItemText(LanguageSelection.English,
|
||||
translate('BiblesPlugin.BiblesTab', 'English'))
|
||||
self.language_selection_label.setText(
|
||||
translate('BiblesPlugin.BiblesTab', 'Book name language in search field,\nsearch results and on display:'))
|
||||
self.language_selection_combo_box.setItemText(
|
||||
LanguageSelection.Bible, translate('BiblesPlugin.BiblesTab', 'Bible Language'))
|
||||
self.language_selection_combo_box.setItemText(
|
||||
LanguageSelection.Application, translate('BiblesPlugin.BiblesTab', 'Application Language'))
|
||||
self.language_selection_combo_box.setItemText(
|
||||
LanguageSelection.English, translate('BiblesPlugin.BiblesTab', 'English'))
|
||||
|
||||
def on_bible_theme_combo_box_changed(self):
|
||||
self.bible_theme = self.bible_theme_combo_box.currentText()
|
||||
@ -208,7 +208,7 @@ class BiblesTab(SettingsTab):
|
||||
def on_display_style_combo_box_changed(self):
|
||||
self.display_style = self.display_style_combo_box.currentIndex()
|
||||
|
||||
def on_layout_style_combo_boxChanged(self):
|
||||
def on_layout_style_combo_box_changed(self):
|
||||
self.layout_style = self.layout_style_combo_box.currentIndex()
|
||||
|
||||
def on_language_selection_combo_box_changed(self):
|
||||
@ -238,11 +238,11 @@ class BiblesTab(SettingsTab):
|
||||
self.verse_separator_line_edit.setFocus()
|
||||
else:
|
||||
self.verse_separator_line_edit.setText(get_reference_separator('sep_v_default'))
|
||||
self.verse_separator_line_edit.setPalette(self.getGreyTextPalette(not checked))
|
||||
self.verse_separator_line_edit.setPalette(self.get_grey_text_palette(not checked))
|
||||
|
||||
def on_verse_separator_line_edit_edited(self, text):
|
||||
self.verse_separator_check_box.setChecked(True)
|
||||
self.verse_separator_line_edit.setPalette(self.getGreyTextPalette(False))
|
||||
self.verse_separator_line_edit.setPalette(self.get_grey_text_palette(False))
|
||||
|
||||
def on_verse_separator_line_edit_finished(self):
|
||||
if self.verse_separator_line_edit.isModified():
|
||||
@ -250,18 +250,18 @@ class BiblesTab(SettingsTab):
|
||||
if text == get_reference_separator('sep_v_default') or not text.replace('|', ''):
|
||||
self.verse_separator_check_box.setChecked(False)
|
||||
self.verse_separator_line_edit.setText(get_reference_separator('sep_v_default'))
|
||||
self.verse_separator_line_edit.setPalette(self.getGreyTextPalette(True))
|
||||
self.verse_separator_line_edit.setPalette(self.get_grey_text_palette(True))
|
||||
|
||||
def on_range_separator_check_box_clicked(self, checked):
|
||||
if checked:
|
||||
self.range_separator_line_edit.setFocus()
|
||||
else:
|
||||
self.range_separator_line_edit.setText(get_reference_separator('sep_r_default'))
|
||||
self.range_separator_line_edit.setPalette(self.getGreyTextPalette(not checked))
|
||||
self.range_separator_line_edit.setPalette(self.get_grey_text_palette(not checked))
|
||||
|
||||
def on_range_separator_line_edit_edited(self, text):
|
||||
self.range_separator_check_box.setChecked(True)
|
||||
self.range_separator_line_edit.setPalette(self.getGreyTextPalette(False))
|
||||
self.range_separator_line_edit.setPalette(self.get_grey_text_palette(False))
|
||||
|
||||
def on_range_separator_line_edit_finished(self):
|
||||
if self.range_separator_line_edit.isModified():
|
||||
@ -269,18 +269,18 @@ class BiblesTab(SettingsTab):
|
||||
if text == get_reference_separator('sep_r_default') or not text.replace('|', ''):
|
||||
self.range_separator_check_box.setChecked(False)
|
||||
self.range_separator_line_edit.setText(get_reference_separator('sep_r_default'))
|
||||
self.range_separator_line_edit.setPalette(self.getGreyTextPalette(True))
|
||||
self.range_separator_line_edit.setPalette(self.get_grey_text_palette(True))
|
||||
|
||||
def on_list_separator_check_box_clicked(self, checked):
|
||||
if checked:
|
||||
self.list_separator_line_edit.setFocus()
|
||||
else:
|
||||
self.list_separator_line_edit.setText(get_reference_separator('sep_l_default'))
|
||||
self.list_separator_line_edit.setPalette(self.getGreyTextPalette(not checked))
|
||||
self.list_separator_line_edit.setPalette(self.get_grey_text_palette(not checked))
|
||||
|
||||
def on_list_separator_line_edit_edited(self, text):
|
||||
self.list_separator_check_box.setChecked(True)
|
||||
self.list_separator_line_edit.setPalette(self.getGreyTextPalette(False))
|
||||
self.list_separator_line_edit.setPalette(self.get_grey_text_palette(False))
|
||||
|
||||
def on_list_separator_line_edit_finished(self):
|
||||
if self.list_separator_line_edit.isModified():
|
||||
@ -288,18 +288,18 @@ class BiblesTab(SettingsTab):
|
||||
if text == get_reference_separator('sep_l_default') or not text.replace('|', ''):
|
||||
self.list_separator_check_box.setChecked(False)
|
||||
self.list_separator_line_edit.setText(get_reference_separator('sep_l_default'))
|
||||
self.list_separator_line_edit.setPalette(self.getGreyTextPalette(True))
|
||||
self.list_separator_line_edit.setPalette(self.get_grey_text_palette(True))
|
||||
|
||||
def on_end_separator_check_box_clicked(self, checked):
|
||||
if checked:
|
||||
self.end_separator_line_edit.setFocus()
|
||||
else:
|
||||
self.end_separator_line_edit.setText(get_reference_separator('sep_e_default'))
|
||||
self.end_separator_line_edit.setPalette(self.getGreyTextPalette(not checked))
|
||||
self.end_separator_line_edit.setPalette(self.get_grey_text_palette(not checked))
|
||||
|
||||
def on_end_separator_line_edit_edited(self, text):
|
||||
self.end_separator_check_box.setChecked(True)
|
||||
self.end_separator_line_edit.setPalette(self.getGreyTextPalette(False))
|
||||
self.end_separator_line_edit.setPalette(self.get_grey_text_palette(False))
|
||||
|
||||
def on_end_separator_line_edit_finished(self):
|
||||
if self.end_separator_line_edit.isModified():
|
||||
@ -307,7 +307,7 @@ class BiblesTab(SettingsTab):
|
||||
if text == get_reference_separator('sep_e_default') or not text.replace('|', ''):
|
||||
self.end_separator_check_box.setChecked(False)
|
||||
self.end_separator_line_edit.setText(get_reference_separator('sep_e_default'))
|
||||
self.end_separator_line_edit.setPalette(self.getGreyTextPalette(True))
|
||||
self.end_separator_line_edit.setPalette(self.get_grey_text_palette(True))
|
||||
|
||||
def load(self):
|
||||
settings = Settings()
|
||||
@ -327,38 +327,38 @@ class BiblesTab(SettingsTab):
|
||||
verse_separator = settings.value('verse separator')
|
||||
if (verse_separator.strip('|') == '') or (verse_separator == get_reference_separator('sep_v_default')):
|
||||
self.verse_separator_line_edit.setText(get_reference_separator('sep_v_default'))
|
||||
self.verse_separator_line_edit.setPalette(self.getGreyTextPalette(True))
|
||||
self.verse_separator_line_edit.setPalette(self.get_grey_text_palette(True))
|
||||
self.verse_separator_check_box.setChecked(False)
|
||||
else:
|
||||
self.verse_separator_line_edit.setText(verse_separator)
|
||||
self.verse_separator_line_edit.setPalette(self.getGreyTextPalette(False))
|
||||
self.verse_separator_line_edit.setPalette(self.get_grey_text_palette(False))
|
||||
self.verse_separator_check_box.setChecked(True)
|
||||
range_separator = settings.value('range separator')
|
||||
if (range_separator.strip('|') == '') or (range_separator == get_reference_separator('sep_r_default')):
|
||||
self.range_separator_line_edit.setText(get_reference_separator('sep_r_default'))
|
||||
self.range_separator_line_edit.setPalette(self.getGreyTextPalette(True))
|
||||
self.range_separator_line_edit.setPalette(self.get_grey_text_palette(True))
|
||||
self.range_separator_check_box.setChecked(False)
|
||||
else:
|
||||
self.range_separator_line_edit.setText(range_separator)
|
||||
self.range_separator_line_edit.setPalette(self.getGreyTextPalette(False))
|
||||
self.range_separator_line_edit.setPalette(self.get_grey_text_palette(False))
|
||||
self.range_separator_check_box.setChecked(True)
|
||||
list_separator = settings.value('list separator')
|
||||
if (list_separator.strip('|') == '') or (list_separator == get_reference_separator('sep_l_default')):
|
||||
self.list_separator_line_edit.setText(get_reference_separator('sep_l_default'))
|
||||
self.list_separator_line_edit.setPalette(self.getGreyTextPalette(True))
|
||||
self.list_separator_line_edit.setPalette(self.get_grey_text_palette(True))
|
||||
self.list_separator_check_box.setChecked(False)
|
||||
else:
|
||||
self.list_separator_line_edit.setText(list_separator)
|
||||
self.list_separator_line_edit.setPalette(self.getGreyTextPalette(False))
|
||||
self.list_separator_line_edit.setPalette(self.get_grey_text_palette(False))
|
||||
self.list_separator_check_box.setChecked(True)
|
||||
end_separator = settings.value('end separator')
|
||||
if (end_separator.strip('|') == '') or (end_separator == get_reference_separator('sep_e_default')):
|
||||
self.end_separator_line_edit.setText(get_reference_separator('sep_e_default'))
|
||||
self.end_separator_line_edit.setPalette(self.getGreyTextPalette(True))
|
||||
self.end_separator_line_edit.setPalette(self.get_grey_text_palette(True))
|
||||
self.end_separator_check_box.setChecked(False)
|
||||
else:
|
||||
self.end_separator_line_edit.setText(end_separator)
|
||||
self.end_separator_line_edit.setPalette(self.getGreyTextPalette(False))
|
||||
self.end_separator_line_edit.setPalette(self.get_grey_text_palette(False))
|
||||
self.end_separator_check_box.setChecked(True)
|
||||
self.language_selection = settings.value('book name language')
|
||||
self.language_selection_combo_box.setCurrentIndex(self.language_selection)
|
||||
@ -402,7 +402,7 @@ class BiblesTab(SettingsTab):
|
||||
"""
|
||||
Called from ThemeManager when the Themes have changed.
|
||||
|
||||
``theme_list``
|
||||
:param theme_list:
|
||||
The list of available themes::
|
||||
|
||||
[u'Bible Theme', u'Song Theme']
|
||||
@ -412,7 +412,7 @@ class BiblesTab(SettingsTab):
|
||||
self.bible_theme_combo_box.addItems(theme_list)
|
||||
find_and_set_in_combo_box(self.bible_theme_combo_box, self.bible_theme)
|
||||
|
||||
def getGreyTextPalette(self, greyed):
|
||||
def get_grey_text_palette(self, greyed):
|
||||
"""
|
||||
Returns a QPalette with greyed out text as used for placeholderText.
|
||||
"""
|
||||
|
@ -80,8 +80,8 @@ class CSVBible(BibleDB):
|
||||
"""
|
||||
log.info(self.__class__.__name__)
|
||||
BibleDB.__init__(self, parent, **kwargs)
|
||||
self.booksfile = kwargs['booksfile']
|
||||
self.versesfile = kwargs['versefile']
|
||||
self.books_file = kwargs['books_file']
|
||||
self.verses_file = kwargs['versefile']
|
||||
|
||||
def do_import(self, bible_name=None):
|
||||
"""
|
||||
@ -99,8 +99,8 @@ class CSVBible(BibleDB):
|
||||
book_list = {}
|
||||
# Populate the Tables
|
||||
try:
|
||||
details = get_file_encoding(self.booksfile)
|
||||
books_file = open(self.booksfile, 'r')
|
||||
details = get_file_encoding(self.books_file)
|
||||
books_file = open(self.books_file, 'r')
|
||||
if not books_file.read(3) == '\xEF\xBB\xBF':
|
||||
# no BOM was found
|
||||
books_file.seek(0)
|
||||
@ -109,10 +109,10 @@ class CSVBible(BibleDB):
|
||||
if self.stop_import_flag:
|
||||
break
|
||||
self.wizard.increment_progress_bar(translate('BiblesPlugin.CSVBible', 'Importing books... %s') %
|
||||
str(line[2], details['encoding']))
|
||||
str(line[2], details['encoding']))
|
||||
book_ref_id = self.get_book_ref_id_by_name(str(line[2], details['encoding']), 67, language_id)
|
||||
if not book_ref_id:
|
||||
log.error('Importing books from "%s" failed' % self.booksfile)
|
||||
log.error('Importing books from "%s" failed' % self.books_file)
|
||||
return False
|
||||
book_details = BiblesResourcesDB.get_book_by_id(book_ref_id)
|
||||
self.create_book(str(line[2], details['encoding']), book_ref_id, book_details['testament_id'])
|
||||
@ -131,8 +131,8 @@ class CSVBible(BibleDB):
|
||||
verse_file = None
|
||||
try:
|
||||
book_ptr = None
|
||||
details = get_file_encoding(self.versesfile)
|
||||
verse_file = open(self.versesfile, 'rb')
|
||||
details = get_file_encoding(self.verses_file)
|
||||
verse_file = open(self.verses_file, 'rb')
|
||||
if not verse_file.read(3) == '\xEF\xBB\xBF':
|
||||
# no BOM was found
|
||||
verse_file.seek(0)
|
||||
@ -147,8 +147,9 @@ class CSVBible(BibleDB):
|
||||
if book_ptr != line_book:
|
||||
book = self.get_book(line_book)
|
||||
book_ptr = book.name
|
||||
self.wizard.increment_progress_bar(translate('BiblesPlugin.CSVBible',
|
||||
'Importing verses from %s... Importing verses from <book name>...') % book.name)
|
||||
self.wizard.increment_progress_bar(
|
||||
translate('BiblesPlugin.CSVBible',
|
||||
'Importing verses from %s... Importing verses from <book name>...') % book.name)
|
||||
self.session.commit()
|
||||
try:
|
||||
verse_text = str(line[3], details['encoding'])
|
||||
@ -169,6 +170,7 @@ class CSVBible(BibleDB):
|
||||
else:
|
||||
return success
|
||||
|
||||
|
||||
def get_file_encoding(filename):
|
||||
"""
|
||||
Utility function to get the file encoding.
|
||||
|
@ -48,6 +48,7 @@ log = logging.getLogger(__name__)
|
||||
|
||||
RESERVED_CHARACTERS = '\\.^$*+?{}[]()'
|
||||
|
||||
|
||||
class BibleMeta(BaseModel):
|
||||
"""
|
||||
Bible Meta Data
|
||||
@ -79,23 +80,23 @@ def init_schema(url):
|
||||
session, metadata = init_db(url)
|
||||
|
||||
meta_table = Table('metadata', metadata,
|
||||
Column('key', types.Unicode(255), primary_key=True, index=True),
|
||||
Column('value', types.Unicode(255)),
|
||||
Column('key', types.Unicode(255), primary_key=True, index=True),
|
||||
Column('value', types.Unicode(255)),
|
||||
)
|
||||
|
||||
book_table = Table('book', metadata,
|
||||
Column('id', types.Integer, primary_key=True),
|
||||
Column('book_reference_id', types.Integer, index=True),
|
||||
Column('testament_reference_id', types.Integer),
|
||||
Column('name', types.Unicode(50), index=True),
|
||||
Column('id', types.Integer, primary_key=True),
|
||||
Column('book_reference_id', types.Integer, index=True),
|
||||
Column('testament_reference_id', types.Integer),
|
||||
Column('name', types.Unicode(50), index=True),
|
||||
)
|
||||
verse_table = Table('verse', metadata,
|
||||
Column('id', types.Integer, primary_key=True, index=True),
|
||||
Column('book_id', types.Integer, ForeignKey(
|
||||
'book.id'), index=True),
|
||||
Column('chapter', types.Integer, index=True),
|
||||
Column('verse', types.Integer, index=True),
|
||||
Column('text', types.UnicodeText, index=True),
|
||||
Column('id', types.Integer, primary_key=True, index=True),
|
||||
Column('book_id', types.Integer, ForeignKey(
|
||||
'book.id'), index=True),
|
||||
Column('chapter', types.Integer, index=True),
|
||||
Column('verse', types.Integer, index=True),
|
||||
Column('text', types.UnicodeText, index=True),
|
||||
)
|
||||
|
||||
try:
|
||||
@ -105,8 +106,7 @@ def init_schema(url):
|
||||
try:
|
||||
class_mapper(Book)
|
||||
except UnmappedClassError:
|
||||
mapper(Book, book_table,
|
||||
properties={'verses': relation(Verse, backref='book')})
|
||||
mapper(Book, book_table, properties={'verses': relation(Verse, backref='book')})
|
||||
try:
|
||||
class_mapper(Verse)
|
||||
except UnmappedClassError:
|
||||
@ -118,9 +118,8 @@ def init_schema(url):
|
||||
|
||||
class BibleDB(QtCore.QObject, Manager):
|
||||
"""
|
||||
This class represents a database-bound Bible. It is used as a base class
|
||||
for all the custom importers, so that the can implement their own import
|
||||
methods, but benefit from the database methods in here via inheritance,
|
||||
This class represents a database-bound Bible. It is used as a base class for all the custom importers, so that
|
||||
the can implement their own import methods, but benefit from the database methods in here via inheritance,
|
||||
rather than depending on yet another object.
|
||||
"""
|
||||
log.info('BibleDB loaded')
|
||||
@ -130,14 +129,13 @@ class BibleDB(QtCore.QObject, Manager):
|
||||
The constructor loads up the database and creates and initialises the
|
||||
tables if the database doesn't exist.
|
||||
|
||||
**Required keyword arguments:**
|
||||
:param parent:
|
||||
:param kwargs:
|
||||
``path``
|
||||
The path to the bible database file.
|
||||
|
||||
``path``
|
||||
The path to the bible database file.
|
||||
|
||||
``name``
|
||||
The name of the database. This is also used as the file name for
|
||||
SQLite databases.
|
||||
``name``
|
||||
The name of the database. This is also used as the file name for SQLite databases.
|
||||
"""
|
||||
log.info('BibleDB loaded')
|
||||
QtCore.QObject.__init__(self)
|
||||
@ -179,13 +177,11 @@ class BibleDB(QtCore.QObject, Manager):
|
||||
|
||||
def register(self, wizard):
|
||||
"""
|
||||
This method basically just initialialises the database. It is called
|
||||
from the Bible Manager when a Bible is imported. Descendant classes
|
||||
may want to override this method to supply their own custom
|
||||
This method basically just initialises the database. It is called from the Bible Manager when a Bible is
|
||||
imported. Descendant classes may want to override this method to supply their own custom
|
||||
initialisation as well.
|
||||
|
||||
``wizard``
|
||||
The actual Qt wizard form.
|
||||
:param wizard: The actual Qt wizard form.
|
||||
"""
|
||||
self.wizard = wizard
|
||||
return self.name
|
||||
@ -194,14 +190,9 @@ class BibleDB(QtCore.QObject, Manager):
|
||||
"""
|
||||
Add a book to the database.
|
||||
|
||||
``name``
|
||||
The name of the book.
|
||||
|
||||
``bk_ref_id``
|
||||
The book_reference_id from bibles_resources.sqlite of the book.
|
||||
|
||||
``testament``
|
||||
*Defaults to 1.* The testament_reference_id from
|
||||
:param name: The name of the book.
|
||||
:param bk_ref_id: The book_reference_id from bibles_resources.sqlite of the book.
|
||||
:param testament: *Defaults to 1.* The testament_reference_id from
|
||||
bibles_resources.sqlite of the testament this book belongs to.
|
||||
"""
|
||||
log.debug('BibleDB.create_book("%s", "%s")', name, bk_ref_id)
|
||||
@ -213,8 +204,7 @@ class BibleDB(QtCore.QObject, Manager):
|
||||
"""
|
||||
Update a book in the database.
|
||||
|
||||
``book``
|
||||
The book object
|
||||
:param book: The book object
|
||||
"""
|
||||
log.debug('BibleDB.update_book("%s")', book.name)
|
||||
return self.save_object(book)
|
||||
@ -223,31 +213,24 @@ class BibleDB(QtCore.QObject, Manager):
|
||||
"""
|
||||
Delete a book from the database.
|
||||
|
||||
``db_book``
|
||||
The book object.
|
||||
:param db_book: The book object.
|
||||
"""
|
||||
log.debug('BibleDB.delete_book("%s")', db_book.name)
|
||||
if self.delete_object(Book, db_book.id):
|
||||
return True
|
||||
return False
|
||||
|
||||
def create_chapter(self, book_id, chapter, textlist):
|
||||
def create_chapter(self, book_id, chapter, text_list):
|
||||
"""
|
||||
Add a chapter and its verses to a book.
|
||||
|
||||
``book_id``
|
||||
The id of the book being appended.
|
||||
|
||||
``chapter``
|
||||
The chapter number.
|
||||
|
||||
``textlist``
|
||||
A dict of the verses to be inserted. The key is the verse number,
|
||||
and the value is the verse text.
|
||||
:param book_id: The id of the book being appended.
|
||||
:param chapter: The chapter number.
|
||||
:param text_list: A dict of the verses to be inserted. The key is the verse number, and the value is the verse text.
|
||||
"""
|
||||
log.debug('BibleDBcreate_chapter("%s", "%s")', book_id, chapter)
|
||||
# Text list has book and chapter as first two elements of the array.
|
||||
for verse_number, verse_text in textlist.items():
|
||||
for verse_number, verse_text in text_list.items():
|
||||
verse = Verse.populate(
|
||||
book_id=book_id,
|
||||
chapter=chapter,
|
||||
@ -261,17 +244,10 @@ class BibleDB(QtCore.QObject, Manager):
|
||||
"""
|
||||
Add a single verse to a chapter.
|
||||
|
||||
``book_id``
|
||||
The id of the book being appended.
|
||||
|
||||
``chapter``
|
||||
The chapter number.
|
||||
|
||||
``verse``
|
||||
The verse number.
|
||||
|
||||
``text``
|
||||
The verse text.
|
||||
:param book_id: The id of the book being appended.
|
||||
:param chapter: The chapter number.
|
||||
:param verse: The verse number.
|
||||
:param text: The verse text.
|
||||
"""
|
||||
if not isinstance(text, str):
|
||||
details = chardet.detect(text)
|
||||
@ -289,11 +265,8 @@ class BibleDB(QtCore.QObject, Manager):
|
||||
"""
|
||||
Utility method to save or update BibleMeta objects in a Bible database.
|
||||
|
||||
``key``
|
||||
The key for this instance.
|
||||
|
||||
``value``
|
||||
The value for this instance.
|
||||
:param key: The key for this instance.
|
||||
:param value: The value for this instance.
|
||||
"""
|
||||
if not isinstance(value, str):
|
||||
value = str(value)
|
||||
@ -309,8 +282,7 @@ class BibleDB(QtCore.QObject, Manager):
|
||||
"""
|
||||
Return a book object from the database.
|
||||
|
||||
``book``
|
||||
The name of the book to return.
|
||||
:param book: The name of the book to return.
|
||||
"""
|
||||
log.debug('BibleDB.get_book("%s")', book)
|
||||
return self.get_object_filtered(Book, Book.name.like(book + '%'))
|
||||
@ -327,8 +299,7 @@ class BibleDB(QtCore.QObject, Manager):
|
||||
"""
|
||||
Return a book object from the database.
|
||||
|
||||
``id``
|
||||
The reference id of the book to return.
|
||||
:param id: The reference id of the book to return.
|
||||
"""
|
||||
log.debug('BibleDB.get_book_by_book_ref_id("%s")', id)
|
||||
return self.get_object_filtered(Book, Book.book_reference_id.like(id))
|
||||
@ -357,11 +328,8 @@ class BibleDB(QtCore.QObject, Manager):
|
||||
"""
|
||||
Return the id of a named book.
|
||||
|
||||
``book``
|
||||
The name of the book, according to the selected language.
|
||||
|
||||
``language_selection``
|
||||
The language selection the user has chosen in the settings section of the Bible.
|
||||
:param book: The name of the book, according to the selected language.
|
||||
:param language_selection: The language selection the user has chosen in the settings section of the Bible.
|
||||
"""
|
||||
log.debug('get_book_ref_id_by_localised_name("%s", "%s")', book, language_selection)
|
||||
from openlp.plugins.bibles.lib import LanguageSelection, BibleStrings
|
||||
@ -398,8 +366,7 @@ class BibleDB(QtCore.QObject, Manager):
|
||||
This is probably the most used function. It retrieves the list of
|
||||
verses based on the user's query.
|
||||
|
||||
``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 the following format::
|
||||
|
||||
(book_reference_id, chapter, start_verse, end_verse)
|
||||
@ -410,6 +377,7 @@ class BibleDB(QtCore.QObject, Manager):
|
||||
list of ``Verse`` objects. For example::
|
||||
|
||||
[(u'35', 1, 1, 1), (u'35', 2, 2, 3)]
|
||||
:param show_error:
|
||||
"""
|
||||
log.debug('BibleDB.get_verses("%s")', reference_list)
|
||||
verse_list = []
|
||||
@ -436,14 +404,14 @@ class BibleDB(QtCore.QObject, Manager):
|
||||
critical_error_message_box(
|
||||
translate('BiblesPlugin', 'No Book Found'),
|
||||
translate('BiblesPlugin', 'No matching book '
|
||||
'could be found in this Bible. Check that you have spelled the name of the book correctly.'))
|
||||
'could be found in this Bible. Check that you have spelled the name of the book correctly.'))
|
||||
return verse_list
|
||||
|
||||
def verse_search(self, text):
|
||||
"""
|
||||
Search for verses containing text ``text``.
|
||||
|
||||
``text``
|
||||
:param text:
|
||||
The text to search for. If the text contains commas, it will be
|
||||
split apart and OR'd on the list of values. If the text just
|
||||
contains spaces, it will split apart and AND'd on the list of
|
||||
@ -452,13 +420,11 @@ class BibleDB(QtCore.QObject, Manager):
|
||||
log.debug('BibleDB.verse_search("%s")', text)
|
||||
verses = self.session.query(Verse)
|
||||
if text.find(',') > -1:
|
||||
keywords = \
|
||||
['%%%s%%' % keyword.strip() for keyword in text.split(',')]
|
||||
keywords = ['%%%s%%' % keyword.strip() for keyword in text.split(',')]
|
||||
or_clause = [Verse.text.like(keyword) for keyword in keywords]
|
||||
verses = verses.filter(or_(*or_clause))
|
||||
else:
|
||||
keywords = \
|
||||
['%%%s%%' % keyword.strip() for keyword in text.split(' ')]
|
||||
keywords = ['%%%s%%' % keyword.strip() for keyword in text.split(' ')]
|
||||
for keyword in keywords:
|
||||
verses = verses.filter(Verse.text.like(keyword))
|
||||
verses = verses.all()
|
||||
@ -468,8 +434,7 @@ class BibleDB(QtCore.QObject, Manager):
|
||||
"""
|
||||
Return the number of chapters in a book.
|
||||
|
||||
``book``
|
||||
The book object to get the chapter count for.
|
||||
:param book: The book object to get the chapter count for.
|
||||
"""
|
||||
log.debug('BibleDB.get_chapter_count("%s")', book.name)
|
||||
count = self.session.query(func.max(Verse.chapter)).join(Book).filter(
|
||||
@ -482,11 +447,8 @@ class BibleDB(QtCore.QObject, Manager):
|
||||
"""
|
||||
Return the number of verses in a chapter.
|
||||
|
||||
``book_ref_id``
|
||||
The book reference id.
|
||||
|
||||
``chapter``
|
||||
The chapter to get the verse count for.
|
||||
:param book_ref_id: The book reference id.
|
||||
:param chapter: The chapter to get the verse count for.
|
||||
"""
|
||||
log.debug('BibleDB.get_verse_count("%s", "%s")', book_ref_id, chapter)
|
||||
count = self.session.query(func.max(Verse.verse)).join(Book) \
|
||||
@ -499,12 +461,10 @@ class BibleDB(QtCore.QObject, Manager):
|
||||
|
||||
def get_language(self, bible_name=None):
|
||||
"""
|
||||
If no language is given it calls a dialog window where the user could
|
||||
select the bible language.
|
||||
If no language is given it calls a dialog window where the user could select the bible language.
|
||||
Return the language id of a bible.
|
||||
|
||||
``book``
|
||||
The language the bible is.
|
||||
:param bible_name: The language the bible is.
|
||||
"""
|
||||
log.debug('BibleDB.get_language()')
|
||||
from openlp.plugins.bibles.forms import LanguageForm
|
||||
@ -521,8 +481,7 @@ class BibleDB(QtCore.QObject, Manager):
|
||||
|
||||
def is_old_database(self):
|
||||
"""
|
||||
Returns ``True`` if it is a bible database, which has been created
|
||||
prior to 1.9.6.
|
||||
Returns ``True`` if it is a bible database, which has been created prior to 1.9.6.
|
||||
"""
|
||||
try:
|
||||
self.session.query(Book).all()
|
||||
@ -576,9 +535,9 @@ class BiblesResourcesDB(QtCore.QObject, Manager):
|
||||
Return the cursor object. Instantiate one if it doesn't exist yet.
|
||||
"""
|
||||
if BiblesResourcesDB.cursor is None:
|
||||
filepath = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir),
|
||||
'bibles', 'resources', 'bibles_resources.sqlite')
|
||||
conn = sqlite3.connect(filepath)
|
||||
file_path = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir),
|
||||
'bibles', 'resources', 'bibles_resources.sqlite')
|
||||
conn = sqlite3.connect(file_path)
|
||||
BiblesResourcesDB.cursor = conn.cursor()
|
||||
return BiblesResourcesDB.cursor
|
||||
|
||||
@ -603,8 +562,8 @@ class BiblesResourcesDB(QtCore.QObject, Manager):
|
||||
Return a list of all the books of the Bible.
|
||||
"""
|
||||
log.debug('BiblesResourcesDB.get_books()')
|
||||
books = BiblesResourcesDB.run_sql('SELECT id, testament_id, name, '
|
||||
'abbreviation, chapters FROM book_reference ORDER BY id')
|
||||
books = BiblesResourcesDB.run_sql(
|
||||
'SELECT id, testament_id, name, abbreviation, chapters FROM book_reference ORDER BY id')
|
||||
return [{
|
||||
'id': book[0],
|
||||
'testament_id': book[1],
|
||||
@ -618,24 +577,20 @@ class BiblesResourcesDB(QtCore.QObject, Manager):
|
||||
"""
|
||||
Return a book by name or abbreviation.
|
||||
|
||||
``name``
|
||||
The name or abbreviation of the book.
|
||||
|
||||
``lower``
|
||||
True if the comparsion should be only lowercase
|
||||
:param name: The name or abbreviation of the book.
|
||||
:param lower: True if the comparsion should be only lowercase
|
||||
"""
|
||||
log.debug('BiblesResourcesDB.get_book("%s")', name)
|
||||
if not isinstance(name, str):
|
||||
name = str(name)
|
||||
if lower:
|
||||
books = BiblesResourcesDB.run_sql('SELECT id, testament_id, name, '
|
||||
'abbreviation, chapters FROM book_reference WHERE '
|
||||
'LOWER(name) = ? OR LOWER(abbreviation) = ?',
|
||||
(name.lower(), name.lower()))
|
||||
books = BiblesResourcesDB.run_sql(
|
||||
'SELECT id, testament_id, name, abbreviation, chapters FROM book_reference WHERE '
|
||||
'LOWER(name) = ? OR LOWER(abbreviation) = ?', (name.lower(), name.lower()))
|
||||
else:
|
||||
books = BiblesResourcesDB.run_sql('SELECT id, testament_id, name, '
|
||||
'abbreviation, chapters FROM book_reference WHERE name = ?'
|
||||
' OR abbreviation = ?', (name, name))
|
||||
books = BiblesResourcesDB.run_sql(
|
||||
'SELECT id, testament_id, name, abbreviation, chapters FROM book_reference WHERE name = ?'
|
||||
' OR abbreviation = ?', (name, name))
|
||||
if books:
|
||||
return {
|
||||
'id': books[0][0],
|
||||
@ -652,16 +607,15 @@ class BiblesResourcesDB(QtCore.QObject, Manager):
|
||||
"""
|
||||
Return the books which include string.
|
||||
|
||||
``string``
|
||||
The string to search for in the book names or abbreviations.
|
||||
:param string: The string to search for in the book names or abbreviations.
|
||||
"""
|
||||
log.debug('BiblesResourcesDB.get_book_like("%s")', string)
|
||||
if not isinstance(string, str):
|
||||
name = str(string)
|
||||
books = BiblesResourcesDB.run_sql('SELECT id, testament_id, name, '
|
||||
'abbreviation, chapters FROM book_reference WHERE '
|
||||
'LOWER(name) LIKE ? OR LOWER(abbreviation) LIKE ?',
|
||||
('%' + string.lower() + '%', '%' + string.lower() + '%'))
|
||||
books = BiblesResourcesDB.run_sql(
|
||||
'SELECT id, testament_id, name, abbreviation, chapters FROM book_reference WHERE '
|
||||
'LOWER(name) LIKE ? OR LOWER(abbreviation) LIKE ?',
|
||||
('%' + string.lower() + '%', '%' + string.lower() + '%'))
|
||||
if books:
|
||||
return [{
|
||||
'id': book[0],
|
||||
@ -678,14 +632,13 @@ class BiblesResourcesDB(QtCore.QObject, Manager):
|
||||
"""
|
||||
Return a book by id.
|
||||
|
||||
``id``
|
||||
The id of the book.
|
||||
:param id: The id of the book.
|
||||
"""
|
||||
log.debug('BiblesResourcesDB.get_book_by_id("%s")', id)
|
||||
if not isinstance(id, int):
|
||||
id = int(id)
|
||||
books = BiblesResourcesDB.run_sql('SELECT id, testament_id, name, '
|
||||
'abbreviation, chapters FROM book_reference WHERE id = ?', (id, ))
|
||||
books = BiblesResourcesDB.run_sql(
|
||||
'SELECT id, testament_id, name, abbreviation, chapters FROM book_reference WHERE id = ?', (id, ))
|
||||
if books:
|
||||
return {
|
||||
'id': books[0][0],
|
||||
@ -702,16 +655,14 @@ class BiblesResourcesDB(QtCore.QObject, Manager):
|
||||
"""
|
||||
Return the chapter details for a specific chapter of a book.
|
||||
|
||||
``book_ref_id``
|
||||
The id of a book.
|
||||
|
||||
``chapter``
|
||||
The chapter number.
|
||||
:param book_ref_id: The id of a book.
|
||||
:param chapter: The chapter number.
|
||||
"""
|
||||
log.debug('BiblesResourcesDB.get_chapter("%s", "%s")', book_ref_id, chapter)
|
||||
if not isinstance(chapter, int):
|
||||
chapter = int(chapter)
|
||||
chapters = BiblesResourcesDB.run_sql('SELECT id, book_reference_id, '
|
||||
chapters = BiblesResourcesDB.run_sql(
|
||||
'SELECT id, book_reference_id, '
|
||||
'chapter, verse_count FROM chapters WHERE book_reference_id = ?', (book_ref_id,))
|
||||
try:
|
||||
return {
|
||||
@ -728,8 +679,7 @@ class BiblesResourcesDB(QtCore.QObject, Manager):
|
||||
"""
|
||||
Return the number of chapters in a book.
|
||||
|
||||
``book_ref_id``
|
||||
The id of the book.
|
||||
:param book_ref_id: The id of the book.
|
||||
"""
|
||||
log.debug('BiblesResourcesDB.get_chapter_count("%s")', book_ref_id)
|
||||
details = BiblesResourcesDB.get_book_by_id(book_ref_id)
|
||||
@ -742,11 +692,8 @@ class BiblesResourcesDB(QtCore.QObject, Manager):
|
||||
"""
|
||||
Return the number of verses in a chapter.
|
||||
|
||||
``book``
|
||||
The id of the book.
|
||||
|
||||
``chapter``
|
||||
The number of the chapter.
|
||||
:param book_ref_id: The id of the book.
|
||||
:param chapter: The number of the chapter.
|
||||
"""
|
||||
log.debug('BiblesResourcesDB.get_verse_count("%s", "%s")', book_ref_id, chapter)
|
||||
details = BiblesResourcesDB.get_chapter(book_ref_id, chapter)
|
||||
@ -759,15 +706,14 @@ class BiblesResourcesDB(QtCore.QObject, Manager):
|
||||
"""
|
||||
Return a download_source_id by source.
|
||||
|
||||
``name``
|
||||
The name or abbreviation of the book.
|
||||
:param source: The name or abbreviation of the book.
|
||||
"""
|
||||
log.debug('BiblesResourcesDB.get_download_source("%s")', source)
|
||||
if not isinstance(source, str):
|
||||
source = str(source)
|
||||
source = source.title()
|
||||
dl_source = BiblesResourcesDB.run_sql('SELECT id, source FROM '
|
||||
'download_source WHERE source = ?', (source.lower(),))
|
||||
dl_source = BiblesResourcesDB.run_sql(
|
||||
'SELECT id, source FROM download_source WHERE source = ?', (source.lower(),))
|
||||
if dl_source:
|
||||
return {
|
||||
'id': dl_source[0][0],
|
||||
@ -781,8 +727,7 @@ class BiblesResourcesDB(QtCore.QObject, Manager):
|
||||
"""
|
||||
Return the bibles a webbible provide for download.
|
||||
|
||||
``source``
|
||||
The source of the webbible.
|
||||
:param source: The source of the webbible.
|
||||
"""
|
||||
log.debug('BiblesResourcesDB.get_webbibles("%s")', source)
|
||||
if not isinstance(source, str):
|
||||
@ -806,11 +751,8 @@ class BiblesResourcesDB(QtCore.QObject, Manager):
|
||||
"""
|
||||
Return the bibles a webbible provide for download.
|
||||
|
||||
``abbreviation``
|
||||
The abbreviation of the webbible.
|
||||
|
||||
``source``
|
||||
The source of the webbible.
|
||||
:param abbreviation: The abbreviation of the webbible.
|
||||
:param source: The source of the webbible.
|
||||
"""
|
||||
log.debug('BiblesResourcesDB.get_webbibles("%s", "%s")', abbreviation, source)
|
||||
if not isinstance(abbreviation, str):
|
||||
@ -818,8 +760,8 @@ class BiblesResourcesDB(QtCore.QObject, Manager):
|
||||
if not isinstance(source, str):
|
||||
source = str(source)
|
||||
source = BiblesResourcesDB.get_download_source(source)
|
||||
bible = BiblesResourcesDB.run_sql('SELECT id, name, abbreviation, '
|
||||
'language_id, download_source_id FROM webbibles WHERE '
|
||||
bible = BiblesResourcesDB.run_sql(
|
||||
'SELECT id, name, abbreviation, language_id, download_source_id FROM webbibles WHERE '
|
||||
'download_source_id = ? AND abbreviation = ?', (source['id'], abbreviation))
|
||||
try:
|
||||
return {
|
||||
@ -837,16 +779,14 @@ class BiblesResourcesDB(QtCore.QObject, Manager):
|
||||
"""
|
||||
Return a book_reference_id if the name matches.
|
||||
|
||||
``name``
|
||||
The name to search the id.
|
||||
|
||||
``language_id``
|
||||
The language_id for which language should be searched
|
||||
:param name: The name to search the id.
|
||||
:param language_id: The language_id for which language should be searched
|
||||
"""
|
||||
log.debug('BiblesResourcesDB.get_alternative_book_name("%s", "%s")', name, language_id)
|
||||
if language_id:
|
||||
books = BiblesResourcesDB.run_sql('SELECT book_reference_id, name '
|
||||
'FROM alternative_book_names WHERE language_id = ? ORDER BY id', (language_id, ))
|
||||
books = BiblesResourcesDB.run_sql(
|
||||
'SELECT book_reference_id, name FROM alternative_book_names WHERE language_id = ? ORDER BY id',
|
||||
(language_id, ))
|
||||
else:
|
||||
books = BiblesResourcesDB.run_sql('SELECT book_reference_id, name FROM alternative_book_names ORDER BY id')
|
||||
for book in books:
|
||||
@ -857,17 +797,15 @@ class BiblesResourcesDB(QtCore.QObject, Manager):
|
||||
@staticmethod
|
||||
def get_language(name):
|
||||
"""
|
||||
Return a dict containing the language id, name and code by name or
|
||||
abbreviation.
|
||||
Return a dict containing the language id, name and code by name or abbreviation.
|
||||
|
||||
``name``
|
||||
The name or abbreviation of the language.
|
||||
:param name: The name or abbreviation of the language.
|
||||
"""
|
||||
log.debug('BiblesResourcesDB.get_language("%s")', name)
|
||||
if not isinstance(name, str):
|
||||
name = str(name)
|
||||
language = BiblesResourcesDB.run_sql('SELECT id, name, code FROM '
|
||||
'language WHERE name = ? OR code = ?', (name, name.lower()))
|
||||
language = BiblesResourcesDB.run_sql(
|
||||
'SELECT id, name, code FROM language WHERE name = ? OR code = ?', (name, name.lower()))
|
||||
if language:
|
||||
return {
|
||||
'id': language[0][0],
|
||||
@ -920,22 +858,20 @@ class AlternativeBookNamesDB(QtCore.QObject, Manager):
|
||||
def get_cursor():
|
||||
"""
|
||||
Return the cursor object. Instantiate one if it doesn't exist yet.
|
||||
If necessary loads up the database and creates the tables if the
|
||||
database doesn't exist.
|
||||
If necessary loads up the database and creates the tables if the database doesn't exist.
|
||||
"""
|
||||
if AlternativeBookNamesDB.cursor is None:
|
||||
filepath = os.path.join(
|
||||
file_path = os.path.join(
|
||||
AppLocation.get_directory(AppLocation.DataDir), 'bibles', 'alternative_book_names.sqlite')
|
||||
if not os.path.exists(filepath):
|
||||
if not os.path.exists(file_path):
|
||||
#create new DB, create table alternative_book_names
|
||||
AlternativeBookNamesDB.conn = sqlite3.connect(filepath)
|
||||
AlternativeBookNamesDB.conn.execute('CREATE TABLE '
|
||||
'alternative_book_names(id INTEGER NOT NULL, '
|
||||
'book_reference_id INTEGER, language_id INTEGER, name '
|
||||
'VARCHAR(50), PRIMARY KEY (id))')
|
||||
AlternativeBookNamesDB.conn = sqlite3.connect(file_path)
|
||||
AlternativeBookNamesDB.conn.execute(
|
||||
'CREATE TABLE alternative_book_names(id INTEGER NOT NULL, '
|
||||
'book_reference_id INTEGER, language_id INTEGER, name VARCHAR(50), PRIMARY KEY (id))')
|
||||
else:
|
||||
#use existing DB
|
||||
AlternativeBookNamesDB.conn = sqlite3.connect(filepath)
|
||||
AlternativeBookNamesDB.conn = sqlite3.connect(file_path)
|
||||
AlternativeBookNamesDB.cursor = AlternativeBookNamesDB.conn.cursor()
|
||||
return AlternativeBookNamesDB.cursor
|
||||
|
||||
@ -944,14 +880,9 @@ class AlternativeBookNamesDB(QtCore.QObject, Manager):
|
||||
"""
|
||||
Run an SQL query on the database, returning the results.
|
||||
|
||||
``query``
|
||||
The actual SQL query to run.
|
||||
|
||||
``parameters``
|
||||
Any variable parameters to add to the query
|
||||
|
||||
``commit``
|
||||
If a commit statement is necessary this should be True.
|
||||
:param query: The actual SQL query to run.
|
||||
:param parameters: Any variable parameters to add to the query
|
||||
:param commit: If a commit statement is necessary this should be True.
|
||||
"""
|
||||
cursor = AlternativeBookNamesDB.get_cursor()
|
||||
cursor.execute(query, parameters)
|
||||
@ -964,19 +895,16 @@ class AlternativeBookNamesDB(QtCore.QObject, Manager):
|
||||
"""
|
||||
Return a book_reference_id if the name matches.
|
||||
|
||||
``name``
|
||||
The name to search the id.
|
||||
|
||||
``language_id``
|
||||
The language_id for which language should be searched
|
||||
:param name: The name to search the id.
|
||||
:param language_id: The language_id for which language should be searched
|
||||
"""
|
||||
log.debug('AlternativeBookNamesDB.get_book_reference_id("%s", "%s")', name, language_id)
|
||||
if language_id:
|
||||
books = AlternativeBookNamesDB.run_sql('SELECT book_reference_id, '
|
||||
'name FROM alternative_book_names WHERE language_id = ?', (language_id, ))
|
||||
books = AlternativeBookNamesDB.run_sql(
|
||||
'SELECT book_reference_id, name FROM alternative_book_names WHERE language_id = ?', (language_id, ))
|
||||
else:
|
||||
books = AlternativeBookNamesDB.run_sql('SELECT book_reference_id, '
|
||||
'name FROM alternative_book_names')
|
||||
books = AlternativeBookNamesDB.run_sql(
|
||||
'SELECT book_reference_id, name FROM alternative_book_names')
|
||||
for book in books:
|
||||
if book[1].lower() == name.lower():
|
||||
return book[0]
|
||||
@ -987,19 +915,14 @@ class AlternativeBookNamesDB(QtCore.QObject, Manager):
|
||||
"""
|
||||
Add an alternative book name to the database.
|
||||
|
||||
``name``
|
||||
The name of the alternative book name.
|
||||
|
||||
``book_reference_id``
|
||||
The book_reference_id of the book.
|
||||
|
||||
``language_id``
|
||||
The language to which the alternative book name belong.
|
||||
:param name: The name of the alternative book name.
|
||||
:param book_reference_id: The book_reference_id of the book.
|
||||
:param language_id: The language to which the alternative book name belong.
|
||||
"""
|
||||
log.debug('AlternativeBookNamesDB.create_alternative_book_name("%s", '
|
||||
'"%s", "%s"', name, book_reference_id, language_id)
|
||||
return AlternativeBookNamesDB.run_sql('INSERT INTO '
|
||||
'alternative_book_names(book_reference_id, language_id, name) '
|
||||
log.debug('AlternativeBookNamesDB.create_alternative_book_name("%s", "%s", "%s")',
|
||||
name, book_reference_id, language_id)
|
||||
return AlternativeBookNamesDB.run_sql(
|
||||
'INSERT INTO alternative_book_names(book_reference_id, language_id, name) '
|
||||
'VALUES (?, ?, ?)', (book_reference_id, language_id, name), True)
|
||||
|
||||
|
||||
@ -1012,8 +935,7 @@ class OldBibleDB(QtCore.QObject, Manager):
|
||||
|
||||
def __init__(self, parent, **kwargs):
|
||||
"""
|
||||
The constructor loads up the database and creates and initialises the
|
||||
tables if the database doesn't exist.
|
||||
The constructor loads up the database and creates and initialises the tables if the database doesn't exist.
|
||||
|
||||
**Required keyword arguments:**
|
||||
|
||||
@ -1021,8 +943,7 @@ class OldBibleDB(QtCore.QObject, Manager):
|
||||
The path to the bible database file.
|
||||
|
||||
``name``
|
||||
The name of the database. This is also used as the file name for
|
||||
SQLite databases.
|
||||
The name of the database. This is also used as the file name for SQLite databases.
|
||||
"""
|
||||
log.info('OldBibleDB loaded')
|
||||
QtCore.QObject.__init__(self)
|
||||
@ -1040,8 +961,8 @@ class OldBibleDB(QtCore.QObject, Manager):
|
||||
Return the cursor object. Instantiate one if it doesn't exist yet.
|
||||
"""
|
||||
if self.cursor is None:
|
||||
filepath = os.path.join(self.path, self.file)
|
||||
self.connection = sqlite3.connect(filepath)
|
||||
file_path = os.path.join(self.path, self.file)
|
||||
self.connection = sqlite3.connect(file_path)
|
||||
self.cursor = self.connection.cursor()
|
||||
return self.cursor
|
||||
|
||||
@ -1049,11 +970,8 @@ class OldBibleDB(QtCore.QObject, Manager):
|
||||
"""
|
||||
Run an SQL query on the database, returning the results.
|
||||
|
||||
``query``
|
||||
The actual SQL query to run.
|
||||
|
||||
``parameters``
|
||||
Any variable parameters to add to the query.
|
||||
:param query: The actual SQL query to run.
|
||||
:param parameters: Any variable parameters to add to the query.
|
||||
"""
|
||||
cursor = self.get_cursor()
|
||||
cursor.execute(query, parameters)
|
||||
@ -1092,9 +1010,9 @@ class OldBibleDB(QtCore.QObject, Manager):
|
||||
"""
|
||||
if not isinstance(name, str):
|
||||
name = str(name)
|
||||
books = self.run_sql('SELECT id, testament_id, name, '
|
||||
'abbreviation FROM book WHERE LOWER(name) = ? OR '
|
||||
'LOWER(abbreviation) = ?', (name.lower(), name.lower()))
|
||||
books = self.run_sql(
|
||||
'SELECT id, testament_id, name, abbreviation FROM book WHERE LOWER(name) = ? OR '
|
||||
'LOWER(abbreviation) = ?', (name.lower(), name.lower()))
|
||||
if books:
|
||||
return {
|
||||
'id': books[0][0],
|
||||
@ -1122,8 +1040,8 @@ class OldBibleDB(QtCore.QObject, Manager):
|
||||
"""
|
||||
Returns the verses of the Bible.
|
||||
"""
|
||||
verses = self.run_sql('SELECT book_id, chapter, verse, text FROM '
|
||||
'verse WHERE book_id = ? ORDER BY id', (book_id, ))
|
||||
verses = self.run_sql(
|
||||
'SELECT book_id, chapter, verse, text FROM verse WHERE book_id = ? ORDER BY id', (book_id, ))
|
||||
if verses:
|
||||
return [{
|
||||
'book_id': int(verse[0]),
|
||||
|
@ -74,14 +74,9 @@ class BGExtract(object):
|
||||
"""
|
||||
Remove a particular element from the BeautifulSoup tree.
|
||||
|
||||
``parent``
|
||||
The element from which items need to be removed.
|
||||
|
||||
``tag``
|
||||
A string of the tab type, e.g. "div"
|
||||
|
||||
``class_``
|
||||
An HTML class attribute for further qualification.
|
||||
:param parent: The element from which items need to be removed.
|
||||
:param tag: A string of the tab type, e.g. "div"
|
||||
:param class_: An HTML class attribute for further qualification.
|
||||
"""
|
||||
if class_:
|
||||
all_tags = parent.find_all(tag, class_)
|
||||
@ -94,8 +89,7 @@ class BGExtract(object):
|
||||
"""
|
||||
Extract a verse (or part of a verse) from a tag.
|
||||
|
||||
``tag``
|
||||
The BeautifulSoup Tag element with the stuff we want.
|
||||
:param tag: The BeautifulSoup Tag element with the stuff we want.
|
||||
"""
|
||||
if isinstance(tag, NavigableString):
|
||||
return None, str(tag)
|
||||
@ -122,8 +116,7 @@ class BGExtract(object):
|
||||
"""
|
||||
Remove all the rubbish from the HTML page.
|
||||
|
||||
``tag``
|
||||
The base tag within which we want to remove stuff.
|
||||
:param tag: The base tag within which we want to remove stuff.
|
||||
"""
|
||||
self._remove_elements(tag, 'sup', 'crossreference')
|
||||
self._remove_elements(tag, 'sup', 'footnote')
|
||||
@ -137,8 +130,7 @@ class BGExtract(object):
|
||||
"""
|
||||
Extract all the verses from a pre-prepared list of HTML tags.
|
||||
|
||||
``tags``
|
||||
A list of BeautifulSoup Tag elements.
|
||||
:param tags: A list of BeautifulSoup Tag elements.
|
||||
"""
|
||||
verses = []
|
||||
tags = tags[::-1]
|
||||
@ -184,8 +176,7 @@ class BGExtract(object):
|
||||
Use the old style of parsing for those Bibles on BG who mysteriously have not been migrated to the new (still
|
||||
broken) HTML.
|
||||
|
||||
``div``
|
||||
The parent div.
|
||||
:param div: The parent div.
|
||||
"""
|
||||
verse_list = {}
|
||||
# Cater for inconsistent mark up in the first verse of a chapter.
|
||||
@ -225,14 +216,9 @@ class BGExtract(object):
|
||||
"""
|
||||
Access and decode Bibles via the BibleGateway website.
|
||||
|
||||
``version``
|
||||
The version of the Bible like 31 for New International version.
|
||||
|
||||
``book_name``
|
||||
Name of the Book.
|
||||
|
||||
``chapter``
|
||||
Chapter number.
|
||||
:param version: The version of the Bible like 31 for New International version.
|
||||
:param book_name: Name of the Book.
|
||||
:param chapter: Chapter number.
|
||||
"""
|
||||
log.debug('BGExtract.get_bible_chapter("%s", "%s", "%s")', version, book_name, chapter)
|
||||
url_book_name = urllib.parse.quote(book_name.encode("utf-8"))
|
||||
@ -259,10 +245,9 @@ class BGExtract(object):
|
||||
|
||||
def get_books_from_http(self, version):
|
||||
"""
|
||||
Load a list of all books a Bible contaions from BibleGateway website.
|
||||
Load a list of all books a Bible contains from BibleGateway website.
|
||||
|
||||
``version``
|
||||
The version of the Bible like NIV for New International Version
|
||||
:param version: The version of the Bible like NIV for New International Version
|
||||
"""
|
||||
log.debug('BGExtract.get_books_from_http("%s")', version)
|
||||
url_params = urllib.parse.urlencode({'action': 'getVersionInfo', 'vid': '%s' % version})
|
||||
@ -328,14 +313,9 @@ class BSExtract(object):
|
||||
"""
|
||||
Access and decode bibles via Bibleserver mobile website
|
||||
|
||||
``version``
|
||||
The version of the bible like NIV for New International Version
|
||||
|
||||
``book_name``
|
||||
Text name of bible book e.g. Genesis, 1. John, 1John or Offenbarung
|
||||
|
||||
``chapter``
|
||||
Chapter number
|
||||
:param version: The version of the bible like NIV for New International Version
|
||||
:param book_name: Text name of bible book e.g. Genesis, 1. John, 1John or Offenbarung
|
||||
:param chapter: Chapter number
|
||||
"""
|
||||
log.debug('BSExtract.get_bible_chapter("%s", "%s", "%s")', version, book_name, chapter)
|
||||
url_version = urllib.parse.quote(version.encode("utf-8"))
|
||||
@ -363,12 +343,11 @@ class BSExtract(object):
|
||||
"""
|
||||
Load a list of all books a Bible contains from Bibleserver mobile website.
|
||||
|
||||
``version``
|
||||
The version of the Bible like NIV for New International Version
|
||||
:param version: The version of the Bible like NIV for New International Version
|
||||
"""
|
||||
log.debug('BSExtract.get_books_from_http("%s")', version)
|
||||
url_version = urllib.parse.quote(version.encode("utf-8"))
|
||||
chapter_url = 'http://m.bibleserver.com/overlay/selectBook?translation=%s' % (url_version)
|
||||
chapter_url = 'http://m.bibleserver.com/overlay/selectBook?translation=%s' % url_version
|
||||
soup = get_soup_for_bible_ref(chapter_url)
|
||||
if not soup:
|
||||
return None
|
||||
@ -408,14 +387,9 @@ class CWExtract(object):
|
||||
"""
|
||||
Access and decode bibles via the Crosswalk website
|
||||
|
||||
``version``
|
||||
The version of the Bible like niv for New International Version
|
||||
|
||||
``book_name``
|
||||
Text name of in english e.g. 'gen' for Genesis
|
||||
|
||||
``chapter``
|
||||
Chapter number
|
||||
:param version: The version of the Bible like niv for New International Version
|
||||
:param book_name: Text name of in english e.g. 'gen' for Genesis
|
||||
:param chapter: Chapter number
|
||||
"""
|
||||
log.debug('CWExtract.get_bible_chapter("%s", "%s", "%s")', version, book_name, chapter)
|
||||
url_book_name = book_name.replace(' ', '-')
|
||||
@ -463,11 +437,10 @@ class CWExtract(object):
|
||||
"""
|
||||
Load a list of all books a Bible contain from the Crosswalk website.
|
||||
|
||||
``version``
|
||||
The version of the bible like NIV for New International Version
|
||||
:param version: The version of the bible like NIV for New International Version
|
||||
"""
|
||||
log.debug('CWExtract.get_books_from_http("%s")', version)
|
||||
chapter_url = 'http://www.biblestudytools.com/%s/' % (version)
|
||||
chapter_url = 'http://www.biblestudytools.com/%s/' % version
|
||||
soup = get_soup_for_bible_ref(chapter_url)
|
||||
if not soup:
|
||||
return None
|
||||
@ -533,7 +506,8 @@ class HTTPBible(BibleDB):
|
||||
failure.
|
||||
"""
|
||||
self.wizard.progress_bar.setMaximum(68)
|
||||
self.wizard.increment_progress_bar(translate('BiblesPlugin.HTTPBible', 'Registering Bible and loading books...'))
|
||||
self.wizard.increment_progress_bar(translate('BiblesPlugin.HTTPBible',
|
||||
'Registering Bible and loading books...'))
|
||||
self.save_meta('download_source', self.download_source)
|
||||
self.save_meta('download_name', self.download_name)
|
||||
if self.proxy_server:
|
||||
@ -552,8 +526,8 @@ class HTTPBible(BibleDB):
|
||||
handler = BSExtract(self.proxy_server)
|
||||
books = handler.get_books_from_http(self.download_name)
|
||||
if not books:
|
||||
log.error('Importing books from %s - download name: "%s" '\
|
||||
'failed' % (self.download_source, self.download_name))
|
||||
log.error('Importing books from %s - download name: "%s" failed' %
|
||||
(self.download_source, self.download_name))
|
||||
return False
|
||||
self.wizard.progress_bar.setMaximum(len(books) + 2)
|
||||
self.wizard.increment_progress_bar(translate( 'BiblesPlugin.HTTPBible', 'Registering Language...'))
|
||||
@ -573,12 +547,12 @@ class HTTPBible(BibleDB):
|
||||
'BiblesPlugin.HTTPBible', 'Importing %s...', 'Importing <book name>...') % book)
|
||||
book_ref_id = self.get_book_ref_id_by_name(book, len(books), language_id)
|
||||
if not book_ref_id:
|
||||
log.error('Importing books from %s - download name: "%s" '\
|
||||
'failed' % (self.download_source, self.download_name))
|
||||
log.error('Importing books from %s - download name: "%s" failed' %
|
||||
(self.download_source, self.download_name))
|
||||
return False
|
||||
book_details = BiblesResourcesDB.get_book_by_id(book_ref_id)
|
||||
log.debug('Book details: Name:%s; id:%s; testament_id:%s',
|
||||
book, book_ref_id, book_details['testament_id'])
|
||||
book, book_ref_id, book_details['testament_id'])
|
||||
self.create_book(book, book_ref_id, book_details['testament_id'])
|
||||
if self.stop_import_flag:
|
||||
return False
|
||||
@ -612,7 +586,7 @@ class HTTPBible(BibleDB):
|
||||
critical_error_message_box(
|
||||
translate('BiblesPlugin', 'No Book Found'),
|
||||
translate('BiblesPlugin', 'No matching book could be found in this Bible. Check that you have '
|
||||
'spelled the name of the book correctly.'))
|
||||
'spelled the name of the book correctly.'))
|
||||
return []
|
||||
book = db_book.name
|
||||
if BibleDB.get_verse_count(self, book_id, reference[1]) == 0:
|
||||
@ -658,8 +632,7 @@ class HTTPBible(BibleDB):
|
||||
"""
|
||||
Return the number of chapters in a particular book.
|
||||
|
||||
``book``
|
||||
The book object to get the chapter count for.
|
||||
:param book: The book object to get the chapter count for.
|
||||
"""
|
||||
log.debug('HTTPBible.get_chapter_count("%s")', book.name)
|
||||
return BiblesResourcesDB.get_chapter_count(book.book_reference_id)
|
||||
@ -668,11 +641,8 @@ class HTTPBible(BibleDB):
|
||||
"""
|
||||
Return the number of verses for the specified chapter and book.
|
||||
|
||||
``book``
|
||||
The name of the book.
|
||||
|
||||
``chapter``
|
||||
The chapter whose verses are being counted.
|
||||
:param book_id: The name of the book.
|
||||
:param chapter: The chapter whose verses are being counted.
|
||||
"""
|
||||
log.debug('HTTPBible.get_verse_count("%s", %s)', book_id, chapter)
|
||||
return BiblesResourcesDB.get_verse_count(book_id, chapter)
|
||||
@ -696,18 +666,11 @@ def get_soup_for_bible_ref(reference_url, header=None, pre_parse_regex=None, pre
|
||||
"""
|
||||
Gets a webpage and returns a parsed and optionally cleaned soup or None.
|
||||
|
||||
``reference_url``
|
||||
The URL to obtain the soup from.
|
||||
|
||||
``header``
|
||||
An optional HTTP header to pass to the bible web server.
|
||||
|
||||
``pre_parse_regex``
|
||||
A regular expression to run on the webpage. Allows manipulation of the webpage before passing to BeautifulSoup
|
||||
for parsing.
|
||||
|
||||
``pre_parse_substitute``
|
||||
The text to replace any matches to the regular expression with.
|
||||
:param reference_url: The URL to obtain the soup from.
|
||||
:param header: An optional HTTP header to pass to the bible web server.
|
||||
:param pre_parse_regex: A regular expression to run on the webpage. Allows manipulation of the webpage before
|
||||
passing to BeautifulSoup for parsing.
|
||||
:param pre_parse_substitute: The text to replace any matches to the regular expression with.
|
||||
"""
|
||||
if not reference_url:
|
||||
return None
|
||||
@ -742,9 +705,10 @@ def send_error_message(error_type):
|
||||
critical_error_message_box(
|
||||
translate('BiblesPlugin.HTTPBible', 'Download Error'),
|
||||
translate('BiblesPlugin.HTTPBible', 'There was a problem downloading your verse selection. Please check '
|
||||
'your Internet connection, and if this error continues to occur please consider reporting a bug.'))
|
||||
'your Internet connection, and if this error continues to occur please consider reporting a bug'
|
||||
'.'))
|
||||
elif error_type == 'parse':
|
||||
critical_error_message_box(
|
||||
translate('BiblesPlugin.HTTPBible', 'Parse Error'),
|
||||
translate('BiblesPlugin.HTTPBible', 'There was a problem extracting your verse selection. If this error '
|
||||
'continues to occur please consider reporting a bug.'))
|
||||
'continues to occur please consider reporting a bug.'))
|
||||
|
@ -40,7 +40,6 @@ from .opensong import OpenSongBible
|
||||
from .osis import OSISBible
|
||||
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -57,10 +56,9 @@ class BibleFormat(object):
|
||||
@staticmethod
|
||||
def get_class(format):
|
||||
"""
|
||||
Return the appropriate imeplementation class.
|
||||
Return the appropriate implementation class.
|
||||
|
||||
``format``
|
||||
The Bible format.
|
||||
:param format: The Bible format.
|
||||
"""
|
||||
if format == BibleFormat.OSIS:
|
||||
return OSISBible
|
||||
@ -94,9 +92,8 @@ class BibleManager(object):
|
||||
|
||||
def __init__(self, parent):
|
||||
"""
|
||||
Finds all the bibles defined for the system and creates an interface
|
||||
object for each bible containing connection information. Throws
|
||||
Exception if no Bibles are found.
|
||||
Finds all the bibles defined for the system and creates an interface object for each bible containing
|
||||
connection information. Throws Exception if no Bibles are found.
|
||||
|
||||
Init confirms the bible exists and stores the database path.
|
||||
"""
|
||||
@ -114,9 +111,8 @@ class BibleManager(object):
|
||||
|
||||
def reload_bibles(self):
|
||||
"""
|
||||
Reloads the Bibles from the available Bible databases on disk. If a web
|
||||
Bible is encountered, an instance of HTTPBible is loaded instead of the
|
||||
BibleDB class.
|
||||
Reloads the Bibles from the available Bible databases on disk. If a web Bible is encountered, an instance
|
||||
of HTTPBible is loaded instead of the BibleDB class.
|
||||
"""
|
||||
log.debug('Reload bibles')
|
||||
files = AppLocation.get_files(self.settings_section, self.suffix)
|
||||
@ -146,7 +142,7 @@ class BibleManager(object):
|
||||
download_name = self.db_cache[name].get_object(BibleMeta, 'download_name').value
|
||||
meta_proxy = self.db_cache[name].get_object(BibleMeta, 'proxy_server')
|
||||
web_bible = HTTPBible(self.parent, path=self.path, file=filename, download_source=source.value,
|
||||
download_name=download_name)
|
||||
download_name=download_name)
|
||||
if meta_proxy:
|
||||
web_bible.proxy_server = meta_proxy.value
|
||||
self.db_cache[name] = web_bible
|
||||
@ -156,8 +152,7 @@ class BibleManager(object):
|
||||
"""
|
||||
Sets the reference to the dialog with the progress bar on it.
|
||||
|
||||
``dialog``
|
||||
The reference to the import wizard.
|
||||
:param wizard: The reference to the import wizard.
|
||||
"""
|
||||
self.import_wizard = wizard
|
||||
|
||||
@ -165,11 +160,8 @@ class BibleManager(object):
|
||||
"""
|
||||
Register a bible in the bible cache, and then import the verses.
|
||||
|
||||
``type``
|
||||
What type of Bible, one of the ``BibleFormat`` values.
|
||||
|
||||
``**kwargs``
|
||||
Keyword arguments to send to the actual importer class.
|
||||
:param type: What type of Bible, one of the ``BibleFormat`` values.
|
||||
:param kwargs: Keyword arguments to send to the actual importer class.
|
||||
"""
|
||||
class_ = BibleFormat.get_class(type)
|
||||
kwargs['path'] = self.path
|
||||
@ -182,8 +174,7 @@ class BibleManager(object):
|
||||
"""
|
||||
Delete a bible completely.
|
||||
|
||||
``name``
|
||||
The name of the bible.
|
||||
:param name: The name of the bible.
|
||||
"""
|
||||
log.debug('BibleManager.delete_bible("%s")', name)
|
||||
bible = self.db_cache[name]
|
||||
@ -202,8 +193,7 @@ class BibleManager(object):
|
||||
"""
|
||||
Returns a list of Bible books, and the number of chapters in that book.
|
||||
|
||||
``bible``
|
||||
Unicode. The Bible to get the list of books from.
|
||||
:param bible: Unicode. The Bible to get the list of books from.
|
||||
"""
|
||||
log.debug('BibleManager.get_books("%s")', bible)
|
||||
return [
|
||||
@ -219,11 +209,8 @@ class BibleManager(object):
|
||||
"""
|
||||
Returns a book object by given id.
|
||||
|
||||
``bible``
|
||||
Unicode. The Bible to get the list of books from.
|
||||
|
||||
``id``
|
||||
Unicode. The book_reference_id to get the book for.
|
||||
:param bible: Unicode. The Bible to get the list of books from.
|
||||
:param id: Unicode. The book_reference_id to get the book for.
|
||||
"""
|
||||
log.debug('BibleManager.get_book_by_id("%s", "%s")', bible, id)
|
||||
return self.db_cache[bible].get_book_by_book_ref_id(id)
|
||||
@ -232,22 +219,17 @@ class BibleManager(object):
|
||||
"""
|
||||
Returns the number of Chapters for a given book.
|
||||
|
||||
``bible``
|
||||
Unicode. The Bible to get the list of books from.
|
||||
|
||||
``book``
|
||||
The book object to get the chapter count for.
|
||||
:param bible: Unicode. The Bible to get the list of books from.
|
||||
:param book: The book object to get the chapter count for.
|
||||
"""
|
||||
log.debug('BibleManager.get_book_chapter_count ("%s", "%s")', bible, book.name)
|
||||
return self.db_cache[bible].get_chapter_count(book)
|
||||
|
||||
def get_verse_count(self, bible, book, chapter):
|
||||
"""
|
||||
Returns all the number of verses for a given
|
||||
book and chapterMaxBibleBookVerses.
|
||||
Returns all the number of verses for a given book and chapterMaxBibleBookVerses.
|
||||
"""
|
||||
log.debug('BibleManager.get_verse_count("%s", "%s", %s)',
|
||||
bible, book, chapter)
|
||||
log.debug('BibleManager.get_verse_count("%s", "%s", %s)', bible, book, chapter)
|
||||
language_selection = self.get_language_selection(bible)
|
||||
book_ref_id = self.db_cache[bible].get_book_ref_id_by_localised_name(book, language_selection)
|
||||
return self.db_cache[bible].get_verse_count(book_ref_id, chapter)
|
||||
@ -260,16 +242,14 @@ class BibleManager(object):
|
||||
log.debug('BibleManager.get_verse_count_by_book_ref_id("%s", "%s", "%s")', bible, book_ref_id, chapter)
|
||||
return self.db_cache[bible].get_verse_count(book_ref_id, chapter)
|
||||
|
||||
def get_verses(self, bible, versetext, book_ref_id=False, show_error=True):
|
||||
def get_verses(self, bible, verse_text, book_ref_id=False, show_error=True):
|
||||
"""
|
||||
Parses a scripture reference, fetches the verses from the Bible
|
||||
specified, and returns a list of ``Verse`` objects.
|
||||
|
||||
``bible``
|
||||
Unicode. The Bible to use.
|
||||
|
||||
``versetext``
|
||||
Unicode. The scripture reference. Valid scripture references are:
|
||||
:param bible: Unicode. The Bible to use.
|
||||
:param verse_text:
|
||||
Unicode. The scripture reference. Valid scripture references are:
|
||||
|
||||
- Genesis 1
|
||||
- Genesis 1-2
|
||||
@ -279,55 +259,53 @@ class BibleManager(object):
|
||||
- Genesis 1:1-2:10
|
||||
- Genesis 1:1-10,2:1-10
|
||||
|
||||
``book_ref_id``
|
||||
Unicode. The book referece id from the book in versetext.
|
||||
:param book_ref_id: Unicode. The book reference id from the book in verse_text.
|
||||
For second bible this is necessary.
|
||||
:param show_error:
|
||||
"""
|
||||
log.debug('BibleManager.get_verses("%s", "%s")', bible, versetext)
|
||||
log.debug('BibleManager.get_verses("%s", "%s")', bible, verse_text)
|
||||
if not bible:
|
||||
if show_error:
|
||||
self.main_window.information_message(
|
||||
translate('BiblesPlugin.BibleManager', 'No Bibles Available'),
|
||||
translate('BiblesPlugin.BibleManager',
|
||||
'There are no Bibles currently installed. Please use the '
|
||||
'Import Wizard to install one or more Bibles.')
|
||||
)
|
||||
translate('BiblesPlugin.BibleManager', 'There are no Bibles currently installed. Please use the '
|
||||
'Import Wizard to install one or more Bibles.')
|
||||
)
|
||||
return None
|
||||
language_selection = self.get_language_selection(bible)
|
||||
reflist = parse_reference(versetext, self.db_cache[bible],
|
||||
language_selection, book_ref_id)
|
||||
if reflist:
|
||||
return self.db_cache[bible].get_verses(reflist, show_error)
|
||||
ref_list = parse_reference(verse_text, self.db_cache[bible], language_selection, book_ref_id)
|
||||
if ref_list:
|
||||
return self.db_cache[bible].get_verses(ref_list, show_error)
|
||||
else:
|
||||
if show_error:
|
||||
reference_seperators = {
|
||||
reference_separators = {
|
||||
'verse': get_reference_separator('sep_v_display'),
|
||||
'range': get_reference_separator('sep_r_display'),
|
||||
'list': get_reference_separator('sep_l_display')}
|
||||
self.main_window.information_message(
|
||||
translate('BiblesPlugin.BibleManager', 'Scripture Reference Error'),
|
||||
translate('BiblesPlugin.BibleManager', 'Your scripture reference is either not supported by '
|
||||
'OpenLP or is invalid. Please make sure your reference '
|
||||
'conforms to one of the following patterns or consult the manual:\n\n'
|
||||
'Book Chapter\n'
|
||||
'Book Chapter%(range)sChapter\n'
|
||||
'Book Chapter%(verse)sVerse%(range)sVerse\n'
|
||||
'Book Chapter%(verse)sVerse%(range)sVerse%(list)sVerse'
|
||||
'%(range)sVerse\n'
|
||||
'Book Chapter%(verse)sVerse%(range)sVerse%(list)sChapter'
|
||||
'%(verse)sVerse%(range)sVerse\n'
|
||||
'Book Chapter%(verse)sVerse%(range)sChapter%(verse)sVerse',
|
||||
'Please pay attention to the appended "s" of the wildcards '
|
||||
'and refrain from translating the words inside the names in the brackets.') % reference_seperators
|
||||
)
|
||||
'OpenLP or is invalid. Please make sure your reference '
|
||||
'conforms to one of the following patterns or consult the manual:\n\n'
|
||||
'Book Chapter\n'
|
||||
'Book Chapter%(range)sChapter\n'
|
||||
'Book Chapter%(verse)sVerse%(range)sVerse\n'
|
||||
'Book Chapter%(verse)sVerse%(range)sVerse%(list)sVerse'
|
||||
'%(range)sVerse\n'
|
||||
'Book Chapter%(verse)sVerse%(range)sVerse%(list)sChapter'
|
||||
'%(verse)sVerse%(range)sVerse\n'
|
||||
'Book Chapter%(verse)sVerse%(range)sChapter%(verse)sVerse',
|
||||
'Please pay attention to the appended "s" of the wildcards '
|
||||
'and refrain from translating the words inside the names in the brackets.')
|
||||
% reference_separators
|
||||
)
|
||||
return None
|
||||
|
||||
def get_language_selection(self, bible):
|
||||
"""
|
||||
Returns the language selection of a bible.
|
||||
|
||||
``bible``
|
||||
Unicode. The Bible to get the language selection from.
|
||||
:param bible: Unicode. The Bible to get the language selection from.
|
||||
"""
|
||||
log.debug('BibleManager.get_language_selection("%s")', bible)
|
||||
language_selection = self.get_meta_data(bible, 'book_name_language')
|
||||
@ -347,34 +325,29 @@ class BibleManager(object):
|
||||
"""
|
||||
Does a verse search for the given bible and text.
|
||||
|
||||
``bible``
|
||||
The bible to search in (unicode).
|
||||
|
||||
``second_bible``
|
||||
The second bible (unicode). We do not search in this bible.
|
||||
|
||||
``text``
|
||||
The text to search for (unicode).
|
||||
:param bible: The bible to search in (unicode).
|
||||
:param second_bible: The second bible (unicode). We do not search in this bible.
|
||||
:param text: The text to search for (unicode).
|
||||
"""
|
||||
log.debug('BibleManager.verse_search("%s", "%s")', bible, text)
|
||||
if not bible:
|
||||
self.main_window.information_message(
|
||||
translate('BiblesPlugin.BibleManager', 'No Bibles Available'),
|
||||
translate('BiblesPlugin.BibleManager',
|
||||
'There are no Bibles currently installed. Please use the Import Wizard to install one or more'
|
||||
' Bibles.')
|
||||
)
|
||||
'There are no Bibles currently installed. Please use the Import Wizard to install one or '
|
||||
'more Bibles.')
|
||||
)
|
||||
return None
|
||||
# Check if the bible or second_bible is a web bible.
|
||||
webbible = self.db_cache[bible].get_object(BibleMeta, 'download_source')
|
||||
second_webbible = ''
|
||||
web_bible = self.db_cache[bible].get_object(BibleMeta, 'download_source')
|
||||
second_web_bible = ''
|
||||
if second_bible:
|
||||
second_webbible = self.db_cache[second_bible].get_object(BibleMeta, 'download_source')
|
||||
if webbible or second_webbible:
|
||||
second_web_bible = self.db_cache[second_bible].get_object(BibleMeta, 'download_source')
|
||||
if web_bible or second_web_bible:
|
||||
self.main_window.information_message(
|
||||
translate('BiblesPlugin.BibleManager', 'Web Bible cannot be used'),
|
||||
translate('BiblesPlugin.BibleManager', 'Text Search is not available with Web Bibles.')
|
||||
)
|
||||
)
|
||||
return None
|
||||
if text:
|
||||
return self.db_cache[bible].verse_search(text)
|
||||
@ -382,23 +355,20 @@ class BibleManager(object):
|
||||
self.main_window.information_message(
|
||||
translate('BiblesPlugin.BibleManager', 'Scripture Reference Error'),
|
||||
translate('BiblesPlugin.BibleManager', 'You did not enter a search keyword.\nYou can separate '
|
||||
'different keywords by a space to search for all of your keywords and you can separate '
|
||||
'them by a comma to search for one of them.')
|
||||
)
|
||||
'different keywords by a space to search for all of your keywords and you can separate '
|
||||
'them by a comma to search for one of them.')
|
||||
)
|
||||
return None
|
||||
|
||||
def save_meta_data(self, bible, version, copyright, permissions,
|
||||
book_name_language=None):
|
||||
def save_meta_data(self, bible, version, copyright, permissions, book_name_language=None):
|
||||
"""
|
||||
Saves the bibles meta data.
|
||||
"""
|
||||
log.debug('save_meta data %s, %s, %s, %s',
|
||||
bible, version, copyright, permissions)
|
||||
log.debug('save_meta data %s, %s, %s, %s', bible, version, copyright, permissions)
|
||||
self.db_cache[bible].save_meta('name', version)
|
||||
self.db_cache[bible].save_meta('copyright', copyright)
|
||||
self.db_cache[bible].save_meta('permissions', permissions)
|
||||
self.db_cache[bible].save_meta('book_name_language',
|
||||
book_name_language)
|
||||
self.db_cache[bible].save_meta('book_name_language', book_name_language)
|
||||
|
||||
def get_meta_data(self, bible, key):
|
||||
"""
|
||||
|
@ -86,19 +86,19 @@ class BibleMediaItem(MediaManagerItem):
|
||||
if not bitem.flags() & QtCore.Qt.ItemIsSelectable:
|
||||
# The item is the "No Search Results" item.
|
||||
self.list_view.clear()
|
||||
self.displayResults(bible, second_bible)
|
||||
self.display_results(bible, second_bible)
|
||||
return
|
||||
else:
|
||||
item_second_bible = self._decode_qt_object(bitem, 'second_bible')
|
||||
if item_second_bible and second_bible or not item_second_bible and not second_bible:
|
||||
self.displayResults(bible, second_bible)
|
||||
self.display_results(bible, second_bible)
|
||||
elif critical_error_message_box(
|
||||
message=translate('BiblesPlugin.MediaItem',
|
||||
'You cannot combine single and dual Bible verse search results. '
|
||||
'Do you want to delete your search results and start a new search?'),
|
||||
parent=self, question=True) == QtGui.QMessageBox.Yes:
|
||||
'You cannot combine single and dual Bible verse search results. '
|
||||
'Do you want to delete your search results and start a new search?'),
|
||||
parent=self, question=True) == QtGui.QMessageBox.Yes:
|
||||
self.list_view.clear()
|
||||
self.displayResults(bible, second_bible)
|
||||
self.display_results(bible, second_bible)
|
||||
|
||||
def _decode_qt_object(self, bitem, key):
|
||||
reference = bitem.data(QtCore.Qt.UserRole)
|
||||
@ -116,8 +116,8 @@ class BibleMediaItem(MediaManagerItem):
|
||||
self.has_delete_icon = True
|
||||
self.add_to_service_item = False
|
||||
|
||||
def addSearchTab(self, prefix, name):
|
||||
self.searchTabBar.addTab(name)
|
||||
def add_search_tab(self, prefix, name):
|
||||
self.search_tab_bar.addTab(name)
|
||||
tab = QtGui.QWidget()
|
||||
tab.setObjectName(prefix + 'Tab')
|
||||
tab.setSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Minimum)
|
||||
@ -126,15 +126,12 @@ class BibleMediaItem(MediaManagerItem):
|
||||
setattr(self, prefix + 'Tab', tab)
|
||||
setattr(self, prefix + 'Layout', layout)
|
||||
|
||||
def addSearchFields(self, prefix, name):
|
||||
def add_search_fields(self, prefix, name):
|
||||
"""
|
||||
Creates and adds generic search tab.
|
||||
|
||||
``prefix``
|
||||
The prefix of the tab, this is either ``quick`` or ``advanced``.
|
||||
|
||||
``name``
|
||||
The translated string to display.
|
||||
:param prefix: The prefix of the tab, this is either ``quick`` or ``advanced``.
|
||||
:param name: The translated string to display.
|
||||
"""
|
||||
if prefix == 'quick':
|
||||
idx = 2
|
||||
@ -142,124 +139,126 @@ class BibleMediaItem(MediaManagerItem):
|
||||
idx = 5
|
||||
tab = getattr(self, prefix + 'Tab')
|
||||
layout = getattr(self, prefix + 'Layout')
|
||||
versionLabel = QtGui.QLabel(tab)
|
||||
versionLabel.setObjectName(prefix + 'VersionLabel')
|
||||
layout.addWidget(versionLabel, idx, 0, QtCore.Qt.AlignRight)
|
||||
versionComboBox = create_horizontal_adjusting_combo_box(tab, prefix + 'VersionComboBox')
|
||||
versionLabel.setBuddy(versionComboBox)
|
||||
layout.addWidget(versionComboBox, idx, 1, 1, 2)
|
||||
secondLabel = QtGui.QLabel(tab)
|
||||
secondLabel.setObjectName(prefix + 'SecondLabel')
|
||||
layout.addWidget(secondLabel, idx + 1, 0, QtCore.Qt.AlignRight)
|
||||
secondComboBox = create_horizontal_adjusting_combo_box(tab, prefix + 'SecondComboBox')
|
||||
versionLabel.setBuddy(secondComboBox)
|
||||
layout.addWidget(secondComboBox, idx + 1, 1, 1, 2)
|
||||
styleLabel = QtGui.QLabel(tab)
|
||||
styleLabel.setObjectName(prefix + 'StyleLabel')
|
||||
layout.addWidget(styleLabel, idx + 2, 0, QtCore.Qt.AlignRight)
|
||||
styleComboBox = create_horizontal_adjusting_combo_box(tab, prefix + 'StyleComboBox')
|
||||
styleComboBox.addItems(['', '', ''])
|
||||
layout.addWidget(styleComboBox, idx + 2, 1, 1, 2)
|
||||
version_label = QtGui.QLabel(tab)
|
||||
version_label.setObjectName(prefix + 'VersionLabel')
|
||||
layout.addWidget(version_label, idx, 0, QtCore.Qt.AlignRight)
|
||||
version_combo_box = create_horizontal_adjusting_combo_box(tab, prefix + 'VersionComboBox')
|
||||
version_label.setBuddy(version_combo_box)
|
||||
layout.addWidget(version_combo_box, idx, 1, 1, 2)
|
||||
second_label = QtGui.QLabel(tab)
|
||||
second_label.setObjectName(prefix + 'SecondLabel')
|
||||
layout.addWidget(second_label, idx + 1, 0, QtCore.Qt.AlignRight)
|
||||
second_combo_box = create_horizontal_adjusting_combo_box(tab, prefix + 'SecondComboBox')
|
||||
version_label.setBuddy(second_combo_box)
|
||||
layout.addWidget(second_combo_box, idx + 1, 1, 1, 2)
|
||||
style_label = QtGui.QLabel(tab)
|
||||
style_label.setObjectName(prefix + 'StyleLabel')
|
||||
layout.addWidget(style_label, idx + 2, 0, QtCore.Qt.AlignRight)
|
||||
style_combo_box = create_horizontal_adjusting_combo_box(tab, prefix + 'StyleComboBox')
|
||||
style_combo_box.addItems(['', '', ''])
|
||||
layout.addWidget(style_combo_box, idx + 2, 1, 1, 2)
|
||||
search_button_layout = QtGui.QHBoxLayout()
|
||||
search_button_layout.setObjectName(prefix + 'search_button_layout')
|
||||
search_button_layout.addStretch()
|
||||
lockButton = QtGui.QToolButton(tab)
|
||||
lockButton.setIcon(self.unlock_icon)
|
||||
lockButton.setCheckable(True)
|
||||
lockButton.setObjectName(prefix + 'LockButton')
|
||||
search_button_layout.addWidget(lockButton)
|
||||
searchButton = QtGui.QPushButton(tab)
|
||||
searchButton.setObjectName(prefix + 'SearchButton')
|
||||
search_button_layout.addWidget(searchButton)
|
||||
lock_button = QtGui.QToolButton(tab)
|
||||
lock_button.setIcon(self.unlock_icon)
|
||||
lock_button.setCheckable(True)
|
||||
lock_button.setObjectName(prefix + 'LockButton')
|
||||
search_button_layout.addWidget(lock_button)
|
||||
search_button = QtGui.QPushButton(tab)
|
||||
search_button.setObjectName(prefix + 'SearchButton')
|
||||
search_button_layout.addWidget(search_button)
|
||||
layout.addLayout(search_button_layout, idx + 3, 1, 1, 2)
|
||||
self.page_layout.addWidget(tab)
|
||||
tab.setVisible(False)
|
||||
lockButton.toggled.connect(self.onLockButtonToggled)
|
||||
setattr(self, prefix + 'VersionLabel', versionLabel)
|
||||
setattr(self, prefix + 'VersionComboBox', versionComboBox)
|
||||
setattr(self, prefix + 'SecondLabel', secondLabel)
|
||||
setattr(self, prefix + 'SecondComboBox', secondComboBox)
|
||||
setattr(self, prefix + 'StyleLabel', styleLabel)
|
||||
setattr(self, prefix + 'StyleComboBox', styleComboBox)
|
||||
setattr(self, prefix + 'LockButton', lockButton)
|
||||
lock_button.toggled.connect(self.on_lock_button_toggled)
|
||||
setattr(self, prefix + 'VersionLabel', version_label)
|
||||
setattr(self, prefix + 'VersionComboBox', version_combo_box)
|
||||
setattr(self, prefix + 'SecondLabel', second_label)
|
||||
setattr(self, prefix + 'SecondComboBox', second_combo_box)
|
||||
setattr(self, prefix + 'StyleLabel', style_label)
|
||||
setattr(self, prefix + 'StyleComboBox', style_combo_box)
|
||||
setattr(self, prefix + 'LockButton', lock_button)
|
||||
setattr(self, prefix + 'SearchButtonLayout', search_button_layout)
|
||||
setattr(self, prefix + 'SearchButton', searchButton)
|
||||
setattr(self, prefix + 'SearchButton', search_button)
|
||||
|
||||
def add_end_header_bar(self):
|
||||
self.searchTabBar = QtGui.QTabBar(self)
|
||||
self.searchTabBar.setExpanding(False)
|
||||
self.searchTabBar.setObjectName('searchTabBar')
|
||||
self.page_layout.addWidget(self.searchTabBar)
|
||||
self.search_tab_bar = QtGui.QTabBar(self)
|
||||
self.search_tab_bar.setExpanding(False)
|
||||
self.search_tab_bar.setObjectName('search_tab_bar')
|
||||
self.page_layout.addWidget(self.search_tab_bar)
|
||||
# Add the Quick Search tab.
|
||||
self.addSearchTab('quick', translate('BiblesPlugin.MediaItem', 'Quick'))
|
||||
self.quickSearchLabel = QtGui.QLabel(self.quickTab)
|
||||
self.quickSearchLabel.setObjectName('quickSearchLabel')
|
||||
self.quickLayout.addWidget(self.quickSearchLabel, 0, 0, QtCore.Qt.AlignRight)
|
||||
self.quickSearchEdit = SearchEdit(self.quickTab)
|
||||
self.quickSearchEdit.setSizePolicy(QtGui.QSizePolicy.Ignored, QtGui.QSizePolicy.Fixed)
|
||||
self.quickSearchEdit.setObjectName('quickSearchEdit')
|
||||
self.quickSearchLabel.setBuddy(self.quickSearchEdit)
|
||||
self.quickLayout.addWidget(self.quickSearchEdit, 0, 1, 1, 2)
|
||||
self.addSearchFields('quick', translate('BiblesPlugin.MediaItem', 'Quick'))
|
||||
self.add_search_tab('quick', translate('BiblesPlugin.MediaItem', 'Quick'))
|
||||
self.quick_search_label = QtGui.QLabel(self.quickTab)
|
||||
self.quick_search_label.setObjectName('quick_search_label')
|
||||
self.quickLayout.addWidget(self.quick_search_label, 0, 0, QtCore.Qt.AlignRight)
|
||||
self.quick_search_edit = SearchEdit(self.quickTab)
|
||||
self.quick_search_edit.setSizePolicy(QtGui.QSizePolicy.Ignored, QtGui.QSizePolicy.Fixed)
|
||||
self.quick_search_edit.setObjectName('quick_search_edit')
|
||||
self.quick_search_label.setBuddy(self.quick_search_edit)
|
||||
self.quickLayout.addWidget(self.quick_search_edit, 0, 1, 1, 2)
|
||||
self.add_search_fields('quick', translate('BiblesPlugin.MediaItem', 'Quick'))
|
||||
self.quickTab.setVisible(True)
|
||||
# Add the Advanced Search tab.
|
||||
self.addSearchTab('advanced', UiStrings().Advanced)
|
||||
self.advancedBookLabel = QtGui.QLabel(self.advancedTab)
|
||||
self.advancedBookLabel.setObjectName('advancedBookLabel')
|
||||
self.advancedLayout.addWidget(self.advancedBookLabel, 0, 0, QtCore.Qt.AlignRight)
|
||||
self.advancedBookComboBox = create_horizontal_adjusting_combo_box(self.advancedTab, 'advancedBookComboBox')
|
||||
self.advancedBookLabel.setBuddy(self.advancedBookComboBox)
|
||||
self.advancedLayout.addWidget(self.advancedBookComboBox, 0, 1, 1, 2)
|
||||
self.advancedChapterLabel = QtGui.QLabel(self.advancedTab)
|
||||
self.advancedChapterLabel.setObjectName('advancedChapterLabel')
|
||||
self.advancedLayout.addWidget(self.advancedChapterLabel, 1, 1, 1, 2)
|
||||
self.advancedVerseLabel = QtGui.QLabel(self.advancedTab)
|
||||
self.advancedVerseLabel.setObjectName('advancedVerseLabel')
|
||||
self.advancedLayout.addWidget(self.advancedVerseLabel, 1, 2)
|
||||
self.advancedFromLabel = QtGui.QLabel(self.advancedTab)
|
||||
self.advancedFromLabel.setObjectName('advancedFromLabel')
|
||||
self.advancedLayout.addWidget(self.advancedFromLabel, 3, 0, QtCore.Qt.AlignRight)
|
||||
self.advancedFromChapter = QtGui.QComboBox(self.advancedTab)
|
||||
self.advancedFromChapter.setObjectName('advancedFromChapter')
|
||||
self.advancedLayout.addWidget(self.advancedFromChapter, 3, 1)
|
||||
self.advancedFromVerse = QtGui.QComboBox(self.advancedTab)
|
||||
self.advancedFromVerse.setObjectName('advancedFromVerse')
|
||||
self.advancedLayout.addWidget(self.advancedFromVerse, 3, 2)
|
||||
self.advancedToLabel = QtGui.QLabel(self.advancedTab)
|
||||
self.advancedToLabel.setObjectName('advancedToLabel')
|
||||
self.advancedLayout.addWidget(self.advancedToLabel, 4, 0, QtCore.Qt.AlignRight)
|
||||
self.advancedToChapter = QtGui.QComboBox(self.advancedTab)
|
||||
self.advancedToChapter.setObjectName('advancedToChapter')
|
||||
self.advancedLayout.addWidget(self.advancedToChapter, 4, 1)
|
||||
self.advancedToVerse = QtGui.QComboBox(self.advancedTab)
|
||||
self.advancedToVerse.setObjectName('advancedToVerse')
|
||||
self.advancedLayout.addWidget(self.advancedToVerse, 4, 2)
|
||||
self.addSearchFields('advanced', UiStrings().Advanced)
|
||||
self.add_search_tab('advanced', UiStrings().Advanced)
|
||||
self.advanced_book_label = QtGui.QLabel(self.advancedTab)
|
||||
self.advanced_book_label.setObjectName('advanced_book_label')
|
||||
self.advancedLayout.addWidget(self.advanced_book_label, 0, 0, QtCore.Qt.AlignRight)
|
||||
self.advanced_book_combo_box = create_horizontal_adjusting_combo_box(self.advancedTab,
|
||||
'advanced_book_combo_box')
|
||||
self.advanced_book_label.setBuddy(self.advanced_book_combo_box)
|
||||
self.advancedLayout.addWidget(self.advanced_book_combo_box, 0, 1, 1, 2)
|
||||
self.advanced_chapter_label = QtGui.QLabel(self.advancedTab)
|
||||
self.advanced_chapter_label.setObjectName('advanced_chapter_label')
|
||||
self.advancedLayout.addWidget(self.advanced_chapter_label, 1, 1, 1, 2)
|
||||
self.advanced_verse_label = QtGui.QLabel(self.advancedTab)
|
||||
self.advanced_verse_label.setObjectName('advanced_verse_label')
|
||||
self.advancedLayout.addWidget(self.advanced_verse_label, 1, 2)
|
||||
self.advanced_from_label = QtGui.QLabel(self.advancedTab)
|
||||
self.advanced_from_label.setObjectName('advanced_from_label')
|
||||
self.advancedLayout.addWidget(self.advanced_from_label, 3, 0, QtCore.Qt.AlignRight)
|
||||
self.advanced_from_chapter = QtGui.QComboBox(self.advancedTab)
|
||||
self.advanced_from_chapter.setObjectName('advanced_from_chapter')
|
||||
self.advancedLayout.addWidget(self.advanced_from_chapter, 3, 1)
|
||||
self.advanced_from_verse = QtGui.QComboBox(self.advancedTab)
|
||||
self.advanced_from_verse.setObjectName('advanced_from_verse')
|
||||
self.advancedLayout.addWidget(self.advanced_from_verse, 3, 2)
|
||||
self.advanced_to_label = QtGui.QLabel(self.advancedTab)
|
||||
self.advanced_to_label.setObjectName('advanced_to_label')
|
||||
self.advancedLayout.addWidget(self.advanced_to_label, 4, 0, QtCore.Qt.AlignRight)
|
||||
self.advanced_to_chapter = QtGui.QComboBox(self.advancedTab)
|
||||
self.advanced_to_chapter.setObjectName('advanced_to_chapter')
|
||||
self.advancedLayout.addWidget(self.advanced_to_chapter, 4, 1)
|
||||
self.advanced_to_verse = QtGui.QComboBox(self.advancedTab)
|
||||
self.advanced_to_verse.setObjectName('advanced_to_verse')
|
||||
self.advancedLayout.addWidget(self.advanced_to_verse, 4, 2)
|
||||
self.add_search_fields('advanced', UiStrings().Advanced)
|
||||
# Combo Boxes
|
||||
self.quickVersionComboBox.activated.connect(self.updateAutoCompleter)
|
||||
self.quickSecondComboBox.activated.connect(self.updateAutoCompleter)
|
||||
self.advancedVersionComboBox.activated.connect(self.onAdvancedVersionComboBox)
|
||||
self.advancedSecondComboBox.activated.connect(self.onAdvancedSecondComboBox)
|
||||
self.advancedBookComboBox.activated.connect(self.onAdvancedBookComboBox)
|
||||
self.advancedFromChapter.activated.connect(self.onAdvancedFromChapter)
|
||||
self.advancedFromVerse.activated.connect(self.onAdvancedFromVerse)
|
||||
self.advancedToChapter.activated.connect(self.onAdvancedToChapter)
|
||||
QtCore.QObject.connect(self.quickSearchEdit, QtCore.SIGNAL('searchTypeChanged(int)'), self.updateAutoCompleter)
|
||||
self.quickVersionComboBox.activated.connect(self.updateAutoCompleter)
|
||||
self.quickStyleComboBox.activated.connect(self.onQuickStyleComboBoxChanged)
|
||||
self.advancedStyleComboBox.activated.connect(self.onAdvancedStyleComboBoxChanged)
|
||||
self.quickVersionComboBox.activated.connect(self.update_auto_completer)
|
||||
self.quickSecondComboBox.activated.connect(self.update_auto_completer)
|
||||
self.advancedVersionComboBox.activated.connect(self.on_advanced_version_combo_box)
|
||||
self.advancedSecondComboBox.activated.connect(self.on_advanced_second_combo_box)
|
||||
self.advanced_book_combo_box.activated.connect(self.on_advanced_book_combo_box)
|
||||
self.advanced_from_chapter.activated.connect(self.on_advanced_from_chapter)
|
||||
self.advanced_from_verse.activated.connect(self.on_advanced_from_verse)
|
||||
self.advanced_to_chapter.activated.connect(self.on_advanced_to_chapter)
|
||||
QtCore.QObject.connect(self.quick_search_edit, QtCore.SIGNAL('searchTypeChanged(int)'),
|
||||
self.update_auto_completer)
|
||||
self.quickVersionComboBox.activated.connect(self.update_auto_completer)
|
||||
self.quickStyleComboBox.activated.connect(self.on_quick_style_combo_box_changed)
|
||||
self.advancedStyleComboBox.activated.connect(self.on_advanced_style_combo_box_changed)
|
||||
# Buttons
|
||||
self.advancedSearchButton.clicked.connect(self.onAdvancedSearchButton)
|
||||
self.quickSearchButton.clicked.connect(self.onQuickSearchButton)
|
||||
self.advancedSearchButton.clicked.connect(self.on_advanced_search_button)
|
||||
self.quickSearchButton.clicked.connect(self.on_quick_search_button)
|
||||
# Other stuff
|
||||
self.quickSearchEdit.returnPressed.connect(self.onQuickSearchButton)
|
||||
self.searchTabBar.currentChanged.connect(self.onSearchTabBarCurrentChanged)
|
||||
self.quick_search_edit.returnPressed.connect(self.on_quick_search_button)
|
||||
self.search_tab_bar.currentChanged.connect(self.on_search_tab_bar_current_changed)
|
||||
|
||||
def on_focus(self):
|
||||
if self.quickTab.isVisible():
|
||||
self.quickSearchEdit.setFocus()
|
||||
self.quick_search_edit.setFocus()
|
||||
else:
|
||||
self.advancedBookComboBox.setFocus()
|
||||
self.advanced_book_combo_box.setFocus()
|
||||
|
||||
def config_update(self):
|
||||
log.debug('config_update')
|
||||
@ -278,7 +277,7 @@ class BibleMediaItem(MediaManagerItem):
|
||||
|
||||
def retranslateUi(self):
|
||||
log.debug('retranslateUi')
|
||||
self.quickSearchLabel.setText(translate('BiblesPlugin.MediaItem', 'Find:'))
|
||||
self.quick_search_label.setText(translate('BiblesPlugin.MediaItem', 'Find:'))
|
||||
self.quickVersionLabel.setText('%s:' % UiStrings().Version)
|
||||
self.quickSecondLabel.setText(translate('BiblesPlugin.MediaItem', 'Second:'))
|
||||
self.quickStyleLabel.setText(UiStrings().LayoutStyle)
|
||||
@ -286,13 +285,13 @@ class BibleMediaItem(MediaManagerItem):
|
||||
self.quickStyleComboBox.setItemText(LayoutStyle.VersePerLine, UiStrings().VersePerLine)
|
||||
self.quickStyleComboBox.setItemText(LayoutStyle.Continuous, UiStrings().Continuous)
|
||||
self.quickLockButton.setToolTip(translate('BiblesPlugin.MediaItem',
|
||||
'Toggle to keep or clear the previous results.'))
|
||||
'Toggle to keep or clear the previous results.'))
|
||||
self.quickSearchButton.setText(UiStrings().Search)
|
||||
self.advancedBookLabel.setText(translate('BiblesPlugin.MediaItem', 'Book:'))
|
||||
self.advancedChapterLabel.setText(translate('BiblesPlugin.MediaItem', 'Chapter:'))
|
||||
self.advancedVerseLabel.setText(translate('BiblesPlugin.MediaItem', 'Verse:'))
|
||||
self.advancedFromLabel.setText(translate('BiblesPlugin.MediaItem', 'From:'))
|
||||
self.advancedToLabel.setText(translate('BiblesPlugin.MediaItem', 'To:'))
|
||||
self.advanced_book_label.setText(translate('BiblesPlugin.MediaItem', 'Book:'))
|
||||
self.advanced_chapter_label.setText(translate('BiblesPlugin.MediaItem', 'Chapter:'))
|
||||
self.advanced_verse_label.setText(translate('BiblesPlugin.MediaItem', 'Verse:'))
|
||||
self.advanced_from_label.setText(translate('BiblesPlugin.MediaItem', 'From:'))
|
||||
self.advanced_to_label.setText(translate('BiblesPlugin.MediaItem', 'To:'))
|
||||
self.advancedVersionLabel.setText('%s:' % UiStrings().Version)
|
||||
self.advancedSecondLabel.setText(translate('BiblesPlugin.MediaItem', 'Second:'))
|
||||
self.advancedStyleLabel.setText(UiStrings().LayoutStyle)
|
||||
@ -300,14 +299,14 @@ class BibleMediaItem(MediaManagerItem):
|
||||
self.advancedStyleComboBox.setItemText(LayoutStyle.VersePerLine, UiStrings().VersePerLine)
|
||||
self.advancedStyleComboBox.setItemText(LayoutStyle.Continuous, UiStrings().Continuous)
|
||||
self.advancedLockButton.setToolTip(translate('BiblesPlugin.MediaItem',
|
||||
'Toggle to keep or clear the previous results.'))
|
||||
'Toggle to keep or clear the previous results.'))
|
||||
self.advancedSearchButton.setText(UiStrings().Search)
|
||||
|
||||
def initialise(self):
|
||||
log.debug('bible manager initialise')
|
||||
self.plugin.manager.media = self
|
||||
self.loadBibles()
|
||||
self.quickSearchEdit.set_search_types([
|
||||
self.load_bibles()
|
||||
self.quick_search_edit.set_search_types([
|
||||
(BibleSearch.Reference, ':/bibles/bibles_search_reference.png',
|
||||
translate('BiblesPlugin.MediaItem', 'Scripture Reference'),
|
||||
translate('BiblesPlugin.MediaItem', 'Search Scripture Reference...')),
|
||||
@ -315,11 +314,11 @@ class BibleMediaItem(MediaManagerItem):
|
||||
translate('BiblesPlugin.MediaItem', 'Text Search'),
|
||||
translate('BiblesPlugin.MediaItem', 'Search Text...'))
|
||||
])
|
||||
self.quickSearchEdit.set_current_search_type(Settings().value('%s/last search type' % self.settings_section))
|
||||
self.quick_search_edit.set_current_search_type(Settings().value('%s/last search type' % self.settings_section))
|
||||
self.config_update()
|
||||
log.debug('bible manager initialise complete')
|
||||
|
||||
def loadBibles(self):
|
||||
def load_bibles(self):
|
||||
log.debug('Loading Bibles')
|
||||
self.quickVersionComboBox.clear()
|
||||
self.quickSecondComboBox.clear()
|
||||
@ -340,80 +339,73 @@ class BibleMediaItem(MediaManagerItem):
|
||||
bible = Settings().value(self.settings_section + '/advanced bible')
|
||||
if bible in bibles:
|
||||
find_and_set_in_combo_box(self.advancedVersionComboBox, bible)
|
||||
self.initialiseAdvancedBible(str(bible))
|
||||
self.initialise_advanced_bible(str(bible))
|
||||
elif bibles:
|
||||
self.initialiseAdvancedBible(bibles[0])
|
||||
self.initialise_advanced_bible(bibles[0])
|
||||
bible = Settings().value(self.settings_section + '/quick bible')
|
||||
find_and_set_in_combo_box(self.quickVersionComboBox, bible)
|
||||
|
||||
def reload_bibles(self, process=False):
|
||||
log.debug('Reloading Bibles')
|
||||
self.plugin.manager.reload_bibles()
|
||||
self.loadBibles()
|
||||
self.load_bibles()
|
||||
# If called from first time wizard re-run, process any new bibles.
|
||||
if process:
|
||||
self.plugin.app_startup()
|
||||
self.updateAutoCompleter()
|
||||
self.update_auto_completer()
|
||||
|
||||
def initialiseAdvancedBible(self, bible, last_book_id=None):
|
||||
def initialise_advanced_bible(self, bible, last_book_id=None):
|
||||
"""
|
||||
This initialises the given bible, which means that its book names and
|
||||
their chapter numbers is added to the combo boxes on the
|
||||
'Advanced Search' Tab. This is not of any importance of the
|
||||
'Quick Search' Tab.
|
||||
This initialises the given bible, which means that its book names and their chapter numbers is added to the
|
||||
combo boxes on the 'Advanced Search' Tab. This is not of any importance of the 'Quick Search' Tab.
|
||||
|
||||
``bible``
|
||||
The bible to initialise (unicode).
|
||||
|
||||
``last_book_id``
|
||||
The "book reference id" of the book which is choosen at the moment.
|
||||
:param bible: The bible to initialise (unicode).
|
||||
:param last_book_id: The "book reference id" of the book which is chosen at the moment.
|
||||
(int)
|
||||
"""
|
||||
log.debug('initialiseAdvancedBible %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)
|
||||
secondbible = self.advancedSecondComboBox.currentText()
|
||||
if secondbible != '':
|
||||
secondbook_data = self.plugin.manager.get_books(secondbible)
|
||||
second_bible = self.advancedSecondComboBox.currentText()
|
||||
if second_bible != '':
|
||||
second_book_data = self.plugin.manager.get_books(second_bible)
|
||||
book_data_temp = []
|
||||
for book in book_data:
|
||||
for secondbook in secondbook_data:
|
||||
if book['book_reference_id'] == \
|
||||
secondbook['book_reference_id']:
|
||||
for second_book in second_book_data:
|
||||
if book['book_reference_id'] == second_book['book_reference_id']:
|
||||
book_data_temp.append(book)
|
||||
book_data = book_data_temp
|
||||
self.advancedBookComboBox.clear()
|
||||
self.advanced_book_combo_box.clear()
|
||||
first = True
|
||||
initialise_chapter_verse = False
|
||||
language_selection = self.plugin.manager.get_language_selection(bible)
|
||||
book_names = BibleStrings().BookNames
|
||||
for book in book_data:
|
||||
row = self.advancedBookComboBox.count()
|
||||
row = self.advanced_book_combo_box.count()
|
||||
if language_selection == LanguageSelection.Bible:
|
||||
self.advancedBookComboBox.addItem(book['name'])
|
||||
self.advanced_book_combo_box.addItem(book['name'])
|
||||
elif language_selection == LanguageSelection.Application:
|
||||
data = BiblesResourcesDB.get_book_by_id(book['book_reference_id'])
|
||||
self.advancedBookComboBox.addItem(book_names[data['abbreviation']])
|
||||
self.advanced_book_combo_box.addItem(book_names[data['abbreviation']])
|
||||
elif language_selection == LanguageSelection.English:
|
||||
data = BiblesResourcesDB.get_book_by_id(book['book_reference_id'])
|
||||
self.advancedBookComboBox.addItem(data['name'])
|
||||
self.advancedBookComboBox.setItemData(row, book['book_reference_id'])
|
||||
self.advanced_book_combo_box.addItem(data['name'])
|
||||
self.advanced_book_combo_box.setItemData(row, book['book_reference_id'])
|
||||
if first:
|
||||
first = False
|
||||
first_book = book
|
||||
initialise_chapter_verse = True
|
||||
if last_book_id and last_book_id == int(book['book_reference_id']):
|
||||
index = self.advancedBookComboBox.findData(book['book_reference_id'])
|
||||
index = self.advanced_book_combo_box.findData(book['book_reference_id'])
|
||||
if index == -1:
|
||||
# Not Found.
|
||||
index = 0
|
||||
self.advancedBookComboBox.setCurrentIndex(index)
|
||||
self.advanced_book_combo_box.setCurrentIndex(index)
|
||||
initialise_chapter_verse = False
|
||||
if initialise_chapter_verse:
|
||||
self.initialiseChapterVerse(bible, first_book['name'],
|
||||
first_book['book_reference_id'])
|
||||
self.initialise_chapter_verse(bible, first_book['name'], first_book['book_reference_id'])
|
||||
|
||||
def initialiseChapterVerse(self, bible, book, book_ref_id):
|
||||
log.debug('initialiseChapterVerse %s, %s, %s', bible, book, book_ref_id)
|
||||
def initialise_chapter_verse(self, bible, book, book_ref_id):
|
||||
log.debug('initialise_chapter_verse %s, %s, %s', bible, book, book_ref_id)
|
||||
book = self.plugin.manager.get_book_by_id(bible, book_ref_id)
|
||||
self.chapter_count = self.plugin.manager.get_chapter_count(bible, book)
|
||||
verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(bible, book_ref_id, 1)
|
||||
@ -422,36 +414,36 @@ class BibleMediaItem(MediaManagerItem):
|
||||
critical_error_message_box(message=translate('BiblesPlugin.MediaItem', 'Bible not fully loaded.'))
|
||||
else:
|
||||
self.advancedSearchButton.setEnabled(True)
|
||||
self.adjustComboBox(1, self.chapter_count, self.advancedFromChapter)
|
||||
self.adjustComboBox(1, self.chapter_count, self.advancedToChapter)
|
||||
self.adjustComboBox(1, verse_count, self.advancedFromVerse)
|
||||
self.adjustComboBox(1, verse_count, self.advancedToVerse)
|
||||
self.adjust_combo_box(1, self.chapter_count, self.advanced_from_chapter)
|
||||
self.adjust_combo_box(1, self.chapter_count, self.advanced_to_chapter)
|
||||
self.adjust_combo_box(1, verse_count, self.advanced_from_verse)
|
||||
self.adjust_combo_box(1, verse_count, self.advanced_to_verse)
|
||||
|
||||
def updateAutoCompleter(self):
|
||||
def update_auto_completer(self):
|
||||
"""
|
||||
This updates the bible book completion list for the search field. The
|
||||
completion depends on the bible. It is only updated when we are doing a
|
||||
reference search, otherwise the auto completion list is removed.
|
||||
"""
|
||||
log.debug('updateAutoCompleter')
|
||||
log.debug('update_auto_completer')
|
||||
# Save the current search type to the configuration.
|
||||
Settings().setValue('%s/last search type' % self.settings_section, self.quickSearchEdit.current_search_type())
|
||||
Settings().setValue('%s/last search type' % self.settings_section, self.quick_search_edit.current_search_type())
|
||||
# Save the current bible to the configuration.
|
||||
Settings().setValue(self.settings_section + '/quick bible', self.quickVersionComboBox.currentText())
|
||||
books = []
|
||||
# We have to do a 'Reference Search'.
|
||||
if self.quickSearchEdit.current_search_type() == BibleSearch.Reference:
|
||||
if self.quick_search_edit.current_search_type() == BibleSearch.Reference:
|
||||
bibles = self.plugin.manager.get_bibles()
|
||||
bible = self.quickVersionComboBox.currentText()
|
||||
if bible:
|
||||
book_data = bibles[bible].get_books()
|
||||
secondbible = self.quickSecondComboBox.currentText()
|
||||
if secondbible != '':
|
||||
secondbook_data = bibles[secondbible].get_books()
|
||||
second_bible = self.quickSecondComboBox.currentText()
|
||||
if second_bible != '':
|
||||
second_book_data = bibles[second_bible].get_books()
|
||||
book_data_temp = []
|
||||
for book in book_data:
|
||||
for secondbook in secondbook_data:
|
||||
if book.book_reference_id == secondbook.book_reference_id:
|
||||
for second_book in second_book_data:
|
||||
if book.book_reference_id == second_book.book_reference_id:
|
||||
book_data_temp.append(book)
|
||||
book_data = book_data_temp
|
||||
language_selection = self.plugin.manager.get_language_selection(bible)
|
||||
@ -467,7 +459,7 @@ class BibleMediaItem(MediaManagerItem):
|
||||
data = BiblesResourcesDB.get_book_by_id(book.book_reference_id)
|
||||
books.append(data['name'] + ' ')
|
||||
books.sort(key=get_locale_key)
|
||||
set_case_insensitive_completer(books, self.quickSearchEdit)
|
||||
set_case_insensitive_completer(books, self.quick_search_edit)
|
||||
|
||||
def on_import_click(self):
|
||||
if not hasattr(self, 'import_wizard'):
|
||||
@ -482,9 +474,9 @@ class BibleMediaItem(MediaManagerItem):
|
||||
elif self.advancedTab.isVisible():
|
||||
bible = self.advancedVersionComboBox.currentText()
|
||||
if bible:
|
||||
self.editBibleForm = EditBibleForm(self, self.main_window, self.plugin.manager)
|
||||
self.editBibleForm.loadBible(bible)
|
||||
if self.editBibleForm.exec_():
|
||||
self.edit_bible_form = EditBibleForm(self, self.main_window, self.plugin.manager)
|
||||
self.edit_bible_form.loadBible(bible)
|
||||
if self.edit_bible_form.exec_():
|
||||
self.reload_bibles()
|
||||
|
||||
def on_delete_click(self):
|
||||
@ -493,117 +485,113 @@ class BibleMediaItem(MediaManagerItem):
|
||||
elif self.advancedTab.isVisible():
|
||||
bible = self.advancedVersionComboBox.currentText()
|
||||
if bible:
|
||||
if QtGui.QMessageBox.question(self, UiStrings().ConfirmDelete,
|
||||
translate('BiblesPlugin.MediaItem', 'Are you sure you want to completely delete "%s" Bible from '
|
||||
'OpenLP?\n\nYou will need to re-import this Bible to use it again.') % bible,
|
||||
if QtGui.QMessageBox.question(
|
||||
self, UiStrings().ConfirmDelete,
|
||||
translate('BiblesPlugin.MediaItem', 'Are you sure you want to completely delete "%s" Bible from '
|
||||
'OpenLP?\n\nYou will need to re-import this Bible to use it again.') % bible,
|
||||
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No),
|
||||
QtGui.QMessageBox.Yes) == QtGui.QMessageBox.No:
|
||||
QtGui.QMessageBox.Yes) == QtGui.QMessageBox.No:
|
||||
return
|
||||
self.plugin.manager.delete_bible(bible)
|
||||
self.reload_bibles()
|
||||
|
||||
def onSearchTabBarCurrentChanged(self, index):
|
||||
def on_search_tab_bar_current_changed(self, index):
|
||||
if index == 0:
|
||||
self.advancedTab.setVisible(False)
|
||||
self.quickTab.setVisible(True)
|
||||
self.quickSearchEdit.setFocus()
|
||||
self.quick_search_edit.setFocus()
|
||||
else:
|
||||
self.quickTab.setVisible(False)
|
||||
self.advancedTab.setVisible(True)
|
||||
self.advancedBookComboBox.setFocus()
|
||||
self.advanced_book_combo_box.setFocus()
|
||||
|
||||
def onLockButtonToggled(self, checked):
|
||||
def on_lock_button_toggled(self, checked):
|
||||
if checked:
|
||||
self.sender().setIcon(self.lock_icon)
|
||||
else:
|
||||
self.sender().setIcon(self.unlock_icon)
|
||||
|
||||
def onQuickStyleComboBoxChanged(self):
|
||||
def on_quick_style_combo_box_changed(self):
|
||||
self.settings.layout_style = self.quickStyleComboBox.currentIndex()
|
||||
self.advancedStyleComboBox.setCurrentIndex(self.settings.layout_style)
|
||||
self.settings.layout_style_combo_box.setCurrentIndex(self.settings.layout_style)
|
||||
Settings().setValue(self.settings_section + '/verse layout style', self.settings.layout_style)
|
||||
|
||||
def onAdvancedStyleComboBoxChanged(self):
|
||||
def on_advanced_style_combo_box_changed(self):
|
||||
self.settings.layout_style = self.advancedStyleComboBox.currentIndex()
|
||||
self.quickStyleComboBox.setCurrentIndex(self.settings.layout_style)
|
||||
self.settings.layout_style_combo_box.setCurrentIndex(self.settings.layout_style)
|
||||
Settings().setValue(self.settings_section + '/verse layout style', self.settings.layout_style)
|
||||
|
||||
def onAdvancedVersionComboBox(self):
|
||||
def on_advanced_version_combo_box(self):
|
||||
Settings().setValue(self.settings_section + '/advanced bible', self.advancedVersionComboBox.currentText())
|
||||
self.initialiseAdvancedBible(self.advancedVersionComboBox.currentText(),
|
||||
self.advancedBookComboBox.itemData(int(self.advancedBookComboBox.currentIndex())))
|
||||
|
||||
def onAdvancedSecondComboBox(self):
|
||||
self.initialiseAdvancedBible(self.advancedVersionComboBox.currentText(),
|
||||
self.advancedBookComboBox.itemData(int(self.advancedBookComboBox.currentIndex())))
|
||||
|
||||
def onAdvancedBookComboBox(self):
|
||||
item = int(self.advancedBookComboBox.currentIndex())
|
||||
self.initialiseChapterVerse(
|
||||
self.initialise_advanced_bible(
|
||||
self.advancedVersionComboBox.currentText(),
|
||||
self.advancedBookComboBox.currentText(),
|
||||
self.advancedBookComboBox.itemData(item))
|
||||
self.advanced_book_combo_box.itemData(int(self.advanced_book_combo_box.currentIndex())))
|
||||
|
||||
def onAdvancedFromVerse(self):
|
||||
chapter_from = int(self.advancedFromChapter.currentText())
|
||||
chapter_to = int(self.advancedToChapter.currentText())
|
||||
def on_advanced_second_combo_box(self):
|
||||
self.initialise_advanced_bible(
|
||||
self.advancedVersionComboBox.currentText(),
|
||||
self.advanced_book_combo_box.itemData(int(self.advanced_book_combo_box.currentIndex())))
|
||||
|
||||
def on_advanced_book_combo_box(self):
|
||||
item = int(self.advanced_book_combo_box.currentIndex())
|
||||
self.initialise_chapter_verse(
|
||||
self.advancedVersionComboBox.currentText(),
|
||||
self.advanced_book_combo_box.currentText(),
|
||||
self.advanced_book_combo_box.itemData(item))
|
||||
|
||||
def on_advanced_from_verse(self):
|
||||
chapter_from = int(self.advanced_from_chapter.currentText())
|
||||
chapter_to = int(self.advanced_to_chapter.currentText())
|
||||
if chapter_from == chapter_to:
|
||||
bible = self.advancedVersionComboBox.currentText()
|
||||
book_ref_id = self.advancedBookComboBox.itemData(int(self.advancedBookComboBox.currentIndex()))
|
||||
verse_from = int(self.advancedFromVerse.currentText())
|
||||
book_ref_id = self.advanced_book_combo_box.itemData(int(self.advanced_book_combo_box.currentIndex()))
|
||||
verse_from = int(self.advanced_from_verse.currentText())
|
||||
verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(bible, book_ref_id, chapter_to)
|
||||
self.adjustComboBox(verse_from, verse_count, self.advancedToVerse, True)
|
||||
self.adjust_combo_box(verse_from, verse_count, self.advanced_to_verse, True)
|
||||
|
||||
def onAdvancedToChapter(self):
|
||||
def on_advanced_to_chapter(self):
|
||||
bible = self.advancedVersionComboBox.currentText()
|
||||
book_ref_id = self.advancedBookComboBox.itemData(int(self.advancedBookComboBox.currentIndex()))
|
||||
chapter_from = int(self.advancedFromChapter.currentText())
|
||||
chapter_to = int(self.advancedToChapter.currentText())
|
||||
verse_from = int(self.advancedFromVerse.currentText())
|
||||
verse_to = int(self.advancedToVerse.currentText())
|
||||
book_ref_id = self.advanced_book_combo_box.itemData(int(self.advanced_book_combo_box.currentIndex()))
|
||||
chapter_from = int(self.advanced_from_chapter.currentText())
|
||||
chapter_to = int(self.advanced_to_chapter.currentText())
|
||||
verse_from = int(self.advanced_from_verse.currentText())
|
||||
verse_to = int(self.advanced_to_verse.currentText())
|
||||
verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(bible, book_ref_id, chapter_to)
|
||||
if chapter_from == chapter_to and verse_from > verse_to:
|
||||
self.adjustComboBox(verse_from, verse_count, self.advancedToVerse)
|
||||
self.adjust_combo_box(verse_from, verse_count, self.advanced_to_verse)
|
||||
else:
|
||||
self.adjustComboBox(1, verse_count, self.advancedToVerse)
|
||||
self.adjust_combo_box(1, verse_count, self.advanced_to_verse)
|
||||
|
||||
def onAdvancedFromChapter(self):
|
||||
def on_advanced_from_chapter(self):
|
||||
bible = self.advancedVersionComboBox.currentText()
|
||||
book_ref_id = self.advancedBookComboBox.itemData(
|
||||
int(self.advancedBookComboBox.currentIndex()))
|
||||
chapter_from = int(self.advancedFromChapter.currentText())
|
||||
chapter_to = int(self.advancedToChapter.currentText())
|
||||
book_ref_id = self.advanced_book_combo_box.itemData(
|
||||
int(self.advanced_book_combo_box.currentIndex()))
|
||||
chapter_from = int(self.advanced_from_chapter.currentText())
|
||||
chapter_to = int(self.advanced_to_chapter.currentText())
|
||||
verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(bible, book_ref_id, chapter_from)
|
||||
self.adjustComboBox(1, verse_count, self.advancedFromVerse)
|
||||
self.adjust_combo_box(1, verse_count, self.advanced_from_verse)
|
||||
if chapter_from > chapter_to:
|
||||
self.adjustComboBox(1, verse_count, self.advancedToVerse)
|
||||
self.adjustComboBox(chapter_from, self.chapter_count, self.advancedToChapter)
|
||||
self.adjust_combo_box(1, verse_count, self.advanced_to_verse)
|
||||
self.adjust_combo_box(chapter_from, self.chapter_count, self.advanced_to_chapter)
|
||||
elif chapter_from == chapter_to:
|
||||
self.adjustComboBox(chapter_from, self.chapter_count, self.advancedToChapter)
|
||||
self.adjustComboBox(1, verse_count, self.advancedToVerse, True)
|
||||
self.adjust_combo_box(chapter_from, self.chapter_count, self.advanced_to_chapter)
|
||||
self.adjust_combo_box(1, verse_count, self.advanced_to_verse, True)
|
||||
else:
|
||||
self.adjustComboBox(chapter_from, self.chapter_count, self.advancedToChapter, True)
|
||||
self.adjust_combo_box(chapter_from, self.chapter_count, self.advanced_to_chapter, True)
|
||||
|
||||
def adjustComboBox(self, range_from, range_to, combo, restore=False):
|
||||
def adjust_combo_box(self, range_from, range_to, combo, restore=False):
|
||||
"""
|
||||
Adjusts the given como box to the given values.
|
||||
|
||||
``range_from``
|
||||
The first number of the range (int).
|
||||
|
||||
``range_to``
|
||||
The last number of the range (int).
|
||||
|
||||
``combo``
|
||||
The combo box itself (QComboBox).
|
||||
|
||||
``restore``
|
||||
If True, then the combo's currentText will be restored after
|
||||
:param range_from: The first number of the range (int).
|
||||
:param range_to: The last number of the range (int).
|
||||
:param combo: The combo box itself (QComboBox).
|
||||
:param restore: If True, then the combo's currentText will be restored after
|
||||
adjusting (if possible).
|
||||
"""
|
||||
log.debug('adjustComboBox %s, %s, %s', combo, range_from, range_to)
|
||||
log.debug('adjust_combo_box %s, %s, %s', combo, range_from, range_to)
|
||||
if restore:
|
||||
old_text = combo.currentText()
|
||||
combo.clear()
|
||||
@ -611,7 +599,7 @@ class BibleMediaItem(MediaManagerItem):
|
||||
if restore and combo.findText(old_text) != -1:
|
||||
combo.setCurrentIndex(combo.findText(old_text))
|
||||
|
||||
def onAdvancedSearchButton(self):
|
||||
def on_advanced_search_button(self):
|
||||
"""
|
||||
Does an advanced search and saves the search results.
|
||||
"""
|
||||
@ -620,32 +608,32 @@ class BibleMediaItem(MediaManagerItem):
|
||||
self.application.process_events()
|
||||
bible = self.advancedVersionComboBox.currentText()
|
||||
second_bible = self.advancedSecondComboBox.currentText()
|
||||
book = self.advancedBookComboBox.currentText()
|
||||
book_ref_id = self.advancedBookComboBox.itemData(int(self.advancedBookComboBox.currentIndex()))
|
||||
chapter_from = self.advancedFromChapter.currentText()
|
||||
chapter_to = self.advancedToChapter.currentText()
|
||||
verse_from = self.advancedFromVerse.currentText()
|
||||
verse_to = self.advancedToVerse.currentText()
|
||||
book = self.advanced_book_combo_box.currentText()
|
||||
book_ref_id = self.advanced_book_combo_box.itemData(int(self.advanced_book_combo_box.currentIndex()))
|
||||
chapter_from = self.advanced_from_chapter.currentText()
|
||||
chapter_to = self.advanced_to_chapter.currentText()
|
||||
verse_from = self.advanced_from_verse.currentText()
|
||||
verse_to = self.advanced_to_verse.currentText()
|
||||
verse_separator = get_reference_separator('sep_v_display')
|
||||
range_separator = get_reference_separator('sep_r_display')
|
||||
verse_range = chapter_from + verse_separator + verse_from + range_separator + chapter_to + \
|
||||
verse_separator + verse_to
|
||||
versetext = '%s %s' % (book, verse_range)
|
||||
verse_text = '%s %s' % (book, verse_range)
|
||||
self.application.set_busy_cursor()
|
||||
self.search_results = self.plugin.manager.get_verses(bible, versetext, book_ref_id)
|
||||
self.search_results = self.plugin.manager.get_verses(bible, verse_text, book_ref_id)
|
||||
if second_bible:
|
||||
self.second_search_results = self.plugin.manager.get_verses(second_bible, versetext, book_ref_id)
|
||||
self.second_search_results = self.plugin.manager.get_verses(second_bible, verse_text, book_ref_id)
|
||||
if not self.advancedLockButton.isChecked():
|
||||
self.list_view.clear()
|
||||
if self.list_view.count() != 0:
|
||||
self.__check_second_bible(bible, second_bible)
|
||||
elif self.search_results:
|
||||
self.displayResults(bible, second_bible)
|
||||
self.display_results(bible, second_bible)
|
||||
self.advancedSearchButton.setEnabled(True)
|
||||
self.check_search_result()
|
||||
self.application.set_normal_cursor()
|
||||
|
||||
def onQuickSearchButton(self):
|
||||
def on_quick_search_button(self):
|
||||
"""
|
||||
Does a quick search and saves the search results. Quick search can
|
||||
either be "Reference Search" or "Text Search".
|
||||
@ -655,13 +643,13 @@ class BibleMediaItem(MediaManagerItem):
|
||||
self.application.process_events()
|
||||
bible = self.quickVersionComboBox.currentText()
|
||||
second_bible = self.quickSecondComboBox.currentText()
|
||||
text = self.quickSearchEdit.text()
|
||||
if self.quickSearchEdit.current_search_type() == BibleSearch.Reference:
|
||||
text = self.quick_search_edit.text()
|
||||
if self.quick_search_edit.current_search_type() == BibleSearch.Reference:
|
||||
# We are doing a 'Reference Search'.
|
||||
self.search_results = self.plugin.manager.get_verses(bible, text)
|
||||
if second_bible and self.search_results:
|
||||
self.second_search_results = self.plugin.manager.get_verses(second_bible, text,
|
||||
self.search_results[0].book.book_reference_id)
|
||||
self.second_search_results = \
|
||||
self.plugin.manager.get_verses(second_bible, text, self.search_results[0].book.book_reference_id)
|
||||
else:
|
||||
# We are doing a 'Text Search'.
|
||||
self.application.set_busy_cursor()
|
||||
@ -681,13 +669,13 @@ class BibleMediaItem(MediaManagerItem):
|
||||
count += 1
|
||||
continue
|
||||
new_search_results.append(verse)
|
||||
text.append((verse.book.book_reference_id, verse.chapter,
|
||||
verse.verse, verse.verse))
|
||||
text.append((verse.book.book_reference_id, verse.chapter, verse.verse, verse.verse))
|
||||
if passage_not_found:
|
||||
QtGui.QMessageBox.information(self, translate('BiblesPlugin.MediaItem', 'Information'),
|
||||
QtGui.QMessageBox.information(
|
||||
self, translate('BiblesPlugin.MediaItem', 'Information'),
|
||||
translate('BiblesPlugin.MediaItem', 'The second Bible does not contain all the verses '
|
||||
'that are in the main Bible. Only verses found in both Bibles will be shown. %d verses '
|
||||
'have not been included in the results.') % count,
|
||||
'that are in the main Bible. Only verses found in both Bibles will be shown. %d '
|
||||
'verses have not been included in the results.') % count,
|
||||
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok))
|
||||
self.search_results = new_search_results
|
||||
self.second_search_results = bibles[second_bible].get_verses(text)
|
||||
@ -696,24 +684,24 @@ class BibleMediaItem(MediaManagerItem):
|
||||
if self.list_view.count() != 0 and self.search_results:
|
||||
self.__check_second_bible(bible, second_bible)
|
||||
elif self.search_results:
|
||||
self.displayResults(bible, second_bible)
|
||||
self.display_results(bible, second_bible)
|
||||
self.quickSearchButton.setEnabled(True)
|
||||
self.check_search_result()
|
||||
self.application.set_normal_cursor()
|
||||
|
||||
def displayResults(self, bible, second_bible=''):
|
||||
def display_results(self, bible, second_bible=''):
|
||||
"""
|
||||
Displays the search results in the media manager. All data needed for
|
||||
further action is saved for/in each row.
|
||||
"""
|
||||
items = self.buildDisplayResults(bible, second_bible, self.search_results)
|
||||
items = self.build_display_results(bible, second_bible, self.search_results)
|
||||
for bible_verse in items:
|
||||
self.list_view.addItem(bible_verse)
|
||||
self.list_view.selectAll()
|
||||
self.search_results = {}
|
||||
self.second_search_results = {}
|
||||
|
||||
def buildDisplayResults(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
|
||||
further action is saved for/in each row.
|
||||
@ -764,7 +752,7 @@ class BibleMediaItem(MediaManagerItem):
|
||||
log.exception('The second_search_results does not have as many verses as the search_results.')
|
||||
break
|
||||
bible_text = '%s %d%s%d (%s, %s)' % (book, verse.chapter, verse_separator, verse.verse, version,
|
||||
second_version)
|
||||
second_version)
|
||||
else:
|
||||
bible_text = '%s %d%s%d (%s)' % (book, verse.chapter, verse_separator, verse.verse, version)
|
||||
bible_verse = QtGui.QListWidgetItem(bible_text)
|
||||
@ -772,11 +760,16 @@ class BibleMediaItem(MediaManagerItem):
|
||||
items.append(bible_verse)
|
||||
return items
|
||||
|
||||
def generate_slide_data(self, service_item, item=None, xmlVersion=False,
|
||||
remote=False, context=ServiceItemContext.Service):
|
||||
def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False,
|
||||
context=ServiceItemContext.Service):
|
||||
"""
|
||||
Generates and formats the slides for the service item as well as the
|
||||
service item's title.
|
||||
Generate the slide data. Needs to be implemented by the plugin.
|
||||
|
||||
:param service_item: The service item to be built on
|
||||
:param item: The Song item to be used
|
||||
:param xml_version: The xml version (not used)
|
||||
:param remote: Triggered from remote
|
||||
:param context: Why is it being generated
|
||||
"""
|
||||
log.debug('generating slide data')
|
||||
if item:
|
||||
@ -806,7 +799,7 @@ class BibleMediaItem(MediaManagerItem):
|
||||
second_permissions = self._decode_qt_object(bitem, 'second_permissions')
|
||||
second_text = self._decode_qt_object(bitem, 'second_text')
|
||||
verses.add(book, chapter, verse, version, copyright, permissions)
|
||||
verse_text = self.formatVerse(old_chapter, chapter, verse)
|
||||
verse_text = self.format_verse(old_chapter, chapter, verse)
|
||||
if second_bible:
|
||||
bible_text = '%s%s\n\n%s %s' % (verse_text, text, verse_text, second_text)
|
||||
raw_slides.append(bible_text.rstrip())
|
||||
@ -825,8 +818,8 @@ class BibleMediaItem(MediaManagerItem):
|
||||
bible_text = bible_text.strip(' ')
|
||||
if not old_item:
|
||||
start_item = bitem
|
||||
elif self.checkTitle(bitem, old_item):
|
||||
raw_title.append(self.formatTitle(start_item, old_item))
|
||||
elif self.check_title(bitem, old_item):
|
||||
raw_title.append(self.format_title(start_item, old_item))
|
||||
start_item = bitem
|
||||
old_item = bitem
|
||||
old_chapter = chapter
|
||||
@ -835,7 +828,7 @@ class BibleMediaItem(MediaManagerItem):
|
||||
if second_bible:
|
||||
verses.add_version(second_version, second_copyright, second_permissions)
|
||||
service_item.raw_footer.append(verses.format_versions())
|
||||
raw_title.append(self.formatTitle(start_item, bitem))
|
||||
raw_title.append(self.format_title(start_item, bitem))
|
||||
# If there are no more items we check whether we have to add bible_text.
|
||||
if bible_text:
|
||||
raw_slides.append(bible_text.lstrip())
|
||||
@ -858,17 +851,14 @@ class BibleMediaItem(MediaManagerItem):
|
||||
service_item.add_from_text(slide)
|
||||
return True
|
||||
|
||||
def formatTitle(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
|
||||
we are at the end of a verse range. E. g. if we want to add
|
||||
Genesis 1:1-6 as well as Daniel 2:14.
|
||||
|
||||
``start_item``
|
||||
The first item of a range.
|
||||
|
||||
``old_item``
|
||||
The last item of a range.
|
||||
:param start_bitem: The first item of a range.
|
||||
:param old_bitem: The last item of a range.
|
||||
"""
|
||||
verse_separator = get_reference_separator('sep_v_display')
|
||||
range_separator = get_reference_separator('sep_r_display')
|
||||
@ -893,17 +883,15 @@ class BibleMediaItem(MediaManagerItem):
|
||||
range_separator + old_chapter + verse_separator + old_verse
|
||||
return '%s %s (%s)' % (start_book, verse_range, bibles)
|
||||
|
||||
def checkTitle(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
|
||||
the case, we return True, otherwise False. E. g. if we added
|
||||
|
||||
Genesis 1:1-6, but the next verse is Daniel 2:14, we return True.
|
||||
|
||||
``item``
|
||||
The item we are dealing with at the moment.
|
||||
|
||||
``old_item``
|
||||
The item we were previously dealing with.
|
||||
:param bitem: The item we are dealing with at the moment.
|
||||
:param old_bitem: The item we were previously dealing with.
|
||||
"""
|
||||
# Get all the necessary meta data.
|
||||
book = self._decode_qt_object(bitem, 'book')
|
||||
@ -930,22 +918,18 @@ class BibleMediaItem(MediaManagerItem):
|
||||
return True
|
||||
return False
|
||||
|
||||
def formatVerse(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
|
||||
chapter and verse. The text is either surrounded by round, square,
|
||||
|
||||
curly brackets or no brackets at all. For example::
|
||||
|
||||
u'{su}1:1{/su}'
|
||||
|
||||
``old_chapter``
|
||||
The previous verse's chapter number (int).
|
||||
|
||||
``chapter``
|
||||
The chapter number (int).
|
||||
|
||||
``verse``
|
||||
The verse number (int).
|
||||
:param old_chapter: The previous verse's chapter number (int).
|
||||
:param chapter: The chapter number (int).
|
||||
:param verse: The verse number (int).
|
||||
"""
|
||||
verse_separator = get_reference_separator('sep_v_display')
|
||||
if not self.settings.is_verse_number_visible:
|
||||
@ -969,16 +953,15 @@ class BibleMediaItem(MediaManagerItem):
|
||||
bible = self.quickVersionComboBox.currentText()
|
||||
search_results = self.plugin.manager.get_verses(bible, string, False, showError)
|
||||
if search_results:
|
||||
versetext = ' '.join([verse.text for verse in search_results])
|
||||
return [[string, versetext]]
|
||||
verse_text = ' '.join([verse.text for verse in search_results])
|
||||
return [[string, verse_text]]
|
||||
return []
|
||||
|
||||
def create_item_from_id(self, item_id):
|
||||
"""
|
||||
Create a media item from an item id.
|
||||
"""
|
||||
item = QtGui.QListWidgetItem()
|
||||
bible = self.quickVersionComboBox.currentText()
|
||||
search_results = self.plugin.manager.get_verses(bible, item_id, False)
|
||||
items = self.buildDisplayResults(bible, '', search_results)
|
||||
items = self.build_display_results(bible, '', search_results)
|
||||
return items
|
||||
|
@ -55,8 +55,7 @@ class OpenSongBible(BibleDB):
|
||||
"""
|
||||
Recursively get all text in an objectify element and its child elements.
|
||||
|
||||
``element``
|
||||
An objectify element to get the text from
|
||||
:param element: An objectify element to get the text from
|
||||
"""
|
||||
verse_text = ''
|
||||
if element.text:
|
||||
@ -122,14 +121,16 @@ class OpenSongBible(BibleDB):
|
||||
else:
|
||||
verse_number += 1
|
||||
self.create_verse(db_book.id, chapter_number, verse_number, self.get_text(verse))
|
||||
self.wizard.increment_progress_bar(translate('BiblesPlugin.Opensong', 'Importing %s %s...',
|
||||
'Importing <book name> <chapter>...')) % (db_book.name, chapter_number)
|
||||
self.wizard.increment_progress_bar(
|
||||
translate('BiblesPlugin.Opensong', 'Importing %s %s...',
|
||||
'Importing <book name> <chapter>...')) % (db_book.name, chapter_number)
|
||||
self.session.commit()
|
||||
self.application.process_events()
|
||||
except etree.XMLSyntaxError as inst:
|
||||
critical_error_message_box(message=translate('BiblesPlugin.OpenSongImport',
|
||||
'Incorrect Bible file type supplied. OpenSong Bibles may be '
|
||||
'compressed. You must decompress them before import.'))
|
||||
critical_error_message_box(
|
||||
message=translate('BiblesPlugin.OpenSongImport',
|
||||
'Incorrect Bible file type supplied. OpenSong Bibles may be '
|
||||
'compressed. You must decompress them before import.'))
|
||||
log.exception(inst)
|
||||
success = False
|
||||
except (IOError, AttributeError):
|
||||
|
@ -84,8 +84,8 @@ class OSISBible(BibleDB):
|
||||
success = True
|
||||
last_chapter = 0
|
||||
match_count = 0
|
||||
self.wizard.increment_progress_bar(translate('BiblesPlugin.OsisImport',
|
||||
'Detecting encoding (this may take a few minutes)...'))
|
||||
self.wizard.increment_progress_bar(
|
||||
translate('BiblesPlugin.OsisImport', 'Detecting encoding (this may take a few minutes)...'))
|
||||
try:
|
||||
detect_file = open(self.filename, 'r')
|
||||
details = chardet.detect(detect_file.read(1048576))
|
||||
@ -101,7 +101,7 @@ class OSISBible(BibleDB):
|
||||
osis = codecs.open(self.filename, 'r', details['encoding'])
|
||||
repl = replacement
|
||||
language_id = False
|
||||
# Decide if the bible propably contains only NT or AT and NT or
|
||||
# Decide if the bible probably contains only NT or AT and NT or
|
||||
# AT, NT and Apocrypha
|
||||
if lines_in_file < 11500:
|
||||
book_count = 27
|
||||
@ -154,10 +154,11 @@ class OSISBible(BibleDB):
|
||||
if last_chapter != chapter:
|
||||
if last_chapter != 0:
|
||||
self.session.commit()
|
||||
self.wizard.increment_progress_bar(translate('BiblesPlugin.OsisImport', 'Importing %s %s...',
|
||||
'Importing <book name> <chapter>...') % (book_details['name'], chapter))
|
||||
self.wizard.increment_progress_bar(
|
||||
translate('BiblesPlugin.OsisImport', 'Importing %s %s...',
|
||||
'Importing <book name> <chapter>...') % (book_details['name'], chapter))
|
||||
last_chapter = chapter
|
||||
# All of this rigmarol below is because the mod2osis tool from the Sword library embeds XML in the
|
||||
# All of this rigmarole below is because the mod2osis tool from the Sword library embeds XML in the
|
||||
# OSIS but neglects to enclose the verse text (with XML) in <[CDATA[ ]]> tags.
|
||||
verse_text = self.note_regex.sub('', verse_text)
|
||||
verse_text = self.title_regex.sub('', verse_text)
|
||||
|
@ -42,18 +42,15 @@ class VerseReferenceList(object):
|
||||
def add(self, book, chapter, verse, version, copyright, permission):
|
||||
self.add_version(version, copyright, permission)
|
||||
if not self.verse_list or self.verse_list[self.current_index]['book'] != book:
|
||||
self.verse_list.append({'version': version, 'book': book,
|
||||
'chapter': chapter, 'start': verse, 'end': verse})
|
||||
self.verse_list.append({'version': version, 'book': book, 'chapter': chapter, 'start': verse, 'end': verse})
|
||||
self.current_index += 1
|
||||
elif self.verse_list[self.current_index]['chapter'] != chapter:
|
||||
self.verse_list.append({'version': version, 'book': book,
|
||||
'chapter': chapter, 'start': verse, 'end': verse})
|
||||
self.verse_list.append({'version': version, 'book': book, 'chapter': chapter, 'start': verse, 'end': verse})
|
||||
self.current_index += 1
|
||||
elif (self.verse_list[self.current_index]['end'] + 1) == verse:
|
||||
self.verse_list[self.current_index]['end'] = verse
|
||||
else:
|
||||
self.verse_list.append({'version': version, 'book': book,
|
||||
'chapter': chapter, 'start': verse, 'end': verse})
|
||||
self.verse_list.append({'version': version, 'book': book, 'chapter': chapter, 'start': verse, 'end': verse})
|
||||
self.current_index += 1
|
||||
|
||||
def add_version(self, version, copyright, permission):
|
||||
|
@ -49,14 +49,9 @@ class AddGroupForm(QtGui.QDialog, Ui_AddGroupDialog):
|
||||
"""
|
||||
Show the form.
|
||||
|
||||
``clear``
|
||||
Set to False if the text input box should not be cleared when showing the dialog (default: True).
|
||||
|
||||
``show_top_level_group``
|
||||
Set to True when "-- Top level group --" should be showed as first item (default: False).
|
||||
|
||||
``selected_group``
|
||||
The ID of the group that should be selected by default when showing the dialog.
|
||||
:param clear: Set to False if the text input box should not be cleared when showing the dialog (default: True).
|
||||
:param show_top_level_group: Set to True when "-- Top level group --" should be showed as first item (default: False).
|
||||
:param selected_group: The ID of the group that should be selected by default when showing the dialog.
|
||||
"""
|
||||
if clear:
|
||||
self.name_edit.clear()
|
||||
@ -76,7 +71,7 @@ class AddGroupForm(QtGui.QDialog, Ui_AddGroupDialog):
|
||||
"""
|
||||
if not self.name_edit.text():
|
||||
critical_error_message_box(message=translate('ImagePlugin.AddGroupForm',
|
||||
'You need to type in a group name.'))
|
||||
'You need to type in a group name.'))
|
||||
self.name_edit.setFocus()
|
||||
return False
|
||||
else:
|
||||
|
@ -57,16 +57,16 @@ class ImagePlugin(Plugin):
|
||||
|
||||
def about(self):
|
||||
about_text = translate('ImagePlugin', '<strong>Image Plugin</strong>'
|
||||
'<br />The image plugin provides displaying of images.<br />One '
|
||||
'of the distinguishing features of this plugin is the ability to '
|
||||
'group a number of images together in the service manager, making '
|
||||
'the displaying of multiple images easier. This plugin can also '
|
||||
'make use of OpenLP\'s "timed looping" feature to create a slide '
|
||||
'show that runs automatically. In addition to this, images from '
|
||||
'the plugin can be used to override the current theme\'s '
|
||||
'background, which renders text-based items like songs with the '
|
||||
'selected image as a background instead of the background '
|
||||
'provided by the theme.')
|
||||
'<br />The image plugin provides displaying of images.<br />One '
|
||||
'of the distinguishing features of this plugin is the ability to '
|
||||
'group a number of images together in the service manager, making '
|
||||
'the displaying of multiple images easier. This plugin can also '
|
||||
'make use of OpenLP\'s "timed looping" feature to create a slide '
|
||||
'show that runs automatically. In addition to this, images from '
|
||||
'the plugin can be used to override the current theme\'s '
|
||||
'background, which renders text-based items like songs with the '
|
||||
'selected image as a background instead of the background '
|
||||
'provided by the theme.')
|
||||
return about_text
|
||||
|
||||
def app_startup(self):
|
||||
|
@ -80,17 +80,17 @@ def init_schema(url):
|
||||
|
||||
# Definition of the "image_groups" table
|
||||
image_groups_table = Table('image_groups', metadata,
|
||||
Column('id', types.Integer(), primary_key=True),
|
||||
Column('parent_id', types.Integer()),
|
||||
Column('group_name', types.Unicode(128))
|
||||
)
|
||||
Column('id', types.Integer(), primary_key=True),
|
||||
Column('parent_id', types.Integer()),
|
||||
Column('group_name', types.Unicode(128))
|
||||
)
|
||||
|
||||
# Definition of the "image_filenames" table
|
||||
image_filenames_table = Table('image_filenames', metadata,
|
||||
Column('id', types.Integer(), primary_key=True),
|
||||
Column('group_id', types.Integer(), ForeignKey('image_groups.id'), default=None),
|
||||
Column('filename', types.Unicode(255), nullable=False)
|
||||
)
|
||||
Column('id', types.Integer(), primary_key=True),
|
||||
Column('group_id', types.Integer(), ForeignKey('image_groups.id'), default=None),
|
||||
Column('filename', types.Unicode(255), nullable=False)
|
||||
)
|
||||
|
||||
mapper(ImageGroups, image_groups_table)
|
||||
mapper(ImageFilenames, image_filenames_table)
|
||||
|
@ -76,8 +76,8 @@ class ImageMediaItem(MediaManagerItem):
|
||||
self.on_new_prompt = translate('ImagePlugin.MediaItem', 'Select Image(s)')
|
||||
file_formats = get_images_filter()
|
||||
self.on_new_file_masks = '%s;;%s (*.*) (*)' % (file_formats, UiStrings().AllFiles)
|
||||
self.addGroupAction.setText(UiStrings().AddGroup)
|
||||
self.addGroupAction.setToolTip(UiStrings().AddGroup)
|
||||
self.add_group_action.setText(UiStrings().AddGroup)
|
||||
self.add_group_action.setToolTip(UiStrings().AddGroup)
|
||||
self.replace_action.setText(UiStrings().ReplaceBG)
|
||||
self.replace_action.setToolTip(UiStrings().ReplaceLiveBG)
|
||||
self.reset_action.setText(UiStrings().ResetBG)
|
||||
@ -120,31 +120,36 @@ class ImageMediaItem(MediaManagerItem):
|
||||
# define and add the context menu
|
||||
self.list_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||
if self.has_edit_icon:
|
||||
create_widget_action(self.list_view,
|
||||
create_widget_action(
|
||||
self.list_view,
|
||||
text=self.plugin.get_string(StringContent.Edit)['title'],
|
||||
icon=':/general/general_edit.png',
|
||||
triggers=self.on_edit_click)
|
||||
create_widget_action(self.list_view, separator=True)
|
||||
if self.has_delete_icon:
|
||||
create_widget_action(self.list_view,
|
||||
create_widget_action(
|
||||
self.list_view,
|
||||
'listView%s%sItem' % (self.plugin.name.title(), StringContent.Delete.title()),
|
||||
text=self.plugin.get_string(StringContent.Delete)['title'],
|
||||
icon=':/general/general_delete.png',
|
||||
can_shortcuts=True, triggers=self.on_delete_click)
|
||||
create_widget_action(self.list_view, separator=True)
|
||||
create_widget_action(self.list_view,
|
||||
create_widget_action(
|
||||
self.list_view,
|
||||
'listView%s%sItem' % (self.plugin.name.title(), StringContent.Preview.title()),
|
||||
text=self.plugin.get_string(StringContent.Preview)['title'],
|
||||
icon=':/general/general_preview.png',
|
||||
can_shortcuts=True,
|
||||
triggers=self.on_preview_click)
|
||||
create_widget_action(self.list_view,
|
||||
create_widget_action(
|
||||
self.list_view,
|
||||
'listView%s%sItem' % (self.plugin.name.title(), StringContent.Live.title()),
|
||||
text=self.plugin.get_string(StringContent.Live)['title'],
|
||||
icon=':/general/general_live.png',
|
||||
can_shortcuts=True,
|
||||
triggers=self.on_live_click)
|
||||
create_widget_action(self.list_view,
|
||||
create_widget_action(
|
||||
self.list_view,
|
||||
'listView%s%sItem' % (self.plugin.name.title(), StringContent.Service.title()),
|
||||
can_shortcuts=True,
|
||||
text=self.plugin.get_string(StringContent.Service)['title'],
|
||||
@ -152,7 +157,8 @@ class ImageMediaItem(MediaManagerItem):
|
||||
triggers=self.on_add_click)
|
||||
if self.add_to_service_item:
|
||||
create_widget_action(self.list_view, separator=True)
|
||||
create_widget_action(self.list_view,
|
||||
create_widget_action(
|
||||
self.list_view,
|
||||
text=translate('OpenLP.MediaManagerItem', '&Add to selected Service Item'),
|
||||
icon=':/general/general_add.png',
|
||||
triggers=self.on_add_edit_click)
|
||||
@ -170,9 +176,11 @@ class ImageMediaItem(MediaManagerItem):
|
||||
Add custom actions to the context menu.
|
||||
"""
|
||||
create_widget_action(self.list_view, separator=True)
|
||||
create_widget_action(self.list_view,
|
||||
create_widget_action(
|
||||
self.list_view,
|
||||
text=UiStrings().AddGroup, icon=':/images/image_new_group.png', triggers=self.on_add_group_click)
|
||||
create_widget_action(self.list_view,
|
||||
create_widget_action(
|
||||
self.list_view,
|
||||
text=self.plugin.get_string(StringContent.Load)['tooltip'],
|
||||
icon=':/general/general_open.png', triggers=self.on_file_click)
|
||||
|
||||
@ -180,24 +188,26 @@ class ImageMediaItem(MediaManagerItem):
|
||||
"""
|
||||
Add custom buttons to the start of the toolbar.
|
||||
"""
|
||||
self.addGroupAction = self.toolbar.add_toolbar_action('addGroupAction',
|
||||
icon=':/images/image_new_group.png', triggers=self.on_add_group_click)
|
||||
self.add_group_action = self.toolbar.add_toolbar_action('add_group_action',
|
||||
icon=':/images/image_new_group.png',
|
||||
triggers=self.on_add_group_click)
|
||||
|
||||
def add_end_header_bar(self):
|
||||
"""
|
||||
Add custom buttons to the end of the toolbar
|
||||
"""
|
||||
self.replace_action = self.toolbar.add_toolbar_action('replace_action',
|
||||
icon=':/slides/slide_blank.png', triggers=self.on_replace_click)
|
||||
icon=':/slides/slide_blank.png',
|
||||
triggers=self.on_replace_click)
|
||||
self.reset_action = self.toolbar.add_toolbar_action('reset_action',
|
||||
icon=':/system/system_close.png', visible=False, triggers=self.on_reset_click)
|
||||
icon=':/system/system_close.png',
|
||||
visible=False, triggers=self.on_reset_click)
|
||||
|
||||
def recursively_delete_group(self, image_group):
|
||||
"""
|
||||
Recursively deletes a group and all groups and images in it.
|
||||
|
||||
``image_group``
|
||||
The ImageGroups instance of the group that will be deleted.
|
||||
:param image_group: The ImageGroups instance of the group that will be deleted.
|
||||
"""
|
||||
images = self.manager.get_all_objects(ImageFilenames, ImageFilenames.group_id == image_group.id)
|
||||
for image in images:
|
||||
@ -215,7 +225,7 @@ class ImageMediaItem(MediaManagerItem):
|
||||
# Turn off auto preview triggers.
|
||||
self.list_view.blockSignals(True)
|
||||
if check_item_selected(self.list_view, translate('ImagePlugin.MediaItem',
|
||||
'You must select an image or group to delete.')):
|
||||
'You must select an image or group to delete.')):
|
||||
item_list = self.list_view.selectedItems()
|
||||
self.application.set_busy_cursor()
|
||||
self.main_window.display_progress_bar(len(item_list))
|
||||
@ -230,12 +240,14 @@ class ImageMediaItem(MediaManagerItem):
|
||||
row_item.parent().removeChild(row_item)
|
||||
self.manager.delete_object(ImageFilenames, row_item.data(0, QtCore.Qt.UserRole).id)
|
||||
elif isinstance(item_data, ImageGroups):
|
||||
if QtGui.QMessageBox.question(self.list_view.parent(),
|
||||
if QtGui.QMessageBox.question(
|
||||
self.list_view.parent(),
|
||||
translate('ImagePlugin.MediaItem', 'Remove group'),
|
||||
translate('ImagePlugin.MediaItem',
|
||||
'Are you sure you want to remove "%s" and everything in it?') % item_data.group_name,
|
||||
'Are you sure you want to remove "%s" and everything in it?') %
|
||||
item_data.group_name,
|
||||
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Yes |
|
||||
QtGui.QMessageBox.No)) == QtGui.QMessageBox.Yes:
|
||||
QtGui.QMessageBox.No)) == QtGui.QMessageBox.Yes:
|
||||
self.recursively_delete_group(item_data)
|
||||
self.manager.delete_object(ImageGroups, row_item.data(0, QtCore.Qt.UserRole).id)
|
||||
if item_data.parent_id == 0:
|
||||
@ -253,11 +265,8 @@ class ImageMediaItem(MediaManagerItem):
|
||||
"""
|
||||
Recursively add subgroups to the given parent group in a QTreeWidget.
|
||||
|
||||
``group_list``
|
||||
The List object that contains all QTreeWidgetItems.
|
||||
|
||||
``parent_group_id``
|
||||
The ID of the group that will be added recursively.
|
||||
:param group_list: The List object that contains all QTreeWidgetItems.
|
||||
:param parent_group_id: The ID of the group that will be added recursively.
|
||||
"""
|
||||
image_groups = self.manager.get_all_objects(ImageGroups, ImageGroups.parent_id == parent_group_id)
|
||||
image_groups.sort(key=lambda group_object: get_locale_key(group_object.group_name))
|
||||
@ -278,14 +287,10 @@ class ImageMediaItem(MediaManagerItem):
|
||||
"""
|
||||
Recursively add groups to the combobox in the 'Add group' dialog.
|
||||
|
||||
``combobox``
|
||||
The QComboBox to add the options to.
|
||||
|
||||
``parent_group_id``
|
||||
The ID of the group that will be added.
|
||||
|
||||
``prefix``
|
||||
A string containing the prefix that will be added in front of the groupname for each level of the tree.
|
||||
:param combobox: The QComboBox to add the options to.
|
||||
:param parent_group_id: The ID of the group that will be added.
|
||||
:param prefix: A string containing the prefix that will be added in front of the groupname for each level of
|
||||
the tree.
|
||||
"""
|
||||
if parent_group_id == 0:
|
||||
combobox.clear()
|
||||
@ -300,11 +305,8 @@ class ImageMediaItem(MediaManagerItem):
|
||||
"""
|
||||
Expand groups in the widget recursively.
|
||||
|
||||
``group_id``
|
||||
The ID of the group that will be expanded.
|
||||
|
||||
``root_item``
|
||||
This option is only used for recursion purposes.
|
||||
:param group_id: The ID of the group that will be expanded.
|
||||
:param root_item: This option is only used for recursion purposes.
|
||||
"""
|
||||
return_value = False
|
||||
if root_item is None:
|
||||
@ -323,14 +325,9 @@ class ImageMediaItem(MediaManagerItem):
|
||||
"""
|
||||
Replace the list of images and groups in the interface.
|
||||
|
||||
``images``
|
||||
A List of ImageFilenames objects that will be used to reload the mediamanager list.
|
||||
|
||||
``initial_load``
|
||||
When set to False, the busy cursor and progressbar will be shown while loading images.
|
||||
|
||||
``open_group``
|
||||
ImageGroups object of the group that must be expanded after reloading the list in the interface.
|
||||
:param images: A List of Image Filenames objects that will be used to reload the mediamanager list.
|
||||
:param initial_load: When set to False, the busy cursor and progressbar will be shown while loading images.
|
||||
:param open_group: ImageGroups object of the group that must be expanded after reloading the list in the interface.
|
||||
"""
|
||||
if not initial_load:
|
||||
self.application.set_busy_cursor()
|
||||
@ -375,11 +372,8 @@ class ImageMediaItem(MediaManagerItem):
|
||||
Process a list for files either from the File Dialog or from Drag and Drop.
|
||||
This method is overloaded from MediaManagerItem.
|
||||
|
||||
``files``
|
||||
A List of strings containing the filenames of the files to be loaded
|
||||
|
||||
``target_group``
|
||||
The QTreeWidgetItem of the group that will be the parent of the added files
|
||||
:param files: A List of strings containing the filenames of the files to be loaded
|
||||
:param target_group: The QTreeWidgetItem of the group that will be the parent of the added files
|
||||
"""
|
||||
self.application.set_normal_cursor()
|
||||
self.load_list(files, target_group)
|
||||
@ -390,14 +384,9 @@ class ImageMediaItem(MediaManagerItem):
|
||||
"""
|
||||
Add new images to the database. This method is called when adding images using the Add button or DnD.
|
||||
|
||||
``images``
|
||||
A List of strings containing the filenames of the files to be loaded
|
||||
|
||||
``target_group``
|
||||
The QTreeWidgetItem of the group that will be the parent of the added files
|
||||
|
||||
``initial_load``
|
||||
When set to False, the busy cursor and progressbar will be shown while loading images
|
||||
:param images: A List of strings containing the filenames of the files to be loaded
|
||||
:param target_group: The QTreeWidgetItem of the group that will be the parent of the added files
|
||||
:param initial_load: When set to False, the busy cursor and progressbar will be shown while loading images
|
||||
"""
|
||||
parent_group = None
|
||||
if target_group is None:
|
||||
@ -464,30 +453,25 @@ class ImageMediaItem(MediaManagerItem):
|
||||
# Save the new images in the database
|
||||
self.save_new_images_list(images, group_id=parent_group.id, reload_list=False)
|
||||
self.load_full_list(self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.filename),
|
||||
initial_load=initial_load, open_group=parent_group)
|
||||
initial_load=initial_load, open_group=parent_group)
|
||||
self.application.set_normal_cursor()
|
||||
|
||||
def save_new_images_list(self, images_list, group_id=0, reload_list=True):
|
||||
"""
|
||||
Convert a list of image filenames to ImageFilenames objects and save them in the database.
|
||||
|
||||
``images_list``
|
||||
A List of strings containing image filenames
|
||||
|
||||
``group_id``
|
||||
The ID of the group to save the images in
|
||||
|
||||
``reload_list``
|
||||
This boolean is set to True when the list in the interface should be reloaded after saving the new images
|
||||
:param images_list: A List of strings containing image filenames
|
||||
:param group_id: The ID of the group to save the images in
|
||||
:param reload_list: This boolean is set to True when the list in the interface should be reloaded after saving the new images
|
||||
"""
|
||||
for filename in images_list:
|
||||
if not isinstance(filename, str):
|
||||
continue
|
||||
log.debug('Adding new image: %s', filename)
|
||||
imageFile = ImageFilenames()
|
||||
imageFile.group_id = group_id
|
||||
imageFile.filename = str(filename)
|
||||
self.manager.save_object(imageFile)
|
||||
image_file = ImageFilenames()
|
||||
image_file.group_id = group_id
|
||||
image_file.filename = str(filename)
|
||||
self.manager.save_object(image_file)
|
||||
self.main_window.increment_progress_bar()
|
||||
if reload_list and images_list:
|
||||
self.load_full_list(self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.filename))
|
||||
@ -496,8 +480,7 @@ class ImageMediaItem(MediaManagerItem):
|
||||
"""
|
||||
Handle drag-and-drop moving of images within the media manager
|
||||
|
||||
``target``
|
||||
This contains the QTreeWidget that is the target of the DnD action
|
||||
:param target: This contains the QTreeWidget that is the target of the DnD action
|
||||
"""
|
||||
items_to_move = self.list_view.selectedItems()
|
||||
# Determine group to move images to
|
||||
@ -538,10 +521,16 @@ class ImageMediaItem(MediaManagerItem):
|
||||
image_items.sort(key=lambda item: get_locale_key(item.text(0)))
|
||||
target_group.addChildren(image_items)
|
||||
|
||||
def generate_slide_data(self, service_item, item=None, xml_version=False,
|
||||
remote=False, context=ServiceItemContext.Service):
|
||||
def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False,
|
||||
context=ServiceItemContext.Service):
|
||||
"""
|
||||
Generate the slide data. Needs to be implemented by the plugin.
|
||||
|
||||
:param service_item: The service item to be built on
|
||||
:param item: The Song item to be used
|
||||
:param xml_version: The xml version (not used)
|
||||
:param remote: Triggered from remote
|
||||
:param context: Why is it being generated
|
||||
"""
|
||||
background = QtGui.QColor(Settings().value(self.settings_section + '/background color'))
|
||||
if item:
|
||||
@ -561,40 +550,41 @@ class ImageMediaItem(MediaManagerItem):
|
||||
service_item.add_capability(ItemCapabilities.CanAppend)
|
||||
# force a nonexistent theme
|
||||
service_item.theme = -1
|
||||
missing_items_filenames = []
|
||||
images_filenames = []
|
||||
missing_items_file_names = []
|
||||
images_file_names = []
|
||||
# Expand groups to images
|
||||
for bitem in items:
|
||||
if isinstance(bitem.data(0, QtCore.Qt.UserRole), ImageGroups) or bitem.data(0, QtCore.Qt.UserRole) is None:
|
||||
for index in range(0, bitem.childCount()):
|
||||
if isinstance(bitem.child(index).data(0, QtCore.Qt.UserRole), ImageFilenames):
|
||||
images_filenames.append(bitem.child(index).data(0, QtCore.Qt.UserRole).filename)
|
||||
images_file_names.append(bitem.child(index).data(0, QtCore.Qt.UserRole).filename)
|
||||
elif isinstance(bitem.data(0, QtCore.Qt.UserRole), ImageFilenames):
|
||||
images_filenames.append(bitem.data(0, QtCore.Qt.UserRole).filename)
|
||||
images_file_names.append(bitem.data(0, QtCore.Qt.UserRole).filename)
|
||||
# Don't try to display empty groups
|
||||
if not images_filenames:
|
||||
if not images_file_names:
|
||||
return False
|
||||
# Find missing files
|
||||
for filename in images_filenames:
|
||||
for filename in images_file_names:
|
||||
if not os.path.exists(filename):
|
||||
missing_items_filenames.append(filename)
|
||||
missing_items_file_names.append(filename)
|
||||
# We cannot continue, as all images do not exist.
|
||||
if not images_filenames:
|
||||
if not images_file_names:
|
||||
if not remote:
|
||||
critical_error_message_box(
|
||||
translate('ImagePlugin.MediaItem', 'Missing Image(s)'),
|
||||
translate('ImagePlugin.MediaItem', 'The following image(s) no longer exist: %s') %
|
||||
'\n'.join(missing_items_filenames))
|
||||
translate('ImagePlugin.MediaItem', 'The following image(s) no longer exist: %s')
|
||||
% '\n'.join(missing_items_file_names))
|
||||
return False
|
||||
# We have missing as well as existing images. We ask what to do.
|
||||
elif missing_items_filenames and QtGui.QMessageBox.question(self,
|
||||
translate('ImagePlugin.MediaItem', 'Missing Image(s)'),
|
||||
translate('ImagePlugin.MediaItem', 'The following image(s) no longer exist: %s\n'
|
||||
'Do you want to add the other images anyway?') % '\n'.join(missing_items_filenames),
|
||||
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.No | QtGui.QMessageBox.Yes)) == QtGui.QMessageBox.No:
|
||||
elif missing_items_file_names and QtGui.QMessageBox.question(
|
||||
self, translate('ImagePlugin.MediaItem', 'Missing Image(s)'),
|
||||
translate('ImagePlugin.MediaItem', 'The following image(s) no longer exist: %s\n'
|
||||
'Do you want to add the other images anyway?') % '\n'.join(missing_items_file_names),
|
||||
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.No | QtGui.QMessageBox.Yes)) == \
|
||||
QtGui.QMessageBox.No:
|
||||
return False
|
||||
# Continue with the existing images.
|
||||
for filename in images_filenames:
|
||||
for filename in images_file_names:
|
||||
name = os.path.split(filename)[1]
|
||||
service_item.add_from_image(filename, name, background)
|
||||
return True
|
||||
@ -603,8 +593,7 @@ class ImageMediaItem(MediaManagerItem):
|
||||
"""
|
||||
Returns *True* if the given Group already exists in the database, otherwise *False*.
|
||||
|
||||
``new_group``
|
||||
The ImageGroups object that contains the name of the group that will be checked
|
||||
:param new_group: The ImageGroups object that contains the name of the group that will be checked
|
||||
"""
|
||||
groups = self.manager.get_all_objects(ImageGroups, ImageGroups.group_name == new_group.group_name)
|
||||
if groups:
|
||||
@ -633,8 +622,8 @@ class ImageMediaItem(MediaManagerItem):
|
||||
group_name=self.add_group_form.name_edit.text())
|
||||
if not self.check_group_exists(new_group):
|
||||
if self.manager.save_object(new_group):
|
||||
self.load_full_list(self.manager.get_all_objects(ImageFilenames,
|
||||
order_by_ref=ImageFilenames.filename))
|
||||
self.load_full_list(self.manager.get_all_objects(
|
||||
ImageFilenames, order_by_ref=ImageFilenames.filename))
|
||||
self.expand_group(new_group.id)
|
||||
self.fill_groups_combobox(self.choose_group_form.group_combobox)
|
||||
self.fill_groups_combobox(self.add_group_form.parent_group_combobox)
|
||||
@ -659,9 +648,10 @@ class ImageMediaItem(MediaManagerItem):
|
||||
|
||||
def on_replace_click(self):
|
||||
"""
|
||||
Called to replace Live backgound with the image selected.
|
||||
Called to replace Live background with the image selected.
|
||||
"""
|
||||
if check_item_selected(self.list_view,
|
||||
if check_item_selected(
|
||||
self.list_view,
|
||||
translate('ImagePlugin.MediaItem', 'You must select an image to replace the background with.')):
|
||||
background = QtGui.QColor(Settings().value(self.settings_section + '/background color'))
|
||||
bitem = self.list_view.selectedItems()[0]
|
||||
@ -673,24 +663,24 @@ class ImageMediaItem(MediaManagerItem):
|
||||
if self.live_controller.display.direct_image(filename, background):
|
||||
self.reset_action.setVisible(True)
|
||||
else:
|
||||
critical_error_message_box(UiStrings().LiveBGError,
|
||||
critical_error_message_box(
|
||||
UiStrings().LiveBGError,
|
||||
translate('ImagePlugin.MediaItem', 'There was no display item to amend.'))
|
||||
else:
|
||||
critical_error_message_box(UiStrings().LiveBGError,
|
||||
critical_error_message_box(
|
||||
UiStrings().LiveBGError,
|
||||
translate('ImagePlugin.MediaItem', 'There was a problem replacing your background, '
|
||||
'the image file "%s" no longer exists.') % filename)
|
||||
'the image file "%s" no longer exists.') % filename)
|
||||
|
||||
def search(self, string, show_error=True):
|
||||
"""
|
||||
Perform a search on the image file names.
|
||||
|
||||
``string``
|
||||
The glob to search for
|
||||
|
||||
``show_error``
|
||||
Unused.
|
||||
:param string: The glob to search for
|
||||
:param show_error: Unused.
|
||||
"""
|
||||
files = self.manager.get_all_objects(ImageFilenames, filter_clause=ImageFilenames.filename.contains(string),
|
||||
files = self.manager.get_all_objects(
|
||||
ImageFilenames, filter_clause=ImageFilenames.filename.contains(string),
|
||||
order_by_ref=ImageFilenames.filename)
|
||||
results = []
|
||||
for file_object in files:
|
||||
|
@ -131,6 +131,11 @@ class MediaMediaItem(MediaManagerItem):
|
||||
self.display_type_combo_box.currentIndexChanged.connect(self.override_player_changed)
|
||||
|
||||
def override_player_changed(self, index):
|
||||
"""
|
||||
The Player has been overridden
|
||||
|
||||
:param index: Index
|
||||
"""
|
||||
player = get_media_players()[0]
|
||||
if index == 0:
|
||||
set_media_players(player)
|
||||
@ -178,9 +183,15 @@ class MediaMediaItem(MediaManagerItem):
|
||||
'the media file "%s" no longer exists.') % filename)
|
||||
|
||||
def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False,
|
||||
context=ServiceItemContext.Live):
|
||||
context=ServiceItemContext.Service):
|
||||
"""
|
||||
Generate the slide data. Needs to be implemented by the plugin.
|
||||
|
||||
:param service_item: The service item to be built on
|
||||
:param item: The Song item to be used
|
||||
:param xml_version: The xml version (not used)
|
||||
:param remote: Triggered from remote
|
||||
:param context: Why is it being generated
|
||||
"""
|
||||
if item is None:
|
||||
item = self.list_view.currentItem()
|
||||
@ -269,6 +280,12 @@ class MediaMediaItem(MediaManagerItem):
|
||||
|
||||
def load_list(self, media, target_group=None):
|
||||
# Sort the media by its filename considering language specific characters.
|
||||
"""
|
||||
Load the media list
|
||||
|
||||
:param media: The media
|
||||
:param target_group:
|
||||
"""
|
||||
media.sort(key=lambda file_name: get_locale_key(os.path.split(str(file_name))[1]))
|
||||
for track in media:
|
||||
track_info = QtCore.QFileInfo(track)
|
||||
|
@ -33,14 +33,6 @@ from openlp.core.common import Settings, UiStrings, translate
|
||||
from openlp.core.lib import SettingsTab
|
||||
|
||||
|
||||
class MediaQ_check_box(QtGui.QCheckBox):
|
||||
"""
|
||||
MediaQ_check_box adds an extra property, playerName to the Q_check_box class.
|
||||
"""
|
||||
def setPlayerName(self, name):
|
||||
self.playerName = name
|
||||
|
||||
|
||||
class MediaTab(SettingsTab):
|
||||
"""
|
||||
MediaTab is the Media settings tab in the settings dialog.
|
||||
|
@ -66,7 +66,7 @@ class MediaPlugin(Plugin):
|
||||
|
||||
def about(self):
|
||||
about_text = translate('MediaPlugin', '<strong>Media Plugin</strong>'
|
||||
'<br />The media plugin provides playback of audio and video.')
|
||||
'<br />The media plugin provides playback of audio and video.')
|
||||
return about_text
|
||||
|
||||
def set_plugin_text_strings(self):
|
||||
|
@ -52,6 +52,7 @@ else:
|
||||
import uno
|
||||
from com.sun.star.beans import PropertyValue
|
||||
from com.sun.star.task import ErrorCodeIOException
|
||||
|
||||
uno_available = True
|
||||
except ImportError:
|
||||
uno_available = False
|
||||
@ -183,9 +184,9 @@ class ImpressController(PresentationController):
|
||||
docs = desktop.getComponents()
|
||||
cnt = 0
|
||||
if docs.hasElements():
|
||||
list = docs.createEnumeration()
|
||||
while list.hasMoreElements():
|
||||
doc = list.nextElement()
|
||||
list_elements = docs.createEnumeration()
|
||||
while list_elements.hasMoreElements():
|
||||
doc = list_elements.nextElement()
|
||||
if doc.getImplementationName() != 'com.sun.star.comp.framework.BackingComp':
|
||||
cnt += 1
|
||||
if cnt > 0:
|
||||
@ -203,7 +204,7 @@ class ImpressDocument(PresentationDocument):
|
||||
Class which holds information and controls a single presentation.
|
||||
"""
|
||||
|
||||
def __init__(self, controller, presentation):
|
||||
def __init__ (self, controller, presentation):
|
||||
"""
|
||||
Constructor, store information about the file and initialise.
|
||||
"""
|
||||
@ -225,10 +226,10 @@ class ImpressDocument(PresentationDocument):
|
||||
if desktop is None:
|
||||
self.controller.start_process()
|
||||
desktop = self.controller.get_com_desktop()
|
||||
url = 'file:///' + self.filepath.replace('\\', '/').replace(':', '|').replace(' ', '%20')
|
||||
url = 'file:///' + self.file_path.replace('\\', '/').replace(':', '|').replace(' ', '%20')
|
||||
else:
|
||||
desktop = self.controller.get_uno_desktop()
|
||||
url = uno.systemPathToFileUrl(self.filepath)
|
||||
url = uno.systemPathToFileUrl(self.file_path)
|
||||
if desktop is None:
|
||||
return False
|
||||
self.desktop = desktop
|
||||
@ -352,7 +353,7 @@ class ImpressDocument(PresentationDocument):
|
||||
log.debug('unblank screen OpenOffice')
|
||||
return self.control.resume()
|
||||
|
||||
def blank_screen(self):
|
||||
def blank_screen (self):
|
||||
"""
|
||||
Blanks the screen.
|
||||
"""
|
||||
@ -409,11 +410,13 @@ class ImpressDocument(PresentationDocument):
|
||||
"""
|
||||
return self.document.getDrawPages().getCount()
|
||||
|
||||
def goto_slide(self, slideno):
|
||||
def goto_slide(self, slide_no):
|
||||
"""
|
||||
Go to a specific slide (from 1).
|
||||
|
||||
:param slide_no: The slide the text is required for, starting at 1
|
||||
"""
|
||||
self.control.gotoSlideIndex(slideno-1)
|
||||
self.control.gotoSlideIndex(slide_no - 1)
|
||||
|
||||
def next_step(self):
|
||||
"""
|
||||
@ -435,8 +438,7 @@ class ImpressDocument(PresentationDocument):
|
||||
"""
|
||||
Returns the text on the slide.
|
||||
|
||||
``slide_no``
|
||||
The slide the text is required for, starting at 1
|
||||
:param slide_no: The slide the text is required for, starting at 1
|
||||
"""
|
||||
return self.__get_text_from_page(slide_no)
|
||||
|
||||
@ -444,8 +446,7 @@ class ImpressDocument(PresentationDocument):
|
||||
"""
|
||||
Returns the text in the slide notes.
|
||||
|
||||
``slide_no``
|
||||
The slide the notes are required for, starting at 1
|
||||
:param slide_no: The slide the notes are required for, starting at 1
|
||||
"""
|
||||
return self.__get_text_from_page(slide_no, True)
|
||||
|
||||
@ -453,8 +454,8 @@ class ImpressDocument(PresentationDocument):
|
||||
"""
|
||||
Return any text extracted from the presentation page.
|
||||
|
||||
``notes``
|
||||
A boolean. If set the method searches the notes of the slide.
|
||||
:param slide_no: The slide the notes are required for, starting at 1
|
||||
:param notes: A boolean. If set the method searches the notes of the slide.
|
||||
"""
|
||||
text = ''
|
||||
pages = self.document.getDrawPages()
|
||||
|
@ -183,7 +183,7 @@ class PresentationMediaItem(MediaManagerItem):
|
||||
translate('PresentationPlugin.MediaItem',
|
||||
'A presentation with that filename already exists.'))
|
||||
continue
|
||||
controller_name = self.findControllerByType(filename)
|
||||
controller_name = self.find_controller_by_type(filename)
|
||||
if controller_name:
|
||||
controller = self.controllers[controller_name]
|
||||
doc = controller.add_document(file)
|
||||
@ -240,11 +240,16 @@ class PresentationMediaItem(MediaManagerItem):
|
||||
self.list_view.takeItem(row)
|
||||
Settings().setValue(self.settings_section + '/presentations files', self.get_file_list())
|
||||
|
||||
def generate_slide_data(self, service_item, item=None, xml_version=False,
|
||||
remote=False, context=ServiceItemContext.Service, presentation_file=None):
|
||||
def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False,
|
||||
context=ServiceItemContext.Service, presentation_file=None):
|
||||
"""
|
||||
Load the relevant information for displaying the presentation in the slidecontroller. In the case of
|
||||
powerpoints, an image for each slide.
|
||||
Generate the slide data. Needs to be implemented by the plugin.
|
||||
|
||||
:param service_item: The service item to be built on
|
||||
:param item: The Song item to be used
|
||||
:param xml_version: The xml version (not used)
|
||||
:param remote: Triggered from remote
|
||||
:param context: Why is it being generated
|
||||
"""
|
||||
if item:
|
||||
items = [item]
|
||||
@ -272,7 +277,7 @@ class PresentationMediaItem(MediaManagerItem):
|
||||
(path, name) = os.path.split(filename)
|
||||
service_item.title = name
|
||||
if os.path.exists(filename):
|
||||
processor = self.findControllerByType(filename)
|
||||
processor = self.find_controller_by_type(filename)
|
||||
if not processor:
|
||||
return False
|
||||
controller = self.controllers[processor]
|
||||
@ -282,13 +287,13 @@ class PresentationMediaItem(MediaManagerItem):
|
||||
os.path.join(doc.get_temp_folder(), 'mainslide001.png')):
|
||||
doc.load_presentation()
|
||||
i = 1
|
||||
imagefile = 'mainslide%03d.png' % i
|
||||
image = os.path.join(doc.get_temp_folder(), imagefile)
|
||||
image_file = 'mainslide%03d.png' % i
|
||||
image = os.path.join(doc.get_temp_folder(), image_file)
|
||||
while os.path.isfile(image):
|
||||
service_item.add_from_image(image, name)
|
||||
i += 1
|
||||
imagefile = 'mainslide%03d.png' % i
|
||||
image = os.path.join(doc.get_temp_folder(), imagefile)
|
||||
image_file = 'mainslide%03d.png' % i
|
||||
image = os.path.join(doc.get_temp_folder(), image_file)
|
||||
doc.close_presentation()
|
||||
return True
|
||||
else:
|
||||
@ -307,7 +312,7 @@ class PresentationMediaItem(MediaManagerItem):
|
||||
service_item.title = name
|
||||
if os.path.exists(filename):
|
||||
if service_item.processor == self.automatic:
|
||||
service_item.processor = self.findControllerByType(filename)
|
||||
service_item.processor = self.find_controller_by_type(filename)
|
||||
if not service_item.processor:
|
||||
return False
|
||||
controller = self.controllers[service_item.processor]
|
||||
@ -340,11 +345,13 @@ class PresentationMediaItem(MediaManagerItem):
|
||||
'The presentation %s no longer exists.') % filename)
|
||||
return False
|
||||
|
||||
def findControllerByType(self, filename):
|
||||
def find_controller_by_type(self, filename):
|
||||
"""
|
||||
Determine the default application controller to use for the selected file type. This is used if "Automatic" is
|
||||
set as the preferred controller. Find the first (alphabetic) enabled controller which "supports" the extension.
|
||||
If none found, then look for a controller which "also supports" it instead.
|
||||
|
||||
:param filename: The file name
|
||||
"""
|
||||
file_type = os.path.splitext(filename)[1][1:]
|
||||
if not file_type:
|
||||
@ -360,6 +367,13 @@ class PresentationMediaItem(MediaManagerItem):
|
||||
return None
|
||||
|
||||
def search(self, string, show_error):
|
||||
"""
|
||||
Search in files
|
||||
|
||||
:param string: name to be found
|
||||
:param show_error: not used
|
||||
:return:
|
||||
"""
|
||||
files = Settings().value(self.settings_section + '/presentations files')
|
||||
results = []
|
||||
string = string.lower()
|
||||
|
@ -71,7 +71,7 @@ class Controller(object):
|
||||
return
|
||||
self.doc.slidenumber = slide_no
|
||||
self.hide_mode = hide_mode
|
||||
log.debug('add_handler, slidenumber: %d' % slide_no)
|
||||
log.debug('add_handler, slide_number: %d' % slide_no)
|
||||
if self.is_live:
|
||||
if hide_mode == HideMode.Screen:
|
||||
Registry().execute('live_display_hide', HideMode.Screen)
|
||||
@ -342,7 +342,7 @@ class MessageListener(object):
|
||||
# so handler & processor is set to None, and we skip adding the handler.
|
||||
self.handler = None
|
||||
if self.handler == self.media_item.automatic:
|
||||
self.handler = self.media_item.findControllerByType(file)
|
||||
self.handler = self.media_item.find_controller_by_type(file)
|
||||
if not self.handler:
|
||||
return
|
||||
if is_live:
|
||||
@ -359,6 +359,8 @@ class MessageListener(object):
|
||||
def slide(self, message):
|
||||
"""
|
||||
React to the message to move to a specific slide.
|
||||
|
||||
:param message: The message {1} is_live {2} slide
|
||||
"""
|
||||
is_live = message[1]
|
||||
slide = message[2]
|
||||
@ -370,6 +372,8 @@ class MessageListener(object):
|
||||
def first(self, message):
|
||||
"""
|
||||
React to the message to move to the first slide.
|
||||
|
||||
:param message: The message {1} is_live
|
||||
"""
|
||||
is_live = message[1]
|
||||
if is_live:
|
||||
@ -380,6 +384,8 @@ class MessageListener(object):
|
||||
def last(self, message):
|
||||
"""
|
||||
React to the message to move to the last slide.
|
||||
|
||||
:param message: The message {1} is_live
|
||||
"""
|
||||
is_live = message[1]
|
||||
if is_live:
|
||||
@ -390,6 +396,8 @@ class MessageListener(object):
|
||||
def next(self, message):
|
||||
"""
|
||||
React to the message to move to the next animation/slide.
|
||||
|
||||
:param message: The message {1} is_live
|
||||
"""
|
||||
is_live = message[1]
|
||||
if is_live:
|
||||
@ -400,6 +408,8 @@ class MessageListener(object):
|
||||
def previous(self, message):
|
||||
"""
|
||||
React to the message to move to the previous animation/slide.
|
||||
|
||||
:param message: The message {1} is_live
|
||||
"""
|
||||
is_live = message[1]
|
||||
if is_live:
|
||||
@ -410,6 +420,8 @@ class MessageListener(object):
|
||||
def shutdown(self, message):
|
||||
"""
|
||||
React to message to shutdown the presentation. I.e. end the show and close the file.
|
||||
|
||||
:param message: The message {1} is_live
|
||||
"""
|
||||
is_live = message[1]
|
||||
if is_live:
|
||||
@ -420,6 +432,8 @@ class MessageListener(object):
|
||||
def hide(self, message):
|
||||
"""
|
||||
React to the message to show the desktop.
|
||||
|
||||
:param message: The message {1} is_live
|
||||
"""
|
||||
is_live = message[1]
|
||||
if is_live:
|
||||
@ -428,6 +442,8 @@ class MessageListener(object):
|
||||
def blank(self, message):
|
||||
"""
|
||||
React to the message to blank the display.
|
||||
|
||||
:param message: The message {1} is_live {2} slide
|
||||
"""
|
||||
is_live = message[1]
|
||||
hide_mode = message[2]
|
||||
@ -437,6 +453,8 @@ class MessageListener(object):
|
||||
def unblank(self, message):
|
||||
"""
|
||||
React to the message to unblank the display.
|
||||
|
||||
:param message: The message {1} is_live
|
||||
"""
|
||||
is_live = message[1]
|
||||
if is_live:
|
||||
|
@ -113,7 +113,7 @@ class PdfController(PresentationController):
|
||||
self.gsbin = ''
|
||||
self.also_supports = []
|
||||
# Use the user defined program if given
|
||||
if (Settings().value('presentations/enable_pdf_program')):
|
||||
if Settings().value('presentations/enable_pdf_program'):
|
||||
pdf_program = Settings().value('presentations/pdf_program')
|
||||
program_type = self.check_binary(pdf_program)
|
||||
if program_type == 'gs':
|
||||
@ -197,7 +197,7 @@ class PdfDocument(PresentationDocument):
|
||||
runlog = []
|
||||
try:
|
||||
runlog = check_output([self.controller.gsbin, '-dNOPAUSE', '-dNODISPLAY', '-dBATCH',
|
||||
'-sFile=' + self.filepath, gs_resolution_script])
|
||||
'-sFile=' + self.file_path, gs_resolution_script])
|
||||
except CalledProcessError as e:
|
||||
log.debug(' '.join(e.cmd))
|
||||
log.debug(e.output)
|
||||
@ -246,13 +246,13 @@ class PdfDocument(PresentationDocument):
|
||||
os.makedirs(self.get_temp_folder())
|
||||
if self.controller.mudrawbin:
|
||||
runlog = check_output([self.controller.mudrawbin, '-w', str(size.right()), '-h', str(size.bottom()),
|
||||
'-o', os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.filepath])
|
||||
'-o', os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.file_path])
|
||||
elif self.controller.gsbin:
|
||||
resolution = self.gs_get_resolution(size)
|
||||
runlog = check_output([self.controller.gsbin, '-dSAFER', '-dNOPAUSE', '-dBATCH', '-sDEVICE=png16m',
|
||||
'-r' + str(resolution), '-dTextAlphaBits=4', '-dGraphicsAlphaBits=4',
|
||||
'-sOutputFile=' + os.path.join(self.get_temp_folder(), 'mainslide%03d.png'),
|
||||
self.filepath])
|
||||
self.file_path])
|
||||
created_files = sorted(os.listdir(self.get_temp_folder()))
|
||||
for fn in created_files:
|
||||
if os.path.isfile(os.path.join(self.get_temp_folder(), fn)):
|
||||
|
@ -112,6 +112,9 @@ class PowerpointDocument(PresentationDocument):
|
||||
def __init__(self, controller, presentation):
|
||||
"""
|
||||
Constructor, store information about the file and initialise.
|
||||
|
||||
:param controller:
|
||||
:param presentation:
|
||||
"""
|
||||
log.debug('Init Presentation Powerpoint')
|
||||
super(PowerpointDocument, self).__init__(controller, presentation)
|
||||
@ -126,7 +129,7 @@ class PowerpointDocument(PresentationDocument):
|
||||
if not self.controller.process or not self.controller.process.Visible:
|
||||
self.controller.start_process()
|
||||
try:
|
||||
self.controller.process.Presentations.Open(self.filepath, False, False, True)
|
||||
self.controller.process.Presentations.Open(self.file_path, False, False, True)
|
||||
except pywintypes.com_error:
|
||||
log.debug('PPT open failed')
|
||||
return False
|
||||
@ -275,12 +278,14 @@ class PowerpointDocument(PresentationDocument):
|
||||
log.debug('get_slide_count')
|
||||
return self.presentation.Slides.Count
|
||||
|
||||
def goto_slide(self, slideno):
|
||||
def goto_slide(self, slide_no):
|
||||
"""
|
||||
Moves to a specific slide in the presentation.
|
||||
|
||||
:param slide_no: The slide the text is required for, starting at 1
|
||||
"""
|
||||
log.debug('goto_slide')
|
||||
self.presentation.SlideShowWindow.View.GotoSlide(slideno)
|
||||
self.presentation.SlideShowWindow.View.GotoSlide(slide_no)
|
||||
|
||||
def next_step(self):
|
||||
"""
|
||||
@ -302,8 +307,7 @@ class PowerpointDocument(PresentationDocument):
|
||||
"""
|
||||
Returns the text on the slide.
|
||||
|
||||
``slide_no``
|
||||
The slide the text is required for, starting at 1.
|
||||
:param slide_no: The slide the text is required for, starting at 1
|
||||
"""
|
||||
return _get_text_from_shapes(self.presentation.Slides(slide_no).Shapes)
|
||||
|
||||
@ -311,8 +315,7 @@ class PowerpointDocument(PresentationDocument):
|
||||
"""
|
||||
Returns the text on the slide.
|
||||
|
||||
``slide_no``
|
||||
The slide the notes are required for, starting at 1.
|
||||
:param slide_no: The slide the text is required for, starting at 1
|
||||
"""
|
||||
return _get_text_from_shapes(self.presentation.Slides(slide_no).NotesPage.Shapes)
|
||||
|
||||
@ -321,8 +324,7 @@ def _get_text_from_shapes(shapes):
|
||||
"""
|
||||
Returns any text extracted from the shapes on a presentation slide.
|
||||
|
||||
``shapes``
|
||||
A set of shapes to search for text.
|
||||
:param shapes: A set of shapes to search for text.
|
||||
"""
|
||||
text = ''
|
||||
for index in range(shapes.Count):
|
||||
|
@ -124,7 +124,7 @@ class PptviewDocument(PresentationDocument):
|
||||
temp_folder = self.get_temp_folder()
|
||||
size = ScreenList().current['size']
|
||||
rect = RECT(size.x(), size.y(), size.right(), size.bottom())
|
||||
file_path = os.path.normpath(self.filepath)
|
||||
file_path = os.path.normpath(self.file_path)
|
||||
preview_path = os.path.join(temp_folder, 'slide')
|
||||
# Ensure that the paths are null terminated
|
||||
file_path = file_path.encode('utf-16-le') + b'\0'
|
||||
@ -228,11 +228,13 @@ class PptviewDocument(PresentationDocument):
|
||||
"""
|
||||
return self.controller.process.GetSlideCount(self.ppt_id)
|
||||
|
||||
def goto_slide(self, slideno):
|
||||
def goto_slide(self, slide_no):
|
||||
"""
|
||||
Moves to a specific slide in the presentation.
|
||||
|
||||
:param slide_no: The slide the text is required for, starting at 1
|
||||
"""
|
||||
self.controller.process.GotoSlide(self.ppt_id, slideno)
|
||||
self.controller.process.GotoSlide(self.ppt_id, slide_no)
|
||||
|
||||
def next_step(self):
|
||||
"""
|
||||
|
@ -103,8 +103,8 @@ class PresentationDocument(object):
|
||||
"""
|
||||
Run some initial setup. This method is separate from __init__ in order to mock it out in tests.
|
||||
"""
|
||||
self.slidenumber = 0
|
||||
self.filepath = name
|
||||
self.slide_number = 0
|
||||
self.file_path = name
|
||||
check_directory_exists(self.get_thumbnail_folder())
|
||||
|
||||
def load_presentation(self):
|
||||
@ -131,7 +131,7 @@ class PresentationDocument(object):
|
||||
"""
|
||||
Return just the filename of the presentation, without the directory
|
||||
"""
|
||||
return os.path.split(self.filepath)[1]
|
||||
return os.path.split(self.file_path)[1]
|
||||
|
||||
def get_thumbnail_folder(self):
|
||||
"""
|
||||
@ -149,10 +149,10 @@ class PresentationDocument(object):
|
||||
"""
|
||||
Returns ``True`` if the thumbnail images exist and are more recent than the powerpoint file.
|
||||
"""
|
||||
lastimage = self.get_thumbnail_path(self.get_slide_count(), True)
|
||||
if not (lastimage and os.path.isfile(lastimage)):
|
||||
last_image = self.get_thumbnail_path(self.get_slide_count(), True)
|
||||
if not (last_image and os.path.isfile(last_image)):
|
||||
return False
|
||||
return validate_thumb(self.filepath, lastimage)
|
||||
return validate_thumb(self.file_path, last_image)
|
||||
|
||||
def close_presentation(self):
|
||||
"""
|
||||
@ -253,8 +253,7 @@ class PresentationDocument(object):
|
||||
``slide_no``
|
||||
The slide an image is required for, starting at 1
|
||||
"""
|
||||
path = os.path.join(self.get_thumbnail_folder(),
|
||||
self.controller.thumbnail_prefix + str(slide_no) + '.png')
|
||||
path = os.path.join(self.get_thumbnail_folder(), self.controller.thumbnail_prefix + str(slide_no) + '.png')
|
||||
if os.path.isfile(path) or not check_exists:
|
||||
return path
|
||||
else:
|
||||
@ -268,21 +267,20 @@ class PresentationDocument(object):
|
||||
return
|
||||
if not hide_mode:
|
||||
current = self.get_slide_number()
|
||||
if current == self.slidenumber:
|
||||
if current == self.slide_number:
|
||||
return
|
||||
self.slidenumber = current
|
||||
self.slide_number = current
|
||||
if is_live:
|
||||
prefix = 'live'
|
||||
else:
|
||||
prefix = 'preview'
|
||||
Registry().execute('slidecontroller_%s_change' % prefix, self.slidenumber - 1)
|
||||
Registry().execute('slidecontroller_%s_change' % prefix, self.slide_number - 1)
|
||||
|
||||
def get_slide_text(self, slide_no):
|
||||
"""
|
||||
Returns the text on the slide
|
||||
|
||||
``slide_no``
|
||||
The slide the text is required for, starting at 1
|
||||
:param slide_no: The slide the text is required for, starting at 1
|
||||
"""
|
||||
return ''
|
||||
|
||||
@ -290,8 +288,7 @@ class PresentationDocument(object):
|
||||
"""
|
||||
Returns the text on the slide
|
||||
|
||||
``slide_no``
|
||||
The slide the notes are required for, starting at 1
|
||||
:param slide_no: The slide the text is required for, starting at 1
|
||||
"""
|
||||
return ''
|
||||
|
||||
@ -350,6 +347,7 @@ class PresentationController(object):
|
||||
def __init__(self, plugin=None, name='PresentationController', document_class=PresentationDocument):
|
||||
"""
|
||||
This is the constructor for the presentationcontroller object. This provides an easy way for descendent plugins
|
||||
|
||||
to populate common data. This method *must* be overridden, like so::
|
||||
|
||||
class MyPresentationController(PresentationController):
|
||||
@ -357,11 +355,9 @@ class PresentationController(object):
|
||||
PresentationController.__init(
|
||||
self, plugin, u'My Presenter App')
|
||||
|
||||
``plugin``
|
||||
Defaults to *None*. The presentationplugin object
|
||||
|
||||
``name``
|
||||
Name of the application, to appear in the application
|
||||
:param plugin: Defaults to *None*. The presentationplugin object
|
||||
:param name: Name of the application, to appear in the application
|
||||
:param document_class:
|
||||
"""
|
||||
self.supports = []
|
||||
self.also_supports = []
|
||||
|
@ -2485,7 +2485,7 @@ $.mobile.transitionFallbacks = {};
|
||||
return path.stripHash( newPath ).replace( /[^\/]*\.[^\/*]+$/, '' );
|
||||
},
|
||||
|
||||
//return the substring of a filepath before the sub-page key, for making a server request
|
||||
//return the substring of a file_path before the sub-page key, for making a server request
|
||||
getFilePath: function( path ) {
|
||||
var splitkey = '&' + $.mobile.subPageUrlKey;
|
||||
return path && path.split( splitkey )[0].split( dialogHashKey )[0];
|
||||
|
@ -201,7 +201,7 @@ class SongImportForm(OpenLPWizard):
|
||||
def validateCurrentPage(self):
|
||||
"""
|
||||
Re-implement the validateCurrentPage() method. Validate the current page before moving on to the next page.
|
||||
Provide each song format class with a chance to validate its input by overriding isValidSource().
|
||||
Provide each song format class with a chance to validate its input by overriding is_valid_source().
|
||||
"""
|
||||
if self.currentPage() == self.welcome_page:
|
||||
return True
|
||||
@ -217,7 +217,7 @@ class SongImportForm(OpenLPWizard):
|
||||
import_source = self.format_widgets[this_format]['file_path_edit'].text()
|
||||
error_title = (UiStrings().IFSs if select_mode == SongFormatSelect.SingleFile else UiStrings().IFdSs)
|
||||
focus_button = self.format_widgets[this_format]['browseButton']
|
||||
if not class_.isValidSource(import_source):
|
||||
if not class_.is_valid_source(import_source):
|
||||
critical_error_message_box(error_title, error_msg)
|
||||
focus_button.setFocus()
|
||||
return False
|
||||
@ -334,7 +334,7 @@ class SongImportForm(OpenLPWizard):
|
||||
|
||||
def perform_wizard(self):
|
||||
"""
|
||||
Perform the actual import. This method pulls in the correct importer class, and then runs the ``doImport``
|
||||
Perform the actual import. This method pulls in the correct importer class, and then runs the ``do_import``
|
||||
method of the importer to do the actual importing.
|
||||
"""
|
||||
source_format = self.current_format
|
||||
@ -349,7 +349,7 @@ class SongImportForm(OpenLPWizard):
|
||||
importer = self.plugin.import_songs(
|
||||
source_format,
|
||||
filenames=self.get_list_of_files(self.format_widgets[source_format]['file_list_widget']))
|
||||
importer.doImport()
|
||||
importer.do_import()
|
||||
self.progress_label.setText(WizardStrings.FinishedImport)
|
||||
|
||||
def on_error_copy_to_button_clicked(self):
|
||||
|
@ -389,7 +389,6 @@ def clean_song(manager, song):
|
||||
song.lyrics = str(song.lyrics, encoding='utf8')
|
||||
verses = SongXML().get_verses(song.lyrics)
|
||||
song.search_lyrics = ' '.join([clean_string(verse[1]) for verse in verses])
|
||||
|
||||
# The song does not have any author, add one.
|
||||
if not song.authors:
|
||||
name = SongStrings.AuthorUnknown
|
||||
|
@ -38,26 +38,23 @@ from .songimport import SongImport
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CCLIFileImport(SongImport):
|
||||
"""
|
||||
The :class:`CCLIFileImport` class provides OpenLP with the ability to import
|
||||
CCLI SongSelect song files in both .txt and .usr formats. See
|
||||
`<http://www.ccli.com/>`_ for more details.
|
||||
The :class:`CCLIFileImport` class provides OpenLP with the ability to import CCLI SongSelect song files in
|
||||
both .txt and .usr formats. See `<http://www.ccli.com/>`_ for more details.
|
||||
"""
|
||||
|
||||
def __init__(self, manager, **kwargs):
|
||||
"""
|
||||
Initialise the import.
|
||||
|
||||
``manager``
|
||||
The song manager for the running OpenLP installation.
|
||||
|
||||
``filenames``
|
||||
The files to be imported.
|
||||
:param manager: The song manager for the running OpenLP installation.
|
||||
:param kwargs: The files to be imported.
|
||||
"""
|
||||
SongImport.__init__(self, manager, **kwargs)
|
||||
|
||||
def doImport(self):
|
||||
def do_import(self):
|
||||
"""
|
||||
Import either a ``.usr`` or a ``.txt`` SongSelect file.
|
||||
"""
|
||||
@ -85,27 +82,24 @@ class CCLIFileImport(SongImport):
|
||||
ext = os.path.splitext(filename)[1]
|
||||
if ext.lower() == '.usr':
|
||||
log.info('SongSelect .usr format file found: %s', filename)
|
||||
if not self.doImportUsrFile(lines):
|
||||
self.logError(filename)
|
||||
if not self.do_import_usr_file(lines):
|
||||
self.log_error(filename)
|
||||
elif ext.lower() == '.txt':
|
||||
log.info('SongSelect .txt format file found: %s', filename)
|
||||
if not self.doImportTxtFile(lines):
|
||||
self.logError(filename)
|
||||
if not self.do_import_txt_file(lines):
|
||||
self.log_error(filename)
|
||||
else:
|
||||
self.logError(filename,
|
||||
self.log_error(filename,
|
||||
translate('SongsPlugin.CCLIFileImport', 'The file does not have a valid extension.'))
|
||||
log.info('Extension %s is not valid', filename)
|
||||
if self.stop_import_flag:
|
||||
return
|
||||
|
||||
def doImportUsrFile(self, textList):
|
||||
def do_import_usr_file(self, text_list):
|
||||
"""
|
||||
The :func:`doImport_usr_file` method provides OpenLP with the ability
|
||||
to import CCLI SongSelect songs in *USR* file format.
|
||||
|
||||
``textList``
|
||||
An array of strings containing the usr file content.
|
||||
|
||||
**SongSelect .usr file format**
|
||||
|
||||
``[File]``
|
||||
@ -155,17 +149,18 @@ class CCLIFileImport(SongImport):
|
||||
*Fields* description
|
||||
e.g. *Words=Above all powers....* [/n = CR, /n/t = CRLF]
|
||||
|
||||
:param text_list: An array of strings containing the usr file content.
|
||||
"""
|
||||
log.debug('USR file text: %s', textList)
|
||||
log.debug('USR file text: %s', text_list)
|
||||
song_author = ''
|
||||
song_topics = ''
|
||||
for line in textList:
|
||||
for line in text_list:
|
||||
if line.startswith('[S '):
|
||||
ccli, line = line.split(']', 1)
|
||||
if ccli.startswith('[S A'):
|
||||
self.ccliNumber = ccli[4:].strip()
|
||||
self.ccli_number = ccli[4:].strip()
|
||||
else:
|
||||
self.ccliNumber = ccli[3:].strip()
|
||||
self.ccli_number = ccli[3:].strip()
|
||||
if line.startswith('Title='):
|
||||
self.title = line[6:].strip()
|
||||
elif line.startswith('Author='):
|
||||
@ -176,7 +171,7 @@ class CCLIFileImport(SongImport):
|
||||
song_topics = line[7:].strip().replace(' | ', '/t')
|
||||
elif line.startswith('Fields='):
|
||||
# Fields contain single line indicating verse, chorus, etc,
|
||||
# /t delimited, same as with words field. store seperately
|
||||
# /t delimited, same as with words field. store separately
|
||||
# and process at end.
|
||||
song_fields = line[7:].strip()
|
||||
elif line.startswith('Words='):
|
||||
@ -213,7 +208,7 @@ class CCLIFileImport(SongImport):
|
||||
verse_type = VerseType.tags[VerseType.Other]
|
||||
verse_text = verse_lines[1]
|
||||
if verse_text:
|
||||
self.addVerse(verse_text, verse_type)
|
||||
self.add_verse(verse_text, verse_type)
|
||||
check_first_verse_line = False
|
||||
# Handle multiple authors
|
||||
author_list = song_author.split('/')
|
||||
@ -223,17 +218,16 @@ class CCLIFileImport(SongImport):
|
||||
separated = author.split(',')
|
||||
if len(separated) > 1:
|
||||
author = ' '.join(map(str.strip, reversed(separated)))
|
||||
self.addAuthor(author.strip())
|
||||
self.add_author(author.strip())
|
||||
self.topics = [topic.strip() for topic in song_topics.split('/t')]
|
||||
return self.finish()
|
||||
|
||||
def doImportTxtFile(self, textList):
|
||||
def do_import_txt_file(self, text_list):
|
||||
"""
|
||||
The :func:`doImport_txt_file` method provides OpenLP with the ability
|
||||
to import CCLI SongSelect songs in *TXT* file format.
|
||||
|
||||
``textList``
|
||||
An array of strings containing the txt file content.
|
||||
:param text_list: An array of strings containing the txt file content.
|
||||
|
||||
SongSelect .txt file format::
|
||||
|
||||
@ -259,20 +253,20 @@ class CCLIFileImport(SongImport):
|
||||
# e.g. CCL-Liedlizenznummer: 14 / CCLI License No. 14
|
||||
|
||||
"""
|
||||
log.debug('TXT file text: %s', textList)
|
||||
log.debug('TXT file text: %s', text_list)
|
||||
line_number = 0
|
||||
check_first_verse_line = False
|
||||
verse_text = ''
|
||||
song_author = ''
|
||||
verse_start = False
|
||||
for line in textList:
|
||||
for line in text_list:
|
||||
clean_line = line.strip()
|
||||
if not clean_line:
|
||||
if line_number == 0:
|
||||
continue
|
||||
elif verse_start:
|
||||
if verse_text:
|
||||
self.addVerse(verse_text, verse_type)
|
||||
self.add_verse(verse_text, verse_type)
|
||||
verse_text = ''
|
||||
verse_start = False
|
||||
else:
|
||||
@ -286,7 +280,7 @@ class CCLIFileImport(SongImport):
|
||||
if clean_line.startswith('CCLI'):
|
||||
line_number += 1
|
||||
ccli_parts = clean_line.split(' ')
|
||||
self.ccliNumber = ccli_parts[len(ccli_parts) - 1]
|
||||
self.ccli_number = ccli_parts[len(ccli_parts) - 1]
|
||||
elif not verse_start:
|
||||
# We have the verse descriptor
|
||||
verse_desc_parts = clean_line.split(' ')
|
||||
@ -348,5 +342,5 @@ class CCLIFileImport(SongImport):
|
||||
author_list = song_author.split('|')
|
||||
# Clean spaces before and after author names.
|
||||
for author_name in author_list:
|
||||
self.addAuthor(author_name.strip())
|
||||
self.add_author(author_name.strip())
|
||||
return self.finish()
|
||||
|
@ -87,6 +87,7 @@ class Topic(BaseModel):
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def init_schema(url):
|
||||
"""
|
||||
Setup the songs database connection and initialise the database schema.
|
||||
@ -183,8 +184,7 @@ def init_schema(url):
|
||||
# Definition of the "media_files" table
|
||||
media_files_table = Table('media_files', metadata,
|
||||
Column('id', types.Integer(), primary_key=True),
|
||||
Column('song_id', types.Integer(), ForeignKey('songs.id'),
|
||||
default=None),
|
||||
Column('song_id', types.Integer(), ForeignKey('songs.id'), default=None),
|
||||
Column('file_name', types.Unicode(255), nullable=False),
|
||||
Column('type', types.Unicode(64), nullable=False, default='audio'),
|
||||
Column('weight', types.Integer(), default=0)
|
||||
@ -200,8 +200,7 @@ def init_schema(url):
|
||||
# Definition of the "songs" table
|
||||
songs_table = Table('songs', metadata,
|
||||
Column('id', types.Integer(), primary_key=True),
|
||||
Column('song_book_id', types.Integer(),
|
||||
ForeignKey('song_books.id'), default=None),
|
||||
Column('song_book_id', types.Integer(), ForeignKey('song_books.id'), default=None),
|
||||
Column('title', types.Unicode(255), nullable=False),
|
||||
Column('alternate_title', types.Unicode(255)),
|
||||
Column('lyrics', types.UnicodeText, nullable=False),
|
||||
@ -214,8 +213,7 @@ def init_schema(url):
|
||||
Column('search_title', types.Unicode(255), index=True, nullable=False),
|
||||
Column('search_lyrics', types.UnicodeText, nullable=False),
|
||||
Column('create_date', types.DateTime(), default=func.now()),
|
||||
Column('last_modified', types.DateTime(), default=func.now(),
|
||||
onupdate=func.now()),
|
||||
Column('last_modified', types.DateTime(), default=func.now(), onupdate=func.now()),
|
||||
Column('temporary', types.Boolean(), default=False)
|
||||
)
|
||||
|
||||
@ -227,18 +225,14 @@ def init_schema(url):
|
||||
|
||||
# Definition of the "authors_songs" table
|
||||
authors_songs_table = Table('authors_songs', metadata,
|
||||
Column('author_id', types.Integer(),
|
||||
ForeignKey('authors.id'), primary_key=True),
|
||||
Column('song_id', types.Integer(),
|
||||
ForeignKey('songs.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)
|
||||
)
|
||||
|
||||
# Definition of the "songs_topics" table
|
||||
songs_topics_table = Table('songs_topics', metadata,
|
||||
Column('song_id', types.Integer(),
|
||||
ForeignKey('songs.id'), primary_key=True),
|
||||
Column('topic_id', types.Integer(),
|
||||
ForeignKey('topics.id'), primary_key=True)
|
||||
Column('song_id', types.Integer(), ForeignKey('songs.id'), primary_key=True),
|
||||
Column('topic_id', types.Integer(), ForeignKey('topics.id'), primary_key=True)
|
||||
)
|
||||
|
||||
mapper(Author, authors_table)
|
||||
@ -246,13 +240,10 @@ def init_schema(url):
|
||||
mapper(MediaFile, media_files_table)
|
||||
mapper(Song, songs_table,
|
||||
properties={
|
||||
'authors': relation(Author, backref='songs',
|
||||
secondary=authors_songs_table, lazy=False),
|
||||
'authors': relation(Author, backref='songs', secondary=authors_songs_table, lazy=False),
|
||||
'book': relation(Book, backref='songs'),
|
||||
'media_files': relation(MediaFile, backref='songs',
|
||||
order_by=media_files_table.c.weight),
|
||||
'topics': relation(Topic, backref='songs',
|
||||
secondary=songs_topics_table)
|
||||
'media_files': relation(MediaFile, backref='songs', order_by=media_files_table.c.weight),
|
||||
'topics': relation(Topic, backref='songs', secondary=songs_topics_table)
|
||||
})
|
||||
mapper(Topic, topics_table)
|
||||
|
||||
|
@ -40,6 +40,7 @@ from openlp.plugins.songs.lib.ui import SongStrings
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DreamBeamImport(SongImport):
|
||||
"""
|
||||
The :class:`DreamBeamImport` class provides the ability to import song files from
|
||||
@ -83,7 +84,7 @@ class DreamBeamImport(SongImport):
|
||||
* \*.xml
|
||||
"""
|
||||
|
||||
def doImport(self):
|
||||
def do_import(self):
|
||||
"""
|
||||
Receive a single file or a list of files to import.
|
||||
"""
|
||||
@ -92,18 +93,19 @@ class DreamBeamImport(SongImport):
|
||||
for file in self.import_source:
|
||||
if self.stop_import_flag:
|
||||
return
|
||||
self.setDefaults()
|
||||
self.set_defaults()
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
try:
|
||||
parsed_file = etree.parse(open(file, 'r'), parser)
|
||||
except etree.XMLSyntaxError:
|
||||
log.exception('XML syntax error in file %s' % file)
|
||||
self.logError(file, SongStrings.XMLSyntaxError)
|
||||
self.log_error(file, SongStrings.XMLSyntaxError)
|
||||
continue
|
||||
xml = etree.tostring(parsed_file).decode()
|
||||
song_xml = objectify.fromstring(xml)
|
||||
if song_xml.tag != 'DreamSong':
|
||||
self.logError(file,
|
||||
self.log_error(
|
||||
file,
|
||||
translate('SongsPlugin.DreamBeamImport', 'Invalid DreamBeam song file. Missing DreamSong tag.'))
|
||||
continue
|
||||
if hasattr(song_xml, 'Version'):
|
||||
@ -121,15 +123,15 @@ class DreamBeamImport(SongImport):
|
||||
verse_type = lyrics_item.get('Type')
|
||||
verse_number = lyrics_item.get('Number')
|
||||
verse_text = str(lyrics_item.text)
|
||||
self.addVerse(verse_text, ('%s%s' % (verse_type[:1], verse_number)))
|
||||
self.add_verse(verse_text, ('%s%s' % (verse_type[:1], verse_number)))
|
||||
if hasattr(song_xml, 'Collection'):
|
||||
self.songBookName = str(song_xml.Collection.text)
|
||||
self.song_book_name = str(song_xml.Collection.text)
|
||||
if hasattr(song_xml, 'Number'):
|
||||
self.songNumber = str(song_xml.Number.text)
|
||||
self.song_number = str(song_xml.Number.text)
|
||||
if hasattr(song_xml, 'Sequence'):
|
||||
for LyricsSequenceItem in (song_xml.Sequence.iterchildren()):
|
||||
self.verseOrderList.append("%s%s" % (LyricsSequenceItem.get('Type')[:1],
|
||||
LyricsSequenceItem.get('Number')))
|
||||
for lyrics_sequence_item in (song_xml.Sequence.iterchildren()):
|
||||
self.verse_order_list.append("%s%s" % (lyrics_sequence_item.get('Type')[:1],
|
||||
lyrics_sequence_item.get('Number')))
|
||||
if hasattr(song_xml, 'Notes'):
|
||||
self.comments = str(song_xml.Notes.text)
|
||||
else:
|
||||
@ -138,15 +140,15 @@ class DreamBeamImport(SongImport):
|
||||
if hasattr(song_xml.Text1, 'Text'):
|
||||
self.lyrics = str(song_xml.Text1.Text.text)
|
||||
for verse in self.lyrics.split('\n\n\n'):
|
||||
self.addVerse(verse)
|
||||
self.add_verse(verse)
|
||||
if hasattr(song_xml.Text2, 'Text'):
|
||||
author_copyright = song_xml.Text2.Text.text
|
||||
if author_copyright:
|
||||
author_copyright = str(author_copyright)
|
||||
if author_copyright.find(
|
||||
str(SongStrings.CopyrightSymbol)) >= 0:
|
||||
self.addCopyright(author_copyright)
|
||||
self.add_copyright(author_copyright)
|
||||
else:
|
||||
self.parse_author(author_copyright)
|
||||
if not self.finish():
|
||||
self.logError(file)
|
||||
self.log_error(file)
|
||||
|
@ -37,6 +37,7 @@ from openlp.plugins.songs.lib.songimport import SongImport
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class EasySlidesImport(SongImport):
|
||||
"""
|
||||
Import songs exported from EasySlides
|
||||
@ -50,7 +51,7 @@ class EasySlidesImport(SongImport):
|
||||
"""
|
||||
SongImport.__init__(self, manager, **kwargs)
|
||||
|
||||
def doImport(self):
|
||||
def do_import(self):
|
||||
log.info('Importing EasySlides XML file %s', self.import_source)
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
parsed_file = etree.parse(self.import_source, parser)
|
||||
@ -60,18 +61,18 @@ class EasySlidesImport(SongImport):
|
||||
for song in song_xml.Item:
|
||||
if self.stop_import_flag:
|
||||
return
|
||||
self._parseSong(song)
|
||||
self._parse_song(song)
|
||||
|
||||
def _parseSong(self, song):
|
||||
def _parse_song(self, song):
|
||||
self._success = True
|
||||
self._add_unicode_attribute('title', song.Title1, True)
|
||||
if hasattr(song, 'Title2'):
|
||||
self._add_unicode_attribute('alternateTitle', song.Title2)
|
||||
self._add_unicode_attribute('alternate_title', song.Title2)
|
||||
if hasattr(song, 'SongNumber'):
|
||||
self._add_unicode_attribute('songNumber', song.SongNumber)
|
||||
if self.songNumber == '0':
|
||||
self.songNumber = ''
|
||||
self._addAuthors(song)
|
||||
self._add_unicode_attribute('song_number', song.SongNumber)
|
||||
if self.song_number == '0':
|
||||
self.song_number = ''
|
||||
self._add_authors(song)
|
||||
if hasattr(song, 'Copyright'):
|
||||
self._add_copyright(song.Copyright)
|
||||
if hasattr(song, 'LicenceAdmin1'):
|
||||
@ -79,13 +80,13 @@ class EasySlidesImport(SongImport):
|
||||
if hasattr(song, 'LicenceAdmin2'):
|
||||
self._add_copyright(song.LicenceAdmin2)
|
||||
if hasattr(song, 'BookReference'):
|
||||
self._add_unicode_attribute('songBookName', song.BookReference)
|
||||
self._parseAndAddLyrics(song)
|
||||
self._add_unicode_attribute('song_book_name', song.BookReference)
|
||||
self._parse_and_add_lyrics(song)
|
||||
if self._success:
|
||||
if not self.finish():
|
||||
self.logError(song.Title1 if song.Title1 else '')
|
||||
self.log_error(song.Title1 if song.Title1 else '')
|
||||
else:
|
||||
self.setDefaults()
|
||||
self.set_defaults()
|
||||
|
||||
def _add_unicode_attribute(self, self_attribute, import_attribute, mandatory=False):
|
||||
"""
|
||||
@ -94,14 +95,9 @@ class EasySlidesImport(SongImport):
|
||||
present _success is set to False so the importer can react
|
||||
appropriately.
|
||||
|
||||
``self_attribute``
|
||||
The attribute in the song model to populate.
|
||||
|
||||
``import_attribute``
|
||||
The imported value to convert to unicode and save to the song.
|
||||
|
||||
``mandatory``
|
||||
Signals that this attribute must exist in a valid song.
|
||||
:param self_attribute: The attribute in the song model to populate.
|
||||
:param import_attribute: The imported value to convert to unicode and save to the song.
|
||||
:param mandatory: Signals that this attribute must exist in a valid song.
|
||||
"""
|
||||
try:
|
||||
setattr(self, self_attribute, str(import_attribute).strip())
|
||||
@ -113,7 +109,7 @@ class EasySlidesImport(SongImport):
|
||||
if mandatory:
|
||||
self._success = False
|
||||
|
||||
def _addAuthors(self, song):
|
||||
def _add_authors(self, song):
|
||||
try:
|
||||
authors = str(song.Writer).split(',')
|
||||
self.authors = [author.strip() for author in authors if author.strip()]
|
||||
@ -125,21 +121,24 @@ class EasySlidesImport(SongImport):
|
||||
|
||||
def _add_copyright(self, element):
|
||||
"""
|
||||
Add a piece of copyright to the total copyright information for the
|
||||
song.
|
||||
Add a piece of copyright to the total copyright information for the song.
|
||||
|
||||
``element``
|
||||
The imported variable to get the data from.
|
||||
:param element: The imported variable to get the data from.
|
||||
"""
|
||||
try:
|
||||
self.addCopyright(str(element).strip())
|
||||
self.add_copyright(str(element).strip())
|
||||
except UnicodeDecodeError:
|
||||
log.exception('Unicode error on decoding copyright: %s' % element)
|
||||
self._success = False
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def _parseAndAddLyrics(self, song):
|
||||
def _parse_and_add_lyrics(self, song):
|
||||
"""
|
||||
Process the song lyrics
|
||||
|
||||
:param song: The song details
|
||||
"""
|
||||
try:
|
||||
lyrics = str(song.Contents).strip()
|
||||
except UnicodeDecodeError:
|
||||
@ -151,29 +150,29 @@ class EasySlidesImport(SongImport):
|
||||
lines = lyrics.split('\n')
|
||||
# we go over all lines first, to determine information,
|
||||
# which tells us how to parse verses later
|
||||
regionlines = {}
|
||||
separatorlines = 0
|
||||
region_lines = {}
|
||||
separator_lines = 0
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
elif line[1:7] == 'region':
|
||||
# this is region separator, probably [region 2]
|
||||
region = self._extractRegion(line)
|
||||
regionlines[region] = 1 + regionlines.get(region, 0)
|
||||
region = self._extract_region(line)
|
||||
region_lines[region] = 1 + region_lines.get(region, 0)
|
||||
elif line[0] == '[':
|
||||
separatorlines += 1
|
||||
separator_lines += 1
|
||||
# if the song has separators
|
||||
separators = (separatorlines > 0)
|
||||
separators = (separator_lines > 0)
|
||||
# the number of different regions in song - 1
|
||||
if len(regionlines) > 1:
|
||||
if len(region_lines) > 1:
|
||||
log.info('EasySlidesImport: the file contained a song named "%s"'
|
||||
'with more than two regions, but only two regions are tested, encountered regions were: %s',
|
||||
self.title, ','.join(list(regionlines.keys())))
|
||||
'with more than two regions, but only two regions are tested, encountered regions were: %s',
|
||||
self.title, ','.join(list(region_lines.keys())))
|
||||
# if the song has regions
|
||||
regions = (len(regionlines) > 0)
|
||||
regions = (len(region_lines) > 0)
|
||||
# if the regions are inside verses
|
||||
regionsInVerses = (regions and regionlines[list(regionlines.keys())[0]] > 1)
|
||||
regions_in_verses = (regions and region_lines[list(region_lines.keys())[0]] > 1)
|
||||
MarkTypes = {
|
||||
'CHORUS': VerseType.tags[VerseType.Chorus],
|
||||
'VERSE': VerseType.tags[VerseType.Verse],
|
||||
@ -185,21 +184,20 @@ class EasySlidesImport(SongImport):
|
||||
verses = {}
|
||||
# list as [region, versetype, versenum, instance]
|
||||
our_verse_order = []
|
||||
defaultregion = '1'
|
||||
reg = defaultregion
|
||||
default_region = '1'
|
||||
reg = default_region
|
||||
verses[reg] = {}
|
||||
# instance differentiates occurrences of same verse tag
|
||||
vt = 'V'
|
||||
vn = '1'
|
||||
inst = 1
|
||||
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
if separators:
|
||||
# separators are used, so empty line means slide break
|
||||
# inside verse
|
||||
if self._listHas(verses, [reg, vt, vn, inst]):
|
||||
if self._list_has(verses, [reg, vt, vn, inst]):
|
||||
inst += 1
|
||||
else:
|
||||
# separators are not used, so empty line starts a new verse
|
||||
@ -207,9 +205,9 @@ class EasySlidesImport(SongImport):
|
||||
vn = len(verses[reg].get(vt, {})) + 1
|
||||
inst = 1
|
||||
elif line[0:7] == '[region':
|
||||
reg = self._extractRegion(line)
|
||||
reg = self._extract_region(line)
|
||||
verses.setdefault(reg, {})
|
||||
if not regionsInVerses:
|
||||
if not regions_in_verses:
|
||||
vt = 'V'
|
||||
vn = '1'
|
||||
inst = 1
|
||||
@ -224,10 +222,10 @@ class EasySlidesImport(SongImport):
|
||||
marker = match.group(1).strip()
|
||||
vn = match.group(2)
|
||||
vt = MarkTypes.get(marker, 'O') if marker else 'V'
|
||||
if regionsInVerses:
|
||||
region = defaultregion
|
||||
if regions_in_verses:
|
||||
region = default_region
|
||||
inst = 1
|
||||
if self._listHas(verses, [reg, vt, vn, inst]):
|
||||
if self._list_has(verses, [reg, vt, vn, inst]):
|
||||
inst = len(verses[reg][vt][vn]) + 1
|
||||
else:
|
||||
if not [reg, vt, vn, inst] in our_verse_order:
|
||||
@ -235,21 +233,19 @@ class EasySlidesImport(SongImport):
|
||||
verses[reg].setdefault(vt, {})
|
||||
verses[reg][vt].setdefault(vn, {})
|
||||
verses[reg][vt][vn].setdefault(inst, [])
|
||||
verses[reg][vt][vn][inst].append(self.tidyText(line))
|
||||
verses[reg][vt][vn][inst].append(self.tidy_text(line))
|
||||
# done parsing
|
||||
|
||||
versetags = []
|
||||
# we use our_verse_order to ensure, we insert lyrics in the same order
|
||||
# as these appeared originally in the file
|
||||
for [reg, vt, vn, inst] in our_verse_order:
|
||||
if self._listHas(verses, [reg, vt, vn, inst]):
|
||||
if self._list_has(verses, [reg, vt, vn, inst]):
|
||||
# this is false, but needs user input
|
||||
lang = None
|
||||
versetag = '%s%s' % (vt, vn)
|
||||
versetags.append(versetag)
|
||||
lines = '\n'.join(verses[reg][vt][vn][inst])
|
||||
self.verses.append([versetag, lines, lang])
|
||||
|
||||
SeqTypes = {
|
||||
'p': 'P1',
|
||||
'q': 'P2',
|
||||
@ -271,25 +267,37 @@ class EasySlidesImport(SongImport):
|
||||
else:
|
||||
continue
|
||||
if tag in versetags:
|
||||
self.verseOrderList.append(tag)
|
||||
self.verse_order_list.append(tag)
|
||||
else:
|
||||
log.info('Got order item %s, which is not in versetags, dropping item from presentation order',
|
||||
tag)
|
||||
log.info('Got order item %s, which is not in versetags, dropping item from presentation order', tag)
|
||||
except UnicodeDecodeError:
|
||||
log.exception('Unicode decode error while decoding Sequence')
|
||||
self._success = False
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def _listHas(self, lst, subitems):
|
||||
for subitem in subitems:
|
||||
if subitem in lst:
|
||||
lst = lst[subitem]
|
||||
def _list_has(self, lst, sub_items):
|
||||
"""
|
||||
See if the list has sub items
|
||||
|
||||
:param lst: The list to check
|
||||
:param sub_items: sub item list
|
||||
:return:
|
||||
"""
|
||||
for sub_item in sub_items:
|
||||
if sub_item in lst:
|
||||
lst = lst[sub_item]
|
||||
else:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _extractRegion(self, line):
|
||||
def _extract_region(self, line):
|
||||
# this was true already: line[0:7] == u'[region':
|
||||
"""
|
||||
Extract the region from text
|
||||
|
||||
:param line: The line of text
|
||||
:return:
|
||||
"""
|
||||
right_bracket = line.find(']')
|
||||
return line[7:right_bracket].strip()
|
||||
|
@ -75,7 +75,12 @@ class EasyWorshipSongImport(SongImport):
|
||||
def __init__(self, manager, **kwargs):
|
||||
SongImport.__init__(self, manager, **kwargs)
|
||||
|
||||
def doImport(self):
|
||||
def do_import(self):
|
||||
"""
|
||||
Import the songs
|
||||
|
||||
:return:
|
||||
"""
|
||||
# Open the DB and MB files if they exist
|
||||
import_source_mb = self.import_source.replace('.DB', '.MB')
|
||||
if not os.path.isfile(self.import_source) or not os.path.isfile(import_source_mb):
|
||||
@ -84,12 +89,12 @@ class EasyWorshipSongImport(SongImport):
|
||||
if db_size < 0x800:
|
||||
return
|
||||
db_file = open(self.import_source, 'rb')
|
||||
self.memoFile = open(import_source_mb, 'rb')
|
||||
self.memo_file = open(import_source_mb, 'rb')
|
||||
# Don't accept files that are clearly not paradox files
|
||||
record_size, header_size, block_size, first_block, num_fields = struct.unpack('<hhxb8xh17xh', db_file.read(35))
|
||||
if header_size != 0x800 or block_size < 1 or block_size > 4:
|
||||
db_file.close()
|
||||
self.memoFile.close()
|
||||
self.memo_file.close()
|
||||
return
|
||||
# Take a stab at how text is encoded
|
||||
self.encoding = 'cp1252'
|
||||
@ -124,20 +129,20 @@ class EasyWorshipSongImport(SongImport):
|
||||
db_file.seek(4 + (num_fields * 4) + 261, os.SEEK_CUR)
|
||||
field_names = db_file.read(header_size - db_file.tell()).split(b'\0', num_fields)
|
||||
field_names.pop()
|
||||
field_descs = []
|
||||
field_descriptions = []
|
||||
for i, field_name in enumerate(field_names):
|
||||
field_type, field_size = struct.unpack_from('BB', field_info, i * 2)
|
||||
field_descs.append(FieldDescEntry(field_name, field_type, field_size))
|
||||
self.setRecordStruct(field_descs)
|
||||
field_descriptions.append(FieldDescEntry(field_name, field_type, field_size))
|
||||
self.set_record_struct(field_descriptions)
|
||||
# Pick out the field description indexes we will need
|
||||
try:
|
||||
success = True
|
||||
fi_title = self.findField(b'Title')
|
||||
fi_author = self.findField(b'Author')
|
||||
fi_copy = self.findField(b'Copyright')
|
||||
fi_admin = self.findField(b'Administrator')
|
||||
fi_words = self.findField(b'Words')
|
||||
fi_ccli = self.findField(b'Song Number')
|
||||
fi_title = self.find_field(b'Title')
|
||||
fi_author = self.find_field(b'Author')
|
||||
fi_copy = self.find_field(b'Copyright')
|
||||
fi_admin = self.find_field(b'Administrator')
|
||||
fi_words = self.find_field(b'Words')
|
||||
fi_ccli = self.find_field(b'Song Number')
|
||||
except IndexError:
|
||||
# This is the wrong table
|
||||
success = False
|
||||
@ -162,15 +167,15 @@ class EasyWorshipSongImport(SongImport):
|
||||
if self.stop_import_flag:
|
||||
break
|
||||
raw_record = db_file.read(record_size)
|
||||
self.fields = self.recordStruct.unpack(raw_record)
|
||||
self.setDefaults()
|
||||
self.title = self.getField(fi_title).decode()
|
||||
self.fields = self.record_structure.unpack(raw_record)
|
||||
self.set_defaults()
|
||||
self.title = self.get_field(fi_title).decode()
|
||||
# Get remaining fields.
|
||||
copy = self.getField(fi_copy)
|
||||
admin = self.getField(fi_admin)
|
||||
ccli = self.getField(fi_ccli)
|
||||
authors = self.getField(fi_author)
|
||||
words = self.getField(fi_words)
|
||||
copy = self.get_field(fi_copy)
|
||||
admin = self.get_field(fi_admin)
|
||||
ccli = self.get_field(fi_ccli)
|
||||
authors = self.get_field(fi_author)
|
||||
words = self.get_field(fi_words)
|
||||
# Set the SongImport object members.
|
||||
if copy:
|
||||
self.copyright = copy.decode()
|
||||
@ -180,7 +185,7 @@ class EasyWorshipSongImport(SongImport):
|
||||
self.copyright += translate('SongsPlugin.EasyWorshipSongImport',
|
||||
'Administered by %s') % admin.decode()
|
||||
if ccli:
|
||||
self.ccliNumber = ccli.decode()
|
||||
self.ccli_number = ccli.decode()
|
||||
if authors:
|
||||
# Split up the authors
|
||||
author_list = authors.split(b'/')
|
||||
@ -189,7 +194,7 @@ class EasyWorshipSongImport(SongImport):
|
||||
if len(author_list) < 2:
|
||||
author_list = authors.split(b',')
|
||||
for author_name in author_list:
|
||||
self.addAuthor(author_name.decode().strip())
|
||||
self.add_author(author_name.decode().strip())
|
||||
if words:
|
||||
# Format the lyrics
|
||||
result = strip_rtf(words.decode(), self.encoding)
|
||||
@ -227,24 +232,35 @@ class EasyWorshipSongImport(SongImport):
|
||||
if not number_found:
|
||||
verse_type += '1'
|
||||
break
|
||||
self.addVerse(verse_split[-1].strip() if first_line_is_tag else verse, verse_type)
|
||||
self.add_verse(verse_split[-1].strip() if first_line_is_tag else verse, verse_type)
|
||||
if len(self.comments) > 5:
|
||||
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.logError(self.import_source)
|
||||
self.log_error(self.import_source)
|
||||
db_file.close()
|
||||
self.memoFile.close()
|
||||
self.memo_file.close()
|
||||
|
||||
def findField(self, field_name):
|
||||
return [i for i, x in enumerate(self.fieldDescs) if x.name == field_name][0]
|
||||
def find_field(self, field_name):
|
||||
"""
|
||||
Find a field in the descriptions
|
||||
|
||||
def setRecordStruct(self, field_descs):
|
||||
:param field_name: field to find
|
||||
:return:
|
||||
"""
|
||||
return [i for i, x in enumerate(self.field_descriptions) if x.name == field_name][0]
|
||||
|
||||
def set_record_struct(self, field_descriptions):
|
||||
"""
|
||||
Save the record structure
|
||||
|
||||
:param field_descriptions: An array of field descriptions
|
||||
"""
|
||||
# Begin with empty field struct list
|
||||
fsl = ['>']
|
||||
for field_desc in field_descs:
|
||||
for field_desc in field_descriptions:
|
||||
if field_desc.field_type == FieldType.String:
|
||||
fsl.append('%ds' % field_desc.size)
|
||||
elif field_desc.field_type == FieldType.Int16:
|
||||
@ -261,12 +277,18 @@ class EasyWorshipSongImport(SongImport):
|
||||
fsl.append('Q')
|
||||
else:
|
||||
fsl.append('%ds' % field_desc.size)
|
||||
self.recordStruct = struct.Struct(''.join(fsl))
|
||||
self.fieldDescs = field_descs
|
||||
self.record_structure = struct.Struct(''.join(fsl))
|
||||
self.field_descriptions = field_descriptions
|
||||
|
||||
def getField(self, field_desc_index):
|
||||
def get_field(self, field_desc_index):
|
||||
"""
|
||||
Extract the field
|
||||
|
||||
:param field_desc_index: Field index value
|
||||
:return:
|
||||
"""
|
||||
field = self.fields[field_desc_index]
|
||||
field_desc = self.fieldDescs[field_desc_index]
|
||||
field_desc = self.field_descriptions[field_desc_index]
|
||||
# Return None in case of 'blank' entries
|
||||
if isinstance(field, bytes):
|
||||
if not field.rstrip(b'\0'):
|
||||
@ -281,23 +303,23 @@ class EasyWorshipSongImport(SongImport):
|
||||
elif field_desc.field_type == FieldType.Int32:
|
||||
return field ^ 0x80000000
|
||||
elif field_desc.field_type == FieldType.Logical:
|
||||
return (field ^ 0x80 == 1)
|
||||
return field ^ 0x80 == 1
|
||||
elif field_desc.field_type == FieldType.Memo or field_desc.field_type == FieldType.Blob:
|
||||
block_start, blob_size = struct.unpack_from('<II', field, len(field)-10)
|
||||
sub_block = block_start & 0xff
|
||||
block_start &= ~0xff
|
||||
self.memoFile.seek(block_start)
|
||||
memo_block_type, = struct.unpack('b', self.memoFile.read(1))
|
||||
self.memo_file.seek(block_start)
|
||||
memo_block_type, = struct.unpack('b', self.memo_file.read(1))
|
||||
if memo_block_type == 2:
|
||||
self.memoFile.seek(8, os.SEEK_CUR)
|
||||
self.memo_file.seek(8, os.SEEK_CUR)
|
||||
elif memo_block_type == 3:
|
||||
if sub_block > 63:
|
||||
return b''
|
||||
self.memoFile.seek(11 + (5 * sub_block), os.SEEK_CUR)
|
||||
sub_block_start, = struct.unpack('B', self.memoFile.read(1))
|
||||
self.memoFile.seek(block_start + (sub_block_start * 16))
|
||||
self.memo_file.seek(11 + (5 * sub_block), os.SEEK_CUR)
|
||||
sub_block_start, = struct.unpack('B', self.memo_file.read(1))
|
||||
self.memo_file.seek(block_start + (sub_block_start * 16))
|
||||
else:
|
||||
return b''
|
||||
return self.memoFile.read(blob_size)
|
||||
return self.memo_file.read(blob_size)
|
||||
else:
|
||||
return 0
|
||||
|
@ -106,6 +106,7 @@ from openlp.plugins.songs.lib.xml import SongXML
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FoilPresenterImport(SongImport):
|
||||
"""
|
||||
This provides the Foilpresenter import.
|
||||
@ -116,9 +117,9 @@ class FoilPresenterImport(SongImport):
|
||||
"""
|
||||
log.debug('initialise FoilPresenterImport')
|
||||
SongImport.__init__(self, manager, **kwargs)
|
||||
self.FoilPresenter = FoilPresenter(self.manager, self)
|
||||
self.foil_presenter = FoilPresenter(self.manager, self)
|
||||
|
||||
def doImport(self):
|
||||
def do_import(self):
|
||||
"""
|
||||
Imports the songs.
|
||||
"""
|
||||
@ -131,9 +132,9 @@ class FoilPresenterImport(SongImport):
|
||||
try:
|
||||
parsed_file = etree.parse(file_path, parser)
|
||||
xml = etree.tostring(parsed_file).decode()
|
||||
self.FoilPresenter.xml_to_song(xml)
|
||||
self.foil_presenter.xml_to_song(xml)
|
||||
except etree.XMLSyntaxError:
|
||||
self.logError(file_path, SongStrings.XMLSyntaxError)
|
||||
self.log_error(file_path, SongStrings.XMLSyntaxError)
|
||||
log.exception('XML syntax error in file %s' % file_path)
|
||||
|
||||
|
||||
@ -245,8 +246,7 @@ class FoilPresenter(object):
|
||||
"""
|
||||
This returns the text of an element as unicode string.
|
||||
|
||||
``element``
|
||||
The element.
|
||||
:param element: The element
|
||||
"""
|
||||
if element is not None:
|
||||
return str(element)
|
||||
@ -256,11 +256,8 @@ class FoilPresenter(object):
|
||||
"""
|
||||
Adds the authors specified in the XML to the song.
|
||||
|
||||
``foilpresenterfolie``
|
||||
The property object (lxml.objectify.ObjectifiedElement).
|
||||
|
||||
``song``
|
||||
The song object.
|
||||
:param foilpresenterfolie: The property object (lxml.objectify.ObjectifiedElement).
|
||||
:param song: The song object.
|
||||
"""
|
||||
authors = []
|
||||
try:
|
||||
@ -324,8 +321,7 @@ class FoilPresenter(object):
|
||||
break
|
||||
author_temp = []
|
||||
for author in strings:
|
||||
temp = re.split(',(?=\D{2})|(?<=\D),|\/(?=\D{3,})|(?<=\D);',
|
||||
author)
|
||||
temp = re.split(',(?=\D{2})|(?<=\D),|\/(?=\D{3,})|(?<=\D);', author)
|
||||
for tempx in temp:
|
||||
author_temp.append(tempx)
|
||||
for author in author_temp:
|
||||
@ -349,7 +345,7 @@ class FoilPresenter(object):
|
||||
if author is None:
|
||||
# We need to create a new author, as the author does not exist.
|
||||
author = Author.populate(display_name=display_name, last_name=display_name.split(' ')[-1],
|
||||
first_name=' '.join(display_name.split(' ')[:-1]))
|
||||
first_name=' '.join(display_name.split(' ')[:-1]))
|
||||
self.manager.save_object(author)
|
||||
song.authors.append(author)
|
||||
|
||||
@ -357,11 +353,8 @@ class FoilPresenter(object):
|
||||
"""
|
||||
Adds the CCLI number to the song.
|
||||
|
||||
``foilpresenterfolie``
|
||||
The property object (lxml.objectify.ObjectifiedElement).
|
||||
|
||||
``song``
|
||||
The song object.
|
||||
:param foilpresenterfolie: The property object (lxml.objectify.ObjectifiedElement).
|
||||
:param song: The song object.
|
||||
"""
|
||||
try:
|
||||
song.ccli_number = self._child(foilpresenterfolie.ccliid)
|
||||
@ -372,11 +365,8 @@ class FoilPresenter(object):
|
||||
"""
|
||||
Joins the comments specified in the XML and add it to the song.
|
||||
|
||||
``foilpresenterfolie``
|
||||
The property object (lxml.objectify.ObjectifiedElement).
|
||||
|
||||
``song``
|
||||
The song object.
|
||||
:param foilpresenterfolie: The property object (lxml.objectify.ObjectifiedElement).
|
||||
:param song: The song object.
|
||||
"""
|
||||
try:
|
||||
song.comments = self._child(foilpresenterfolie.notiz)
|
||||
@ -387,11 +377,8 @@ class FoilPresenter(object):
|
||||
"""
|
||||
Adds the copyright to the song.
|
||||
|
||||
``foilpresenterfolie``
|
||||
The property object (lxml.objectify.ObjectifiedElement).
|
||||
|
||||
``song``
|
||||
The song object.
|
||||
:param foilpresenterfolie: The property object (lxml.objectify.ObjectifiedElement).
|
||||
:param song: The song object.
|
||||
"""
|
||||
try:
|
||||
song.copyright = self._child(foilpresenterfolie.copyright.text_)
|
||||
@ -402,11 +389,8 @@ class FoilPresenter(object):
|
||||
"""
|
||||
Processes the verses and search_lyrics for the song.
|
||||
|
||||
``foilpresenterfolie``
|
||||
The foilpresenterfolie object (lxml.objectify.ObjectifiedElement).
|
||||
|
||||
``song``
|
||||
The song object.
|
||||
:param foilpresenterfolie: The foilpresenterfolie object (lxml.objectify.ObjectifiedElement).
|
||||
:param song: The song object.
|
||||
"""
|
||||
sxml = SongXML()
|
||||
temp_verse_order = {}
|
||||
@ -423,9 +407,9 @@ class FoilPresenter(object):
|
||||
VerseType.tags[VerseType.PreChorus]: 1
|
||||
}
|
||||
if not hasattr(foilpresenterfolie.strophen, 'strophe'):
|
||||
self.importer.logError(self._child(foilpresenterfolie.titel),
|
||||
str(translate('SongsPlugin.FoilPresenterSongImport',
|
||||
'Invalid Foilpresenter song file. No verses found.')))
|
||||
self.importer.log_error(self._child(foilpresenterfolie.titel),
|
||||
str(translate('SongsPlugin.FoilPresenterSongImport',
|
||||
'Invalid Foilpresenter song file. No verses found.')))
|
||||
self.save_song = False
|
||||
return
|
||||
for strophe in foilpresenterfolie.strophen.strophe:
|
||||
@ -478,10 +462,8 @@ class FoilPresenter(object):
|
||||
verse_number = str(int(verse_number) + 1)
|
||||
verse_type_index = VerseType.from_tag(verse_type[0])
|
||||
verse_type = VerseType.tags[verse_type_index]
|
||||
temp_verse_order[verse_sortnr] = ''.join((verse_type[0],
|
||||
verse_number))
|
||||
temp_verse_order_backup.append(''.join((verse_type[0],
|
||||
verse_number)))
|
||||
temp_verse_order[verse_sortnr] = ''.join((verse_type[0], verse_number))
|
||||
temp_verse_order_backup.append(''.join((verse_type[0], verse_number)))
|
||||
sxml.add_verse_to_lyrics(verse_type, verse_number, text)
|
||||
song.lyrics = str(sxml.extract_xml(), 'utf-8')
|
||||
# Process verse order
|
||||
@ -506,11 +488,8 @@ class FoilPresenter(object):
|
||||
"""
|
||||
Adds the song book and song number specified in the XML to the song.
|
||||
|
||||
``foilpresenterfolie``
|
||||
The property object (lxml.objectify.ObjectifiedElement).
|
||||
|
||||
``song``
|
||||
The song object.
|
||||
:param foilpresenterfolie: The property object (lxml.objectify.ObjectifiedElement).
|
||||
:param song: The song object.
|
||||
"""
|
||||
song.song_book_id = 0
|
||||
song.song_number = ''
|
||||
@ -518,8 +497,7 @@ class FoilPresenter(object):
|
||||
for bucheintrag in foilpresenterfolie.buch.bucheintrag:
|
||||
book_name = self._child(bucheintrag.name)
|
||||
if book_name:
|
||||
book = self.manager.get_object_filtered(Book,
|
||||
Book.name == book_name)
|
||||
book = self.manager.get_object_filtered(Book, Book.name == book_name)
|
||||
if book is None:
|
||||
# We need to create a book, because it does not exist.
|
||||
book = Book.populate(name=book_name, publisher='')
|
||||
@ -539,11 +517,8 @@ class FoilPresenter(object):
|
||||
"""
|
||||
Processes the titles specified in the song's XML.
|
||||
|
||||
``foilpresenterfolie``
|
||||
The property object (lxml.objectify.ObjectifiedElement).
|
||||
|
||||
``song``
|
||||
The song object.
|
||||
:param foilpresenterfolie: The property object (lxml.objectify.ObjectifiedElement).
|
||||
:param song: The song object.
|
||||
"""
|
||||
try:
|
||||
for title_string in foilpresenterfolie.titel.titelstring:
|
||||
@ -561,18 +536,14 @@ class FoilPresenter(object):
|
||||
"""
|
||||
Adds the topics to the song.
|
||||
|
||||
``foilpresenterfolie``
|
||||
The property object (lxml.objectify.ObjectifiedElement).
|
||||
|
||||
``song``
|
||||
The song object.
|
||||
:param foilpresenterfolie: The property object (lxml.objectify.ObjectifiedElement).
|
||||
:param song: The song object.
|
||||
"""
|
||||
try:
|
||||
for name in foilpresenterfolie.kategorien.name:
|
||||
topic_text = self._child(name)
|
||||
if topic_text:
|
||||
topic = self.manager.get_object_filtered(Topic,
|
||||
Topic.name == topic_text)
|
||||
topic = self.manager.get_object_filtered(Topic, Topic.name == topic_text)
|
||||
if topic is None:
|
||||
# We need to create a topic, because it does not exist.
|
||||
topic = Topic.populate(name=topic_text)
|
||||
|
@ -82,6 +82,7 @@ if os.name == 'nt':
|
||||
except ImportError:
|
||||
log.exception('Error importing %s', 'WorshipCenterProImport')
|
||||
|
||||
|
||||
class SongFormatSelect(object):
|
||||
"""
|
||||
This is a special enumeration class listing available file selection modes.
|
||||
@ -99,47 +100,47 @@ class SongFormat(object):
|
||||
|
||||
Required attributes for each song format:
|
||||
|
||||
``u'class'``
|
||||
``'class'``
|
||||
Import class, e.g. ``OpenLyricsImport``
|
||||
|
||||
``u'name'``
|
||||
Name of the format, e.g. ``u'OpenLyrics'``
|
||||
``'name'``
|
||||
Name of the format, e.g. ``'OpenLyrics'``
|
||||
|
||||
``u'prefix'``
|
||||
Prefix for Qt objects. Use mixedCase, e.g. ``u'openLyrics'``
|
||||
``'prefix'``
|
||||
Prefix for Qt objects. Use mixedCase, e.g. ``'open_lyrics'``
|
||||
See ``SongImportForm.add_file_select_item()``
|
||||
|
||||
Optional attributes for each song format:
|
||||
|
||||
``u'canDisable'``
|
||||
``'canDisable'``
|
||||
Whether song format importer is disablable.
|
||||
If ``True``, then ``u'disabledLabelText'`` must also be defined.
|
||||
If ``True``, then ``'disabledLabelText'`` must also be defined.
|
||||
|
||||
``u'availability'``
|
||||
``'availability'``
|
||||
Whether song format importer is available.
|
||||
|
||||
``u'selectMode'``
|
||||
``'selectMode'``
|
||||
Whether format accepts single file, multiple files, or single folder
|
||||
(as per ``SongFormatSelect`` options).
|
||||
|
||||
``u'filter'``
|
||||
``'filter'``
|
||||
File extension filter for ``QFileDialog``.
|
||||
|
||||
Optional/custom text Strings for ``SongImportForm`` widgets:
|
||||
|
||||
``u'comboBoxText'``
|
||||
Combo box selector (default value is the format's ``u'name'``).
|
||||
``'comboBoxText'``
|
||||
Combo box selector (default value is the format's ``'name'``).
|
||||
|
||||
``u'disabledLabelText'``
|
||||
``'disabledLabelText'``
|
||||
Required for disablable song formats.
|
||||
|
||||
``u'getFilesTitle'``
|
||||
Title for ``QFileDialog`` (default includes the format's ``u'name'``).
|
||||
``'getFilesTitle'``
|
||||
Title for ``QFileDialog`` (default includes the format's ``'name'``).
|
||||
|
||||
``u'invalidSourceMsg'``
|
||||
Message displayed if ``isValidSource()`` returns ``False``.
|
||||
``'invalidSourceMsg'``
|
||||
Message displayed if ``is_valid_source()`` returns ``False``.
|
||||
|
||||
``u'descriptionText'``
|
||||
``'descriptionText'``
|
||||
Short description (1-2 lines) about the song format.
|
||||
"""
|
||||
# Song formats (ordered alphabetically after Generic)
|
||||
@ -184,7 +185,7 @@ class SongFormat(object):
|
||||
OpenLyrics: {
|
||||
'class': OpenLyricsImport,
|
||||
'name': 'OpenLyrics',
|
||||
'prefix': 'openLyrics',
|
||||
'prefix': 'open_lyrics',
|
||||
'filter': '%s (*.xml)' % translate('SongsPlugin.ImportWizardForm', 'OpenLyrics Files'),
|
||||
'comboBoxText': translate('SongsPlugin.ImportWizardForm', 'OpenLyrics or OpenLP 2.0 Exported Song')
|
||||
},
|
||||
@ -200,8 +201,8 @@ class SongFormat(object):
|
||||
'prefix': 'generic',
|
||||
'canDisable': True,
|
||||
'disabledLabelText': translate('SongsPlugin.ImportWizardForm',
|
||||
'The generic document/presentation importer has been disabled '
|
||||
'because OpenLP cannot access OpenOffice or LibreOffice.'),
|
||||
'The generic document/presentation importer has been disabled '
|
||||
'because OpenLP cannot access OpenOffice or LibreOffice.'),
|
||||
'getFilesTitle': translate('SongsPlugin.ImportWizardForm', 'Select Document/Presentation Files')
|
||||
},
|
||||
CCLI: {
|
||||
@ -241,13 +242,12 @@ class SongFormat(object):
|
||||
'prefix': 'mediaShout',
|
||||
'canDisable': True,
|
||||
'selectMode': SongFormatSelect.SingleFile,
|
||||
'filter': '%s (*.mdb)' % translate('SongsPlugin.ImportWizardForm',
|
||||
'MediaShout Database'),
|
||||
'filter': '%s (*.mdb)' % translate('SongsPlugin.ImportWizardForm', 'MediaShout Database'),
|
||||
'disabledLabelText': translate('SongsPlugin.ImportWizardForm',
|
||||
'The MediaShout importer is only supported on Windows. It has '
|
||||
'been disabled due to a missing Python module. If you want to '
|
||||
'use this importer, you will need to install the "pyodbc" '
|
||||
'module.')
|
||||
'The MediaShout importer is only supported on Windows. It has '
|
||||
'been disabled due to a missing Python module. If you want to '
|
||||
'use this importer, you will need to install the "pyodbc" '
|
||||
'module.')
|
||||
},
|
||||
OpenSong: {
|
||||
'class': OpenSongImport,
|
||||
@ -259,15 +259,14 @@ class SongFormat(object):
|
||||
'name': 'PowerSong 1.0',
|
||||
'prefix': 'powerSong',
|
||||
'selectMode': SongFormatSelect.SingleFolder,
|
||||
'invalidSourceMsg': translate('SongsPlugin.ImportWizardForm',
|
||||
'You need to specify a valid PowerSong 1.0 database folder.')
|
||||
'invalidSourceMsg': translate('SongsPlugin.ImportWizardForm', 'You need to specify a valid PowerSong 1.0 '
|
||||
'database folder.')
|
||||
},
|
||||
SongBeamer: {
|
||||
'class': SongBeamerImport,
|
||||
'name': 'SongBeamer',
|
||||
'prefix': 'songBeamer',
|
||||
'filter': '%s (*.sng)' % translate('SongsPlugin.ImportWizardForm',
|
||||
'SongBeamer Files')
|
||||
'filter': '%s (*.sng)' % translate('SongsPlugin.ImportWizardForm', 'SongBeamer Files')
|
||||
},
|
||||
SongPro: {
|
||||
'class': SongProImport,
|
||||
@ -277,7 +276,7 @@ class SongFormat(object):
|
||||
'filter': '%s (*.txt)' % translate('SongsPlugin.ImportWizardForm', 'SongPro Text Files'),
|
||||
'comboBoxText': translate('SongsPlugin.ImportWizardForm', 'SongPro (Export File)'),
|
||||
'descriptionText': translate('SongsPlugin.ImportWizardForm',
|
||||
'In SongPro, export your songs using the File -> Export menu')
|
||||
'In SongPro, export your songs using the File -> Export menu')
|
||||
},
|
||||
SongShowPlus: {
|
||||
'class': SongShowPlusImport,
|
||||
@ -291,8 +290,8 @@ class SongFormat(object):
|
||||
'canDisable': True,
|
||||
'filter': '%s (*.rtf)' % translate('SongsPlugin.ImportWizardForm', 'Songs Of Fellowship Song Files'),
|
||||
'disabledLabelText': translate('SongsPlugin.ImportWizardForm',
|
||||
'The Songs of Fellowship importer has been disabled because '
|
||||
'OpenLP cannot access OpenOffice or LibreOffice.')
|
||||
'The Songs of Fellowship importer has been disabled because '
|
||||
'OpenLP cannot access OpenOffice or LibreOffice.')
|
||||
},
|
||||
SundayPlus: {
|
||||
'class': SundayPlusImport,
|
||||
@ -304,8 +303,7 @@ class SongFormat(object):
|
||||
'class': WowImport,
|
||||
'name': 'Words of Worship',
|
||||
'prefix': 'wordsOfWorship',
|
||||
'filter': '%s (*.wsg *.wow-song)' %
|
||||
translate('SongsPlugin.ImportWizardForm', 'Words Of Worship Song Files')
|
||||
'filter': '%s (*.wsg *.wow-song)' % translate('SongsPlugin.ImportWizardForm', 'Words Of Worship Song Files')
|
||||
},
|
||||
WorshipCenterPro: {
|
||||
'name': 'WorshipCenter Pro',
|
||||
@ -314,8 +312,9 @@ class SongFormat(object):
|
||||
'selectMode': SongFormatSelect.SingleFile,
|
||||
'filter': '%s (*.mdb)' % translate('SongsPlugin.ImportWizardForm', 'WorshipCenter Pro Song Files'),
|
||||
'disabledLabelText': translate('SongsPlugin.ImportWizardForm',
|
||||
'The WorshipCenter Pro importer is only supported on Windows. It has been disabled due to a missing '
|
||||
'Python module. If you want to use this importer, you will need to install the "pyodbc" module.')
|
||||
'The WorshipCenter Pro importer is only supported on Windows. It has been '
|
||||
'disabled due to a missing Python module. If you want to use this '
|
||||
'importer, you will need to install the "pyodbc" module.')
|
||||
},
|
||||
ZionWorx: {
|
||||
'class': ZionWorxImport,
|
||||
@ -324,9 +323,9 @@ class SongFormat(object):
|
||||
'selectMode': SongFormatSelect.SingleFile,
|
||||
'comboBoxText': translate('SongsPlugin.ImportWizardForm', 'ZionWorx (CSV)'),
|
||||
'descriptionText': translate('SongsPlugin.ImportWizardForm',
|
||||
'First convert your ZionWorx database to a CSV text file, as '
|
||||
'explained in the <a href="http://manual.openlp.org/songs.html'
|
||||
'#importing-from-zionworx">User Manual</a>.')
|
||||
'First convert your ZionWorx database to a CSV text file, as '
|
||||
'explained in the <a href="http://manual.openlp.org/songs.html'
|
||||
'#importing-from-zionworx">User Manual</a>.')
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,8 +36,8 @@ from PyQt4 import QtCore, QtGui
|
||||
from sqlalchemy.sql import or_
|
||||
|
||||
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, create_separated_list
|
||||
from openlp.core.lib import MediaManagerItem, ItemCapabilities, PluginStatus, ServiceItemContext, check_item_selected, \
|
||||
create_separated_list
|
||||
from openlp.core.lib.ui import create_widget_action
|
||||
from openlp.plugins.songs.forms.editsongform import EditSongForm
|
||||
from openlp.plugins.songs.forms.songmaintenanceform import SongMaintenanceForm
|
||||
@ -97,21 +97,21 @@ class SongMediaItem(MediaManagerItem):
|
||||
def add_end_header_bar(self):
|
||||
self.toolbar.addSeparator()
|
||||
## Song Maintenance Button ##
|
||||
self.maintenanceAction = self.toolbar.add_toolbar_action('maintenanceAction',
|
||||
icon=':/songs/song_maintenance.png',
|
||||
triggers=self.on_song_maintenance_click)
|
||||
self.maintenance_action = self.toolbar.add_toolbar_action('maintenance_action',
|
||||
icon=':/songs/song_maintenance.png',
|
||||
triggers=self.on_song_maintenance_click)
|
||||
self.add_search_to_toolbar()
|
||||
# Signals and slots
|
||||
Registry().register_function('songs_load_list', self.on_song_list_load)
|
||||
Registry().register_function('songs_preview', self.on_preview_click)
|
||||
QtCore.QObject.connect(self.search_text_edit, QtCore.SIGNAL('cleared()'), self.on_clear_text_button_click)
|
||||
QtCore.QObject.connect(self.search_text_edit, QtCore.SIGNAL('searchTypeChanged(int)'),
|
||||
self.on_search_text_button_clicked)
|
||||
QtCore.QObject.connect(
|
||||
self.search_text_edit, QtCore.SIGNAL('searchTypeChanged(int)'), self.on_search_text_button_clicked)
|
||||
|
||||
def add_custom_context_actions(self):
|
||||
create_widget_action(self.list_view, separator=True)
|
||||
create_widget_action(self.list_view,
|
||||
text=translate('OpenLP.MediaManagerItem', '&Clone'), icon=':/general/general_clone.png',
|
||||
create_widget_action(
|
||||
self.list_view, text=translate('OpenLP.MediaManagerItem', '&Clone'), icon=':/general/general_clone.png',
|
||||
triggers=self.on_clone_click)
|
||||
|
||||
def on_focus(self):
|
||||
@ -123,15 +123,15 @@ class SongMediaItem(MediaManagerItem):
|
||||
"""
|
||||
log.debug('config_updated')
|
||||
self.search_as_you_type = Settings().value(self.settings_section + '/search as type')
|
||||
self.updateServiceOnEdit = Settings().value(self.settings_section + '/update service on edit')
|
||||
self.addSongFromService = Settings().value(self.settings_section + '/add song from service',)
|
||||
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',)
|
||||
|
||||
def retranslateUi(self):
|
||||
self.search_text_label.setText('%s:' % UiStrings().Search)
|
||||
self.search_text_button.setText(UiStrings().Search)
|
||||
self.maintenanceAction.setText(SongStrings.SongMaintenance)
|
||||
self.maintenanceAction.setToolTip(translate('SongsPlugin.MediaItem',
|
||||
'Maintain the lists of authors, topics and books.'))
|
||||
self.maintenance_action.setText(SongStrings.SongMaintenance)
|
||||
self.maintenance_action.setToolTip(translate('SongsPlugin.MediaItem',
|
||||
'Maintain the lists of authors, topics and books.'))
|
||||
|
||||
def initialise(self):
|
||||
"""
|
||||
@ -139,7 +139,7 @@ class SongMediaItem(MediaManagerItem):
|
||||
"""
|
||||
self.song_maintenance_form = SongMaintenanceForm(self.plugin.manager, self)
|
||||
self.edit_song_form = EditSongForm(self, self.main_window, self.plugin.manager)
|
||||
self.openLyrics = OpenLyrics(self.plugin.manager)
|
||||
self.open_lyrics = OpenLyrics(self.plugin.manager)
|
||||
self.search_text_edit.set_search_types([
|
||||
(SongSearch.Entire, ':/songs/song_search_all.png',
|
||||
translate('SongsPlugin.MediaItem', 'Entire Song'),
|
||||
@ -154,8 +154,7 @@ class SongMediaItem(MediaManagerItem):
|
||||
translate('SongsPlugin.MediaItem', 'Search Authors...')),
|
||||
(SongSearch.Books, ':/songs/song_book_edit.png', SongStrings.SongBooks,
|
||||
translate('SongsPlugin.MediaItem', 'Search Song Books...')),
|
||||
(SongSearch.Themes, ':/slides/slide_theme.png',
|
||||
UiStrings().Themes, UiStrings().SearchThemes)
|
||||
(SongSearch.Themes, ':/slides/slide_theme.png', UiStrings().Themes, UiStrings().SearchThemes)
|
||||
])
|
||||
self.search_text_edit.set_current_search_type(Settings().value('%s/last search type' % self.settings_section))
|
||||
self.config_update()
|
||||
@ -172,64 +171,65 @@ class SongMediaItem(MediaManagerItem):
|
||||
self.display_results_song(search_results)
|
||||
elif search_type == SongSearch.Titles:
|
||||
log.debug('Titles Search')
|
||||
search_results = self.plugin.manager.get_all_objects(Song,
|
||||
Song.search_title.like('%' + clean_string(search_keywords) + '%'))
|
||||
search_string = '%' + clean_string(search_keywords) + '%'
|
||||
search_results = self.plugin.manager.get_all_objects(Song, Song.search_title.like(search_string))
|
||||
self.display_results_song(search_results)
|
||||
elif search_type == SongSearch.Lyrics:
|
||||
log.debug('Lyrics Search')
|
||||
search_results = self.plugin.manager.get_all_objects(Song,
|
||||
Song.search_lyrics.like('%' + clean_string(search_keywords) + '%'))
|
||||
search_string = '%' + clean_string(search_keywords) + '%'
|
||||
search_results = self.plugin.manager.get_all_objects(Song, Song.search_lyrics.like(search_string))
|
||||
self.display_results_song(search_results)
|
||||
elif search_type == SongSearch.Authors:
|
||||
log.debug('Authors Search')
|
||||
search_results = self.plugin.manager.get_all_objects(Author,
|
||||
Author.display_name.like('%' + search_keywords + '%'), Author.display_name.asc())
|
||||
search_string = '%' + search_keywords + '%'
|
||||
search_results = self.plugin.manager.get_all_objects(
|
||||
Author, Author.display_name.like(search_string), Author.display_name.asc())
|
||||
self.display_results_author(search_results)
|
||||
elif search_type == SongSearch.Books:
|
||||
log.debug('Books Search')
|
||||
search_results = self.plugin.manager.get_all_objects(Book,
|
||||
Book.name.like('%' + search_keywords + '%'), Book.name.asc())
|
||||
search_string = '%' + search_keywords + '%'
|
||||
search_results = self.plugin.manager.get_all_objects(Book, Book.name.like(search_string), Book.name.asc())
|
||||
song_number = False
|
||||
if not search_results:
|
||||
search_keywords = search_keywords.rpartition(' ')
|
||||
search_string = '%' + search_keywords + '%'
|
||||
search_results = self.plugin.manager.get_all_objects(Book,
|
||||
Book.name.like('%' + search_keywords[0] + '%'), Book.name.asc())
|
||||
Book.name.like(search_string), Book.name.asc())
|
||||
song_number = re.sub(r'[^0-9]', '', search_keywords[2])
|
||||
self.display_results_book(search_results, song_number)
|
||||
elif search_type == SongSearch.Themes:
|
||||
log.debug('Theme Search')
|
||||
search_results = self.plugin.manager.get_all_objects(Song,
|
||||
Song.theme_name.like('%' + search_keywords + '%'))
|
||||
search_string = '%' + search_keywords + '%'
|
||||
search_results = self.plugin.manager.get_all_objects(Song, Song.theme_name.like(search_string))
|
||||
self.display_results_song(search_results)
|
||||
self.check_search_result()
|
||||
|
||||
def search_entire(self, search_keywords):
|
||||
return self.plugin.manager.get_all_objects(Song,
|
||||
or_(Song.search_title.like('%' + clean_string(search_keywords) + '%'),
|
||||
Song.search_lyrics.like('%' + clean_string(search_keywords) + '%'),
|
||||
Song.comments.like('%' + search_keywords.lower() + '%')))
|
||||
search_string = '%' + clean_string(search_keywords) + '%'
|
||||
return self.plugin.manager.get_all_objects(
|
||||
Song, or_(Song.search_title.like(search_string), Song.search_lyrics.like(search_string),
|
||||
Song.comments.like(search_string)))
|
||||
|
||||
def on_song_list_load(self):
|
||||
"""
|
||||
Handle the exit from the edit dialog and trigger remote updates
|
||||
of songs
|
||||
Handle the exit from the edit dialog and trigger remote updates of songs
|
||||
"""
|
||||
log.debug('on_song_list_load - start')
|
||||
# Called to redisplay the song list screen edit from a search or from the exit of the Song edit dialog. If
|
||||
# remote editing is active Trigger it and clean up so it will not update again. Push edits to the service
|
||||
# manager to update items
|
||||
if self.edit_item and self.updateServiceOnEdit and not self.remote_triggered:
|
||||
if self.edit_item and self.update_service_on_edit and not self.remote_triggered:
|
||||
item = self.build_service_item(self.edit_item)
|
||||
self.service_manager.replace_service_item(item)
|
||||
self.on_search_text_button_clicked()
|
||||
log.debug('on_song_list_load - finished')
|
||||
|
||||
def display_results_song(self, searchresults):
|
||||
def display_results_song(self, search_results):
|
||||
log.debug('display results Song')
|
||||
self.save_auto_select_id()
|
||||
self.list_view.clear()
|
||||
searchresults.sort(key=lambda song: song.sort_key)
|
||||
for song in searchresults:
|
||||
search_results.sort(key=lambda song: song.sort_key)
|
||||
for song in search_results:
|
||||
# Do not display temporary songs
|
||||
if song.temporary:
|
||||
continue
|
||||
@ -244,10 +244,10 @@ class SongMediaItem(MediaManagerItem):
|
||||
self.list_view.setCurrentItem(song_name)
|
||||
self.auto_select_id = -1
|
||||
|
||||
def display_results_author(self, searchresults):
|
||||
def display_results_author(self, search_results):
|
||||
log.debug('display results Author')
|
||||
self.list_view.clear()
|
||||
for author in searchresults:
|
||||
for author in search_results:
|
||||
for song in author.songs:
|
||||
# Do not display temporary songs
|
||||
if song.temporary:
|
||||
@ -257,12 +257,11 @@ class SongMediaItem(MediaManagerItem):
|
||||
song_name.setData(QtCore.Qt.UserRole, song.id)
|
||||
self.list_view.addItem(song_name)
|
||||
|
||||
def display_results_book(self, searchresults, song_number=False):
|
||||
def display_results_book(self, search_results, song_number=False):
|
||||
log.debug('display results Book')
|
||||
self.list_view.clear()
|
||||
for book in searchresults:
|
||||
songs = sorted(book.songs, key=lambda song:
|
||||
int(re.match(r'[0-9]+', '0' + song.song_number).group()))
|
||||
for book in search_results:
|
||||
songs = sorted(book.songs, key=lambda song: int(re.match(r'[0-9]+', '0' + song.song_number).group()))
|
||||
for song in songs:
|
||||
# Do not display temporary songs
|
||||
if song.temporary:
|
||||
@ -305,9 +304,9 @@ class SongMediaItem(MediaManagerItem):
|
||||
Registry().execute('songs_load_list')
|
||||
|
||||
def on_export_click(self):
|
||||
if not hasattr(self, 'exportWizard'):
|
||||
self.exportWizard = SongExportForm(self, self.plugin)
|
||||
self.exportWizard.exec_()
|
||||
if not hasattr(self, 'export_wizard'):
|
||||
self.export_wizard = SongExportForm(self, self.plugin)
|
||||
self.export_wizard.exec_()
|
||||
|
||||
def on_new_click(self):
|
||||
log.debug('on_new_click')
|
||||
@ -362,10 +361,10 @@ class SongMediaItem(MediaManagerItem):
|
||||
"""
|
||||
if check_item_selected(self.list_view, UiStrings().SelectDelete):
|
||||
items = self.list_view.selectedIndexes()
|
||||
if QtGui.QMessageBox.question(self,
|
||||
UiStrings().ConfirmDelete,
|
||||
if QtGui.QMessageBox.question(
|
||||
self, UiStrings().ConfirmDelete,
|
||||
translate('SongsPlugin.MediaItem', 'Are you sure you want to delete the %n selected song(s)?', '',
|
||||
QtCore.QCoreApplication.CodecForTr, len(items)),
|
||||
QtCore.QCoreApplication.CodecForTr, len(items)),
|
||||
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No),
|
||||
QtGui.QMessageBox.Yes) == QtGui.QMessageBox.No:
|
||||
return
|
||||
@ -388,17 +387,23 @@ class SongMediaItem(MediaManagerItem):
|
||||
self.edit_item = self.list_view.currentItem()
|
||||
item_id = self.edit_item.data(QtCore.Qt.UserRole)
|
||||
old_song = self.plugin.manager.get_object(Song, item_id)
|
||||
song_xml = self.openLyrics.song_to_xml(old_song)
|
||||
new_song = self.openLyrics.xml_to_song(song_xml)
|
||||
new_song.title = '%s <%s>' % (new_song.title,
|
||||
translate('SongsPlugin.MediaItem', 'copy', 'For song cloning'))
|
||||
song_xml = self.open_lyrics.song_to_xml(old_song)
|
||||
new_song = self.open_lyrics.xml_to_song(song_xml)
|
||||
new_song.title = '%s <%s>' % \
|
||||
(new_song.title, translate('SongsPlugin.MediaItem', 'copy', 'For song cloning'))
|
||||
self.plugin.manager.save_object(new_song)
|
||||
self.on_song_list_load()
|
||||
|
||||
def generate_slide_data(self, service_item, item=None, xmlVersion=False,
|
||||
remote=False, context=ServiceItemContext.Service):
|
||||
def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False,
|
||||
context=ServiceItemContext.Service):
|
||||
"""
|
||||
Generate the slide data. Needs to be implemented by the plugin.
|
||||
|
||||
:param service_item: The service item to be built on
|
||||
:param item: The Song item to be used
|
||||
:param xml_version: The xml version (not used)
|
||||
:param remote: Triggered from remote
|
||||
:param context: Why is it being generated
|
||||
"""
|
||||
log.debug('generate_slide_data: %s, %s, %s' % (service_item, item, self.remote_song))
|
||||
item_id = self._get_id_of_item_to_generate(item, self.remote_song)
|
||||
@ -411,50 +416,45 @@ class SongMediaItem(MediaManagerItem):
|
||||
song = self.plugin.manager.get_object(Song, item_id)
|
||||
service_item.theme = song.theme_name
|
||||
service_item.edit_id = item_id
|
||||
if song.lyrics.startswith('<?xml version='):
|
||||
verse_list = SongXML().get_verses(song.lyrics)
|
||||
# no verse list or only 1 space (in error)
|
||||
verse_tags_translated = False
|
||||
if VerseType.from_translated_string(str(verse_list[0][0]['type'])) is not None:
|
||||
verse_tags_translated = True
|
||||
if not song.verse_order.strip():
|
||||
for verse in verse_list:
|
||||
# We cannot use from_loose_input() here, because database is supposed to contain English lowercase
|
||||
# singlechar tags.
|
||||
verse_tag = verse[0]['type']
|
||||
verse_index = None
|
||||
if len(verse_tag) > 1:
|
||||
verse_index = VerseType.from_translated_string(verse_tag)
|
||||
if verse_index is None:
|
||||
verse_index = VerseType.from_string(verse_tag, None)
|
||||
verse_list = SongXML().get_verses(song.lyrics)
|
||||
# no verse list or only 1 space (in error)
|
||||
verse_tags_translated = False
|
||||
if VerseType.from_translated_string(str(verse_list[0][0]['type'])) is not None:
|
||||
verse_tags_translated = True
|
||||
if not song.verse_order.strip():
|
||||
for verse in verse_list:
|
||||
# We cannot use from_loose_input() here, because database is supposed to contain English lowercase
|
||||
# singlechar tags.
|
||||
verse_tag = verse[0]['type']
|
||||
verse_index = None
|
||||
if len(verse_tag) > 1:
|
||||
verse_index = VerseType.from_translated_string(verse_tag)
|
||||
if verse_index is None:
|
||||
verse_index = VerseType.from_tag(verse_tag)
|
||||
verse_tag = VerseType.translated_tags[verse_index].upper()
|
||||
verse_def = '%s%s' % (verse_tag, verse[0]['label'])
|
||||
service_item.add_from_text(str(verse[1]), verse_def)
|
||||
else:
|
||||
# Loop through the verse list and expand the song accordingly.
|
||||
for order in song.verse_order.lower().split():
|
||||
if not order:
|
||||
break
|
||||
for verse in verse_list:
|
||||
if verse[0]['type'][0].lower() == order[0] and (verse[0]['label'].lower() == order[1:] or \
|
||||
not order[1:]):
|
||||
if verse_tags_translated:
|
||||
verse_index = VerseType.from_translated_tag(verse[0]['type'])
|
||||
else:
|
||||
verse_index = VerseType.from_tag(verse[0]['type'])
|
||||
verse_tag = VerseType.translated_tags[verse_index]
|
||||
verse_def = '%s%s' % (verse_tag, verse[0]['label'])
|
||||
service_item.add_from_text(verse[1], verse_def)
|
||||
verse_index = VerseType.from_string(verse_tag, None)
|
||||
if verse_index is None:
|
||||
verse_index = VerseType.from_tag(verse_tag)
|
||||
verse_tag = VerseType.translated_tags[verse_index].upper()
|
||||
verse_def = '%s%s' % (verse_tag, verse[0]['label'])
|
||||
service_item.add_from_text(str(verse[1]), verse_def)
|
||||
else:
|
||||
verses = song.lyrics.split('\n\n')
|
||||
for slide in verses:
|
||||
service_item.add_from_text(str(slide))
|
||||
# Loop through the verse list and expand the song accordingly.
|
||||
for order in song.verse_order.lower().split():
|
||||
if not order:
|
||||
break
|
||||
for verse in verse_list:
|
||||
if verse[0]['type'][0].lower() == \
|
||||
order[0] and (verse[0]['label'].lower() == order[1:] or not order[1:]):
|
||||
if verse_tags_translated:
|
||||
verse_index = VerseType.from_translated_tag(verse[0]['type'])
|
||||
else:
|
||||
verse_index = VerseType.from_tag(verse[0]['type'])
|
||||
verse_tag = VerseType.translated_tags[verse_index]
|
||||
verse_def = '%s%s' % (verse_tag, verse[0]['label'])
|
||||
service_item.add_from_text(verse[1], verse_def)
|
||||
service_item.title = song.title
|
||||
author_list = self.generate_footer(service_item, song)
|
||||
service_item.data_string = {'title': song.search_title, 'authors': ', '.join(author_list)}
|
||||
service_item.xml_version = self.openLyrics.song_to_xml(song)
|
||||
service_item.xml_version = self.open_lyrics.song_to_xml(song)
|
||||
# Add the audio file to the service item.
|
||||
if song.media_files:
|
||||
service_item.add_capability(ItemCapabilities.HasBackgroundAudio)
|
||||
@ -466,11 +466,8 @@ class SongMediaItem(MediaManagerItem):
|
||||
Generates the song footer based on a song and adds details to a service item.
|
||||
author_list is only required for initial song generation.
|
||||
|
||||
``item``
|
||||
The service item to be amended
|
||||
|
||||
``song``
|
||||
The song to be used to generate the footer
|
||||
:param item: The service item to be amended
|
||||
:param song: The song to be used to generate the footer
|
||||
"""
|
||||
author_list = [str(author.display_name) for author in song.authors]
|
||||
item.audit = [
|
||||
@ -481,8 +478,8 @@ class SongMediaItem(MediaManagerItem):
|
||||
item.raw_footer.append(create_separated_list(author_list))
|
||||
item.raw_footer.append(song.copyright)
|
||||
if Settings().value('core/ccli number'):
|
||||
item.raw_footer.append(translate('SongsPlugin.MediaItem', 'CCLI License: ') +
|
||||
Settings().value('core/ccli number'))
|
||||
item.raw_footer.append(translate('SongsPlugin.MediaItem',
|
||||
'CCLI License: ') + Settings().value('core/ccli number'))
|
||||
return author_list
|
||||
|
||||
def service_load(self, item):
|
||||
@ -500,8 +497,8 @@ class SongMediaItem(MediaManagerItem):
|
||||
Song.search_title == (re.compile(r'\W+', re.UNICODE).sub(' ',
|
||||
item.data_string['title'].strip()) + '@').strip().lower(), Song.search_title.asc())
|
||||
else:
|
||||
search_results = self.plugin.manager.get_all_objects(Song,
|
||||
Song.search_title == item.data_string['title'], Song.search_title.asc())
|
||||
search_results = self.plugin.manager.get_all_objects(
|
||||
Song, Song.search_title == item.data_string['title'], Song.search_title.asc())
|
||||
edit_id = 0
|
||||
add_song = True
|
||||
if search_results:
|
||||
@ -521,16 +518,16 @@ class SongMediaItem(MediaManagerItem):
|
||||
# If there's any backing tracks, copy them over.
|
||||
if item.background_audio:
|
||||
self._update_background_audio(song, item)
|
||||
if add_song and self.addSongFromService:
|
||||
song = self.openLyrics.xml_to_song(item.xml_version)
|
||||
if add_song and self.add_song_from_service:
|
||||
song = self.open_lyrics.xml_to_song(item.xml_version)
|
||||
# If there's any backing tracks, copy them over.
|
||||
if item.background_audio:
|
||||
self._update_background_audio(song, item)
|
||||
editId = song.id
|
||||
edit_id = song.id
|
||||
self.on_search_text_button_clicked()
|
||||
elif add_song and not self.addSongFromService:
|
||||
elif add_song and not self.add_song_from_service:
|
||||
# Make sure we temporary import formatting tags.
|
||||
song = self.openLyrics.xml_to_song(item.xml_version, True)
|
||||
song = self.open_lyrics.xml_to_song(item.xml_version, True)
|
||||
# If there's any backing tracks, copy them over.
|
||||
if item.background_audio:
|
||||
self._update_background_audio(song, item)
|
||||
@ -540,9 +537,11 @@ class SongMediaItem(MediaManagerItem):
|
||||
self.generate_footer(item, song)
|
||||
return item
|
||||
|
||||
def search(self, string, showError):
|
||||
def search(self, string, show_error):
|
||||
"""
|
||||
Search for some songs
|
||||
:param string: The string to show
|
||||
:param show_error: Is this an error?
|
||||
"""
|
||||
search_results = self.search_entire(string)
|
||||
return [[song.id, song.title] for song in search_results]
|
||||
|
@ -37,6 +37,7 @@ from openlp.plugins.songs.lib.songimport import SongImport
|
||||
|
||||
VERSE_TAGS = ['V', 'C', 'B', 'O', 'P', 'I', 'E']
|
||||
|
||||
|
||||
class MediaShoutImport(SongImport):
|
||||
"""
|
||||
The :class:`MediaShoutImport` class provides the ability to import the
|
||||
@ -48,61 +49,57 @@ class MediaShoutImport(SongImport):
|
||||
"""
|
||||
SongImport.__init__(self, manager, **kwargs)
|
||||
|
||||
def doImport(self):
|
||||
def do_import(self):
|
||||
"""
|
||||
Receive a single file to import.
|
||||
"""
|
||||
try:
|
||||
conn = pyodbc.connect('DRIVER={Microsoft Access Driver (*.mdb)};'
|
||||
'DBQ=%s;PWD=6NOZ4eHK7k' % self.import_source)
|
||||
conn = pyodbc.connect('DRIVER={Microsoft Access Driver (*.mdb)};DBQ=%s;PWD=6NOZ4eHK7k' %
|
||||
self.import_source)
|
||||
except:
|
||||
# Unfortunately no specific exception type
|
||||
self.logError(self.import_source,
|
||||
translate('SongsPlugin.MediaShoutImport', 'Unable to open the MediaShout database.'))
|
||||
self.log_error(self.import_source, translate('SongsPlugin.MediaShoutImport',
|
||||
'Unable to open the MediaShout database.'))
|
||||
return
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('SELECT Record, Title, Author, Copyright, '
|
||||
'SongID, CCLI, Notes FROM Songs ORDER BY Title')
|
||||
cursor.execute('SELECT Record, Title, Author, Copyright, SongID, CCLI, Notes FROM Songs ORDER BY Title')
|
||||
songs = cursor.fetchall()
|
||||
self.import_wizard.progress_bar.setMaximum(len(songs))
|
||||
for song in songs:
|
||||
if self.stop_import_flag:
|
||||
break
|
||||
cursor.execute('SELECT Type, Number, Text FROM Verses '
|
||||
'WHERE Record = %s ORDER BY Type, Number' % song.Record)
|
||||
cursor.execute('SELECT Type, Number, Text FROM Verses WHERE Record = %s ORDER BY Type, Number'
|
||||
% song.Record)
|
||||
verses = cursor.fetchall()
|
||||
cursor.execute('SELECT Type, Number, POrder FROM PlayOrder '
|
||||
'WHERE Record = %s ORDER BY POrder' % song.Record)
|
||||
cursor.execute('SELECT Type, Number, POrder FROM PlayOrder WHERE Record = %s ORDER BY POrder' % song.Record)
|
||||
verse_order = cursor.fetchall()
|
||||
cursor.execute('SELECT Name FROM Themes INNER JOIN SongThemes '
|
||||
'ON SongThemes.ThemeId = Themes.ThemeId '
|
||||
'WHERE SongThemes.Record = %s' % song.Record)
|
||||
cursor.execute('SELECT Name FROM Themes INNER JOIN SongThemes ON SongThemes.ThemeId = Themes.ThemeId '
|
||||
'WHERE SongThemes.Record = %s' % song.Record)
|
||||
topics = cursor.fetchall()
|
||||
cursor.execute('SELECT Name FROM Groups INNER JOIN SongGroups '
|
||||
'ON SongGroups.GroupId = Groups.GroupId '
|
||||
'WHERE SongGroups.Record = %s' % song.Record)
|
||||
cursor.execute('SELECT Name FROM Groups INNER JOIN SongGroups ON SongGroups.GroupId = Groups.GroupId '
|
||||
'WHERE SongGroups.Record = %s' % song.Record)
|
||||
topics += cursor.fetchall()
|
||||
self.processSong(song, verses, verse_order, topics)
|
||||
self.process_song(song, verses, verse_order, topics)
|
||||
|
||||
def processSong(self, song, verses, verse_order, topics):
|
||||
def process_song(self, song, verses, verse_order, topics):
|
||||
"""
|
||||
Create the song, i.e. title, verse etc.
|
||||
"""
|
||||
self.setDefaults()
|
||||
self.set_defaults()
|
||||
self.title = song.Title
|
||||
self.parse_author(song.Author)
|
||||
self.addCopyright(song.Copyright)
|
||||
self.add_copyright(song.Copyright)
|
||||
self.comments = song.Notes
|
||||
for topic in topics:
|
||||
self.topics.append(topic.Name)
|
||||
if '-' in song.SongID:
|
||||
self.songBookName, self.songNumber = song.SongID.split('-', 1)
|
||||
self.song_book_name, self.song_number = song.SongID.split('-', 1)
|
||||
else:
|
||||
self.songBookName = song.SongID
|
||||
self.song_book_name = song.SongID
|
||||
for verse in verses:
|
||||
tag = VERSE_TAGS[verse.Type] + str(verse.Number) if verse.Type < len(VERSE_TAGS) else 'O'
|
||||
self.addVerse(verse.Text, tag)
|
||||
self.add_verse(verse.Text, tag)
|
||||
for order in verse_order:
|
||||
if order.Type < len(VERSE_TAGS):
|
||||
self.verseOrderList.append(VERSE_TAGS[order.Type] + str(order.Number))
|
||||
self.verse_order_list.append(VERSE_TAGS[order.Type] + str(order.Number))
|
||||
self.finish()
|
||||
|
@ -45,6 +45,7 @@ from .songimport import SongImport
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OpenLPSongImport(SongImport):
|
||||
"""
|
||||
The :class:`OpenLPSongImport` class provides OpenLP with the ability to
|
||||
@ -54,20 +55,17 @@ class OpenLPSongImport(SongImport):
|
||||
"""
|
||||
Initialise the import.
|
||||
|
||||
``manager``
|
||||
The song manager for the running OpenLP installation.
|
||||
|
||||
``source_db``
|
||||
The database providing the data to import.
|
||||
:param manager: The song manager for the running OpenLP installation.
|
||||
:param kwargs: The database providing the data to import.
|
||||
"""
|
||||
SongImport.__init__(self, manager, **kwargs)
|
||||
self.sourceSession = None
|
||||
self.source_session = None
|
||||
|
||||
def doImport(self, progressDialog=None):
|
||||
def do_import(self, progress_dialog=None):
|
||||
"""
|
||||
Run the import for an OpenLP version 2 song database.
|
||||
|
||||
``progressDialog``
|
||||
``progress_dialog``
|
||||
The QProgressDialog used when importing songs from the FRW.
|
||||
"""
|
||||
|
||||
@ -77,28 +75,24 @@ class OpenLPSongImport(SongImport):
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class OldBook(BaseModel):
|
||||
"""
|
||||
Book model
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class OldMediaFile(BaseModel):
|
||||
"""
|
||||
MediaFile model
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class OldSong(BaseModel):
|
||||
"""
|
||||
Song model
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class OldTopic(BaseModel):
|
||||
"""
|
||||
Topic model
|
||||
@ -107,15 +101,15 @@ class OpenLPSongImport(SongImport):
|
||||
|
||||
# Check the file type
|
||||
if not self.import_source.endswith('.sqlite'):
|
||||
self.logError(self.import_source,
|
||||
translate('SongsPlugin.OpenLPSongImport', 'Not a valid OpenLP 2.0 song database.'))
|
||||
self.log_error(self.import_source, translate('SongsPlugin.OpenLPSongImport',
|
||||
'Not a valid OpenLP 2.0 song database.'))
|
||||
return
|
||||
self.import_source = 'sqlite:///%s' % self.import_source
|
||||
# Load the db file
|
||||
engine = create_engine(self.import_source)
|
||||
source_meta = MetaData()
|
||||
source_meta.reflect(engine)
|
||||
self.sourceSession = scoped_session(sessionmaker(bind=engine))
|
||||
self.source_session = scoped_session(sessionmaker(bind=engine))
|
||||
if 'media_files' in list(source_meta.tables.keys()):
|
||||
has_media_files = True
|
||||
else:
|
||||
@ -143,14 +137,13 @@ class OpenLPSongImport(SongImport):
|
||||
}
|
||||
if has_media_files:
|
||||
if isinstance(source_media_files_songs_table, Table):
|
||||
song_props['media_files'] = relation(OldMediaFile,
|
||||
backref='songs',
|
||||
secondary=source_media_files_songs_table)
|
||||
song_props['media_files'] = relation(OldMediaFile, backref='songs',
|
||||
secondary=source_media_files_songs_table)
|
||||
else:
|
||||
song_props['media_files'] = relation(OldMediaFile,
|
||||
backref='songs',
|
||||
foreign_keys=[source_media_files_table.c.song_id],
|
||||
primaryjoin=source_songs_table.c.id == source_media_files_table.c.song_id)
|
||||
song_props['media_files'] = \
|
||||
relation(OldMediaFile, backref='songs',
|
||||
foreign_keys=[source_media_files_table.c.song_id],
|
||||
primaryjoin=source_songs_table.c.id == source_media_files_table.c.song_id)
|
||||
try:
|
||||
class_mapper(OldAuthor)
|
||||
except UnmappedClassError:
|
||||
@ -168,7 +161,7 @@ class OpenLPSongImport(SongImport):
|
||||
except UnmappedClassError:
|
||||
mapper(OldTopic, source_topics_table)
|
||||
|
||||
source_songs = self.sourceSession.query(OldSong).all()
|
||||
source_songs = self.source_session.query(OldSong).all()
|
||||
if self.import_wizard:
|
||||
self.import_wizard.progress_bar.setMaximum(len(source_songs))
|
||||
for song in source_songs:
|
||||
@ -212,17 +205,17 @@ class OpenLPSongImport(SongImport):
|
||||
if has_media_files:
|
||||
if song.media_files:
|
||||
for media_file in song.media_files:
|
||||
existing_media_file = self.manager.get_object_filtered(MediaFile,
|
||||
MediaFile.file_name == media_file.file_name)
|
||||
existing_media_file = self.manager.get_object_filtered(
|
||||
MediaFile, MediaFile.file_name == media_file.file_name)
|
||||
if existing_media_file:
|
||||
new_song.media_files.append(existing_media_file)
|
||||
else:
|
||||
new_song.media_files.append(MediaFile.populate(file_name=media_file.file_name))
|
||||
clean_song(self.manager, new_song)
|
||||
self.manager.save_object(new_song)
|
||||
if progressDialog:
|
||||
progressDialog.setValue(progressDialog.value() + 1)
|
||||
progressDialog.setLabelText(WizardStrings.ImportingType % new_song.title)
|
||||
if progress_dialog:
|
||||
progress_dialog.setValue(progress_dialog.value() + 1)
|
||||
progress_dialog.setLabelText(WizardStrings.ImportingType % new_song.title)
|
||||
else:
|
||||
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % new_song.title)
|
||||
if self.stop_import_flag:
|
||||
|
@ -58,20 +58,19 @@ class OooImport(SongImport):
|
||||
"""
|
||||
def __init__(self, manager, **kwargs):
|
||||
"""
|
||||
Initialise the class. Requires a songmanager class which is passed
|
||||
to SongImport for writing song to disk
|
||||
Initialise the class. Requires a songmanager class which is passed to SongImport for writing song to disk
|
||||
"""
|
||||
SongImport.__init__(self, manager, **kwargs)
|
||||
self.document = None
|
||||
self.processStarted = False
|
||||
self.process_started = False
|
||||
|
||||
def doImport(self):
|
||||
def do_import(self):
|
||||
if not isinstance(self.import_source, list):
|
||||
return
|
||||
try:
|
||||
self.startOoo()
|
||||
self.start_ooo()
|
||||
except NoConnectException as exc:
|
||||
self.logError(
|
||||
self.log_error(
|
||||
self.import_source[0],
|
||||
translate('SongsPlugin.SongImport', 'Cannot access OpenOffice or LibreOffice'))
|
||||
log.error(exc)
|
||||
@ -82,34 +81,34 @@ class OooImport(SongImport):
|
||||
break
|
||||
filename = str(filename)
|
||||
if os.path.isfile(filename):
|
||||
self.openOooFile(filename)
|
||||
self.open_ooo_file(filename)
|
||||
if self.document:
|
||||
self.processOooDocument()
|
||||
self.closeOooFile()
|
||||
self.process_ooo_document()
|
||||
self.close_ooo_file()
|
||||
else:
|
||||
self.logError(self.filepath, translate('SongsPlugin.SongImport', 'Unable to open file'))
|
||||
self.log_error(self.file_path, translate('SongsPlugin.SongImport', 'Unable to open file'))
|
||||
else:
|
||||
self.logError(self.filepath, translate('SongsPlugin.SongImport', 'File not found'))
|
||||
self.closeOoo()
|
||||
self.log_error(self.file_path, translate('SongsPlugin.SongImport', 'File not found'))
|
||||
self.close_ooo()
|
||||
|
||||
def processOooDocument(self):
|
||||
def process_ooo_document(self):
|
||||
"""
|
||||
Handle the import process for OpenOffice files. This method facilitates
|
||||
allowing subclasses to handle specific types of OpenOffice files.
|
||||
"""
|
||||
if self.document.supportsService("com.sun.star.presentation.PresentationDocument"):
|
||||
self.processPres()
|
||||
self.process_presentation()
|
||||
if self.document.supportsService("com.sun.star.text.TextDocument"):
|
||||
self.processDoc()
|
||||
self.process_doc()
|
||||
|
||||
def startOoo(self):
|
||||
def start_ooo(self):
|
||||
"""
|
||||
Start OpenOffice.org process
|
||||
TODO: The presentation/Impress plugin may already have it running
|
||||
"""
|
||||
if os.name == 'nt':
|
||||
self.startOooProcess()
|
||||
self.desktop = self.oooManager.createInstance('com.sun.star.frame.Desktop')
|
||||
self.start_ooo_process()
|
||||
self.desktop = self.ooo_manager.createInstance('com.sun.star.frame.Desktop')
|
||||
else:
|
||||
context = uno.getComponentContext()
|
||||
resolver = context.ServiceManager.createInstanceWithContext('com.sun.star.bridge.UnoUrlResolver', context)
|
||||
@ -121,7 +120,7 @@ class OooImport(SongImport):
|
||||
except NoConnectException:
|
||||
time.sleep(0.1)
|
||||
log.exception("Failed to resolve uno connection")
|
||||
self.startOooProcess()
|
||||
self.start_ooo_process()
|
||||
loop += 1
|
||||
else:
|
||||
manager = uno_instance.ServiceManager
|
||||
@ -129,60 +128,62 @@ class OooImport(SongImport):
|
||||
return
|
||||
raise Exception('Unable to start LibreOffice')
|
||||
|
||||
def startOooProcess(self):
|
||||
def start_ooo_process(self):
|
||||
"""
|
||||
Start the OO Process
|
||||
"""
|
||||
try:
|
||||
if os.name == 'nt':
|
||||
self.oooManager = Dispatch('com.sun.star.ServiceManager')
|
||||
self.oooManager._FlagAsMethod('Bridge_GetStruct')
|
||||
self.oooManager._FlagAsMethod('Bridge_GetValueObject')
|
||||
self.ooo_manager = Dispatch('com.sun.star.ServiceManager')
|
||||
self.ooo_manager._FlagAsMethod('Bridge_GetStruct')
|
||||
self.ooo_manager._FlagAsMethod('Bridge_GetValueObject')
|
||||
else:
|
||||
cmd = get_uno_command()
|
||||
process = QtCore.QProcess()
|
||||
process.startDetached(cmd)
|
||||
self.processStarted = True
|
||||
self.process_started = True
|
||||
except:
|
||||
log.exception("startOooProcess failed")
|
||||
log.exception("start_ooo_process failed")
|
||||
|
||||
def openOooFile(self, filepath):
|
||||
def open_ooo_file(self, file_path):
|
||||
"""
|
||||
Open the passed file in OpenOffice.org Impress
|
||||
"""
|
||||
self.filepath = filepath
|
||||
self.file_path = file_path
|
||||
if os.name == 'nt':
|
||||
url = filepath.replace('\\', '/')
|
||||
url = file_path.replace('\\', '/')
|
||||
url = url.replace(':', '|').replace(' ', '%20')
|
||||
url = 'file:///' + url
|
||||
else:
|
||||
url = uno.systemPathToFileUrl(filepath)
|
||||
url = uno.systemPathToFileUrl(file_path)
|
||||
properties = []
|
||||
properties = tuple(properties)
|
||||
try:
|
||||
self.document = self.desktop.loadComponentFromURL(url, '_blank',
|
||||
0, properties)
|
||||
self.document = self.desktop.loadComponentFromURL(url, '_blank', 0, properties)
|
||||
if not self.document.supportsService("com.sun.star.presentation.PresentationDocument") and not \
|
||||
self.document.supportsService("com.sun.star.text.TextDocument"):
|
||||
self.closeOooFile()
|
||||
self.close_ooo_file()
|
||||
else:
|
||||
self.import_wizard.increment_progress_bar('Processing file ' + filepath, 0)
|
||||
self.import_wizard.increment_progress_bar('Processing file ' + file_path, 0)
|
||||
except AttributeError:
|
||||
log.exception("openOooFile failed: %s", url)
|
||||
log.exception("open_ooo_file failed: %s", url)
|
||||
return
|
||||
|
||||
def closeOooFile(self):
|
||||
def close_ooo_file(self):
|
||||
"""
|
||||
Close file.
|
||||
"""
|
||||
self.document.close(True)
|
||||
self.document = None
|
||||
|
||||
def closeOoo(self):
|
||||
def close_ooo(self):
|
||||
"""
|
||||
Close OOo. But only if we started it and not on windows
|
||||
"""
|
||||
if self.processStarted:
|
||||
if self.process_started:
|
||||
self.desktop.terminate()
|
||||
|
||||
def processPres(self):
|
||||
def process_presentation(self):
|
||||
"""
|
||||
Process the file
|
||||
"""
|
||||
@ -194,47 +195,52 @@ class OooImport(SongImport):
|
||||
self.import_wizard.increment_progress_bar('Import cancelled', 0)
|
||||
return
|
||||
slide = slides.getByIndex(slide_no)
|
||||
slidetext = ''
|
||||
slide_text = ''
|
||||
for idx in range(slide.getCount()):
|
||||
shape = slide.getByIndex(idx)
|
||||
if shape.supportsService("com.sun.star.drawing.Text"):
|
||||
if shape.getString().strip() != '':
|
||||
slidetext += shape.getString().strip() + '\n\n'
|
||||
if slidetext.strip() == '':
|
||||
slidetext = '\f'
|
||||
text += slidetext
|
||||
self.processSongsText(text)
|
||||
slide_text += shape.getString().strip() + '\n\n'
|
||||
if slide_text.strip() == '':
|
||||
slide_text = '\f'
|
||||
text += slide_text
|
||||
self.process_songs_text(text)
|
||||
return
|
||||
|
||||
def processDoc(self):
|
||||
def process_doc(self):
|
||||
"""
|
||||
Process the doc file, a paragraph at a time
|
||||
"""
|
||||
text = ''
|
||||
paragraphs = self.document.getText().createEnumeration()
|
||||
while paragraphs.hasMoreElements():
|
||||
paratext = ''
|
||||
para_text = ''
|
||||
paragraph = paragraphs.nextElement()
|
||||
if paragraph.supportsService("com.sun.star.text.Paragraph"):
|
||||
textportions = paragraph.createEnumeration()
|
||||
while textportions.hasMoreElements():
|
||||
textportion = textportions.nextElement()
|
||||
if textportion.BreakType in (PAGE_BEFORE, PAGE_BOTH):
|
||||
paratext += '\f'
|
||||
paratext += textportion.getString()
|
||||
if textportion.BreakType in (PAGE_AFTER, PAGE_BOTH):
|
||||
paratext += '\f'
|
||||
text += paratext + '\n'
|
||||
self.processSongsText(text)
|
||||
text_portions = paragraph.createEnumeration()
|
||||
while text_portions.hasMoreElements():
|
||||
text_portion = text_portions.nextElement()
|
||||
if text_portion.BreakType in (PAGE_BEFORE, PAGE_BOTH):
|
||||
para_text += '\f'
|
||||
para_text += text_portion.getString()
|
||||
if text_portion.BreakType in (PAGE_AFTER, PAGE_BOTH):
|
||||
para_text += '\f'
|
||||
text += para_text + '\n'
|
||||
self.process_songs_text(text)
|
||||
|
||||
def processSongsText(self, text):
|
||||
songtexts = self.tidyText(text).split('\f')
|
||||
self.setDefaults()
|
||||
for songtext in songtexts:
|
||||
if songtext.strip():
|
||||
self.processSongText(songtext.strip())
|
||||
if self.checkComplete():
|
||||
def process_songs_text(self, text):
|
||||
"""
|
||||
Process the songs text
|
||||
|
||||
:param text: The text.
|
||||
"""
|
||||
song_texts = self.tidy_text(text).split('\f')
|
||||
self.set_defaults()
|
||||
for song_text in song_texts:
|
||||
if song_text.strip():
|
||||
self.process_song_text(song_text.strip())
|
||||
if self.check_complete():
|
||||
self.finish()
|
||||
self.setDefaults()
|
||||
if self.checkComplete():
|
||||
self.set_defaults()
|
||||
if self.check_complete():
|
||||
self.finish()
|
||||
|
@ -27,8 +27,8 @@
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
"""
|
||||
The :mod:`openlyricsexport` module provides the functionality for exporting
|
||||
songs from the database to the OpenLyrics format.
|
||||
The :mod:`openlyricsexport` module provides the functionality for exporting songs from the database to the OpenLyrics
|
||||
format.
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
@ -62,15 +62,15 @@ class OpenLyricsExport(object):
|
||||
Export the songs.
|
||||
"""
|
||||
log.debug('started OpenLyricsExport')
|
||||
openLyrics = OpenLyrics(self.manager)
|
||||
open_lyrics = OpenLyrics(self.manager)
|
||||
self.parent.progress_bar.setMaximum(len(self.songs))
|
||||
for song in self.songs:
|
||||
self.application.process_events()
|
||||
if self.parent.stop_export_flag:
|
||||
return False
|
||||
self.parent.increment_progress_bar(translate('SongsPlugin.OpenLyricsExport', 'Exporting "%s"...') %
|
||||
song.title)
|
||||
xml = openLyrics.song_to_xml(song)
|
||||
song.title)
|
||||
xml = open_lyrics.song_to_xml(song)
|
||||
tree = etree.ElementTree(etree.fromstring(xml.encode()))
|
||||
filename = '%s (%s)' % (song.title, ', '.join([author.display_name for author in song.authors]))
|
||||
filename = clean_filename(filename)
|
||||
@ -78,8 +78,8 @@ class OpenLyricsExport(object):
|
||||
filename = '%s.xml' % filename[0:250 - len(self.save_path)]
|
||||
# Pass a file object, because lxml does not cope with some special
|
||||
# characters in the path (see lp:757673 and lp:744337).
|
||||
tree.write(open(os.path.join(self.save_path, filename), 'wb'),
|
||||
encoding='utf-8', xml_declaration=True, pretty_print=True)
|
||||
tree.write(open(os.path.join(self.save_path, filename), 'wb'), encoding='utf-8', xml_declaration=True,
|
||||
pretty_print=True)
|
||||
return True
|
||||
|
||||
def _get_application(self):
|
||||
|
@ -54,9 +54,9 @@ class OpenLyricsImport(SongImport):
|
||||
"""
|
||||
log.debug('initialise OpenLyricsImport')
|
||||
SongImport.__init__(self, manager, **kwargs)
|
||||
self.openLyrics = OpenLyrics(self.manager)
|
||||
self.open_lyrics = OpenLyrics(self.manager)
|
||||
|
||||
def doImport(self):
|
||||
def do_import(self):
|
||||
"""
|
||||
Imports the songs.
|
||||
"""
|
||||
@ -71,11 +71,11 @@ class OpenLyricsImport(SongImport):
|
||||
# special characters in the path (see lp:757673 and lp:744337).
|
||||
parsed_file = etree.parse(open(file_path, 'r'), parser)
|
||||
xml = etree.tostring(parsed_file).decode()
|
||||
self.openLyrics.xml_to_song(xml)
|
||||
self.open_lyrics.xml_to_song(xml)
|
||||
except etree.XMLSyntaxError:
|
||||
log.exception('XML syntax error in file %s' % file_path)
|
||||
self.logError(file_path, SongStrings.XMLSyntaxError)
|
||||
self.log_error(file_path, SongStrings.XMLSyntaxError)
|
||||
except OpenLyricsError as exception:
|
||||
log.exception('OpenLyricsException %d in file %s: %s'
|
||||
% (exception.type, file_path, exception.log_message))
|
||||
self.logError(file_path, exception.display_message)
|
||||
log.exception('OpenLyricsException %d in file %s: %s' %
|
||||
(exception.type, file_path, exception.log_message))
|
||||
self.log_error(file_path, exception.display_message)
|
||||
|
@ -109,34 +109,34 @@ class OpenSongImport(SongImport):
|
||||
"""
|
||||
SongImport.__init__(self, manager, **kwargs)
|
||||
|
||||
def doImport(self):
|
||||
def do_import(self):
|
||||
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
|
||||
for filename in self.import_source:
|
||||
if self.stop_import_flag:
|
||||
return
|
||||
song_file = open(filename)
|
||||
self.doImportFile(song_file)
|
||||
self.do_import_file(song_file)
|
||||
song_file.close()
|
||||
|
||||
def doImportFile(self, file):
|
||||
def do_import_file(self, file):
|
||||
"""
|
||||
Process the OpenSong file - pass in a file-like object, not a file path.
|
||||
"""
|
||||
self.setDefaults()
|
||||
self.set_defaults()
|
||||
try:
|
||||
tree = objectify.parse(file)
|
||||
except (Error, LxmlError):
|
||||
self.logError(file.name, SongStrings.XMLSyntaxError)
|
||||
self.log_error(file.name, SongStrings.XMLSyntaxError)
|
||||
log.exception('Error parsing XML')
|
||||
return
|
||||
root = tree.getroot()
|
||||
if root.tag != 'song':
|
||||
self.logError(file.name, str(
|
||||
translate('SongsPlugin.OpenSongImport', ('Invalid OpenSong song file. Missing song tag.'))))
|
||||
self.log_error(file.name, str(
|
||||
translate('SongsPlugin.OpenSongImport', 'Invalid OpenSong song file. Missing song tag.')))
|
||||
return
|
||||
fields = dir(root)
|
||||
decode = {
|
||||
'copyright': self.addCopyright,
|
||||
'copyright': self.add_copyright,
|
||||
'ccli': 'ccli_number',
|
||||
'author': self.parse_author,
|
||||
'title': 'title',
|
||||
@ -207,7 +207,7 @@ class OpenSongImport(SongImport):
|
||||
verses[verse_tag][verse_num][inst] = []
|
||||
our_verse_order.append([verse_tag, verse_num, inst])
|
||||
# Tidy text and remove the ____s from extended words
|
||||
this_line = self.tidyText(this_line)
|
||||
this_line = self.tidy_text(this_line)
|
||||
this_line = this_line.replace('_', '')
|
||||
this_line = this_line.replace('|', '\n')
|
||||
this_line = this_line.strip()
|
||||
@ -218,15 +218,15 @@ class OpenSongImport(SongImport):
|
||||
for (verse_tag, verse_num, inst) in our_verse_order:
|
||||
lines = '\n'.join(verses[verse_tag][verse_num][inst])
|
||||
length = 0
|
||||
while(length < len(verse_num) and verse_num[length].isnumeric()):
|
||||
while length < len(verse_num) and verse_num[length].isnumeric():
|
||||
length += 1
|
||||
verse_def = '%s%s' % (verse_tag, verse_num[:length])
|
||||
verse_joints[verse_def] = '%s\n[---]\n%s' % (verse_joints[verse_def], lines) \
|
||||
if verse_def in verse_joints else lines
|
||||
for verse_def, lines in verse_joints.items():
|
||||
self.addVerse(lines, verse_def)
|
||||
self.add_verse(lines, verse_def)
|
||||
if not self.verses:
|
||||
self.addVerse('')
|
||||
self.add_verse('')
|
||||
# figure out the presentation order, if present
|
||||
if 'presentation' in fields and root.presentation:
|
||||
order = str(root.presentation)
|
||||
@ -246,9 +246,9 @@ class OpenSongImport(SongImport):
|
||||
verse_num = '1'
|
||||
verse_def = '%s%s' % (verse_tag, verse_num)
|
||||
if verse_num in verses.get(verse_tag, {}):
|
||||
self.verseOrderList.append(verse_def)
|
||||
self.verse_order_list.append(verse_def)
|
||||
else:
|
||||
log.info('Got order %s but not in verse tags, dropping'
|
||||
'this item from presentation order', verse_def)
|
||||
log.info('Got order %s but not in verse tags, dropping this item from presentation order',
|
||||
verse_def)
|
||||
if not self.finish():
|
||||
self.logError(file.name)
|
||||
self.log_error(file.name)
|
||||
|
@ -39,6 +39,7 @@ from openlp.plugins.songs.lib.songimport import SongImport
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PowerSongImport(SongImport):
|
||||
"""
|
||||
The :class:`PowerSongImport` class provides the ability to import song files
|
||||
@ -73,7 +74,7 @@ class PowerSongImport(SongImport):
|
||||
* .song
|
||||
"""
|
||||
@staticmethod
|
||||
def isValidSource(import_source):
|
||||
def is_valid_source(import_source):
|
||||
"""
|
||||
Checks if source is a PowerSong 1.0 folder:
|
||||
* is a directory
|
||||
@ -85,12 +86,12 @@ class PowerSongImport(SongImport):
|
||||
return True
|
||||
return False
|
||||
|
||||
def doImport(self):
|
||||
def do_import(self):
|
||||
"""
|
||||
Receive either a list of files or a folder (unicode) to import.
|
||||
"""
|
||||
from .importer import SongFormat
|
||||
PS_string = SongFormat.get(SongFormat.PowerSong, 'name')
|
||||
ps_string = SongFormat.get(SongFormat.PowerSong, 'name')
|
||||
if isinstance(self.import_source, str):
|
||||
if os.path.isdir(self.import_source):
|
||||
dir = self.import_source
|
||||
@ -101,27 +102,27 @@ class PowerSongImport(SongImport):
|
||||
else:
|
||||
self.import_source = ''
|
||||
if not self.import_source or not isinstance(self.import_source, list):
|
||||
self.logError(translate('SongsPlugin.PowerSongImport', 'No songs to import.'),
|
||||
translate('SongsPlugin.PowerSongImport', 'No %s files found.') % PS_string)
|
||||
self.log_error(translate('SongsPlugin.PowerSongImport', 'No songs to import.'),
|
||||
translate('SongsPlugin.PowerSongImport', 'No %s files found.') % ps_string)
|
||||
return
|
||||
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
|
||||
for file in self.import_source:
|
||||
if self.stop_import_flag:
|
||||
return
|
||||
self.setDefaults()
|
||||
self.set_defaults()
|
||||
parse_error = False
|
||||
with open(file, 'rb') as song_data:
|
||||
while True:
|
||||
try:
|
||||
label = self._readString(song_data)
|
||||
label = self._read_string(song_data)
|
||||
if not label:
|
||||
break
|
||||
field = self._readString(song_data)
|
||||
field = self._read_string(song_data)
|
||||
except ValueError:
|
||||
parse_error = True
|
||||
self.logError(os.path.basename(file), str(
|
||||
self.log_error(os.path.basename(file), str(
|
||||
translate('SongsPlugin.PowerSongImport', 'Invalid %s file. Unexpected byte value.')) %
|
||||
PS_string)
|
||||
ps_string)
|
||||
break
|
||||
else:
|
||||
if label == 'TITLE':
|
||||
@ -130,38 +131,38 @@ class PowerSongImport(SongImport):
|
||||
self.parse_author(field)
|
||||
elif label == 'COPYRIGHTLINE':
|
||||
found_copyright = True
|
||||
self._parseCopyrightCCLI(field)
|
||||
self._parse_copyright_cCCLI(field)
|
||||
elif label == 'PART':
|
||||
self.addVerse(field)
|
||||
self.add_verse(field)
|
||||
if parse_error:
|
||||
continue
|
||||
# Check that file had TITLE field
|
||||
if not self.title:
|
||||
self.logError(os.path.basename(file), str(
|
||||
translate('SongsPlugin.PowerSongImport', 'Invalid %s file. Missing "TITLE" header.')) % PS_string)
|
||||
self.log_error(os.path.basename(file), str(
|
||||
translate('SongsPlugin.PowerSongImport', 'Invalid %s file. Missing "TITLE" header.')) % ps_string)
|
||||
continue
|
||||
# Check that file had COPYRIGHTLINE label
|
||||
if not found_copyright:
|
||||
self.logError(self.title, str(
|
||||
self.log_error(self.title, str(
|
||||
translate('SongsPlugin.PowerSongImport', 'Invalid %s file. Missing "COPYRIGHTLINE" header.')) %
|
||||
PS_string)
|
||||
ps_string)
|
||||
continue
|
||||
# Check that file had at least one verse
|
||||
if not self.verses:
|
||||
self.logError(self.title, str(
|
||||
self.log_error(self.title, str(
|
||||
translate('SongsPlugin.PowerSongImport', 'Verses not found. Missing "PART" header.')))
|
||||
continue
|
||||
if not self.finish():
|
||||
self.logError(self.title)
|
||||
self.log_error(self.title)
|
||||
|
||||
def _readString(self, file_object):
|
||||
def _read_string(self, file_object):
|
||||
"""
|
||||
Reads in next variable-length string.
|
||||
"""
|
||||
string_len = self._read7BitEncodedInteger(file_object)
|
||||
string_len = self._read_7_bit_encoded_integer(file_object)
|
||||
return str(file_object.read(string_len), 'utf-8', 'ignore')
|
||||
|
||||
def _read7BitEncodedInteger(self, file_object):
|
||||
def _read_7_bit_encoded_integer(self, file_object):
|
||||
"""
|
||||
Reads in a 32-bit integer in compressed 7-bit format.
|
||||
|
||||
@ -179,7 +180,7 @@ class PowerSongImport(SongImport):
|
||||
# Check for corrupted stream (since max 5 bytes per 32-bit integer)
|
||||
if i == 5:
|
||||
raise ValueError
|
||||
byte = self._readByte(file_object)
|
||||
byte = self._read_byte(file_object)
|
||||
# Strip high bit and shift left
|
||||
val += (byte & 0x7f) << shift
|
||||
shift += 7
|
||||
@ -189,7 +190,7 @@ class PowerSongImport(SongImport):
|
||||
i += 1
|
||||
return val
|
||||
|
||||
def _readByte(self, file_object):
|
||||
def _read_byte(self, file_object):
|
||||
"""
|
||||
Reads in next byte as an unsigned integer
|
||||
|
||||
@ -202,7 +203,7 @@ class PowerSongImport(SongImport):
|
||||
else:
|
||||
return ord(byte_str)
|
||||
|
||||
def _parseCopyrightCCLI(self, field):
|
||||
def _parse_copyright_cCCLI(self, field):
|
||||
"""
|
||||
Look for CCLI song number, and get copyright
|
||||
"""
|
||||
@ -211,8 +212,8 @@ class PowerSongImport(SongImport):
|
||||
copyright = ccli_no
|
||||
ccli_no = ''
|
||||
if copyright:
|
||||
self.addCopyright(copyright.rstrip('\n').replace('\n', ' '))
|
||||
self.add_copyright(copyright.rstrip('\n').replace('\n', ' '))
|
||||
if ccli_no:
|
||||
ccli_no = ccli_no.strip(' :')
|
||||
if ccli_no.isdigit():
|
||||
self.ccliNumber = ccli_no
|
||||
self.ccli_number = ccli_no
|
||||
|
@ -61,6 +61,7 @@ try:
|
||||
except ImportError:
|
||||
ITALIC = 2
|
||||
|
||||
|
||||
class SofImport(OooImport):
|
||||
"""
|
||||
Import songs provided on disks with the Songs of Fellowship music books
|
||||
@ -68,8 +69,8 @@ class SofImport(OooImport):
|
||||
|
||||
Use OpenOffice.org Writer for processing the rtf file
|
||||
|
||||
The three books are not only inconsistant with each other, they are
|
||||
inconsistant in themselves too with their formatting. Not only this, but
|
||||
The three books are not only inconsistent with each other, they are
|
||||
inconsistent in themselves too with their formatting. Not only this, but
|
||||
the 1+2 book does not space out verses correctly. This script attempts
|
||||
to sort it out, but doesn't get it 100% right. But better than having to
|
||||
type them all out!
|
||||
@ -85,18 +86,18 @@ class SofImport(OooImport):
|
||||
OooImport.__init__(self, manager, **kwargs)
|
||||
self.song = False
|
||||
|
||||
def processOooDocument(self):
|
||||
def process_ooo_document(self):
|
||||
"""
|
||||
Handle the import process for SoF files.
|
||||
"""
|
||||
self.processSofFile()
|
||||
self.process_sof_file()
|
||||
|
||||
def processSofFile(self):
|
||||
def process_sof_file(self):
|
||||
"""
|
||||
Process the RTF file, a paragraph at a time
|
||||
"""
|
||||
self.blankLines = 0
|
||||
self.newSong()
|
||||
self.blank_lines = 0
|
||||
self.new_song()
|
||||
try:
|
||||
paragraphs = self.document.getText().createEnumeration()
|
||||
while paragraphs.hasMoreElements():
|
||||
@ -104,65 +105,68 @@ class SofImport(OooImport):
|
||||
return
|
||||
paragraph = paragraphs.nextElement()
|
||||
if paragraph.supportsService("com.sun.star.text.Paragraph"):
|
||||
self.processParagraph(paragraph)
|
||||
self.process_paragraph(paragraph)
|
||||
except RuntimeException as exc:
|
||||
log.exception('Error processing file: %s', exc)
|
||||
if not self.finish():
|
||||
self.logError(self.filepath)
|
||||
self.log_error(self.file_path)
|
||||
|
||||
def processParagraph(self, paragraph):
|
||||
def process_paragraph(self, paragraph):
|
||||
"""
|
||||
Process a paragraph.
|
||||
In the first book, a paragraph is a single line. In the latter ones
|
||||
they may contain multiple lines.
|
||||
Each paragraph contains textportions. Each textportion has it's own
|
||||
styling, e.g. italics, bold etc.
|
||||
In the first book, a paragraph is a single line. In the latter ones they may contain multiple lines.
|
||||
Each paragraph contains textportions. Each textportion has it's own styling, e.g. italics, bold etc.
|
||||
Also check for page breaks, which indicates a new song in books 1+2.
|
||||
In later books, there may not be line breaks, so check for 3 or more
|
||||
newlines
|
||||
In later books, there may not be line breaks, so check for 3 or more newlines
|
||||
|
||||
:param paragraph: The paragraph text
|
||||
"""
|
||||
text = ''
|
||||
textportions = paragraph.createEnumeration()
|
||||
while textportions.hasMoreElements():
|
||||
textportion = textportions.nextElement()
|
||||
if textportion.BreakType in (PAGE_BEFORE, PAGE_BOTH):
|
||||
self.processParagraphText(text)
|
||||
self.newSong()
|
||||
text_portions = paragraph.createEnumeration()
|
||||
while text_portions.hasMoreElements():
|
||||
text_portion = text_portions.nextElement()
|
||||
if text_portion.BreakType in (PAGE_BEFORE, PAGE_BOTH):
|
||||
self.process_paragraph_text(text)
|
||||
self.new_song()
|
||||
text = ''
|
||||
text += self.processTextPortion(textportion)
|
||||
if textportion.BreakType in (PAGE_AFTER, PAGE_BOTH):
|
||||
self.processParagraphText(text)
|
||||
self.newSong()
|
||||
text += self.process_text_portion(text_portion)
|
||||
if text_portion.BreakType in (PAGE_AFTER, PAGE_BOTH):
|
||||
self.process_paragraph_text(text)
|
||||
self.new_song()
|
||||
text = ''
|
||||
self.processParagraphText(text)
|
||||
self.process_paragraph_text(text)
|
||||
|
||||
def processParagraphText(self, text):
|
||||
def process_paragraph_text(self, text):
|
||||
"""
|
||||
Split the paragraph text into multiple lines and process
|
||||
|
||||
:param text: The text
|
||||
"""
|
||||
for line in text.split('\n'):
|
||||
self.processParagraphLine(line)
|
||||
if self.blankLines > 2:
|
||||
self.newSong()
|
||||
self.process_paragraph_line(line)
|
||||
if self.blank_lines > 2:
|
||||
self.new_song()
|
||||
|
||||
def processParagraphLine(self, text):
|
||||
def process_paragraph_line(self, text):
|
||||
"""
|
||||
Process a single line. Throw away that text which isn't relevant, i.e.
|
||||
stuff that appears at the end of the song.
|
||||
Anything that is OK, append to the current verse
|
||||
|
||||
:param text: The text
|
||||
"""
|
||||
text = text.strip()
|
||||
if text == '':
|
||||
self.blankLines += 1
|
||||
if self.blankLines > 1:
|
||||
self.blank_lines += 1
|
||||
if self.blank_lines > 1:
|
||||
return
|
||||
if self.title != '':
|
||||
self.finishVerse()
|
||||
self.finish_verse()
|
||||
return
|
||||
self.blankLines = 0
|
||||
if self.skipToCloseBracket:
|
||||
self.blank_lines = 0
|
||||
if self.skip_to_close_bracket:
|
||||
if text.endswith(')'):
|
||||
self.skipToCloseBracket = False
|
||||
self.skip_to_close_bracket = False
|
||||
return
|
||||
if text.startswith('CCL Licence'):
|
||||
self.italics = False
|
||||
@ -170,90 +174,93 @@ class SofImport(OooImport):
|
||||
if text == 'A Songs of Fellowship Worship Resource':
|
||||
return
|
||||
if text.startswith('(NB.') or text.startswith('(Regrettably') or text.startswith('(From'):
|
||||
self.skipToCloseBracket = True
|
||||
self.skip_to_close_bracket = True
|
||||
return
|
||||
if text.startswith('Copyright'):
|
||||
self.addCopyright(text)
|
||||
self.add_copyright(text)
|
||||
return
|
||||
if text == '(Repeat)':
|
||||
self.finishVerse()
|
||||
self.repeatVerse()
|
||||
self.finish_verse()
|
||||
self.repeat_verse()
|
||||
return
|
||||
if self.title == '':
|
||||
if self.copyright == '':
|
||||
self.addSofAuthor(text)
|
||||
self.add_sof_author(text)
|
||||
else:
|
||||
self.addCopyright(text)
|
||||
self.add_copyright(text)
|
||||
return
|
||||
self.addVerseLine(text)
|
||||
self.add_verse_line(text)
|
||||
|
||||
def processTextPortion(self, textportion):
|
||||
def process_text_portion(self, text_portion):
|
||||
"""
|
||||
Process a text portion. Here we just get the text and detect if
|
||||
it's bold or italics. If it's bold then its a song number or song title.
|
||||
Song titles are in all capitals, so we must bring the capitalization
|
||||
into line
|
||||
Process a text portion. Here we just get the text and detect if it's bold or italics. If it's bold then its a
|
||||
song number or song title.
|
||||
Song titles are in all capitals, so we must bring the capitalization into line
|
||||
|
||||
:param text_portion: A Piece of text
|
||||
"""
|
||||
text = textportion.getString()
|
||||
text = self.tidyText(text)
|
||||
text = text_portion.getString()
|
||||
text = self.tidy_text(text)
|
||||
if text.strip() == '':
|
||||
return text
|
||||
if textportion.CharWeight == BOLD:
|
||||
boldtext = text.strip()
|
||||
if boldtext.isdigit() and self.songNumber == '':
|
||||
self.addSongNumber(boldtext)
|
||||
if text_portion.CharWeight == BOLD:
|
||||
bold_text = text.strip()
|
||||
if bold_text.isdigit() and self.song_number == '':
|
||||
self.add_song_number(bold_text)
|
||||
return ''
|
||||
text = self.uncapText(text)
|
||||
text = self.uncap_text(text)
|
||||
if self.title == '':
|
||||
self.addTitle(text)
|
||||
self.add_title(text)
|
||||
return text
|
||||
if text.strip().startswith('('):
|
||||
return text
|
||||
self.italics = (textportion.CharPosture == ITALIC)
|
||||
self.italics = (text_portion.CharPosture == ITALIC)
|
||||
return text
|
||||
|
||||
def newSong(self):
|
||||
def new_song(self):
|
||||
"""
|
||||
A change of song. Store the old, create a new
|
||||
... but only if the last song was complete. If not, stick with it
|
||||
"""
|
||||
if self.song:
|
||||
self.finishVerse()
|
||||
if not self.checkComplete():
|
||||
self.finish_verse()
|
||||
if not self.check_complete():
|
||||
return
|
||||
self.finish()
|
||||
self.song = True
|
||||
self.setDefaults()
|
||||
self.skipToCloseBracket = False
|
||||
self.isChorus = False
|
||||
self.set_defaults()
|
||||
self.skip_to_close_bracket = False
|
||||
self.is_chorus = False
|
||||
self.italics = False
|
||||
self.currentVerse = ''
|
||||
self.current__verse = ''
|
||||
|
||||
def addSongNumber(self, song_no):
|
||||
def add_song_number(self, song_no):
|
||||
"""
|
||||
Add a song number, store as alternate title. Also use the song
|
||||
number to work out which songbook we're in
|
||||
Add a song number, store as alternate title. Also use the song number to work out which songbook we're in
|
||||
|
||||
:param song_no: The Song number
|
||||
"""
|
||||
self.songNumber = song_no
|
||||
self.alternateTitle = song_no + '.'
|
||||
self.songBook_pub = 'Kingsway Publications'
|
||||
self.song_number = song_no
|
||||
self.alternate_title = song_no + '.'
|
||||
self.song_book_pub = 'Kingsway Publications'
|
||||
if int(song_no) <= 640:
|
||||
self.songBook = 'Songs of Fellowship 1'
|
||||
self.song_book = 'Songs of Fellowship 1'
|
||||
elif int(song_no) <= 1150:
|
||||
self.songBook = 'Songs of Fellowship 2'
|
||||
self.song_book = 'Songs of Fellowship 2'
|
||||
elif int(song_no) <= 1690:
|
||||
self.songBook = 'Songs of Fellowship 3'
|
||||
self.song_book = 'Songs of Fellowship 3'
|
||||
elif int(song_no) <= 2200:
|
||||
self.songBook = 'Songs of Fellowship 4'
|
||||
self.song_book = 'Songs of Fellowship 4'
|
||||
elif int(song_no) <= 2710:
|
||||
self.songBook = 'Songs of Fellowship 5'
|
||||
self.song_book = 'Songs of Fellowship 5'
|
||||
else:
|
||||
self.songBook = 'Songs of Fellowship Other'
|
||||
self.song_book = 'Songs of Fellowship Other'
|
||||
|
||||
def addTitle(self, text):
|
||||
def add_title(self, text):
|
||||
"""
|
||||
Add the title to the song. Strip some leading/trailing punctuation that
|
||||
we don't want in a title
|
||||
Add the title to the song. Strip some leading/trailing punctuation that we don't want in a title
|
||||
|
||||
:param text: Title text
|
||||
"""
|
||||
title = text.strip()
|
||||
if title.startswith('\''):
|
||||
@ -263,50 +270,50 @@ class SofImport(OooImport):
|
||||
self.title = title
|
||||
self.import_wizard.increment_progress_bar('Processing song ' + title, 0)
|
||||
|
||||
def addSofAuthor(self, text):
|
||||
def add_sof_author(self, text):
|
||||
"""
|
||||
Add the author. OpenLP stores them individually so split by 'and', '&'
|
||||
and comma.
|
||||
However need to check for "Mr and Mrs Smith" and turn it to
|
||||
"Mr Smith" and "Mrs Smith".
|
||||
Add the author. OpenLP stores them individually so split by 'and', '&' and comma.
|
||||
However need to check for "Mr and Mrs Smith" and turn it to "Mr Smith" and "Mrs Smith".
|
||||
|
||||
:param text: Author text
|
||||
"""
|
||||
text = text.replace(' and ', ' & ')
|
||||
self.parse_author(text)
|
||||
|
||||
def addVerseLine(self, text):
|
||||
def add_verse_line(self, text):
|
||||
"""
|
||||
Add a line to the current verse. If the formatting has changed and
|
||||
we're beyond the second line of first verse, then this indicates
|
||||
a change of verse. Italics are a chorus
|
||||
"""
|
||||
if self.italics != self.isChorus and ((len(self.verses) > 0) or
|
||||
(self.currentVerse.count('\n') > 1)):
|
||||
self.finishVerse()
|
||||
if self.italics:
|
||||
self.isChorus = True
|
||||
self.currentVerse += text + '\n'
|
||||
Add a line to the current verse. If the formatting has changed and we're beyond the second line of first verse,
|
||||
then this indicates a change of verse. Italics are a chorus
|
||||
|
||||
def finishVerse(self):
|
||||
:param text: The verse text
|
||||
"""
|
||||
Verse is finished, store it. Note in book 1+2, some songs are formatted
|
||||
incorrectly. Here we try and split songs with missing line breaks into
|
||||
the correct number of verses.
|
||||
if self.italics != self.is_chorus and ((len(self.verses) > 0) or
|
||||
(self.current__verse.count('\n') > 1)):
|
||||
self.finish_verse()
|
||||
if self.italics:
|
||||
self.is_chorus = True
|
||||
self.current__verse += text + '\n'
|
||||
|
||||
def finish_verse(self):
|
||||
"""
|
||||
if self.currentVerse.strip() == '':
|
||||
Verse is finished, store it. Note in book 1+2, some songs are formatted incorrectly. Here we try and split
|
||||
songs with missing line breaks into the correct number of verses.
|
||||
"""
|
||||
if self.current__verse.strip() == '':
|
||||
return
|
||||
if self.isChorus:
|
||||
if self.is_chorus:
|
||||
versetag = 'C'
|
||||
splitat = None
|
||||
else:
|
||||
versetag = 'V'
|
||||
splitat = self.verseSplits(self.songNumber)
|
||||
splitat = self.verse_splits(self.song_number)
|
||||
if splitat:
|
||||
ln = 0
|
||||
verse = ''
|
||||
for line in self.currentVerse.split('\n'):
|
||||
for line in self.current__verse.split('\n'):
|
||||
ln += 1
|
||||
if line == '' or ln > splitat:
|
||||
self.addSofVerse(verse, versetag)
|
||||
self.add_sof_verse(verse, versetag)
|
||||
ln = 0
|
||||
if line:
|
||||
verse = line + '\n'
|
||||
@ -315,19 +322,19 @@ class SofImport(OooImport):
|
||||
else:
|
||||
verse += line + '\n'
|
||||
if verse:
|
||||
self.addSofVerse(verse, versetag)
|
||||
self.add_sof_verse(verse, versetag)
|
||||
else:
|
||||
self.addSofVerse(self.currentVerse, versetag)
|
||||
self.currentVerse = ''
|
||||
self.isChorus = False
|
||||
self.add_sof_verse(self.current__verse, versetag)
|
||||
self.current__verse = ''
|
||||
self.is_chorus = False
|
||||
|
||||
def addSofVerse(self, lyrics, tag):
|
||||
self.addVerse(lyrics, tag)
|
||||
if not self.isChorus and 'C1' in self.verseOrderListGenerated:
|
||||
self.verseOrderListGenerated.append('C1')
|
||||
self.verseOrderListGenerated_useful = True
|
||||
def add_sof_verse(self, lyrics, tag):
|
||||
self.add_verse(lyrics, tag)
|
||||
if not self.is_chorus and 'C1' in self.verse_order_list_generated:
|
||||
self.verse_order_list_generated.append('C1')
|
||||
self.verse_order_list_generated_useful = True
|
||||
|
||||
def uncapText(self, text):
|
||||
def uncap_text(self, text):
|
||||
"""
|
||||
Words in the title are in all capitals, so we lowercase them.
|
||||
However some of these words, e.g. referring to God need a leading
|
||||
@ -336,28 +343,26 @@ class SofImport(OooImport):
|
||||
There is a complicated word "One", which is sometimes lower and
|
||||
sometimes upper depending on context. Never mind, keep it lower.
|
||||
"""
|
||||
textarr = re.split('(\W+)', text)
|
||||
textarr[0] = textarr[0].capitalize()
|
||||
for i in range(1, len(textarr)):
|
||||
text_arr = re.split('(\W+)', text)
|
||||
text_arr[0] = text_arr[0].capitalize()
|
||||
for i in range(1, len(text_arr)):
|
||||
# Do not translate these. Fixed strings in SOF song file
|
||||
if textarr[i] in ('JESUS', 'CHRIST', 'KING', 'ALMIGHTY',
|
||||
'REDEEMER', 'SHEPHERD', 'SON', 'GOD', 'LORD', 'FATHER',
|
||||
'HOLY', 'SPIRIT', 'LAMB', 'YOU', 'YOUR', 'I', 'I\'VE',
|
||||
'I\'M', 'I\'LL', 'SAVIOUR', 'O', 'YOU\'RE', 'HE', 'HIS',
|
||||
'HIM', 'ZION', 'EMMANUEL', 'MAJESTY', 'JESUS\'', 'JIREH',
|
||||
'JUDAH', 'LION', 'LORD\'S', 'ABRAHAM', 'GOD\'S',
|
||||
'FATHER\'S', 'ELIJAH' 'MARTHA', 'CHRISTMAS', 'ALPHA',
|
||||
'OMEGA'):
|
||||
textarr[i] = textarr[i].capitalize()
|
||||
if text_arr[i] in ('JESUS', 'CHRIST', 'KING', 'ALMIGHTY', 'REDEEMER', 'SHEPHERD', 'SON', 'GOD', 'LORD',
|
||||
'FATHER', 'HOLY', 'SPIRIT', 'LAMB', 'YOU', 'YOUR', 'I', 'I\'VE', 'I\'M', 'I\'LL',
|
||||
'SAVIOUR', 'O', 'YOU\'RE', 'HE', 'HIS', 'HIM', 'ZION', 'EMMANUEL', 'MAJESTY', 'JESUS\'',
|
||||
'JIREH', 'JUDAH', 'LION', 'LORD\'S', 'ABRAHAM', 'GOD\'S', 'FATHER\'S', 'ELIJAH' 'MARTHA',
|
||||
'CHRISTMAS', 'ALPHA', 'OMEGA'):
|
||||
text_arr[i] = text_arr[i].capitalize()
|
||||
else:
|
||||
textarr[i] = textarr[i].lower()
|
||||
text = ''.join(textarr)
|
||||
text_arr[i] = text_arr[i].lower()
|
||||
text = ''.join(text_arr)
|
||||
return text
|
||||
|
||||
def verseSplits(self, song_number):
|
||||
def verse_splits(self, song_number):
|
||||
"""
|
||||
Because someone at Kingsway forgot to check the 1+2 RTF file,
|
||||
some verses were not formatted correctly.
|
||||
Because someone at Kingsway forgot to check the 1+2 RTF file, some verses were not formatted correctly.
|
||||
|
||||
:param song_number: The Song number
|
||||
"""
|
||||
if song_number == 11:
|
||||
return 8
|
||||
|
@ -40,6 +40,7 @@ from openlp.plugins.songs.lib.songimport import SongImport
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SongBeamerTypes(object):
|
||||
MarkTypes = {
|
||||
'Refrain': VerseType.tags[VerseType.Chorus],
|
||||
@ -98,7 +99,7 @@ class SongBeamerImport(SongImport):
|
||||
"""
|
||||
SongImport.__init__(self, manager, **kwargs)
|
||||
|
||||
def doImport(self):
|
||||
def do_import(self):
|
||||
"""
|
||||
Receive a single file or a list of files to import.
|
||||
"""
|
||||
@ -109,9 +110,9 @@ class SongBeamerImport(SongImport):
|
||||
# TODO: check that it is a valid SongBeamer file
|
||||
if self.stop_import_flag:
|
||||
return
|
||||
self.setDefaults()
|
||||
self.currentVerse = ''
|
||||
self.currentVerseType = VerseType.tags[VerseType.Verse]
|
||||
self.set_defaults()
|
||||
self.current_verse = ''
|
||||
self.current_verse_type = VerseType.tags[VerseType.Verse]
|
||||
read_verses = False
|
||||
file_name = os.path.split(import_file)[1]
|
||||
if os.path.isfile(import_file):
|
||||
@ -132,39 +133,38 @@ class SongBeamerImport(SongImport):
|
||||
if line.startswith('#') and not read_verses:
|
||||
self.parseTags(line)
|
||||
elif line.startswith('---'):
|
||||
if self.currentVerse:
|
||||
self.replaceHtmlTags()
|
||||
self.addVerse(self.currentVerse, self.currentVerseType)
|
||||
self.currentVerse = ''
|
||||
self.currentVerseType = VerseType.tags[VerseType.Verse]
|
||||
if self.current_verse:
|
||||
self.replace_html_tags()
|
||||
self.add_verse(self.current_verse, self.current_verse_type)
|
||||
self.current_verse = ''
|
||||
self.current_verse_type = VerseType.tags[VerseType.Verse]
|
||||
read_verses = True
|
||||
verse_start = True
|
||||
elif read_verses:
|
||||
if verse_start:
|
||||
verse_start = False
|
||||
if not self.checkVerseMarks(line):
|
||||
self.currentVerse = line + '\n'
|
||||
if not self.check_verse_marks(line):
|
||||
self.current_verse = line + '\n'
|
||||
else:
|
||||
self.currentVerse += line + '\n'
|
||||
if self.currentVerse:
|
||||
self.replaceHtmlTags()
|
||||
self.addVerse(self.currentVerse, self.currentVerseType)
|
||||
self.current_verse += line + '\n'
|
||||
if self.current_verse:
|
||||
self.replace_html_tags()
|
||||
self.add_verse(self.current_verse, self.current_verse_type)
|
||||
if not self.finish():
|
||||
self.logError(import_file)
|
||||
self.log_error(import_file)
|
||||
|
||||
def replaceHtmlTags(self):
|
||||
def replace_html_tags(self):
|
||||
"""
|
||||
This can be called to replace SongBeamer's specific (html) tags with OpenLP's specific (html) tags.
|
||||
"""
|
||||
for pair in SongBeamerImport.HTML_TAG_PAIRS:
|
||||
self.currentVerse = pair[0].sub(pair[1], self.currentVerse)
|
||||
self.current_verse = pair[0].sub(pair[1], self.current_verse)
|
||||
|
||||
def parseTags(self, line):
|
||||
"""
|
||||
Parses a meta data line.
|
||||
|
||||
``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'
|
||||
"""
|
||||
@ -174,7 +174,7 @@ class SongBeamerImport(SongImport):
|
||||
if not tag_val[0] or not tag_val[1]:
|
||||
return
|
||||
if tag_val[0] == '#(c)':
|
||||
self.addCopyright(tag_val[1])
|
||||
self.add_copyright(tag_val[1])
|
||||
elif tag_val[0] == '#AddCopyrightInfo':
|
||||
pass
|
||||
elif tag_val[0] == '#Author':
|
||||
@ -186,7 +186,7 @@ class SongBeamerImport(SongImport):
|
||||
elif tag_val[0] == '#Categories':
|
||||
self.topics = tag_val[1].split(',')
|
||||
elif tag_val[0] == '#CCLI':
|
||||
self.ccliNumber = tag_val[1]
|
||||
self.ccli_number = tag_val[1]
|
||||
elif tag_val[0] == '#Chords':
|
||||
pass
|
||||
elif tag_val[0] == '#ChurchSongID':
|
||||
@ -233,10 +233,10 @@ class SongBeamerImport(SongImport):
|
||||
song_book_pub = tag_val[1]
|
||||
elif tag_val[0] == '#Songbook' or tag_val[0] == '#SongBook':
|
||||
book_data = tag_val[1].split('/')
|
||||
self.songBookName = book_data[0].strip()
|
||||
self.song_book_name = book_data[0].strip()
|
||||
if len(book_data) == 2:
|
||||
number = book_data[1].strip()
|
||||
self.songNumber = number if number.isdigit() else ''
|
||||
self.song_number = number if number.isdigit() else ''
|
||||
elif tag_val[0] == '#Speed':
|
||||
pass
|
||||
elif tag_val[0] == 'Tempo':
|
||||
@ -267,20 +267,19 @@ class SongBeamerImport(SongImport):
|
||||
# TODO: add the verse order.
|
||||
pass
|
||||
|
||||
def checkVerseMarks(self, line):
|
||||
def check_verse_marks(self, line):
|
||||
"""
|
||||
Check and add the verse's MarkType. Returns ``True`` if the given linE contains a correct verse mark otherwise
|
||||
``False``.
|
||||
|
||||
``line``
|
||||
The line to check for marks (unicode).
|
||||
:param line: The line to check for marks (unicode).
|
||||
"""
|
||||
marks = line.split(' ')
|
||||
if len(marks) <= 2 and marks[0] in SongBeamerTypes.MarkTypes:
|
||||
self.currentVerseType = SongBeamerTypes.MarkTypes[marks[0]]
|
||||
self.current_verse_type = SongBeamerTypes.MarkTypes[marks[0]]
|
||||
if len(marks) == 2:
|
||||
# If we have a digit, we append it to current_verse_type.
|
||||
if marks[1].isdigit():
|
||||
self.currentVerseType += marks[1]
|
||||
self.current_verse_type += marks[1]
|
||||
return True
|
||||
return False
|
||||
|
@ -56,11 +56,8 @@ def songs_probably_equal(song1, song2):
|
||||
"""
|
||||
Calculate and return whether two songs are probably equal.
|
||||
|
||||
``song1``
|
||||
The first song to compare.
|
||||
|
||||
``song2``
|
||||
The second song to compare.
|
||||
:param song1: The first song to compare.
|
||||
:param song2: The second song to compare.
|
||||
"""
|
||||
if len(song1.search_lyrics) < len(song2.search_lyrics):
|
||||
small = song1.search_lyrics
|
||||
@ -96,8 +93,7 @@ def _op_length(opcode):
|
||||
"""
|
||||
Return the length of a given difference.
|
||||
|
||||
``opcode``
|
||||
The difference.
|
||||
:param opcode: The difference.
|
||||
"""
|
||||
return max(opcode[2] - opcode[1], opcode[4] - opcode[3])
|
||||
|
||||
@ -107,33 +103,28 @@ def _remove_typos(diff):
|
||||
Remove typos from a diff set. A typo is a small difference (<max_typo_size)
|
||||
surrounded by larger equal passages (>min_fragment_size).
|
||||
|
||||
``diff``
|
||||
The diff set to remove the typos from.
|
||||
:param diff: The diff set to remove the typos from.
|
||||
"""
|
||||
# Remove typo at beginning of the string.
|
||||
if len(diff) >= 2:
|
||||
if diff[0][0] != "equal" and _op_length(diff[0]) <= MAX_TYPO_SIZE and \
|
||||
_op_length(diff[1]) >= MIN_FRAGMENT_SIZE:
|
||||
del diff[0]
|
||||
if diff[0][0] != "equal" and _op_length(diff[0]) <= MAX_TYPO_SIZE and _op_length(diff[1]) >= MIN_FRAGMENT_SIZE:
|
||||
del diff[0]
|
||||
# Remove typos in the middle of the string.
|
||||
if len(diff) >= 3:
|
||||
for index in range(len(diff) - 3, -1, -1):
|
||||
if _op_length(diff[index]) >= MIN_FRAGMENT_SIZE and \
|
||||
diff[index + 1][0] != "equal" and _op_length(diff[index + 1]) <= MAX_TYPO_SIZE and \
|
||||
_op_length(diff[index + 2]) >= MIN_FRAGMENT_SIZE:
|
||||
del diff[index + 1]
|
||||
if _op_length(diff[index]) >= MIN_FRAGMENT_SIZE and diff[index + 1][0] != "equal" and \
|
||||
_op_length(diff[index + 1]) <= MAX_TYPO_SIZE and _op_length(diff[index + 2]) >= MIN_FRAGMENT_SIZE:
|
||||
del diff[index + 1]
|
||||
# Remove typo at the end of the string.
|
||||
if len(diff) >= 2:
|
||||
if _op_length(diff[-2]) >= MIN_FRAGMENT_SIZE and \
|
||||
diff[-1][0] != "equal" and _op_length(diff[-1]) <= MAX_TYPO_SIZE:
|
||||
del diff[-1]
|
||||
if _op_length(diff[-2]) >= MIN_FRAGMENT_SIZE and diff[-1][0] != "equal" \
|
||||
and _op_length(diff[-1]) <= MAX_TYPO_SIZE:
|
||||
del diff[-1]
|
||||
|
||||
# Merge the bordering equal passages that occured by removing differences.
|
||||
for index in range(len(diff) - 2, -1, -1):
|
||||
if diff[index][0] == "equal" and _op_length(diff[index]) >= MIN_FRAGMENT_SIZE and \
|
||||
diff[index + 1][0] == "equal" and _op_length(diff[index + 1]) >= MIN_FRAGMENT_SIZE:
|
||||
diff[index] = ("equal", diff[index][1], diff[index + 1][2], diff[index][3],
|
||||
diff[index + 1][4])
|
||||
del diff[index + 1]
|
||||
|
||||
diff[index + 1][0] == "equal" and _op_length(diff[index + 1]) >= MIN_FRAGMENT_SIZE:
|
||||
diff[index] = ("equal", diff[index][1], diff[index + 1][2], diff[index][3], diff[index + 1][4])
|
||||
del diff[index + 1]
|
||||
return diff
|
||||
|
@ -53,7 +53,7 @@ class SongImport(QtCore.QObject):
|
||||
as necessary
|
||||
"""
|
||||
@staticmethod
|
||||
def isValidSource(import_source):
|
||||
def is_valid_source(import_source):
|
||||
"""
|
||||
Override this method to validate the source prior to import.
|
||||
"""
|
||||
@ -63,10 +63,8 @@ class SongImport(QtCore.QObject):
|
||||
"""
|
||||
Initialise and create defaults for properties
|
||||
|
||||
``manager``
|
||||
An instance of a SongManager, through which all database access is
|
||||
performed.
|
||||
|
||||
:param manager: An instance of a SongManager, through which all database access is performed.
|
||||
:param kwargs:
|
||||
"""
|
||||
self.manager = manager
|
||||
QtCore.QObject.__init__(self)
|
||||
@ -82,56 +80,51 @@ class SongImport(QtCore.QObject):
|
||||
self.import_wizard = None
|
||||
self.song = None
|
||||
self.stop_import_flag = False
|
||||
self.setDefaults()
|
||||
self.set_defaults()
|
||||
Registry().register_function('openlp_stop_wizard', self.stop_import)
|
||||
|
||||
def setDefaults(self):
|
||||
def set_defaults(self):
|
||||
"""
|
||||
Create defaults for properties - call this before each song
|
||||
if importing many songs at once to ensure a clean beginning
|
||||
"""
|
||||
self.title = ''
|
||||
self.songNumber = ''
|
||||
self.song_number = ''
|
||||
self.alternate_title = ''
|
||||
self.copyright = ''
|
||||
self.comments = ''
|
||||
self.themeName = ''
|
||||
self.ccliNumber = ''
|
||||
self.theme_name = ''
|
||||
self.ccli_number = ''
|
||||
self.authors = []
|
||||
self.topics = []
|
||||
self.mediaFiles = []
|
||||
self.songBookName = ''
|
||||
self.songBookPub = ''
|
||||
self.verseOrderListGeneratedUseful = False
|
||||
self.verseOrderListGenerated = []
|
||||
self.verseOrderList = []
|
||||
self.media_files = []
|
||||
self.song_book_name = ''
|
||||
self.song_book_pub = ''
|
||||
self.verse_order_list_generated_useful = False
|
||||
self.verse_order_list_generated = []
|
||||
self.verse_order_list = []
|
||||
self.verses = []
|
||||
self.verseCounts = {}
|
||||
self.copyrightString = translate('SongsPlugin.SongImport', 'copyright')
|
||||
self.verse_counts = {}
|
||||
self.copyright_string = translate('SongsPlugin.SongImport', 'copyright')
|
||||
|
||||
def logError(self, filepath, reason=SongStrings.SongIncomplete):
|
||||
def log_error(self, file_path, reason=SongStrings.SongIncomplete):
|
||||
"""
|
||||
This should be called, when a song could not be imported.
|
||||
|
||||
``filepath``
|
||||
This should be the file path if ``self.import_source`` is a list
|
||||
with different files. If it is not a list, but a single file (for
|
||||
instance a database), then this should be the song's title.
|
||||
|
||||
``reason``
|
||||
The reason why the import failed. The string should be as
|
||||
informative as possible.
|
||||
:param file_path: This should be the file path if ``self.import_source`` is a list with different files. If it
|
||||
is not a list, but a single file (for instance a database), then this should be the song's title.
|
||||
:param reason: The reason why the import failed. The string should be as informative as possible.
|
||||
"""
|
||||
self.setDefaults()
|
||||
self.set_defaults()
|
||||
if self.import_wizard is None:
|
||||
return
|
||||
if self.import_wizard.error_report_text_edit.isHidden():
|
||||
self.import_wizard.error_report_text_edit.setText(translate('SongsPlugin.SongImport',
|
||||
'The following songs could not be imported:'))
|
||||
self.import_wizard.error_report_text_edit.setText(
|
||||
translate('SongsPlugin.SongImport', 'The following songs could not be imported:'))
|
||||
self.import_wizard.error_report_text_edit.setVisible(True)
|
||||
self.import_wizard.error_copy_to_button.setVisible(True)
|
||||
self.import_wizard.error_save_to_button.setVisible(True)
|
||||
self.import_wizard.error_report_text_edit.append('- %s (%s)' % (filepath, reason))
|
||||
self.import_wizard.error_report_text_edit.append('- %s (%s)' % (file_path, reason))
|
||||
|
||||
def stop_import(self):
|
||||
"""
|
||||
@ -143,10 +136,9 @@ class SongImport(QtCore.QObject):
|
||||
def register(self, import_wizard):
|
||||
self.import_wizard = import_wizard
|
||||
|
||||
def tidyText(self, text):
|
||||
def tidy_text(self, text):
|
||||
"""
|
||||
Get rid of some dodgy unicode and formatting characters we're not
|
||||
interested in. Some can be converted to ascii.
|
||||
Get rid of some dodgy unicode and formatting characters we're not interested in. Some can be converted to ascii.
|
||||
"""
|
||||
text = text.replace('\u2018', '\'')
|
||||
text = text.replace('\u2019', '\'')
|
||||
@ -161,21 +153,31 @@ class SongImport(QtCore.QObject):
|
||||
text = re.sub(r' ?(\n{5}|\f)+ ?', '\f', text)
|
||||
return text
|
||||
|
||||
def processSongText(self, text):
|
||||
def process_song_text(self, text):
|
||||
"""
|
||||
Process the song text from import
|
||||
|
||||
:param text: Some text
|
||||
"""
|
||||
verse_texts = text.split('\n\n')
|
||||
for verse_text in verse_texts:
|
||||
if verse_text.strip() != '':
|
||||
self.processVerseText(verse_text.strip())
|
||||
self.process_verse_text(verse_text.strip())
|
||||
|
||||
def processVerseText(self, text):
|
||||
def process_verse_text(self, text):
|
||||
"""
|
||||
Process the song verse text from import
|
||||
|
||||
:param text: Some text
|
||||
"""
|
||||
lines = text.split('\n')
|
||||
if text.lower().find(self.copyrightString) >= 0 or text.find(str(SongStrings.CopyrightSymbol)) >= 0:
|
||||
if text.lower().find(self.copyright_string) >= 0 or text.find(str(SongStrings.CopyrightSymbol)) >= 0:
|
||||
copyright_found = False
|
||||
for line in lines:
|
||||
if (copyright_found or line.lower().find(self.copyrightString) >= 0 or
|
||||
if (copyright_found or line.lower().find(self.copyright_string) >= 0 or
|
||||
line.find(str(SongStrings.CopyrightSymbol)) >= 0):
|
||||
copyright_found = True
|
||||
self.addCopyright(line)
|
||||
self.add_copyright(line)
|
||||
else:
|
||||
self.parse_author(line)
|
||||
return
|
||||
@ -184,9 +186,9 @@ class SongImport(QtCore.QObject):
|
||||
return
|
||||
if not self.title:
|
||||
self.title = lines[0]
|
||||
self.addVerse(text)
|
||||
self.add_verse(text)
|
||||
|
||||
def addCopyright(self, copyright):
|
||||
def add_copyright(self, copyright):
|
||||
"""
|
||||
Build the copyright field
|
||||
"""
|
||||
@ -198,9 +200,8 @@ class SongImport(QtCore.QObject):
|
||||
|
||||
def parse_author(self, text):
|
||||
"""
|
||||
Add the author. OpenLP stores them individually so split by 'and', '&'
|
||||
and comma. However need to check for 'Mr and Mrs Smith' and turn it to
|
||||
'Mr Smith' and 'Mrs Smith'.
|
||||
Add the author. OpenLP stores them individually so split by 'and', '&' and comma. However need to check
|
||||
for 'Mr and Mrs Smith' and turn it to 'Mr Smith' and 'Mrs Smith'.
|
||||
"""
|
||||
for author in text.split(','):
|
||||
authors = author.split('&')
|
||||
@ -211,9 +212,9 @@ class SongImport(QtCore.QObject):
|
||||
if author2.endswith('.'):
|
||||
author2 = author2[:-1]
|
||||
if author2:
|
||||
self.addAuthor(author2)
|
||||
self.add_author(author2)
|
||||
|
||||
def addAuthor(self, author):
|
||||
def add_author(self, author):
|
||||
"""
|
||||
Add an author to the list
|
||||
"""
|
||||
@ -221,63 +222,56 @@ class SongImport(QtCore.QObject):
|
||||
return
|
||||
self.authors.append(author)
|
||||
|
||||
def addMediaFile(self, filename, weight=0):
|
||||
def add_media_file(self, filename, weight=0):
|
||||
"""
|
||||
Add a media file to the list
|
||||
"""
|
||||
if filename in [x[0] for x in self.mediaFiles]:
|
||||
if filename in [x[0] for x in self.media_files]:
|
||||
return
|
||||
self.mediaFiles.append((filename, weight))
|
||||
self.media_files.append((filename, weight))
|
||||
|
||||
def addVerse(self, verse_text, verse_def='v', lang=None):
|
||||
def add_verse(self, verse_text, verse_def='v', lang=None):
|
||||
"""
|
||||
Add a verse. This is the whole verse, lines split by \\n. It will also
|
||||
attempt to detect duplicates. In this case it will just add to the verse
|
||||
order.
|
||||
|
||||
``verse_text``
|
||||
The text of the verse.
|
||||
|
||||
``verse_def``
|
||||
The verse tag can be v1/c1/b etc, or 'v' and 'c' (will count the
|
||||
:param verse_text: The text of the verse.
|
||||
:param verse_def: The verse tag can be v1/c1/b etc, or 'v' and 'c' (will count the
|
||||
verses/choruses itself) or None, where it will assume verse.
|
||||
|
||||
``lang``
|
||||
The language code (ISO-639) of the verse, for example *en* or *de*.
|
||||
|
||||
:param lang: The language code (ISO-639) of the verse, for example *en* or *de*.
|
||||
"""
|
||||
for (old_verse_def, old_verse, old_lang) in self.verses:
|
||||
if old_verse.strip() == verse_text.strip():
|
||||
self.verseOrderListGenerated.append(old_verse_def)
|
||||
self.verseOrderListGeneratedUseful = True
|
||||
self.verse_order_list_generated.append(old_verse_def)
|
||||
self.verse_order_list_generated_useful = True
|
||||
return
|
||||
if verse_def[0] in self.verseCounts:
|
||||
self.verseCounts[verse_def[0]] += 1
|
||||
if verse_def[0] in self.verse_counts:
|
||||
self.verse_counts[verse_def[0]] += 1
|
||||
else:
|
||||
self.verseCounts[verse_def[0]] = 1
|
||||
self.verse_counts[verse_def[0]] = 1
|
||||
if len(verse_def) == 1:
|
||||
verse_def += str(self.verseCounts[verse_def[0]])
|
||||
elif int(verse_def[1:]) > self.verseCounts[verse_def[0]]:
|
||||
self.verseCounts[verse_def[0]] = int(verse_def[1:])
|
||||
verse_def += str(self.verse_counts[verse_def[0]])
|
||||
elif int(verse_def[1:]) > self.verse_counts[verse_def[0]]:
|
||||
self.verse_counts[verse_def[0]] = int(verse_def[1:])
|
||||
self.verses.append([verse_def, verse_text.rstrip(), lang])
|
||||
# A verse_def refers to all verses with that name, adding it once adds every instance, so do not add if already
|
||||
# used.
|
||||
if verse_def not in self.verseOrderListGenerated:
|
||||
self.verseOrderListGenerated.append(verse_def)
|
||||
if verse_def not in self.verse_order_list_generated:
|
||||
self.verse_order_list_generated.append(verse_def)
|
||||
|
||||
def repeatVerse(self):
|
||||
def repeat_verse(self):
|
||||
"""
|
||||
Repeat the previous verse in the verse order
|
||||
"""
|
||||
if self.verseOrderListGenerated:
|
||||
self.verseOrderListGenerated.append(self.verseOrderListGenerated[-1])
|
||||
self.verseOrderListGeneratedUseful = True
|
||||
if self.verse_order_list_generated:
|
||||
self.verse_order_list_generated.append(self.verse_order_list_generated[-1])
|
||||
self.verse_order_list_generated_useful = True
|
||||
|
||||
def checkComplete(self):
|
||||
def check_complete(self):
|
||||
"""
|
||||
Check the mandatory fields are entered (i.e. title and a verse)
|
||||
Author not checked here, if no author then "Author unknown" is
|
||||
automatically added
|
||||
Author not checked here, if no author then "Author unknown" is automatically added
|
||||
"""
|
||||
if not self.title or not self.verses:
|
||||
return False
|
||||
@ -288,8 +282,8 @@ class SongImport(QtCore.QObject):
|
||||
"""
|
||||
All fields have been set to this song. Write the song to disk.
|
||||
"""
|
||||
if not self.checkComplete():
|
||||
self.setDefaults()
|
||||
if not self.check_complete():
|
||||
self.set_defaults()
|
||||
return False
|
||||
log.info('committing song %s to database', self.title)
|
||||
song = Song()
|
||||
@ -301,7 +295,7 @@ class SongImport(QtCore.QObject):
|
||||
song.search_title = ''
|
||||
song.search_lyrics = ''
|
||||
song.verse_order = ''
|
||||
song.song_number = self.songNumber
|
||||
song.song_number = self.song_number
|
||||
verses_changed_to_other = {}
|
||||
sxml = SongXML()
|
||||
other_count = 1
|
||||
@ -317,32 +311,32 @@ class SongImport(QtCore.QObject):
|
||||
verse_def = new_verse_def
|
||||
sxml.add_verse_to_lyrics(verse_tag, verse_def[1:], verse_text, lang)
|
||||
song.lyrics = str(sxml.extract_xml(), 'utf-8')
|
||||
if not self.verseOrderList and self.verseOrderListGeneratedUseful:
|
||||
self.verseOrderList = self.verseOrderListGenerated
|
||||
self.verseOrderList = [verses_changed_to_other.get(v, v) for v in self.verseOrderList]
|
||||
song.verse_order = ' '.join(self.verseOrderList)
|
||||
if not self.verse_order_list and self.verse_order_list_generated_useful:
|
||||
self.verse_order_list = self.verse_order_list_generated
|
||||
self.verse_order_list = [verses_changed_to_other.get(v, v) for v in self.verse_order_list]
|
||||
song.verse_order = ' '.join(self.verse_order_list)
|
||||
song.copyright = self.copyright
|
||||
song.comments = self.comments
|
||||
song.theme_name = self.themeName
|
||||
song.ccli_number = self.ccliNumber
|
||||
for authortext in self.authors:
|
||||
author = self.manager.get_object_filtered(Author, Author.display_name == authortext)
|
||||
song.theme_name = self.theme_name
|
||||
song.ccli_number = self.ccli_number
|
||||
for author_text in self.authors:
|
||||
author = self.manager.get_object_filtered(Author, Author.display_name == author_text)
|
||||
if not author:
|
||||
author = Author.populate(display_name=authortext,
|
||||
last_name=authortext.split(' ')[-1],
|
||||
first_name=' '.join(authortext.split(' ')[:-1]))
|
||||
author = Author.populate(display_name=author_text,
|
||||
last_name=author_text.split(' ')[-1],
|
||||
first_name=' '.join(author_text.split(' ')[:-1]))
|
||||
song.authors.append(author)
|
||||
if self.songBookName:
|
||||
song_book = self.manager.get_object_filtered(Book, Book.name == self.songBookName)
|
||||
if self.song_book_name:
|
||||
song_book = self.manager.get_object_filtered(Book, Book.name == self.song_book_name)
|
||||
if song_book is None:
|
||||
song_book = Book.populate(name=self.songBookName, publisher=self.songBookPub)
|
||||
song_book = Book.populate(name=self.song_book_name, publisher=self.song_book_pub)
|
||||
song.book = song_book
|
||||
for topictext in self.topics:
|
||||
if not topictext:
|
||||
for topic_text in self.topics:
|
||||
if not topic_text:
|
||||
continue
|
||||
topic = self.manager.get_object_filtered(Topic, Topic.name == topictext)
|
||||
topic = self.manager.get_object_filtered(Topic, Topic.name == topic_text)
|
||||
if topic is None:
|
||||
topic = Topic.populate(name=topictext)
|
||||
topic = Topic.populate(name=topic_text)
|
||||
song.topics.append(topic)
|
||||
# We need to save the song now, before adding the media files, so that
|
||||
# we know where to save the media files to.
|
||||
@ -350,29 +344,29 @@ class SongImport(QtCore.QObject):
|
||||
self.manager.save_object(song)
|
||||
# Now loop through the media files, copy them to the correct location,
|
||||
# and save the song again.
|
||||
for filename, weight in self.mediaFiles:
|
||||
for filename, weight in self.media_files:
|
||||
media_file = self.manager.get_object_filtered(MediaFile, MediaFile.file_name == filename)
|
||||
if not media_file:
|
||||
if os.path.dirname(filename):
|
||||
filename = self.copyMediaFile(song.id, filename)
|
||||
filename = self.copy_media_file(song.id, filename)
|
||||
song.media_files.append(MediaFile.populate(file_name=filename, weight=weight))
|
||||
self.manager.save_object(song)
|
||||
self.setDefaults()
|
||||
self.set_defaults()
|
||||
return True
|
||||
|
||||
def copyMediaFile(self, song_id, filename):
|
||||
def copy_media_file(self, song_id, filename):
|
||||
"""
|
||||
This method copies the media file to the correct location and returns
|
||||
the new file location.
|
||||
|
||||
``filename``
|
||||
The file to copy.
|
||||
:param song_id:
|
||||
:param filename: The file to copy.
|
||||
"""
|
||||
if not hasattr(self, 'save_path'):
|
||||
self.save_path = os.path.join(AppLocation.get_section_data_path(self.import_wizard.plugin.name),
|
||||
'audio', str(song_id))
|
||||
'audio', str(song_id))
|
||||
check_directory_exists(self.save_path)
|
||||
if not filename.startswith(self.save_path):
|
||||
oldfile, filename = filename, os.path.join(self.save_path, os.path.split(filename)[1])
|
||||
shutil.copyfile(oldfile, filename)
|
||||
old_file, filename = filename, os.path.join(self.save_path, os.path.split(filename)[1])
|
||||
shutil.copyfile(old_file, filename)
|
||||
return filename
|
||||
|
@ -74,7 +74,7 @@ class SongProImport(SongImport):
|
||||
"""
|
||||
SongImport.__init__(self, manager, **kwargs)
|
||||
|
||||
def doImport(self):
|
||||
def do_import(self):
|
||||
"""
|
||||
Receive a single file or a list of files to import.
|
||||
"""
|
||||
@ -89,18 +89,18 @@ class SongProImport(SongImport):
|
||||
file_line = str(file_line, 'cp1252')
|
||||
file_text = file_line.rstrip()
|
||||
if file_text and file_text[0] == '#':
|
||||
self.processSection(tag, text.rstrip())
|
||||
self.process_section(tag, text.rstrip())
|
||||
tag = file_text[1:]
|
||||
text = ''
|
||||
else:
|
||||
text += file_line
|
||||
|
||||
def processSection(self, tag, text):
|
||||
def process_section(self, tag, text):
|
||||
"""
|
||||
Process a section of the song, i.e. title, verse etc.
|
||||
"""
|
||||
if tag == 'T':
|
||||
self.setDefaults()
|
||||
self.set_defaults()
|
||||
if text:
|
||||
self.title = text
|
||||
return
|
||||
@ -118,29 +118,29 @@ class SongProImport(SongImport):
|
||||
if tag == 'A':
|
||||
self.parse_author(text)
|
||||
elif tag in ['B', 'C']:
|
||||
self.addVerse(text, tag)
|
||||
self.add_verse(text, tag)
|
||||
elif tag == 'D':
|
||||
self.addVerse(text, 'E')
|
||||
self.add_verse(text, 'E')
|
||||
elif tag == 'G':
|
||||
self.topics.append(text)
|
||||
elif tag == 'M':
|
||||
matches = re.findall(r'\d+', text)
|
||||
if matches:
|
||||
self.songNumber = matches[-1]
|
||||
self.songBookName = text[:text.rfind(self.songNumber)]
|
||||
self.song_number = matches[-1]
|
||||
self.song_book_name = text[:text.rfind(self.song_number)]
|
||||
elif tag == 'N':
|
||||
self.comments = text
|
||||
elif tag == 'O':
|
||||
for char in text:
|
||||
if char == 'C':
|
||||
self.verseOrderList.append('C1')
|
||||
self.verse_order_list.append('C1')
|
||||
elif char == 'B':
|
||||
self.verseOrderList.append('B1')
|
||||
self.verse_order_list.append('B1')
|
||||
elif char == 'D':
|
||||
self.verseOrderList.append('E1')
|
||||
self.verse_order_list.append('E1')
|
||||
elif '1' <= char <= '7':
|
||||
self.verseOrderList.append('V' + char)
|
||||
self.verse_order_list.append('V' + char)
|
||||
elif tag == 'R':
|
||||
self.addCopyright(text)
|
||||
self.add_copyright(text)
|
||||
elif '1' <= tag <= '7':
|
||||
self.addVerse(text, 'V' + tag[1:])
|
||||
self.add_verse(text, 'V' + tag[1:])
|
||||
|
@ -94,7 +94,7 @@ class SongShowPlusImport(SongImport):
|
||||
"""
|
||||
SongImport.__init__(self, manager, **kwargs)
|
||||
|
||||
def doImport(self):
|
||||
def do_import(self):
|
||||
"""
|
||||
Receive a single file or a list of files to import.
|
||||
"""
|
||||
@ -141,19 +141,19 @@ class SongShowPlusImport(SongImport):
|
||||
authors = self.decode(data).split(" / ")
|
||||
for author in authors:
|
||||
if author.find(",") !=-1:
|
||||
authorParts = author.split(", ")
|
||||
author = authorParts[1] + " " + authorParts[0]
|
||||
author_parts = author.split(", ")
|
||||
author = author_parts[1] + " " + author_parts[0]
|
||||
self.parse_author(author)
|
||||
elif block_key == COPYRIGHT:
|
||||
self.addCopyright(self.decode(data))
|
||||
self.add_copyright(self.decode(data))
|
||||
elif block_key == CCLI_NO:
|
||||
self.ccliNumber = int(data)
|
||||
self.ccli_number = int(data)
|
||||
elif block_key == VERSE:
|
||||
self.addVerse(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:
|
||||
self.addVerse(self.decode(data), "%s%s" % (VerseType.tags[VerseType.Chorus], verse_no))
|
||||
self.add_verse(self.decode(data), "%s%s" % (VerseType.tags[VerseType.Chorus], verse_no))
|
||||
elif block_key == BRIDGE:
|
||||
self.addVerse(self.decode(data), "%s%s" % (VerseType.tags[VerseType.Bridge], verse_no))
|
||||
self.add_verse(self.decode(data), "%s%s" % (VerseType.tags[VerseType.Bridge], verse_no))
|
||||
elif block_key == TOPIC:
|
||||
self.topics.append(self.decode(data))
|
||||
elif block_key == COMMENTS:
|
||||
@ -165,21 +165,28 @@ class SongShowPlusImport(SongImport):
|
||||
verse_tag = self.decode(verse_tag)
|
||||
self.ssp_verse_order_list.append(verse_tag)
|
||||
elif block_key == SONG_BOOK:
|
||||
self.songBookName = self.decode(data)
|
||||
self.song_book_name = self.decode(data)
|
||||
elif block_key == SONG_NUMBER:
|
||||
self.songNumber = ord(data)
|
||||
self.song_number = ord(data)
|
||||
elif block_key == CUSTOM_VERSE:
|
||||
verse_tag = self.to_openlp_verse_tag(verse_name)
|
||||
self.addVerse(self.decode(data), verse_tag)
|
||||
self.add_verse(self.decode(data), verse_tag)
|
||||
else:
|
||||
log.debug("Unrecognised blockKey: %s, data: %s" % (block_key, data))
|
||||
song_data.seek(next_block_starts)
|
||||
self.verseOrderList = self.ssp_verse_order_list
|
||||
self.verse_order_list = self.ssp_verse_order_list
|
||||
song_data.close()
|
||||
if not self.finish():
|
||||
self.logError(file)
|
||||
self.log_error(file)
|
||||
|
||||
def to_openlp_verse_tag(self, verse_name, ignore_unique=False):
|
||||
"""
|
||||
Handle OpenLP verse tags
|
||||
|
||||
:param verse_name: The verse name
|
||||
:param ignore_unique: Ignore if unique
|
||||
:return: The verse tags and verse number concatenated
|
||||
"""
|
||||
# Have we got any digits? If so, verse number is everything from the digits to the end (OpenLP does not have
|
||||
# concept of part verses, so just ignore any non integers on the end (including floats))
|
||||
match = re.match(r'(\D*)(\d+)', verse_name)
|
||||
|
@ -71,10 +71,10 @@ class SongsTab(SettingsTab):
|
||||
self.mode_group_box.setTitle(translate('SongsPlugin.SongsTab', 'Songs Mode'))
|
||||
self.search_as_type_check_box.setText(translate('SongsPlugin.SongsTab', 'Enable search as you type'))
|
||||
self.tool_bar_active_check_box.setText(translate('SongsPlugin.SongsTab',
|
||||
'Display verses on live tool bar'))
|
||||
'Display verses on live tool bar'))
|
||||
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',
|
||||
'Import missing songs from service files'))
|
||||
'Import missing songs from service files'))
|
||||
|
||||
def on_search_as_type_check_box_changed(self, check_state):
|
||||
self.song_search = (check_state == QtCore.Qt.Checked)
|
||||
|
@ -48,6 +48,7 @@ HOTKEY_TO_VERSE_TYPE = {
|
||||
'+': 'b',
|
||||
'Z': 'o'}
|
||||
|
||||
|
||||
class SundayPlusImport(SongImport):
|
||||
"""
|
||||
Import Sunday Plus songs
|
||||
@ -62,31 +63,38 @@ class SundayPlusImport(SongImport):
|
||||
SongImport.__init__(self, manager, **kwargs)
|
||||
self.encoding = 'us-ascii'
|
||||
|
||||
def doImport(self):
|
||||
def do_import(self):
|
||||
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
|
||||
for filename in self.import_source:
|
||||
if self.stop_import_flag:
|
||||
return
|
||||
song_file = open(filename)
|
||||
self.doImportFile(song_file)
|
||||
self.do_import_file(song_file)
|
||||
song_file.close()
|
||||
|
||||
def doImportFile(self, file):
|
||||
def do_import_file(self, file):
|
||||
"""
|
||||
Process the Sunday Plus file object.
|
||||
"""
|
||||
self.setDefaults()
|
||||
self.set_defaults()
|
||||
if not self.parse(file.read()):
|
||||
self.logError(file.name)
|
||||
self.log_error(file.name)
|
||||
return
|
||||
if not self.title:
|
||||
self.title = self.titleFromFilename(file.name)
|
||||
self.title = self.title_from_filename(file.name)
|
||||
if not self.finish():
|
||||
self.logError(file.name)
|
||||
self.log_error(file.name)
|
||||
|
||||
def parse(self, data, cell=False):
|
||||
"""
|
||||
Process the records
|
||||
|
||||
:param data: The data to be processed
|
||||
:param cell: ?
|
||||
:return:
|
||||
"""
|
||||
if len(data) == 0 or data[0:1] != '[' or data[-1] != ']':
|
||||
self.logError('File is malformed')
|
||||
self.log_error('File is malformed')
|
||||
return False
|
||||
i = 1
|
||||
verse_type = VerseType.tags[VerseType.Verse]
|
||||
@ -126,7 +134,7 @@ class SundayPlusImport(SongImport):
|
||||
elif name == 'Author':
|
||||
author = self.decode(self.unescape(value))
|
||||
if len(author):
|
||||
self.addAuthor(author)
|
||||
self.add_author(author)
|
||||
elif name == 'Copyright':
|
||||
self.copyright = self.decode(self.unescape(value))
|
||||
elif name[0:4] == 'CELL':
|
||||
@ -160,20 +168,26 @@ class SundayPlusImport(SongImport):
|
||||
if line[:3].lower() == 'ccl':
|
||||
m = re.search(r'[0-9]+', line)
|
||||
if m:
|
||||
self.ccliNumber = int(m.group(0))
|
||||
self.ccli_number = int(m.group(0))
|
||||
continue
|
||||
elif line.lower() == 'public domain':
|
||||
self.copyright = 'Public Domain'
|
||||
continue
|
||||
processed_lines.append(line)
|
||||
self.addVerse('\n'.join(processed_lines).strip(), verse_type)
|
||||
self.add_verse('\n'.join(processed_lines).strip(), verse_type)
|
||||
if end == -1:
|
||||
break
|
||||
i = end + 1
|
||||
i += 1
|
||||
return True
|
||||
|
||||
def titleFromFilename(self, filename):
|
||||
def title_from_filename(self, filename):
|
||||
"""
|
||||
Extract the title from the filename
|
||||
|
||||
:param filename: File name
|
||||
:return:
|
||||
"""
|
||||
title = os.path.split(filename)[1]
|
||||
if title.endswith('.ptf'):
|
||||
title = title[:-4]
|
||||
|
@ -51,7 +51,7 @@ class WorshipCenterProImport(SongImport):
|
||||
"""
|
||||
SongImport.__init__(self, manager, **kwargs)
|
||||
|
||||
def doImport(self):
|
||||
def do_import(self):
|
||||
"""
|
||||
Receive a single file to import.
|
||||
"""
|
||||
@ -60,7 +60,7 @@ class WorshipCenterProImport(SongImport):
|
||||
except (pyodbc.DatabaseError, pyodbc.IntegrityError, pyodbc.InternalError, pyodbc.OperationalError) as e:
|
||||
log.warn('Unable to connect the WorshipCenter Pro database %s. %s', self.import_source, str(e))
|
||||
# Unfortunately no specific exception type
|
||||
self.logError(self.import_source,
|
||||
self.log_error(self.import_source,
|
||||
translate('SongsPlugin.WorshipCenterProImport', 'Unable to connect the WorshipCenter Pro database.'))
|
||||
return
|
||||
cursor = conn.cursor()
|
||||
@ -76,10 +76,10 @@ class WorshipCenterProImport(SongImport):
|
||||
for song in songs:
|
||||
if self.stop_import_flag:
|
||||
break
|
||||
self.setDefaults()
|
||||
self.set_defaults()
|
||||
self.title = songs[song]['TITLE']
|
||||
lyrics = songs[song]['LYRICS'].strip('&crlf;&crlf;')
|
||||
for verse in lyrics.split('&crlf;&crlf;'):
|
||||
verse = verse.replace('&crlf;', '\n')
|
||||
self.addVerse(verse)
|
||||
self.add_verse(verse)
|
||||
self.finish()
|
||||
|
@ -40,6 +40,7 @@ BLOCK_TYPES = ('V', 'C', 'B')
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WowImport(SongImport):
|
||||
"""
|
||||
The :class:`WowImport` class provides the ability to import song files from
|
||||
@ -101,7 +102,7 @@ class WowImport(SongImport):
|
||||
"""
|
||||
SongImport.__init__(self, manager, **kwargs)
|
||||
|
||||
def doImport(self):
|
||||
def do_import(self):
|
||||
"""
|
||||
Receive a single file or a list of files to import.
|
||||
"""
|
||||
@ -110,38 +111,42 @@ class WowImport(SongImport):
|
||||
for source in self.import_source:
|
||||
if self.stop_import_flag:
|
||||
return
|
||||
self.setDefaults()
|
||||
self.set_defaults()
|
||||
song_data = open(source, 'rb')
|
||||
if song_data.read(19) != 'WoW File\nSong Words':
|
||||
self.logError(source, str(translate('SongsPlugin.WordsofWorshipSongImport',
|
||||
('Invalid Words of Worship song file. Missing "Wow File\\nSong Words" header.'))))
|
||||
self.log_error(source,
|
||||
str(translate('SongsPlugin.WordsofWorshipSongImport',
|
||||
'Invalid Words of Worship song file. Missing "Wow File\\nSong '
|
||||
'Words" header.')))
|
||||
continue
|
||||
# Seek to byte which stores number of blocks in the song
|
||||
song_data.seek(56)
|
||||
no_of_blocks = ord(song_data.read(1))
|
||||
song_data.seek(66)
|
||||
if song_data.read(16) != 'CSongDoc::CBlock':
|
||||
self.logError(source, str(translate('SongsPlugin.WordsofWorshipSongImport',
|
||||
('Invalid Words of Worship song file. Missing "CSongDoc::CBlock" string.'))))
|
||||
self.log_error(source,
|
||||
str(translate('SongsPlugin.WordsofWorshipSongImport',
|
||||
'Invalid Words of Worship song file. Missing "CSongDoc::CBlock" '
|
||||
'string.')))
|
||||
continue
|
||||
# Seek to the beginning of the first block
|
||||
song_data.seek(82)
|
||||
for block in range(no_of_blocks):
|
||||
self.linesToRead = ord(song_data.read(4)[:1])
|
||||
self.lines_to_read = ord(song_data.read(4)[:1])
|
||||
block_text = ''
|
||||
while self.linesToRead:
|
||||
self.lineText = str(song_data.read(ord(song_data.read(1))), 'cp1252')
|
||||
while self.lines_to_read:
|
||||
self.line_text = str(song_data.read(ord(song_data.read(1))), 'cp1252')
|
||||
song_data.seek(1, os.SEEK_CUR)
|
||||
if block_text:
|
||||
block_text += '\n'
|
||||
block_text += self.lineText
|
||||
self.linesToRead -= 1
|
||||
block_text += self.line_text
|
||||
self.lines_to_read -= 1
|
||||
block_type = BLOCK_TYPES[ord(song_data.read(4)[:1])]
|
||||
# Blocks are separated by 2 bytes, skip them, but not if
|
||||
# this is the last block!
|
||||
if block + 1 < no_of_blocks:
|
||||
song_data.seek(2, os.SEEK_CUR)
|
||||
self.addVerse(block_text, block_type)
|
||||
self.add_verse(block_text, block_type)
|
||||
# Now to extract the author
|
||||
author_length = ord(song_data.read(1))
|
||||
if author_length:
|
||||
@ -149,10 +154,10 @@ class WowImport(SongImport):
|
||||
# Finally the copyright
|
||||
copyright_length = ord(song_data.read(1))
|
||||
if copyright_length:
|
||||
self.addCopyright(str(song_data.read(copyright_length), 'cp1252'))
|
||||
self.add_copyright(str(song_data.read(copyright_length), 'cp1252'))
|
||||
file_name = os.path.split(source)[1]
|
||||
# Get the song title
|
||||
self.title = file_name.rpartition('.')[0]
|
||||
song_data.close()
|
||||
if not self.finish():
|
||||
self.logError(source)
|
||||
self.log_error(source)
|
||||
|
@ -97,21 +97,13 @@ class SongXML(object):
|
||||
"""
|
||||
Add a verse to the ``<lyrics>`` tag.
|
||||
|
||||
``type``
|
||||
A string denoting the type of verse. Possible values are *v*, *c*, *b*, *p*, *i*, *e* and *o*. Any other
|
||||
type is **not** allowed, this also includes translated types.
|
||||
|
||||
``number``
|
||||
An integer denoting the number of the item, for example: verse 1.
|
||||
|
||||
``content``
|
||||
The actual text of the verse to be stored.
|
||||
|
||||
``lang``
|
||||
The verse's language code (ISO-639). This is not required, but should be added if available.
|
||||
:param type: A string denoting the type of verse. Possible values are *v*, *c*, *b*, *p*, *i*, *e* and *o*.
|
||||
Any other type is **not** allowed, this also includes translated types.
|
||||
:param number: An integer denoting the number of the item, for example: verse 1.
|
||||
:param content: The actual text of the verse to be stored.
|
||||
:param lang: The verse's language code (ISO-639). This is not required, but should be added if available.
|
||||
"""
|
||||
verse = etree.Element('verse', type=str(type),
|
||||
label=str(number))
|
||||
verse = etree.Element('verse', type=str(type), label=str(number))
|
||||
if lang:
|
||||
verse.set('lang', lang)
|
||||
verse.text = etree.CDATA(content)
|
||||
@ -121,16 +113,13 @@ class SongXML(object):
|
||||
"""
|
||||
Extract our newly created XML song.
|
||||
"""
|
||||
return etree.tostring(self.song_xml, encoding='UTF-8',
|
||||
xml_declaration=True)
|
||||
return etree.tostring(self.song_xml, encoding='UTF-8', xml_declaration=True)
|
||||
|
||||
def get_verses(self, xml):
|
||||
"""
|
||||
Iterates through the verses in the XML and returns a list of verses and their attributes.
|
||||
|
||||
``xml``
|
||||
The XML of the song to be parsed.
|
||||
|
||||
:param xml: The XML of the song to be parsed.
|
||||
The returned list has the following format::
|
||||
|
||||
[[{'type': 'v', 'label': '1'}, u"optional slide split 1[---]optional slide split 2"],
|
||||
@ -351,8 +340,7 @@ class OpenLyrics(object):
|
||||
|
||||
The first unicode string are the start tags (for the next slide). The second unicode string are the end tags.
|
||||
|
||||
``text``
|
||||
The text to test. The text must **not** contain html tags, only OpenLP formatting tags are allowed::
|
||||
:param text: The text to test. The text must **not** contain html tags, only OpenLP formatting tags are allowed::
|
||||
|
||||
{st}{r}Text text text
|
||||
"""
|
||||
@ -378,12 +366,9 @@ class OpenLyrics(object):
|
||||
Create and save a song from OpenLyrics format xml to the database. Since we also export XML from external
|
||||
sources (e. g. OpenLyrics import), we cannot ensure, that it completely conforms to the OpenLyrics standard.
|
||||
|
||||
``xml``
|
||||
The XML to parse (unicode).
|
||||
|
||||
``parse_and_temporary_save``
|
||||
Switch to skip processing the whole song and storing the songs in the database with a temporary flag.
|
||||
Defaults to ``False``.
|
||||
:param xml: The XML to parse (unicode).
|
||||
:param parse_and_temporary_save: Switch to skip processing the whole song and storing the songs in the database
|
||||
with a temporary flag. Defaults to ``False``.
|
||||
"""
|
||||
# No xml get out of here.
|
||||
if not xml:
|
||||
@ -418,6 +403,15 @@ class OpenLyrics(object):
|
||||
return song
|
||||
|
||||
def _add_text_to_element(self, tag, parent, text=None, label=None):
|
||||
"""
|
||||
Build an element
|
||||
|
||||
:param tag: A Tag
|
||||
:param parent: Its parent
|
||||
:param text: Some text to be added
|
||||
:param label: And a label
|
||||
:return:
|
||||
"""
|
||||
if label:
|
||||
element = etree.Element(tag, name=str(label))
|
||||
else:
|
||||
@ -430,6 +424,9 @@ class OpenLyrics(object):
|
||||
def _add_tag_to_formatting(self, tag_name, tags_element):
|
||||
"""
|
||||
Add new formatting tag to the element ``<format>`` if the tag is not present yet.
|
||||
|
||||
:param tag_name: The tag_name
|
||||
:param tags_element: Some tag elements
|
||||
"""
|
||||
available_tags = FormattingTags.get_html_tags()
|
||||
start_tag = '{%s}' % tag_name
|
||||
@ -477,16 +474,16 @@ class OpenLyrics(object):
|
||||
def _extract_xml(self, xml):
|
||||
"""
|
||||
Extract our newly created XML song.
|
||||
|
||||
:param xml: The XML
|
||||
"""
|
||||
return etree.tostring(xml, encoding='UTF-8',
|
||||
xml_declaration=True)
|
||||
return etree.tostring(xml, encoding='UTF-8', xml_declaration=True)
|
||||
|
||||
def _text(self, element):
|
||||
"""
|
||||
This returns the text of an element as unicode string.
|
||||
|
||||
``element``
|
||||
The element.
|
||||
:param element: The element.
|
||||
"""
|
||||
if element.text is not None:
|
||||
return str(element.text)
|
||||
@ -496,11 +493,8 @@ class OpenLyrics(object):
|
||||
"""
|
||||
Adds the authors specified in the XML to the song.
|
||||
|
||||
``properties``
|
||||
The property object (lxml.objectify.ObjectifiedElement).
|
||||
|
||||
``song``
|
||||
The song object.
|
||||
:param properties: The property object (lxml.objectify.ObjectifiedElement).
|
||||
:param song: The song object
|
||||
"""
|
||||
authors = []
|
||||
if hasattr(properties, 'authors'):
|
||||
@ -509,24 +503,20 @@ class OpenLyrics(object):
|
||||
if display_name:
|
||||
authors.append(display_name)
|
||||
for display_name 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:
|
||||
# We need to create a new author, as the author does not exist.
|
||||
author = Author.populate(display_name=display_name,
|
||||
last_name=display_name.split(' ')[-1],
|
||||
first_name=' '.join(display_name.split(' ')[:-1]))
|
||||
last_name=display_name.split(' ')[-1],
|
||||
first_name=' '.join(display_name.split(' ')[:-1]))
|
||||
song.authors.append(author)
|
||||
|
||||
def _process_cclinumber(self, properties, song):
|
||||
"""
|
||||
Adds the CCLI number to the song.
|
||||
|
||||
``properties``
|
||||
The property object (lxml.objectify.ObjectifiedElement).
|
||||
|
||||
``song``
|
||||
The song object.
|
||||
:param properties: The property object (lxml.objectify.ObjectifiedElement).
|
||||
:param song: The song object.
|
||||
"""
|
||||
if hasattr(properties, 'ccliNo'):
|
||||
song.ccli_number = self._text(properties.ccliNo)
|
||||
@ -535,11 +525,8 @@ class OpenLyrics(object):
|
||||
"""
|
||||
Joins the comments specified in the XML and add it to the song.
|
||||
|
||||
``properties``
|
||||
The property object (lxml.objectify.ObjectifiedElement).
|
||||
|
||||
``song``
|
||||
The song object.
|
||||
:param properties: The property object (lxml.objectify.ObjectifiedElement).
|
||||
:param song: The song object.
|
||||
"""
|
||||
if hasattr(properties, 'comments'):
|
||||
comments_list = []
|
||||
@ -553,11 +540,8 @@ class OpenLyrics(object):
|
||||
"""
|
||||
Adds the copyright to the song.
|
||||
|
||||
``properties``
|
||||
The property object (lxml.objectify.ObjectifiedElement).
|
||||
|
||||
``song``
|
||||
The song object.
|
||||
:param properties: The property object (lxml.objectify.ObjectifiedElement).
|
||||
:param song: The song object.
|
||||
"""
|
||||
if hasattr(properties, 'copyright'):
|
||||
song.copyright = self._text(properties.copyright)
|
||||
@ -566,6 +550,9 @@ class OpenLyrics(object):
|
||||
"""
|
||||
Process the formatting tags from the song and either add missing tags temporary or permanently to the
|
||||
formatting tag list.
|
||||
|
||||
:param song_xml: The song XML
|
||||
:param temporary: Is the song temporary?
|
||||
"""
|
||||
if not hasattr(song_xml, 'format'):
|
||||
return
|
||||
@ -603,11 +590,9 @@ class OpenLyrics(object):
|
||||
Converts the xml text with mixed content to OpenLP representation. Chords are skipped and formatting tags are
|
||||
converted.
|
||||
|
||||
``element``
|
||||
The property object (lxml.etree.Element).
|
||||
|
||||
``newlines``
|
||||
The switch to enable/disable processing of line breaks <br/>. The <br/> is used since OpenLyrics 0.8.
|
||||
:param element: The property object (lxml.etree.Element).
|
||||
:param newlines: The switch to enable/disable processing of line breaks <br/>. The <br/> is used since
|
||||
OpenLyrics 0.8.
|
||||
"""
|
||||
text = ''
|
||||
use_endtag = True
|
||||
@ -655,8 +640,8 @@ class OpenLyrics(object):
|
||||
"""
|
||||
Converts lyrics lines to OpenLP representation.
|
||||
|
||||
``lines``
|
||||
The lines object (lxml.objectify.ObjectifiedElement).
|
||||
:param lines: The lines object (lxml.objectify.ObjectifiedElement).
|
||||
:param version:
|
||||
"""
|
||||
text = ''
|
||||
# Convert lxml.objectify to lxml.etree representation.
|
||||
@ -682,14 +667,9 @@ class OpenLyrics(object):
|
||||
"""
|
||||
Processes the verses and search_lyrics for the song.
|
||||
|
||||
``properties``
|
||||
The properties object (lxml.objectify.ObjectifiedElement).
|
||||
|
||||
``song_xml``
|
||||
The objectified song (lxml.objectify.ObjectifiedElement).
|
||||
|
||||
``song_obj``
|
||||
The song object.
|
||||
:param properties: The properties object (lxml.objectify.ObjectifiedElement).
|
||||
:param song_xml: The objectified song (lxml.objectify.ObjectifiedElement).
|
||||
:param song_obj: The song object.
|
||||
"""
|
||||
sxml = SongXML()
|
||||
verses = {}
|
||||
@ -698,12 +678,12 @@ class OpenLyrics(object):
|
||||
lyrics = song_xml.lyrics
|
||||
except AttributeError:
|
||||
raise OpenLyricsError(OpenLyricsError.LyricsError, '<lyrics> tag is missing.',
|
||||
translate('OpenLP.OpenLyricsImportError', '<lyrics> tag is missing.'))
|
||||
translate('OpenLP.OpenLyricsImportError', '<lyrics> tag is missing.'))
|
||||
try:
|
||||
verse_list = lyrics.verse
|
||||
except AttributeError:
|
||||
raise OpenLyricsError(OpenLyricsError.VerseError, '<verse> tag is missing.',
|
||||
translate('OpenLP.OpenLyricsImportError', '<verse> tag is missing.'))
|
||||
translate('OpenLP.OpenLyricsImportError', '<verse> tag is missing.'))
|
||||
# Loop over the "verse" elements.
|
||||
for verse in verse_list:
|
||||
text = ''
|
||||
@ -712,8 +692,7 @@ class OpenLyrics(object):
|
||||
if text:
|
||||
text += '\n'
|
||||
# Append text from "lines" element to verse text.
|
||||
text += self._process_verse_lines(lines,
|
||||
version=song_xml.get('version'))
|
||||
text += self._process_verse_lines(lines, version=song_xml.get('version'))
|
||||
# Add an optional split to the verse text.
|
||||
if lines.get('break') is not None:
|
||||
text += '\n[---]'
|
||||
@ -749,11 +728,8 @@ class OpenLyrics(object):
|
||||
"""
|
||||
Adds the song book and song number specified in the XML to the song.
|
||||
|
||||
``properties``
|
||||
The property object (lxml.objectify.ObjectifiedElement).
|
||||
|
||||
``song``
|
||||
The song object.
|
||||
:param properties: The property object (lxml.objectify.ObjectifiedElement).
|
||||
:param song: The song object.
|
||||
"""
|
||||
song.song_book_id = None
|
||||
song.song_number = ''
|
||||
@ -775,11 +751,8 @@ class OpenLyrics(object):
|
||||
"""
|
||||
Processes the titles specified in the song's XML.
|
||||
|
||||
``properties``
|
||||
The property object (lxml.objectify.ObjectifiedElement).
|
||||
|
||||
``song``
|
||||
The song object.
|
||||
:param properties: The property object (lxml.objectify.ObjectifiedElement).
|
||||
:param song: The song object.
|
||||
"""
|
||||
for title in properties.titles.title:
|
||||
if not song.title:
|
||||
@ -792,11 +765,8 @@ class OpenLyrics(object):
|
||||
"""
|
||||
Adds the topics to the song.
|
||||
|
||||
``properties``
|
||||
The property object (lxml.objectify.ObjectifiedElement).
|
||||
|
||||
``song``
|
||||
The song object.
|
||||
:param properties: The property object (lxml.objectify.ObjectifiedElement).
|
||||
:param song: The song object.
|
||||
"""
|
||||
if hasattr(properties, 'themes'):
|
||||
for topic_text in properties.themes.theme:
|
||||
|
@ -41,6 +41,7 @@ log = logging.getLogger(__name__)
|
||||
# Used to strip control chars (except 10=LF, 13=CR)
|
||||
CONTROL_CHARS_MAP = dict.fromkeys(list(range(10)) + [11, 12] + list(range(14,32)) + [127])
|
||||
|
||||
|
||||
class ZionWorxImport(SongImport):
|
||||
"""
|
||||
The :class:`ZionWorxImport` class provides the ability to import songs
|
||||
@ -78,19 +79,19 @@ class ZionWorxImport(SongImport):
|
||||
* Note: This is the default format of the Python ``csv`` module.
|
||||
|
||||
"""
|
||||
def doImport(self):
|
||||
def do_import(self):
|
||||
"""
|
||||
Receive a CSV file (from a ZionWorx database dump) to import.
|
||||
"""
|
||||
with open(self.import_source, 'rb') as songs_file:
|
||||
field_names = ['SongNum', 'Title1', 'Title2', 'Lyrics', 'Writer', 'Copyright', 'Keywords',
|
||||
'DefaultStyle']
|
||||
'DefaultStyle']
|
||||
songs_reader = csv.DictReader(songs_file, field_names)
|
||||
try:
|
||||
records = list(songs_reader)
|
||||
except csv.Error as e:
|
||||
self.logError(translate('SongsPlugin.ZionWorxImport', 'Error reading CSV file.'),
|
||||
translate('SongsPlugin.ZionWorxImport', 'Line %d: %s') % (songs_reader.line_num, e))
|
||||
self.log_error(translate('SongsPlugin.ZionWorxImport', 'Error reading CSV file.'),
|
||||
translate('SongsPlugin.ZionWorxImport', 'Line %d: %s') % (songs_reader.line_num, e))
|
||||
return
|
||||
num_records = len(records)
|
||||
log.info('%s records found in CSV file' % num_records)
|
||||
@ -98,20 +99,20 @@ class ZionWorxImport(SongImport):
|
||||
for index, record in enumerate(records, 1):
|
||||
if self.stop_import_flag:
|
||||
return
|
||||
self.setDefaults()
|
||||
self.set_defaults()
|
||||
try:
|
||||
self.title = self._decode(record['Title1'])
|
||||
if record['Title2']:
|
||||
self.alternate_title = self._decode(record['Title2'])
|
||||
self.parse_author(self._decode(record['Writer']))
|
||||
self.addCopyright(self._decode(record['Copyright']))
|
||||
self.add_copyright(self._decode(record['Copyright']))
|
||||
lyrics = self._decode(record['Lyrics'])
|
||||
except UnicodeDecodeError as e:
|
||||
self.logError(translate('SongsPlugin.ZionWorxImport', 'Record %d' % index),
|
||||
translate('SongsPlugin.ZionWorxImport', 'Decoding error: %s') % e)
|
||||
self.log_error(translate('SongsPlugin.ZionWorxImport', 'Record %d' % index),
|
||||
translate('SongsPlugin.ZionWorxImport', 'Decoding error: %s') % e)
|
||||
continue
|
||||
except TypeError as e:
|
||||
self.logError(translate(
|
||||
self.log_error(translate(
|
||||
'SongsPlugin.ZionWorxImport', 'File not valid ZionWorx CSV format.'), 'TypeError: %s' % e)
|
||||
return
|
||||
verse = ''
|
||||
@ -119,19 +120,18 @@ class ZionWorxImport(SongImport):
|
||||
if line and not line.isspace():
|
||||
verse += line + '\n'
|
||||
elif verse:
|
||||
self.addVerse(verse)
|
||||
self.add_verse(verse)
|
||||
verse = ''
|
||||
if verse:
|
||||
self.addVerse(verse)
|
||||
self.add_verse(verse)
|
||||
title = self.title
|
||||
if not self.finish():
|
||||
self.logError(translate('SongsPlugin.ZionWorxImport', 'Record %d') % index
|
||||
+ (': "' + title + '"' if title else ''))
|
||||
self.log_error(translate('SongsPlugin.ZionWorxImport', 'Record %d') % index
|
||||
+ (': "' + title + '"' if title else ''))
|
||||
|
||||
def _decode(self, str):
|
||||
"""
|
||||
Decodes CSV input to unicode, stripping all control characters (except
|
||||
new lines).
|
||||
Decodes CSV input to unicode, stripping all control characters (except new lines).
|
||||
"""
|
||||
# This encoding choice seems OK. ZionWorx has no option for setting the
|
||||
# encoding for its songs, so we assume encoding is always the same.
|
||||
|
@ -314,7 +314,7 @@ class SongsPlugin(Plugin):
|
||||
self.application.process_events()
|
||||
for db in song_dbs:
|
||||
importer = OpenLPSongImport(self.manager, filename=db)
|
||||
importer.doImport(progress)
|
||||
importer.do_import(progress)
|
||||
self.application.process_events()
|
||||
progress.setValue(song_count)
|
||||
self.media_item.on_search_text_button_clicked()
|
||||
|
@ -1,3 +1,28 @@
|
||||
"""
|
||||
Tests for the Bibles plugin
|
||||
"""
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2014 Raoul Snyman #
|
||||
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
|
||||
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
|
||||
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
|
||||
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
|
||||
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
|
||||
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
|
||||
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
@ -85,3 +85,5 @@ class TestLib(TestCase):
|
||||
|
||||
# THEN: It should be False
|
||||
self.assertFalse(has_verse_list, 'The SearchResults object should have a verse list')
|
||||
|
||||
|
||||
|
@ -83,7 +83,7 @@ class TestVerseReferenceList(TestCase):
|
||||
# THEN: The current index should be 0 and the end pointer of the entry should be '2'
|
||||
self.assertEqual(reference_list.current_index, 0, 'The current index should be 0')
|
||||
self.assertEqual(reference_list.verse_list[0]['end'], next_verse,
|
||||
'The end in first entry should be %u' % next_verse)
|
||||
'The end in first entry should be %u' % next_verse)
|
||||
|
||||
def add_another_verse_test(self):
|
||||
"""
|
||||
@ -124,8 +124,8 @@ class TestVerseReferenceList(TestCase):
|
||||
# THEN: the data will be appended to the list
|
||||
self.assertEqual(len(reference_list.version_list), 1, 'The version data should be appended')
|
||||
self.assertEqual(reference_list.version_list[0],
|
||||
{'version': version, 'copyright': copyright_, 'permission': permission},
|
||||
'The version data should be appended')
|
||||
{'version': version, 'copyright': copyright_, 'permission': permission},
|
||||
'The version data should be appended')
|
||||
|
||||
def add_existing_version_test(self):
|
||||
"""
|
||||
|
@ -118,7 +118,7 @@ class TestPptviewDocument(TestCase):
|
||||
self.mock_os.path.isdir.return_value = True
|
||||
self.mock_controller.process.OpenPPT.return_value = 0
|
||||
instance = PptviewDocument(self.mock_controller, self.mock_presentation)
|
||||
instance.filepath = 'test\path.ppt'
|
||||
instance.file_path = 'test\path.ppt'
|
||||
|
||||
if os.name == 'nt':
|
||||
result = instance.load_presentation()
|
||||
@ -138,7 +138,7 @@ class TestPptviewDocument(TestCase):
|
||||
self.mock_os.path.isdir.return_value = False
|
||||
self.mock_controller.process.OpenPPT.return_value = -1
|
||||
instance = PptviewDocument(self.mock_controller, self.mock_presentation)
|
||||
instance.filepath = 'test\path.ppt'
|
||||
instance.file_path = 'test\path.ppt'
|
||||
if os.name == 'nt':
|
||||
result = instance.load_presentation()
|
||||
|
||||
|
@ -154,13 +154,13 @@ class TestEasyWorshipSongImport(TestCase):
|
||||
|
||||
def find_field_exists_test(self):
|
||||
"""
|
||||
Test finding an existing field in a given list using the :mod:`findField`
|
||||
Test finding an existing field in a given list using the :mod:`find_field`
|
||||
"""
|
||||
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a list of field descriptions.
|
||||
with patch('openlp.plugins.songs.lib.ewimport.SongImport'):
|
||||
mocked_manager = MagicMock()
|
||||
importer = EasyWorshipSongImport(mocked_manager)
|
||||
importer.fieldDescs = TEST_FIELD_DESCS
|
||||
importer.field_descriptions = TEST_FIELD_DESCS
|
||||
|
||||
# WHEN: Called with a field name that exists
|
||||
existing_fields = ['Title', 'Text Percentage Bottom', 'RecID', 'Default Background', 'Words',
|
||||
@ -168,28 +168,28 @@ class TestEasyWorshipSongImport(TestCase):
|
||||
for field_name in existing_fields:
|
||||
|
||||
# THEN: The item corresponding the index returned should have the same name attribute
|
||||
self.assertEquals(importer.fieldDescs[importer.findField(field_name)].name, field_name)
|
||||
self.assertEquals(importer.field_descriptions[importer.find_field(field_name)].name, field_name)
|
||||
|
||||
def find_non_existing_field_test(self):
|
||||
"""
|
||||
Test finding an non-existing field in a given list using the :mod:`findField`
|
||||
Test finding an non-existing field in a given list using the :mod:`find_field`
|
||||
"""
|
||||
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a list of field descriptions
|
||||
with patch('openlp.plugins.songs.lib.ewimport.SongImport'):
|
||||
mocked_manager = MagicMock()
|
||||
importer = EasyWorshipSongImport(mocked_manager)
|
||||
importer.fieldDescs = TEST_FIELD_DESCS
|
||||
importer.field_descriptions = TEST_FIELD_DESCS
|
||||
|
||||
# WHEN: Called with a field name that does not exist
|
||||
non_existing_fields = ['BK Gradient Shading', 'BK Gradient Variant', 'Favorite', 'Copyright']
|
||||
for field_name in non_existing_fields:
|
||||
|
||||
# THEN: The importer object should not be None
|
||||
self.assertRaises(IndexError, importer.findField, field_name)
|
||||
self.assertRaises(IndexError, importer.find_field, field_name)
|
||||
|
||||
def set_record_struct_test(self):
|
||||
"""
|
||||
Test the :mod:`setRecordStruct` module
|
||||
Test the :mod:`set_record_struct` module
|
||||
"""
|
||||
# GIVEN: A mocked out SongImport class, a mocked out struct class, and a mocked out "manager" and a list of
|
||||
# field descriptions
|
||||
@ -198,17 +198,17 @@ class TestEasyWorshipSongImport(TestCase):
|
||||
mocked_manager = MagicMock()
|
||||
importer = EasyWorshipSongImport(mocked_manager)
|
||||
|
||||
# WHEN: setRecordStruct is called with a list of field descriptions
|
||||
return_value = importer.setRecordStruct(TEST_FIELD_DESCS)
|
||||
# WHEN: set_record_struct is called with a list of field descriptions
|
||||
return_value = importer.set_record_struct(TEST_FIELD_DESCS)
|
||||
|
||||
# THEN: setRecordStruct should return None and Struct should be called with a value representing
|
||||
# THEN: set_record_struct should return None and Struct should be called with a value representing
|
||||
# the list of field descriptions
|
||||
self.assertIsNone(return_value, 'setRecordStruct should return None')
|
||||
self.assertIsNone(return_value, 'set_record_struct should return None')
|
||||
mocked_struct.Struct.assert_called_with('>50sHIB250s250s10sQ')
|
||||
|
||||
def get_field_test(self):
|
||||
"""
|
||||
Test the :mod:`getField` module
|
||||
Test the :mod:`get_field` module
|
||||
"""
|
||||
# GIVEN: A mocked out SongImport class, a mocked out "manager", an encoding and some test data and known results
|
||||
with patch('openlp.plugins.songs.lib.ewimport.SongImport'):
|
||||
@ -216,20 +216,20 @@ class TestEasyWorshipSongImport(TestCase):
|
||||
importer = EasyWorshipSongImport(mocked_manager)
|
||||
importer.encoding = TEST_DATA_ENCODING
|
||||
importer.fields = TEST_FIELDS
|
||||
importer.fieldDescs = TEST_FIELD_DESCS
|
||||
importer.field_descriptions = TEST_FIELD_DESCS
|
||||
field_results = [(0, b'A Heart Like Thine'), (1, 100), (2, 102), (3, True), (6, None), (7, None)]
|
||||
|
||||
# WHEN: Called with test data
|
||||
for field_index, result in field_results:
|
||||
return_value = importer.getField(field_index)
|
||||
return_value = importer.get_field(field_index)
|
||||
|
||||
# THEN: getField should return the known results
|
||||
# THEN: get_field should return the known results
|
||||
self.assertEquals(return_value, result,
|
||||
'getField should return "%s" when called with "%s"' % (result, TEST_FIELDS[field_index]))
|
||||
'get_field should return "%s" when called with "%s"' % (result, TEST_FIELDS[field_index]))
|
||||
|
||||
def get_memo_field_test(self):
|
||||
"""
|
||||
Test the :mod:`getField` module
|
||||
Test the :mod:`get_field` module
|
||||
"""
|
||||
for test_results in GET_MEMO_FIELD_TEST_RESULTS:
|
||||
# GIVEN: A mocked out SongImport class, a mocked out "manager", a mocked out memo_file and an encoding
|
||||
@ -237,20 +237,20 @@ class TestEasyWorshipSongImport(TestCase):
|
||||
mocked_manager = MagicMock()
|
||||
mocked_memo_file = MagicMock()
|
||||
importer = EasyWorshipSongImport(mocked_manager)
|
||||
importer.memoFile = mocked_memo_file
|
||||
importer.memo_file = mocked_memo_file
|
||||
importer.encoding = TEST_DATA_ENCODING
|
||||
|
||||
# WHEN: Supplied with test fields and test field descriptions
|
||||
importer.fields = TEST_FIELDS
|
||||
importer.fieldDescs = TEST_FIELD_DESCS
|
||||
importer.field_descriptions = TEST_FIELD_DESCS
|
||||
field_index = test_results[0]
|
||||
mocked_memo_file.read.return_value = test_results[1]
|
||||
get_field_result = test_results[2]['return']
|
||||
get_field_read_calls = test_results[2]['read']
|
||||
get_field_seek_calls = test_results[2]['seek']
|
||||
|
||||
# THEN: getField should return the appropriate value with the appropriate mocked objects being called
|
||||
self.assertEquals(importer.getField(field_index), get_field_result)
|
||||
# THEN: get_field should return the appropriate value with the appropriate mocked objects being called
|
||||
self.assertEquals(importer.get_field(field_index), get_field_result)
|
||||
for call in get_field_read_calls:
|
||||
mocked_memo_file.read.assert_any_call(call)
|
||||
for call in get_field_seek_calls:
|
||||
@ -261,7 +261,7 @@ class TestEasyWorshipSongImport(TestCase):
|
||||
|
||||
def do_import_source_test(self):
|
||||
"""
|
||||
Test the :mod:`doImport` module opens the correct files
|
||||
Test the :mod:`do_import` module opens the correct files
|
||||
"""
|
||||
# GIVEN: A mocked out SongImport class, a mocked out "manager"
|
||||
with patch('openlp.plugins.songs.lib.ewimport.SongImport'), \
|
||||
@ -273,14 +273,14 @@ class TestEasyWorshipSongImport(TestCase):
|
||||
# WHEN: Supplied with an import source
|
||||
importer.import_source = 'Songs.DB'
|
||||
|
||||
# THEN: doImport should return None having called os.path.isfile
|
||||
self.assertIsNone(importer.doImport(), 'doImport should return None')
|
||||
# THEN: do_import should return None having called os.path.isfile
|
||||
self.assertIsNone(importer.do_import(), 'do_import should return None')
|
||||
mocked_os_path.isfile.assert_any_call('Songs.DB')
|
||||
mocked_os_path.isfile.assert_any_call('Songs.MB')
|
||||
|
||||
def do_import_database_validity_test(self):
|
||||
"""
|
||||
Test the :mod:`doImport` module handles invalid database files correctly
|
||||
Test the :mod:`do_import` module handles invalid database files correctly
|
||||
"""
|
||||
# GIVEN: A mocked out SongImport class, os.path and a mocked out "manager"
|
||||
with patch('openlp.plugins.songs.lib.ewimport.SongImport'), \
|
||||
@ -293,13 +293,13 @@ class TestEasyWorshipSongImport(TestCase):
|
||||
# WHEN: DB file size is less than 0x800
|
||||
mocked_os_path.getsize.return_value = 0x7FF
|
||||
|
||||
# THEN: doImport should return None having called os.path.isfile
|
||||
self.assertIsNone(importer.doImport(), 'doImport should return None when db_size is less than 0x800')
|
||||
# THEN: do_import should return None having called os.path.isfile
|
||||
self.assertIsNone(importer.do_import(), 'do_import should return None when db_size is less than 0x800')
|
||||
mocked_os_path.getsize.assert_any_call('Songs.DB')
|
||||
|
||||
def do_import_memo_validty_test(self):
|
||||
"""
|
||||
Test the :mod:`doImport` module handles invalid memo files correctly
|
||||
Test the :mod:`do_import` module handles invalid memo files correctly
|
||||
"""
|
||||
# GIVEN: A mocked out SongImport class, a mocked out "manager"
|
||||
with patch('openlp.plugins.songs.lib.ewimport.SongImport'), \
|
||||
@ -316,9 +316,9 @@ class TestEasyWorshipSongImport(TestCase):
|
||||
struct_unpack_return_values = [(0, 0x700, 2, 0, 0), (0, 0x800, 0, 0, 0), (0, 0x800, 5, 0, 0)]
|
||||
mocked_struct.unpack.side_effect = struct_unpack_return_values
|
||||
|
||||
# THEN: doImport should return None having called closed the open files db and memo files.
|
||||
# THEN: do_import should return None having called closed the open files db and memo files.
|
||||
for effect in struct_unpack_return_values:
|
||||
self.assertIsNone(importer.doImport(), 'doImport should return None when db_size is less than 0x800')
|
||||
self.assertIsNone(importer.do_import(), 'do_import should return None when db_size is less than 0x800')
|
||||
self.assertEqual(mocked_open().close.call_count, 2,
|
||||
'The open db and memo files should have been closed')
|
||||
mocked_open().close.reset_mock()
|
||||
@ -326,7 +326,7 @@ class TestEasyWorshipSongImport(TestCase):
|
||||
|
||||
def code_page_to_encoding_test(self):
|
||||
"""
|
||||
Test the :mod:`doImport` converts the code page to the encoding correctly
|
||||
Test the :mod:`do_import` converts the code page to the encoding correctly
|
||||
"""
|
||||
# GIVEN: A mocked out SongImport class, a mocked out "manager"
|
||||
with patch('openlp.plugins.songs.lib.ewimport.SongImport'), \
|
||||
@ -345,8 +345,8 @@ class TestEasyWorshipSongImport(TestCase):
|
||||
mocked_struct.unpack.side_effect = struct_unpack_return_values
|
||||
mocked_retrieve_windows_encoding.return_value = False
|
||||
|
||||
# THEN: doImport should return None having called retrieve_windows_encoding with the correct encoding.
|
||||
self.assertIsNone(importer.doImport(), 'doImport should return None when db_size is less than 0x800')
|
||||
# THEN: do_import should return None having called retrieve_windows_encoding with the correct encoding.
|
||||
self.assertIsNone(importer.do_import(), 'do_import should return None when db_size is less than 0x800')
|
||||
mocked_retrieve_windows_encoding.assert_call(encoding)
|
||||
|
||||
def file_import_test(self):
|
||||
@ -369,8 +369,8 @@ class TestEasyWorshipSongImport(TestCase):
|
||||
importer = EasyWorshipSongImportLogger(mocked_manager)
|
||||
importer.import_wizard = mocked_import_wizard
|
||||
importer.stop_import_flag = False
|
||||
importer.addAuthor = mocked_add_author
|
||||
importer.addVerse = mocked_add_verse
|
||||
importer.add_author = mocked_add_author
|
||||
importer.add_verse = mocked_add_verse
|
||||
importer.title = mocked_title
|
||||
importer.finish = mocked_finish
|
||||
importer.topics = []
|
||||
@ -378,9 +378,9 @@ class TestEasyWorshipSongImport(TestCase):
|
||||
# WHEN: Importing each file
|
||||
importer.import_source = os.path.join(TEST_PATH, 'Songs.DB')
|
||||
|
||||
# THEN: doImport should return none, the song data should be as expected, and finish should have been
|
||||
# THEN: do_import should return none, the song data should be as expected, and finish should have been
|
||||
# called.
|
||||
self.assertIsNone(importer.doImport(), 'doImport should return None when it has completed')
|
||||
self.assertIsNone(importer.do_import(), 'do_import should return None when it has completed')
|
||||
for song_data in SONG_TEST_DATA:
|
||||
title = song_data['title']
|
||||
author_calls = song_data['authors']
|
||||
@ -394,11 +394,11 @@ class TestEasyWorshipSongImport(TestCase):
|
||||
if song_copyright:
|
||||
self.assertEqual(importer.copyright, song_copyright)
|
||||
if ccli_number:
|
||||
self.assertEquals(importer.ccliNumber, ccli_number, 'ccliNumber for %s should be %s'
|
||||
self.assertEquals(importer.ccli_number, ccli_number, 'ccli_number for %s should be %s'
|
||||
% (title, ccli_number))
|
||||
for verse_text, verse_tag in add_verse_calls:
|
||||
mocked_add_verse.assert_any_call(verse_text, verse_tag)
|
||||
if verse_order_list:
|
||||
self.assertEquals(importer.verseOrderList, verse_order_list, 'verseOrderList for %s should be %s'
|
||||
self.assertEquals(importer.verse_order_list, verse_order_list, 'verse_order_list for %s should be %s'
|
||||
% (title, verse_order_list))
|
||||
mocked_finish.assert_called_with()
|
||||
|
@ -116,17 +116,17 @@ class TestFoilPresenter(TestCase):
|
||||
|
||||
def create_foil_presenter_test(self):
|
||||
"""
|
||||
Test creating an instance of the FoilPresenter class
|
||||
Test creating an instance of the foil_presenter class
|
||||
"""
|
||||
# GIVEN: A mocked out "manager" and "SongImport" instance
|
||||
mocked_manager = MagicMock()
|
||||
mocked_song_import = MagicMock()
|
||||
|
||||
# WHEN: An FoilPresenter instance is created
|
||||
# WHEN: An foil_presenter instance is created
|
||||
foil_presenter_instance = FoilPresenter(mocked_manager, mocked_song_import)
|
||||
|
||||
# THEN: The instance should not be None
|
||||
self.assertIsNotNone(foil_presenter_instance, 'FoilPresenter instance should not be none')
|
||||
self.assertIsNotNone(foil_presenter_instance, 'foil_presenter instance should not be none')
|
||||
|
||||
def no_xml_test(self):
|
||||
"""
|
||||
@ -169,7 +169,7 @@ class TestFoilPresenter(TestCase):
|
||||
# WHEN: xml_to_song is called with a string without an xml encoding declaration
|
||||
foil_presenter_instance.xml_to_song('<foilpresenterfolie>')
|
||||
|
||||
# THEN: the string shiuld have been left intact
|
||||
# THEN: the string should have been left intact
|
||||
self.mocked_re.compile.sub.called_with('<foilpresenterfolie>')
|
||||
|
||||
def process_lyrics_no_verses_test(self):
|
||||
@ -188,7 +188,7 @@ class TestFoilPresenter(TestCase):
|
||||
# WHEN: _process_lyrics is called
|
||||
result = foil_presenter_instance._process_lyrics(mock_foilpresenterfolie, mocked_song)
|
||||
|
||||
# THEN: _process_lyrics should return None and the song_import logError method should have been called once
|
||||
# THEN: _process_lyrics should return None and the song_import log_error method should have been called once
|
||||
self.assertIsNone(result)
|
||||
self.mocked_song_import.logError.assert_called_once_with('Element Text', 'Translated String')
|
||||
self.mocked_song_import.log_error.assert_called_once_with('Element Text', 'Translated String')
|
||||
self.process_lyrics_patcher.start()
|
||||
|
@ -73,7 +73,7 @@ class TestSongBeamerImport(TestCase):
|
||||
|
||||
def invalid_import_source_test(self):
|
||||
"""
|
||||
Test SongBeamerImport.doImport handles different invalid import_source values
|
||||
Test SongBeamerImport.do_import handles different invalid import_source values
|
||||
"""
|
||||
# GIVEN: A mocked out SongImport class, and a mocked out "manager"
|
||||
with patch('openlp.plugins.songs.lib.songbeamerimport.SongImport'):
|
||||
@ -87,14 +87,14 @@ class TestSongBeamerImport(TestCase):
|
||||
for source in ['not a list', 0]:
|
||||
importer.import_source = source
|
||||
|
||||
# THEN: doImport should return none and the progress bar maximum should not be set.
|
||||
self.assertIsNone(importer.doImport(), 'doImport should return None when import_source is not a list')
|
||||
# THEN: do_import should return none and the progress bar maximum should not be set.
|
||||
self.assertIsNone(importer.do_import(), 'do_import should return None when import_source is not a list')
|
||||
self.assertEquals(mocked_import_wizard.progress_bar.setMaximum.called, False,
|
||||
'setMaxium on import_wizard.progress_bar should not have been called')
|
||||
|
||||
def valid_import_source_test(self):
|
||||
"""
|
||||
Test SongBeamerImport.doImport handles different invalid import_source values
|
||||
Test SongBeamerImport.do_import handles different invalid import_source values
|
||||
"""
|
||||
# GIVEN: A mocked out SongImport class, and a mocked out "manager"
|
||||
with patch('openlp.plugins.songs.lib.songbeamerimport.SongImport'):
|
||||
@ -107,10 +107,10 @@ class TestSongBeamerImport(TestCase):
|
||||
# WHEN: Import source is a list
|
||||
importer.import_source = ['List', 'of', 'files']
|
||||
|
||||
# THEN: doImport should return none and the progress bar setMaximum should be called with the length of
|
||||
# THEN: do_import should return none and the progress bar setMaximum should be called with the length of
|
||||
# import_source.
|
||||
self.assertIsNone(importer.doImport(),
|
||||
'doImport should return None when import_source is a list and stop_import_flag is True')
|
||||
self.assertIsNone(importer.do_import(),
|
||||
'do_import should return None when import_source is a list and stop_import_flag is True')
|
||||
mocked_import_wizard.progress_bar.setMaximum.assert_called_with(len(importer.import_source))
|
||||
|
||||
def file_import_test(self):
|
||||
@ -130,7 +130,7 @@ class TestSongBeamerImport(TestCase):
|
||||
importer = SongBeamerImport(mocked_manager)
|
||||
importer.import_wizard = mocked_import_wizard
|
||||
importer.stop_import_flag = False
|
||||
importer.addVerse = mocked_add_verse
|
||||
importer.add_verse = mocked_add_verse
|
||||
importer.finish = mocked_finish
|
||||
|
||||
# WHEN: Importing each file
|
||||
@ -140,16 +140,16 @@ class TestSongBeamerImport(TestCase):
|
||||
song_book_name = SONG_TEST_DATA[song_file]['song_book_name']
|
||||
song_number = SONG_TEST_DATA[song_file]['song_number']
|
||||
|
||||
# THEN: doImport should return none, the song data should be as expected, and finish should have been
|
||||
# THEN: do_import should return none, the song data should be as expected, and finish should have been
|
||||
# called.
|
||||
self.assertIsNone(importer.doImport(), 'doImport should return None when it has completed')
|
||||
self.assertIsNone(importer.do_import(), 'do_import should return None when it has completed')
|
||||
self.assertEquals(importer.title, title, 'title for %s should be "%s"' % (song_file, title))
|
||||
for verse_text, verse_tag in add_verse_calls:
|
||||
mocked_add_verse.assert_any_call(verse_text, verse_tag)
|
||||
if song_book_name:
|
||||
self.assertEquals(importer.songBookName, song_book_name, 'songBookName for %s should be "%s"'
|
||||
self.assertEquals(importer.song_book_name, song_book_name, 'song_book_name for %s should be "%s"'
|
||||
% (song_file, song_book_name))
|
||||
if song_number:
|
||||
self.assertEquals(importer.songNumber, song_number, 'songNumber for %s should be %s'
|
||||
self.assertEquals(importer.song_number, song_number, 'song_number for %s should be %s'
|
||||
% (song_file, song_number))
|
||||
mocked_finish.assert_called_with()
|
||||
|
@ -43,6 +43,7 @@ TEST_PATH = os.path.abspath(
|
||||
|
||||
|
||||
class TestSongShowPlusFileImport(SongImportTestHelper):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.importer_class_name = 'SongShowPlusImport'
|
||||
self.importer_module_name = 'songshowplusimport'
|
||||
@ -78,7 +79,7 @@ class TestSongShowPlusImport(TestCase):
|
||||
|
||||
def invalid_import_source_test(self):
|
||||
"""
|
||||
Test SongShowPlusImport.doImport handles different invalid import_source values
|
||||
Test SongShowPlusImport.do_import handles different invalid import_source values
|
||||
"""
|
||||
# GIVEN: A mocked out SongImport class, and a mocked out "manager"
|
||||
with patch('openlp.plugins.songs.lib.songshowplusimport.SongImport'):
|
||||
@ -92,14 +93,14 @@ class TestSongShowPlusImport(TestCase):
|
||||
for source in ['not a list', 0]:
|
||||
importer.import_source = source
|
||||
|
||||
# THEN: doImport should return none and the progress bar maximum should not be set.
|
||||
self.assertIsNone(importer.doImport(), 'doImport should return None when import_source is not a list')
|
||||
# THEN: do_import should return none and the progress bar maximum should not be set.
|
||||
self.assertIsNone(importer.do_import(), 'do_import should return None when import_source is not a list')
|
||||
self.assertEquals(mocked_import_wizard.progress_bar.setMaximum.called, False,
|
||||
'setMaximum on import_wizard.progress_bar should not have been called')
|
||||
|
||||
def valid_import_source_test(self):
|
||||
"""
|
||||
Test SongShowPlusImport.doImport handles different invalid import_source values
|
||||
Test SongShowPlusImport.do_import handles different invalid import_source values
|
||||
"""
|
||||
# GIVEN: A mocked out SongImport class, and a mocked out "manager"
|
||||
with patch('openlp.plugins.songs.lib.songshowplusimport.SongImport'):
|
||||
@ -112,10 +113,10 @@ class TestSongShowPlusImport(TestCase):
|
||||
# WHEN: Import source is a list
|
||||
importer.import_source = ['List', 'of', 'files']
|
||||
|
||||
# THEN: doImport should return none and the progress bar setMaximum should be called with the length of
|
||||
# THEN: do_import should return none and the progress bar setMaximum should be called with the length of
|
||||
# import_source.
|
||||
self.assertIsNone(importer.doImport(),
|
||||
'doImport should return None when import_source is a list and stop_import_flag is True')
|
||||
self.assertIsNone(importer.do_import(),
|
||||
'do_import should return None when import_source is a list and stop_import_flag is True')
|
||||
mocked_import_wizard.progress_bar.setMaximum.assert_called_with(len(importer.import_source))
|
||||
|
||||
def to_openlp_verse_tag_test(self):
|
||||
|
@ -152,7 +152,7 @@ class TestWorshipCenterProSongImport(TestCase):
|
||||
Test that exceptions raised by pyodbc are handled
|
||||
"""
|
||||
# GIVEN: A mocked out SongImport class, a mocked out pyodbc module, a mocked out translate method,
|
||||
# a mocked "manager" and a mocked out logError method.
|
||||
# a mocked "manager" and a mocked out log_error method.
|
||||
with patch('openlp.plugins.songs.lib.worshipcenterproimport.SongImport'), \
|
||||
patch('openlp.plugins.songs.lib.worshipcenterproimport.pyodbc.connect') as mocked_pyodbc_connect, \
|
||||
patch('openlp.plugins.songs.lib.worshipcenterproimport.translate') as mocked_translate:
|
||||
@ -160,17 +160,17 @@ class TestWorshipCenterProSongImport(TestCase):
|
||||
mocked_log_error = MagicMock()
|
||||
mocked_translate.return_value = 'Translated Text'
|
||||
importer = WorshipCenterProImport(mocked_manager)
|
||||
importer.logError = mocked_log_error
|
||||
importer.log_error = mocked_log_error
|
||||
importer.import_source = 'import_source'
|
||||
pyodbc_errors = [pyodbc.DatabaseError, pyodbc.IntegrityError, pyodbc.InternalError, pyodbc.OperationalError]
|
||||
mocked_pyodbc_connect.side_effect = pyodbc_errors
|
||||
|
||||
# WHEN: Calling the doImport method
|
||||
# WHEN: Calling the do_import method
|
||||
for effect in pyodbc_errors:
|
||||
return_value = importer.doImport()
|
||||
return_value = importer.do_import()
|
||||
|
||||
# THEN: doImport should return None, and pyodbc, translate & logError are called with known calls
|
||||
self.assertIsNone(return_value, 'doImport should return None when pyodbc raises an exception.')
|
||||
# THEN: do_import should return None, and pyodbc, translate & log_error are called with known calls
|
||||
self.assertIsNone(return_value, 'do_import should return None when pyodbc raises an exception.')
|
||||
mocked_pyodbc_connect.assert_called_with( 'DRIVER={Microsoft Access Driver (*.mdb)};DBQ=import_source')
|
||||
mocked_translate.assert_called_with('SongsPlugin.WorshipCenterProImport',
|
||||
'Unable to connect the WorshipCenter Pro database.')
|
||||
@ -181,7 +181,7 @@ class TestWorshipCenterProSongImport(TestCase):
|
||||
Test that a simulated WorshipCenter Pro recordset is imported correctly
|
||||
"""
|
||||
# GIVEN: A mocked out SongImport class, a mocked out pyodbc module with a simulated recordset, a mocked out
|
||||
# translate method, a mocked "manager", addVerse method & mocked_finish method.
|
||||
# translate method, a mocked "manager", add_verse method & mocked_finish method.
|
||||
with patch('openlp.plugins.songs.lib.worshipcenterproimport.SongImport'), \
|
||||
patch('openlp.plugins.songs.lib.worshipcenterproimport.pyodbc') as mocked_pyodbc, \
|
||||
patch('openlp.plugins.songs.lib.worshipcenterproimport.translate') as mocked_translate:
|
||||
@ -194,17 +194,17 @@ class TestWorshipCenterProSongImport(TestCase):
|
||||
importer = WorshipCenterProImportLogger(mocked_manager)
|
||||
importer.import_source = 'import_source'
|
||||
importer.import_wizard = mocked_import_wizard
|
||||
importer.addVerse = mocked_add_verse
|
||||
importer.add_verse = mocked_add_verse
|
||||
importer.stop_import_flag = False
|
||||
importer.finish = mocked_finish
|
||||
|
||||
# WHEN: Calling the doImport method
|
||||
return_value = importer.doImport()
|
||||
# WHEN: Calling the do_import method
|
||||
return_value = importer.do_import()
|
||||
|
||||
|
||||
# THEN: doImport should return None, and pyodbc, import_wizard, importer.title and addVerse are called with
|
||||
# THEN: do_import should return None, and pyodbc, import_wizard, importer.title and add_verse are called with
|
||||
# known calls
|
||||
self.assertIsNone(return_value, 'doImport should return None when pyodbc raises an exception.')
|
||||
self.assertIsNone(return_value, 'do_import should return None when pyodbc raises an exception.')
|
||||
mocked_pyodbc.connect.assert_called_with('DRIVER={Microsoft Access Driver (*.mdb)};DBQ=import_source')
|
||||
mocked_pyodbc.connect().cursor.assert_any_call()
|
||||
mocked_pyodbc.connect().cursor().execute.assert_called_with('SELECT ID, Field, Value FROM __SONGDATA')
|
||||
@ -220,4 +220,4 @@ class TestWorshipCenterProSongImport(TestCase):
|
||||
for call in verse_calls:
|
||||
mocked_add_verse.assert_any_call(call)
|
||||
self.assertEqual(mocked_add_verse.call_count, add_verse_call_count,
|
||||
'Incorrect number of calls made to addVerse')
|
||||
'Incorrect number of calls made to add_verse')
|
||||
|
@ -51,9 +51,9 @@ class SongImportTestHelper(TestCase):
|
||||
Patch and set up the mocks required.
|
||||
"""
|
||||
self.add_copyright_patcher = patch(
|
||||
'openlp.plugins.songs.lib.%s.%s.addCopyright' % (self.importer_module_name, self.importer_class_name))
|
||||
'openlp.plugins.songs.lib.%s.%s.add_copyright' % (self.importer_module_name, self.importer_class_name))
|
||||
self.add_verse_patcher = patch(
|
||||
'openlp.plugins.songs.lib.%s.%s.addVerse' % (self.importer_module_name, self.importer_class_name))
|
||||
'openlp.plugins.songs.lib.%s.%s.add_verse' % (self.importer_module_name, self.importer_class_name))
|
||||
self.finish_patcher = patch(
|
||||
'openlp.plugins.songs.lib.%s.%s.finish' % (self.importer_module_name, self.importer_class_name))
|
||||
self.parse_author_patcher = patch(
|
||||
@ -107,16 +107,16 @@ class SongImportTestHelper(TestCase):
|
||||
topics = self._get_data(result_data, 'topics')
|
||||
verse_order_list = self._get_data(result_data, 'verse_order_list')
|
||||
|
||||
# THEN: doImport should return none, the song data should be as expected, and finish should have been
|
||||
# THEN: do_import should return none, the song data should be as expected, and finish should have been
|
||||
# called.
|
||||
self.assertIsNone(importer.doImport(), 'doImport should return None when it has completed')
|
||||
self.assertIsNone(importer.do_import(), 'do_import should return None when it has completed')
|
||||
self.assertEquals(importer.title, title, 'title for %s should be "%s"' % (source_file_name, title))
|
||||
for author in author_calls:
|
||||
self.mocked_parse_author.assert_any_call(author)
|
||||
if song_copyright:
|
||||
self.mocked_add_copyright.assert_called_with(song_copyright)
|
||||
if ccli_number:
|
||||
self.assertEquals(importer.ccliNumber, ccli_number, 'ccliNumber for %s should be %s'
|
||||
self.assertEquals(importer.ccli_number, ccli_number, 'ccli_number for %s should be %s'
|
||||
% (source_file_name, ccli_number))
|
||||
for verse_text, verse_tag in add_verse_calls:
|
||||
self.mocked_add_verse.assert_any_call(verse_text, verse_tag)
|
||||
@ -126,13 +126,13 @@ class SongImportTestHelper(TestCase):
|
||||
self.assertEquals(importer.comments, comments, 'comments for %s should be "%s"'
|
||||
% (source_file_name, comments))
|
||||
if song_book_name:
|
||||
self.assertEquals(importer.songBookName, song_book_name, 'songBookName for %s should be "%s"'
|
||||
self.assertEquals(importer.song_book_name, song_book_name, 'song_book_name for %s should be "%s"'
|
||||
% (source_file_name, song_book_name))
|
||||
if song_number:
|
||||
self.assertEquals(importer.songNumber, song_number, 'songNumber for %s should be %s'
|
||||
self.assertEquals(importer.song_number, song_number, 'song_number for %s should be %s'
|
||||
% (source_file_name, song_number))
|
||||
if verse_order_list:
|
||||
self.assertEquals(importer.verseOrderList, [], 'verseOrderList for %s should be %s'
|
||||
self.assertEquals(importer.verse_order_list, [], 'verse_order_list for %s should be %s'
|
||||
% (source_file_name, verse_order_list))
|
||||
self.mocked_finish.assert_called_with()
|
||||
|
||||
|
@ -1 +1,28 @@
|
||||
__author__ = 'tim'
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2014 Raoul Snyman #
|
||||
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
|
||||
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
|
||||
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
|
||||
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
|
||||
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
|
||||
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
|
||||
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
|
@ -1,3 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2014 Raoul Snyman #
|
||||
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
|
||||
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
|
||||
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
|
||||
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
|
||||
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
|
||||
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
|
||||
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
"""
|
||||
Package to test the openlp.plugin.bible.lib.https package.
|
||||
"""
|
||||
|
118
tests/interfaces/openlp_plugins/bibles/test_lib_manager.py
Normal file
118
tests/interfaces/openlp_plugins/bibles/test_lib_manager.py
Normal file
@ -0,0 +1,118 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2014 Raoul Snyman #
|
||||
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
|
||||
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
|
||||
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
|
||||
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
|
||||
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
|
||||
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
|
||||
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
"""
|
||||
Functional tests to test the Bible Manager class and related methods.
|
||||
"""
|
||||
from unittest import TestCase
|
||||
|
||||
from openlp.core.common import Registry, Settings
|
||||
from openlp.plugins.bibles.lib import BibleManager, LanguageSelection
|
||||
from tests.interfaces import MagicMock, patch
|
||||
|
||||
from tests.utils.constants import TEST_RESOURCES_PATH
|
||||
from tests.helpers.testmixin import TestMixin
|
||||
|
||||
|
||||
class TestBibleManager(TestCase, TestMixin):
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Set up the environment for testing bible queries with 1 Timothy 3
|
||||
"""
|
||||
self.build_settings()
|
||||
Registry.create()
|
||||
Registry().register('service_list', MagicMock())
|
||||
Registry().register('application', MagicMock())
|
||||
bible_settings = {
|
||||
'bibles/proxy name': '',
|
||||
'bibles/db type': 'sqlite',
|
||||
'bibles/book name language': LanguageSelection.Bible,
|
||||
'bibles/verse separator': '',
|
||||
'bibles/range separator': '',
|
||||
'bibles/list separator': '',
|
||||
'bibles/end separator': '',
|
||||
}
|
||||
Settings().extend_default_settings(bible_settings)
|
||||
with patch('openlp.core.common.applocation.Settings') as mocked_class, \
|
||||
patch('openlp.core.common.AppLocation.get_section_data_path') as mocked_get_section_data_path, \
|
||||
patch('openlp.core.common.AppLocation.get_files') as mocked_get_files:
|
||||
# GIVEN: A mocked out Settings class and a mocked out AppLocation.get_files()
|
||||
mocked_settings = mocked_class.return_value
|
||||
mocked_settings.contains.return_value = False
|
||||
mocked_get_files.return_value = ["tests.sqlite"]
|
||||
mocked_get_section_data_path.return_value = TEST_RESOURCES_PATH + "/bibles"
|
||||
self.manager = BibleManager(MagicMock())
|
||||
|
||||
def tearDown(self):
|
||||
"""
|
||||
Delete all the C++ objects at the end so that we don't have a segfault
|
||||
"""
|
||||
self.destroy_settings()
|
||||
|
||||
def get_books_test(self):
|
||||
"""
|
||||
Test the get_books method
|
||||
"""
|
||||
# GIVEN given a bible in the bible manager
|
||||
# WHEN asking for the books of the bible
|
||||
books = self.manager.get_books('tests')
|
||||
# THEN a list of books should be returned
|
||||
self.assertEqual(66, len(books), 'There should be 66 books in the bible')
|
||||
|
||||
def get_book_by_id_test(self):
|
||||
"""
|
||||
Test the get_book_by_id method
|
||||
"""
|
||||
# GIVEN given a bible in the bible manager
|
||||
# WHEN asking for the book of the bible
|
||||
book = self.manager.get_book_by_id('tests', 54)
|
||||
# THEN a book should be returned
|
||||
self.assertEqual('1 Timothy', book.name, '1 Timothy should have been returned from the bible')
|
||||
|
||||
def get_chapter_count_test(self):
|
||||
"""
|
||||
Test the get_chapter_count method
|
||||
"""
|
||||
# GIVEN given a bible in the bible manager
|
||||
# WHEN asking for the chapters in a book of the bible
|
||||
book = self.manager.get_book_by_id('tests', 54)
|
||||
chapter = self.manager.get_chapter_count('tests', book)
|
||||
# THEN the chapter count should be returned
|
||||
self.assertEqual(6, chapter, '1 Timothy should have 6 chapters returned from the bible')
|
||||
|
||||
def get_verse_count_by_book_ref_id_test(self):
|
||||
"""
|
||||
Test the get_verse_count_by_book_ref_id method
|
||||
"""
|
||||
# GIVEN given a bible in the bible manager
|
||||
# WHEN asking for the number of verses in a book of the bible
|
||||
verses = self.manager.get_verse_count_by_book_ref_id('tests', 54, 3)
|
||||
# THEN the chapter count should be returned
|
||||
self.assertEqual(16, verses, '1 Timothy v3 should have 16 verses returned from the bible')
|
||||
|
@ -0,0 +1,106 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2014 Raoul Snyman #
|
||||
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
|
||||
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
|
||||
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
|
||||
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
|
||||
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
|
||||
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
|
||||
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
"""
|
||||
This module contains tests for the lib submodule of the Bibles plugin.
|
||||
"""
|
||||
from unittest import TestCase
|
||||
from tests.interfaces import MagicMock, patch
|
||||
|
||||
from openlp.core.common import Registry, Settings
|
||||
from openlp.plugins.bibles.lib import BibleManager, parse_reference, LanguageSelection
|
||||
|
||||
from tests.utils.constants import TEST_RESOURCES_PATH
|
||||
from tests.helpers.testmixin import TestMixin
|
||||
|
||||
|
||||
class TestBibleManager(TestCase, TestMixin):
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Set up the environment for testing bible parse reference
|
||||
"""
|
||||
self.build_settings()
|
||||
Registry.create()
|
||||
Registry().register('service_list', MagicMock())
|
||||
Registry().register('application', MagicMock())
|
||||
bible_settings = {
|
||||
'bibles/proxy name': '',
|
||||
'bibles/db type': 'sqlite',
|
||||
'bibles/book name language': LanguageSelection.Bible,
|
||||
'bibles/verse separator': '',
|
||||
'bibles/range separator': '',
|
||||
'bibles/list separator': '',
|
||||
'bibles/end separator': '',
|
||||
}
|
||||
Settings().extend_default_settings(bible_settings)
|
||||
with patch('openlp.core.common.applocation.Settings') as mocked_class, \
|
||||
patch('openlp.core.common.AppLocation.get_section_data_path') as mocked_get_section_data_path, \
|
||||
patch('openlp.core.common.AppLocation.get_files') as mocked_get_files:
|
||||
# GIVEN: A mocked out Settings class and a mocked out AppLocation.get_files()
|
||||
mocked_settings = mocked_class.return_value
|
||||
mocked_settings.contains.return_value = False
|
||||
mocked_get_files.return_value = ["tests.sqlite"]
|
||||
mocked_get_section_data_path.return_value = TEST_RESOURCES_PATH + "/bibles"
|
||||
self.manager = BibleManager(MagicMock())
|
||||
|
||||
def tearDown(self):
|
||||
"""
|
||||
Delete all the C++ objects at the end so that we don't have a segfault
|
||||
"""
|
||||
self.destroy_settings()
|
||||
|
||||
def parse_reference_one_test(self):
|
||||
"""
|
||||
Test the parse_reference method with 1 Timothy 1
|
||||
"""
|
||||
# GIVEN given a bible in the bible manager
|
||||
# WHEN asking to parse the bible reference
|
||||
results = parse_reference('1 Timothy 1', self.manager.db_cache['tests'], MagicMock(), 54)
|
||||
# THEN a verse array should be returned
|
||||
self.assertEquals([(54, 1, 1, -1)], results , "The bible verses should matches the expected results")
|
||||
|
||||
def parse_reference_two_test(self):
|
||||
"""
|
||||
Test the parse_reference method with 1 Timothy 1:1-2
|
||||
"""
|
||||
# GIVEN given a bible in the bible manager
|
||||
# WHEN asking to parse the bible reference
|
||||
results = parse_reference('1 Timothy 1:1-2', self.manager.db_cache['tests'], MagicMock(), 54)
|
||||
# THEN a verse array should be returned
|
||||
self.assertEquals([(54, 1, 1, 2)], results , "The bible verses should matches the expected results")
|
||||
|
||||
def parse_reference_three_test(self):
|
||||
"""
|
||||
Test the parse_reference method with 1 Timothy 1:1-2
|
||||
"""
|
||||
# GIVEN given a bible in the bible manager
|
||||
# WHEN asking to parse the bible reference
|
||||
results = parse_reference('1 Timothy 1:1-2:1', self.manager.db_cache['tests'], MagicMock(), 54)
|
||||
# THEN a verse array should be returned
|
||||
self.assertEquals([(54,1,1,-1),(54,2,1,1)], results , "The bible verses should matches the expected results")
|
@ -0,0 +1,28 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2014 Raoul Snyman #
|
||||
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
|
||||
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
|
||||
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
|
||||
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
|
||||
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
|
||||
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
|
||||
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
@ -1,3 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2014 Raoul Snyman #
|
||||
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
|
||||
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
|
||||
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
|
||||
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
|
||||
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
|
||||
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
|
||||
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
"""
|
||||
Module to test the EditCustomForm.
|
||||
"""
|
||||
|
BIN
tests/resources/bibles/tests.sqlite
Normal file
BIN
tests/resources/bibles/tests.sqlite
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user