forked from openlp/openlp
Merge trunk in
This commit is contained in:
commit
5ba53fa097
@ -44,3 +44,4 @@ __pycache__
|
||||
cover
|
||||
*.kdev4
|
||||
coverage
|
||||
tags
|
||||
|
@ -1 +1 @@
|
||||
2.3.1
|
||||
2.4
|
||||
|
@ -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()
|
||||
|
@ -252,67 +252,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 +318,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 +338,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)],
|
||||
|
@ -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')
|
||||
|
@ -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
|
||||
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):
|
||||
"""
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
||||
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
|
||||
line = line.replace('\n', ' ')
|
||||
return line.split(' ')
|
||||
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)
|
||||
|
@ -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):
|
||||
|
@ -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' \
|
||||
|
@ -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):
|
||||
|
@ -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')
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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
|
||||
|
@ -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,6 +115,7 @@ class Display(QtWidgets.QGraphicsView):
|
||||
|
||||
:param event: The event to be handled
|
||||
"""
|
||||
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,20 @@ 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():
|
||||
# Workaround for bug #1531319, should not be needed with PyQt 5.6.
|
||||
fade_shake_timer.stop()
|
||||
elif is_win():
|
||||
self.shake_web_view()
|
||||
# Wait for the webview to update before getting the preview.
|
||||
# Important otherwise first preview will miss the background !
|
||||
while not self.web_loaded:
|
||||
@ -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):
|
||||
"""
|
||||
|
@ -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)
|
||||
|
592
openlp/core/ui/media/vendor/vlc.py
vendored
592
openlp/core/ui/media/vendor/vlc.py
vendored
@ -49,7 +49,7 @@ import functools
|
||||
from inspect import getargspec
|
||||
|
||||
__version__ = "N/A"
|
||||
build_date = "Thu Nov 5 23:41:43 2015"
|
||||
build_date = "Mon Jan 25 19:40:05 2016"
|
||||
|
||||
# The libvlc doc states that filenames are expected to be in UTF8, do
|
||||
# not rely on sys.getfilesystemencoding() which will be confused,
|
||||
@ -425,20 +425,10 @@ class EventType(_Enum):
|
||||
273: 'MediaPlayerLengthChanged',
|
||||
274: 'MediaPlayerVout',
|
||||
275: 'MediaPlayerScrambledChanged',
|
||||
276: 'MediaPlayerESAdded',
|
||||
277: 'MediaPlayerESDeleted',
|
||||
278: 'MediaPlayerESSelected',
|
||||
279: 'MediaPlayerCorked',
|
||||
280: 'MediaPlayerUncorked',
|
||||
281: 'MediaPlayerMuted',
|
||||
282: 'MediaPlayerUnmuted',
|
||||
283: 'MediaPlayerAudioVolume',
|
||||
284: 'MediaPlayerAudioDevice',
|
||||
0x200: 'MediaListItemAdded',
|
||||
513: 'MediaListWillAddItem',
|
||||
514: 'MediaListItemDeleted',
|
||||
515: 'MediaListWillDeleteItem',
|
||||
516: 'MediaListEndReached',
|
||||
0x300: 'MediaListViewItemAdded',
|
||||
769: 'MediaListViewWillAddItem',
|
||||
770: 'MediaListViewItemDeleted',
|
||||
@ -464,7 +454,6 @@ EventType.MediaDiscovererEnded = EventType(1281)
|
||||
EventType.MediaDiscovererStarted = EventType(0x500)
|
||||
EventType.MediaDurationChanged = EventType(2)
|
||||
EventType.MediaFreed = EventType(4)
|
||||
EventType.MediaListEndReached = EventType(516)
|
||||
EventType.MediaListItemAdded = EventType(0x200)
|
||||
EventType.MediaListItemDeleted = EventType(514)
|
||||
EventType.MediaListPlayerNextItemSet = EventType(1025)
|
||||
@ -478,20 +467,13 @@ EventType.MediaListWillAddItem = EventType(513)
|
||||
EventType.MediaListWillDeleteItem = EventType(515)
|
||||
EventType.MediaMetaChanged = EventType(0)
|
||||
EventType.MediaParsedChanged = EventType(3)
|
||||
EventType.MediaPlayerAudioDevice = EventType(284)
|
||||
EventType.MediaPlayerAudioVolume = EventType(283)
|
||||
EventType.MediaPlayerBackward = EventType(264)
|
||||
EventType.MediaPlayerBuffering = EventType(259)
|
||||
EventType.MediaPlayerCorked = EventType(279)
|
||||
EventType.MediaPlayerESAdded = EventType(276)
|
||||
EventType.MediaPlayerESDeleted = EventType(277)
|
||||
EventType.MediaPlayerESSelected = EventType(278)
|
||||
EventType.MediaPlayerEncounteredError = EventType(266)
|
||||
EventType.MediaPlayerEndReached = EventType(265)
|
||||
EventType.MediaPlayerForward = EventType(263)
|
||||
EventType.MediaPlayerLengthChanged = EventType(273)
|
||||
EventType.MediaPlayerMediaChanged = EventType(0x100)
|
||||
EventType.MediaPlayerMuted = EventType(281)
|
||||
EventType.MediaPlayerNothingSpecial = EventType(257)
|
||||
EventType.MediaPlayerOpening = EventType(258)
|
||||
EventType.MediaPlayerPausableChanged = EventType(270)
|
||||
@ -504,8 +486,6 @@ EventType.MediaPlayerSnapshotTaken = EventType(272)
|
||||
EventType.MediaPlayerStopped = EventType(262)
|
||||
EventType.MediaPlayerTimeChanged = EventType(267)
|
||||
EventType.MediaPlayerTitleChanged = EventType(271)
|
||||
EventType.MediaPlayerUncorked = EventType(280)
|
||||
EventType.MediaPlayerUnmuted = EventType(282)
|
||||
EventType.MediaPlayerVout = EventType(274)
|
||||
EventType.MediaStateChanged = EventType(5)
|
||||
EventType.MediaSubItemAdded = EventType(1)
|
||||
@ -549,19 +529,15 @@ class Meta(_Enum):
|
||||
20: 'Episode',
|
||||
21: 'ShowName',
|
||||
22: 'Actors',
|
||||
23: 'AlbumArtist',
|
||||
24: 'DiscNumber',
|
||||
}
|
||||
Meta.Actors = Meta(22)
|
||||
Meta.Album = Meta(4)
|
||||
Meta.AlbumArtist = Meta(23)
|
||||
Meta.Artist = Meta(1)
|
||||
Meta.ArtworkURL = Meta(15)
|
||||
Meta.Copyright = Meta(3)
|
||||
Meta.Date = Meta(8)
|
||||
Meta.Description = Meta(6)
|
||||
Meta.Director = Meta(18)
|
||||
Meta.DiscNumber = Meta(24)
|
||||
Meta.EncodedBy = Meta(14)
|
||||
Meta.Episode = Meta(20)
|
||||
Meta.Genre = Meta(2)
|
||||
@ -619,40 +595,6 @@ TrackType.text = TrackType(2)
|
||||
TrackType.unknown = TrackType(-1)
|
||||
TrackType.video = TrackType(1)
|
||||
|
||||
class MediaType(_Enum):
|
||||
'''Media type
|
||||
See libvlc_media_get_type.
|
||||
'''
|
||||
_enum_names_ = {
|
||||
0: 'unknown',
|
||||
1: 'file',
|
||||
2: 'directory',
|
||||
3: 'disc',
|
||||
4: 'stream',
|
||||
5: 'playlist',
|
||||
}
|
||||
MediaType.directory = MediaType(2)
|
||||
MediaType.disc = MediaType(3)
|
||||
MediaType.file = MediaType(1)
|
||||
MediaType.playlist = MediaType(5)
|
||||
MediaType.stream = MediaType(4)
|
||||
MediaType.unknown = MediaType(0)
|
||||
|
||||
class MediaParseFlag(_Enum):
|
||||
'''Parse flags used by libvlc_media_parse_with_options()
|
||||
See libvlc_media_parse_with_options.
|
||||
'''
|
||||
_enum_names_ = {
|
||||
0x00: 'local',
|
||||
0x01: 'network',
|
||||
0x02: 'local',
|
||||
0x04: 'network',
|
||||
}
|
||||
MediaParseFlag.local = MediaParseFlag(0x00)
|
||||
MediaParseFlag.local = MediaParseFlag(0x02)
|
||||
MediaParseFlag.network = MediaParseFlag(0x01)
|
||||
MediaParseFlag.network = MediaParseFlag(0x04)
|
||||
|
||||
class PlaybackMode(_Enum):
|
||||
'''Defines playback modes for playlist.
|
||||
'''
|
||||
@ -823,7 +765,7 @@ class Callback(ctypes.c_void_p):
|
||||
class LogCb(ctypes.c_void_p):
|
||||
"""Callback prototype for LibVLC log message handler.
|
||||
\param data data pointer as given to L{libvlc_log_set}()
|
||||
\param level message level (@ref libvlc_log_level)
|
||||
\param level message level (@ref enum libvlc_log_level)
|
||||
\param ctx message context (meta-information about the message)
|
||||
\param fmt printf() format string (as defined by ISO C11)
|
||||
\param args variable argument list for the format
|
||||
@ -832,49 +774,6 @@ class LogCb(ctypes.c_void_p):
|
||||
variable arguments are only valid until the callback returns.
|
||||
"""
|
||||
pass
|
||||
class MediaOpenCb(ctypes.c_void_p):
|
||||
"""Callback prototype to open a custom bitstream input media.
|
||||
The same media item can be opened multiple times. Each time, this callback
|
||||
is invoked. It should allocate and initialize any instance-specific
|
||||
resources, then store them in *datap. The instance resources can be freed
|
||||
in the @ref libvlc_media_close_cb callback.
|
||||
\param opaque private pointer as passed to L{libvlc_media_new_callbacks}()
|
||||
\param datap storage space for a private data pointer [OUT]
|
||||
\param sizep byte length of the bitstream or UINT64_MAX if unknown [OUT]
|
||||
\note For convenience, *datap is initially NULL and *sizep is initially 0.
|
||||
\return 0 on success, non-zero on error. In case of failure, the other
|
||||
callbacks will not be invoked and any value stored in *datap and *sizep is
|
||||
discarded.
|
||||
"""
|
||||
pass
|
||||
class MediaReadCb(ctypes.c_void_p):
|
||||
"""Callback prototype to read data from a custom bitstream input media.
|
||||
\param opaque private pointer as set by the @ref libvlc_media_open_cb
|
||||
callback
|
||||
\param buf start address of the buffer to read data into
|
||||
\param len bytes length of the buffer
|
||||
\return strictly positive number of bytes read, 0 on end-of-stream,
|
||||
or -1 on non-recoverable error
|
||||
\note If no data is immediately available, then the callback should sleep.
|
||||
\warning The application is responsible for avoiding deadlock situations.
|
||||
In particular, the callback should return an error if playback is stopped;
|
||||
if it does not return, then L{libvlc_media_player_stop}() will never return.
|
||||
"""
|
||||
pass
|
||||
class MediaSeekCb(ctypes.c_void_p):
|
||||
"""Callback prototype to seek a custom bitstream input media.
|
||||
\param opaque private pointer as set by the @ref libvlc_media_open_cb
|
||||
callback
|
||||
\param offset absolute byte offset to seek to
|
||||
\return 0 on success, -1 on error.
|
||||
"""
|
||||
pass
|
||||
class MediaCloseCb(ctypes.c_void_p):
|
||||
"""Callback prototype to close a custom bitstream input media.
|
||||
\param opaque private pointer as set by the @ref libvlc_media_open_cb
|
||||
callback
|
||||
"""
|
||||
pass
|
||||
class VideoLockCb(ctypes.c_void_p):
|
||||
"""Callback prototype to allocate and lock a picture buffer.
|
||||
Whenever a new video frame needs to be decoded, the lock callback is
|
||||
@ -932,7 +831,7 @@ the number of bytes per pixel multiplied by the pixel width.
|
||||
Similarly, the number of scanlines must be bigger than of equal to
|
||||
the pixel height.
|
||||
Furthermore, we recommend that pitches and lines be multiple of 32
|
||||
to not break assumptions that might be held by optimized code
|
||||
to not break assumption that might be made by various optimizations
|
||||
in the video decoders, video filters and/or video converters.
|
||||
"""
|
||||
pass
|
||||
@ -1009,7 +908,7 @@ class CallbackDecorators(object):
|
||||
LogCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int, Log_ptr, ctypes.c_char_p, ctypes.c_void_p)
|
||||
LogCb.__doc__ = '''Callback prototype for LibVLC log message handler.
|
||||
\param data data pointer as given to L{libvlc_log_set}()
|
||||
\param level message level (@ref libvlc_log_level)
|
||||
\param level message level (@ref enum libvlc_log_level)
|
||||
\param ctx message context (meta-information about the message)
|
||||
\param fmt printf() format string (as defined by ISO C11)
|
||||
\param args variable argument list for the format
|
||||
@ -1017,45 +916,6 @@ class CallbackDecorators(object):
|
||||
\warning The message context pointer, the format string parameters and the
|
||||
variable arguments are only valid until the callback returns.
|
||||
'''
|
||||
MediaOpenCb = ctypes.CFUNCTYPE(ctypes.POINTER(ctypes.c_int), ctypes.c_void_p, ListPOINTER(ctypes.c_void_p), ctypes.POINTER(ctypes.c_uint64))
|
||||
MediaOpenCb.__doc__ = '''Callback prototype to open a custom bitstream input media.
|
||||
The same media item can be opened multiple times. Each time, this callback
|
||||
is invoked. It should allocate and initialize any instance-specific
|
||||
resources, then store them in *datap. The instance resources can be freed
|
||||
in the @ref libvlc_media_close_cb callback.
|
||||
\param opaque private pointer as passed to L{libvlc_media_new_callbacks}()
|
||||
\param datap storage space for a private data pointer [OUT]
|
||||
\param sizep byte length of the bitstream or UINT64_MAX if unknown [OUT]
|
||||
\note For convenience, *datap is initially NULL and *sizep is initially 0.
|
||||
\return 0 on success, non-zero on error. In case of failure, the other
|
||||
callbacks will not be invoked and any value stored in *datap and *sizep is
|
||||
discarded.
|
||||
'''
|
||||
MediaReadCb = ctypes.CFUNCTYPE(ctypes.POINTER(ctypes.c_ssize_t), ctypes.c_void_p, ctypes.c_char_p, ctypes.c_size_t)
|
||||
MediaReadCb.__doc__ = '''Callback prototype to read data from a custom bitstream input media.
|
||||
\param opaque private pointer as set by the @ref libvlc_media_open_cb
|
||||
callback
|
||||
\param buf start address of the buffer to read data into
|
||||
\param len bytes length of the buffer
|
||||
\return strictly positive number of bytes read, 0 on end-of-stream,
|
||||
or -1 on non-recoverable error
|
||||
\note If no data is immediately available, then the callback should sleep.
|
||||
\warning The application is responsible for avoiding deadlock situations.
|
||||
In particular, the callback should return an error if playback is stopped;
|
||||
if it does not return, then L{libvlc_media_player_stop}() will never return.
|
||||
'''
|
||||
MediaSeekCb = ctypes.CFUNCTYPE(ctypes.POINTER(ctypes.c_int), ctypes.c_void_p, ctypes.c_uint64)
|
||||
MediaSeekCb.__doc__ = '''Callback prototype to seek a custom bitstream input media.
|
||||
\param opaque private pointer as set by the @ref libvlc_media_open_cb
|
||||
callback
|
||||
\param offset absolute byte offset to seek to
|
||||
\return 0 on success, -1 on error.
|
||||
'''
|
||||
MediaCloseCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p)
|
||||
MediaCloseCb.__doc__ = '''Callback prototype to close a custom bitstream input media.
|
||||
\param opaque private pointer as set by the @ref libvlc_media_open_cb
|
||||
callback
|
||||
'''
|
||||
VideoLockCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ListPOINTER(ctypes.c_void_p))
|
||||
VideoLockCb.__doc__ = '''Callback prototype to allocate and lock a picture buffer.
|
||||
Whenever a new video frame needs to be decoded, the lock callback is
|
||||
@ -1110,7 +970,7 @@ the number of bytes per pixel multiplied by the pixel width.
|
||||
Similarly, the number of scanlines must be bigger than of equal to
|
||||
the pixel height.
|
||||
Furthermore, we recommend that pitches and lines be multiple of 32
|
||||
to not break assumptions that might be held by optimized code
|
||||
to not break assumption that might be made by various optimizations
|
||||
in the video decoders, video filters and/or video converters.
|
||||
'''
|
||||
VideoCleanupCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p)
|
||||
@ -1760,19 +1620,6 @@ class Instance(_Ctype):
|
||||
return libvlc_media_new_fd(self, fd)
|
||||
|
||||
|
||||
def media_new_callbacks(self, open_cb, read_cb, seek_cb, close_cb, opaque):
|
||||
'''Create a media with custom callbacks to read the data from.
|
||||
@param open_cb: callback to open the custom bitstream input media.
|
||||
@param read_cb: callback to read data (must not be None).
|
||||
@param seek_cb: callback to seek, or None if seeking is not supported.
|
||||
@param close_cb: callback to close the media, or None if unnecessary.
|
||||
@param opaque: data pointer for the open callback.
|
||||
@return: the newly created media or None on error @note If open_cb is None, the opaque pointer will be passed to read_cb, seek_cb and close_cb, and the stream size will be treated as unknown. @note The callbacks may be called asynchronously (from another thread). A single stream instance need not be reentrant. However the open_cb needs to be reentrant if the media is used by multiple player instances. @warning The callbacks may be used until all or any player instances that were supplied the media item are stopped. See L{media_release}.
|
||||
@version: LibVLC 3.0.0 and later.
|
||||
'''
|
||||
return libvlc_media_new_callbacks(self, open_cb, read_cb, seek_cb, close_cb, opaque)
|
||||
|
||||
|
||||
def media_new_as_node(self, psz_name):
|
||||
'''Create a media as an empty node with a given name.
|
||||
See L{media_release}.
|
||||
@ -1782,22 +1629,12 @@ class Instance(_Ctype):
|
||||
return libvlc_media_new_as_node(self, str_to_bytes(psz_name))
|
||||
|
||||
|
||||
def media_discoverer_new(self, psz_name):
|
||||
'''Create a media discoverer object by name.
|
||||
After this object is created, you should attach to events in order to be
|
||||
notified of the discoverer state.
|
||||
You should also attach to media_list events in order to be notified of new
|
||||
items discovered.
|
||||
You need to call L{media_discoverer_start}() in order to start the
|
||||
discovery.
|
||||
See L{media_discoverer_media_list}
|
||||
See L{media_discoverer_event_manager}
|
||||
See L{media_discoverer_start}.
|
||||
def media_discoverer_new_from_name(self, psz_name):
|
||||
'''Discover media service by name.
|
||||
@param psz_name: service name.
|
||||
@return: media discover object or None in case of error.
|
||||
@version: LibVLC 3.0.0 or later.
|
||||
'''
|
||||
return libvlc_media_discoverer_new(self, str_to_bytes(psz_name))
|
||||
return libvlc_media_discoverer_new_from_name(self, str_to_bytes(psz_name))
|
||||
|
||||
|
||||
def media_library_new(self):
|
||||
@ -1809,7 +1646,7 @@ class Instance(_Ctype):
|
||||
|
||||
def audio_output_list_get(self):
|
||||
'''Gets the list of available audio output modules.
|
||||
@return: list of available audio outputs. It must be freed with In case of error, None is returned.
|
||||
@return: list of available audio outputs. It must be freed it with In case of error, None is returned.
|
||||
'''
|
||||
return libvlc_audio_output_list_get(self)
|
||||
|
||||
@ -1825,7 +1662,7 @@ class Instance(_Ctype):
|
||||
some circumstances. By default, it is recommended to not specify any
|
||||
explicit audio device.
|
||||
@param psz_aout: audio output name (as returned by L{audio_output_list_get}()).
|
||||
@return: A None-terminated linked list of potential audio output devices. It must be freed with L{audio_output_device_list_release}().
|
||||
@return: A None-terminated linked list of potential audio output devices. It must be freed it with L{audio_output_device_list_release}().
|
||||
@version: LibVLC 2.1.0 or later.
|
||||
'''
|
||||
return libvlc_audio_output_device_list_get(self, str_to_bytes(aout))
|
||||
@ -2255,7 +2092,7 @@ class Media(_Ctype):
|
||||
|
||||
def parse(self):
|
||||
'''Parse a media.
|
||||
This fetches (local) art, meta data and tracks information.
|
||||
This fetches (local) meta data and tracks information.
|
||||
The method is synchronous.
|
||||
See L{parse_async}
|
||||
See L{get_meta}
|
||||
@ -2266,7 +2103,7 @@ class Media(_Ctype):
|
||||
|
||||
def parse_async(self):
|
||||
'''Parse a media.
|
||||
This fetches (local) art, meta data and tracks information.
|
||||
This fetches (local) meta data and tracks information.
|
||||
The method is the asynchronous of L{parse}().
|
||||
To track when this is over you can listen to libvlc_MediaParsedChanged
|
||||
event. However if the media was already parsed you will not receive this
|
||||
@ -2279,27 +2116,6 @@ class Media(_Ctype):
|
||||
return libvlc_media_parse_async(self)
|
||||
|
||||
|
||||
def parse_with_options(self, parse_flag):
|
||||
'''Parse the media asynchronously with options.
|
||||
This fetches (local or network) art, meta data and/or tracks information.
|
||||
This method is the extended version of L{parse_async}().
|
||||
To track when this is over you can listen to libvlc_MediaParsedChanged
|
||||
event. However if this functions returns an error, you will not receive this
|
||||
event.
|
||||
It uses a flag to specify parse options (see libvlc_media_parse_flag_t). All
|
||||
these flags can be combined. By default, media is parsed if it's a local
|
||||
file.
|
||||
See libvlc_MediaParsedChanged
|
||||
See L{get_meta}
|
||||
See L{tracks_get}
|
||||
See libvlc_media_parse_flag_t.
|
||||
@param parse_flag: parse options:
|
||||
@return: -1 in case of error, 0 otherwise.
|
||||
@version: LibVLC 3.0.0 or later.
|
||||
'''
|
||||
return libvlc_media_parse_with_options(self, parse_flag)
|
||||
|
||||
|
||||
def is_parsed(self):
|
||||
'''Get Parsed status for media descriptor object.
|
||||
See libvlc_MediaParsedChanged.
|
||||
@ -2325,14 +2141,6 @@ class Media(_Ctype):
|
||||
return libvlc_media_get_user_data(self)
|
||||
|
||||
|
||||
def get_type(self):
|
||||
'''Get the media type of the media descriptor object.
|
||||
@return: media type.
|
||||
@version: LibVLC 3.0.0 and later. See libvlc_media_type_t.
|
||||
'''
|
||||
return libvlc_media_get_type(self)
|
||||
|
||||
|
||||
def player_new_from_media(self):
|
||||
'''Create a Media Player object from a Media.
|
||||
@return: a new media player object, or None on error.
|
||||
@ -2348,25 +2156,6 @@ class MediaDiscoverer(_Ctype):
|
||||
'''
|
||||
return _Constructor(cls, ptr)
|
||||
|
||||
def start(self):
|
||||
'''Start media discovery.
|
||||
To stop it, call L{stop}() or
|
||||
L{release}() directly.
|
||||
See L{stop}.
|
||||
@return: -1 in case of error, 0 otherwise.
|
||||
@version: LibVLC 3.0.0 or later.
|
||||
'''
|
||||
return libvlc_media_discoverer_start(self)
|
||||
|
||||
|
||||
def stop(self):
|
||||
'''Stop media discovery.
|
||||
See L{start}.
|
||||
@version: LibVLC 3.0.0 or later.
|
||||
'''
|
||||
return libvlc_media_discoverer_stop(self)
|
||||
|
||||
|
||||
def release(self):
|
||||
'''Release media discover object. If the reference count reaches 0, then
|
||||
the object will be released.
|
||||
@ -2647,13 +2436,6 @@ class MediaListPlayer(_Ctype):
|
||||
return libvlc_media_list_player_set_media_player(self, p_mi)
|
||||
|
||||
|
||||
def get_media_player(self):
|
||||
'''Get media player of the media_list_player instance.
|
||||
@return: media player instance @note the caller is responsible for releasing the returned instance.
|
||||
'''
|
||||
return libvlc_media_list_player_get_media_player(self)
|
||||
|
||||
|
||||
def set_media_list(self, p_mlist):
|
||||
'''Set the media list associated with the player.
|
||||
@param p_mlist: list of media.
|
||||
@ -3008,7 +2790,7 @@ class MediaPlayer(_Ctype):
|
||||
Use the vout called "macosx".
|
||||
The drawable is an NSObject that follow the VLCOpenGLVideoViewEmbedding
|
||||
protocol:
|
||||
@code.m
|
||||
@begincode
|
||||
\@protocol VLCOpenGLVideoViewEmbedding <NSObject>
|
||||
- (void)addVoutSubview:(NSView *)view;
|
||||
- (void)removeVoutSubview:(NSView *)view;
|
||||
@ -3017,7 +2799,7 @@ class MediaPlayer(_Ctype):
|
||||
Or it can be an NSView object.
|
||||
If you want to use it along with Qt4 see the QMacCocoaViewContainer. Then
|
||||
the following code should work:
|
||||
@code.mm
|
||||
@begincode
|
||||
|
||||
NSView *video = [[NSView alloc] init];
|
||||
QMacCocoaViewContainer *container = new QMacCocoaViewContainer(video, parent);
|
||||
@ -3039,36 +2821,29 @@ class MediaPlayer(_Ctype):
|
||||
|
||||
|
||||
def set_agl(self, drawable):
|
||||
'''\deprecated Use L{set_nsobject} instead.
|
||||
'''Set the agl handler where the media player should render its video output.
|
||||
@param drawable: the agl handler.
|
||||
'''
|
||||
return libvlc_media_player_set_agl(self, drawable)
|
||||
|
||||
|
||||
def get_agl(self):
|
||||
'''\deprecated Use L{get_nsobject} instead.
|
||||
'''Get the agl handler previously set with L{set_agl}().
|
||||
@return: the agl handler or 0 if none where set.
|
||||
'''
|
||||
return libvlc_media_player_get_agl(self)
|
||||
|
||||
|
||||
def set_xwindow(self, drawable):
|
||||
'''Set an X Window System drawable where the media player should render its
|
||||
video output. The call takes effect when the playback starts. If it is
|
||||
already started, it might need to be stopped before changes apply.
|
||||
If LibVLC was built without X11 output support, then this function has no
|
||||
effects.
|
||||
By default, LibVLC will capture input events on the video rendering area.
|
||||
Use L{video_set_mouse_input}() and L{video_set_key_input}() to
|
||||
disable that and deliver events to the parent window / to the application
|
||||
instead. By design, the X11 protocol delivers input events to only one
|
||||
recipient.
|
||||
@warning
|
||||
The application must call the XInitThreads() function from Xlib before
|
||||
L{new}(), and before any call to XOpenDisplay() directly or via any
|
||||
other library. Failure to call XInitThreads() will seriously impede LibVLC
|
||||
performance. Calling XOpenDisplay() before XInitThreads() will eventually
|
||||
crash the process. That is a limitation of Xlib.
|
||||
@param drawable: X11 window ID @note The specified identifier must correspond to an existing Input/Output class X11 window. Pixmaps are B{not} currently supported. The default X11 server is assumed, i.e. that specified in the DISPLAY environment variable. @warning LibVLC can deal with invalid X11 handle errors, however some display drivers (EGL, GLX, VA and/or VDPAU) can unfortunately not. Thus the window handle must remain valid until playback is stopped, otherwise the process may abort or crash.
|
||||
@bug No more than one window handle per media player instance can be specified. If the media has multiple simultaneously active video tracks, extra tracks will be rendered into external windows beyond the control of the application.
|
||||
video output. If LibVLC was built without X11 output support, then this has
|
||||
no effects.
|
||||
The specified identifier must correspond to an existing Input/Output class
|
||||
X11 window. Pixmaps are B{not} supported. The caller shall ensure that
|
||||
the X11 server is the same as the one the VLC instance has been configured
|
||||
with. This function must be called before video playback is started;
|
||||
otherwise it will only take effect after playback stop and restart.
|
||||
@param drawable: the ID of the X window.
|
||||
'''
|
||||
return libvlc_media_player_set_xwindow(self, drawable)
|
||||
|
||||
@ -3092,15 +2867,6 @@ class MediaPlayer(_Ctype):
|
||||
return libvlc_media_player_get_hwnd(self)
|
||||
|
||||
|
||||
def set_android_context(self, p_jvm, p_awindow_handler):
|
||||
'''Set the android context.
|
||||
@param p_jvm: the Java VM of the android process.
|
||||
@param awindow_handler: org.videolan.libvlc.IAWindowNativeHandler jobject implemented by the org.videolan.libvlc.MediaPlayer class from the libvlc-android project.
|
||||
@version: LibVLC 3.0.0 and later.
|
||||
'''
|
||||
return libvlc_media_player_set_android_context(self, p_jvm, p_awindow_handler)
|
||||
|
||||
|
||||
def audio_set_callbacks(self, play, pause, resume, flush, drain, opaque):
|
||||
'''Set callbacks and private data for decoded audio.
|
||||
Use L{audio_set_format}() or L{audio_set_format_callbacks}()
|
||||
@ -3668,7 +3434,7 @@ class MediaPlayer(_Ctype):
|
||||
@warning: Some audio output devices in the list might not actually work in
|
||||
some circumstances. By default, it is recommended to not specify any
|
||||
explicit audio device.
|
||||
@return: A None-terminated linked list of potential audio output devices. It must be freed with L{audio_output_device_list_release}().
|
||||
@return: A None-terminated linked list of potential audio output devices. It must be freed it with L{audio_output_device_list_release}().
|
||||
@version: LibVLC 2.2.0 or later.
|
||||
'''
|
||||
return libvlc_audio_output_device_enum(self)
|
||||
@ -3702,24 +3468,6 @@ class MediaPlayer(_Ctype):
|
||||
return libvlc_audio_output_device_set(self, str_to_bytes(module), str_to_bytes(device_id))
|
||||
|
||||
|
||||
def audio_output_device_get(self):
|
||||
'''Get the current audio output device identifier.
|
||||
This complements L{audio_output_device_set}().
|
||||
@warning: The initial value for the current audio output device identifier
|
||||
may not be set or may be some unknown value. A LibVLC application should
|
||||
compare this value against the known device identifiers (e.g. those that
|
||||
were previously retrieved by a call to L{audio_output_device_enum} or
|
||||
L{audio_output_device_list_get}) to find the current audio output device.
|
||||
It is possible that the selected audio output device changes (an external
|
||||
change) without a call to L{audio_output_device_set}. That may make this
|
||||
method unsuitable to use if a LibVLC application is attempting to track
|
||||
dynamic audio device changes as they happen.
|
||||
@return: the current audio output device identifier None if no device is selected or in case of error (the result must be released with free() or L{free}()).
|
||||
@version: LibVLC 3.0.0 or later.
|
||||
'''
|
||||
return libvlc_audio_output_device_get(self)
|
||||
|
||||
|
||||
def audio_toggle_mute(self):
|
||||
'''Toggle mute status.
|
||||
'''
|
||||
@ -3872,27 +3620,6 @@ def libvlc_new(argc, argv):
|
||||
'''Create and initialize a libvlc instance.
|
||||
This functions accept a list of "command line" arguments similar to the
|
||||
main(). These arguments affect the LibVLC instance default configuration.
|
||||
@note
|
||||
LibVLC may create threads. Therefore, any thread-unsafe process
|
||||
initialization must be performed before calling L{libvlc_new}(). In particular
|
||||
and where applicable:
|
||||
- setlocale() and textdomain(),
|
||||
- setenv(), unsetenv() and putenv(),
|
||||
- with the X11 display system, XInitThreads()
|
||||
(see also L{libvlc_media_player_set_xwindow}()) and
|
||||
- on Microsoft Windows, SetErrorMode().
|
||||
- sigprocmask() shall never be invoked; pthread_sigmask() can be used.
|
||||
On POSIX systems, the SIGCHLD signal must B{not} be ignored, i.e. the
|
||||
signal handler must set to SIG_DFL or a function pointer, not SIG_IGN.
|
||||
Also while LibVLC is active, the wait() function shall not be called, and
|
||||
any call to waitpid() shall use a strictly positive value for the first
|
||||
parameter (i.e. the PID). Failure to follow those rules may lead to a
|
||||
deadlock or a busy loop.
|
||||
Also on POSIX systems, it is recommended that the SIGPIPE signal be blocked,
|
||||
even if it is not, in principles, necessary.
|
||||
On Microsoft Windows Vista/2008, the process error mode
|
||||
SEM_FAILCRITICALERRORS flag B{must} with the SetErrorMode() function
|
||||
before using LibVLC. On later versions, it is optional and unnecessary.
|
||||
@param argc: the number of arguments (should be 0).
|
||||
@param argv: list of arguments (should be None).
|
||||
@return: the libvlc instance or None in case of error.
|
||||
@ -4202,22 +3929,6 @@ def libvlc_media_new_fd(p_instance, fd):
|
||||
ctypes.c_void_p, Instance, ctypes.c_int)
|
||||
return f(p_instance, fd)
|
||||
|
||||
def libvlc_media_new_callbacks(instance, open_cb, read_cb, seek_cb, close_cb, opaque):
|
||||
'''Create a media with custom callbacks to read the data from.
|
||||
@param instance: LibVLC instance.
|
||||
@param open_cb: callback to open the custom bitstream input media.
|
||||
@param read_cb: callback to read data (must not be None).
|
||||
@param seek_cb: callback to seek, or None if seeking is not supported.
|
||||
@param close_cb: callback to close the media, or None if unnecessary.
|
||||
@param opaque: data pointer for the open callback.
|
||||
@return: the newly created media or None on error @note If open_cb is None, the opaque pointer will be passed to read_cb, seek_cb and close_cb, and the stream size will be treated as unknown. @note The callbacks may be called asynchronously (from another thread). A single stream instance need not be reentrant. However the open_cb needs to be reentrant if the media is used by multiple player instances. @warning The callbacks may be used until all or any player instances that were supplied the media item are stopped. See L{libvlc_media_release}.
|
||||
@version: LibVLC 3.0.0 and later.
|
||||
'''
|
||||
f = _Cfunctions.get('libvlc_media_new_callbacks', None) or \
|
||||
_Cfunction('libvlc_media_new_callbacks', ((1,), (1,), (1,), (1,), (1,), (1,),), class_result(Media),
|
||||
ctypes.c_void_p, Instance, MediaOpenCb, MediaReadCb, MediaSeekCb, MediaCloseCb, ctypes.c_void_p)
|
||||
return f(instance, open_cb, read_cb, seek_cb, close_cb, opaque)
|
||||
|
||||
def libvlc_media_new_as_node(p_instance, psz_name):
|
||||
'''Create a media as an empty node with a given name.
|
||||
See L{libvlc_media_release}.
|
||||
@ -4413,7 +4124,7 @@ def libvlc_media_get_duration(p_md):
|
||||
|
||||
def libvlc_media_parse(p_md):
|
||||
'''Parse a media.
|
||||
This fetches (local) art, meta data and tracks information.
|
||||
This fetches (local) meta data and tracks information.
|
||||
The method is synchronous.
|
||||
See L{libvlc_media_parse_async}
|
||||
See L{libvlc_media_get_meta}
|
||||
@ -4427,7 +4138,7 @@ def libvlc_media_parse(p_md):
|
||||
|
||||
def libvlc_media_parse_async(p_md):
|
||||
'''Parse a media.
|
||||
This fetches (local) art, meta data and tracks information.
|
||||
This fetches (local) meta data and tracks information.
|
||||
The method is the asynchronous of L{libvlc_media_parse}().
|
||||
To track when this is over you can listen to libvlc_MediaParsedChanged
|
||||
event. However if the media was already parsed you will not receive this
|
||||
@ -4443,30 +4154,6 @@ def libvlc_media_parse_async(p_md):
|
||||
None, Media)
|
||||
return f(p_md)
|
||||
|
||||
def libvlc_media_parse_with_options(p_md, parse_flag):
|
||||
'''Parse the media asynchronously with options.
|
||||
This fetches (local or network) art, meta data and/or tracks information.
|
||||
This method is the extended version of L{libvlc_media_parse_async}().
|
||||
To track when this is over you can listen to libvlc_MediaParsedChanged
|
||||
event. However if this functions returns an error, you will not receive this
|
||||
event.
|
||||
It uses a flag to specify parse options (see libvlc_media_parse_flag_t). All
|
||||
these flags can be combined. By default, media is parsed if it's a local
|
||||
file.
|
||||
See libvlc_MediaParsedChanged
|
||||
See L{libvlc_media_get_meta}
|
||||
See L{libvlc_media_tracks_get}
|
||||
See libvlc_media_parse_flag_t.
|
||||
@param p_md: media descriptor object.
|
||||
@param parse_flag: parse options:
|
||||
@return: -1 in case of error, 0 otherwise.
|
||||
@version: LibVLC 3.0.0 or later.
|
||||
'''
|
||||
f = _Cfunctions.get('libvlc_media_parse_with_options', None) or \
|
||||
_Cfunction('libvlc_media_parse_with_options', ((1,), (1,),), None,
|
||||
ctypes.c_int, Media, MediaParseFlag)
|
||||
return f(p_md, parse_flag)
|
||||
|
||||
def libvlc_media_is_parsed(p_md):
|
||||
'''Get Parsed status for media descriptor object.
|
||||
See libvlc_MediaParsedChanged.
|
||||
@ -4516,18 +4203,6 @@ def libvlc_media_tracks_get(p_md, tracks):
|
||||
ctypes.c_uint, Media, ctypes.POINTER(ctypes.POINTER(MediaTrack)))
|
||||
return f(p_md, tracks)
|
||||
|
||||
def libvlc_media_get_codec_description(i_type, i_codec):
|
||||
'''Get codec description from media elementary stream.
|
||||
@param i_type: i_type from L{MediaTrack}.
|
||||
@param i_codec: i_codec or i_original_fourcc from L{MediaTrack}.
|
||||
@return: codec description.
|
||||
@version: LibVLC 3.0.0 and later. See L{MediaTrack}.
|
||||
'''
|
||||
f = _Cfunctions.get('libvlc_media_get_codec_description', None) or \
|
||||
_Cfunction('libvlc_media_get_codec_description', ((1,), (1,),), None,
|
||||
ctypes.c_char_p, TrackType, ctypes.c_uint32)
|
||||
return f(i_type, i_codec)
|
||||
|
||||
def libvlc_media_tracks_release(p_tracks, i_count):
|
||||
'''Release media descriptor's elementary streams description array.
|
||||
@param p_tracks: tracks info array to release.
|
||||
@ -4539,63 +4214,17 @@ def libvlc_media_tracks_release(p_tracks, i_count):
|
||||
None, ctypes.POINTER(MediaTrack), ctypes.c_uint)
|
||||
return f(p_tracks, i_count)
|
||||
|
||||
def libvlc_media_get_type(p_md):
|
||||
'''Get the media type of the media descriptor object.
|
||||
@param p_md: media descriptor object.
|
||||
@return: media type.
|
||||
@version: LibVLC 3.0.0 and later. See libvlc_media_type_t.
|
||||
'''
|
||||
f = _Cfunctions.get('libvlc_media_get_type', None) or \
|
||||
_Cfunction('libvlc_media_get_type', ((1,),), None,
|
||||
MediaType, Media)
|
||||
return f(p_md)
|
||||
|
||||
def libvlc_media_discoverer_new(p_inst, psz_name):
|
||||
'''Create a media discoverer object by name.
|
||||
After this object is created, you should attach to events in order to be
|
||||
notified of the discoverer state.
|
||||
You should also attach to media_list events in order to be notified of new
|
||||
items discovered.
|
||||
You need to call L{libvlc_media_discoverer_start}() in order to start the
|
||||
discovery.
|
||||
See L{libvlc_media_discoverer_media_list}
|
||||
See L{libvlc_media_discoverer_event_manager}
|
||||
See L{libvlc_media_discoverer_start}.
|
||||
def libvlc_media_discoverer_new_from_name(p_inst, psz_name):
|
||||
'''Discover media service by name.
|
||||
@param p_inst: libvlc instance.
|
||||
@param psz_name: service name.
|
||||
@return: media discover object or None in case of error.
|
||||
@version: LibVLC 3.0.0 or later.
|
||||
'''
|
||||
f = _Cfunctions.get('libvlc_media_discoverer_new', None) or \
|
||||
_Cfunction('libvlc_media_discoverer_new', ((1,), (1,),), class_result(MediaDiscoverer),
|
||||
f = _Cfunctions.get('libvlc_media_discoverer_new_from_name', None) or \
|
||||
_Cfunction('libvlc_media_discoverer_new_from_name', ((1,), (1,),), class_result(MediaDiscoverer),
|
||||
ctypes.c_void_p, Instance, ctypes.c_char_p)
|
||||
return f(p_inst, psz_name)
|
||||
|
||||
def libvlc_media_discoverer_start(p_mdis):
|
||||
'''Start media discovery.
|
||||
To stop it, call L{libvlc_media_discoverer_stop}() or
|
||||
L{libvlc_media_discoverer_release}() directly.
|
||||
See L{libvlc_media_discoverer_stop}.
|
||||
@param p_mdis: media discover object.
|
||||
@return: -1 in case of error, 0 otherwise.
|
||||
@version: LibVLC 3.0.0 or later.
|
||||
'''
|
||||
f = _Cfunctions.get('libvlc_media_discoverer_start', None) or \
|
||||
_Cfunction('libvlc_media_discoverer_start', ((1,),), None,
|
||||
ctypes.c_int, MediaDiscoverer)
|
||||
return f(p_mdis)
|
||||
|
||||
def libvlc_media_discoverer_stop(p_mdis):
|
||||
'''Stop media discovery.
|
||||
See L{libvlc_media_discoverer_start}.
|
||||
@param p_mdis: media discover object.
|
||||
@version: LibVLC 3.0.0 or later.
|
||||
'''
|
||||
f = _Cfunctions.get('libvlc_media_discoverer_stop', None) or \
|
||||
_Cfunction('libvlc_media_discoverer_stop', ((1,),), None,
|
||||
None, MediaDiscoverer)
|
||||
return f(p_mdis)
|
||||
|
||||
def libvlc_media_discoverer_release(p_mdis):
|
||||
'''Release media discover object. If the reference count reaches 0, then
|
||||
the object will be released.
|
||||
@ -4916,16 +4545,6 @@ def libvlc_media_list_player_set_media_player(p_mlp, p_mi):
|
||||
None, MediaListPlayer, MediaPlayer)
|
||||
return f(p_mlp, p_mi)
|
||||
|
||||
def libvlc_media_list_player_get_media_player(p_mlp):
|
||||
'''Get media player of the media_list_player instance.
|
||||
@param p_mlp: media list player instance.
|
||||
@return: media player instance @note the caller is responsible for releasing the returned instance.
|
||||
'''
|
||||
f = _Cfunctions.get('libvlc_media_list_player_get_media_player', None) or \
|
||||
_Cfunction('libvlc_media_list_player_get_media_player', ((1,),), class_result(MediaPlayer),
|
||||
ctypes.c_void_p, MediaListPlayer)
|
||||
return f(p_mlp)
|
||||
|
||||
def libvlc_media_list_player_set_media_list(p_mlp, p_mlist):
|
||||
'''Set the media list associated with the player.
|
||||
@param p_mlp: media list player instance.
|
||||
@ -5210,7 +4829,7 @@ def libvlc_media_player_set_nsobject(p_mi, drawable):
|
||||
Use the vout called "macosx".
|
||||
The drawable is an NSObject that follow the VLCOpenGLVideoViewEmbedding
|
||||
protocol:
|
||||
@code.m
|
||||
@begincode
|
||||
\@protocol VLCOpenGLVideoViewEmbedding <NSObject>
|
||||
- (void)addVoutSubview:(NSView *)view;
|
||||
- (void)removeVoutSubview:(NSView *)view;
|
||||
@ -5219,7 +4838,7 @@ def libvlc_media_player_set_nsobject(p_mi, drawable):
|
||||
Or it can be an NSView object.
|
||||
If you want to use it along with Qt4 see the QMacCocoaViewContainer. Then
|
||||
the following code should work:
|
||||
@code.mm
|
||||
@begincode
|
||||
|
||||
NSView *video = [[NSView alloc] init];
|
||||
QMacCocoaViewContainer *container = new QMacCocoaViewContainer(video, parent);
|
||||
@ -5247,7 +4866,9 @@ def libvlc_media_player_get_nsobject(p_mi):
|
||||
return f(p_mi)
|
||||
|
||||
def libvlc_media_player_set_agl(p_mi, drawable):
|
||||
'''\deprecated Use L{libvlc_media_player_set_nsobject} instead.
|
||||
'''Set the agl handler where the media player should render its video output.
|
||||
@param p_mi: the Media Player.
|
||||
@param drawable: the agl handler.
|
||||
'''
|
||||
f = _Cfunctions.get('libvlc_media_player_set_agl', None) or \
|
||||
_Cfunction('libvlc_media_player_set_agl', ((1,), (1,),), None,
|
||||
@ -5255,7 +4876,9 @@ def libvlc_media_player_set_agl(p_mi, drawable):
|
||||
return f(p_mi, drawable)
|
||||
|
||||
def libvlc_media_player_get_agl(p_mi):
|
||||
'''\deprecated Use L{libvlc_media_player_get_nsobject} instead.
|
||||
'''Get the agl handler previously set with L{libvlc_media_player_set_agl}().
|
||||
@param p_mi: the Media Player.
|
||||
@return: the agl handler or 0 if none where set.
|
||||
'''
|
||||
f = _Cfunctions.get('libvlc_media_player_get_agl', None) or \
|
||||
_Cfunction('libvlc_media_player_get_agl', ((1,),), None,
|
||||
@ -5264,24 +4887,15 @@ def libvlc_media_player_get_agl(p_mi):
|
||||
|
||||
def libvlc_media_player_set_xwindow(p_mi, drawable):
|
||||
'''Set an X Window System drawable where the media player should render its
|
||||
video output. The call takes effect when the playback starts. If it is
|
||||
already started, it might need to be stopped before changes apply.
|
||||
If LibVLC was built without X11 output support, then this function has no
|
||||
effects.
|
||||
By default, LibVLC will capture input events on the video rendering area.
|
||||
Use L{libvlc_video_set_mouse_input}() and L{libvlc_video_set_key_input}() to
|
||||
disable that and deliver events to the parent window / to the application
|
||||
instead. By design, the X11 protocol delivers input events to only one
|
||||
recipient.
|
||||
@warning
|
||||
The application must call the XInitThreads() function from Xlib before
|
||||
L{libvlc_new}(), and before any call to XOpenDisplay() directly or via any
|
||||
other library. Failure to call XInitThreads() will seriously impede LibVLC
|
||||
performance. Calling XOpenDisplay() before XInitThreads() will eventually
|
||||
crash the process. That is a limitation of Xlib.
|
||||
@param p_mi: media player.
|
||||
@param drawable: X11 window ID @note The specified identifier must correspond to an existing Input/Output class X11 window. Pixmaps are B{not} currently supported. The default X11 server is assumed, i.e. that specified in the DISPLAY environment variable. @warning LibVLC can deal with invalid X11 handle errors, however some display drivers (EGL, GLX, VA and/or VDPAU) can unfortunately not. Thus the window handle must remain valid until playback is stopped, otherwise the process may abort or crash.
|
||||
@bug No more than one window handle per media player instance can be specified. If the media has multiple simultaneously active video tracks, extra tracks will be rendered into external windows beyond the control of the application.
|
||||
video output. If LibVLC was built without X11 output support, then this has
|
||||
no effects.
|
||||
The specified identifier must correspond to an existing Input/Output class
|
||||
X11 window. Pixmaps are B{not} supported. The caller shall ensure that
|
||||
the X11 server is the same as the one the VLC instance has been configured
|
||||
with. This function must be called before video playback is started;
|
||||
otherwise it will only take effect after playback stop and restart.
|
||||
@param p_mi: the Media Player.
|
||||
@param drawable: the ID of the X window.
|
||||
'''
|
||||
f = _Cfunctions.get('libvlc_media_player_set_xwindow', None) or \
|
||||
_Cfunction('libvlc_media_player_set_xwindow', ((1,), (1,),), None,
|
||||
@ -5325,18 +4939,6 @@ def libvlc_media_player_get_hwnd(p_mi):
|
||||
ctypes.c_void_p, MediaPlayer)
|
||||
return f(p_mi)
|
||||
|
||||
def libvlc_media_player_set_android_context(p_mi, p_jvm, p_awindow_handler):
|
||||
'''Set the android context.
|
||||
@param p_mi: the media player.
|
||||
@param p_jvm: the Java VM of the android process.
|
||||
@param awindow_handler: org.videolan.libvlc.IAWindowNativeHandler jobject implemented by the org.videolan.libvlc.MediaPlayer class from the libvlc-android project.
|
||||
@version: LibVLC 3.0.0 and later.
|
||||
'''
|
||||
f = _Cfunctions.get('libvlc_media_player_set_android_context', None) or \
|
||||
_Cfunction('libvlc_media_player_set_android_context', ((1,), (1,), (1,),), None,
|
||||
None, MediaPlayer, ctypes.c_void_p, ctypes.c_void_p)
|
||||
return f(p_mi, p_jvm, p_awindow_handler)
|
||||
|
||||
def libvlc_audio_set_callbacks(mp, play, pause, resume, flush, drain, opaque):
|
||||
'''Set callbacks and private data for decoded audio.
|
||||
Use L{libvlc_audio_set_format}() or L{libvlc_audio_set_format_callbacks}()
|
||||
@ -5842,7 +5444,7 @@ def libvlc_video_get_spu_count(p_mi):
|
||||
def libvlc_video_get_spu_description(p_mi):
|
||||
'''Get the description of available video subtitles.
|
||||
@param p_mi: the media player.
|
||||
@return: list containing description of available video subtitles. It must be freed with L{libvlc_track_description_list_release}().
|
||||
@return: list containing description of available video subtitles.
|
||||
'''
|
||||
f = _Cfunctions.get('libvlc_video_get_spu_description', None) or \
|
||||
_Cfunction('libvlc_video_get_spu_description', ((1,),), None,
|
||||
@ -5898,52 +5500,26 @@ def libvlc_video_set_spu_delay(p_mi, i_delay):
|
||||
ctypes.c_int, MediaPlayer, ctypes.c_int64)
|
||||
return f(p_mi, i_delay)
|
||||
|
||||
def libvlc_media_player_get_full_title_descriptions(p_mi, titles):
|
||||
'''Get the full description of available titles.
|
||||
def libvlc_video_get_title_description(p_mi):
|
||||
'''Get the description of available titles.
|
||||
@param p_mi: the media player.
|
||||
@param address: to store an allocated array of title descriptions descriptions (must be freed with L{libvlc_title_descriptions_release}() by the caller) [OUT].
|
||||
@return: the number of titles (-1 on error).
|
||||
@version: LibVLC 3.0.0 and later.
|
||||
@return: list containing description of available titles.
|
||||
'''
|
||||
f = _Cfunctions.get('libvlc_media_player_get_full_title_descriptions', None) or \
|
||||
_Cfunction('libvlc_media_player_get_full_title_descriptions', ((1,), (1,),), None,
|
||||
ctypes.c_int, MediaPlayer, ctypes.POINTER(ctypes.POINTER(TitleDescription)))
|
||||
return f(p_mi, titles)
|
||||
f = _Cfunctions.get('libvlc_video_get_title_description', None) or \
|
||||
_Cfunction('libvlc_video_get_title_description', ((1,),), None,
|
||||
ctypes.POINTER(TrackDescription), MediaPlayer)
|
||||
return f(p_mi)
|
||||
|
||||
def libvlc_title_descriptions_release(p_titles, i_count):
|
||||
'''Release a title description.
|
||||
@param title: description array to release.
|
||||
@param number: of title descriptions to release.
|
||||
@version: LibVLC 3.0.0 and later.
|
||||
'''
|
||||
f = _Cfunctions.get('libvlc_title_descriptions_release', None) or \
|
||||
_Cfunction('libvlc_title_descriptions_release', ((1,), (1,),), None,
|
||||
None, ctypes.POINTER(TitleDescription), ctypes.c_uint)
|
||||
return f(p_titles, i_count)
|
||||
|
||||
def libvlc_media_player_get_full_chapter_descriptions(p_mi, i_chapters_of_title, pp_chapters):
|
||||
'''Get the full description of available chapters.
|
||||
def libvlc_video_get_chapter_description(p_mi, i_title):
|
||||
'''Get the description of available chapters for specific title.
|
||||
@param p_mi: the media player.
|
||||
@param index: of the title to query for chapters (uses current title if set to -1).
|
||||
@param address: to store an allocated array of chapter descriptions descriptions (must be freed with L{libvlc_chapter_descriptions_release}() by the caller) [OUT].
|
||||
@return: the number of chapters (-1 on error).
|
||||
@version: LibVLC 3.0.0 and later.
|
||||
@param i_title: selected title.
|
||||
@return: list containing description of available chapter for title i_title.
|
||||
'''
|
||||
f = _Cfunctions.get('libvlc_media_player_get_full_chapter_descriptions', None) or \
|
||||
_Cfunction('libvlc_media_player_get_full_chapter_descriptions', ((1,), (1,), (1,),), None,
|
||||
ctypes.c_int, MediaPlayer, ctypes.c_int, ctypes.POINTER(ctypes.POINTER(ChapterDescription)))
|
||||
return f(p_mi, i_chapters_of_title, pp_chapters)
|
||||
|
||||
def libvlc_chapter_descriptions_release(p_chapters, i_count):
|
||||
'''Release a chapter description.
|
||||
@param chapter: description array to release.
|
||||
@param number: of chapter descriptions to release.
|
||||
@version: LibVLC 3.0.0 and later.
|
||||
'''
|
||||
f = _Cfunctions.get('libvlc_chapter_descriptions_release', None) or \
|
||||
_Cfunction('libvlc_chapter_descriptions_release', ((1,), (1,),), None,
|
||||
None, ctypes.POINTER(ChapterDescription), ctypes.c_uint)
|
||||
return f(p_chapters, i_count)
|
||||
f = _Cfunctions.get('libvlc_video_get_chapter_description', None) or \
|
||||
_Cfunction('libvlc_video_get_chapter_description', ((1,), (1,),), None,
|
||||
ctypes.POINTER(TrackDescription), MediaPlayer, ctypes.c_int)
|
||||
return f(p_mi, i_title)
|
||||
|
||||
def libvlc_video_get_crop_geometry(p_mi):
|
||||
'''Get current crop filter geometry.
|
||||
@ -6007,7 +5583,7 @@ def libvlc_video_get_track_count(p_mi):
|
||||
def libvlc_video_get_track_description(p_mi):
|
||||
'''Get the description of available video tracks.
|
||||
@param p_mi: media player.
|
||||
@return: list with description of available video tracks, or None on error. It must be freed with L{libvlc_track_description_list_release}().
|
||||
@return: list with description of available video tracks, or None on error.
|
||||
'''
|
||||
f = _Cfunctions.get('libvlc_video_get_track_description', None) or \
|
||||
_Cfunction('libvlc_video_get_track_description', ((1,),), None,
|
||||
@ -6194,7 +5770,7 @@ def libvlc_video_set_adjust_float(p_mi, option, value):
|
||||
def libvlc_audio_output_list_get(p_instance):
|
||||
'''Gets the list of available audio output modules.
|
||||
@param p_instance: libvlc instance.
|
||||
@return: list of available audio outputs. It must be freed with In case of error, None is returned.
|
||||
@return: list of available audio outputs. It must be freed it with In case of error, None is returned.
|
||||
'''
|
||||
f = _Cfunctions.get('libvlc_audio_output_list_get', None) or \
|
||||
_Cfunction('libvlc_audio_output_list_get', ((1,),), None,
|
||||
@ -6233,7 +5809,7 @@ def libvlc_audio_output_device_enum(mp):
|
||||
some circumstances. By default, it is recommended to not specify any
|
||||
explicit audio device.
|
||||
@param mp: media player.
|
||||
@return: A None-terminated linked list of potential audio output devices. It must be freed with L{libvlc_audio_output_device_list_release}().
|
||||
@return: A None-terminated linked list of potential audio output devices. It must be freed it with L{libvlc_audio_output_device_list_release}().
|
||||
@version: LibVLC 2.2.0 or later.
|
||||
'''
|
||||
f = _Cfunctions.get('libvlc_audio_output_device_enum', None) or \
|
||||
@ -6253,7 +5829,7 @@ def libvlc_audio_output_device_list_get(p_instance, aout):
|
||||
explicit audio device.
|
||||
@param p_instance: libvlc instance.
|
||||
@param psz_aout: audio output name (as returned by L{libvlc_audio_output_list_get}()).
|
||||
@return: A None-terminated linked list of potential audio output devices. It must be freed with L{libvlc_audio_output_device_list_release}().
|
||||
@return: A None-terminated linked list of potential audio output devices. It must be freed it with L{libvlc_audio_output_device_list_release}().
|
||||
@version: LibVLC 2.1.0 or later.
|
||||
'''
|
||||
f = _Cfunctions.get('libvlc_audio_output_device_list_get', None) or \
|
||||
@ -6302,27 +5878,6 @@ def libvlc_audio_output_device_set(mp, module, device_id):
|
||||
None, MediaPlayer, ctypes.c_char_p, ctypes.c_char_p)
|
||||
return f(mp, module, device_id)
|
||||
|
||||
def libvlc_audio_output_device_get(mp):
|
||||
'''Get the current audio output device identifier.
|
||||
This complements L{libvlc_audio_output_device_set}().
|
||||
@warning: The initial value for the current audio output device identifier
|
||||
may not be set or may be some unknown value. A LibVLC application should
|
||||
compare this value against the known device identifiers (e.g. those that
|
||||
were previously retrieved by a call to L{libvlc_audio_output_device_enum} or
|
||||
L{libvlc_audio_output_device_list_get}) to find the current audio output device.
|
||||
It is possible that the selected audio output device changes (an external
|
||||
change) without a call to L{libvlc_audio_output_device_set}. That may make this
|
||||
method unsuitable to use if a LibVLC application is attempting to track
|
||||
dynamic audio device changes as they happen.
|
||||
@param mp: media player.
|
||||
@return: the current audio output device identifier None if no device is selected or in case of error (the result must be released with free() or L{libvlc_free}()).
|
||||
@version: LibVLC 3.0.0 or later.
|
||||
'''
|
||||
f = _Cfunctions.get('libvlc_audio_output_device_get', None) or \
|
||||
_Cfunction('libvlc_audio_output_device_get', ((1,),), None,
|
||||
ctypes.c_char_p, MediaPlayer)
|
||||
return f(mp)
|
||||
|
||||
def libvlc_audio_toggle_mute(p_mi):
|
||||
'''Toggle mute status.
|
||||
@param p_mi: media player @warning Toggling mute atomically is not always possible: On some platforms, other processes can mute the VLC audio playback stream asynchronously. Thus, there is a small race condition where toggling will not work. See also the limitations of L{libvlc_audio_set_mute}().
|
||||
@ -6386,7 +5941,7 @@ def libvlc_audio_get_track_count(p_mi):
|
||||
def libvlc_audio_get_track_description(p_mi):
|
||||
'''Get the description of available audio tracks.
|
||||
@param p_mi: media player.
|
||||
@return: list with description of available audio tracks, or None. It must be freed with L{libvlc_track_description_list_release}().
|
||||
@return: list with description of available audio tracks, or None.
|
||||
'''
|
||||
f = _Cfunctions.get('libvlc_audio_get_track_description', None) or \
|
||||
_Cfunction('libvlc_audio_get_track_description', ((1,),), None,
|
||||
@ -6939,7 +6494,7 @@ def libvlc_vlm_get_event_manager(p_instance):
|
||||
# libvlc_printerr
|
||||
# libvlc_set_exit_handler
|
||||
|
||||
# 31 function(s) not wrapped as methods:
|
||||
# 28 function(s) not wrapped as methods:
|
||||
# libvlc_audio_equalizer_get_amp_at_index
|
||||
# libvlc_audio_equalizer_get_band_count
|
||||
# libvlc_audio_equalizer_get_band_frequency
|
||||
@ -6953,7 +6508,6 @@ def libvlc_vlm_get_event_manager(p_instance):
|
||||
# libvlc_audio_equalizer_set_preamp
|
||||
# libvlc_audio_output_device_list_release
|
||||
# libvlc_audio_output_list_release
|
||||
# libvlc_chapter_descriptions_release
|
||||
# libvlc_clearerr
|
||||
# libvlc_clock
|
||||
# libvlc_errmsg
|
||||
@ -6964,11 +6518,9 @@ def libvlc_vlm_get_event_manager(p_instance):
|
||||
# libvlc_get_version
|
||||
# libvlc_log_get_context
|
||||
# libvlc_log_get_object
|
||||
# libvlc_media_get_codec_description
|
||||
# libvlc_media_tracks_release
|
||||
# libvlc_module_description_list_release
|
||||
# libvlc_new
|
||||
# libvlc_title_descriptions_release
|
||||
# libvlc_track_description_list_release
|
||||
# libvlc_vprinterr
|
||||
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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')
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
@ -293,14 +293,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):
|
||||
"""
|
||||
@ -1139,6 +1131,8 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
|
||||
:param item: The service item to be checked
|
||||
"""
|
||||
pos = item.data(0, QtCore.Qt.UserRole)
|
||||
# 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,6 +1151,8 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
|
||||
:param item: The service item to be checked
|
||||
"""
|
||||
pos = item.data(0, QtCore.Qt.UserRole)
|
||||
# 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):
|
||||
@ -1585,7 +1581,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 +1594,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,7 +1652,14 @@ 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):
|
||||
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
|
||||
|
||||
@ -1667,16 +1670,3 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
|
||||
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
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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
|
||||
@ -828,13 +828,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 +886,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 +1125,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 +1138,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 +1421,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))
|
||||
|
@ -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):
|
||||
|
@ -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
71
openlp/core/utils/db.py
Normal 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)
|
@ -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.
|
||||
"""
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
"""
|
||||
|
@ -69,8 +69,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):
|
||||
"""
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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.')
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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))
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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 '
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
"""
|
||||
|
@ -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,6 +350,7 @@ 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
|
||||
else:
|
||||
if self.handler == self.media_item.automatic:
|
||||
self.handler = self.media_item.find_controller_by_type(file)
|
||||
if not self.handler:
|
||||
|
@ -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.
|
||||
"""
|
||||
|
@ -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];
|
||||
|
@ -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)
|
||||
|
@ -88,7 +88,8 @@ class RemotesPlugin(Plugin):
|
||||
self.server.stop_server()
|
||||
self.server = None
|
||||
|
||||
def about(self):
|
||||
@staticmethod
|
||||
def about():
|
||||
"""
|
||||
Information about this plugin
|
||||
"""
|
||||
|
@ -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)
|
||||
|
@ -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'))
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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:'))
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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'))
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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',
|
||||
|
@ -292,7 +292,7 @@ class EasyWorshipSongImport(SongImport):
|
||||
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')
|
||||
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)
|
||||
@ -300,16 +300,16 @@ class EasyWorshipSongImport(SongImport):
|
||||
authors = self.get_field(fi_author)
|
||||
words = self.get_field(fi_words)
|
||||
if copy:
|
||||
self.copyright = copy.decode('unicode-escape')
|
||||
self.copyright = copy.decode(self.encoding)
|
||||
if admin:
|
||||
if copy:
|
||||
self.copyright += ', '
|
||||
self.copyright += translate('SongsPlugin.EasyWorshipSongImport',
|
||||
'Administered by %s') % admin.decode('unicode-escape')
|
||||
'Administered by %s') % admin.decode(self.encoding)
|
||||
if ccli:
|
||||
self.ccli_number = ccli.decode('unicode-escape')
|
||||
self.ccli_number = ccli.decode(self.encoding)
|
||||
if authors:
|
||||
authors = authors.decode('unicode-escape')
|
||||
authors = authors.decode(self.encoding)
|
||||
else:
|
||||
authors = ''
|
||||
# Set the SongImport object members.
|
||||
@ -497,7 +497,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):
|
||||
"""
|
||||
|
@ -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 ''
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
from openlp.plugins.songs.lib.ui import SongStrings
|
||||
from openlp.plugins.songs.lib.openlyricsxml import OpenLyrics, SongXML
|
||||
|
||||
@ -112,6 +112,7 @@ class SongMediaItem(MediaManagerItem):
|
||||
|
||||
def on_focus(self):
|
||||
self.search_text_edit.setFocus()
|
||||
self.search_text_edit.selectAll()
|
||||
|
||||
def config_update(self):
|
||||
"""
|
||||
@ -151,7 +152,7 @@ class SongMediaItem(MediaManagerItem):
|
||||
(SongSearch.Authors, ':/songs/song_search_author.png', SongStrings.Authors,
|
||||
translate('SongsPlugin.MediaItem', 'Search Authors...')),
|
||||
(SongSearch.Books, ':/songs/song_book_edit.png', SongStrings.SongBooks,
|
||||
translate('SongsPlugin.MediaItem', 'Search Song Books...')),
|
||||
translate('SongsPlugin.MediaItem', 'Search Songbooks...')),
|
||||
(SongSearch.Themes, ':/slides/slide_theme.png', UiStrings().Themes, UiStrings().SearchThemes)
|
||||
])
|
||||
self.search_text_edit.set_current_search_type(Settings().value('%s/last search type' % self.settings_section))
|
||||
@ -184,17 +185,8 @@ class SongMediaItem(MediaManagerItem):
|
||||
Author, Author.display_name.like(search_string), Author.display_name.asc())
|
||||
self.display_results_author(search_results)
|
||||
elif search_type == SongSearch.Books:
|
||||
log.debug('Books Search')
|
||||
search_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)
|
||||
log.debug('Songbook Search')
|
||||
self.display_results_book(search_keywords)
|
||||
elif search_type == SongSearch.Themes:
|
||||
log.debug('Theme Search')
|
||||
search_string = '%' + search_keywords + '%'
|
||||
@ -254,20 +246,28 @@ 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):
|
||||
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()))
|
||||
for song in songs:
|
||||
# Do not display temporary songs
|
||||
if song.temporary:
|
||||
|
||||
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 song_number and song_number not in song.song_number:
|
||||
if search_book.lower() not in songbook_entry.songbook.name.lower():
|
||||
continue
|
||||
song_detail = '%s - %s (%s)' % (book.name, song.song_number, song.title)
|
||||
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, song.id)
|
||||
song_name.setData(QtCore.Qt.UserRole, songbook_entry.song.id)
|
||||
self.list_view.addItem(song_name)
|
||||
|
||||
def on_clear_text_button_click(self):
|
||||
@ -363,8 +363,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 +523,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'))
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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
|
||||
|
||||
|
@ -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,12 +48,13 @@ 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?'),
|
||||
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.StandardButtons(QtWidgets.QMessageBox.Yes |
|
||||
QtWidgets.QMessageBox.No),
|
||||
QtWidgets.QMessageBox.No)
|
||||
if ret == QtWidgets.QMessageBox.Yes:
|
||||
delete_date = self.delete_calendar.selectedDate().toPyDate()
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
4253
resources/i18n/af.ts
4253
resources/i18n/af.ts
File diff suppressed because it is too large
Load Diff
4198
resources/i18n/bg.ts
4198
resources/i18n/bg.ts
File diff suppressed because it is too large
Load Diff
4334
resources/i18n/cs.ts
4334
resources/i18n/cs.ts
File diff suppressed because it is too large
Load Diff
4351
resources/i18n/da.ts
4351
resources/i18n/da.ts
File diff suppressed because it is too large
Load Diff
4380
resources/i18n/de.ts
4380
resources/i18n/de.ts
File diff suppressed because it is too large
Load Diff
4220
resources/i18n/el.ts
4220
resources/i18n/el.ts
File diff suppressed because it is too large
Load Diff
4314
resources/i18n/en.ts
4314
resources/i18n/en.ts
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
4472
resources/i18n/es.ts
4472
resources/i18n/es.ts
File diff suppressed because it is too large
Load Diff
4343
resources/i18n/et.ts
4343
resources/i18n/et.ts
File diff suppressed because it is too large
Load Diff
4603
resources/i18n/fi.ts
4603
resources/i18n/fi.ts
File diff suppressed because it is too large
Load Diff
4539
resources/i18n/fr.ts
4539
resources/i18n/fr.ts
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
Loading…
Reference in New Issue
Block a user