This commit is contained in:
Tomas Groth 2016-03-22 15:14:16 +01:00
commit c9a8e2cf59
183 changed files with 67800 additions and 64771 deletions

View File

@ -43,3 +43,5 @@ __pycache__
.coverage
cover
*.kdev4
coverage
tags

5
.coveragerc Normal file
View File

@ -0,0 +1,5 @@
[run]
source = openlp
[html]
directory = coverage

View File

@ -1 +1 @@
2.3.1
2.4

View File

@ -153,10 +153,10 @@ class OpenLP(OpenLPMixin, QtWidgets.QApplication):
self.processEvents()
if not has_run_wizard:
self.main_window.first_time()
update_check = Settings().value('core/update check')
if update_check:
version = VersionThread(self.main_window)
version.start()
# update_check = Settings().value('core/update check')
# if update_check:
# version = VersionThread(self.main_window)
# version.start()
self.main_window.is_display_blank()
self.main_window.app_startup()
return self.exec()

View File

@ -131,6 +131,7 @@ class Settings(QtCore.QSettings):
'advanced/save current plugin': False,
'advanced/slide limits': SlideLimits.End,
'advanced/single click preview': False,
'advanced/single click service preview': False,
'advanced/x11 bypass wm': X11_BYPASS_DEFAULT,
'advanced/search as type': True,
'crashreport/last directory': '',
@ -252,67 +253,56 @@ class Settings(QtCore.QSettings):
'shortcuts/blankScreen': [QtGui.QKeySequence(QtCore.Qt.Key_Period)],
'shortcuts/collapse': [QtGui.QKeySequence(QtCore.Qt.Key_Minus)],
'shortcuts/desktopScreen': [QtGui.QKeySequence(QtCore.Qt.Key_D)],
'shortcuts/delete': [QtGui.QKeySequence(QtGui.QKeySequence.Delete), QtGui.QKeySequence(QtCore.Qt.Key_Delete)],
'shortcuts/delete': [QtGui.QKeySequence(QtGui.QKeySequence.Delete)],
'shortcuts/down': [QtGui.QKeySequence(QtCore.Qt.Key_Down)],
'shortcuts/editSong': [],
'shortcuts/escapeItem': [QtGui.QKeySequence(QtCore.Qt.Key_Escape)],
'shortcuts/expand': [QtGui.QKeySequence(QtCore.Qt.Key_Plus)],
'shortcuts/exportThemeItem': [],
'shortcuts/fileNewItem': [QtGui.QKeySequence(QtGui.QKeySequence.New),
QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_N)],
'shortcuts/fileSaveAsItem': [QtGui.QKeySequence(QtGui.QKeySequence.SaveAs),
QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.SHIFT + QtCore.Qt.Key_S)],
'shortcuts/fileExitItem': [QtGui.QKeySequence(QtGui.QKeySequence.Quit),
QtGui.QKeySequence(QtCore.Qt.ALT + QtCore.Qt.Key_F4)],
'shortcuts/fileSaveItem': [QtGui.QKeySequence(QtGui.QKeySequence.Save),
QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_S)],
'shortcuts/fileOpenItem': [QtGui.QKeySequence(QtGui.QKeySequence.Open),
QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_O)],
'shortcuts/fileNewItem': [QtGui.QKeySequence(QtGui.QKeySequence.New)],
'shortcuts/fileSaveAsItem': [QtGui.QKeySequence(QtGui.QKeySequence.SaveAs)],
'shortcuts/fileExitItem': [QtGui.QKeySequence(QtGui.QKeySequence.Quit)],
'shortcuts/fileSaveItem': [QtGui.QKeySequence(QtGui.QKeySequence.Save)],
'shortcuts/fileOpenItem': [QtGui.QKeySequence(QtGui.QKeySequence.Open)],
'shortcuts/goLive': [],
'shortcuts/importThemeItem': [],
'shortcuts/importBibleItem': [],
'shortcuts/listViewBiblesDeleteItem': [QtGui.QKeySequence(QtGui.QKeySequence.Delete),
QtGui.QKeySequence(QtCore.Qt.Key_Delete)],
'shortcuts/listViewBiblesDeleteItem': [QtGui.QKeySequence(QtGui.QKeySequence.Delete)],
'shortcuts/listViewBiblesPreviewItem': [QtGui.QKeySequence(QtCore.Qt.Key_Return),
QtGui.QKeySequence(QtCore.Qt.Key_Enter)],
'shortcuts/listViewBiblesLiveItem': [QtGui.QKeySequence(QtCore.Qt.SHIFT + QtCore.Qt.Key_Return),
QtGui.QKeySequence(QtCore.Qt.SHIFT + QtCore.Qt.Key_Enter)],
'shortcuts/listViewBiblesServiceItem': [QtGui.QKeySequence(QtCore.Qt.Key_Plus),
QtGui.QKeySequence(QtCore.Qt.Key_Equal)],
'shortcuts/listViewCustomDeleteItem': [QtGui.QKeySequence(QtGui.QKeySequence.Delete),
QtGui.QKeySequence(QtCore.Qt.Key_Delete)],
'shortcuts/listViewCustomDeleteItem': [QtGui.QKeySequence(QtGui.QKeySequence.Delete)],
'shortcuts/listViewCustomPreviewItem': [QtGui.QKeySequence(QtCore.Qt.Key_Return),
QtGui.QKeySequence(QtCore.Qt.Key_Enter)],
'shortcuts/listViewCustomLiveItem': [QtGui.QKeySequence(QtCore.Qt.SHIFT + QtCore.Qt.Key_Return),
QtGui.QKeySequence(QtCore.Qt.SHIFT + QtCore.Qt.Key_Enter)],
'shortcuts/listViewCustomServiceItem': [QtGui.QKeySequence(QtCore.Qt.Key_Plus),
QtGui.QKeySequence(QtCore.Qt.Key_Equal)],
'shortcuts/listViewImagesDeleteItem': [QtGui.QKeySequence(QtGui.QKeySequence.Delete),
QtGui.QKeySequence(QtCore.Qt.Key_Delete)],
'shortcuts/listViewImagesDeleteItem': [QtGui.QKeySequence(QtGui.QKeySequence.Delete)],
'shortcuts/listViewImagesPreviewItem': [QtGui.QKeySequence(QtCore.Qt.Key_Return),
QtGui.QKeySequence(QtCore.Qt.Key_Enter)],
'shortcuts/listViewImagesLiveItem': [QtGui.QKeySequence(QtCore.Qt.SHIFT + QtCore.Qt.Key_Return),
QtGui.QKeySequence(QtCore.Qt.SHIFT + QtCore.Qt.Key_Enter)],
'shortcuts/listViewImagesServiceItem': [QtGui.QKeySequence(QtCore.Qt.Key_Plus),
QtGui.QKeySequence(QtCore.Qt.Key_Equal)],
'shortcuts/listViewMediaDeleteItem': [QtGui.QKeySequence(QtGui.QKeySequence.Delete),
QtGui.QKeySequence(QtCore.Qt.Key_Delete)],
'shortcuts/listViewMediaDeleteItem': [QtGui.QKeySequence(QtGui.QKeySequence.Delete)],
'shortcuts/listViewMediaPreviewItem': [QtGui.QKeySequence(QtCore.Qt.Key_Return),
QtGui.QKeySequence(QtCore.Qt.Key_Enter)],
'shortcuts/listViewMediaLiveItem': [QtGui.QKeySequence(QtCore.Qt.SHIFT + QtCore.Qt.Key_Return),
QtGui.QKeySequence(QtCore.Qt.SHIFT + QtCore.Qt.Key_Enter)],
'shortcuts/listViewMediaServiceItem': [QtGui.QKeySequence(QtCore.Qt.Key_Plus),
QtGui.QKeySequence(QtCore.Qt.Key_Equal)],
'shortcuts/listViewPresentationsDeleteItem': [QtGui.QKeySequence(QtGui.QKeySequence.Delete),
QtGui.QKeySequence(QtCore.Qt.Key_Delete)],
'shortcuts/listViewPresentationsDeleteItem': [QtGui.QKeySequence(QtGui.QKeySequence.Delete)],
'shortcuts/listViewPresentationsPreviewItem': [QtGui.QKeySequence(QtCore.Qt.Key_Return),
QtGui.QKeySequence(QtCore.Qt.Key_Enter)],
'shortcuts/listViewPresentationsLiveItem': [QtGui.QKeySequence(QtCore.Qt.SHIFT + QtCore.Qt.Key_Return),
QtGui.QKeySequence(QtCore.Qt.SHIFT + QtCore.Qt.Key_Enter)],
'shortcuts/listViewPresentationsServiceItem': [QtGui.QKeySequence(QtCore.Qt.Key_Plus),
QtGui.QKeySequence(QtCore.Qt.Key_Equal)],
'shortcuts/listViewSongsDeleteItem': [QtGui.QKeySequence(QtGui.QKeySequence.Delete),
QtGui.QKeySequence(QtCore.Qt.Key_Delete)],
'shortcuts/listViewSongsDeleteItem': [QtGui.QKeySequence(QtGui.QKeySequence.Delete)],
'shortcuts/listViewSongsPreviewItem': [QtGui.QKeySequence(QtCore.Qt.Key_Return),
QtGui.QKeySequence(QtCore.Qt.Key_Enter)],
'shortcuts/listViewSongsLiveItem': [QtGui.QKeySequence(QtCore.Qt.SHIFT + QtCore.Qt.Key_Return),
@ -329,17 +319,18 @@ class Settings(QtCore.QSettings):
'shortcuts/moveBottom': [QtGui.QKeySequence(QtCore.Qt.Key_End)],
'shortcuts/moveDown': [QtGui.QKeySequence(QtCore.Qt.Key_PageDown)],
'shortcuts/nextTrackItem': [],
'shortcuts/nextItem_live': [QtGui.QKeySequence(QtCore.Qt.Key_Down), QtGui.QKeySequence(QtCore.Qt.Key_PageDown)],
'shortcuts/nextItem_live': [QtGui.QKeySequence(QtCore.Qt.Key_Down),
QtGui.QKeySequence(QtCore.Qt.Key_PageDown)],
'shortcuts/nextItem_preview': [QtGui.QKeySequence(QtCore.Qt.Key_Down),
QtGui.QKeySequence(QtCore.Qt.Key_PageDown)],
'shortcuts/nextService': [QtGui.QKeySequence(QtCore.Qt.Key_Right)],
'shortcuts/newService': [],
'shortcuts/offlineHelpItem': [QtGui.QKeySequence(QtGui.QKeySequence.HelpContents)],
'shortcuts/onlineHelpItem': [QtGui.QKeySequence(QtGui.QKeySequence.HelpContents),
QtGui.QKeySequence(QtCore.Qt.ALT + QtCore.Qt.Key_F1)],
'shortcuts/onlineHelpItem': [QtGui.QKeySequence(QtGui.QKeySequence.HelpContents)],
'shortcuts/openService': [],
'shortcuts/saveService': [],
'shortcuts/previousItem_live': [QtGui.QKeySequence(QtCore.Qt.Key_Up), QtGui.QKeySequence(QtCore.Qt.Key_PageUp)],
'shortcuts/previousItem_live': [QtGui.QKeySequence(QtCore.Qt.Key_Up),
QtGui.QKeySequence(QtCore.Qt.Key_PageUp)],
'shortcuts/playbackPause': [],
'shortcuts/playbackPlay': [],
'shortcuts/playbackStop': [],
@ -348,12 +339,10 @@ class Settings(QtCore.QSettings):
'shortcuts/previousService': [QtGui.QKeySequence(QtCore.Qt.Key_Left)],
'shortcuts/previousItem_preview': [QtGui.QKeySequence(QtCore.Qt.Key_Up),
QtGui.QKeySequence(QtCore.Qt.Key_PageUp)],
'shortcuts/printServiceItem': [QtGui.QKeySequence(QtGui.QKeySequence.Print),
QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_P)],
'shortcuts/printServiceItem': [QtGui.QKeySequence(QtGui.QKeySequence.Print)],
'shortcuts/songExportItem': [],
'shortcuts/songUsageStatus': [QtGui.QKeySequence(QtCore.Qt.Key_F4)],
'shortcuts/searchShortcut': [QtGui.QKeySequence(QtGui.QKeySequence.Find),
QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_F)],
'shortcuts/searchShortcut': [QtGui.QKeySequence(QtGui.QKeySequence.Find)],
'shortcuts/settingsShortcutsItem': [],
'shortcuts/settingsImportItem': [],
'shortcuts/settingsPluginListItem': [QtGui.QKeySequence(QtCore.Qt.ALT + QtCore.Qt.Key_F7)],

View File

@ -122,8 +122,8 @@ class UiStrings(object):
self.Projectors = translate('OpenLP.Ui', 'Projectors', 'Plural')
self.ReplaceBG = translate('OpenLP.Ui', 'Replace Background')
self.ReplaceLiveBG = translate('OpenLP.Ui', 'Replace live background.')
self.ReplaceLiveBGDisabled = translate('OpenLP.Ui', 'Replace live background is not available on this '
'platform in this version of OpenLP.')
self.ReplaceLiveBGDisabled = translate('OpenLP.Ui', 'Replace live background is not available when the WebKit '
'player is disabled.')
self.ResetBG = translate('OpenLP.Ui', 'Reset Background')
self.ResetLiveBG = translate('OpenLP.Ui', 'Reset live background.')
self.Seconds = translate('OpenLP.Ui', 's', 'The abbreviated unit for seconds')

View File

@ -55,7 +55,7 @@ def init_db(url, auto_flush=True, auto_commit=False, base=None):
metadata = MetaData(bind=engine)
else:
base.metadata.bind = engine
metadata = None
metadata = base.metadata
session = scoped_session(sessionmaker(autoflush=auto_flush, autocommit=auto_commit, bind=engine))
return session, metadata
@ -227,13 +227,12 @@ class Manager(object):
"""
self.is_dirty = False
self.session = None
# See if we're using declarative_base with a pre-existing session.
log.debug('Manager: Testing for pre-existing session')
if session is not None:
log.debug('Manager: Using existing session')
else:
log.debug('Manager: Creating new session')
self.db_url = None
if db_file_name:
log.debug('Manager: Creating new DB url')
self.db_url = init_url(plugin_name, db_file_name)
else:
self.db_url = init_url(plugin_name)
if upgrade_mod:
try:
db_ver, up_ver = upgrade_db(self.db_url, upgrade_mod)
@ -248,10 +247,13 @@ class Manager(object):
'not be loaded.\n\nDatabase: %s') % (db_ver, up_ver, self.db_url)
)
return
try:
self.session = init_schema(self.db_url)
except (SQLAlchemyError, DBAPIError):
handle_db_error(plugin_name, db_file_name)
if not session:
try:
self.session = init_schema(self.db_url)
except (SQLAlchemyError, DBAPIError):
handle_db_error(plugin_name, db_file_name)
else:
self.session = session
def save_object(self, object_instance, commit=True):
"""
@ -344,13 +346,13 @@ class Manager(object):
for try_count in range(3):
try:
return self.session.query(object_class).filter(filter_clause).first()
except OperationalError:
except OperationalError as oe:
# This exception clause is for users running MySQL which likes to terminate connections on its own
# without telling anyone. See bug #927473. However, other dbms can raise it, usually in a
# non-recoverable way. So we only retry 3 times.
log.exception('Probably a MySQL issue, "MySQL has gone away"')
if try_count >= 2:
if try_count >= 2 or 'MySQL has gone away' in str(oe):
raise
log.exception('Probably a MySQL issue, "MySQL has gone away"')
def get_all_objects(self, object_class, filter_clause=None, order_by_ref=None):
"""

View File

@ -263,7 +263,8 @@ class Plugin(QtCore.QObject, RegistryProperties):
else:
self.media_item.on_add_click()
def about(self):
@staticmethod
def about():
"""
Show a dialog when the user clicks on the 'About' button in the plugin manager.
"""
@ -275,7 +276,7 @@ class Plugin(QtCore.QObject, RegistryProperties):
"""
if self.media_item:
self.media_item.initialise()
self.main_window.media_dock_manager.insert_dock(self.media_item, self.icon, self.weight)
self.main_window.media_dock_manager.add_item_to_dock(self.media_item)
def finalise(self):
"""

View File

@ -20,18 +20,18 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
:mod:`openlp.core.lib.projector.db` module
:mod:`openlp.core.lib.projector.db` module
Provides the database functions for the Projector module.
Provides the database functions for the Projector module.
The Manufacturer, Model, Source tables keep track of the video source
strings used for display of input sources. The Source table maps
manufacturer-defined or user-defined strings from PJLink default strings
to end-user readable strings; ex: PJLink code 11 would map "RGB 1"
default string to "RGB PC (analog)" string.
(Future feature).
The Manufacturer, Model, Source tables keep track of the video source
strings used for display of input sources. The Source table maps
manufacturer-defined or user-defined strings from PJLink default strings
to end-user readable strings; ex: PJLink code 11 would map "RGB 1"
default string to "RGB PC (analog)" string.
(Future feature).
The Projector table keeps track of entries for controlled projectors.
The Projector table keeps track of entries for controlled projectors.
"""
import logging
@ -218,19 +218,19 @@ class ProjectorDB(Manager):
"""
def __init__(self, *args, **kwargs):
log.debug('ProjectorDB().__init__(args="%s", kwargs="%s")' % (args, kwargs))
super().__init__(plugin_name='projector',
init_schema=self.init_schema)
super().__init__(plugin_name='projector', init_schema=self.init_schema)
log.debug('ProjectorDB() Initialized using db url %s' % self.db_url)
log.debug('Session: %s', self.session)
def init_schema(*args, **kwargs):
def init_schema(self, *args, **kwargs):
"""
Setup the projector database and initialize the schema.
Declarative uses table classes to define schema.
"""
url = init_url('projector')
session, metadata = init_db(url, base=Base)
Base.metadata.create_all(checkfirst=True)
self.db_url = init_url('projector')
session, metadata = init_db(self.db_url, base=Base)
metadata.create_all(checkfirst=True)
return session
def get_projector_by_id(self, dbid):

View File

@ -101,7 +101,7 @@ class PJLink1(QTcpSocket):
self.location = None
self.notes = None
self.dbid = None if 'dbid' not in kwargs else kwargs['dbid']
self.location = None if 'location' not in kwargs else kwargs['notes']
self.location = None if 'location' not in kwargs else kwargs['location']
self.notes = None if 'notes' not in kwargs else kwargs['notes']
# Poll time 20 seconds unless called with something else
self.poll_time = 20000 if 'poll_time' not in kwargs else kwargs['poll_time'] * 1000
@ -345,7 +345,7 @@ class PJLink1(QTcpSocket):
# Authenticated login with salt
log.debug('(%s) Setting hash with salt="%s"' % (self.ip, data_check[2]))
log.debug('(%s) pin="%s"' % (self.ip, self.pin))
salt = qmd5_hash(salt=data_check[2].endcode('ascii'), data=self.pin.encode('ascii'))
salt = qmd5_hash(salt=data_check[2].encode('ascii'), data=self.pin.encode('ascii'))
else:
salt = None
# We're connected at this point, so go ahead and do regular I/O
@ -515,7 +515,7 @@ class PJLink1(QTcpSocket):
self.socket_timer.start()
try:
self.projectorNetwork.emit(S_NETWORK_SENDING)
sent = self.write(out)
sent = self.write(out.encode('ascii'))
self.waitForBytesWritten(2000) # 2 seconds should be enough
if sent == -1:
# Network error?
@ -665,7 +665,15 @@ class PJLink1(QTcpSocket):
:param data: Class that projector supports.
"""
self.pjlink_class = data
# bug 1550891: Projector returns non-standard class response:
# : Expected: %1CLSS=1
# : Received: %1CLSS=Class 1
if len(data) > 1:
# Split non-standard information from response
clss = data.split()[-1]
else:
clss = data
self.pjlink_class = clss
log.debug('(%s) Setting pjlink_class for this projector to "%s"' % (self.ip, self.pjlink_class))
return

View File

@ -20,6 +20,7 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
import re
from PyQt5 import QtGui, QtCore, QtWebKitWidgets
@ -273,7 +274,7 @@ class Renderer(OpenLPMixin, RegistryMixin, RegistryProperties):
except ValueError:
text_to_render = text.split('\n[---]\n')[0]
text = ''
text_to_render, raw_tags, html_tags = self._get_start_tags(text_to_render)
text_to_render, raw_tags, html_tags = get_start_tags(text_to_render)
if text:
text = raw_tags + text
else:
@ -441,7 +442,7 @@ class Renderer(OpenLPMixin, RegistryMixin, RegistryProperties):
previous_raw = line + line_end
continue
# Figure out how many words of the line will fit on screen as the line will not fit as a whole.
raw_words = self._words_split(line)
raw_words = words_split(line)
html_words = list(map(expand_tags, raw_words))
previous_html, previous_raw = \
self._binary_chop(formatted, previous_html, previous_raw, html_words, raw_words, ' ', line_end)
@ -451,42 +452,6 @@ class Renderer(OpenLPMixin, RegistryMixin, RegistryProperties):
formatted.append(previous_raw)
return formatted
def _get_start_tags(self, raw_text):
"""
Tests the given text for not closed formatting tags and returns a tuple consisting of three unicode strings::
('{st}{r}Text text text{/r}{/st}', '{st}{r}', '<strong><span style="-webkit-text-fill-color:red">')
The first unicode string is the text, with correct closing tags. The second unicode string are OpenLP's opening
formatting tags and the third unicode string the html opening formatting tags.
:param raw_text: The text to test. The text must **not** contain html tags, only OpenLP formatting tags
are allowed::
{st}{r}Text text text
"""
raw_tags = []
html_tags = []
for tag in FormattingTags.get_html_tags():
if tag['start tag'] == '{br}':
continue
if raw_text.count(tag['start tag']) != raw_text.count(tag['end tag']):
raw_tags.append((raw_text.find(tag['start tag']), tag['start tag'], tag['end tag']))
html_tags.append((raw_text.find(tag['start tag']), tag['start html']))
# Sort the lists, so that the tags which were opened first on the first slide (the text we are checking) will be
# opened first on the next slide as well.
raw_tags.sort(key=lambda tag: tag[0])
html_tags.sort(key=lambda tag: tag[0])
# Create a list with closing tags for the raw_text.
end_tags = []
start_tags = []
for tag in raw_tags:
start_tags.append(tag[1])
end_tags.append(tag[2])
end_tags.reverse()
# Remove the indexes.
html_tags = [tag[1] for tag in html_tags]
return raw_text + ''.join(end_tags), ''.join(start_tags), ''.join(html_tags)
def _binary_chop(self, formatted, previous_html, previous_raw, html_list, raw_list, separator, line_end):
"""
This implements the binary chop algorithm for faster rendering. This algorithm works line based (line by line)
@ -521,7 +486,7 @@ class Renderer(OpenLPMixin, RegistryMixin, RegistryProperties):
if smallest_index == index or highest_index == index:
index = smallest_index
text = previous_raw.rstrip('<br>') + separator.join(raw_list[:index + 1])
text, raw_tags, html_tags = self._get_start_tags(text)
text, raw_tags, html_tags = get_start_tags(text)
formatted.append(text)
previous_html = ''
previous_raw = ''
@ -556,12 +521,49 @@ class Renderer(OpenLPMixin, RegistryMixin, RegistryProperties):
self.web_frame.evaluateJavaScript('show_text("%s")' % text.replace('\\', '\\\\').replace('\"', '\\\"'))
return self.web_frame.contentsSize().height() <= self.empty_height
def _words_split(self, line):
"""
Split the slide up by word so can wrap better
:param line: Line to be split
"""
# this parse we are to be wordy
line = line.replace('\n', ' ')
return line.split(' ')
def words_split(line):
"""
Split the slide up by word so can wrap better
:param line: Line to be split
"""
# this parse we are to be wordy
return re.split('\s+', line)
def get_start_tags(raw_text):
"""
Tests the given text for not closed formatting tags and returns a tuple consisting of three unicode strings::
('{st}{r}Text text text{/r}{/st}', '{st}{r}', '<strong><span style="-webkit-text-fill-color:red">')
The first unicode string is the text, with correct closing tags. The second unicode string are OpenLP's opening
formatting tags and the third unicode string the html opening formatting tags.
:param raw_text: The text to test. The text must **not** contain html tags, only OpenLP formatting tags
are allowed::
{st}{r}Text text text
"""
raw_tags = []
html_tags = []
for tag in FormattingTags.get_html_tags():
if tag['start tag'] == '{br}':
continue
if raw_text.count(tag['start tag']) != raw_text.count(tag['end tag']):
raw_tags.append((raw_text.find(tag['start tag']), tag['start tag'], tag['end tag']))
html_tags.append((raw_text.find(tag['start tag']), tag['start html']))
# Sort the lists, so that the tags which were opened first on the first slide (the text we are checking) will be
# opened first on the next slide as well.
raw_tags.sort(key=lambda tag: tag[0])
html_tags.sort(key=lambda tag: tag[0])
# Create a list with closing tags for the raw_text.
end_tags = []
start_tags = []
for tag in raw_tags:
start_tags.append(tag[1])
end_tags.append(tag[2])
end_tags.reverse()
# Remove the indexes.
html_tags = [tag[1] for tag in html_tags]
return raw_text + ''.join(end_tags), ''.join(start_tags), ''.join(html_tags)

View File

@ -24,7 +24,7 @@ The About dialog.
"""
import webbrowser
from PyQt5 import QtWidgets
from PyQt5 import QtCore, QtWidgets
from openlp.core.lib import translate
from openlp.core.utils import get_application_version
@ -40,7 +40,7 @@ class AboutForm(QtWidgets.QDialog, UiAboutDialog):
"""
Do some initialisation stuff
"""
super(AboutForm, self).__init__(parent)
super(AboutForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
self._setup()
def _setup(self):

View File

@ -77,6 +77,9 @@ class AdvancedTab(SettingsTab):
self.single_click_preview_check_box = QtWidgets.QCheckBox(self.ui_group_box)
self.single_click_preview_check_box.setObjectName('single_click_preview_check_box')
self.ui_layout.addRow(self.single_click_preview_check_box)
self.single_click_service_preview_check_box = QtWidgets.QCheckBox(self.ui_group_box)
self.single_click_service_preview_check_box.setObjectName('single_click_service_preview_check_box')
self.ui_layout.addRow(self.single_click_service_preview_check_box)
self.expand_service_item_check_box = QtWidgets.QCheckBox(self.ui_group_box)
self.expand_service_item_check_box.setObjectName('expand_service_item_check_box')
self.ui_layout.addRow(self.expand_service_item_check_box)
@ -270,6 +273,8 @@ class AdvancedTab(SettingsTab):
'Double-click to send items straight to live'))
self.single_click_preview_check_box.setText(translate('OpenLP.AdvancedTab',
'Preview items when clicked in Media Manager'))
self.single_click_service_preview_check_box.setText(translate('OpenLP.AdvancedTab',
'Preview items when clicked in Service Manager'))
self.expand_service_item_check_box.setText(translate('OpenLP.AdvancedTab',
'Expand new service items on creation'))
self.enable_auto_close_check_box.setText(translate('OpenLP.AdvancedTab',
@ -339,6 +344,7 @@ class AdvancedTab(SettingsTab):
self.media_plugin_check_box.setChecked(settings.value('save current plugin'))
self.double_click_live_check_box.setChecked(settings.value('double click live'))
self.single_click_preview_check_box.setChecked(settings.value('single click preview'))
self.single_click_service_preview_check_box.setChecked(settings.value('single click service preview'))
self.expand_service_item_check_box.setChecked(settings.value('expand service item'))
self.enable_auto_close_check_box.setChecked(settings.value('enable exit confirmation'))
self.hide_mouse_check_box.setChecked(settings.value('hide mouse'))
@ -420,6 +426,7 @@ class AdvancedTab(SettingsTab):
settings.setValue('save current plugin', self.media_plugin_check_box.isChecked())
settings.setValue('double click live', self.double_click_live_check_box.isChecked())
settings.setValue('single click preview', self.single_click_preview_check_box.isChecked())
settings.setValue('single click service preview', self.single_click_service_preview_check_box.isChecked())
settings.setValue('expand service item', self.expand_service_item_check_box.isChecked())
settings.setValue('enable exit confirmation', self.enable_auto_close_check_box.isChecked())
settings.setValue('hide mouse', self.hide_mouse_check_box.isChecked())

View File

@ -89,7 +89,7 @@ class ExceptionForm(QtWidgets.QDialog, Ui_ExceptionDialog, RegistryProperties):
"""
Constructor.
"""
super(ExceptionForm, self).__init__()
super(ExceptionForm, self).__init__(None, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
self.setupUi(self)
self.settings_section = 'crashreport'
self.report_text = '**OpenLP Bug Report**\n' \
@ -180,11 +180,13 @@ class ExceptionForm(QtWidgets.QDialog, Ui_ExceptionDialog, RegistryProperties):
if ':' in line:
exception = line.split('\n')[-1].split(':')[0]
subject = 'Bug report: %s in %s' % (exception, source)
mail_to_url = QtCore.QUrlQuery('mailto:bugs@openlp.org')
mail_to_url.addQueryItem('subject', subject)
mail_to_url.addQueryItem('body', self.report_text % content)
mail_urlquery = QtCore.QUrlQuery()
mail_urlquery.addQueryItem('subject', subject)
mail_urlquery.addQueryItem('body', self.report_text % content)
if self.file_attachment:
mail_to_url.addQueryItem('attach', self.file_attachment)
mail_urlquery.addQueryItem('attach', self.file_attachment)
mail_to_url = QtCore.QUrl('mailto:bugs@openlp.org')
mail_to_url.setQuery(mail_urlquery)
QtGui.QDesktopServices.openUrl(mail_to_url)
def on_description_updated(self):

View File

@ -23,7 +23,7 @@
The file rename dialog.
"""
from PyQt5 import QtWidgets
from PyQt5 import QtCore, QtWidgets
from .filerenamedialog import Ui_FileRenameDialog
@ -38,7 +38,8 @@ class FileRenameForm(QtWidgets.QDialog, Ui_FileRenameDialog, RegistryProperties)
"""
Constructor
"""
super(FileRenameForm, self).__init__(Registry().get('main_window'))
super(FileRenameForm, self).__init__(Registry().get('main_window'),
QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
self._setup()
def _setup(self):

View File

@ -22,7 +22,7 @@
"""
The language selection dialog.
"""
from PyQt5 import QtWidgets
from PyQt5 import QtCore, QtWidgets
from openlp.core.lib.ui import create_action
from openlp.core.utils import LanguageManager
@ -37,7 +37,7 @@ class FirstTimeLanguageForm(QtWidgets.QDialog, Ui_FirstTimeLanguageDialog):
"""
Constructor
"""
super(FirstTimeLanguageForm, self).__init__(parent)
super(FirstTimeLanguageForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
self.setupUi(self)
self.qm_list = LanguageManager.get_qm_list()
self.language_combo_box.addItem('Autodetect')

View File

@ -25,7 +25,7 @@ Custom tags can be defined and saved. The Custom Tag arrays are saved in a json
Base Tags cannot be changed.
"""
from PyQt5 import QtWidgets
from PyQt5 import QtCore, QtWidgets
from openlp.core.common import translate
from openlp.core.lib import FormattingTags
@ -51,7 +51,7 @@ class FormattingTagForm(QtWidgets.QDialog, Ui_FormattingTagDialog, FormattingTag
"""
Constructor
"""
super(FormattingTagForm, self).__init__(parent)
super(FormattingTagForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
self.setupUi(self)
self._setup()

View File

@ -159,6 +159,7 @@ class GeneralTab(SettingsTab):
self.startup_layout.addWidget(self.show_splash_check_box)
self.check_for_updates_check_box = QtWidgets.QCheckBox(self.startup_group_box)
self.check_for_updates_check_box.setObjectName('check_for_updates_check_box')
self.check_for_updates_check_box.setVisible(False)
self.startup_layout.addWidget(self.check_for_updates_check_box)
self.right_layout.addWidget(self.startup_group_box)
# Application Settings

View File

@ -34,7 +34,7 @@ import logging
from PyQt5 import QtCore, QtWidgets, QtWebKit, QtWebKitWidgets, QtOpenGL, QtGui, QtMultimedia
from openlp.core.common import Registry, RegistryProperties, OpenLPMixin, Settings, translate, is_macosx
from openlp.core.common import Registry, RegistryProperties, OpenLPMixin, Settings, translate, is_macosx, is_win
from openlp.core.lib import ServiceItem, ImageSource, ScreenList, build_html, expand_tags, image_to_byte
from openlp.core.lib.theme import BackgroundType
from openlp.core.ui import HideMode, AlertLocation
@ -86,12 +86,6 @@ class Display(QtWidgets.QGraphicsView):
super(Display, self).__init__()
self.controller = parent
self.screen = {}
# FIXME: On Mac OS X (tested on 10.7) the display screen is corrupt with
# OpenGL. Only white blank screen is shown on the 2nd monitor all the
# time. We need to investigate more how to use OpenGL properly on Mac OS
# X.
if not is_macosx():
self.setViewport(QtOpenGL.QGLWidget())
def setup(self):
"""
@ -121,7 +115,8 @@ class Display(QtWidgets.QGraphicsView):
:param event: The event to be handled
"""
self.web_view.setGeometry(0, 0, self.width(), self.height())
if hasattr(self, 'web_view'):
self.web_view.setGeometry(0, 0, self.width(), self.height())
def is_web_loaded(self, field=None):
"""
@ -331,6 +326,9 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
else:
self.setVisible(False)
self.setGeometry(self.screen['size'])
# Workaround for bug #1531319, should not be needed with PyQt 5.6.
if is_win():
self.shake_web_view()
def direct_image(self, path, background):
"""
@ -400,8 +398,17 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
# Wait for the fade to finish before geting the preview.
# Important otherwise preview will have incorrect text if at all!
if self.service_item.theme_data and self.service_item.theme_data.display_slide_transition:
# Workaround for bug #1531319, should not be needed with PyQt 5.6.
if is_win():
fade_shake_timer = QtCore.QTimer(self)
fade_shake_timer.setInterval(25)
fade_shake_timer.timeout.connect(self.shake_web_view)
fade_shake_timer.start()
while not self.frame.evaluateJavaScript('show_text_completed()'):
self.application.process_events()
# Workaround for bug #1531319, should not be needed with PyQt 5.6.
if is_win():
fade_shake_timer.stop()
# Wait for the webview to update before getting the preview.
# Important otherwise first preview will miss the background !
while not self.web_loaded:
@ -419,6 +426,9 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
self.setVisible(True)
else:
self.setVisible(True)
# Workaround for bug #1531319, should not be needed with PyQt 5.6.
if is_win():
self.shake_web_view()
return self.grab()
def build_html(self, service_item, image_path=''):
@ -498,6 +508,9 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
if self.isHidden():
self.setVisible(True)
self.web_view.setVisible(True)
# Workaround for bug #1531319, should not be needed with PyQt 5.6.
if is_win():
self.shake_web_view()
self.hide_mode = mode
def show_display(self):
@ -516,6 +529,9 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
# Trigger actions when display is active again.
if self.is_live:
Registry().execute('live_display_active')
# Workaround for bug #1531319, should not be needed with PyQt 5.6.
if is_win():
self.shake_web_view()
def _hide_mouse(self):
"""
@ -558,6 +574,13 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
if window_id == main_window_id:
self.main_window.raise_()
def shake_web_view(self):
"""
Resizes the web_view a bit to force an update. Workaround for bug #1531319, should not be needed with PyQt 5.6.
"""
self.web_view.setGeometry(0, 0, self.width(), self.height() - 1)
self.web_view.setGeometry(0, 0, self.width(), self.height())
class AudioPlayer(OpenLPMixin, QtCore.QObject):
"""
@ -575,6 +598,7 @@ class AudioPlayer(OpenLPMixin, QtCore.QObject):
self.player = QtMultimedia.QMediaPlayer()
self.playlist = QtMultimedia.QMediaPlaylist(self.player)
self.volume_slider = None
self.player.setPlaylist(self.playlist)
self.player.positionChanged.connect(self._on_position_changed)
def __del__(self):
@ -642,7 +666,7 @@ class AudioPlayer(OpenLPMixin, QtCore.QObject):
if not isinstance(file_names, list):
file_names = [file_names]
for file_name in file_names:
self.playlist.addMedia(QtCore.QUrl(file_name))
self.playlist.addMedia(QtMultimedia.QMediaContent(QtCore.QUrl.fromLocalFile(file_name)))
def next(self):
"""

View File

@ -47,6 +47,7 @@ from openlp.core.utils import LanguageManager, add_actions, get_application_vers
from openlp.core.utils.actions import ActionList, CategoryOrder
from openlp.core.ui.firsttimeform import FirstTimeForm
from openlp.core.ui.projector.manager import ProjectorManager
from openlp.core.ui.printserviceform import PrintServiceForm
log = logging.getLogger(__name__)
@ -197,7 +198,7 @@ class Ui_MainWindow(object):
triggers=self.service_manager_contents.save_file_as)
self.print_service_order_item = create_action(main_window, 'printServiceItem', can_shortcuts=True,
category=UiStrings().File,
triggers=self.service_manager_contents.print_service_order)
triggers=lambda x: PrintServiceForm().exec())
self.file_exit_item = create_action(main_window, 'fileExitItem', icon=':/system/system_exit.png',
can_shortcuts=True,
category=UiStrings().File, triggers=main_window.close)

File diff suppressed because it is too large Load Diff

View File

@ -326,10 +326,10 @@ class WebkitPlayer(MediaPlayer):
controller = display.controller
if controller.media_info.is_flash:
seek = seek_value
display.frame.evaluateJavaScript('show_flash("seek", null, null, "%s");' % (seek))
display.frame.evaluateJavaScript('show_flash("seek", null, null, "%s");' % seek)
else:
seek = float(seek_value) / 1000
display.frame.evaluateJavaScript('show_video("seek", null, null, null, "%f");' % (seek))
display.frame.evaluateJavaScript('show_video("seek", null, null, null, "%f");' % seek)
def reset(self, display):
"""

View File

@ -39,23 +39,12 @@ class MediaDockManager(object):
"""
self.media_dock = media_dock
def add_dock(self, media_item, icon, weight):
def add_item_to_dock(self, media_item):
"""
Add a MediaManagerItem to the dock
If the item has been added before, it's silently skipped
:param media_item: The item to add to the dock
:param icon: An icon for this dock item
:param weight:
"""
visible_title = media_item.plugin.get_string(StringContent.VisibleName)
log.info('Adding %s dock' % visible_title)
self.media_dock.addItem(media_item, icon, visible_title['title'])
def insert_dock(self, media_item, icon, weight):
"""
This should insert a dock item at a given location
This does not work as it gives a Segmentation error.
For now add at end of stack if not present
:param media_item: The item to add to the dock
"""
visible_title = media_item.plugin.get_string(StringContent.VisibleName)
log.debug('Inserting %s dock' % visible_title['title'])
@ -65,7 +54,7 @@ class MediaDockManager(object):
match = True
break
if not match:
self.media_dock.addItem(media_item, icon, visible_title['title'])
self.media_dock.addItem(media_item, visible_title['title'])
def remove_dock(self, media_item):
"""

View File

@ -24,7 +24,7 @@ The actual plugin view form
"""
import logging
from PyQt5 import QtWidgets
from PyQt5 import QtCore, QtWidgets
from openlp.core.common import RegistryProperties, translate
from openlp.core.lib import PluginStatus
@ -41,7 +41,7 @@ class PluginForm(QtWidgets.QDialog, Ui_PluginViewDialog, RegistryProperties):
"""
Constructor
"""
super(PluginForm, self).__init__(parent)
super(PluginForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
self.active_plugin = None
self.programatic_change = False
self.setupUi(self)

View File

@ -112,7 +112,8 @@ class PrintServiceForm(QtWidgets.QDialog, Ui_PrintServiceDialog, RegistryPropert
"""
Constructor
"""
super(PrintServiceForm, self).__init__(Registry().get('main_window'))
super(PrintServiceForm, self).__init__(Registry().get('main_window'),
QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
self.printer = QtPrintSupport.QPrinter()
self.print_dialog = QtPrintSupport.QPrintDialog(self.printer, self)
self.document = QtGui.QTextDocument()

View File

@ -144,7 +144,7 @@ class ProjectorEditForm(QDialog, Ui_ProjectorEditForm):
editProjector = pyqtSignal(object)
def __init__(self, parent=None, projectordb=None):
super(ProjectorEditForm, self).__init__(parent=parent)
super(ProjectorEditForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
self.projectordb = projectordb
self.setupUi(self)
self.button_box.accepted.connect(self.accept_me)

View File

@ -83,60 +83,60 @@ class Ui_ProjectorManager(object):
self.one_toolbar.add_toolbar_action('new_projector',
text=translate('OpenLP.ProjectorManager', 'Add Projector'),
icon=':/projector/projector_new.png',
tooltip=translate('OpenLP.ProjectorManager', 'Add a new projector'),
tooltip=translate('OpenLP.ProjectorManager', 'Add a new projector.'),
triggers=self.on_add_projector)
# Show edit/delete when projector not connected
self.one_toolbar.add_toolbar_action('edit_projector',
text=translate('OpenLP.ProjectorManager', 'Edit Projector'),
icon=':/general/general_edit.png',
tooltip=translate('OpenLP.ProjectorManager', 'Edit selected projector'),
tooltip=translate('OpenLP.ProjectorManager', 'Edit selected projector.'),
triggers=self.on_edit_projector)
self.one_toolbar.add_toolbar_action('delete_projector',
text=translate('OpenLP.ProjectorManager', 'Delete Projector'),
icon=':/general/general_delete.png',
tooltip=translate('OpenLP.ProjectorManager', 'Delete selected projector'),
tooltip=translate('OpenLP.ProjectorManager', 'Delete selected projector.'),
triggers=self.on_delete_projector)
# Show source/view when projector connected
self.one_toolbar.add_toolbar_action('source_view_projector',
text=translate('OpenLP.ProjectorManager', 'Select Input Source'),
icon=':/projector/projector_hdmi.png',
tooltip=translate('OpenLP.ProjectorManager',
'Choose input source on selected projector'),
'Choose input source on selected projector.'),
triggers=self.on_select_input)
self.one_toolbar.add_toolbar_action('view_projector',
text=translate('OpenLP.ProjectorManager', 'View Projector'),
icon=':/system/system_about.png',
tooltip=translate('OpenLP.ProjectorManager',
'View selected projector information'),
'View selected projector information.'),
triggers=self.on_status_projector)
self.one_toolbar.addSeparator()
self.one_toolbar.add_toolbar_action('connect_projector',
text=translate('OpenLP.ProjectorManager',
'Connect to selected projector'),
'Connect to selected projector.'),
icon=':/projector/projector_connect.png',
tooltip=translate('OpenLP.ProjectorManager',
'Connect to selected projector'),
'Connect to selected projector.'),
triggers=self.on_connect_projector)
self.one_toolbar.add_toolbar_action('connect_projector_multiple',
text=translate('OpenLP.ProjectorManager',
'Connect to selected projectors'),
icon=':/projector/projector_connect_tiled.png',
tooltip=translate('OpenLP.ProjectorManager',
'Connect to selected projector'),
'Connect to selected projectors.'),
triggers=self.on_connect_projector)
self.one_toolbar.add_toolbar_action('disconnect_projector',
text=translate('OpenLP.ProjectorManager',
'Disconnect from selected projectors'),
icon=':/projector/projector_disconnect.png',
tooltip=translate('OpenLP.ProjectorManager',
'Disconnect from selected projector'),
'Disconnect from selected projector.'),
triggers=self.on_disconnect_projector)
self.one_toolbar.add_toolbar_action('disconnect_projector_multiple',
text=translate('OpenLP.ProjectorManager',
'Disconnect from selected projector'),
icon=':/projector/projector_disconnect_tiled.png',
tooltip=translate('OpenLP.ProjectorManager',
'Disconnect from selected projector'),
'Disconnect from selected projectors.'),
triggers=self.on_disconnect_projector)
self.one_toolbar.addSeparator()
self.one_toolbar.add_toolbar_action('poweron_projector',
@ -144,26 +144,26 @@ class Ui_ProjectorManager(object):
'Power on selected projector'),
icon=':/projector/projector_power_on.png',
tooltip=translate('OpenLP.ProjectorManager',
'Power on selected projector'),
'Power on selected projector.'),
triggers=self.on_poweron_projector)
self.one_toolbar.add_toolbar_action('poweron_projector_multiple',
text=translate('OpenLP.ProjectorManager',
'Power on selected projector'),
icon=':/projector/projector_power_on_tiled.png',
tooltip=translate('OpenLP.ProjectorManager',
'Power on selected projector'),
'Power on selected projectors.'),
triggers=self.on_poweron_projector)
self.one_toolbar.add_toolbar_action('poweroff_projector',
text=translate('OpenLP.ProjectorManager', 'Standby selected projector'),
icon=':/projector/projector_power_off.png',
tooltip=translate('OpenLP.ProjectorManager',
'Put selected projector in standby'),
'Put selected projector in standby.'),
triggers=self.on_poweroff_projector)
self.one_toolbar.add_toolbar_action('poweroff_projector_multiple',
text=translate('OpenLP.ProjectorManager', 'Standby selected projector'),
icon=':/projector/projector_power_off_tiled.png',
tooltip=translate('OpenLP.ProjectorManager',
'Put selected projector in standby'),
'Put selected projectors in standby.'),
triggers=self.on_poweroff_projector)
self.one_toolbar.addSeparator()
self.one_toolbar.add_toolbar_action('blank_projector',
@ -175,24 +175,24 @@ class Ui_ProjectorManager(object):
triggers=self.on_blank_projector)
self.one_toolbar.add_toolbar_action('blank_projector_multiple',
text=translate('OpenLP.ProjectorManager',
'Blank selected projector screen'),
'Blank selected projectors screen'),
icon=':/projector/projector_blank_tiled.png',
tooltip=translate('OpenLP.ProjectorManager',
'Blank selected projector screen'),
'Blank selected projectors screen.'),
triggers=self.on_blank_projector)
self.one_toolbar.add_toolbar_action('show_projector',
text=translate('OpenLP.ProjectorManager',
'Show selected projector screen'),
icon=':/projector/projector_show.png',
tooltip=translate('OpenLP.ProjectorManager',
'Show selected projector screen'),
'Show selected projector screen.'),
triggers=self.on_show_projector)
self.one_toolbar.add_toolbar_action('show_projector_multiple',
text=translate('OpenLP.ProjectorManager',
'Show selected projector screen'),
icon=':/projector/projector_show_tiled.png',
tooltip=translate('OpenLP.ProjectorManager',
'Show selected projector screen'),
'Show selected projectors screen.'),
triggers=self.on_show_projector)
self.layout.addWidget(self.one_toolbar)
self.projector_one_widget = QtWidgets.QWidgetAction(self.one_toolbar)

View File

@ -236,7 +236,7 @@ class SourceSelectTabs(QDialog):
:param projectordb: ProjectorDB session to use
"""
log.debug('Initializing SourceSelectTabs()')
super(SourceSelectTabs, self).__init__(parent)
super(SourceSelectTabs, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
self.setMinimumWidth(350)
self.projectordb = projectordb
self.edit = edit
@ -385,7 +385,7 @@ class SourceSelectSingle(QDialog):
"""
log.debug('Initializing SourceSelectSingle()')
self.projectordb = projectordb
super(SourceSelectSingle, self).__init__(parent)
super(SourceSelectSingle, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
self.edit = edit
if self.edit:
title = translate('OpenLP.SourceSelectForm', 'Edit Projector Source Text')

View File

@ -22,7 +22,7 @@
"""
The service item edit dialog
"""
from PyQt5 import QtWidgets
from PyQt5 import QtCore, QtWidgets
from openlp.core.common import Registry, RegistryProperties
@ -37,7 +37,8 @@ class ServiceItemEditForm(QtWidgets.QDialog, Ui_ServiceItemEditDialog, RegistryP
"""
Constructor
"""
super(ServiceItemEditForm, self).__init__(Registry().get('main_window'))
super(ServiceItemEditForm, self).__init__(Registry().get('main_window'),
QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
self.setupUi(self)
self.item_list = []
self.list_widget.currentRowChanged.connect(self.on_current_row_changed)

View File

@ -144,8 +144,8 @@ class Ui_ServiceManager(object):
self.service_manager_list.customContextMenuRequested.connect(self.context_menu)
self.service_manager_list.setObjectName('service_manager_list')
# enable drop
self.service_manager_list.__class__.dragEnterEvent = self.drag_enter_event
self.service_manager_list.__class__.dragMoveEvent = self.drag_enter_event
self.service_manager_list.__class__.dragEnterEvent = lambda x, event: event.accept()
self.service_manager_list.__class__.dragMoveEvent = lambda x, event: event.accept()
self.service_manager_list.__class__.dropEvent = self.drop_event
self.layout.addWidget(self.service_manager_list)
# Add the bottom toolbar
@ -211,7 +211,8 @@ class Ui_ServiceManager(object):
self.layout.addWidget(self.order_toolbar)
# Connect up our signals and slots
self.theme_combo_box.activated.connect(self.on_theme_combo_box_selected)
self.service_manager_list.doubleClicked.connect(self.on_make_live)
self.service_manager_list.doubleClicked.connect(self.on_double_click_live)
self.service_manager_list.clicked.connect(self.on_single_click_preview)
self.service_manager_list.itemCollapsed.connect(self.collapsed)
self.service_manager_list.itemExpanded.connect(self.expanded)
# Last little bits of setting up
@ -293,14 +294,6 @@ class Ui_ServiceManager(object):
Registry().register_function('theme_update_global', self.theme_change)
Registry().register_function('mediaitem_suffix_reset', self.reset_supported_suffixes)
def drag_enter_event(self, event):
"""
Accept Drag events
:param event: Handle of the event passed
"""
event.accept()
class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceManager, RegistryProperties):
"""
@ -327,6 +320,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
self._modified = False
self._file_name = ''
self.service_has_all_original_files = True
self.list_double_clicked = False
def bootstrap_initialise(self):
"""
@ -1139,7 +1133,9 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
:param item: The service item to be checked
"""
pos = item.data(0, QtCore.Qt.UserRole)
self.service_items[pos - 1]['expanded'] = False
# Only set root items as collapsed, and since we only have 2 levels we find them by checking for children
if item.childCount():
self.service_items[pos - 1]['expanded'] = False
def on_expand_all(self, field=None):
"""
@ -1157,7 +1153,9 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
:param item: The service item to be checked
"""
pos = item.data(0, QtCore.Qt.UserRole)
self.service_items[pos - 1]['expanded'] = True
# Only set root items as expanded, and since we only have 2 levels we find them by checking for children
if item.childCount():
self.service_items[pos - 1]['expanded'] = True
def on_service_top(self, field=None):
"""
@ -1458,13 +1456,38 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
else:
return self.service_items[item]['service_item']
def on_make_live(self, field=None):
def on_double_click_live(self, field=None):
"""
Send the current item to the Live slide controller but triggered by a tablewidget click event.
:param field:
"""
self.list_double_clicked = True
self.make_live()
def on_single_click_preview(self, field=None):
"""
If single click previewing is enabled, and triggered by a tablewidget click event,
start a timeout to verify a double-click hasn't triggered.
:param field:
"""
if Settings().value('advanced/single click service preview'):
if not self.list_double_clicked:
# If a double click has not registered start a timer, otherwise wait for the existing timer to finish.
QtCore.QTimer.singleShot(QtWidgets.QApplication.instance().doubleClickInterval(),
self.on_single_click_preview_timeout)
def on_single_click_preview_timeout(self):
"""
If a single click ok, but double click not triggered, send the current item to the Preview slide controller.
:param field:
"""
if self.list_double_clicked:
# If a double click has registered, clear it.
self.list_double_clicked = False
else:
# Otherwise preview the item.
self.make_preview()
def make_live(self, row=-1):
"""
Send the current item to the Live slide controller
@ -1585,7 +1608,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
if item is None:
end_pos = len(self.service_items)
else:
end_pos = self._get_parent_item_data(item) - 1
end_pos = get_parent_item_data(item) - 1
service_item = self.service_items[start_pos]
self.service_items.remove(service_item)
self.service_items.insert(end_pos, service_item)
@ -1598,21 +1621,21 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
self.drop_position = len(self.service_items)
else:
# we are over something so lets investigate
pos = self._get_parent_item_data(item) - 1
pos = get_parent_item_data(item) - 1
service_item = self.service_items[pos]
if (plugin == service_item['service_item'].name and
service_item['service_item'].is_capable(ItemCapabilities.CanAppend)):
action = self.dnd_menu.exec(QtGui.QCursor.pos())
# New action required
if action == self.new_action:
self.drop_position = self._get_parent_item_data(item)
self.drop_position = get_parent_item_data(item)
# Append to existing action
if action == self.add_to_action:
self.drop_position = self._get_parent_item_data(item)
self.drop_position = get_parent_item_data(item)
item.setSelected(True)
replace = True
else:
self.drop_position = self._get_parent_item_data(item) - 1
self.drop_position = get_parent_item_data(item) - 1
Registry().execute('%s_add_service_item' % plugin, replace)
def update_theme_list(self, theme_list):
@ -1656,27 +1679,21 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
self.service_items[item]['service_item'].update_theme(theme)
self.regenerate_service_items(True)
def _get_parent_item_data(self, item):
"""
Finds and returns the parent item for any item
:param item: The service item list item to be checked.
"""
parent_item = item.parent()
if parent_item is None:
return item.data(0, QtCore.Qt.UserRole)
else:
return parent_item.data(0, QtCore.Qt.UserRole)
def print_service_order(self, field=None):
"""
Print a Service Order Sheet.
"""
setting_dialog = PrintServiceForm()
setting_dialog.exec()
def get_drop_position(self):
"""
Getter for drop_position. Used in: MediaManagerItem
"""
return self.drop_position
def get_parent_item_data(item):
"""
Finds and returns the parent item for any item
:param item: The service item list item to be checked.
"""
parent_item = item.parent()
if parent_item is None:
return item.data(0, QtCore.Qt.UserRole)
else:
return parent_item.data(0, QtCore.Qt.UserRole)

View File

@ -22,7 +22,7 @@
"""
The :mod:`~openlp.core.ui.servicenoteform` module contains the `ServiceNoteForm` class.
"""
from PyQt5 import QtWidgets
from PyQt5 import QtCore, QtWidgets
from openlp.core.common import Registry, RegistryProperties, translate
from openlp.core.lib import SpellTextEdit
@ -37,7 +37,8 @@ class ServiceNoteForm(QtWidgets.QDialog, RegistryProperties):
"""
Constructor
"""
super(ServiceNoteForm, self).__init__(Registry().get('main_window'))
super(ServiceNoteForm, self).__init__(Registry().get('main_window'),
QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
self.setupUi()
self.retranslateUi()

View File

@ -46,7 +46,7 @@ class SettingsForm(QtWidgets.QDialog, Ui_SettingsDialog, RegistryProperties):
"""
Registry().register('settings_form', self)
Registry().register_function('bootstrap_post_set_up', self.bootstrap_post_set_up)
super(SettingsForm, self).__init__(parent)
super(SettingsForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
self.processes = []
self.setupUi(self)
self.setting_list_widget.currentRowChanged.connect(self.list_item_changed)

View File

@ -44,7 +44,7 @@ class ShortcutListForm(QtWidgets.QDialog, Ui_ShortcutListDialog, RegistryPropert
"""
Constructor
"""
super(ShortcutListForm, self).__init__(parent)
super(ShortcutListForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
self.setupUi(self)
self.changed_actions = {}
self.action_list = ActionList.get_instance()
@ -320,9 +320,17 @@ class ShortcutListForm(QtWidgets.QDialog, Ui_ShortcutListDialog, RegistryPropert
"""
if not toggled:
return
self.on_primary_push_button_clicked(False)
self.on_alternate_push_button_clicked(False)
action = self._current_item_action()
shortcuts = self._action_shortcuts(action)
self.refresh_shortcut_list()
primary_button_text = ''
alternate_button_text = ''
if shortcuts:
primary_button_text = self.get_shortcut_string(shortcuts[0], for_display=True)
if len(shortcuts) == 2:
alternate_button_text = self.get_shortcut_string(shortcuts[1], for_display=True)
self.primary_push_button.setText(primary_button_text)
self.alternate_push_button.setText(alternate_button_text)
def save(self):
"""

View File

@ -31,7 +31,7 @@ from threading import Lock
from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common import Registry, RegistryProperties, Settings, SlideLimits, UiStrings, translate, \
RegistryMixin, OpenLPMixin
RegistryMixin, OpenLPMixin, is_win
from openlp.core.lib import OpenLPToolbar, ItemCapabilities, ServiceItem, ImageSource, ServiceItemAction, \
ScreenList, build_icon, build_html
from openlp.core.ui import HideMode, MainDisplay, Display, DisplayControllerType
@ -601,13 +601,21 @@ class SlideController(DisplayController, RegistryProperties):
def __add_actions_to_widget(self, widget):
"""
Add actions to the widget specified by `widget`
This defines the controls available when Live display has stolen focus.
Examples of this happening: Clicking anything in the live window or certain single screen mode scenarios.
Needles to say, blank to modes should not be removed from here.
For some reason this required a test. It may be found in test_slidecontroller.py as
"live_stolen_focus_shortcuts_test. If you want to modify things here, you must also modify them there. (Duh)
:param widget: The UI widget for the actions
"""
widget.addActions([
self.previous_item, self.next_item,
self.previous_service, self.next_service,
self.escape_item])
self.escape_item,
self.desktop_screen,
self.theme_screen,
self.blank_screen])
def preview_size_changed(self):
"""
@ -828,13 +836,13 @@ class SlideController(DisplayController, RegistryProperties):
self.selected_row = 0
# take a copy not a link to the servicemanager copy.
self.service_item = copy.copy(service_item)
if self.service_item.is_command():
Registry().execute(
'%s_start' % service_item.name.lower(), [self.service_item, self.is_live, self.hide_mode(), slide_no])
# Reset blanking if needed
if old_item and self.is_live and (old_item.is_capable(ItemCapabilities.ProvidesOwnDisplay) or
self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay)):
self._reset_blank(self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay))
if service_item.is_command():
Registry().execute(
'%s_start' % service_item.name.lower(), [self.service_item, self.is_live, self.hide_mode(), slide_no])
self.info_label.setText(self.service_item.title)
self.slide_list = {}
if self.is_live:
@ -886,28 +894,28 @@ class SlideController(DisplayController, RegistryProperties):
self.service_item.bg_image_bytes = \
self.image_manager.get_image_bytes(frame['path'], ImageSource.ImagePlugin)
self.preview_widget.replace_service_item(self.service_item, width, slide_no)
self.enable_tool_bar(service_item)
self.enable_tool_bar(self.service_item)
# Pass to display for viewing.
# Postpone image build, we need to do this later to avoid the theme
# flashing on the screen
if not self.service_item.is_image():
self.display.build_html(self.service_item)
if service_item.is_media():
self.on_media_start(service_item)
if self.service_item.is_media():
self.on_media_start(self.service_item)
self.slide_selected(True)
if service_item.from_service:
if self.service_item.from_service:
self.preview_widget.setFocus()
if old_item:
# Close the old item after the new one is opened
# This avoids the service theme/desktop flashing on screen
# However opening a new item of the same type will automatically
# close the previous, so make sure we don't close the new one.
if old_item.is_command() and not service_item.is_command() or \
old_item.is_command() and not old_item.is_media() and service_item.is_media():
if old_item.is_command() and not self.service_item.is_command() or \
old_item.is_command() and not old_item.is_media() and self.service_item.is_media():
Registry().execute('%s_stop' % old_item.name.lower(), [old_item, self.is_live])
if old_item.is_media() and not service_item.is_media():
if old_item.is_media() and not self.service_item.is_media():
self.on_media_close()
Registry().execute('slidecontroller_%s_started' % self.type_prefix, [service_item])
Registry().execute('slidecontroller_%s_started' % self.type_prefix, [self.service_item])
def on_slide_selected_index(self, message):
"""
@ -1125,8 +1133,8 @@ class SlideController(DisplayController, RegistryProperties):
self.log_debug('update_preview %s ' % self.screens.current['primary'])
if self.service_item and self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay):
# Grab now, but try again in a couple of seconds if slide change is slow
QtCore.QTimer.singleShot(0.5, self.grab_maindisplay)
QtCore.QTimer.singleShot(2.5, self.grab_maindisplay)
QtCore.QTimer.singleShot(500, self.grab_maindisplay)
QtCore.QTimer.singleShot(2500, self.grab_maindisplay)
else:
self.slide_image = self.display.preview()
self.slide_image.setDevicePixelRatio(self.main_window.devicePixelRatio())
@ -1138,8 +1146,9 @@ class SlideController(DisplayController, RegistryProperties):
Creates an image of the current screen and updates the preview frame.
"""
win_id = QtWidgets.QApplication.desktop().winId()
screen = QtWidgets.QApplication.primaryScreen()
rect = self.screens.current['size']
win_image = QtGui.QScreen.grabWindow(win_id, rect.x(), rect.y(), rect.width(), rect.height())
win_image = screen.grabWindow(win_id, rect.x(), rect.y(), rect.width(), rect.height())
win_image.setDevicePixelRatio(self.slide_preview.devicePixelRatio())
self.slide_preview.setPixmap(win_image)
self.slide_image = win_image
@ -1420,7 +1429,7 @@ class SlideController(DisplayController, RegistryProperties):
:param time: the time remaining
"""
seconds = self.display.audio_player.media_object.remainingTime() // 1000
seconds = (self.display.audio_player.player.duration() - self.display.audio_player.player.position()) // 1000
minutes = seconds // 60
seconds %= 60
self.audio_time_label.setText(' %02d:%02d ' % (minutes, seconds))

View File

@ -22,7 +22,7 @@
"""
The actual start time form.
"""
from PyQt5 import QtWidgets
from PyQt5 import QtCore, QtWidgets
from .starttimedialog import Ui_StartTimeDialog
@ -38,7 +38,8 @@ class StartTimeForm(QtWidgets.QDialog, Ui_StartTimeDialog, RegistryProperties):
"""
Constructor
"""
super(StartTimeForm, self).__init__(Registry().get('main_window'))
super(StartTimeForm, self).__init__(Registry().get('main_window'),
QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
self.setupUi(self)
def exec(self):

View File

@ -159,7 +159,7 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties):
if not event:
event = QtGui.QResizeEvent(self.size(), self.size())
QtWidgets.QWizard.resizeEvent(self, event)
if self.currentPage() == self.preview_page:
if hasattr(self, 'preview_page') and self.currentPage() == self.preview_page:
frame_width = self.preview_box_label.lineWidth()
pixmap_width = self.preview_area.width() - 2 * frame_width
pixmap_height = self.preview_area.height() - 2 * frame_width

71
openlp/core/utils/db.py Normal file
View File

@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2016 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
The :mod:`db` module provides helper functions for database related methods.
"""
import sqlalchemy
import logging
from copy import deepcopy
log = logging.getLogger(__name__)
def drop_column(op, tablename, columnname):
drop_columns(op, tablename, [columnname])
def drop_columns(op, tablename, columns):
"""
Column dropping functionality for SQLite, as there is no DROP COLUMN support in SQLite
From https://github.com/klugjohannes/alembic-sqlite
"""
# get the db engine and reflect database tables
engine = op.get_bind()
meta = sqlalchemy.MetaData(bind=engine)
meta.reflect()
# create a select statement from the old table
old_table = meta.tables[tablename]
select = sqlalchemy.sql.select([c for c in old_table.c if c.name not in columns])
# get remaining columns without table attribute attached
remaining_columns = [deepcopy(c) for c in old_table.columns if c.name not in columns]
for column in remaining_columns:
column.table = None
# create a temporary new table
new_tablename = '{0}_new'.format(tablename)
op.create_table(new_tablename, *remaining_columns)
meta.reflect()
new_table = meta.tables[new_tablename]
# copy data from old table
insert = sqlalchemy.sql.insert(new_table).from_select([c.name for c in remaining_columns], select)
engine.execute(insert)
# drop the old table and rename the new table to take the old tables
# position
op.drop_table(tablename)
op.rename_table(new_tablename, tablename)

View File

@ -191,7 +191,8 @@ class AlertsPlugin(Plugin):
self.alert_form.load_list()
self.alert_form.exec()
def about(self):
@staticmethod
def about():
"""
Plugin Alerts about method
@ -215,7 +216,8 @@ class AlertsPlugin(Plugin):
'title': translate('AlertsPlugin', 'Alerts', 'container title')
}
def get_display_javascript(self):
@staticmethod
def get_display_javascript():
"""
Add Javascript to the main display.
"""
@ -229,7 +231,8 @@ class AlertsPlugin(Plugin):
return CSS % (align, self.settings_tab.font_face, self.settings_tab.font_size, self.settings_tab.font_color,
self.settings_tab.background_color)
def get_display_html(self):
@staticmethod
def get_display_html():
"""
Add HTML to the main display.
"""

View File

@ -36,10 +36,11 @@ class AlertForm(QtWidgets.QDialog, Ui_AlertDialog):
"""
Initialise the alert form
"""
super(AlertForm, self).__init__(Registry().get('main_window'),
QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
self.manager = plugin.manager
self.plugin = plugin
self.item_id = None
super(AlertForm, self).__init__(Registry().get('main_window'))
self.setupUi(self)
self.display_button.clicked.connect(self.on_display_clicked)
self.display_close_button.clicked.connect(self.on_display_close_clicked)

View File

@ -167,7 +167,8 @@ class BiblePlugin(Plugin):
if self.media_item:
self.media_item.on_import_click()
def about(self):
@staticmethod
def about():
"""
Return the about text for the plugin manager
"""

View File

@ -74,8 +74,8 @@ class BibleImportForm(OpenLPWizard):
"""
self.manager = manager
self.web_bible_list = {}
super(BibleImportForm, self).__init__(
parent, bible_plugin, 'bibleImportWizard', ':/wizards/wizard_importbible.bmp')
super(BibleImportForm, self).__init__(parent, bible_plugin,
'bibleImportWizard', ':/wizards/wizard_importbible.bmp')
def setupUi(self, image):
"""

View File

@ -49,7 +49,7 @@ class BookNameForm(QDialog, Ui_BookNameDialog):
"""
Constructor
"""
super(BookNameForm, self).__init__(parent)
super(BookNameForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
self.setupUi(self)
self.custom_signals()
self.book_names = BibleStrings().BookNames

View File

@ -24,7 +24,7 @@ import logging
import os
import re
from PyQt5 import QtWidgets
from PyQt5 import QtCore, QtWidgets
from openlp.core.common import RegistryProperties, UiStrings, translate
from openlp.core.lib.ui import critical_error_message_box
@ -45,7 +45,7 @@ class EditBibleForm(QtWidgets.QDialog, Ui_EditBibleDialog, RegistryProperties):
"""
Constructor
"""
super(EditBibleForm, self).__init__(parent)
super(EditBibleForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
self.media_item = media_item
self.book_names = BibleStrings().BookNames
self.setupUi(self)

View File

@ -26,6 +26,7 @@ Module implementing LanguageForm.
import logging
from PyQt5.QtWidgets import QDialog
from PyQt5 import QtCore
from openlp.core.common import translate
from openlp.core.lib.ui import critical_error_message_box
@ -46,7 +47,7 @@ class LanguageForm(QDialog, Ui_LanguageDialog):
"""
Constructor
"""
super(LanguageForm, self).__init__(parent)
super(LanguageForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
self.setupUi(self)
def exec(self, bible_name):

View File

@ -288,7 +288,7 @@ class BGExtract(RegistryProperties):
except UnicodeDecodeError:
page_source = str(page_source, 'cp1251')
try:
soup = BeautifulSoup(page_source)
soup = BeautifulSoup(page_source, 'lxml')
except Exception:
log.error('BeautifulSoup could not parse the Bible page.')
send_error_message('parse')
@ -759,7 +759,7 @@ def get_soup_for_bible_ref(reference_url, header=None, pre_parse_regex=None, pre
page_source = re.sub(pre_parse_regex, pre_parse_substitute, page_source.decode())
soup = None
try:
soup = BeautifulSoup(page_source)
soup = BeautifulSoup(page_source, 'lxml')
CLEANER_REGEX.sub('', str(soup))
except Exception:
log.exception('BeautifulSoup could not parse the bible page.')

View File

@ -254,6 +254,7 @@ class BibleMediaItem(MediaManagerItem):
def on_focus(self):
if self.quickTab.isVisible():
self.quick_search_edit.setFocus()
self.quick_search_edit.selectAll()
else:
self.advanced_book_combo_box.setFocus()

View File

@ -62,7 +62,8 @@ class CustomPlugin(Plugin):
self.icon_path = ':/plugins/plugin_custom.png'
self.icon = build_icon(self.icon_path)
def about(self):
@staticmethod
def about():
about_text = translate('CustomPlugin', '<strong>Custom Slide Plugin </strong><br />The custom slide plugin '
'provides the ability to set up custom text slides that can be displayed on the screen '
'the same way songs are. This plugin provides greater freedom over the songs plugin.')
@ -73,6 +74,7 @@ class CustomPlugin(Plugin):
Called to find out if the custom plugin is currently using a theme.
Returns count of the times the theme is used.
:param theme: Theme to be queried
"""
return len(self.db_manager.get_all_objects(CustomSlide, CustomSlide.theme_name == theme))

View File

@ -22,7 +22,7 @@
import logging
from PyQt5 import QtWidgets
from PyQt5 import QtCore, QtWidgets
from openlp.core.common import Registry, translate
from openlp.core.lib.ui import critical_error_message_box, find_and_set_in_combo_box
@ -44,7 +44,7 @@ class EditCustomForm(QtWidgets.QDialog, Ui_CustomEditDialog):
"""
Constructor
"""
super(EditCustomForm, self).__init__(parent)
super(EditCustomForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
self.manager = manager
self.media_item = media_item
self.setupUi(self)
@ -198,6 +198,7 @@ class EditCustomForm(QtWidgets.QDialog, Ui_CustomEditDialog):
# Insert all slides to make the old_slides list complete.
for slide in slides:
old_slides.insert(old_row, slide)
old_row += 1
self.slide_list_view.addItems(old_slides)
self.slide_list_view.repaint()

View File

@ -22,7 +22,7 @@
import logging
from PyQt5 import QtWidgets
from PyQt5 import QtCore, QtWidgets
from .editcustomslidedialog import Ui_CustomSlideEditDialog
@ -39,7 +39,7 @@ class EditCustomSlideForm(QtWidgets.QDialog, Ui_CustomSlideEditDialog):
"""
Constructor
"""
super(EditCustomSlideForm, self).__init__(parent)
super(EditCustomSlideForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
self.setupUi(self)
# Connecting signals and slots
self.insert_button.clicked.connect(self.on_insert_button_clicked)

View File

@ -190,8 +190,7 @@ class CustomMediaItem(MediaManagerItem):
if QtWidgets.QMessageBox.question(
self, UiStrings().ConfirmDelete,
translate('CustomPlugin.MediaItem',
'Are you sure you want to delete the %n selected custom slide(s)?',
'', QtCore.QCoreApplication.CodecForTr, len(items)),
'Are you sure you want to delete the "%d" selected custom slide(s)?') % len(items),
QtWidgets.QMessageBox.StandardButtons(
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No),
QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.No:
@ -208,6 +207,7 @@ class CustomMediaItem(MediaManagerItem):
Set the focus
"""
self.search_text_edit.setFocus()
self.search_text_edit.selectAll()
def generate_slide_data(self, service_item, item=None, xml_version=False,
remote=False, context=ServiceItemContext.Service):

View File

@ -20,7 +20,7 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
from PyQt5 import QtWidgets
from PyQt5 import QtCore, QtWidgets
from openlp.core.common import translate
from openlp.core.lib.ui import critical_error_message_box
@ -35,7 +35,7 @@ class AddGroupForm(QtWidgets.QDialog, Ui_AddGroupDialog):
"""
Constructor
"""
super(AddGroupForm, self).__init__(parent)
super(AddGroupForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
self.setupUi(self)
def exec(self, clear=True, show_top_level_group=False, selected_group=None):

View File

@ -20,7 +20,7 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
from PyQt5 import QtWidgets
from PyQt5 import QtCore, QtWidgets
from openlp.plugins.images.forms.choosegroupdialog import Ui_ChooseGroupDialog
@ -33,7 +33,7 @@ class ChooseGroupForm(QtWidgets.QDialog, Ui_ChooseGroupDialog):
"""
Constructor
"""
super(ChooseGroupForm, self).__init__(parent)
super(ChooseGroupForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
self.setupUi(self)
def exec(self, selected_group=None):

View File

@ -23,9 +23,8 @@
from PyQt5 import QtGui
import logging
import os
from openlp.core.common import Registry, Settings, translate
from openlp.core.common import Settings, translate
from openlp.core.lib import Plugin, StringContent, ImageSource, build_icon
from openlp.core.lib.db import Manager
from openlp.plugins.images.lib import ImageMediaItem, ImageTab
@ -53,7 +52,8 @@ class ImagePlugin(Plugin):
self.icon_path = ':/plugins/plugin_images.png'
self.icon = build_icon(self.icon_path)
def about(self):
@staticmethod
def about():
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 '

View File

@ -52,7 +52,7 @@ class MediaClipSelectorForm(QtWidgets.QDialog, Ui_MediaClipSelector, RegistryPro
"""
Constructor
"""
super(MediaClipSelectorForm, self).__init__(parent)
super(MediaClipSelectorForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
self.vlc_instance = None
self.vlc_media_player = None
self.vlc_media = None

View File

@ -84,7 +84,8 @@ class MediaPlugin(Plugin):
visible_name = self.get_string(StringContent.VisibleName)
self.settings_tab = MediaTab(parent, self.name, visible_name['title'], self.icon_path)
def about(self):
@staticmethod
def about():
"""
Return the about text for the plugin manager
"""

View File

@ -63,6 +63,7 @@ class Controller(object):
if not self.doc.load_presentation():
# Display error message to user
# Inform slidecontroller that the action failed?
self.doc.slidenumber = 0
return
self.doc.slidenumber = slide_no
self.hide_mode = hide_mode
@ -349,16 +350,17 @@ class MessageListener(object):
# When presenting PDF/XPS/OXPS, we are using the image presentation code,
# 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.find_controller_by_type(file)
if not self.handler:
return
else:
# the saved handler is not present so need to use one based on file suffix.
if not self.controllers[self.handler].available:
if self.handler == self.media_item.automatic:
self.handler = self.media_item.find_controller_by_type(file)
if not self.handler:
return
else:
# the saved handler is not present so need to use one based on file suffix.
if not self.controllers[self.handler].available:
self.handler = self.media_item.find_controller_by_type(file)
if not self.handler:
return
if is_live:
controller = self.live_handler
else:

View File

@ -28,7 +28,7 @@ import logging
from PyQt5 import QtCore
from openlp.core.common import AppLocation, Settings, translate
from openlp.core.common import AppLocation, translate
from openlp.core.lib import Plugin, StringContent, build_icon
from openlp.plugins.presentations.lib import PresentationController, PresentationMediaItem, PresentationTab
@ -71,6 +71,7 @@ class PresentationPlugin(Plugin):
def create_settings_tab(self, parent):
"""
Create the settings Tab.
:param parent: parent UI Element
"""
visible_name = self.get_string(StringContent.VisibleName)
self.settings_tab = PresentationTab(parent, self.name, visible_name['title'], self.controllers, self.icon_path)
@ -112,6 +113,7 @@ class PresentationPlugin(Plugin):
def register_controllers(self, controller):
"""
Register each presentation controller (Impress, PPT etc) and store for later use.
:param controller: controller to register
"""
self.controllers[controller.name] = controller
@ -137,7 +139,8 @@ class PresentationPlugin(Plugin):
self.register_controllers(controller)
return bool(self.controllers)
def about(self):
@staticmethod
def about():
"""
Return information about this plugin.
"""

View File

@ -272,7 +272,7 @@ window.OpenLP = {
value[0] = OpenLP.escapeString(value[0])
}
var txt = "";
if (value[2].length > 0) {
if (value.length > 2) {
txt = value[1] + " ( " + value[2] + " )";
} else {
txt = value[1];

View File

@ -621,7 +621,7 @@ class HttpRouter(RegistryProperties):
event = getattr(self.service_manager, 'servicemanager_%s_item' % action)
if self.request_data:
try:
data = json.loads(self.request_data)['request']['id']
data = int(json.loads(self.request_data)['request']['id'])
except KeyError:
return self.do_http_error()
event.emit(data)

View File

@ -88,7 +88,8 @@ class RemotesPlugin(Plugin):
self.server.stop_server()
self.server = None
def about(self):
@staticmethod
def about():
"""
Information about this plugin
"""

View File

@ -20,7 +20,7 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
from PyQt5 import QtWidgets
from PyQt5 import QtCore, QtWidgets
from openlp.core.lib import translate
from openlp.core.lib.ui import critical_error_message_box
@ -35,7 +35,7 @@ class AuthorsForm(QtWidgets.QDialog, Ui_AuthorsDialog):
"""
Set up the screen and common data
"""
super(AuthorsForm, self).__init__(parent)
super(AuthorsForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
self.setupUi(self)
self.auto_display_name = False
self.first_name_edit.textEdited.connect(self.on_first_name_edited)

View File

@ -37,7 +37,7 @@ class Ui_EditSongDialog(object):
def setupUi(self, edit_song_dialog):
edit_song_dialog.setObjectName('edit_song_dialog')
edit_song_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
edit_song_dialog.resize(650, 400)
edit_song_dialog.resize(900, 600)
edit_song_dialog.setModal(True)
self.dialog_layout = QtWidgets.QVBoxLayout(edit_song_dialog)
self.dialog_layout.setSpacing(8)
@ -173,22 +173,33 @@ class Ui_EditSongDialog(object):
self.topic_remove_layout.addWidget(self.topic_remove_button)
self.topics_layout.addLayout(self.topic_remove_layout)
self.authors_right_layout.addWidget(self.topics_group_box)
self.song_book_group_box = QtWidgets.QGroupBox(self.authors_tab)
self.song_book_group_box.setObjectName('song_book_group_box')
self.song_book_layout = QtWidgets.QFormLayout(self.song_book_group_box)
self.song_book_layout.setObjectName('song_book_layout')
self.song_book_name_label = QtWidgets.QLabel(self.song_book_group_box)
self.song_book_name_label.setObjectName('song_book_name_label')
self.song_book_combo_box = create_combo_box(self.song_book_group_box, 'song_book_combo_box')
self.song_book_name_label.setBuddy(self.song_book_combo_box)
self.song_book_layout.addRow(self.song_book_name_label, self.song_book_combo_box)
self.song_book_number_label = QtWidgets.QLabel(self.song_book_group_box)
self.song_book_number_label.setObjectName('song_book_number_label')
self.song_book_number_edit = QtWidgets.QLineEdit(self.song_book_group_box)
self.song_book_number_edit.setObjectName('song_book_number_edit')
self.song_book_number_label.setBuddy(self.song_book_number_edit)
self.song_book_layout.addRow(self.song_book_number_label, self.song_book_number_edit)
self.authors_right_layout.addWidget(self.song_book_group_box)
self.songbook_group_box = QtWidgets.QGroupBox(self.authors_tab)
self.songbook_group_box.setObjectName('songbook_group_box')
self.songbooks_layout = QtWidgets.QVBoxLayout(self.songbook_group_box)
self.songbooks_layout.setObjectName('songbooks_layout')
self.songbook_add_layout = QtWidgets.QHBoxLayout()
self.songbook_add_layout.setObjectName('songbook_add_layout')
self.songbooks_combo_box = create_combo_box(self.songbook_group_box, 'songbooks_combo_box')
self.songbook_add_layout.addWidget(self.songbooks_combo_box)
self.songbook_entry_edit = QtWidgets.QLineEdit(self.songbook_group_box)
self.songbook_entry_edit.setMaximumWidth(100)
self.songbook_add_layout.addWidget(self.songbook_entry_edit)
self.songbook_add_button = QtWidgets.QPushButton(self.songbook_group_box)
self.songbook_add_button.setObjectName('songbook_add_button')
self.songbook_add_layout.addWidget(self.songbook_add_button)
self.songbooks_layout.addLayout(self.songbook_add_layout)
self.songbooks_list_view = QtWidgets.QListWidget(self.songbook_group_box)
self.songbooks_list_view.setAlternatingRowColors(True)
self.songbooks_list_view.setObjectName('songbooks_list_view')
self.songbooks_layout.addWidget(self.songbooks_list_view)
self.songbook_remove_layout = QtWidgets.QHBoxLayout()
self.songbook_remove_layout.setObjectName('songbook_remove_layout')
self.songbook_remove_layout.addStretch()
self.songbook_remove_button = QtWidgets.QPushButton(self.songbook_group_box)
self.songbook_remove_button.setObjectName('songbook_remove_button')
self.songbook_remove_layout.addWidget(self.songbook_remove_button)
self.songbooks_layout.addLayout(self.songbook_remove_layout)
self.authors_right_layout.addWidget(self.songbook_group_box)
self.authors_tab_layout.addLayout(self.authors_right_layout)
self.song_tab_widget.addTab(self.authors_tab, '')
# theme tab
@ -303,15 +314,15 @@ class Ui_EditSongDialog(object):
self.author_add_button.setText(translate('SongsPlugin.EditSongForm', '&Add to Song'))
self.author_edit_button.setText(translate('SongsPlugin.EditSongForm', '&Edit Author Type'))
self.author_remove_button.setText(translate('SongsPlugin.EditSongForm', '&Remove'))
self.maintenance_button.setText(translate('SongsPlugin.EditSongForm', '&Manage Authors, Topics, Song Books'))
self.topics_group_box.setTitle(SongStrings.Topic)
self.maintenance_button.setText(translate('SongsPlugin.EditSongForm', '&Manage Authors, Topics, Songbooks'))
self.topics_group_box.setTitle(SongStrings.Topics)
self.topic_add_button.setText(translate('SongsPlugin.EditSongForm', 'A&dd to Song'))
self.topic_remove_button.setText(translate('SongsPlugin.EditSongForm', 'R&emove'))
self.song_book_group_box.setTitle(SongStrings.SongBook)
self.song_book_name_label.setText(translate('SongsPlugin.EditSongForm', 'Book:'))
self.song_book_number_label.setText(translate('SongsPlugin.EditSongForm', 'Number:'))
self.songbook_group_box.setTitle(SongStrings.SongBooks)
self.songbook_add_button.setText(translate('SongsPlugin.EditSongForm', 'Add &to Song'))
self.songbook_remove_button.setText(translate('SongsPlugin.EditSongForm', 'Re&move'))
self.song_tab_widget.setTabText(self.song_tab_widget.indexOf(self.authors_tab),
translate('SongsPlugin.EditSongForm', 'Authors, Topics && Song Book'))
translate('SongsPlugin.EditSongForm', 'Authors, Topics && Songbooks'))
self.theme_group_box.setTitle(UiStrings().Theme)
self.theme_add_button.setText(translate('SongsPlugin.EditSongForm', 'New &Theme'))
self.rights_group_box.setTitle(translate('SongsPlugin.EditSongForm', 'Copyright Information'))

View File

@ -35,7 +35,7 @@ from openlp.core.common import Registry, RegistryProperties, AppLocation, UiStri
from openlp.core.lib import FileDialog, PluginStatus, MediaType, create_separated_list
from openlp.core.lib.ui import set_case_insensitive_completer, critical_error_message_box, find_and_set_in_combo_box
from openlp.plugins.songs.lib import VerseType, clean_song
from openlp.plugins.songs.lib.db import Book, Song, Author, AuthorType, Topic, MediaFile
from openlp.plugins.songs.lib.db import Book, Song, Author, AuthorType, Topic, MediaFile, SongBookEntry
from openlp.plugins.songs.lib.ui import SongStrings
from openlp.plugins.songs.lib.openlyricsxml import SongXML
from openlp.plugins.songs.forms.editsongdialog import Ui_EditSongDialog
@ -55,7 +55,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
"""
Constructor
"""
super(EditSongForm, self).__init__(parent)
super(EditSongForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
self.media_item = media_item
self.song = None
# can this be automated?
@ -69,6 +69,9 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
self.topic_add_button.clicked.connect(self.on_topic_add_button_clicked)
self.topic_remove_button.clicked.connect(self.on_topic_remove_button_clicked)
self.topics_list_view.itemClicked.connect(self.on_topic_list_view_clicked)
self.songbook_add_button.clicked.connect(self.on_songbook_add_button_clicked)
self.songbook_remove_button.clicked.connect(self.on_songbook_remove_button_clicked)
self.songbooks_list_view.itemClicked.connect(self.on_songbook_list_view_clicked)
self.copyright_insert_button.clicked.connect(self.on_copyright_insert_button_triggered)
self.verse_add_button.clicked.connect(self.on_verse_add_button_clicked)
self.verse_list_widget.doubleClicked.connect(self.on_verse_edit_button_clicked)
@ -76,7 +79,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
self.verse_edit_all_button.clicked.connect(self.on_verse_edit_all_button_clicked)
self.verse_delete_button.clicked.connect(self.on_verse_delete_button_clicked)
self.verse_list_widget.itemClicked.connect(self.on_verse_list_view_clicked)
self.verse_order_edit.textChanged.connect(self.on_verse_order_text_changed)
self.verse_order_edit.textEdited.connect(self.on_verse_order_text_changed)
self.theme_add_button.clicked.connect(self.theme_manager.on_add_theme)
self.maintenance_button.clicked.connect(self.on_maintenance_button_clicked)
self.from_file_button.clicked.connect(self.on_audio_add_from_file_button_clicked)
@ -125,6 +128,11 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
author_item.setData(QtCore.Qt.UserRole, (author.id, author_type))
self.authors_list_view.addItem(author_item)
def add_songbook_entry_to_list(self, songbook_id, songbook_name, entry):
songbook_entry_item = QtWidgets.QListWidgetItem(SongBookEntry.get_display_name(songbook_name, entry))
songbook_entry_item.setData(QtCore.Qt.UserRole, (songbook_id, entry))
self.songbooks_list_view.addItem(songbook_entry_item)
def _extract_verse_order(self, verse_order):
"""
Split out the verse order
@ -219,17 +227,6 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
result = self._validate_verse_list(self.verse_order_edit.text(), self.verse_list_widget.rowCount())
if not result:
return False
text = self.song_book_combo_box.currentText()
if self.song_book_combo_box.findText(text, QtCore.Qt.MatchExactly) < 0:
if QtWidgets.QMessageBox.question(
self, translate('SongsPlugin.EditSongForm', 'Add Book'),
translate('SongsPlugin.EditSongForm', 'This song book does not exist, do you want to add it?'),
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.Yes:
book = Book.populate(name=text, publisher='')
self.manager.save_object(book)
else:
return False
# Validate tags (lp#1199639)
misplaced_tags = []
verse_tags = []
@ -327,6 +324,9 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
if self.topics_combo_box.hasFocus() and self.topics_combo_box.currentText():
self.on_topic_add_button_clicked()
return
if self.songbooks_combo_box.hasFocus() or self.songbook_entry_edit.hasFocus():
self.on_songbook_add_button_clicked()
return
QtWidgets.QDialog.keyPressEvent(self, event)
def initialise(self):
@ -367,12 +367,12 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
self.topics = []
self._load_objects(Topic, self.topics_combo_box, self.topics)
def load_books(self):
def load_songbooks(self):
"""
Load the song books into the combobox
Load the Songbooks into the combobox
"""
self.books = []
self._load_objects(Book, self.song_book_combo_box, self.books)
self.songbooks = []
self._load_objects(Book, self.songbooks_combo_box, self.songbooks)
def load_themes(self, theme_list):
"""
@ -413,12 +413,13 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
self.verse_list_widget.setRowCount(0)
self.authors_list_view.clear()
self.topics_list_view.clear()
self.songbooks_list_view.clear()
self.songbook_entry_edit.clear()
self.audio_list_widget.clear()
self.title_edit.setFocus()
self.song_book_number_edit.clear()
self.load_authors()
self.load_topics()
self.load_books()
self.load_songbooks()
self.load_media_files()
self.theme_combo_box.setEditText('')
self.theme_combo_box.setCurrentIndex(0)
@ -437,18 +438,11 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
self.song_tab_widget.setCurrentIndex(0)
self.load_authors()
self.load_topics()
self.load_books()
self.load_songbooks()
self.load_media_files()
self.song = self.manager.get_object(Song, song_id)
self.title_edit.setText(self.song.title)
self.alternative_edit.setText(
self.song.alternate_title if self.song.alternate_title else '')
if self.song.song_book_id != 0:
book_name = self.manager.get_object(Book, self.song.song_book_id)
find_and_set_in_combo_box(self.song_book_combo_box, str(book_name.name))
else:
self.song_book_combo_box.setEditText('')
self.song_book_combo_box.setCurrentIndex(0)
self.alternative_edit.setText(self.song.alternate_title if self.song.alternate_title else '')
if self.song.theme_name:
find_and_set_in_combo_box(self.theme_combo_box, str(self.song.theme_name))
else:
@ -458,7 +452,6 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
self.copyright_edit.setText(self.song.copyright if self.song.copyright else '')
self.comments_edit.setPlainText(self.song.comments if self.song.comments else '')
self.ccli_number_edit.setText(self.song.ccli_number if self.song.ccli_number else '')
self.song_book_number_edit.setText(self.song.song_number if self.song.song_number else '')
# lazy xml migration for now
self.verse_list_widget.clear()
self.verse_list_widget.setRowCount(0)
@ -520,6 +513,11 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
topic_name = QtWidgets.QListWidgetItem(str(topic.name))
topic_name.setData(QtCore.Qt.UserRole, topic.id)
self.topics_list_view.addItem(topic_name)
self.songbooks_list_view.clear()
self.songbook_entry_edit.clear()
for songbook_entry in self.song.songbook_entries:
self.add_songbook_entry_to_list(songbook_entry.songbook.id, songbook_entry.songbook.name,
songbook_entry.entry)
self.audio_list_widget.clear()
for media in self.song.media_files:
media_file = QtWidgets.QListWidgetItem(os.path.split(media.file_name)[1])
@ -678,6 +676,48 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
row = self.topics_list_view.row(item)
self.topics_list_view.takeItem(row)
def on_songbook_add_button_clicked(self):
item = int(self.songbooks_combo_box.currentIndex())
text = self.songbooks_combo_box.currentText()
if item == 0 and text:
if QtWidgets.QMessageBox.question(
self, translate('SongsPlugin.EditSongForm', 'Add Songbook'),
translate('SongsPlugin.EditSongForm', 'This Songbook does not exist, do you want to add it?'),
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.Yes:
songbook = Book.populate(name=text)
self.manager.save_object(songbook)
self.add_songbook_entry_to_list(songbook.id, songbook.name, self.songbook_entry_edit.text())
self.load_songbooks()
self.songbooks_combo_box.setCurrentIndex(0)
self.songbook_entry_edit.clear()
else:
return
elif item > 0:
item_id = (self.songbooks_combo_box.itemData(item))
songbook = self.manager.get_object(Book, item_id)
if self.songbooks_list_view.findItems(str(songbook.name), QtCore.Qt.MatchExactly):
critical_error_message_box(
message=translate('SongsPlugin.EditSongForm', 'This Songbook is already in the list.'))
else:
self.add_songbook_entry_to_list(songbook.id, songbook.name, self.songbook_entry_edit.text())
self.songbooks_combo_box.setCurrentIndex(0)
self.songbook_entry_edit.clear()
else:
QtWidgets.QMessageBox.warning(
self, UiStrings().NISs,
translate('SongsPlugin.EditSongForm', 'You have not selected a valid Songbook. Either select a '
'Songbook from the list, or type in a new Songbook and click the "Add to Song" '
'button to add the new Songbook.'))
def on_songbook_list_view_clicked(self):
self.songbook_remove_button.setEnabled(True)
def on_songbook_remove_button_clicked(self):
self.songbook_remove_button.setEnabled(False)
row = self.songbooks_list_view.row(self.songbooks_list_view.currentItem())
self.songbooks_list_view.takeItem(row)
def on_verse_list_view_clicked(self):
self.verse_edit_button.setEnabled(True)
self.verse_delete_button.setEnabled(True)
@ -803,6 +843,10 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
:param text: The text of the verse order edit (ignored).
"""
# First make sure that all letters entered in the verse order field are uppercase
pos = self.verse_order_edit.cursorPosition()
self.verse_order_edit.setText(text.upper())
self.verse_order_edit.setCursorPosition(pos)
# Extract all verses which were used in the order.
verses_in_order = self._extract_verse_order(self.verse_order_edit.text())
# Find the verses which were not used in the order.
@ -838,17 +882,10 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
"""
Maintenance button pressed
"""
temp_song_book = None
item = int(self.song_book_combo_box.currentIndex())
text = self.song_book_combo_box.currentText()
if item == 0 and text:
temp_song_book = text
self.media_item.song_maintenance_form.exec(True)
self.load_authors()
self.load_books()
self.load_songbooks()
self.load_topics()
if temp_song_book:
self.song_book_combo_box.setEditText(temp_song_book)
def on_preview(self, button):
"""
@ -928,7 +965,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
log.debug('SongEditForm.clearCaches')
self.authors = []
self.themes = []
self.books = []
self.songbooks = []
self.topics = []
def reject(self):
@ -977,12 +1014,6 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
order.append('%s%s' % (verse_tag, verse_num))
self.song.verse_order = ' '.join(order)
self.song.ccli_number = self.ccli_number_edit.text()
self.song.song_number = self.song_book_number_edit.text()
book_name = self.song_book_combo_box.currentText()
if book_name:
self.song.book = self.manager.get_object_filtered(Book, Book.name == book_name)
else:
self.song.book = None
theme_name = self.theme_combo_box.currentText()
if theme_name:
self.song.theme_name = theme_name
@ -1001,6 +1032,13 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
topic = self.manager.get_object(Topic, topic_id)
if topic is not None:
self.song.topics.append(topic)
self.song.songbook_entries = []
for row in range(self.songbooks_list_view.count()):
item = self.songbooks_list_view.item(row)
songbook_id = item.data(QtCore.Qt.UserRole)[0]
songbook = self.manager.get_object(Book, songbook_id)
entry = item.data(QtCore.Qt.UserRole)[1]
self.song.add_songbook_entry(songbook, entry)
# Save the song here because we need a valid id for the audio files.
clean_song(self.manager, self.song)
self.manager.save_object(self.song)

View File

@ -23,7 +23,7 @@
import re
import logging
from PyQt5 import QtGui, QtWidgets
from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.plugins.songs.lib import VerseType
from .editversedialog import Ui_EditVerseDialog
@ -41,7 +41,7 @@ class EditVerseForm(QtWidgets.QDialog, Ui_EditVerseDialog):
"""
Constructor
"""
super(EditVerseForm, self).__init__(parent)
super(EditVerseForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
self.setupUi(self)
self.has_single_verse = False
self.insert_button.clicked.connect(self.on_insert_button_clicked)

View File

@ -37,7 +37,7 @@ class MediaFilesForm(QtWidgets.QDialog, Ui_MediaFilesDialog):
log.info('%s MediaFilesForm loaded', __name__)
def __init__(self, parent):
super(MediaFilesForm, self).__init__()
super(MediaFilesForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
self.setupUi(self)
def populate_files(self, files):

View File

@ -28,7 +28,7 @@ from openlp.core.lib.ui import create_button_box
class Ui_SongBookDialog(object):
"""
The user interface for the song book dialog.
The user interface for the Songbook dialog.
"""
def setupUi(self, song_book_dialog):
"""
@ -63,6 +63,6 @@ class Ui_SongBookDialog(object):
"""
Translate the UI on the fly.
"""
song_book_dialog.setWindowTitle(translate('SongsPlugin.SongBookForm', 'Song Book Maintenance'))
song_book_dialog.setWindowTitle(translate('SongsPlugin.SongBookForm', 'Songbook Maintenance'))
self.name_label.setText(translate('SongsPlugin.SongBookForm', '&Name:'))
self.publisher_label.setText(translate('SongsPlugin.SongBookForm', '&Publisher:'))

View File

@ -23,7 +23,7 @@
This module contains the song book form
"""
from PyQt5 import QtWidgets
from PyQt5 import QtCore, QtWidgets
from openlp.core.lib import translate
from openlp.core.lib.ui import critical_error_message_box
@ -38,7 +38,7 @@ class SongBookForm(QtWidgets.QDialog, Ui_SongBookDialog):
"""
Constructor
"""
super(SongBookForm, self).__init__(parent)
super(SongBookForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
self.setupUi(self)
def exec(self, clear=True):

View File

@ -72,7 +72,7 @@ class SongExportForm(OpenLPWizard):
"""
Song wizard specific signals.
"""
self.available_list_widget.itemActivated.connect(self.on_item_activated)
self.available_list_widget.itemActivated.connect(on_item_activated)
self.search_line_edit.textEdited.connect(self.on_search_line_edit_changed)
self.uncheck_button.clicked.connect(self.on_uncheck_button_clicked)
self.check_button.clicked.connect(self.on_check_button_clicked)
@ -172,7 +172,7 @@ class SongExportForm(OpenLPWizard):
return True
elif self.currentPage() == self.available_songs_page:
items = [
item for item in self._find_list_widget_items(self.available_list_widget) if item.checkState()
item for item in find_list_widget_items(self.available_list_widget) if item.checkState()
]
if not items:
critical_error_message_box(
@ -241,7 +241,7 @@ class SongExportForm(OpenLPWizard):
"""
songs = [
song.data(QtCore.Qt.UserRole)
for song in self._find_list_widget_items(self.selected_list_widget)
for song in find_list_widget_items(self.selected_list_widget)
]
exporter = OpenLyricsExport(self, songs, self.directory_line_edit.text())
try:
@ -255,28 +255,6 @@ class SongExportForm(OpenLPWizard):
self.progress_label.setText(translate('SongsPlugin.SongExportForm', 'Your song export failed because this '
'error occurred: %s') % ose.strerror)
def _find_list_widget_items(self, list_widget, text=''):
"""
Returns a list of *QListWidgetItem*s of the ``list_widget``. Note, that hidden items are included.
:param list_widget: The widget to get all items from. (QListWidget)
:param text: The text to search for. (unicode string)
"""
return [
item for item in list_widget.findItems(text, QtCore.Qt.MatchContains)
]
def on_item_activated(self, item):
"""
Called, when an item in the *available_list_widget* has been triggered.
The item is check if it was not checked, whereas it is unchecked when it
was checked.
:param item: The *QListWidgetItem* which was triggered.
"""
item.setCheckState(
QtCore.Qt.Unchecked if item.checkState() else QtCore.Qt.Checked)
def on_search_line_edit_changed(self, text):
"""
The *search_line_edit*'s text has been changed. Update the list of
@ -286,9 +264,9 @@ class SongExportForm(OpenLPWizard):
:param text: The text of the *search_line_edit*.
"""
search_result = [
song for song in self._find_list_widget_items(self.available_list_widget, text)
song for song in find_list_widget_items(self.available_list_widget, text)
]
for item in self._find_list_widget_items(self.available_list_widget):
for item in find_list_widget_items(self.available_list_widget):
item.setHidden(item not in search_result)
def on_uncheck_button_clicked(self):
@ -317,3 +295,26 @@ class SongExportForm(OpenLPWizard):
self.get_folder(
translate('SongsPlugin.ExportWizardForm', 'Select Destination Folder'),
self.directory_line_edit, 'last directory export')
def find_list_widget_items(list_widget, text=''):
"""
Returns a list of *QListWidgetItem*s of the ``list_widget``. Note, that hidden items are included.
:param list_widget: The widget to get all items from. (QListWidget)
:param text: The text to search for. (unicode string)
"""
return [
item for item in list_widget.findItems(text, QtCore.Qt.MatchContains)
]
def on_item_activated(item):
"""
Called, when an item in the *available_list_widget* has been triggered.
The item is check if it was not checked, whereas it is unchecked when it
was checked.
:param item: The *QListWidgetItem* which was triggered.
"""
item.setCheckState(QtCore.Qt.Unchecked if item.checkState() else QtCore.Qt.Checked)

View File

@ -44,7 +44,7 @@ class SongMaintenanceForm(QtWidgets.QDialog, Ui_SongMaintenanceDialog, RegistryP
"""
Constructor
"""
super(SongMaintenanceForm, self).__init__(parent)
super(SongMaintenanceForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
self.setupUi(self)
self.manager = manager
self.author_form = AuthorsForm(self)

View File

@ -114,12 +114,19 @@ class Ui_SongSelectDialog(object):
self.search_button.setObjectName('search_button')
self.search_input_layout.addWidget(self.search_button)
self.search_layout.addLayout(self.search_input_layout)
self.search_progress_layout = QtWidgets.QHBoxLayout()
self.search_progress_layout.setSpacing(8)
self.search_progress_layout.setObjectName('search_progress_layout')
self.search_progress_bar = QtWidgets.QProgressBar(self.search_page)
self.search_progress_bar.setMinimum(0)
self.search_progress_bar.setMaximum(3)
self.search_progress_bar.setValue(0)
self.search_progress_bar.setVisible(False)
self.search_layout.addWidget(self.search_progress_bar)
self.search_progress_layout.addWidget(self.search_progress_bar)
self.stop_button = QtWidgets.QPushButton(self.search_page)
self.stop_button.setIcon(build_icon(':/songs/song_search_stop.png'))
self.stop_button.setObjectName('stop_button')
self.search_progress_layout.addWidget(self.stop_button)
self.search_layout.addLayout(self.search_progress_layout)
self.search_results_widget = QtWidgets.QListWidget(self.search_page)
self.search_results_widget.setProperty("showDropIndicator", False)
self.search_results_widget.setAlternatingRowColors(True)
@ -234,6 +241,7 @@ class Ui_SongSelectDialog(object):
self.login_button.setText(translate('SongsPlugin.SongSelectForm', 'Login'))
self.search_label.setText(translate('SongsPlugin.SongSelectForm', 'Search Text:'))
self.search_button.setText(translate('SongsPlugin.SongSelectForm', 'Search'))
self.stop_button.setText(translate('SongsPlugin.SongSelectForm', 'Stop'))
self.result_count_label.setText(translate('SongsPlugin.SongSelectForm', 'Found %s song(s)') % 0)
self.logout_button.setText(translate('SongsPlugin.SongSelectForm', 'Logout'))
self.view_button.setText(translate('SongsPlugin.SongSelectForm', 'View'))

View File

@ -81,7 +81,7 @@ class SongSelectForm(QtWidgets.QDialog, Ui_SongSelectDialog):
"""
def __init__(self, parent=None, plugin=None, db_manager=None):
QtWidgets.QDialog.__init__(self, parent)
QtWidgets.QDialog.__init__(self, parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
self.plugin = plugin
self.db_manager = db_manager
self.setup_ui(self)
@ -94,11 +94,13 @@ class SongSelectForm(QtWidgets.QDialog, Ui_SongSelectDialog):
self.worker = None
self.song_count = 0
self.song = None
self.set_progress_visible(False)
self.song_select_importer = SongSelectImport(self.db_manager)
self.save_password_checkbox.toggled.connect(self.on_save_password_checkbox_toggled)
self.login_button.clicked.connect(self.on_login_button_clicked)
self.search_button.clicked.connect(self.on_search_button_clicked)
self.search_combobox.returnPressed.connect(self.on_search_button_clicked)
self.stop_button.clicked.connect(self.on_stop_button_clicked)
self.logout_button.clicked.connect(self.done)
self.search_results_widget.itemDoubleClicked.connect(self.on_search_results_widget_double_clicked)
self.search_results_widget.itemSelectionChanged.connect(self.on_search_results_widget_selection_changed)
@ -153,18 +155,30 @@ class SongSelectForm(QtWidgets.QDialog, Ui_SongSelectDialog):
return QtWidgets.QDialog.done(self, r)
def _update_login_progress(self):
"""
Update the progress bar as the user logs in.
"""
self.login_progress_bar.setValue(self.login_progress_bar.value() + 1)
self.application.process_events()
def _update_song_progress(self):
"""
Update the progress bar as the song is being downloaded.
"""
self.song_progress_bar.setValue(self.song_progress_bar.value() + 1)
self.application.process_events()
def _view_song(self, current_item):
"""
Load a song into the song view.
"""
if not current_item:
return
else:
current_item = current_item.data(QtCore.Qt.UserRole)
# Stop the current search, if it's running
self.song_select_importer.stop()
# Clear up the UI
self.song_progress_bar.setVisible(True)
self.import_button.setEnabled(False)
self.back_button.setEnabled(False)
@ -288,7 +302,7 @@ class SongSelectForm(QtWidgets.QDialog, Ui_SongSelectDialog):
self.search_progress_bar.setMinimum(0)
self.search_progress_bar.setMaximum(0)
self.search_progress_bar.setValue(0)
self.search_progress_bar.setVisible(True)
self.set_progress_visible(True)
self.search_results_widget.clear()
self.result_count_label.setText(translate('SongsPlugin.SongSelectForm', 'Found %s song(s)') % self.song_count)
self.application.process_events()
@ -308,6 +322,12 @@ class SongSelectForm(QtWidgets.QDialog, Ui_SongSelectDialog):
self.thread.finished.connect(self.thread.deleteLater)
self.thread.start()
def on_stop_button_clicked(self):
"""
Stop the search when the stop button is clicked.
"""
self.song_select_importer.stop()
def on_search_show_info(self, title, message):
"""
Show an informational message from the search thread
@ -332,7 +352,7 @@ class SongSelectForm(QtWidgets.QDialog, Ui_SongSelectDialog):
Slot which is called when the search is completed.
"""
self.application.process_events()
self.search_progress_bar.setVisible(False)
self.set_progress_visible(False)
self.search_button.setEnabled(True)
self.application.process_events()
@ -380,6 +400,13 @@ class SongSelectForm(QtWidgets.QDialog, Ui_SongSelectDialog):
self.application.process_events()
self.done(QtWidgets.QDialog.Accepted)
def set_progress_visible(self, is_visible):
"""
Show or hide the search progress, including the stop button.
"""
self.search_progress_bar.setVisible(is_visible)
self.stop_button.setVisible(is_visible)
@property
def application(self):
"""

View File

@ -23,7 +23,7 @@
This module contains the topic edit form.
"""
from PyQt5 import QtWidgets
from PyQt5 import QtCore, QtWidgets
from openlp.core.lib import translate
from openlp.core.lib.ui import critical_error_message_box
@ -38,7 +38,7 @@ class TopicsForm(QtWidgets.QDialog, Ui_TopicsDialog):
"""
Constructor
"""
super(TopicsForm, self).__init__(parent)
super(TopicsForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
self.setupUi(self)
def exec(self, clear=True):

View File

@ -160,6 +160,36 @@ class Song(BaseModel):
self.authors_songs.remove(author_song)
return
def add_songbook_entry(self, songbook, entry):
"""
Add a Songbook Entry to the song if it not yet exists
:param songbook_name: Name of the Songbook.
:param entry: Entry in the Songbook (usually a number)
"""
for songbook_entry in self.songbook_entries:
if songbook_entry.songbook.name == songbook.name and songbook_entry.entry == entry:
return
new_songbook_entry = SongBookEntry()
new_songbook_entry.songbook = songbook
new_songbook_entry.entry = entry
self.songbook_entries.append(new_songbook_entry)
class SongBookEntry(BaseModel):
"""
SongBookEntry model
"""
def __repr__(self):
return SongBookEntry.get_display_name(self.songbook.name, self.entry)
@staticmethod
def get_display_name(songbook_name, entry):
if entry:
return "%s #%s" % (songbook_name, entry)
return songbook_name
class Topic(BaseModel):
"""
@ -182,6 +212,7 @@ def init_schema(url):
* media_files_songs
* song_books
* songs
* songs_songbooks
* songs_topics
* topics
@ -222,7 +253,6 @@ def init_schema(url):
The *songs* table has the following columns:
* id
* song_book_id
* title
* alternate_title
* lyrics
@ -230,11 +260,17 @@ def init_schema(url):
* copyright
* comments
* ccli_number
* song_number
* theme_name
* search_title
* search_lyrics
**songs_songsbooks Table**
This is a mapping table between the *songs* and the *song_books* tables. It has the following columns:
* songbook_id
* song_id
* entry # The song number, like 120 or 550A
**songs_topics Table**
This is a bridging table between the *songs* and *topics* tables, which
serves to create a many-to-many relationship between the two tables. It
@ -284,7 +320,6 @@ def init_schema(url):
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('title', types.Unicode(255), nullable=False),
Column('alternate_title', types.Unicode(255)),
Column('lyrics', types.UnicodeText, nullable=False),
@ -292,7 +327,6 @@ def init_schema(url):
Column('copyright', types.Unicode(255)),
Column('comments', types.UnicodeText),
Column('ccli_number', types.Unicode(64)),
Column('song_number', types.Unicode(64)),
Column('theme_name', types.Unicode(128)),
Column('search_title', types.Unicode(255), index=True, nullable=False),
Column('search_lyrics', types.UnicodeText, nullable=False),
@ -316,6 +350,14 @@ def init_schema(url):
Column('author_type', types.Unicode(255), primary_key=True, nullable=False, server_default=text('""'))
)
# Definition of the "songs_songbooks" table
songs_songbooks_table = Table(
'songs_songbooks', metadata,
Column('songbook_id', types.Integer(), ForeignKey('song_books.id'), primary_key=True),
Column('song_id', types.Integer(), ForeignKey('songs.id'), primary_key=True),
Column('entry', types.Unicode(255), primary_key=True, nullable=False)
)
# Definition of the "songs_topics" table
songs_topics_table = Table(
'songs_topics', metadata,
@ -329,6 +371,9 @@ def init_schema(url):
mapper(AuthorSong, authors_songs_table, properties={
'author': relation(Author)
})
mapper(SongBookEntry, songs_songbooks_table, properties={
'songbook': relation(Book)
})
mapper(Book, song_books_table)
mapper(MediaFile, media_files_table)
mapper(Song, songs_table, properties={
@ -337,8 +382,8 @@ def init_schema(url):
'authors_songs': relation(AuthorSong, cascade="all, delete-orphan"),
# Use lazy='joined' to always load authors when the song is fetched from the database (bug 1366198)
'authors': relation(Author, secondary=authors_songs_table, viewonly=True, lazy='joined'),
'book': relation(Book, backref='songs'),
'media_files': relation(MediaFile, backref='songs', order_by=media_files_table.c.weight),
'songbook_entries': relation(SongBookEntry, backref='song', cascade="all, delete-orphan"),
'topics': relation(Topic, backref='songs', secondary=songs_topics_table)
})
mapper(Topic, topics_table)

View File

@ -153,23 +153,23 @@ class SongFormat(object):
EasyWorshipDB = 6
EasyWorshipService = 7
FoilPresenter = 8
MediaShout = 9
OpenSong = 10
PowerPraise = 11
PowerSong = 12
PresentationManager = 13
ProPresenter = 14
SongBeamer = 15
SongPro = 16
SongShowPlus = 17
SongsOfFellowship = 18
SundayPlus = 19
WordsOfWorship = 20
WorshipAssistant = 21
WorshipCenterPro = 22
ZionWorx = 23
Lyrix = 24
VideoPsalm = 25
Lyrix = 9
MediaShout = 10
OpenSong = 11
PowerPraise = 12
PowerSong = 13
PresentationManager = 14
ProPresenter = 15
SongBeamer = 16
SongPro = 17
SongShowPlus = 18
SongsOfFellowship = 19
SundayPlus = 20
VideoPsalm = 21
WordsOfWorship = 22
WorshipAssistant = 23
WorshipCenterPro = 24
ZionWorx = 25
# Set optional attribute defaults
__defaults__ = {
@ -390,7 +390,7 @@ class SongFormat(object):
"""
Return a list of the supported song formats.
"""
return [
return sorted([
SongFormat.OpenLyrics,
SongFormat.OpenLP2,
SongFormat.Generic,
@ -400,6 +400,7 @@ class SongFormat(object):
SongFormat.EasyWorshipDB,
SongFormat.EasyWorshipService,
SongFormat.FoilPresenter,
SongFormat.Lyrix,
SongFormat.MediaShout,
SongFormat.OpenSong,
SongFormat.PowerPraise,
@ -411,13 +412,12 @@ class SongFormat(object):
SongFormat.SongShowPlus,
SongFormat.SongsOfFellowship,
SongFormat.SundayPlus,
SongFormat.VideoPsalm,
SongFormat.WordsOfWorship,
SongFormat.WorshipAssistant,
SongFormat.WorshipCenterPro,
SongFormat.ZionWorx,
SongFormat.Lyrix,
SongFormat.VideoPsalm
]
])
@staticmethod
def get(song_format, *attributes):

View File

@ -65,7 +65,8 @@ class EasySlidesImport(SongImport):
self._add_unicode_attribute('song_number', song.SongNumber)
if self.song_number == '0':
self.song_number = ''
self._add_authors(song)
if hasattr(song, 'Writer'):
self._add_authors(song.Writer)
if hasattr(song, 'Copyright'):
self._add_copyright(song.Copyright)
if hasattr(song, 'LicenceAdmin1'):
@ -102,15 +103,12 @@ class EasySlidesImport(SongImport):
if mandatory:
self._success = False
def _add_authors(self, song):
def _add_authors(self, writer):
try:
authors = str(song.Writer).split(',')
self.authors = [author.strip() for author in authors if author.strip()]
except UnicodeDecodeError:
self.parse_author(str(writer))
except UnicodeDecodeError as e:
log.exception('Unicode decode error while decoding Writer')
self._success = False
except AttributeError:
pass
def _add_copyright(self, element):
"""
@ -234,11 +232,10 @@ class EasySlidesImport(SongImport):
for [reg, vt, vn, inst] in our_verse_order:
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])
self.add_verse(lines, versetag)
SeqTypes = {
'p': 'P1',
'q': 'P2',

View File

@ -289,40 +289,45 @@ class EasyWorshipSongImport(SongImport):
for i in range(rec_count):
if self.stop_import_flag:
break
raw_record = db_file.read(record_size)
self.fields = self.record_structure.unpack(raw_record)
self.set_defaults()
self.title = self.get_field(fi_title).decode('unicode-escape')
# Get remaining fields.
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)
if copy:
self.copyright = copy.decode('unicode-escape')
if admin:
try:
raw_record = db_file.read(record_size)
self.fields = self.record_structure.unpack(raw_record)
self.set_defaults()
self.title = self.get_field(fi_title).decode(self.encoding)
# Get remaining fields.
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)
if copy:
self.copyright += ', '
self.copyright += translate('SongsPlugin.EasyWorshipSongImport',
'Administered by %s') % admin.decode('unicode-escape')
if ccli:
self.ccli_number = ccli.decode('unicode-escape')
if authors:
authors = authors.decode('unicode-escape')
else:
authors = ''
# Set the SongImport object members.
self.set_song_import_object(authors, words)
if self.stop_import_flag:
break
if self.entry_error_log:
self.copyright = copy.decode(self.encoding)
if admin:
if copy:
self.copyright += ', '
self.copyright += translate('SongsPlugin.EasyWorshipSongImport',
'Administered by %s') % admin.decode(self.encoding)
if ccli:
self.ccli_number = ccli.decode(self.encoding)
if authors:
authors = authors.decode(self.encoding)
else:
authors = ''
# Set the SongImport object members.
self.set_song_import_object(authors, words)
if self.stop_import_flag:
break
if self.entry_error_log:
self.log_error(self.import_source,
translate('SongsPlugin.EasyWorshipSongImport', '"%s" could not be imported. %s')
% (self.title, self.entry_error_log))
self.entry_error_log = ''
elif not self.finish():
self.log_error(self.import_source)
except Exception as e:
self.log_error(self.import_source,
translate('SongsPlugin.EasyWorshipSongImport', '"%s" could not be imported. %s')
% (self.title, self.entry_error_log))
self.entry_error_log = ''
elif not self.finish():
self.log_error(self.import_source)
% (self.title, e))
db_file.close()
self.memo_file.close()
@ -368,7 +373,7 @@ class EasyWorshipSongImport(SongImport):
first_line_is_tag = False
# EW tags: verse, chorus, pre-chorus, bridge, tag,
# intro, ending, slide
for tag in VerseType.tags + ['tag', 'slide']:
for tag in VerseType.names + ['tag', 'slide', 'end']:
tag = tag.lower()
ew_tag = verse_split[0].strip().lower()
if ew_tag.startswith(tag):
@ -390,6 +395,9 @@ class EasyWorshipSongImport(SongImport):
if not number_found:
verse_type += '1'
break
# If the verse only consist of the tag-line, add an empty line to create an empty slide
if first_line_is_tag and len(verse_split) == 1:
verse_split.append("")
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',
@ -497,7 +505,7 @@ class EasyWorshipSongImport(SongImport):
bytes = self.get_bytes(pos, length)
mask = '<' + str(length) + 's'
byte_str, = struct.unpack(mask, bytes)
return byte_str.decode('unicode-escape').replace('\0', '').strip()
return byte_str.decode(self.encoding).replace('\0', '').strip()
def get_i16(self, pos):
"""

View File

@ -234,16 +234,6 @@ class FoilPresenter(object):
clean_song(self.manager, song)
self.manager.save_object(song)
def _child(self, element):
"""
This returns the text of an element as unicode string.
:param element: The element
"""
if element is not None:
return str(element)
return ''
def _process_authors(self, foilpresenterfolie, song):
"""
Adds the authors specified in the XML to the song.
@ -253,7 +243,7 @@ class FoilPresenter(object):
"""
authors = []
try:
copyright = self._child(foilpresenterfolie.copyright.text_)
copyright = to_str(foilpresenterfolie.copyright.text_)
except AttributeError:
copyright = None
if copyright:
@ -346,7 +336,7 @@ class FoilPresenter(object):
:param song: The song object.
"""
try:
song.ccli_number = self._child(foilpresenterfolie.ccliid)
song.ccli_number = to_str(foilpresenterfolie.ccliid)
except AttributeError:
song.ccli_number = ''
@ -358,7 +348,7 @@ class FoilPresenter(object):
:param song: The song object.
"""
try:
song.comments = self._child(foilpresenterfolie.notiz)
song.comments = to_str(foilpresenterfolie.notiz)
except AttributeError:
song.comments = ''
@ -370,7 +360,7 @@ class FoilPresenter(object):
:param song: The song object.
"""
try:
song.copyright = self._child(foilpresenterfolie.copyright.text_)
song.copyright = to_str(foilpresenterfolie.copyright.text_)
except AttributeError:
song.copyright = ''
@ -396,19 +386,19 @@ class FoilPresenter(object):
VerseType.tags[VerseType.PreChorus]: 1
}
if not hasattr(foilpresenterfolie.strophen, 'strophe'):
self.importer.log_error(self._child(foilpresenterfolie.titel),
self.importer.log_error(to_str(foilpresenterfolie.titel),
str(translate('SongsPlugin.FoilPresenterSongImport',
'Invalid Foilpresenter song file. No verses found.')))
self.save_song = False
return
for strophe in foilpresenterfolie.strophen.strophe:
text = self._child(strophe.text_) if hasattr(strophe, 'text_') else ''
verse_name = self._child(strophe.key)
text = to_str(strophe.text_) if hasattr(strophe, 'text_') else ''
verse_name = to_str(strophe.key)
children = strophe.getchildren()
sortnr = False
for child in children:
if child.tag == 'sortnr':
verse_sortnr = self._child(strophe.sortnr)
verse_sortnr = to_str(strophe.sortnr)
sortnr = True
# In older Version there is no sortnr, but we need one
if not sortnr:
@ -484,7 +474,7 @@ class FoilPresenter(object):
song.song_number = ''
try:
for bucheintrag in foilpresenterfolie.buch.bucheintrag:
book_name = self._child(bucheintrag.name)
book_name = to_str(bucheintrag.name)
if book_name:
book = self.manager.get_object_filtered(Book, Book.name == book_name)
if book is None:
@ -493,8 +483,8 @@ class FoilPresenter(object):
self.manager.save_object(book)
song.song_book_id = book.id
try:
if self._child(bucheintrag.nummer):
song.song_number = self._child(bucheintrag.nummer)
if to_str(bucheintrag.nummer):
song.song_number = to_str(bucheintrag.nummer)
except AttributeError:
pass
# We only support one song book, so take the first one.
@ -512,13 +502,13 @@ class FoilPresenter(object):
try:
for title_string in foilpresenterfolie.titel.titelstring:
if not song.title:
song.title = self._child(title_string)
song.title = to_str(title_string)
song.alternate_title = ''
else:
song.alternate_title = self._child(title_string)
song.alternate_title = to_str(title_string)
except AttributeError:
# Use first line of first verse
first_line = self._child(foilpresenterfolie.strophen.strophe.text_)
first_line = to_str(foilpresenterfolie.strophen.strophe.text_)
song.title = first_line.split('\n')[0]
def _process_topics(self, foilpresenterfolie, song):
@ -530,7 +520,7 @@ class FoilPresenter(object):
"""
try:
for name in foilpresenterfolie.kategorien.name:
topic_text = self._child(name)
topic_text = to_str(name)
if topic_text:
topic = self.manager.get_object_filtered(Topic, Topic.name == topic_text)
if topic is None:
@ -540,3 +530,14 @@ class FoilPresenter(object):
song.topics.append(topic)
except AttributeError:
pass
def to_str(element):
"""
This returns the text of an element as unicode string.
:param element: The element
"""
if element is not None:
return str(element)
return ''

View File

@ -157,6 +157,7 @@ class OpenSongImport(SongImport):
if isinstance(fn_or_string, str):
if attr in ['ccli']:
if ustring:
ustring = ''.join(re.findall('\d+', ustring))
setattr(self, fn_or_string, int(ustring))
else:
setattr(self, fn_or_string, None)

View File

@ -36,28 +36,28 @@ log = logging.getLogger(__name__)
class SongBeamerTypes(object):
MarkTypes = {
'Refrain': VerseType.tags[VerseType.Chorus],
'Chorus': VerseType.tags[VerseType.Chorus],
'Vers': VerseType.tags[VerseType.Verse],
'Verse': VerseType.tags[VerseType.Verse],
'Strophe': VerseType.tags[VerseType.Verse],
'Intro': VerseType.tags[VerseType.Intro],
'Coda': VerseType.tags[VerseType.Ending],
'Ending': VerseType.tags[VerseType.Ending],
'Bridge': VerseType.tags[VerseType.Bridge],
'Interlude': VerseType.tags[VerseType.Bridge],
'Zwischenspiel': VerseType.tags[VerseType.Bridge],
'Pre-Chorus': VerseType.tags[VerseType.PreChorus],
'Pre-Refrain': VerseType.tags[VerseType.PreChorus],
'Misc': VerseType.tags[VerseType.Other],
'Pre-Bridge': VerseType.tags[VerseType.Other],
'Pre-Coda': VerseType.tags[VerseType.Other],
'Part': VerseType.tags[VerseType.Other],
'Teil': VerseType.tags[VerseType.Other],
'Unbekannt': VerseType.tags[VerseType.Other],
'Unknown': VerseType.tags[VerseType.Other],
'Unbenannt': VerseType.tags[VerseType.Other],
'$$M=': VerseType.tags[VerseType.Other]
'refrain': VerseType.tags[VerseType.Chorus],
'chorus': VerseType.tags[VerseType.Chorus],
'vers': VerseType.tags[VerseType.Verse],
'verse': VerseType.tags[VerseType.Verse],
'strophe': VerseType.tags[VerseType.Verse],
'intro': VerseType.tags[VerseType.Intro],
'coda': VerseType.tags[VerseType.Ending],
'ending': VerseType.tags[VerseType.Ending],
'bridge': VerseType.tags[VerseType.Bridge],
'interlude': VerseType.tags[VerseType.Bridge],
'zwischenspiel': VerseType.tags[VerseType.Bridge],
'pre-chorus': VerseType.tags[VerseType.PreChorus],
'pre-refrain': VerseType.tags[VerseType.PreChorus],
'misc': VerseType.tags[VerseType.Other],
'pre-bridge': VerseType.tags[VerseType.Other],
'pre-coda': VerseType.tags[VerseType.Other],
'part': VerseType.tags[VerseType.Other],
'teil': VerseType.tags[VerseType.Other],
'unbekannt': VerseType.tags[VerseType.Other],
'unknown': VerseType.tags[VerseType.Other],
'unbenannt': VerseType.tags[VerseType.Other],
'$$m=': VerseType.tags[VerseType.Other]
}
@ -242,7 +242,7 @@ class SongBeamerImport(SongImport):
elif tag_val[0] == '#TextAlign':
pass
elif tag_val[0] == '#Title':
self.title = str(tag_val[1])
self.title = str(tag_val[1]).strip()
elif tag_val[0] == '#TitleAlign':
pass
elif tag_val[0] == '#TitleFontSize':
@ -267,20 +267,20 @@ class SongBeamerImport(SongImport):
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
Check and add the verse's MarkType. Returns ``True`` if the given line contains a correct verse mark otherwise
``False``.
:param line: The line to check for marks (unicode).
"""
marks = line.split(' ')
if len(marks) <= 2 and marks[0] in SongBeamerTypes.MarkTypes:
self.current_verse_type = SongBeamerTypes.MarkTypes[marks[0]]
if len(marks) <= 2 and marks[0].lower() in SongBeamerTypes.MarkTypes:
self.current_verse_type = SongBeamerTypes.MarkTypes[marks[0].lower()]
if len(marks) == 2:
# If we have a digit, we append it to current_verse_type.
if marks[1].isdigit():
self.current_verse_type += marks[1]
return True
elif marks[0].startswith('$$M='): # this verse-mark cannot be numbered
self.current_verse_type = SongBeamerTypes.MarkTypes['$$M=']
elif marks[0].lower().startswith('$$m='): # this verse-mark cannot be numbered
self.current_verse_type = SongBeamerTypes.MarkTypes['$$m=']
return True
return False

View File

@ -22,6 +22,8 @@
import os
import re
import logging
from openlp.plugins.songs.lib import VerseType, retrieve_windows_encoding
from openlp.plugins.songs.lib import strip_rtf
@ -53,8 +55,8 @@ class SundayPlusImport(SongImport):
"""
Initialise the class.
"""
SongImport.__init__(self, manager, **kwargs)
self.encoding = 'us-ascii'
super(SundayPlusImport, self).__init__(manager, **kwargs)
self.encoding = 'cp1252'
def do_import(self):
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
@ -73,7 +75,7 @@ class SundayPlusImport(SongImport):
if not self.parse(file.read()):
self.log_error(file.name)
return
if not self.title:
if self.title == '':
self.title = self.title_from_filename(file.name)
if not self.finish():
self.log_error(file.name)
@ -86,7 +88,7 @@ class SundayPlusImport(SongImport):
:param cell: ?
:return:
"""
if len(data) == 0 or data[0:1] != '[' or data[-1] != ']':
if not cell and (len(data) == 0 or data[0:1] != b'[' or data.strip()[-1:] != b']'):
self.log_error('File is malformed')
return False
i = 1
@ -94,31 +96,31 @@ class SundayPlusImport(SongImport):
while i < len(data):
# Data is held as #name: value pairs inside groups marked as [].
# Now we are looking for the name.
if data[i:i + 1] == '#':
name_end = data.find(':', i + 1)
name = data[i + 1:name_end].upper()
if data[i:i + 1] == b'#':
name_end = data.find(b':', i + 1)
name = data[i + 1:name_end].decode(self.encoding).upper()
i = name_end + 1
while data[i:i + 1] == ' ':
while data[i:i + 1] == b' ':
i += 1
if data[i:i + 1] == '"':
end = data.find('"', i + 1)
if data[i:i + 1] == b'"':
end = data.find(b'"', i + 1)
value = data[i + 1:end]
elif data[i:i + 1] == '[':
elif data[i:i + 1] == b'[':
j = i
inside_quotes = False
while j < len(data):
char = data[j:j + 1]
if char == '"':
if char == b'"':
inside_quotes = not inside_quotes
elif not inside_quotes and char == ']':
elif not inside_quotes and char == b']':
end = j + 1
break
j += 1
value = data[i:end]
else:
end = data.find(',', i + 1)
if data.find('(', i, end) != -1:
end = data.find(')', i) + 1
end = data.find(b',', i + 1)
if data.find(b'(', i, end) != -1:
end = data.find(b')', i) + 1
value = data[i:end]
# If we are in the main group.
if not cell:
@ -129,27 +131,29 @@ class SundayPlusImport(SongImport):
if len(author):
self.add_author(author)
elif name == 'COPYRIGHT':
self.copyright = self.decode(self.unescape(value))
self.add_copyright(self.decode(self.unescape(value)))
elif name[0:4] == 'CELL':
self.parse(value, cell=name[4:])
# We are in a verse group.
else:
if name == 'MARKER_NAME':
value = value.strip()
value = self.decode(value).strip()
if len(value):
verse_type = VerseType.tags[VerseType.from_loose_input(value[0])]
if len(value) >= 2 and value[-1] in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']:
verse_type = "%s%s" % (verse_type, value[-1])
elif name == 'HOTKEY':
value = self.decode(value).strip()
# HOTKEY always appears after MARKER_NAME, so it
# effectively overrides MARKER_NAME, if present.
if len(value) and value in list(HOTKEY_TO_VERSE_TYPE.keys()):
verse_type = HOTKEY_TO_VERSE_TYPE[value]
if name == 'RTF':
value = self.unescape(value)
value = self.decode(value)
result = strip_rtf(value, self.encoding)
if result is None:
return
return False
verse, self.encoding = result
lines = verse.strip().split('\n')
# If any line inside any verse contains CCLI or
@ -164,7 +168,7 @@ class SundayPlusImport(SongImport):
self.ccli_number = int(m.group(0))
continue
elif line.lower() == 'public domain':
self.copyright = 'Public Domain'
self.add_copyright('Public Domain')
continue
processed_lines.append(line)
self.add_verse('\n'.join(processed_lines).strip(), verse_type)
@ -192,11 +196,11 @@ class SundayPlusImport(SongImport):
def decode(self, blob):
while True:
try:
return str(blob, self.encoding)
except:
return blob.decode(self.encoding)
except Exception as e:
self.encoding = retrieve_windows_encoding()
def unescape(self, text):
text = text.replace('^^', '"')
text = text.replace('^', '\'')
text = text.replace(b'^^', b'"')
text = text.replace(b'^', b'\'')
return text.strip()

View File

@ -28,7 +28,6 @@ import json
import os
from openlp.core.common import translate
from openlp.plugins.songs.lib import VerseType
from openlp.plugins.songs.lib.importers.songimport import SongImport
from openlp.plugins.songs.lib.db import AuthorType

View File

@ -26,7 +26,7 @@ import os
import shutil
from PyQt5 import QtCore, QtWidgets
from sqlalchemy.sql import or_
from sqlalchemy.sql import and_, or_
from openlp.core.common import Registry, AppLocation, Settings, check_directory_exists, UiStrings, translate
from openlp.core.lib import MediaManagerItem, ItemCapabilities, PluginStatus, ServiceItemContext, \
@ -37,7 +37,7 @@ from openlp.plugins.songs.forms.songmaintenanceform import SongMaintenanceForm
from openlp.plugins.songs.forms.songimportform import SongImportForm
from openlp.plugins.songs.forms.songexportform import SongExportForm
from openlp.plugins.songs.lib import VerseType, clean_string, delete_song
from openlp.plugins.songs.lib.db import Author, AuthorType, Song, Book, MediaFile
from openlp.plugins.songs.lib.db import Author, AuthorType, Song, Book, MediaFile, SongBookEntry, Topic
from openlp.plugins.songs.lib.ui import SongStrings
from openlp.plugins.songs.lib.openlyricsxml import OpenLyrics, SongXML
@ -52,8 +52,11 @@ class SongSearch(object):
Titles = 2
Lyrics = 3
Authors = 4
Books = 5
Themes = 6
Topics = 5
Books = 6
Themes = 7
Copyright = 8
CCLInumber = 9
class SongMediaItem(MediaManagerItem):
@ -112,6 +115,7 @@ class SongMediaItem(MediaManagerItem):
def on_focus(self):
self.search_text_edit.setFocus()
self.search_text_edit.selectAll()
def config_update(self):
"""
@ -150,9 +154,17 @@ class SongMediaItem(MediaManagerItem):
translate('SongsPlugin.MediaItem', 'Search Lyrics...')),
(SongSearch.Authors, ':/songs/song_search_author.png', SongStrings.Authors,
translate('SongsPlugin.MediaItem', 'Search Authors...')),
(SongSearch.Topics, ':/songs/song_search_topic.png', SongStrings.Topics,
translate('SongsPlugin.MediaItem', 'Search Topics...')),
(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)
translate('SongsPlugin.MediaItem', 'Search Songbooks...')),
(SongSearch.Themes, ':/slides/slide_theme.png', UiStrings().Themes, UiStrings().SearchThemes),
(SongSearch.Copyright, ':/songs/song_search_copy.png',
translate('SongsPlugin.MediaItem', 'Copyright'),
translate('SongsPlugin.MediaItem', 'Search Copyright...')),
(SongSearch.CCLInumber, ':/songs/song_search_ccli.png',
translate('SongsPlugin.MediaItem', 'CCLI number'),
translate('SongsPlugin.MediaItem', 'Search CCLI number...'))
])
self.search_text_edit.set_current_search_type(Settings().value('%s/last search type' % self.settings_section))
self.config_update()
@ -183,23 +195,33 @@ class SongMediaItem(MediaManagerItem):
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')
elif search_type == SongSearch.Topics:
log.debug('Topics Search')
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[0] + '%'
search_results = self.plugin.manager.get_all_objects(Book,
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)
search_results = self.plugin.manager.get_all_objects(
Topic, Topic.name.like(search_string), Topic.name.asc())
self.display_results_topic(search_results)
elif search_type == SongSearch.Books:
log.debug('Songbook Search')
self.display_results_book(search_keywords)
elif search_type == SongSearch.Themes:
log.debug('Theme Search')
search_string = '%' + search_keywords + '%'
search_results = self.plugin.manager.get_all_objects(Song, Song.theme_name.like(search_string))
search_results = self.plugin.manager.get_all_objects(
Song, Song.theme_name.like(search_string), Song.theme_name.asc())
self.display_results_themes(search_results)
elif search_type == SongSearch.Copyright:
log.debug('Copyright Search')
search_string = '%' + search_keywords + '%'
search_results = self.plugin.manager.get_all_objects(
Song, and_(Song.copyright.like(search_string), Song.copyright != ''))
self.display_results_song(search_results)
elif search_type == SongSearch.CCLInumber:
log.debug('CCLI number Search')
search_string = '%' + search_keywords + '%'
search_results = self.plugin.manager.get_all_objects(
Song, and_(Song.ccli_number.like(search_string), Song.ccli_number != ''))
self.display_results_cclinumber(search_results)
self.check_search_result()
def search_entire(self, search_keywords):
@ -223,6 +245,12 @@ class SongMediaItem(MediaManagerItem):
log.debug('on_song_list_load - finished')
def display_results_song(self, search_results):
"""
Display the song search results in the media manager list
:param search_results: A list of db Song objects
:return: None
"""
log.debug('display results Song')
self.save_auto_select_id()
self.list_view.clear()
@ -242,6 +270,12 @@ class SongMediaItem(MediaManagerItem):
self.auto_select_id = -1
def display_results_author(self, search_results):
"""
Display the song search results in the media manager list, grouped by author
:param search_results: A list of db Author objects
:return: None
"""
log.debug('display results Author')
self.list_view.clear()
for author in search_results:
@ -254,22 +288,95 @@ class SongMediaItem(MediaManagerItem):
song_name.setData(QtCore.Qt.UserRole, song.id)
self.list_view.addItem(song_name)
def display_results_book(self, search_results, song_number=False):
def display_results_book(self, search_keywords):
"""
Display the song search results in the media manager list, grouped by book
:param search_keywords: A list of search keywords - book first, then number
:return: None
"""
log.debug('display results Book')
self.list_view.clear()
for book in search_results:
songs = sorted(book.songs, key=lambda song: int(re.match(r'[0-9]+', '0' + song.song_number).group()))
search_keywords = search_keywords.rpartition(' ')
search_book = search_keywords[0]
search_entry = re.sub(r'[^0-9]', '', search_keywords[2])
songbook_entries = (self.plugin.manager.session.query(SongBookEntry)
.join(Book)
.order_by(Book.name)
.order_by(SongBookEntry.entry))
for songbook_entry in songbook_entries:
if songbook_entry.song.temporary:
continue
if search_book.lower() not in songbook_entry.songbook.name.lower():
continue
if search_entry not in songbook_entry.entry:
continue
song_detail = '%s #%s: %s' % (songbook_entry.songbook.name, songbook_entry.entry, songbook_entry.song.title)
song_name = QtWidgets.QListWidgetItem(song_detail)
song_name.setData(QtCore.Qt.UserRole, songbook_entry.song.id)
self.list_view.addItem(song_name)
def display_results_topic(self, search_results):
"""
Display the song search results in the media manager list, grouped by topic
:param search_results: A list of db Topic objects
:return: None
"""
log.debug('display results Topic')
self.list_view.clear()
search_results = sorted(search_results, key=lambda topic: self._natural_sort_key(topic.name))
for topic in search_results:
songs = sorted(topic.songs, key=lambda song: song.sort_key)
for song in songs:
# Do not display temporary songs
if song.temporary:
continue
if song_number and song_number not in song.song_number:
continue
song_detail = '%s - %s (%s)' % (book.name, song.song_number, song.title)
song_detail = '%s (%s)' % (topic.name, song.title)
song_name = QtWidgets.QListWidgetItem(song_detail)
song_name.setData(QtCore.Qt.UserRole, song.id)
self.list_view.addItem(song_name)
def display_results_themes(self, search_results):
"""
Display the song search results in the media manager list, sorted by theme
:param search_results: A list of db Song objects
:return: None
"""
log.debug('display results Themes')
self.list_view.clear()
for song in search_results:
# Do not display temporary songs
if song.temporary:
continue
song_detail = '%s (%s)' % (song.theme_name, song.title)
song_name = QtWidgets.QListWidgetItem(song_detail)
song_name.setData(QtCore.Qt.UserRole, song.id)
self.list_view.addItem(song_name)
def display_results_cclinumber(self, search_results):
"""
Display the song search results in the media manager list, sorted by CCLI number
:param search_results: A list of db Song objects
:return: None
"""
log.debug('display results CCLI number')
self.list_view.clear()
songs = sorted(search_results, key=lambda song: self._natural_sort_key(song.ccli_number))
for song in songs:
# Do not display temporary songs
if song.temporary:
continue
song_detail = '%s (%s)' % (song.ccli_number, song.title)
song_name = QtWidgets.QListWidgetItem(song_detail)
song_name.setData(QtCore.Qt.UserRole, song.id)
self.list_view.addItem(song_name)
def on_clear_text_button_click(self):
"""
Clear the search text.
@ -363,8 +470,8 @@ class SongMediaItem(MediaManagerItem):
items = self.list_view.selectedIndexes()
if QtWidgets.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)),
translate('SongsPlugin.MediaItem',
'Are you sure you want to delete the "%d" selected song(s)?') % len(items),
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No),
QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.No:
return
@ -523,8 +630,9 @@ class SongMediaItem(MediaManagerItem):
item.raw_footer.append("%s %s" % (SongStrings.CopyrightSymbol, song.copyright))
else:
item.raw_footer.append(song.copyright)
if self.display_songbook and song.book:
item.raw_footer.append("%s #%s" % (song.book.name, song.song_number))
if self.display_songbook and song.songbook_entries:
songbooks = [str(songbook_entry) for songbook_entry in song.songbook_entries]
item.raw_footer.append(", ".join(songbooks))
if Settings().value('core/ccli number'):
item.raw_footer.append(translate('SongsPlugin.MediaItem',
'CCLI License: ') + Settings().value('core/ccli number'))
@ -586,6 +694,14 @@ class SongMediaItem(MediaManagerItem):
# List must be empty at the end
return not author_list
def _natural_sort_key(self, s):
"""
Return a tuple by which s is sorted.
:param s: A string value from the list we want to sort.
"""
return [int(text) if text.isdecimal() else text.lower()
for text in re.split('(\d+)', s)]
def search(self, string, show_error):
"""
Search for some songs

View File

@ -266,13 +266,12 @@ class OpenLyrics(object):
element.set('type', AuthorType.Music)
else:
element.set('type', author_song.author_type)
book = self.manager.get_object_filtered(Book, Book.id == song.song_book_id)
if book is not None:
book = book.name
if song.songbook_entries:
songbooks = etree.SubElement(properties, 'songbooks')
element = self._add_text_to_element('songbook', songbooks, None, book)
if song.song_number:
element.set('entry', song.song_number)
for songbook_entry in song.songbook_entries:
element = self._add_text_to_element('songbook', songbooks, None, songbook_entry.songbook.name)
if songbook_entry.entry:
element.set('entry', songbook_entry.entry)
if song.topics:
themes = etree.SubElement(properties, 'themes')
for topic in song.topics:
@ -744,8 +743,6 @@ class OpenLyrics(object):
:param properties: The property object (lxml.objectify.ObjectifiedElement).
:param song: The song object.
"""
song.song_book_id = None
song.song_number = ''
if hasattr(properties, 'songbooks'):
for songbook in properties.songbooks.songbook:
book_name = songbook.get('name', '')
@ -755,10 +752,7 @@ class OpenLyrics(object):
# We need to create a book, because it does not exist.
book = Book.populate(name=book_name, publisher='')
self.manager.save_object(book)
song.song_book_id = book.id
song.song_number = songbook.get('entry', '')
# We only support one song book, so take the first one.
break
song.add_songbook_entry(book, songbook.get('entry', ''))
def _process_titles(self, properties, song):
"""

View File

@ -61,6 +61,7 @@ class SongSelectImport(object):
self.html_parser = HTMLParser()
self.opener = build_opener(HTTPCookieProcessor(CookieJar()))
self.opener.addheaders = [('User-Agent', USER_AGENT)]
self.run_search = True
def login(self, username, password, callback=None):
"""
@ -115,10 +116,11 @@ class SongSelectImport(object):
:param callback: A method which is called when each song is found, with the song as a parameter.
:return: List of songs
"""
self.run_search = True
params = {'allowredirect': 'false', 'SearchTerm': search_text}
current_page = 1
songs = []
while True:
while self.run_search:
if current_page > 1:
params['page'] = current_page
try:
@ -220,3 +222,9 @@ class SongSelectImport(object):
db_song.add_author(author)
self.db_manager.save_object(db_song)
return db_song
def stop(self):
"""
Stop the search.
"""
self.run_search = False

View File

@ -35,8 +35,8 @@ class SongStrings(object):
Authors = translate('OpenLP.Ui', 'Authors', 'Plural')
AuthorUnknown = translate('OpenLP.Ui', 'Author Unknown') # Used to populate the database.
CopyrightSymbol = translate('OpenLP.Ui', '\xa9', 'Copyright symbol.')
SongBook = translate('OpenLP.Ui', 'Song Book', 'Singular')
SongBooks = translate('OpenLP.Ui', 'Song Books', 'Plural')
SongBook = translate('OpenLP.Ui', 'Songbook', 'Singular')
SongBooks = translate('OpenLP.Ui', 'Songbooks', 'Plural')
SongIncomplete = translate('OpenLP.Ui', 'Title and/or verses not found')
SongMaintenance = translate('OpenLP.Ui', 'Song Maintenance')
Topic = translate('OpenLP.Ui', 'Topic', 'Singular')

View File

@ -29,10 +29,10 @@ from sqlalchemy import Table, Column, ForeignKey, types
from sqlalchemy.sql.expression import func, false, null, text
from openlp.core.lib.db import get_upgrade_op
from openlp.core.common import trace_error_handler
from openlp.core.utils.db import drop_columns
log = logging.getLogger(__name__)
__version__ = 4
__version__ = 5
# TODO: When removing an upgrade path the ftw-data needs updating to the minimum supported version
@ -117,3 +117,34 @@ def upgrade_4(session, metadata):
op.rename_table('authors_songs_tmp', 'authors_songs')
else:
log.warning('Skipping upgrade_4 step of upgrading the song db')
def upgrade_5(session, metadata):
"""
Version 5 upgrade.
This upgrade adds support for multiple songbooks
"""
op = get_upgrade_op(session)
songs_table = Table('songs', metadata)
if 'song_book_id' in [col.name for col in songs_table.c.values()]:
log.warning('Skipping upgrade_5 step of upgrading the song db')
return
# Create the mapping table (songs <-> songbooks)
op.create_table('songs_songbooks',
Column('songbook_id', types.Integer(), ForeignKey('song_books.id'), primary_key=True),
Column('song_id', types.Integer(), ForeignKey('songs.id'), primary_key=True),
Column('entry', types.Unicode(255), primary_key=True, nullable=False))
# Migrate old data
op.execute('INSERT INTO songs_songbooks SELECT song_book_id, id, song_number FROM songs\
WHERE song_book_id IS NOT NULL AND song_number IS NOT NULL')
# Drop old columns
if metadata.bind.url.get_dialect().name == 'sqlite':
drop_columns(op, 'songs', ['song_book_id', 'song_number'])
else:
op.drop_constraint('songs_ibfk_1', 'songs', 'foreignkey')
op.drop_column('songs', 'song_book_id')
op.drop_column('songs', 'song_number')

View File

@ -211,7 +211,8 @@ class SongsPlugin(Plugin):
if self.media_item:
self.media_item.on_export_click()
def about(self):
@staticmethod
def about():
"""
Provides information for the plugin manager to display.
@ -296,7 +297,7 @@ class SongsPlugin(Plugin):
if sfile.startswith('songs_') and sfile.endswith('.sqlite'):
self.application.process_events()
song_dbs.append(os.path.join(db_dir, sfile))
song_count += self._count_songs(os.path.join(db_dir, sfile))
song_count += SongsPlugin._count_songs(os.path.join(db_dir, sfile))
if not song_dbs:
return
self.application.process_events()
@ -343,7 +344,8 @@ class SongsPlugin(Plugin):
for song in songs:
self.manager.delete_object(Song, song.id)
def _count_songs(self, db_file):
@staticmethod
def _count_songs(db_file):
"""
Provide a count of the songs in the database

View File

@ -20,7 +20,7 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
from PyQt5 import QtWidgets
from PyQt5 import QtCore, QtWidgets
from openlp.core.common import RegistryProperties, translate
from openlp.plugins.songusage.lib.db import SongUsageItem
@ -36,7 +36,8 @@ class SongUsageDeleteForm(QtWidgets.QDialog, Ui_SongUsageDeleteDialog, RegistryP
Constructor
"""
self.manager = manager
super(SongUsageDeleteForm, self).__init__(parent)
super(SongUsageDeleteForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint |
QtCore.Qt.WindowTitleHint)
self.setupUi(self)
self.button_box.clicked.connect(self.on_button_box_clicked)
@ -47,13 +48,14 @@ class SongUsageDeleteForm(QtWidgets.QDialog, Ui_SongUsageDeleteDialog, RegistryP
:param button: The button pressed
"""
if self.button_box.standardButton(button) == QtWidgets.QDialogButtonBox.Ok:
ret = QtWidgets.QMessageBox.question(
self,
translate('SongUsagePlugin.SongUsageDeleteForm', 'Delete Selected Song Usage Events?'),
translate('SongUsagePlugin.SongUsageDeleteForm',
'Are you sure you want to delete selected Song Usage data?'),
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No),
QtWidgets.QMessageBox.No)
ret = QtWidgets.QMessageBox.question(self,
translate('SongUsagePlugin.SongUsageDeleteForm',
'Delete Selected Song Usage Events?'),
translate('SongUsagePlugin.SongUsageDeleteForm',
'Are you sure you want to delete selected Song Usage data?'),
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes |
QtWidgets.QMessageBox.No),
QtWidgets.QMessageBox.No)
if ret == QtWidgets.QMessageBox.Yes:
delete_date = self.delete_calendar.selectedDate().toPyDate()
self.manager.delete_all_objects(SongUsageItem, SongUsageItem.usagedate <= delete_date)

View File

@ -23,7 +23,7 @@
import logging
import os
from PyQt5 import QtWidgets
from PyQt5 import QtCore, QtWidgets
from sqlalchemy.sql import and_
from openlp.core.common import RegistryProperties, Settings, check_directory_exists, translate
@ -44,7 +44,7 @@ class SongUsageDetailForm(QtWidgets.QDialog, Ui_SongUsageDetailDialog, RegistryP
"""
Initialise the form
"""
super(SongUsageDetailForm, self).__init__(parent)
super(SongUsageDetailForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
self.plugin = plugin
self.setupUi(self)

View File

@ -118,7 +118,6 @@ class SongUsagePlugin(Plugin):
self.main_window.status_bar.insertPermanentWidget(1, self.song_usage_active_button)
self.song_usage_active_button.hide()
# Signals and slots
self.song_usage_status.changed.connect(self.toggle_song_usage_state)
self.song_usage_active_button.toggled.connect(self.toggle_song_usage_state)
self.song_usage_menu.menuAction().setVisible(False)
@ -227,7 +226,8 @@ class SongUsagePlugin(Plugin):
self.song_usage_detail_form.initialise()
self.song_usage_detail_form.exec()
def about(self):
@staticmethod
def about():
"""
The plugin about text

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More