forked from openlp/openlp
This commit is contained in:
commit
77519be74c
@ -40,3 +40,6 @@ __pycache__
|
|||||||
# Rejected diff's
|
# Rejected diff's
|
||||||
*.rej
|
*.rej
|
||||||
*.~\?~
|
*.~\?~
|
||||||
|
.coverage
|
||||||
|
cover
|
||||||
|
*.kdev4
|
||||||
|
@ -1 +1 @@
|
|||||||
2.1.2
|
2.1.3
|
||||||
|
@ -250,7 +250,7 @@ class OpenLP(OpenLPMixin, QtGui.QApplication):
|
|||||||
|
|
||||||
def event(self, event):
|
def event(self, event):
|
||||||
"""
|
"""
|
||||||
Enables direct file opening on OS X
|
Enables platform specific event handling i.e. direct file opening on OS X
|
||||||
|
|
||||||
:param event: The event
|
:param event: The event
|
||||||
"""
|
"""
|
||||||
@ -259,8 +259,19 @@ class OpenLP(OpenLPMixin, QtGui.QApplication):
|
|||||||
log.debug('Got open file event for %s!', file_name)
|
log.debug('Got open file event for %s!', file_name)
|
||||||
self.args.insert(0, file_name)
|
self.args.insert(0, file_name)
|
||||||
return True
|
return True
|
||||||
else:
|
# Mac OS X should restore app window when user clicked on the OpenLP icon
|
||||||
return QtGui.QApplication.event(self, event)
|
# in the Dock bar. However, OpenLP consists of multiple windows and this
|
||||||
|
# does not work. This workaround fixes that.
|
||||||
|
# The main OpenLP window is restored when it was previously minimized.
|
||||||
|
elif event.type() == QtCore.QEvent.ApplicationActivate:
|
||||||
|
if is_macosx() and hasattr(self, 'main_window'):
|
||||||
|
if self.main_window.isMinimized():
|
||||||
|
# Copied from QWidget.setWindowState() docs on how to restore and activate a minimized window
|
||||||
|
# while preserving its maximized and/or full-screen state.
|
||||||
|
self.main_window.setWindowState(self.main_window.windowState() & ~QtCore.Qt.WindowMinimized |
|
||||||
|
QtCore.Qt.WindowActive)
|
||||||
|
return True
|
||||||
|
return QtGui.QApplication.event(self, event)
|
||||||
|
|
||||||
|
|
||||||
def parse_options(args):
|
def parse_options(args):
|
||||||
|
@ -66,8 +66,9 @@ def check_directory_exists(directory, do_not_log=False):
|
|||||||
try:
|
try:
|
||||||
if not os.path.exists(directory):
|
if not os.path.exists(directory):
|
||||||
os.makedirs(directory)
|
os.makedirs(directory)
|
||||||
except IOError:
|
except IOError as e:
|
||||||
pass
|
if not do_not_log:
|
||||||
|
log.exception('failed to check if directory exists or create directory')
|
||||||
|
|
||||||
|
|
||||||
def get_frozen_path(frozen_option, non_frozen_option):
|
def get_frozen_path(frozen_option, non_frozen_option):
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
###############################################################################
|
###############################################################################
|
||||||
"""
|
"""
|
||||||
The :mod:`~openlp.core.lib.historycombobox` module contains the HistoryComboBox widget
|
The :mod:`~openlp.core.common.historycombobox` module contains the HistoryComboBox widget
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from PyQt4 import QtCore, QtGui
|
from PyQt4 import QtCore, QtGui
|
||||||
@ -28,9 +28,9 @@ from PyQt4 import QtCore, QtGui
|
|||||||
|
|
||||||
class HistoryComboBox(QtGui.QComboBox):
|
class HistoryComboBox(QtGui.QComboBox):
|
||||||
"""
|
"""
|
||||||
The :class:`~openlp.core.lib.historycombobox.HistoryComboBox` widget emulates the QLineEdit ``returnPressed`` signal
|
The :class:`~openlp.core.common.historycombobox.HistoryComboBox` widget emulates the QLineEdit ``returnPressed``
|
||||||
for when the :kbd:`Enter` or :kbd:`Return` keys are pressed, and saves anything that is typed into the edit box into
|
signal for when the :kbd:`Enter` or :kbd:`Return` keys are pressed, and saves anything that is typed into the edit
|
||||||
its list.
|
box into its list.
|
||||||
"""
|
"""
|
||||||
returnPressed = QtCore.pyqtSignal()
|
returnPressed = QtCore.pyqtSignal()
|
||||||
|
|
||||||
|
@ -25,7 +25,6 @@ This class contains the core default settings.
|
|||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
|
|
||||||
from PyQt4 import QtCore, QtGui
|
from PyQt4 import QtCore, QtGui
|
||||||
|
|
||||||
@ -45,6 +44,21 @@ if is_linux():
|
|||||||
X11_BYPASS_DEFAULT = False
|
X11_BYPASS_DEFAULT = False
|
||||||
|
|
||||||
|
|
||||||
|
def recent_files_conv(value):
|
||||||
|
"""
|
||||||
|
If the value is not a list convert it to a list
|
||||||
|
:param value: Value to convert
|
||||||
|
:return: value as a List
|
||||||
|
"""
|
||||||
|
if isinstance(value, list):
|
||||||
|
return value
|
||||||
|
elif isinstance(value, str):
|
||||||
|
return [value]
|
||||||
|
elif isinstance(value, bytes):
|
||||||
|
return [value.decode()]
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
class Settings(QtCore.QSettings):
|
class Settings(QtCore.QSettings):
|
||||||
"""
|
"""
|
||||||
Class to wrap QSettings.
|
Class to wrap QSettings.
|
||||||
@ -66,10 +80,9 @@ class Settings(QtCore.QSettings):
|
|||||||
The first entry is the *old key*; it will be removed.
|
The first entry is the *old key*; it will be removed.
|
||||||
|
|
||||||
The second entry is the *new key*; we will add it to the config. If this is just an empty string, we just remove
|
The second entry is the *new key*; we will add it to the config. If this is just an empty string, we just remove
|
||||||
the old key.
|
the old key. The last entry is a list containing two-pair tuples. If the list is empty, no conversion is made.
|
||||||
|
If the first value is callable i.e. a function, the function will be called with the old setting's value.
|
||||||
The last entry is a list containing two-pair tuples. If the list is empty, no conversion is made. Otherwise each
|
Otherwise each pair describes how to convert the old setting's value::
|
||||||
pair describes how to convert the old setting's value::
|
|
||||||
|
|
||||||
(SlideLimits.Wrap, True)
|
(SlideLimits.Wrap, True)
|
||||||
|
|
||||||
@ -216,7 +229,8 @@ class Settings(QtCore.QSettings):
|
|||||||
'shortcuts/moveDown': [QtGui.QKeySequence(QtCore.Qt.Key_PageDown)],
|
'shortcuts/moveDown': [QtGui.QKeySequence(QtCore.Qt.Key_PageDown)],
|
||||||
'shortcuts/nextTrackItem': [],
|
'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': [],
|
'shortcuts/nextItem_preview': [QtGui.QKeySequence(QtCore.Qt.Key_Down),
|
||||||
|
QtGui.QKeySequence(QtCore.Qt.Key_PageDown)],
|
||||||
'shortcuts/nextService': [QtGui.QKeySequence(QtCore.Qt.Key_Right)],
|
'shortcuts/nextService': [QtGui.QKeySequence(QtCore.Qt.Key_Right)],
|
||||||
'shortcuts/newService': [],
|
'shortcuts/newService': [],
|
||||||
'shortcuts/offlineHelpItem': [],
|
'shortcuts/offlineHelpItem': [],
|
||||||
@ -230,7 +244,8 @@ class Settings(QtCore.QSettings):
|
|||||||
'shortcuts/playSlidesLoop': [],
|
'shortcuts/playSlidesLoop': [],
|
||||||
'shortcuts/playSlidesOnce': [],
|
'shortcuts/playSlidesOnce': [],
|
||||||
'shortcuts/previousService': [QtGui.QKeySequence(QtCore.Qt.Key_Left)],
|
'shortcuts/previousService': [QtGui.QKeySequence(QtCore.Qt.Key_Left)],
|
||||||
'shortcuts/previousItem_preview': [],
|
'shortcuts/previousItem_preview': [QtGui.QKeySequence(QtCore.Qt.Key_Up),
|
||||||
|
QtGui.QKeySequence(QtCore.Qt.Key_PageUp)],
|
||||||
'shortcuts/printServiceItem': [QtGui.QKeySequence('Ctrl+P')],
|
'shortcuts/printServiceItem': [QtGui.QKeySequence('Ctrl+P')],
|
||||||
'shortcuts/songExportItem': [],
|
'shortcuts/songExportItem': [],
|
||||||
'shortcuts/songUsageStatus': [QtGui.QKeySequence(QtCore.Qt.Key_F4)],
|
'shortcuts/songUsageStatus': [QtGui.QKeySequence(QtCore.Qt.Key_F4)],
|
||||||
@ -292,6 +307,10 @@ class Settings(QtCore.QSettings):
|
|||||||
'user interface/preview panel': True,
|
'user interface/preview panel': True,
|
||||||
'user interface/preview splitter geometry': QtCore.QByteArray(),
|
'user interface/preview splitter geometry': QtCore.QByteArray(),
|
||||||
'projector/db type': 'sqlite',
|
'projector/db type': 'sqlite',
|
||||||
|
'projector/db username': '',
|
||||||
|
'projector/db password': '',
|
||||||
|
'projector/db hostname': '',
|
||||||
|
'projector/db database': '',
|
||||||
'projector/enable': True,
|
'projector/enable': True,
|
||||||
'projector/connect on start': False,
|
'projector/connect on start': False,
|
||||||
'projector/last directory import': '',
|
'projector/last directory import': '',
|
||||||
@ -328,7 +347,7 @@ class Settings(QtCore.QSettings):
|
|||||||
('general/language', 'core/language', []),
|
('general/language', 'core/language', []),
|
||||||
('general/last version test', 'core/last version test', []),
|
('general/last version test', 'core/last version test', []),
|
||||||
('general/loop delay', 'core/loop delay', []),
|
('general/loop delay', 'core/loop delay', []),
|
||||||
('general/recent files', 'core/recent files', []),
|
('general/recent files', 'core/recent files', [(recent_files_conv, None)]),
|
||||||
('general/save prompt', 'core/save prompt', []),
|
('general/save prompt', 'core/save prompt', []),
|
||||||
('general/screen blank', 'core/screen blank', []),
|
('general/screen blank', 'core/screen blank', []),
|
||||||
('general/show splash', 'core/show splash', []),
|
('general/show splash', 'core/show splash', []),
|
||||||
@ -353,7 +372,7 @@ class Settings(QtCore.QSettings):
|
|||||||
|
|
||||||
:param default_values: A dict with setting keys and their default values.
|
:param default_values: A dict with setting keys and their default values.
|
||||||
"""
|
"""
|
||||||
Settings.__default_settings__ = dict(list(default_values.items()) + list(Settings.__default_settings__.items()))
|
Settings.__default_settings__.update(default_values)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def set_filename(ini_file):
|
def set_filename(ini_file):
|
||||||
@ -410,7 +429,9 @@ class Settings(QtCore.QSettings):
|
|||||||
for new, old in rules:
|
for new, old in rules:
|
||||||
# If the value matches with the condition (rule), then use the provided value. This is used to
|
# If the value matches with the condition (rule), then use the provided value. This is used to
|
||||||
# convert values. E. g. an old value 1 results in True, and 0 in False.
|
# convert values. E. g. an old value 1 results in True, and 0 in False.
|
||||||
if old == old_value:
|
if callable(new):
|
||||||
|
old_value = new(old_value)
|
||||||
|
elif old == old_value:
|
||||||
old_value = new
|
old_value = new
|
||||||
break
|
break
|
||||||
self.setValue(new_key, old_value)
|
self.setValue(new_key, old_value)
|
||||||
|
@ -115,11 +115,14 @@ class UiStrings(object):
|
|||||||
self.PlaySlidesInLoop = translate('OpenLP.Ui', 'Play Slides in Loop')
|
self.PlaySlidesInLoop = translate('OpenLP.Ui', 'Play Slides in Loop')
|
||||||
self.PlaySlidesToEnd = translate('OpenLP.Ui', 'Play Slides to End')
|
self.PlaySlidesToEnd = translate('OpenLP.Ui', 'Play Slides to End')
|
||||||
self.Preview = translate('OpenLP.Ui', 'Preview')
|
self.Preview = translate('OpenLP.Ui', 'Preview')
|
||||||
|
self.PreviewToolbar = translate('OpenLP.Ui', 'Preview Toolbar')
|
||||||
self.PrintService = translate('OpenLP.Ui', 'Print Service')
|
self.PrintService = translate('OpenLP.Ui', 'Print Service')
|
||||||
self.Projector = translate('OpenLP.Ui', 'Projector', 'Singular')
|
self.Projector = translate('OpenLP.Ui', 'Projector', 'Singular')
|
||||||
self.Projectors = translate('OpenLP.Ui', 'Projectors', 'Plural')
|
self.Projectors = translate('OpenLP.Ui', 'Projectors', 'Plural')
|
||||||
self.ReplaceBG = translate('OpenLP.Ui', 'Replace Background')
|
self.ReplaceBG = translate('OpenLP.Ui', 'Replace Background')
|
||||||
self.ReplaceLiveBG = translate('OpenLP.Ui', 'Replace live 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.ResetBG = translate('OpenLP.Ui', 'Reset Background')
|
self.ResetBG = translate('OpenLP.Ui', 'Reset Background')
|
||||||
self.ResetLiveBG = translate('OpenLP.Ui', 'Reset live background.')
|
self.ResetLiveBG = translate('OpenLP.Ui', 'Reset live background.')
|
||||||
self.Seconds = translate('OpenLP.Ui', 's', 'The abbreviated unit for seconds')
|
self.Seconds = translate('OpenLP.Ui', 's', 'The abbreviated unit for seconds')
|
||||||
|
@ -90,7 +90,7 @@ def get_text_file_string(text_file):
|
|||||||
file_handle = None
|
file_handle = None
|
||||||
content = None
|
content = None
|
||||||
try:
|
try:
|
||||||
file_handle = open(text_file, 'r')
|
file_handle = open(text_file, 'r', encoding='utf-8')
|
||||||
if not file_handle.read(3) == '\xEF\xBB\xBF':
|
if not file_handle.read(3) == '\xEF\xBB\xBF':
|
||||||
# no BOM was found
|
# no BOM was found
|
||||||
file_handle.seek(0)
|
file_handle.seek(0)
|
||||||
@ -312,6 +312,7 @@ def create_separated_list(string_list):
|
|||||||
|
|
||||||
|
|
||||||
from .colorbutton import ColorButton
|
from .colorbutton import ColorButton
|
||||||
|
from .exceptions import ValidationError
|
||||||
from .filedialog import FileDialog
|
from .filedialog import FileDialog
|
||||||
from .screen import ScreenList
|
from .screen import ScreenList
|
||||||
from .listwidgetwithdnd import ListWidgetWithDnD
|
from .listwidgetwithdnd import ListWidgetWithDnD
|
||||||
|
@ -60,6 +60,35 @@ def init_db(url, auto_flush=True, auto_commit=False, base=None):
|
|||||||
return session, metadata
|
return session, metadata
|
||||||
|
|
||||||
|
|
||||||
|
def get_db_path(plugin_name, db_file_name=None):
|
||||||
|
"""
|
||||||
|
Create a path to a database from the plugin name and database name
|
||||||
|
|
||||||
|
:param plugin_name: Name of plugin
|
||||||
|
:param db_file_name: File name of database
|
||||||
|
:return: The path to the database as type str
|
||||||
|
"""
|
||||||
|
if db_file_name is None:
|
||||||
|
return 'sqlite:///%s/%s.sqlite' % (AppLocation.get_section_data_path(plugin_name), plugin_name)
|
||||||
|
else:
|
||||||
|
return 'sqlite:///%s/%s' % (AppLocation.get_section_data_path(plugin_name), db_file_name)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_db_error(plugin_name, db_file_name):
|
||||||
|
"""
|
||||||
|
Log and report to the user that a database cannot be loaded
|
||||||
|
|
||||||
|
:param plugin_name: Name of plugin
|
||||||
|
:param db_file_name: File name of database
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
db_path = get_db_path(plugin_name, db_file_name)
|
||||||
|
log.exception('Error loading database: %s', db_path)
|
||||||
|
critical_error_message_box(translate('OpenLP.Manager', 'Database Error'),
|
||||||
|
translate('OpenLP.Manager', 'OpenLP cannot load your database.\n\nDatabase: %s')
|
||||||
|
% db_path)
|
||||||
|
|
||||||
|
|
||||||
def init_url(plugin_name, db_file_name=None):
|
def init_url(plugin_name, db_file_name=None):
|
||||||
"""
|
"""
|
||||||
Return the database URL.
|
Return the database URL.
|
||||||
@ -69,21 +98,14 @@ def init_url(plugin_name, db_file_name=None):
|
|||||||
"""
|
"""
|
||||||
settings = Settings()
|
settings = Settings()
|
||||||
settings.beginGroup(plugin_name)
|
settings.beginGroup(plugin_name)
|
||||||
db_url = ''
|
|
||||||
db_type = settings.value('db type')
|
db_type = settings.value('db type')
|
||||||
if db_type == 'sqlite':
|
if db_type == 'sqlite':
|
||||||
if db_file_name is None:
|
db_url = get_db_path(plugin_name, db_file_name)
|
||||||
db_url = 'sqlite:///%s/%s.sqlite' % (AppLocation.get_section_data_path(plugin_name), plugin_name)
|
|
||||||
else:
|
|
||||||
db_url = 'sqlite:///%s/%s' % (AppLocation.get_section_data_path(plugin_name), db_file_name)
|
|
||||||
else:
|
else:
|
||||||
db_url = '%s://%s:%s@%s/%s' % (db_type, urlquote(settings.value('db username')),
|
db_url = '%s://%s:%s@%s/%s' % (db_type, urlquote(settings.value('db username')),
|
||||||
urlquote(settings.value('db password')),
|
urlquote(settings.value('db password')),
|
||||||
urlquote(settings.value('db hostname')),
|
urlquote(settings.value('db hostname')),
|
||||||
urlquote(settings.value('db database')))
|
urlquote(settings.value('db database')))
|
||||||
if db_type == 'mysql':
|
|
||||||
db_encoding = settings.value('db encoding')
|
|
||||||
db_url += '?charset=%s' % urlquote(db_encoding)
|
|
||||||
settings.endGroup()
|
settings.endGroup()
|
||||||
return db_url
|
return db_url
|
||||||
|
|
||||||
@ -123,9 +145,13 @@ def upgrade_db(url, upgrade):
|
|||||||
version_meta = session.query(Metadata).get('version')
|
version_meta = session.query(Metadata).get('version')
|
||||||
if version_meta is None:
|
if version_meta is None:
|
||||||
# Tables have just been created - fill the version field with the most recent version
|
# Tables have just been created - fill the version field with the most recent version
|
||||||
version = upgrade.__version__
|
if session.query(Metadata).get('dbversion'):
|
||||||
|
version = 0
|
||||||
|
else:
|
||||||
|
version = upgrade.__version__
|
||||||
version_meta = Metadata.populate(key='version', value=version)
|
version_meta = Metadata.populate(key='version', value=version)
|
||||||
session.add(version_meta)
|
session.add(version_meta)
|
||||||
|
session.commit()
|
||||||
else:
|
else:
|
||||||
version = int(version_meta.value)
|
version = int(version_meta.value)
|
||||||
if version > upgrade.__version__:
|
if version > upgrade.__version__:
|
||||||
@ -212,7 +238,7 @@ class Manager(object):
|
|||||||
try:
|
try:
|
||||||
db_ver, up_ver = upgrade_db(self.db_url, upgrade_mod)
|
db_ver, up_ver = upgrade_db(self.db_url, upgrade_mod)
|
||||||
except (SQLAlchemyError, DBAPIError):
|
except (SQLAlchemyError, DBAPIError):
|
||||||
log.exception('Error loading database: %s', self.db_url)
|
handle_db_error(plugin_name, db_file_name)
|
||||||
return
|
return
|
||||||
if db_ver > up_ver:
|
if db_ver > up_ver:
|
||||||
critical_error_message_box(
|
critical_error_message_box(
|
||||||
@ -225,10 +251,7 @@ class Manager(object):
|
|||||||
try:
|
try:
|
||||||
self.session = init_schema(self.db_url)
|
self.session = init_schema(self.db_url)
|
||||||
except (SQLAlchemyError, DBAPIError):
|
except (SQLAlchemyError, DBAPIError):
|
||||||
log.exception('Error loading database: %s', self.db_url)
|
handle_db_error(plugin_name, db_file_name)
|
||||||
critical_error_message_box(translate('OpenLP.Manager', 'Database Error'),
|
|
||||||
translate('OpenLP.Manager', 'OpenLP cannot load your database.\n\nDatabase: %s')
|
|
||||||
% self.db_url)
|
|
||||||
|
|
||||||
def save_object(self, object_instance, commit=True):
|
def save_object(self, object_instance, commit=True):
|
||||||
"""
|
"""
|
||||||
|
32
openlp/core/lib/exceptions.py
Normal file
32
openlp/core/lib/exceptions.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Copyright (c) 2008-2015 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:`~openlp.core.lib.exceptions` module contains custom exceptions
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class ValidationError(Exception):
|
||||||
|
"""
|
||||||
|
The :class:`~openlp.core.lib.exceptions.ValidationError` exception provides a custom exception for validating
|
||||||
|
import files.
|
||||||
|
"""
|
||||||
|
pass
|
@ -95,7 +95,7 @@ class ListWidgetWithDnD(QtGui.QListWidget):
|
|||||||
event.accept()
|
event.accept()
|
||||||
files = []
|
files = []
|
||||||
for url in event.mimeData().urls():
|
for url in event.mimeData().urls():
|
||||||
local_file = url.toLocalFile()
|
local_file = os.path.normpath(url.toLocalFile())
|
||||||
if os.path.isfile(local_file):
|
if os.path.isfile(local_file):
|
||||||
files.append(local_file)
|
files.append(local_file)
|
||||||
elif os.path.isdir(local_file):
|
elif os.path.isdir(local_file):
|
||||||
|
@ -345,7 +345,7 @@ class MediaManagerItem(QtGui.QWidget, RegistryProperties):
|
|||||||
def dnd_move_internal(self, target):
|
def dnd_move_internal(self, target):
|
||||||
"""
|
"""
|
||||||
Handle internal moving of media manager items
|
Handle internal moving of media manager items
|
||||||
s
|
|
||||||
:param target: The target of the DnD action
|
:param target: The target of the DnD action
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
@ -460,7 +460,8 @@ s
|
|||||||
"""
|
"""
|
||||||
if Settings().value('advanced/double click live'):
|
if Settings().value('advanced/double click live'):
|
||||||
self.on_live_click()
|
self.on_live_click()
|
||||||
else:
|
elif not Settings().value('advanced/single click preview'):
|
||||||
|
# NOTE: The above check is necessary to prevent bug #1419300
|
||||||
self.on_preview_click()
|
self.on_preview_click()
|
||||||
|
|
||||||
def on_selection_change(self):
|
def on_selection_change(self):
|
||||||
|
@ -271,8 +271,8 @@ ERROR_MSG = {E_OK: translate('OpenLP.ProjectorConstants', 'OK'), # E_OK | S_OK
|
|||||||
E_PROXY_NOT_FOUND: translate('OpenLP.ProjectorConstants',
|
E_PROXY_NOT_FOUND: translate('OpenLP.ProjectorConstants',
|
||||||
'The proxy address set with setProxy() was not found'),
|
'The proxy address set with setProxy() was not found'),
|
||||||
E_PROXY_PROTOCOL: translate('OpenLP.ProjectorConstants',
|
E_PROXY_PROTOCOL: translate('OpenLP.ProjectorConstants',
|
||||||
'The connection negotiation with the proxy server because the response '
|
'The connection negotiation with the proxy server failed because the '
|
||||||
'from the proxy server could not be understood'),
|
'response from the proxy server could not be understood'),
|
||||||
E_UNKNOWN_SOCKET_ERROR: translate('OpenLP.ProjectorConstants', 'An unidentified error occurred'),
|
E_UNKNOWN_SOCKET_ERROR: translate('OpenLP.ProjectorConstants', 'An unidentified error occurred'),
|
||||||
S_NOT_CONNECTED: translate('OpenLP.ProjectorConstants', 'Not connected'),
|
S_NOT_CONNECTED: translate('OpenLP.ProjectorConstants', 'Not connected'),
|
||||||
S_CONNECTING: translate('OpenLP.ProjectorConstants', 'Connecting'),
|
S_CONNECTING: translate('OpenLP.ProjectorConstants', 'Connecting'),
|
||||||
|
@ -51,6 +51,12 @@ class SettingsTab(QtGui.QWidget, RegistryProperties):
|
|||||||
self.tab_visited = False
|
self.tab_visited = False
|
||||||
if icon_path:
|
if icon_path:
|
||||||
self.icon_path = icon_path
|
self.icon_path = icon_path
|
||||||
|
self._setup()
|
||||||
|
|
||||||
|
def _setup(self):
|
||||||
|
"""
|
||||||
|
Run some initial setup. This method is separate from __init__ in order to mock it out in tests.
|
||||||
|
"""
|
||||||
self.setupUi()
|
self.setupUi()
|
||||||
self.retranslateUi()
|
self.retranslateUi()
|
||||||
self.initialise()
|
self.initialise()
|
||||||
|
@ -144,7 +144,7 @@ class UiAboutDialog(object):
|
|||||||
'id': ['Mico "bangmico" Siahaan', ' ign_christian'],
|
'id': ['Mico "bangmico" Siahaan', ' ign_christian'],
|
||||||
'ja': ['Kunio "Kunio" Nakamaru', 'Chris Haris'],
|
'ja': ['Kunio "Kunio" Nakamaru', 'Chris Haris'],
|
||||||
'nb': ['Atle "pendlaren" Weibell', 'Frode "frodus" Woldsund'],
|
'nb': ['Atle "pendlaren" Weibell', 'Frode "frodus" Woldsund'],
|
||||||
'nl': ['Arjen "typovar" van Voorst'],
|
'nl': ['Arjan Schrijver', 'Arjen "typovar" van Voorst'],
|
||||||
'pl': ['Agata \u017B\u0105d\u0142o', 'Piotr Karze\u0142ek'],
|
'pl': ['Agata \u017B\u0105d\u0142o', 'Piotr Karze\u0142ek'],
|
||||||
'pt_BR': ['David Mederiros', 'Rafael "rafaellerm" Lerm', 'Eduardo Levi Chaves',
|
'pt_BR': ['David Mederiros', 'Rafael "rafaellerm" Lerm', 'Eduardo Levi Chaves',
|
||||||
'Gustavo Bim', 'Rog\xeanio Bel\xe9m', 'Simon "samscudder" Scudder', 'Van Der Fran'],
|
'Gustavo Bim', 'Rog\xeanio Bel\xe9m', 'Simon "samscudder" Scudder', 'Van Der Fran'],
|
||||||
|
@ -39,6 +39,12 @@ class FileRenameForm(QtGui.QDialog, Ui_FileRenameDialog, RegistryProperties):
|
|||||||
Constructor
|
Constructor
|
||||||
"""
|
"""
|
||||||
super(FileRenameForm, self).__init__(Registry().get('main_window'))
|
super(FileRenameForm, self).__init__(Registry().get('main_window'))
|
||||||
|
self._setup()
|
||||||
|
|
||||||
|
def _setup(self):
|
||||||
|
"""
|
||||||
|
Set up the class. This method is mocked out by the tests.
|
||||||
|
"""
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
|
|
||||||
def exec_(self, copy=False):
|
def exec_(self, copy=False):
|
||||||
|
@ -22,8 +22,10 @@
|
|||||||
"""
|
"""
|
||||||
This module contains the first time wizard.
|
This module contains the first time wizard.
|
||||||
"""
|
"""
|
||||||
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import socket
|
||||||
import time
|
import time
|
||||||
import urllib.request
|
import urllib.request
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
@ -47,10 +49,10 @@ class ThemeScreenshotWorker(QtCore.QObject):
|
|||||||
"""
|
"""
|
||||||
This thread downloads a theme's screenshot
|
This thread downloads a theme's screenshot
|
||||||
"""
|
"""
|
||||||
screenshot_downloaded = QtCore.pyqtSignal(str, str)
|
screenshot_downloaded = QtCore.pyqtSignal(str, str, str)
|
||||||
finished = QtCore.pyqtSignal()
|
finished = QtCore.pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, themes_url, title, filename, screenshot):
|
def __init__(self, themes_url, title, filename, sha256, screenshot):
|
||||||
"""
|
"""
|
||||||
Set up the worker object
|
Set up the worker object
|
||||||
"""
|
"""
|
||||||
@ -58,7 +60,9 @@ class ThemeScreenshotWorker(QtCore.QObject):
|
|||||||
self.themes_url = themes_url
|
self.themes_url = themes_url
|
||||||
self.title = title
|
self.title = title
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
|
self.sha256 = sha256
|
||||||
self.screenshot = screenshot
|
self.screenshot = screenshot
|
||||||
|
socket.setdefaulttimeout(CONNECTION_TIMEOUT)
|
||||||
super(ThemeScreenshotWorker, self).__init__()
|
super(ThemeScreenshotWorker, self).__init__()
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
@ -71,7 +75,7 @@ class ThemeScreenshotWorker(QtCore.QObject):
|
|||||||
urllib.request.urlretrieve('%s%s' % (self.themes_url, self.screenshot),
|
urllib.request.urlretrieve('%s%s' % (self.themes_url, self.screenshot),
|
||||||
os.path.join(gettempdir(), 'openlp', self.screenshot))
|
os.path.join(gettempdir(), 'openlp', self.screenshot))
|
||||||
# Signal that the screenshot has been downloaded
|
# Signal that the screenshot has been downloaded
|
||||||
self.screenshot_downloaded.emit(self.title, self.filename)
|
self.screenshot_downloaded.emit(self.title, self.filename, self.sha256)
|
||||||
except:
|
except:
|
||||||
log.exception('Unable to download screenshot')
|
log.exception('Unable to download screenshot')
|
||||||
finally:
|
finally:
|
||||||
@ -221,8 +225,9 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
|||||||
self.application.process_events()
|
self.application.process_events()
|
||||||
title = self.config.get('songs_%s' % song, 'title')
|
title = self.config.get('songs_%s' % song, 'title')
|
||||||
filename = self.config.get('songs_%s' % song, 'filename')
|
filename = self.config.get('songs_%s' % song, 'filename')
|
||||||
|
sha256 = self.config.get('songs_%s' % song, 'sha256', fallback='')
|
||||||
item = QtGui.QListWidgetItem(title, self.songs_list_widget)
|
item = QtGui.QListWidgetItem(title, self.songs_list_widget)
|
||||||
item.setData(QtCore.Qt.UserRole, filename)
|
item.setData(QtCore.Qt.UserRole, (filename, sha256))
|
||||||
item.setCheckState(QtCore.Qt.Unchecked)
|
item.setCheckState(QtCore.Qt.Unchecked)
|
||||||
item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable)
|
item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable)
|
||||||
bible_languages = self.config.get('bibles', 'languages')
|
bible_languages = self.config.get('bibles', 'languages')
|
||||||
@ -237,8 +242,9 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
|||||||
self.application.process_events()
|
self.application.process_events()
|
||||||
title = self.config.get('bible_%s' % bible, 'title')
|
title = self.config.get('bible_%s' % bible, 'title')
|
||||||
filename = self.config.get('bible_%s' % bible, 'filename')
|
filename = self.config.get('bible_%s' % bible, 'filename')
|
||||||
|
sha256 = self.config.get('bible_%s' % bible, 'sha256', fallback='')
|
||||||
item = QtGui.QTreeWidgetItem(lang_item, [title])
|
item = QtGui.QTreeWidgetItem(lang_item, [title])
|
||||||
item.setData(0, QtCore.Qt.UserRole, filename)
|
item.setData(0, QtCore.Qt.UserRole, (filename, sha256))
|
||||||
item.setCheckState(0, QtCore.Qt.Unchecked)
|
item.setCheckState(0, QtCore.Qt.Unchecked)
|
||||||
item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable)
|
item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable)
|
||||||
self.bibles_tree_widget.expandAll()
|
self.bibles_tree_widget.expandAll()
|
||||||
@ -246,11 +252,11 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
|||||||
# Download the theme screenshots
|
# Download the theme screenshots
|
||||||
themes = self.config.get('themes', 'files').split(',')
|
themes = self.config.get('themes', 'files').split(',')
|
||||||
for theme in themes:
|
for theme in themes:
|
||||||
self.application.process_events()
|
|
||||||
title = self.config.get('theme_%s' % theme, 'title')
|
title = self.config.get('theme_%s' % theme, 'title')
|
||||||
filename = self.config.get('theme_%s' % theme, 'filename')
|
filename = self.config.get('theme_%s' % theme, 'filename')
|
||||||
|
sha256 = self.config.get('theme_%s' % theme, 'sha256', fallback='')
|
||||||
screenshot = self.config.get('theme_%s' % theme, 'screenshot')
|
screenshot = self.config.get('theme_%s' % theme, 'screenshot')
|
||||||
worker = ThemeScreenshotWorker(self.themes_url, title, filename, screenshot)
|
worker = ThemeScreenshotWorker(self.themes_url, title, filename, sha256, screenshot)
|
||||||
self.theme_screenshot_workers.append(worker)
|
self.theme_screenshot_workers.append(worker)
|
||||||
worker.screenshot_downloaded.connect(self.on_screenshot_downloaded)
|
worker.screenshot_downloaded.connect(self.on_screenshot_downloaded)
|
||||||
thread = QtCore.QThread(self)
|
thread = QtCore.QThread(self)
|
||||||
@ -259,6 +265,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
|||||||
worker.finished.connect(thread.quit)
|
worker.finished.connect(thread.quit)
|
||||||
worker.moveToThread(thread)
|
worker.moveToThread(thread)
|
||||||
thread.start()
|
thread.start()
|
||||||
|
self.application.process_events()
|
||||||
|
|
||||||
def set_defaults(self):
|
def set_defaults(self):
|
||||||
"""
|
"""
|
||||||
@ -356,7 +363,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
|||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
self.application.set_normal_cursor()
|
self.application.set_normal_cursor()
|
||||||
|
|
||||||
def on_screenshot_downloaded(self, title, filename):
|
def on_screenshot_downloaded(self, title, filename, sha256):
|
||||||
"""
|
"""
|
||||||
Add an item to the list when a theme has been downloaded
|
Add an item to the list when a theme has been downloaded
|
||||||
|
|
||||||
@ -364,7 +371,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
|||||||
:param filename: The filename of the theme
|
:param filename: The filename of the theme
|
||||||
"""
|
"""
|
||||||
item = QtGui.QListWidgetItem(title, self.themes_list_widget)
|
item = QtGui.QListWidgetItem(title, self.themes_list_widget)
|
||||||
item.setData(QtCore.Qt.UserRole, filename)
|
item.setData(QtCore.Qt.UserRole, (filename, sha256))
|
||||||
item.setCheckState(QtCore.Qt.Unchecked)
|
item.setCheckState(QtCore.Qt.Unchecked)
|
||||||
item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable)
|
item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable)
|
||||||
|
|
||||||
@ -385,7 +392,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
|||||||
self.was_cancelled = True
|
self.was_cancelled = True
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
def url_get_file(self, url, f_path):
|
def url_get_file(self, url, f_path, sha256=None):
|
||||||
""""
|
""""
|
||||||
Download a file given a URL. The file is retrieved in chunks, giving the ability to cancel the download at any
|
Download a file given a URL. The file is retrieved in chunks, giving the ability to cancel the download at any
|
||||||
point. Returns False on download error.
|
point. Returns False on download error.
|
||||||
@ -398,18 +405,26 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
|||||||
retries = 0
|
retries = 0
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
url_file = urllib.request.urlopen(url, timeout=CONNECTION_TIMEOUT)
|
|
||||||
filename = open(f_path, "wb")
|
filename = open(f_path, "wb")
|
||||||
|
url_file = urllib.request.urlopen(url, timeout=CONNECTION_TIMEOUT)
|
||||||
|
if sha256:
|
||||||
|
hasher = hashlib.sha256()
|
||||||
# Download until finished or canceled.
|
# Download until finished or canceled.
|
||||||
while not self.was_cancelled:
|
while not self.was_cancelled:
|
||||||
data = url_file.read(block_size)
|
data = url_file.read(block_size)
|
||||||
if not data:
|
if not data:
|
||||||
break
|
break
|
||||||
filename.write(data)
|
filename.write(data)
|
||||||
|
if sha256:
|
||||||
|
hasher.update(data)
|
||||||
block_count += 1
|
block_count += 1
|
||||||
self._download_progress(block_count, block_size)
|
self._download_progress(block_count, block_size)
|
||||||
filename.close()
|
filename.close()
|
||||||
except ConnectionError:
|
if sha256 and hasher.hexdigest() != sha256:
|
||||||
|
log.error('sha256 sums did not match for file: {}'.format(f_path))
|
||||||
|
os.remove(f_path)
|
||||||
|
return False
|
||||||
|
except (urllib.error.URLError, socket.timeout) as err:
|
||||||
trace_error_handler(log)
|
trace_error_handler(log)
|
||||||
filename.close()
|
filename.close()
|
||||||
os.remove(f_path)
|
os.remove(f_path)
|
||||||
@ -434,8 +449,8 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
|||||||
for index, theme in enumerate(themes):
|
for index, theme in enumerate(themes):
|
||||||
screenshot = self.config.get('theme_%s' % theme, 'screenshot')
|
screenshot = self.config.get('theme_%s' % theme, 'screenshot')
|
||||||
item = self.themes_list_widget.item(index)
|
item = self.themes_list_widget.item(index)
|
||||||
# if item:
|
if item:
|
||||||
item.setIcon(build_icon(os.path.join(gettempdir(), 'openlp', screenshot)))
|
item.setIcon(build_icon(os.path.join(gettempdir(), 'openlp', screenshot)))
|
||||||
|
|
||||||
def _get_file_size(self, url):
|
def _get_file_size(self, url):
|
||||||
"""
|
"""
|
||||||
@ -449,7 +464,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
|||||||
site = urllib.request.urlopen(url, timeout=CONNECTION_TIMEOUT)
|
site = urllib.request.urlopen(url, timeout=CONNECTION_TIMEOUT)
|
||||||
meta = site.info()
|
meta = site.info()
|
||||||
return int(meta.get("Content-Length"))
|
return int(meta.get("Content-Length"))
|
||||||
except ConnectionException:
|
except urllib.error.URLError:
|
||||||
if retries > CONNECTION_RETRIES:
|
if retries > CONNECTION_RETRIES:
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
@ -491,7 +506,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
|||||||
self.application.process_events()
|
self.application.process_events()
|
||||||
item = self.songs_list_widget.item(i)
|
item = self.songs_list_widget.item(i)
|
||||||
if item.checkState() == QtCore.Qt.Checked:
|
if item.checkState() == QtCore.Qt.Checked:
|
||||||
filename = item.data(QtCore.Qt.UserRole)
|
filename, sha256 = item.data(QtCore.Qt.UserRole)
|
||||||
size = self._get_file_size('%s%s' % (self.songs_url, filename))
|
size = self._get_file_size('%s%s' % (self.songs_url, filename))
|
||||||
self.max_progress += size
|
self.max_progress += size
|
||||||
# Loop through the Bibles list and increase for each selected item
|
# Loop through the Bibles list and increase for each selected item
|
||||||
@ -500,7 +515,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
|||||||
self.application.process_events()
|
self.application.process_events()
|
||||||
item = iterator.value()
|
item = iterator.value()
|
||||||
if item.parent() and item.checkState(0) == QtCore.Qt.Checked:
|
if item.parent() and item.checkState(0) == QtCore.Qt.Checked:
|
||||||
filename = item.data(0, QtCore.Qt.UserRole)
|
filename, sha256 = item.data(0, QtCore.Qt.UserRole)
|
||||||
size = self._get_file_size('%s%s' % (self.bibles_url, filename))
|
size = self._get_file_size('%s%s' % (self.bibles_url, filename))
|
||||||
self.max_progress += size
|
self.max_progress += size
|
||||||
iterator += 1
|
iterator += 1
|
||||||
@ -509,10 +524,10 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
|||||||
self.application.process_events()
|
self.application.process_events()
|
||||||
item = self.themes_list_widget.item(i)
|
item = self.themes_list_widget.item(i)
|
||||||
if item.checkState() == QtCore.Qt.Checked:
|
if item.checkState() == QtCore.Qt.Checked:
|
||||||
filename = item.data(QtCore.Qt.UserRole)
|
filename, sha256 = item.data(QtCore.Qt.UserRole)
|
||||||
size = self._get_file_size('%s%s' % (self.themes_url, filename))
|
size = self._get_file_size('%s%s' % (self.themes_url, filename))
|
||||||
self.max_progress += size
|
self.max_progress += size
|
||||||
except ConnectionError:
|
except urllib.error.URLError:
|
||||||
trace_error_handler(log)
|
trace_error_handler(log)
|
||||||
critical_error_message_box(translate('OpenLP.FirstTimeWizard', 'Download Error'),
|
critical_error_message_box(translate('OpenLP.FirstTimeWizard', 'Download Error'),
|
||||||
translate('OpenLP.FirstTimeWizard', 'There was a connection problem during '
|
translate('OpenLP.FirstTimeWizard', 'There was a connection problem during '
|
||||||
@ -604,36 +619,52 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
|||||||
songs_destination = os.path.join(gettempdir(), 'openlp')
|
songs_destination = os.path.join(gettempdir(), 'openlp')
|
||||||
bibles_destination = AppLocation.get_section_data_path('bibles')
|
bibles_destination = AppLocation.get_section_data_path('bibles')
|
||||||
themes_destination = AppLocation.get_section_data_path('themes')
|
themes_destination = AppLocation.get_section_data_path('themes')
|
||||||
|
missed_files = []
|
||||||
# Download songs
|
# Download songs
|
||||||
for i in range(self.songs_list_widget.count()):
|
for i in range(self.songs_list_widget.count()):
|
||||||
item = self.songs_list_widget.item(i)
|
item = self.songs_list_widget.item(i)
|
||||||
if item.checkState() == QtCore.Qt.Checked:
|
if item.checkState() == QtCore.Qt.Checked:
|
||||||
filename = item.data(QtCore.Qt.UserRole)
|
filename, sha256 = item.data(QtCore.Qt.UserRole)
|
||||||
self._increment_progress_bar(self.downloading % filename, 0)
|
self._increment_progress_bar(self.downloading % filename, 0)
|
||||||
self.previous_size = 0
|
self.previous_size = 0
|
||||||
destination = os.path.join(songs_destination, str(filename))
|
destination = os.path.join(songs_destination, str(filename))
|
||||||
if not self.url_get_file('%s%s' % (self.songs_url, filename), destination):
|
if not self.url_get_file('%s%s' % (self.songs_url, filename), destination, sha256):
|
||||||
return False
|
missed_files.append('Song: {}'.format(filename))
|
||||||
# Download Bibles
|
# Download Bibles
|
||||||
bibles_iterator = QtGui.QTreeWidgetItemIterator(self.bibles_tree_widget)
|
bibles_iterator = QtGui.QTreeWidgetItemIterator(self.bibles_tree_widget)
|
||||||
while bibles_iterator.value():
|
while bibles_iterator.value():
|
||||||
item = bibles_iterator.value()
|
item = bibles_iterator.value()
|
||||||
if item.parent() and item.checkState(0) == QtCore.Qt.Checked:
|
if item.parent() and item.checkState(0) == QtCore.Qt.Checked:
|
||||||
bible = item.data(0, QtCore.Qt.UserRole)
|
bible, sha256 = item.data(0, QtCore.Qt.UserRole)
|
||||||
self._increment_progress_bar(self.downloading % bible, 0)
|
self._increment_progress_bar(self.downloading % bible, 0)
|
||||||
self.previous_size = 0
|
self.previous_size = 0
|
||||||
if not self.url_get_file('%s%s' % (self.bibles_url, bible), os.path.join(bibles_destination, bible)):
|
if not self.url_get_file('%s%s' % (self.bibles_url, bible), os.path.join(bibles_destination, bible),
|
||||||
return False
|
sha256):
|
||||||
|
missed_files.append('Bible: {}'.format(bible))
|
||||||
bibles_iterator += 1
|
bibles_iterator += 1
|
||||||
# Download themes
|
# Download themes
|
||||||
for i in range(self.themes_list_widget.count()):
|
for i in range(self.themes_list_widget.count()):
|
||||||
item = self.themes_list_widget.item(i)
|
item = self.themes_list_widget.item(i)
|
||||||
if item.checkState() == QtCore.Qt.Checked:
|
if item.checkState() == QtCore.Qt.Checked:
|
||||||
theme = item.data(QtCore.Qt.UserRole)
|
theme, sha256 = item.data(QtCore.Qt.UserRole)
|
||||||
self._increment_progress_bar(self.downloading % theme, 0)
|
self._increment_progress_bar(self.downloading % theme, 0)
|
||||||
self.previous_size = 0
|
self.previous_size = 0
|
||||||
if not self.url_get_file('%s%s' % (self.themes_url, theme), os.path.join(themes_destination, theme)):
|
if not self.url_get_file('%s%s' % (self.themes_url, theme), os.path.join(themes_destination, theme),
|
||||||
return False
|
sha256):
|
||||||
|
missed_files.append('Theme: {}'.format(theme))
|
||||||
|
if missed_files:
|
||||||
|
file_list = ''
|
||||||
|
for entry in missed_files:
|
||||||
|
file_list += '{}<br \>'.format(entry)
|
||||||
|
msg = QtGui.QMessageBox()
|
||||||
|
msg.setIcon(QtGui.QMessageBox.Warning)
|
||||||
|
msg.setWindowTitle(translate('OpenLP.FirstTimeWizard', 'Network Error'))
|
||||||
|
msg.setText(translate('OpenLP.FirstTimeWizard', 'Unable to download some files'))
|
||||||
|
msg.setInformativeText(translate('OpenLP.FirstTimeWizard',
|
||||||
|
'The following files were not able to be '
|
||||||
|
'downloaded:<br \>{}'.format(file_list)))
|
||||||
|
msg.setStandardButtons(msg.Ok)
|
||||||
|
ans = msg.exec_()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _set_plugin_status(self, field, tag):
|
def _set_plugin_status(self, field, tag):
|
||||||
|
@ -146,7 +146,7 @@ class FormattingTagController(object):
|
|||||||
end = self.start_html_to_end_html(start_html)
|
end = self.start_html_to_end_html(start_html)
|
||||||
if not end_html:
|
if not end_html:
|
||||||
if not end:
|
if not end:
|
||||||
return translate('OpenLP.FormattingTagForm', 'Start tag %s is not valid HTML' % start_html), None
|
return translate('OpenLP.FormattingTagForm', 'Start tag %s is not valid HTML') % start_html, None
|
||||||
return None, end
|
return None, end
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
@ -166,5 +166,5 @@ class FormattingTagController(object):
|
|||||||
return None, end
|
return None, end
|
||||||
if end and end != end_html:
|
if end and end != end_html:
|
||||||
return translate('OpenLP.FormattingTagForm',
|
return translate('OpenLP.FormattingTagForm',
|
||||||
'End tag %s does not match end tag for start tag %s' % (end, start_html)), None
|
'End tag %s does not match end tag for start tag %s') % (end, start_html), None
|
||||||
return None, None
|
return None, None
|
||||||
|
@ -29,7 +29,7 @@ Some of the code for this form is based on the examples at:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import cgi
|
import html
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from PyQt4 import QtCore, QtGui, QtWebKit, QtOpenGL
|
from PyQt4 import QtCore, QtGui, QtWebKit, QtOpenGL
|
||||||
@ -273,7 +273,7 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
# First we convert <>& marks to html variants, then apply
|
# First we convert <>& marks to html variants, then apply
|
||||||
# formattingtags, finally we double all backslashes for JavaScript.
|
# formattingtags, finally we double all backslashes for JavaScript.
|
||||||
text_prepared = expand_tags(cgi.escape(text)).replace('\\', '\\\\').replace('\"', '\\\"')
|
text_prepared = expand_tags(html.escape(text)).replace('\\', '\\\\').replace('\"', '\\\"')
|
||||||
if self.height() != self.screen['size'].height() or not self.isVisible():
|
if self.height() != self.screen['size'].height() or not self.isVisible():
|
||||||
shrink = True
|
shrink = True
|
||||||
js = 'show_alert("%s", "%s")' % (text_prepared, 'top')
|
js = 'show_alert("%s", "%s")' % (text_prepared, 'top')
|
||||||
|
@ -989,15 +989,21 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow, RegistryProperties):
|
|||||||
# Read the temp file and output the user's CONF file with blanks to
|
# Read the temp file and output the user's CONF file with blanks to
|
||||||
# make it more readable.
|
# make it more readable.
|
||||||
temp_conf = open(temp_file, 'r')
|
temp_conf = open(temp_file, 'r')
|
||||||
export_conf = open(export_file_name, 'w')
|
try:
|
||||||
for file_record in temp_conf:
|
export_conf = open(export_file_name, 'w')
|
||||||
# Get rid of any invalid entries.
|
for file_record in temp_conf:
|
||||||
if file_record.find('@Invalid()') == -1:
|
# Get rid of any invalid entries.
|
||||||
file_record = file_record.replace('%20', ' ')
|
if file_record.find('@Invalid()') == -1:
|
||||||
export_conf.write(file_record)
|
file_record = file_record.replace('%20', ' ')
|
||||||
temp_conf.close()
|
export_conf.write(file_record)
|
||||||
export_conf.close()
|
temp_conf.close()
|
||||||
os.remove(temp_file)
|
export_conf.close()
|
||||||
|
os.remove(temp_file)
|
||||||
|
except OSError as ose:
|
||||||
|
QtGui.QMessageBox.critical(self, translate('OpenLP.MainWindow', 'Export setting error'),
|
||||||
|
translate('OpenLP.MainWindow', 'An error occurred while exporting the '
|
||||||
|
'settings: %s') % ose.strerror,
|
||||||
|
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok))
|
||||||
|
|
||||||
def on_mode_default_item_clicked(self):
|
def on_mode_default_item_clicked(self):
|
||||||
"""
|
"""
|
||||||
@ -1311,6 +1317,8 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow, RegistryProperties):
|
|||||||
filename = filename[0].upper() + filename[1:]
|
filename = filename[0].upper() + filename[1:]
|
||||||
if filename in self.recent_files:
|
if filename in self.recent_files:
|
||||||
self.recent_files.remove(filename)
|
self.recent_files.remove(filename)
|
||||||
|
if not isinstance(self.recent_files, list):
|
||||||
|
self.recent_files = [self.recent_files]
|
||||||
self.recent_files.insert(0, filename)
|
self.recent_files.insert(0, filename)
|
||||||
while len(self.recent_files) > max_recent_files:
|
while len(self.recent_files) > max_recent_files:
|
||||||
self.recent_files.pop()
|
self.recent_files.pop()
|
||||||
|
@ -517,9 +517,14 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||||||
used_players = get_media_players()[0]
|
used_players = get_media_players()[0]
|
||||||
if service_item.processor != UiStrings().Automatic:
|
if service_item.processor != UiStrings().Automatic:
|
||||||
used_players = [service_item.processor.lower()]
|
used_players = [service_item.processor.lower()]
|
||||||
|
# If no player, we can't play
|
||||||
|
if not used_players:
|
||||||
|
return False
|
||||||
if controller.media_info.file_info.isFile():
|
if controller.media_info.file_info.isFile():
|
||||||
suffix = '*.%s' % controller.media_info.file_info.suffix().lower()
|
suffix = '*.%s' % controller.media_info.file_info.suffix().lower()
|
||||||
for title in used_players:
|
for title in used_players:
|
||||||
|
if not title:
|
||||||
|
continue
|
||||||
player = self.media_players[title]
|
player = self.media_players[title]
|
||||||
if suffix in player.video_extensions_list:
|
if suffix in player.video_extensions_list:
|
||||||
if not controller.media_info.is_background or controller.media_info.is_background and \
|
if not controller.media_info.is_background or controller.media_info.is_background and \
|
||||||
@ -586,7 +591,7 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||||||
else:
|
else:
|
||||||
controller.mediabar.actions['playbackPlay'].setVisible(False)
|
controller.mediabar.actions['playbackPlay'].setVisible(False)
|
||||||
controller.mediabar.actions['playbackPause'].setVisible(True)
|
controller.mediabar.actions['playbackPause'].setVisible(True)
|
||||||
controller.mediabar.actions['playbackStop'].setVisible(True)
|
controller.mediabar.actions['playbackStop'].setDisabled(False)
|
||||||
if controller.is_live:
|
if controller.is_live:
|
||||||
if controller.hide_menu.defaultAction().isChecked() and not controller.media_info.is_background:
|
if controller.hide_menu.defaultAction().isChecked() and not controller.media_info.is_background:
|
||||||
controller.hide_menu.defaultAction().trigger()
|
controller.hide_menu.defaultAction().trigger()
|
||||||
@ -616,7 +621,7 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||||||
display = self._define_display(controller)
|
display = self._define_display(controller)
|
||||||
self.current_media_players[controller.controller_type].pause(display)
|
self.current_media_players[controller.controller_type].pause(display)
|
||||||
controller.mediabar.actions['playbackPlay'].setVisible(True)
|
controller.mediabar.actions['playbackPlay'].setVisible(True)
|
||||||
controller.mediabar.actions['playbackStop'].setVisible(True)
|
controller.mediabar.actions['playbackStop'].setDisabled(False)
|
||||||
controller.mediabar.actions['playbackPause'].setVisible(False)
|
controller.mediabar.actions['playbackPause'].setVisible(False)
|
||||||
|
|
||||||
def media_stop_msg(self, msg):
|
def media_stop_msg(self, msg):
|
||||||
@ -642,7 +647,7 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||||||
self.current_media_players[controller.controller_type].set_visible(display, False)
|
self.current_media_players[controller.controller_type].set_visible(display, False)
|
||||||
controller.seek_slider.setSliderPosition(0)
|
controller.seek_slider.setSliderPosition(0)
|
||||||
controller.mediabar.actions['playbackPlay'].setVisible(True)
|
controller.mediabar.actions['playbackPlay'].setVisible(True)
|
||||||
controller.mediabar.actions['playbackStop'].setVisible(False)
|
controller.mediabar.actions['playbackStop'].setDisabled(True)
|
||||||
controller.mediabar.actions['playbackPause'].setVisible(False)
|
controller.mediabar.actions['playbackPause'].setVisible(False)
|
||||||
|
|
||||||
def media_volume_msg(self, msg):
|
def media_volume_msg(self, msg):
|
||||||
|
@ -27,71 +27,87 @@ from distutils.version import LooseVersion
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
|
import sys
|
||||||
|
|
||||||
from PyQt4 import QtGui
|
from PyQt4 import QtGui
|
||||||
|
|
||||||
from openlp.core.common import Settings, is_win, is_macosx
|
from openlp.core.common import Settings, is_win, is_macosx, is_linux
|
||||||
from openlp.core.lib import translate
|
from openlp.core.lib import translate
|
||||||
from openlp.core.ui.media import MediaState, MediaType
|
from openlp.core.ui.media import MediaState, MediaType
|
||||||
from openlp.core.ui.media.mediaplayer import MediaPlayer
|
from openlp.core.ui.media.mediaplayer import MediaPlayer
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
VLC_AVAILABLE = False
|
# Audio and video extensions copied from 'include/vlc_interface.h' from vlc 2.2.0 source
|
||||||
try:
|
AUDIO_EXT = ['*.3ga', '*.669', '*.a52', '*.aac', '*.ac3', '*.adt', '*.adts', '*.aif', '*.aifc', '*.aiff', '*.amr',
|
||||||
from openlp.core.ui.media.vendor import vlc
|
'*.aob', '*.ape', '*.awb', '*.caf', '*.dts', '*.flac', '*.it', '*.kar', '*.m4a', '*.m4b', '*.m4p', '*.m5p',
|
||||||
VLC_AVAILABLE = bool(vlc.get_default_instance())
|
'*.mid', '*.mka', '*.mlp', '*.mod', '*.mpa', '*.mp1', '*.mp2', '*.mp3', '*.mpc', '*.mpga', '*.mus',
|
||||||
except (ImportError, NameError, NotImplementedError):
|
'*.oga', '*.ogg', '*.oma', '*.opus', '*.qcp', '*.ra', '*.rmi', '*.s3m', '*.sid', '*.spx', '*.thd', '*.tta',
|
||||||
pass
|
'*.voc', '*.vqf', '*.w64', '*.wav', '*.wma', '*.wv', '*.xa', '*.xm']
|
||||||
except OSError as e:
|
|
||||||
if is_win():
|
|
||||||
if not isinstance(e, WindowsError) and e.winerror != 126:
|
|
||||||
raise
|
|
||||||
elif is_macosx():
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
|
|
||||||
if VLC_AVAILABLE:
|
VIDEO_EXT = ['*.3g2', '*.3gp', '*.3gp2', '*.3gpp', '*.amv', '*.asf', '*.avi', '*.bik', '*.divx', '*.drc', '*.dv',
|
||||||
|
'*.f4v', '*.flv', '*.gvi', '*.gxf', '*.iso', '*.m1v', '*.m2v', '*.m2t', '*.m2ts', '*.m4v', '*.mkv',
|
||||||
|
'*.mov', '*.mp2', '*.mp2v', '*.mp4', '*.mp4v', '*.mpe', '*.mpeg', '*.mpeg1', '*.mpeg2', '*.mpeg4', '*.mpg',
|
||||||
|
'*.mpv2', '*.mts', '*.mtv', '*.mxf', '*.mxg', '*.nsv', '*.nuv', '*.ogg', '*.ogm', '*.ogv', '*.ogx', '*.ps',
|
||||||
|
'*.rec', '*.rm', '*.rmvb', '*.rpl', '*.thp', '*.tod', '*.ts', '*.tts', '*.txd', '*.vob', '*.vro', '*.webm',
|
||||||
|
'*.wm', '*.wmv', '*.wtv', '*.xesc',
|
||||||
|
# These extensions was not in the official list, added manually.
|
||||||
|
'*.nut', '*.rv', '*.xvid']
|
||||||
|
|
||||||
|
|
||||||
|
def get_vlc():
|
||||||
|
"""
|
||||||
|
In order to make this module more testable, we have to wrap the VLC import inside a method. We do this so that we
|
||||||
|
can mock out the VLC module entirely.
|
||||||
|
|
||||||
|
:return: The "vlc" module, or None
|
||||||
|
"""
|
||||||
|
if 'openlp.core.ui.media.vendor.vlc' in sys.modules:
|
||||||
|
# If VLC has already been imported, no need to do all the stuff below again
|
||||||
|
return sys.modules['openlp.core.ui.media.vendor.vlc']
|
||||||
|
|
||||||
|
is_vlc_available = False
|
||||||
try:
|
try:
|
||||||
VERSION = vlc.libvlc_get_version().decode('UTF-8')
|
if is_macosx():
|
||||||
except:
|
# Newer versions of VLC on OS X need this. See https://forum.videolan.org/viewtopic.php?t=124521
|
||||||
VERSION = '0.0.0'
|
os.environ['VLC_PLUGIN_PATH'] = '/Applications/VLC.app/Contents/MacOS/plugins'
|
||||||
# LooseVersion does not work when a string contains letter and digits (e. g. 2.0.5 Twoflower).
|
from openlp.core.ui.media.vendor import vlc
|
||||||
# http://bugs.python.org/issue14894
|
|
||||||
if LooseVersion(VERSION.split()[0]) < LooseVersion('1.1.0'):
|
|
||||||
VLC_AVAILABLE = False
|
|
||||||
log.debug('VLC could not be loaded, because the vlc version is too old: %s' % VERSION)
|
|
||||||
|
|
||||||
AUDIO_EXT = ['*.mp3', '*.wav', '*.wma', '*.ogg']
|
is_vlc_available = bool(vlc.get_default_instance())
|
||||||
|
except (ImportError, NameError, NotImplementedError):
|
||||||
|
pass
|
||||||
|
except OSError as e:
|
||||||
|
if is_win():
|
||||||
|
if not isinstance(e, WindowsError) and e.winerror != 126:
|
||||||
|
raise
|
||||||
|
elif is_macosx():
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
VIDEO_EXT = [
|
if is_vlc_available:
|
||||||
'*.3gp',
|
try:
|
||||||
'*.asf', '*.wmv',
|
VERSION = vlc.libvlc_get_version().decode('UTF-8')
|
||||||
'*.au',
|
except:
|
||||||
'*.avi',
|
VERSION = '0.0.0'
|
||||||
'*.divx',
|
# LooseVersion does not work when a string contains letter and digits (e. g. 2.0.5 Twoflower).
|
||||||
'*.flv',
|
# http://bugs.python.org/issue14894
|
||||||
'*.mov',
|
if LooseVersion(VERSION.split()[0]) < LooseVersion('1.1.0'):
|
||||||
'*.mp4', '*.m4v',
|
is_vlc_available = False
|
||||||
'*.ogm', '*.ogv',
|
log.debug('VLC could not be loaded, because the vlc version is too old: %s' % VERSION)
|
||||||
'*.mkv', '*.mka',
|
# On linux we need to initialise X threads, but not when running tests.
|
||||||
'*.ts', '*.mpg',
|
if is_vlc_available and is_linux() and 'nose' not in sys.argv[0]:
|
||||||
'*.mpg', '*.mp2',
|
import ctypes
|
||||||
'*.nsc',
|
try:
|
||||||
'*.nsv',
|
x11 = ctypes.cdll.LoadLibrary('libX11.so')
|
||||||
'*.nut',
|
x11.XInitThreads()
|
||||||
'*.ra', '*.ram', '*.rm', '*.rv', '*.rmbv',
|
except:
|
||||||
'*.a52', '*.dts', '*.aac', '*.flac', '*.dv', '*.vid',
|
log.exception('Failed to run XInitThreads(), VLC might not work properly!')
|
||||||
'*.tta', '*.tac',
|
|
||||||
'*.ty',
|
if is_vlc_available:
|
||||||
'*.dts',
|
return vlc
|
||||||
'*.xa',
|
else:
|
||||||
'*.iso',
|
return None
|
||||||
'*.vob',
|
|
||||||
'*.webm',
|
|
||||||
'*.xvid'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class VlcPlayer(MediaPlayer):
|
class VlcPlayer(MediaPlayer):
|
||||||
@ -115,6 +131,7 @@ class VlcPlayer(MediaPlayer):
|
|||||||
"""
|
"""
|
||||||
Set up the media player
|
Set up the media player
|
||||||
"""
|
"""
|
||||||
|
vlc = get_vlc()
|
||||||
display.vlc_widget = QtGui.QFrame(display)
|
display.vlc_widget = QtGui.QFrame(display)
|
||||||
display.vlc_widget.setFrameStyle(QtGui.QFrame.NoFrame)
|
display.vlc_widget.setFrameStyle(QtGui.QFrame.NoFrame)
|
||||||
# creating a basic vlc instance
|
# creating a basic vlc instance
|
||||||
@ -142,7 +159,7 @@ class VlcPlayer(MediaPlayer):
|
|||||||
# framework and not the old Carbon.
|
# framework and not the old Carbon.
|
||||||
display.vlc_media_player.set_nsobject(win_id)
|
display.vlc_media_player.set_nsobject(win_id)
|
||||||
else:
|
else:
|
||||||
# for Linux using the X Server
|
# for Linux/*BSD using the X Server
|
||||||
display.vlc_media_player.set_xwindow(win_id)
|
display.vlc_media_player.set_xwindow(win_id)
|
||||||
self.has_own_widget = True
|
self.has_own_widget = True
|
||||||
|
|
||||||
@ -150,12 +167,13 @@ class VlcPlayer(MediaPlayer):
|
|||||||
"""
|
"""
|
||||||
Return the availability of VLC
|
Return the availability of VLC
|
||||||
"""
|
"""
|
||||||
return VLC_AVAILABLE
|
return get_vlc() is not None
|
||||||
|
|
||||||
def load(self, display):
|
def load(self, display):
|
||||||
"""
|
"""
|
||||||
Load a video into VLC
|
Load a video into VLC
|
||||||
"""
|
"""
|
||||||
|
vlc = get_vlc()
|
||||||
log.debug('load vid in Vlc Controller')
|
log.debug('load vid in Vlc Controller')
|
||||||
controller = display.controller
|
controller = display.controller
|
||||||
volume = controller.media_info.volume
|
volume = controller.media_info.volume
|
||||||
@ -195,6 +213,7 @@ class VlcPlayer(MediaPlayer):
|
|||||||
Wait for the video to change its state
|
Wait for the video to change its state
|
||||||
Wait no longer than 60 seconds. (loading an iso file needs a long time)
|
Wait no longer than 60 seconds. (loading an iso file needs a long time)
|
||||||
"""
|
"""
|
||||||
|
vlc = get_vlc()
|
||||||
start = datetime.now()
|
start = datetime.now()
|
||||||
while not media_state == display.vlc_media.get_state():
|
while not media_state == display.vlc_media.get_state():
|
||||||
if display.vlc_media.get_state() == vlc.State.Error:
|
if display.vlc_media.get_state() == vlc.State.Error:
|
||||||
@ -214,6 +233,7 @@ class VlcPlayer(MediaPlayer):
|
|||||||
"""
|
"""
|
||||||
Play the current item
|
Play the current item
|
||||||
"""
|
"""
|
||||||
|
vlc = get_vlc()
|
||||||
controller = display.controller
|
controller = display.controller
|
||||||
start_time = 0
|
start_time = 0
|
||||||
log.debug('vlc play')
|
log.debug('vlc play')
|
||||||
@ -259,6 +279,7 @@ class VlcPlayer(MediaPlayer):
|
|||||||
"""
|
"""
|
||||||
Pause the current item
|
Pause the current item
|
||||||
"""
|
"""
|
||||||
|
vlc = get_vlc()
|
||||||
if display.vlc_media.get_state() != vlc.State.Playing:
|
if display.vlc_media.get_state() != vlc.State.Playing:
|
||||||
return
|
return
|
||||||
display.vlc_media_player.pause()
|
display.vlc_media_player.pause()
|
||||||
@ -308,6 +329,7 @@ class VlcPlayer(MediaPlayer):
|
|||||||
"""
|
"""
|
||||||
Update the UI
|
Update the UI
|
||||||
"""
|
"""
|
||||||
|
vlc = get_vlc()
|
||||||
# Stop video if playback is finished.
|
# Stop video if playback is finished.
|
||||||
if display.vlc_media.get_state() == vlc.State.Ended:
|
if display.vlc_media.get_state() == vlc.State.Ended:
|
||||||
self.stop(display)
|
self.stop(display)
|
||||||
|
@ -375,7 +375,7 @@ class WebkitPlayer(MediaPlayer):
|
|||||||
# check if conversion was ok and value is not 'NaN'
|
# check if conversion was ok and value is not 'NaN'
|
||||||
if length and length != float('inf'):
|
if length and length != float('inf'):
|
||||||
length = int(length * 1000)
|
length = int(length * 1000)
|
||||||
if current_time:
|
if current_time and length:
|
||||||
controller.media_info.length = length
|
controller.media_info.length = length
|
||||||
controller.seek_slider.setMaximum(length)
|
controller.seek_slider.setMaximum(length)
|
||||||
if not controller.seek_slider.isSliderDown():
|
if not controller.seek_slider.isSliderDown():
|
||||||
|
@ -193,13 +193,15 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog, RegistryProperties)
|
|||||||
# Add the text of the service item.
|
# Add the text of the service item.
|
||||||
if item.is_text():
|
if item.is_text():
|
||||||
verse_def = None
|
verse_def = None
|
||||||
|
verse_html = None
|
||||||
for slide in item.get_frames():
|
for slide in item.get_frames():
|
||||||
if not verse_def or verse_def != slide['verseTag']:
|
if not verse_def or verse_def != slide['verseTag'] or verse_html == slide['html']:
|
||||||
text_div = self._add_element('div', parent=div, classId='itemText')
|
text_div = self._add_element('div', parent=div, classId='itemText')
|
||||||
else:
|
else:
|
||||||
self._add_element('br', parent=text_div)
|
self._add_element('br', parent=text_div)
|
||||||
self._add_element('span', slide['html'], text_div)
|
self._add_element('span', slide['html'], text_div)
|
||||||
verse_def = slide['verseTag']
|
verse_def = slide['verseTag']
|
||||||
|
verse_html = slide['html']
|
||||||
# Break the page before the div element.
|
# Break the page before the div element.
|
||||||
if index != 0 and self.page_break_after_text.isChecked():
|
if index != 0 and self.page_break_after_text.isChecked():
|
||||||
div.set('class', 'item newPage')
|
div.set('class', 'item newPage')
|
||||||
|
@ -114,15 +114,15 @@ class Ui_ProjectorManager(object):
|
|||||||
text=translate('OpenLP.ProjectorManager',
|
text=translate('OpenLP.ProjectorManager',
|
||||||
'Connect to selected projector'),
|
'Connect to selected projector'),
|
||||||
icon=':/projector/projector_connect.png',
|
icon=':/projector/projector_connect.png',
|
||||||
tootip=translate('OpenLP.ProjectorManager',
|
tooltip=translate('OpenLP.ProjectorManager',
|
||||||
'Connect to selected projector'),
|
'Connect to selected projector'),
|
||||||
triggers=self.on_connect_projector)
|
triggers=self.on_connect_projector)
|
||||||
self.one_toolbar.add_toolbar_action('connect_projector_multiple',
|
self.one_toolbar.add_toolbar_action('connect_projector_multiple',
|
||||||
text=translate('OpenLP.ProjectorManager',
|
text=translate('OpenLP.ProjectorManager',
|
||||||
'Connect to selected projectors'),
|
'Connect to selected projectors'),
|
||||||
icon=':/projector/projector_connect_tiled.png',
|
icon=':/projector/projector_connect_tiled.png',
|
||||||
tootip=translate('OpenLP.ProjectorManager',
|
tooltip=translate('OpenLP.ProjectorManager',
|
||||||
'Connect to selected projector'),
|
'Connect to selected projector'),
|
||||||
triggers=self.on_connect_projector)
|
triggers=self.on_connect_projector)
|
||||||
self.one_toolbar.add_toolbar_action('disconnect_projector',
|
self.one_toolbar.add_toolbar_action('disconnect_projector',
|
||||||
text=translate('OpenLP.ProjectorManager',
|
text=translate('OpenLP.ProjectorManager',
|
||||||
@ -181,15 +181,15 @@ class Ui_ProjectorManager(object):
|
|||||||
'Blank selected projector screen'),
|
'Blank selected projector screen'),
|
||||||
triggers=self.on_blank_projector)
|
triggers=self.on_blank_projector)
|
||||||
self.one_toolbar.add_toolbar_action('show_projector',
|
self.one_toolbar.add_toolbar_action('show_projector',
|
||||||
ext=translate('OpenLP.ProjectorManager',
|
text=translate('OpenLP.ProjectorManager',
|
||||||
'Show selected projector screen'),
|
'Show selected projector screen'),
|
||||||
icon=':/projector/projector_show.png',
|
icon=':/projector/projector_show.png',
|
||||||
tooltip=translate('OpenLP.ProjectorManager',
|
tooltip=translate('OpenLP.ProjectorManager',
|
||||||
'Show selected projector screen'),
|
'Show selected projector screen'),
|
||||||
triggers=self.on_show_projector)
|
triggers=self.on_show_projector)
|
||||||
self.one_toolbar.add_toolbar_action('show_projector_multiple',
|
self.one_toolbar.add_toolbar_action('show_projector_multiple',
|
||||||
ext=translate('OpenLP.ProjectorManager',
|
text=translate('OpenLP.ProjectorManager',
|
||||||
'Show selected projector screen'),
|
'Show selected projector screen'),
|
||||||
icon=':/projector/projector_show_tiled.png',
|
icon=':/projector/projector_show_tiled.png',
|
||||||
tooltip=translate('OpenLP.ProjectorManager',
|
tooltip=translate('OpenLP.ProjectorManager',
|
||||||
'Show selected projector screen'),
|
'Show selected projector screen'),
|
||||||
|
@ -533,7 +533,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ServiceManage
|
|||||||
self.application.set_normal_cursor()
|
self.application.set_normal_cursor()
|
||||||
title = translate('OpenLP.ServiceManager', 'Service File(s) Missing')
|
title = translate('OpenLP.ServiceManager', 'Service File(s) Missing')
|
||||||
message = translate('OpenLP.ServiceManager',
|
message = translate('OpenLP.ServiceManager',
|
||||||
'The following file(s) in the service are missing:\n\t%s\n\n'
|
'The following file(s) in the service are missing: %s\n\n'
|
||||||
'These files will be removed if you continue to save.') % "\n\t".join(missing_list)
|
'These files will be removed if you continue to save.') % "\n\t".join(missing_list)
|
||||||
answer = QtGui.QMessageBox.critical(self, title, message,
|
answer = QtGui.QMessageBox.critical(self, title, message,
|
||||||
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok |
|
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok |
|
||||||
@ -601,6 +601,12 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ServiceManage
|
|||||||
shutil.copy(temp_file_name, path_file_name)
|
shutil.copy(temp_file_name, path_file_name)
|
||||||
except shutil.Error:
|
except shutil.Error:
|
||||||
return self.save_file_as()
|
return self.save_file_as()
|
||||||
|
except OSError as ose:
|
||||||
|
QtGui.QMessageBox.critical(self, translate('OpenLP.ServiceManager', 'Error Saving File'),
|
||||||
|
translate('OpenLP.ServiceManager', 'An error occurred while writing the '
|
||||||
|
'service file: %s') % ose.strerror,
|
||||||
|
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok))
|
||||||
|
success = False
|
||||||
self.main_window.add_recent_file(path_file_name)
|
self.main_window.add_recent_file(path_file_name)
|
||||||
self.set_modified(False)
|
self.set_modified(False)
|
||||||
delete_file(temp_file_name)
|
delete_file(temp_file_name)
|
||||||
|
@ -408,7 +408,7 @@ class SlideController(DisplayController, RegistryProperties):
|
|||||||
self.set_live_hot_keys(self)
|
self.set_live_hot_keys(self)
|
||||||
self.__add_actions_to_widget(self.controller)
|
self.__add_actions_to_widget(self.controller)
|
||||||
else:
|
else:
|
||||||
self.preview_widget.doubleClicked.connect(self.on_preview_add_to_service)
|
self.preview_widget.doubleClicked.connect(self.on_preview_double_click)
|
||||||
self.toolbar.set_widget_visible(['editSong'], False)
|
self.toolbar.set_widget_visible(['editSong'], False)
|
||||||
self.controller.addActions([self.next_item, self.previous_item])
|
self.controller.addActions([self.next_item, self.previous_item])
|
||||||
Registry().register_function('slidecontroller_%s_stop_loop' % self.type_prefix, self.on_stop_loop)
|
Registry().register_function('slidecontroller_%s_stop_loop' % self.type_prefix, self.on_stop_loop)
|
||||||
@ -717,8 +717,8 @@ class SlideController(DisplayController, RegistryProperties):
|
|||||||
self.play_slides_loop.setChecked(False)
|
self.play_slides_loop.setChecked(False)
|
||||||
self.play_slides_loop.setIcon(build_icon(':/media/media_time.png'))
|
self.play_slides_loop.setIcon(build_icon(':/media/media_time.png'))
|
||||||
if item.is_text():
|
if item.is_text():
|
||||||
if (Settings().value(self.main_window.songs_settings_section + '/display songbar')
|
if (Settings().value(self.main_window.songs_settings_section + '/display songbar') and
|
||||||
and not self.song_menu.menu().isEmpty()):
|
not self.song_menu.menu().isEmpty()):
|
||||||
self.toolbar.set_widget_visible(['song_menu'], True)
|
self.toolbar.set_widget_visible(['song_menu'], True)
|
||||||
if item.is_capable(ItemCapabilities.CanLoop) and len(item.get_frames()) > 1:
|
if item.is_capable(ItemCapabilities.CanLoop) and len(item.get_frames()) > 1:
|
||||||
self.toolbar.set_widget_visible(LOOP_LIST)
|
self.toolbar.set_widget_visible(LOOP_LIST)
|
||||||
@ -824,6 +824,8 @@ class SlideController(DisplayController, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
self.on_stop_loop()
|
self.on_stop_loop()
|
||||||
old_item = self.service_item
|
old_item = self.service_item
|
||||||
|
# rest to allow the remote pick up verse 1 if large imaged
|
||||||
|
self.selected_row = 0
|
||||||
# take a copy not a link to the servicemanager copy.
|
# take a copy not a link to the servicemanager copy.
|
||||||
self.service_item = copy.copy(service_item)
|
self.service_item = copy.copy(service_item)
|
||||||
if old_item and self.is_live and old_item.is_capable(ItemCapabilities.ProvidesOwnDisplay):
|
if old_item and self.is_live and old_item.is_capable(ItemCapabilities.ProvidesOwnDisplay):
|
||||||
@ -1069,8 +1071,13 @@ class SlideController(DisplayController, RegistryProperties):
|
|||||||
:param start:
|
:param start:
|
||||||
"""
|
"""
|
||||||
# Only one thread should be in here at the time. If already locked just skip, since the update will be
|
# Only one thread should be in here at the time. If already locked just skip, since the update will be
|
||||||
# done by the thread holding the lock. If it is a "start" slide, we must wait for the lock.
|
# done by the thread holding the lock. If it is a "start" slide, we must wait for the lock, but only for 0.2
|
||||||
if not self.slide_selected_lock.acquire(start):
|
# seconds, since we don't want to cause a deadlock
|
||||||
|
timeout = 0.2 if start else -1
|
||||||
|
if not self.slide_selected_lock.acquire(start, timeout):
|
||||||
|
if start:
|
||||||
|
self.log_debug('Could not get lock in slide_selected after waiting %f, skip to avoid deadlock.'
|
||||||
|
% timeout)
|
||||||
return
|
return
|
||||||
row = self.preview_widget.current_slide_number()
|
row = self.preview_widget.current_slide_number()
|
||||||
self.selected_row = 0
|
self.selected_row = 0
|
||||||
@ -1309,18 +1316,21 @@ class SlideController(DisplayController, RegistryProperties):
|
|||||||
if self.service_item:
|
if self.service_item:
|
||||||
self.service_manager.add_service_item(self.service_item)
|
self.service_manager.add_service_item(self.service_item)
|
||||||
|
|
||||||
def on_go_live_click(self, field=None):
|
def on_preview_double_click(self, field=None):
|
||||||
"""
|
"""
|
||||||
triggered by clicking the Preview slide items
|
Triggered when a preview slide item is doubleclicked
|
||||||
"""
|
"""
|
||||||
if Settings().value('advanced/double click live'):
|
if self.service_item:
|
||||||
# Live and Preview have issues if we have video or presentations
|
if Settings().value('advanced/double click live'):
|
||||||
# playing in both at the same time.
|
# Live and Preview have issues if we have video or presentations
|
||||||
if self.service_item.is_command():
|
# playing in both at the same time.
|
||||||
Registry().execute('%s_stop' % self.service_item.name.lower(), [self.service_item, self.is_live])
|
if self.service_item.is_command():
|
||||||
if self.service_item.is_media():
|
Registry().execute('%s_stop' % self.service_item.name.lower(), [self.service_item, self.is_live])
|
||||||
self.on_media_close()
|
if self.service_item.is_media():
|
||||||
self.on_go_live()
|
self.on_media_close()
|
||||||
|
self.on_go_live()
|
||||||
|
else:
|
||||||
|
self.on_preview_add_to_service()
|
||||||
|
|
||||||
def on_go_live(self, field=None):
|
def on_go_live(self, field=None):
|
||||||
"""
|
"""
|
||||||
@ -1409,16 +1419,16 @@ class SlideController(DisplayController, RegistryProperties):
|
|||||||
|
|
||||||
class PreviewController(RegistryMixin, OpenLPMixin, SlideController):
|
class PreviewController(RegistryMixin, OpenLPMixin, SlideController):
|
||||||
"""
|
"""
|
||||||
Set up the Live Controller.
|
Set up the Preview Controller.
|
||||||
"""
|
"""
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
"""
|
"""
|
||||||
Set up the general Controller.
|
Set up the base Controller as a preview.
|
||||||
"""
|
"""
|
||||||
super(PreviewController, self).__init__(parent)
|
super(PreviewController, self).__init__(parent)
|
||||||
self.split = 0
|
self.split = 0
|
||||||
self.type_prefix = 'preview'
|
self.type_prefix = 'preview'
|
||||||
self.category = None
|
self.category = 'Preview Toolbar'
|
||||||
|
|
||||||
def bootstrap_post_set_up(self):
|
def bootstrap_post_set_up(self):
|
||||||
"""
|
"""
|
||||||
@ -1433,7 +1443,7 @@ class LiveController(RegistryMixin, OpenLPMixin, SlideController):
|
|||||||
"""
|
"""
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
"""
|
"""
|
||||||
Set up the general Controller.
|
Set up the base Controller as a live.
|
||||||
"""
|
"""
|
||||||
super(LiveController, self).__init__(parent)
|
super(LiveController, self).__init__(parent)
|
||||||
self.is_live = True
|
self.is_live = True
|
||||||
|
@ -31,7 +31,7 @@ from PyQt4 import QtCore, QtGui
|
|||||||
|
|
||||||
from openlp.core.common import Registry, RegistryProperties, AppLocation, Settings, OpenLPMixin, RegistryMixin, \
|
from openlp.core.common import Registry, RegistryProperties, AppLocation, Settings, OpenLPMixin, RegistryMixin, \
|
||||||
check_directory_exists, UiStrings, translate, is_win
|
check_directory_exists, UiStrings, translate, is_win
|
||||||
from openlp.core.lib import FileDialog, ImageSource, OpenLPToolbar, get_text_file_string, build_icon, \
|
from openlp.core.lib import FileDialog, ImageSource, OpenLPToolbar, ValidationError, get_text_file_string, build_icon, \
|
||||||
check_item_selected, create_thumb, validate_thumb
|
check_item_selected, create_thumb, validate_thumb
|
||||||
from openlp.core.lib.theme import ThemeXML, BackgroundType
|
from openlp.core.lib.theme import ThemeXML, BackgroundType
|
||||||
from openlp.core.lib.ui import critical_error_message_box, create_widget_action
|
from openlp.core.lib.ui import critical_error_message_box, create_widget_action
|
||||||
@ -354,11 +354,15 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ThemeManager, R
|
|||||||
delete_file(os.path.join(self.path, thumb))
|
delete_file(os.path.join(self.path, thumb))
|
||||||
delete_file(os.path.join(self.thumb_path, thumb))
|
delete_file(os.path.join(self.thumb_path, thumb))
|
||||||
try:
|
try:
|
||||||
encoding = get_filesystem_encoding()
|
# Windows is always unicode, so no need to encode filenames
|
||||||
shutil.rmtree(os.path.join(self.path, theme).encode(encoding))
|
if is_win():
|
||||||
|
shutil.rmtree(os.path.join(self.path, theme))
|
||||||
|
else:
|
||||||
|
encoding = get_filesystem_encoding()
|
||||||
|
shutil.rmtree(os.path.join(self.path, theme).encode(encoding))
|
||||||
except OSError as os_error:
|
except OSError as os_error:
|
||||||
shutil.Error = os_error
|
shutil.Error = os_error
|
||||||
self.log_exception('Error deleting theme %s', theme)
|
self.log_exception('Error deleting theme %s' % theme)
|
||||||
|
|
||||||
def on_export_theme(self, field=None):
|
def on_export_theme(self, field=None):
|
||||||
"""
|
"""
|
||||||
@ -377,17 +381,11 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ThemeManager, R
|
|||||||
self.application.set_busy_cursor()
|
self.application.set_busy_cursor()
|
||||||
if path:
|
if path:
|
||||||
Settings().setValue(self.settings_section + '/last directory export', path)
|
Settings().setValue(self.settings_section + '/last directory export', path)
|
||||||
try:
|
if self._export_theme(path, theme):
|
||||||
self._export_theme(path, theme)
|
|
||||||
QtGui.QMessageBox.information(self,
|
QtGui.QMessageBox.information(self,
|
||||||
translate('OpenLP.ThemeManager', 'Theme Exported'),
|
translate('OpenLP.ThemeManager', 'Theme Exported'),
|
||||||
translate('OpenLP.ThemeManager',
|
translate('OpenLP.ThemeManager',
|
||||||
'Your theme has been successfully exported.'))
|
'Your theme has been successfully exported.'))
|
||||||
except (IOError, OSError):
|
|
||||||
self.log_exception('Export Theme Failed')
|
|
||||||
critical_error_message_box(translate('OpenLP.ThemeManager', 'Theme Export Failed'),
|
|
||||||
translate('OpenLP.ThemeManager',
|
|
||||||
'Your theme could not be exported due to an error.'))
|
|
||||||
self.application.set_normal_cursor()
|
self.application.set_normal_cursor()
|
||||||
|
|
||||||
def _export_theme(self, path, theme):
|
def _export_theme(self, path, theme):
|
||||||
@ -397,30 +395,35 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ThemeManager, R
|
|||||||
:param theme: The name of the theme to be exported
|
:param theme: The name of the theme to be exported
|
||||||
"""
|
"""
|
||||||
theme_path = os.path.join(path, theme + '.otz')
|
theme_path = os.path.join(path, theme + '.otz')
|
||||||
|
theme_zip = None
|
||||||
try:
|
try:
|
||||||
theme_zip = zipfile.ZipFile(theme_path, 'w')
|
theme_zip = zipfile.ZipFile(theme_path, 'w')
|
||||||
source = os.path.join(self.path, theme)
|
source = os.path.join(self.path, theme)
|
||||||
for files in os.walk(source):
|
for files in os.walk(source):
|
||||||
for name in files[2]:
|
for name in files[2]:
|
||||||
theme_zip.write(os.path.join(source, name), os.path.join(theme, name))
|
theme_zip.write(os.path.join(source, name), os.path.join(theme, name))
|
||||||
except (IOError, OSError):
|
theme_zip.close()
|
||||||
|
return True
|
||||||
|
except OSError as ose:
|
||||||
|
self.log_exception('Export Theme Failed')
|
||||||
|
critical_error_message_box(translate('OpenLP.ThemeManager', 'Theme Export Failed'),
|
||||||
|
translate('OpenLP.ThemeManager', 'The theme export failed because this error '
|
||||||
|
'occurred: %s') % ose.strerror)
|
||||||
if theme_zip:
|
if theme_zip:
|
||||||
theme_zip.close()
|
theme_zip.close()
|
||||||
shutil.rmtree(theme_path, True)
|
shutil.rmtree(theme_path, True)
|
||||||
raise
|
return False
|
||||||
else:
|
|
||||||
theme_zip.close()
|
|
||||||
|
|
||||||
def on_import_theme(self, field=None):
|
def on_import_theme(self, field=None):
|
||||||
"""
|
"""
|
||||||
Opens a file dialog to select the theme file(s) to import before attempting to extract OpenLP themes from
|
Opens a file dialog to select the theme file(s) to import before attempting to extract OpenLP themes from
|
||||||
those files. This process will load both OpenLP version 1 and version 2 themes.
|
those files. This process will only load version 2 themes.
|
||||||
:param field:
|
:param field:
|
||||||
"""
|
"""
|
||||||
files = FileDialog.getOpenFileNames(self,
|
files = FileDialog.getOpenFileNames(self,
|
||||||
translate('OpenLP.ThemeManager', 'Select Theme Import File'),
|
translate('OpenLP.ThemeManager', 'Select Theme Import File'),
|
||||||
Settings().value(self.settings_section + '/last directory import'),
|
Settings().value(self.settings_section + '/last directory import'),
|
||||||
translate('OpenLP.ThemeManager', 'OpenLP Themes (*.theme *.otz)'))
|
translate('OpenLP.ThemeManager', 'OpenLP Themes (*.otz)'))
|
||||||
self.log_info('New Themes %s' % str(files))
|
self.log_info('New Themes %s' % str(files))
|
||||||
if not files:
|
if not files:
|
||||||
return
|
return
|
||||||
@ -535,7 +538,6 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ThemeManager, R
|
|||||||
:param directory:
|
:param directory:
|
||||||
"""
|
"""
|
||||||
self.log_debug('Unzipping theme %s' % file_name)
|
self.log_debug('Unzipping theme %s' % file_name)
|
||||||
file_name = str(file_name)
|
|
||||||
theme_zip = None
|
theme_zip = None
|
||||||
out_file = None
|
out_file = None
|
||||||
file_xml = None
|
file_xml = None
|
||||||
@ -545,8 +547,12 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ThemeManager, R
|
|||||||
xml_file = [name for name in theme_zip.namelist() if os.path.splitext(name)[1].lower() == '.xml']
|
xml_file = [name for name in theme_zip.namelist() if os.path.splitext(name)[1].lower() == '.xml']
|
||||||
if len(xml_file) != 1:
|
if len(xml_file) != 1:
|
||||||
self.log_error('Theme contains "%s" XML files' % len(xml_file))
|
self.log_error('Theme contains "%s" XML files' % len(xml_file))
|
||||||
raise Exception('validation')
|
raise ValidationError
|
||||||
xml_tree = ElementTree(element=XML(theme_zip.read(xml_file[0]))).getroot()
|
xml_tree = ElementTree(element=XML(theme_zip.read(xml_file[0]))).getroot()
|
||||||
|
theme_version = xml_tree.get('version', default=None)
|
||||||
|
if not theme_version or float(theme_version) < 2.0:
|
||||||
|
self.log_error('Theme version is less than 2.0')
|
||||||
|
raise ValidationError
|
||||||
theme_name = xml_tree.find('name').text.strip()
|
theme_name = xml_tree.find('name').text.strip()
|
||||||
theme_folder = os.path.join(directory, theme_name)
|
theme_folder = os.path.join(directory, theme_name)
|
||||||
theme_exists = os.path.exists(theme_folder)
|
theme_exists = os.path.exists(theme_folder)
|
||||||
@ -565,7 +571,7 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ThemeManager, R
|
|||||||
check_directory_exists(os.path.dirname(full_name))
|
check_directory_exists(os.path.dirname(full_name))
|
||||||
if os.path.splitext(name)[1].lower() == '.xml':
|
if os.path.splitext(name)[1].lower() == '.xml':
|
||||||
file_xml = str(theme_zip.read(name), 'utf-8')
|
file_xml = str(theme_zip.read(name), 'utf-8')
|
||||||
out_file = open(full_name, 'w')
|
out_file = open(full_name, 'w', encoding='utf-8')
|
||||||
out_file.write(file_xml)
|
out_file.write(file_xml)
|
||||||
else:
|
else:
|
||||||
out_file = open(full_name, 'wb')
|
out_file = open(full_name, 'wb')
|
||||||
@ -573,13 +579,10 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ThemeManager, R
|
|||||||
out_file.close()
|
out_file.close()
|
||||||
except (IOError, zipfile.BadZipfile):
|
except (IOError, zipfile.BadZipfile):
|
||||||
self.log_exception('Importing theme from zip failed %s' % file_name)
|
self.log_exception('Importing theme from zip failed %s' % file_name)
|
||||||
raise Exception('validation')
|
raise ValidationError
|
||||||
except Exception as info:
|
except ValidationError:
|
||||||
if str(info) == 'validation':
|
critical_error_message_box(translate('OpenLP.ThemeManager', 'Validation Error'),
|
||||||
critical_error_message_box(translate('OpenLP.ThemeManager', 'Validation Error'),
|
translate('OpenLP.ThemeManager', 'File is not a valid theme.'))
|
||||||
translate('OpenLP.ThemeManager', 'File is not a valid theme.'))
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
finally:
|
finally:
|
||||||
# Close the files, to be able to continue creating the theme.
|
# Close the files, to be able to continue creating the theme.
|
||||||
if theme_zip:
|
if theme_zip:
|
||||||
@ -646,8 +649,8 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ThemeManager, R
|
|||||||
delete_file(self.old_background_image)
|
delete_file(self.old_background_image)
|
||||||
out_file = None
|
out_file = None
|
||||||
try:
|
try:
|
||||||
out_file = open(theme_file, 'w')
|
out_file = open(theme_file, 'w', encoding='utf-8')
|
||||||
out_file.write(theme_pretty_xml.decode('UTF-8'))
|
out_file.write(theme_pretty_xml.decode('utf-8'))
|
||||||
except IOError:
|
except IOError:
|
||||||
self.log_exception('Saving theme to file failed')
|
self.log_exception('Saving theme to file failed')
|
||||||
finally:
|
finally:
|
||||||
|
@ -29,6 +29,7 @@ import locale
|
|||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import re
|
import re
|
||||||
|
import socket
|
||||||
import time
|
import time
|
||||||
from shutil import which
|
from shutil import which
|
||||||
from subprocess import Popen, PIPE
|
from subprocess import Popen, PIPE
|
||||||
@ -394,26 +395,44 @@ def get_web_page(url, header=None, update_openlp=False):
|
|||||||
req.add_header('User-Agent', user_agent)
|
req.add_header('User-Agent', user_agent)
|
||||||
if header:
|
if header:
|
||||||
req.add_header(header[0], header[1])
|
req.add_header(header[0], header[1])
|
||||||
page = None
|
|
||||||
log.debug('Downloading URL = %s' % url)
|
log.debug('Downloading URL = %s' % url)
|
||||||
retries = 1
|
retries = 0
|
||||||
while True:
|
while retries <= CONNECTION_RETRIES:
|
||||||
|
retries += 1
|
||||||
|
time.sleep(0.1)
|
||||||
try:
|
try:
|
||||||
page = urllib.request.urlopen(req, timeout=CONNECTION_TIMEOUT)
|
page = urllib.request.urlopen(req, timeout=CONNECTION_TIMEOUT)
|
||||||
log.debug('Downloaded URL = %s' % page.geturl())
|
log.debug('Downloaded page {}'.format(page.geturl()))
|
||||||
except (urllib.error.URLError, ConnectionError):
|
except urllib.error.URLError as err:
|
||||||
|
log.exception('URLError on {}'.format(url))
|
||||||
|
log.exception('URLError: {}'.format(err.reason))
|
||||||
|
page = None
|
||||||
if retries > CONNECTION_RETRIES:
|
if retries > CONNECTION_RETRIES:
|
||||||
log.exception('The web page could not be downloaded')
|
|
||||||
raise
|
raise
|
||||||
else:
|
except socket.timeout:
|
||||||
retries += 1
|
log.exception('Socket timeout: {}'.format(url))
|
||||||
time.sleep(0.1)
|
page = None
|
||||||
continue
|
if retries > CONNECTION_RETRIES:
|
||||||
break
|
raise
|
||||||
if not page:
|
except ConnectionRefusedError:
|
||||||
return None
|
log.exception('ConnectionRefused: {}'.format(url))
|
||||||
|
page = None
|
||||||
|
if retries > CONNECTION_RETRIES:
|
||||||
|
raise
|
||||||
|
break
|
||||||
|
except ConnectionError:
|
||||||
|
log.exception('Connection error: {}'.format(url))
|
||||||
|
page = None
|
||||||
|
if retries > CONNECTION_RETRIES:
|
||||||
|
raise
|
||||||
|
except:
|
||||||
|
# Don't know what's happening, so reraise the original
|
||||||
|
raise
|
||||||
if update_openlp:
|
if update_openlp:
|
||||||
Registry().get('application').process_events()
|
Registry().get('application').process_events()
|
||||||
|
if not page:
|
||||||
|
log.exception('{} could not be downloaded'.format(url))
|
||||||
|
return None
|
||||||
log.debug(page)
|
log.debug(page)
|
||||||
return page
|
return page
|
||||||
|
|
||||||
|
@ -112,6 +112,10 @@ __default_settings__ = {
|
|||||||
'alerts/font face': QtGui.QFont().family(),
|
'alerts/font face': QtGui.QFont().family(),
|
||||||
'alerts/font size': 40,
|
'alerts/font size': 40,
|
||||||
'alerts/db type': 'sqlite',
|
'alerts/db type': 'sqlite',
|
||||||
|
'alerts/db username': '',
|
||||||
|
'alerts/db password': '',
|
||||||
|
'alerts/db hostname': '',
|
||||||
|
'alerts/db database': '',
|
||||||
'alerts/location': AlertLocation.Bottom,
|
'alerts/location': AlertLocation.Bottom,
|
||||||
'alerts/background color': '#660000',
|
'alerts/background color': '#660000',
|
||||||
'alerts/font color': '#ffffff',
|
'alerts/font color': '#ffffff',
|
||||||
|
@ -26,7 +26,7 @@ displaying of alerts.
|
|||||||
|
|
||||||
from PyQt4 import QtCore
|
from PyQt4 import QtCore
|
||||||
|
|
||||||
from openlp.core.common import OpenLPMixin, RegistryMixin, Registry, RegistryProperties, translate
|
from openlp.core.common import OpenLPMixin, RegistryMixin, Registry, RegistryProperties, Settings, translate
|
||||||
|
|
||||||
|
|
||||||
class AlertsManager(OpenLPMixin, RegistryMixin, QtCore.QObject, RegistryProperties):
|
class AlertsManager(OpenLPMixin, RegistryMixin, QtCore.QObject, RegistryProperties):
|
||||||
@ -70,7 +70,8 @@ class AlertsManager(OpenLPMixin, RegistryMixin, QtCore.QObject, RegistryProperti
|
|||||||
"""
|
"""
|
||||||
Format and request the Alert and start the timer.
|
Format and request the Alert and start the timer.
|
||||||
"""
|
"""
|
||||||
if not self.alert_list:
|
if not self.alert_list or (self.live_controller.display.screens.display_count == 1 and
|
||||||
|
not Settings().value('core/display on monitor')):
|
||||||
return
|
return
|
||||||
text = self.alert_list.pop(0)
|
text = self.alert_list.pop(0)
|
||||||
alert_tab = self.parent().settings_tab
|
alert_tab = self.parent().settings_tab
|
||||||
|
@ -37,6 +37,10 @@ log = logging.getLogger(__name__)
|
|||||||
|
|
||||||
__default_settings__ = {
|
__default_settings__ = {
|
||||||
'bibles/db type': 'sqlite',
|
'bibles/db type': 'sqlite',
|
||||||
|
'bibles/db username': '',
|
||||||
|
'bibles/db password': '',
|
||||||
|
'bibles/db hostname': '',
|
||||||
|
'bibles/db database': '',
|
||||||
'bibles/last search type': BibleSearch.Reference,
|
'bibles/last search type': BibleSearch.Reference,
|
||||||
'bibles/verse layout style': LayoutStyle.VersePerSlide,
|
'bibles/verse layout style': LayoutStyle.VersePerSlide,
|
||||||
'bibles/book name language': LanguageSelection.Bible,
|
'bibles/book name language': LanguageSelection.Bible,
|
||||||
@ -109,12 +113,13 @@ class BiblePlugin(Plugin):
|
|||||||
'existing Bibles.\nShould OpenLP upgrade now?'),
|
'existing Bibles.\nShould OpenLP upgrade now?'),
|
||||||
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No)) == \
|
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No)) == \
|
||||||
QtGui.QMessageBox.Yes:
|
QtGui.QMessageBox.Yes:
|
||||||
self.on_tools_upgrade_Item_triggered()
|
self.on_tools_upgrade_item_triggered()
|
||||||
|
|
||||||
def add_import_menu_item(self, import_menu):
|
def add_import_menu_item(self, import_menu):
|
||||||
"""
|
"""
|
||||||
|
Add an import menu item
|
||||||
|
|
||||||
:param import_menu:
|
:param import_menu: The menu to insert the menu item into.
|
||||||
"""
|
"""
|
||||||
self.import_bible_item = create_action(import_menu, 'importBibleItem',
|
self.import_bible_item = create_action(import_menu, 'importBibleItem',
|
||||||
text=translate('BiblesPlugin', '&Bible'), visible=False,
|
text=translate('BiblesPlugin', '&Bible'), visible=False,
|
||||||
@ -123,8 +128,9 @@ class BiblePlugin(Plugin):
|
|||||||
|
|
||||||
def add_export_menu_item(self, export_menu):
|
def add_export_menu_item(self, export_menu):
|
||||||
"""
|
"""
|
||||||
|
Add an export menu item
|
||||||
|
|
||||||
:param export_menu:
|
:param export_menu: The menu to insert the menu item into.
|
||||||
"""
|
"""
|
||||||
self.export_bible_item = create_action(export_menu, 'exportBibleItem',
|
self.export_bible_item = create_action(export_menu, 'exportBibleItem',
|
||||||
text=translate('BiblesPlugin', '&Bible'), visible=False)
|
text=translate('BiblesPlugin', '&Bible'), visible=False)
|
||||||
@ -141,10 +147,10 @@ class BiblePlugin(Plugin):
|
|||||||
tools_menu, 'toolsUpgradeItem',
|
tools_menu, 'toolsUpgradeItem',
|
||||||
text=translate('BiblesPlugin', '&Upgrade older Bibles'),
|
text=translate('BiblesPlugin', '&Upgrade older Bibles'),
|
||||||
statustip=translate('BiblesPlugin', 'Upgrade the Bible databases to the latest format.'),
|
statustip=translate('BiblesPlugin', 'Upgrade the Bible databases to the latest format.'),
|
||||||
visible=False, triggers=self.on_tools_upgrade_Item_triggered)
|
visible=False, triggers=self.on_tools_upgrade_item_triggered)
|
||||||
tools_menu.addAction(self.tools_upgrade_item)
|
tools_menu.addAction(self.tools_upgrade_item)
|
||||||
|
|
||||||
def on_tools_upgrade_Item_triggered(self):
|
def on_tools_upgrade_item_triggered(self):
|
||||||
"""
|
"""
|
||||||
Upgrade older bible databases.
|
Upgrade older bible databases.
|
||||||
"""
|
"""
|
||||||
@ -155,10 +161,16 @@ class BiblePlugin(Plugin):
|
|||||||
self.media_item.reload_bibles()
|
self.media_item.reload_bibles()
|
||||||
|
|
||||||
def on_bible_import_click(self):
|
def on_bible_import_click(self):
|
||||||
|
"""
|
||||||
|
Show the Bible Import wizard
|
||||||
|
"""
|
||||||
if self.media_item:
|
if self.media_item:
|
||||||
self.media_item.on_import_click()
|
self.media_item.on_import_click()
|
||||||
|
|
||||||
def about(self):
|
def about(self):
|
||||||
|
"""
|
||||||
|
Return the about text for the plugin manager
|
||||||
|
"""
|
||||||
about_text = translate('BiblesPlugin', '<strong>Bible Plugin</strong>'
|
about_text = translate('BiblesPlugin', '<strong>Bible Plugin</strong>'
|
||||||
'<br />The Bible plugin provides the ability to display Bible '
|
'<br />The Bible plugin provides the ability to display Bible '
|
||||||
'verses from different sources during the service.')
|
'verses from different sources during the service.')
|
||||||
|
@ -24,6 +24,7 @@ The bible import functions for OpenLP
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import urllib.error
|
||||||
|
|
||||||
from PyQt4 import QtGui
|
from PyQt4 import QtGui
|
||||||
|
|
||||||
@ -34,6 +35,7 @@ from openlp.core.ui.wizard import OpenLPWizard, WizardStrings
|
|||||||
from openlp.core.utils import get_locale_key
|
from openlp.core.utils import get_locale_key
|
||||||
from openlp.plugins.bibles.lib.manager import BibleFormat
|
from openlp.plugins.bibles.lib.manager import BibleFormat
|
||||||
from openlp.plugins.bibles.lib.db import BiblesResourcesDB, clean_filename
|
from openlp.plugins.bibles.lib.db import BiblesResourcesDB, clean_filename
|
||||||
|
from openlp.plugins.bibles.lib.http import CWExtract, BGExtract, BSExtract
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -90,7 +92,6 @@ class BibleImportForm(OpenLPWizard):
|
|||||||
Perform any custom initialisation for bible importing.
|
Perform any custom initialisation for bible importing.
|
||||||
"""
|
"""
|
||||||
self.manager.set_process_dialog(self)
|
self.manager.set_process_dialog(self)
|
||||||
self.load_Web_Bibles()
|
|
||||||
self.restart()
|
self.restart()
|
||||||
self.select_stack.setCurrentIndex(0)
|
self.select_stack.setCurrentIndex(0)
|
||||||
|
|
||||||
@ -104,6 +105,7 @@ class BibleImportForm(OpenLPWizard):
|
|||||||
self.csv_verses_button.clicked.connect(self.on_csv_verses_browse_button_clicked)
|
self.csv_verses_button.clicked.connect(self.on_csv_verses_browse_button_clicked)
|
||||||
self.open_song_browse_button.clicked.connect(self.on_open_song_browse_button_clicked)
|
self.open_song_browse_button.clicked.connect(self.on_open_song_browse_button_clicked)
|
||||||
self.zefania_browse_button.clicked.connect(self.on_zefania_browse_button_clicked)
|
self.zefania_browse_button.clicked.connect(self.on_zefania_browse_button_clicked)
|
||||||
|
self.web_update_button.clicked.connect(self.on_web_update_button_clicked)
|
||||||
|
|
||||||
def add_custom_pages(self):
|
def add_custom_pages(self):
|
||||||
"""
|
"""
|
||||||
@ -202,20 +204,33 @@ class BibleImportForm(OpenLPWizard):
|
|||||||
self.web_bible_tab.setObjectName('WebBibleTab')
|
self.web_bible_tab.setObjectName('WebBibleTab')
|
||||||
self.web_bible_layout = QtGui.QFormLayout(self.web_bible_tab)
|
self.web_bible_layout = QtGui.QFormLayout(self.web_bible_tab)
|
||||||
self.web_bible_layout.setObjectName('WebBibleLayout')
|
self.web_bible_layout.setObjectName('WebBibleLayout')
|
||||||
|
self.web_update_label = QtGui.QLabel(self.web_bible_tab)
|
||||||
|
self.web_update_label.setObjectName('WebUpdateLabel')
|
||||||
|
self.web_bible_layout.setWidget(0, QtGui.QFormLayout.LabelRole, self.web_update_label)
|
||||||
|
self.web_update_button = QtGui.QPushButton(self.web_bible_tab)
|
||||||
|
self.web_update_button.setObjectName('WebUpdateButton')
|
||||||
|
self.web_bible_layout.setWidget(0, QtGui.QFormLayout.FieldRole, self.web_update_button)
|
||||||
self.web_source_label = QtGui.QLabel(self.web_bible_tab)
|
self.web_source_label = QtGui.QLabel(self.web_bible_tab)
|
||||||
self.web_source_label.setObjectName('WebSourceLabel')
|
self.web_source_label.setObjectName('WebSourceLabel')
|
||||||
self.web_bible_layout.setWidget(0, QtGui.QFormLayout.LabelRole, self.web_source_label)
|
self.web_bible_layout.setWidget(1, QtGui.QFormLayout.LabelRole, self.web_source_label)
|
||||||
self.web_source_combo_box = QtGui.QComboBox(self.web_bible_tab)
|
self.web_source_combo_box = QtGui.QComboBox(self.web_bible_tab)
|
||||||
self.web_source_combo_box.setObjectName('WebSourceComboBox')
|
self.web_source_combo_box.setObjectName('WebSourceComboBox')
|
||||||
self.web_source_combo_box.addItems(['', '', ''])
|
self.web_source_combo_box.addItems(['', '', ''])
|
||||||
self.web_bible_layout.setWidget(0, QtGui.QFormLayout.FieldRole, self.web_source_combo_box)
|
self.web_source_combo_box.setEnabled(False)
|
||||||
|
self.web_bible_layout.setWidget(1, QtGui.QFormLayout.FieldRole, self.web_source_combo_box)
|
||||||
self.web_translation_label = QtGui.QLabel(self.web_bible_tab)
|
self.web_translation_label = QtGui.QLabel(self.web_bible_tab)
|
||||||
self.web_translation_label.setObjectName('web_translation_label')
|
self.web_translation_label.setObjectName('web_translation_label')
|
||||||
self.web_bible_layout.setWidget(1, QtGui.QFormLayout.LabelRole, self.web_translation_label)
|
self.web_bible_layout.setWidget(2, QtGui.QFormLayout.LabelRole, self.web_translation_label)
|
||||||
self.web_translation_combo_box = QtGui.QComboBox(self.web_bible_tab)
|
self.web_translation_combo_box = QtGui.QComboBox(self.web_bible_tab)
|
||||||
self.web_translation_combo_box.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents)
|
self.web_translation_combo_box.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents)
|
||||||
self.web_translation_combo_box.setObjectName('WebTranslationComboBox')
|
self.web_translation_combo_box.setObjectName('WebTranslationComboBox')
|
||||||
self.web_bible_layout.setWidget(1, QtGui.QFormLayout.FieldRole, self.web_translation_combo_box)
|
self.web_translation_combo_box.setEnabled(False)
|
||||||
|
self.web_bible_layout.setWidget(2, QtGui.QFormLayout.FieldRole, self.web_translation_combo_box)
|
||||||
|
self.web_progress_bar = QtGui.QProgressBar(self)
|
||||||
|
self.web_progress_bar.setRange(0, 3)
|
||||||
|
self.web_progress_bar.setObjectName('WebTranslationProgressBar')
|
||||||
|
self.web_progress_bar.setVisible(False)
|
||||||
|
self.web_bible_layout.setWidget(3, QtGui.QFormLayout.SpanningRole, self.web_progress_bar)
|
||||||
self.web_tab_widget.addTab(self.web_bible_tab, '')
|
self.web_tab_widget.addTab(self.web_bible_tab, '')
|
||||||
self.web_proxy_tab = QtGui.QWidget()
|
self.web_proxy_tab = QtGui.QWidget()
|
||||||
self.web_proxy_tab.setObjectName('WebProxyTab')
|
self.web_proxy_tab.setObjectName('WebProxyTab')
|
||||||
@ -314,6 +329,8 @@ class BibleImportForm(OpenLPWizard):
|
|||||||
self.open_song_file_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Bible file:'))
|
self.open_song_file_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Bible file:'))
|
||||||
self.web_source_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Location:'))
|
self.web_source_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Location:'))
|
||||||
self.zefania_file_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Bible file:'))
|
self.zefania_file_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Bible file:'))
|
||||||
|
self.web_update_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Click to download bible list'))
|
||||||
|
self.web_update_button.setText(translate('BiblesPlugin.ImportWizardForm', 'Download bible list'))
|
||||||
self.web_source_combo_box.setItemText(WebDownload.Crosswalk, translate('BiblesPlugin.ImportWizardForm',
|
self.web_source_combo_box.setItemText(WebDownload.Crosswalk, translate('BiblesPlugin.ImportWizardForm',
|
||||||
'Crosswalk'))
|
'Crosswalk'))
|
||||||
self.web_source_combo_box.setItemText(WebDownload.BibleGateway, translate('BiblesPlugin.ImportWizardForm',
|
self.web_source_combo_box.setItemText(WebDownload.BibleGateway, translate('BiblesPlugin.ImportWizardForm',
|
||||||
@ -388,8 +405,11 @@ class BibleImportForm(OpenLPWizard):
|
|||||||
self.zefania_file_edit.setFocus()
|
self.zefania_file_edit.setFocus()
|
||||||
return False
|
return False
|
||||||
elif self.field('source_format') == BibleFormat.WebDownload:
|
elif self.field('source_format') == BibleFormat.WebDownload:
|
||||||
self.version_name_edit.setText(self.web_translation_combo_box.currentText())
|
# If count is 0 the bible list has not yet been downloaded
|
||||||
return True
|
if self.web_translation_combo_box.count() == 0:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
self.version_name_edit.setText(self.web_translation_combo_box.currentText())
|
||||||
return True
|
return True
|
||||||
elif self.currentPage() == self.license_details_page:
|
elif self.currentPage() == self.license_details_page:
|
||||||
license_version = self.field('license_version')
|
license_version = self.field('license_version')
|
||||||
@ -434,9 +454,10 @@ class BibleImportForm(OpenLPWizard):
|
|||||||
:param index: The index of the combo box.
|
:param index: The index of the combo box.
|
||||||
"""
|
"""
|
||||||
self.web_translation_combo_box.clear()
|
self.web_translation_combo_box.clear()
|
||||||
bibles = list(self.web_bible_list[index].keys())
|
if self.web_bible_list:
|
||||||
bibles.sort(key=get_locale_key)
|
bibles = list(self.web_bible_list[index].keys())
|
||||||
self.web_translation_combo_box.addItems(bibles)
|
bibles.sort(key=get_locale_key)
|
||||||
|
self.web_translation_combo_box.addItems(bibles)
|
||||||
|
|
||||||
def on_osis_browse_button_clicked(self):
|
def on_osis_browse_button_clicked(self):
|
||||||
"""
|
"""
|
||||||
@ -475,6 +496,39 @@ class BibleImportForm(OpenLPWizard):
|
|||||||
self.get_file_name(WizardStrings.OpenTypeFile % WizardStrings.ZEF, self.zefania_file_edit,
|
self.get_file_name(WizardStrings.OpenTypeFile % WizardStrings.ZEF, self.zefania_file_edit,
|
||||||
'last directory import')
|
'last directory import')
|
||||||
|
|
||||||
|
def on_web_update_button_clicked(self):
|
||||||
|
"""
|
||||||
|
Download list of bibles from Crosswalk, BibleServer and BibleGateway.
|
||||||
|
"""
|
||||||
|
# Download from Crosswalk, BiblesGateway, BibleServer
|
||||||
|
self.web_bible_list = {}
|
||||||
|
self.web_source_combo_box.setEnabled(False)
|
||||||
|
self.web_translation_combo_box.setEnabled(False)
|
||||||
|
self.web_update_button.setEnabled(False)
|
||||||
|
self.web_progress_bar.setVisible(True)
|
||||||
|
self.web_progress_bar.setValue(0)
|
||||||
|
proxy_server = self.field('proxy_server')
|
||||||
|
for (download_type, extractor) in ((WebDownload.Crosswalk, CWExtract(proxy_server)),
|
||||||
|
(WebDownload.BibleGateway, BGExtract(proxy_server)),
|
||||||
|
(WebDownload.Bibleserver, BSExtract(proxy_server))):
|
||||||
|
try:
|
||||||
|
bibles = extractor.get_bibles_from_http()
|
||||||
|
except (urllib.error.URLError, ConnectionError) as err:
|
||||||
|
critical_error_message_box(translate('BiblesPlugin.ImportWizardForm', 'Error during download'),
|
||||||
|
translate('BiblesPlugin.ImportWizardForm',
|
||||||
|
'An error occurred while downloading the list of bibles from %s.'))
|
||||||
|
self.web_bible_list[download_type] = {}
|
||||||
|
for (bible_name, bible_key, language_code) in bibles:
|
||||||
|
self.web_bible_list[download_type][bible_name] = (bible_key, language_code)
|
||||||
|
self.web_progress_bar.setValue(download_type + 1)
|
||||||
|
# Update combo box if something got into the list
|
||||||
|
if self.web_bible_list:
|
||||||
|
self.on_web_source_combo_box_index_changed(0)
|
||||||
|
self.web_source_combo_box.setEnabled(True)
|
||||||
|
self.web_translation_combo_box.setEnabled(True)
|
||||||
|
self.web_update_button.setEnabled(True)
|
||||||
|
self.web_progress_bar.setVisible(False)
|
||||||
|
|
||||||
def register_fields(self):
|
def register_fields(self):
|
||||||
"""
|
"""
|
||||||
Register the bible import wizard fields.
|
Register the bible import wizard fields.
|
||||||
@ -520,30 +574,6 @@ class BibleImportForm(OpenLPWizard):
|
|||||||
self.on_web_source_combo_box_index_changed(WebDownload.Crosswalk)
|
self.on_web_source_combo_box_index_changed(WebDownload.Crosswalk)
|
||||||
settings.endGroup()
|
settings.endGroup()
|
||||||
|
|
||||||
def load_Web_Bibles(self):
|
|
||||||
"""
|
|
||||||
Load the lists of Crosswalk, BibleGateway and Bibleserver bibles.
|
|
||||||
"""
|
|
||||||
# Load Crosswalk Bibles.
|
|
||||||
self.load_Bible_Resource(WebDownload.Crosswalk)
|
|
||||||
# Load BibleGateway Bibles.
|
|
||||||
self.load_Bible_Resource(WebDownload.BibleGateway)
|
|
||||||
# Load and Bibleserver Bibles.
|
|
||||||
self.load_Bible_Resource(WebDownload.Bibleserver)
|
|
||||||
|
|
||||||
def load_Bible_Resource(self, download_type):
|
|
||||||
"""
|
|
||||||
Loads a web bible from bible_resources.sqlite.
|
|
||||||
|
|
||||||
:param download_type: The WebDownload type e.g. bibleserver.
|
|
||||||
"""
|
|
||||||
self.web_bible_list[download_type] = {}
|
|
||||||
bibles = BiblesResourcesDB.get_webbibles(WebDownload.Names[download_type])
|
|
||||||
for bible in bibles:
|
|
||||||
version = bible['name']
|
|
||||||
name = bible['abbreviation']
|
|
||||||
self.web_bible_list[download_type][version] = name.strip()
|
|
||||||
|
|
||||||
def pre_wizard(self):
|
def pre_wizard(self):
|
||||||
"""
|
"""
|
||||||
Prepare the UI for the import.
|
Prepare the UI for the import.
|
||||||
@ -583,14 +613,15 @@ class BibleImportForm(OpenLPWizard):
|
|||||||
self.progress_bar.setMaximum(1)
|
self.progress_bar.setMaximum(1)
|
||||||
download_location = self.field('web_location')
|
download_location = self.field('web_location')
|
||||||
bible_version = self.web_translation_combo_box.currentText()
|
bible_version = self.web_translation_combo_box.currentText()
|
||||||
bible = self.web_bible_list[download_location][bible_version]
|
(bible, language_id) = self.web_bible_list[download_location][bible_version]
|
||||||
importer = self.manager.import_bible(
|
importer = self.manager.import_bible(
|
||||||
BibleFormat.WebDownload, name=license_version,
|
BibleFormat.WebDownload, name=license_version,
|
||||||
download_source=WebDownload.Names[download_location],
|
download_source=WebDownload.Names[download_location],
|
||||||
download_name=bible,
|
download_name=bible,
|
||||||
proxy_server=self.field('proxy_server'),
|
proxy_server=self.field('proxy_server'),
|
||||||
proxy_username=self.field('proxy_username'),
|
proxy_username=self.field('proxy_username'),
|
||||||
proxy_password=self.field('proxy_password')
|
proxy_password=self.field('proxy_password'),
|
||||||
|
language_id=language_id
|
||||||
)
|
)
|
||||||
elif bible_type == BibleFormat.Zefania:
|
elif bible_type == BibleFormat.Zefania:
|
||||||
# Import an Zefania bible.
|
# Import an Zefania bible.
|
||||||
|
@ -73,7 +73,7 @@ class CSVBible(BibleDB):
|
|||||||
"""
|
"""
|
||||||
log.info(self.__class__.__name__)
|
log.info(self.__class__.__name__)
|
||||||
BibleDB.__init__(self, parent, **kwargs)
|
BibleDB.__init__(self, parent, **kwargs)
|
||||||
self.books_file = kwargs['books_file']
|
self.books_file = kwargs['booksfile']
|
||||||
self.verses_file = kwargs['versefile']
|
self.verses_file = kwargs['versefile']
|
||||||
|
|
||||||
def do_import(self, bible_name=None):
|
def do_import(self, bible_name=None):
|
||||||
@ -93,23 +93,20 @@ class CSVBible(BibleDB):
|
|||||||
# Populate the Tables
|
# Populate the Tables
|
||||||
try:
|
try:
|
||||||
details = get_file_encoding(self.books_file)
|
details = get_file_encoding(self.books_file)
|
||||||
books_file = open(self.books_file, 'r')
|
books_file = open(self.books_file, 'r', encoding=details['encoding'])
|
||||||
if not books_file.read(3) == '\xEF\xBB\xBF':
|
|
||||||
# no BOM was found
|
|
||||||
books_file.seek(0)
|
|
||||||
books_reader = csv.reader(books_file, delimiter=',', quotechar='"')
|
books_reader = csv.reader(books_file, delimiter=',', quotechar='"')
|
||||||
for line in books_reader:
|
for line in books_reader:
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
break
|
break
|
||||||
self.wizard.increment_progress_bar(translate('BiblesPlugin.CSVBible', 'Importing books... %s') %
|
self.wizard.increment_progress_bar(translate('BiblesPlugin.CSVBible', 'Importing books... %s')
|
||||||
str(line[2], details['encoding']))
|
% line[2])
|
||||||
book_ref_id = self.get_book_ref_id_by_name(str(line[2], details['encoding']), 67, language_id)
|
book_ref_id = self.get_book_ref_id_by_name(line[2], 67, language_id)
|
||||||
if not book_ref_id:
|
if not book_ref_id:
|
||||||
log.error('Importing books from "%s" failed' % self.books_file)
|
log.error('Importing books from "%s" failed' % self.books_file)
|
||||||
return False
|
return False
|
||||||
book_details = BiblesResourcesDB.get_book_by_id(book_ref_id)
|
book_details = BiblesResourcesDB.get_book_by_id(book_ref_id)
|
||||||
self.create_book(str(line[2], details['encoding']), book_ref_id, book_details['testament_id'])
|
self.create_book(line[2], book_ref_id, book_details['testament_id'])
|
||||||
book_list[int(line[0])] = str(line[2], details['encoding'])
|
book_list.update({int(line[0]): line[2]})
|
||||||
self.application.process_events()
|
self.application.process_events()
|
||||||
except (IOError, IndexError):
|
except (IOError, IndexError):
|
||||||
log.exception('Loading books from file failed')
|
log.exception('Loading books from file failed')
|
||||||
@ -125,10 +122,7 @@ class CSVBible(BibleDB):
|
|||||||
try:
|
try:
|
||||||
book_ptr = None
|
book_ptr = None
|
||||||
details = get_file_encoding(self.verses_file)
|
details = get_file_encoding(self.verses_file)
|
||||||
verse_file = open(self.verses_file, 'rb')
|
verse_file = open(self.verses_file, 'r', encoding=details['encoding'])
|
||||||
if not verse_file.read(3) == '\xEF\xBB\xBF':
|
|
||||||
# no BOM was found
|
|
||||||
verse_file.seek(0)
|
|
||||||
verse_reader = csv.reader(verse_file, delimiter=',', quotechar='"')
|
verse_reader = csv.reader(verse_file, delimiter=',', quotechar='"')
|
||||||
for line in verse_reader:
|
for line in verse_reader:
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
@ -136,7 +130,7 @@ class CSVBible(BibleDB):
|
|||||||
try:
|
try:
|
||||||
line_book = book_list[int(line[0])]
|
line_book = book_list[int(line[0])]
|
||||||
except ValueError:
|
except ValueError:
|
||||||
line_book = str(line[0], details['encoding'])
|
line_book = line[0]
|
||||||
if book_ptr != line_book:
|
if book_ptr != line_book:
|
||||||
book = self.get_book(line_book)
|
book = self.get_book(line_book)
|
||||||
book_ptr = book.name
|
book_ptr = book.name
|
||||||
@ -144,10 +138,7 @@ class CSVBible(BibleDB):
|
|||||||
translate('BiblesPlugin.CSVBible',
|
translate('BiblesPlugin.CSVBible',
|
||||||
'Importing verses from %s...' % book.name, 'Importing verses from <book name>...'))
|
'Importing verses from %s...' % book.name, 'Importing verses from <book name>...'))
|
||||||
self.session.commit()
|
self.session.commit()
|
||||||
try:
|
verse_text = line[3]
|
||||||
verse_text = str(line[3], details['encoding'])
|
|
||||||
except UnicodeError:
|
|
||||||
verse_text = str(line[3], 'cp1252')
|
|
||||||
self.create_verse(book.id, line[1], line[2], verse_text)
|
self.create_verse(book.id, line[1], line[2], verse_text)
|
||||||
self.wizard.increment_progress_bar(translate('BiblesPlugin.CSVBible', 'Importing verses... done.'))
|
self.wizard.increment_progress_bar(translate('BiblesPlugin.CSVBible', 'Importing verses... done.'))
|
||||||
self.application.process_events()
|
self.application.process_events()
|
||||||
@ -170,7 +161,7 @@ def get_file_encoding(filename):
|
|||||||
"""
|
"""
|
||||||
detect_file = None
|
detect_file = None
|
||||||
try:
|
try:
|
||||||
detect_file = open(filename, 'r')
|
detect_file = open(filename, 'rb')
|
||||||
details = chardet.detect(detect_file.read(1024))
|
details = chardet.detect(detect_file.read(1024))
|
||||||
except IOError:
|
except IOError:
|
||||||
log.exception('Error detecting file encoding')
|
log.exception('Error detecting file encoding')
|
||||||
|
@ -131,6 +131,7 @@ class BibleDB(QtCore.QObject, Manager, RegistryProperties):
|
|||||||
log.info('BibleDB loaded')
|
log.info('BibleDB loaded')
|
||||||
QtCore.QObject.__init__(self)
|
QtCore.QObject.__init__(self)
|
||||||
self.bible_plugin = parent
|
self.bible_plugin = parent
|
||||||
|
self.session = None
|
||||||
if 'path' not in kwargs:
|
if 'path' not in kwargs:
|
||||||
raise KeyError('Missing keyword argument "path".')
|
raise KeyError('Missing keyword argument "path".')
|
||||||
if 'name' not in kwargs and 'file' not in kwargs:
|
if 'name' not in kwargs and 'file' not in kwargs:
|
||||||
@ -144,8 +145,8 @@ class BibleDB(QtCore.QObject, Manager, RegistryProperties):
|
|||||||
if 'file' in kwargs:
|
if 'file' in kwargs:
|
||||||
self.file = kwargs['file']
|
self.file = kwargs['file']
|
||||||
Manager.__init__(self, 'bibles', init_schema, self.file, upgrade)
|
Manager.__init__(self, 'bibles', init_schema, self.file, upgrade)
|
||||||
if 'file' in kwargs:
|
if self.session and 'file' in kwargs:
|
||||||
self.get_name()
|
self.get_name()
|
||||||
if 'path' in kwargs:
|
if 'path' in kwargs:
|
||||||
self.path = kwargs['path']
|
self.path = kwargs['path']
|
||||||
self.wizard = None
|
self.wizard = None
|
||||||
@ -163,9 +164,6 @@ class BibleDB(QtCore.QObject, Manager, RegistryProperties):
|
|||||||
Returns the version name of the Bible.
|
Returns the version name of the Bible.
|
||||||
"""
|
"""
|
||||||
version_name = self.get_object(BibleMeta, 'name')
|
version_name = self.get_object(BibleMeta, 'name')
|
||||||
# Fallback to old way of naming
|
|
||||||
if not version_name:
|
|
||||||
version_name = self.get_object(BibleMeta, 'Version')
|
|
||||||
self.name = version_name.value if version_name else None
|
self.name = version_name.value if version_name else None
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
@ -50,6 +50,38 @@ UGLY_CHARS = {
|
|||||||
}
|
}
|
||||||
VERSE_NUMBER_REGEX = re.compile(r'v(\d{1,2})(\d{3})(\d{3}) verse.*')
|
VERSE_NUMBER_REGEX = re.compile(r'v(\d{1,2})(\d{3})(\d{3}) verse.*')
|
||||||
|
|
||||||
|
BIBLESERVER_LANGUAGE_CODE = {
|
||||||
|
'fl_1': 'de',
|
||||||
|
'fl_2': 'en',
|
||||||
|
'fl_3': 'fr',
|
||||||
|
'fl_4': 'it',
|
||||||
|
'fl_5': 'es',
|
||||||
|
'fl_6': 'pt',
|
||||||
|
'fl_7': 'ru',
|
||||||
|
'fl_8': 'sv',
|
||||||
|
'fl_9': 'no',
|
||||||
|
'fl_10': 'nl',
|
||||||
|
'fl_11': 'cs',
|
||||||
|
'fl_12': 'sk',
|
||||||
|
'fl_13': 'ro',
|
||||||
|
'fl_14': 'hr',
|
||||||
|
'fl_15': 'hu',
|
||||||
|
'fl_16': 'bg',
|
||||||
|
'fl_17': 'ar',
|
||||||
|
'fl_18': 'tr',
|
||||||
|
'fl_19': 'pl',
|
||||||
|
'fl_20': 'da',
|
||||||
|
'fl_21': 'zh'
|
||||||
|
}
|
||||||
|
|
||||||
|
CROSSWALK_LANGUAGES = {
|
||||||
|
'Portuguese': 'pt',
|
||||||
|
'German': 'de',
|
||||||
|
'Italian': 'it',
|
||||||
|
'Español': 'es',
|
||||||
|
'French': 'fr',
|
||||||
|
'Dutch': 'nl'
|
||||||
|
}
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -222,6 +254,8 @@ class BGExtract(RegistryProperties):
|
|||||||
if not soup:
|
if not soup:
|
||||||
return None
|
return None
|
||||||
div = soup.find('div', 'result-text-style-normal')
|
div = soup.find('div', 'result-text-style-normal')
|
||||||
|
if not div:
|
||||||
|
return None
|
||||||
self._clean_soup(div)
|
self._clean_soup(div)
|
||||||
span_list = div.find_all('span', 'text')
|
span_list = div.find_all('span', 'text')
|
||||||
log.debug('Span list: %s', span_list)
|
log.debug('Span list: %s', span_list)
|
||||||
@ -278,6 +312,42 @@ class BGExtract(RegistryProperties):
|
|||||||
books.append(book.contents[0])
|
books.append(book.contents[0])
|
||||||
return books
|
return books
|
||||||
|
|
||||||
|
def get_bibles_from_http(self):
|
||||||
|
"""
|
||||||
|
Load a list of bibles from BibleGateway website.
|
||||||
|
|
||||||
|
returns a list in the form [(biblename, biblekey, language_code)]
|
||||||
|
"""
|
||||||
|
log.debug('BGExtract.get_bibles_from_http')
|
||||||
|
bible_url = 'https://legacy.biblegateway.com/versions/'
|
||||||
|
soup = get_soup_for_bible_ref(bible_url)
|
||||||
|
if not soup:
|
||||||
|
return None
|
||||||
|
bible_select = soup.find('select', {'class': 'translation-dropdown'})
|
||||||
|
if not bible_select:
|
||||||
|
log.debug('No select tags found - did site change?')
|
||||||
|
return None
|
||||||
|
option_tags = bible_select.find_all('option')
|
||||||
|
if not option_tags:
|
||||||
|
log.debug('No option tags found - did site change?')
|
||||||
|
return None
|
||||||
|
current_lang = ''
|
||||||
|
bibles = []
|
||||||
|
for ot in option_tags:
|
||||||
|
tag_class = ''
|
||||||
|
try:
|
||||||
|
tag_class = ot['class'][0]
|
||||||
|
except KeyError:
|
||||||
|
tag_class = ''
|
||||||
|
tag_text = ot.get_text()
|
||||||
|
if tag_class == 'lang':
|
||||||
|
current_lang = tag_text[tag_text.find('(') + 1:tag_text.find(')')].lower()
|
||||||
|
elif tag_class == 'spacer':
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
bibles.append((tag_text, ot['value'], current_lang))
|
||||||
|
return bibles
|
||||||
|
|
||||||
|
|
||||||
class BSExtract(RegistryProperties):
|
class BSExtract(RegistryProperties):
|
||||||
"""
|
"""
|
||||||
@ -338,6 +408,43 @@ class BSExtract(RegistryProperties):
|
|||||||
content = content.find_all('li')
|
content = content.find_all('li')
|
||||||
return [book.contents[0].contents[0] for book in content if len(book.contents[0].contents)]
|
return [book.contents[0].contents[0] for book in content if len(book.contents[0].contents)]
|
||||||
|
|
||||||
|
def get_bibles_from_http(self):
|
||||||
|
"""
|
||||||
|
Load a list of bibles from Bibleserver website.
|
||||||
|
|
||||||
|
returns a list in the form [(biblename, biblekey, language_code)]
|
||||||
|
"""
|
||||||
|
log.debug('BSExtract.get_bibles_from_http')
|
||||||
|
bible_url = 'http://www.bibleserver.com/index.php?language=2'
|
||||||
|
soup = get_soup_for_bible_ref(bible_url)
|
||||||
|
if not soup:
|
||||||
|
return None
|
||||||
|
bible_links = soup.find_all('a', {'class': 'trlCell'})
|
||||||
|
if not bible_links:
|
||||||
|
log.debug('No a tags found - did site change?')
|
||||||
|
return None
|
||||||
|
bibles = []
|
||||||
|
for link in bible_links:
|
||||||
|
bible_name = link.get_text()
|
||||||
|
# Skip any audio
|
||||||
|
if 'audio' in bible_name.lower():
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
bible_link = link['href']
|
||||||
|
bible_key = bible_link[bible_link.rfind('/') + 1:]
|
||||||
|
css_classes = link['class']
|
||||||
|
except KeyError:
|
||||||
|
log.debug('No href/class attribute found - did site change?')
|
||||||
|
language_code = ''
|
||||||
|
for css_class in css_classes:
|
||||||
|
if css_class.startswith('fl_'):
|
||||||
|
try:
|
||||||
|
language_code = BIBLESERVER_LANGUAGE_CODE[css_class]
|
||||||
|
except KeyError:
|
||||||
|
language_code = ''
|
||||||
|
bibles.append((bible_name, bible_key, language_code))
|
||||||
|
return bibles
|
||||||
|
|
||||||
|
|
||||||
class CWExtract(RegistryProperties):
|
class CWExtract(RegistryProperties):
|
||||||
"""
|
"""
|
||||||
@ -365,31 +472,20 @@ class CWExtract(RegistryProperties):
|
|||||||
if not soup:
|
if not soup:
|
||||||
return None
|
return None
|
||||||
self.application.process_events()
|
self.application.process_events()
|
||||||
html_verses = soup.find_all('span', 'versetext')
|
verses_div = soup.find_all('div', 'verse')
|
||||||
if not html_verses:
|
if not verses_div:
|
||||||
log.error('No verses found in the CrossWalk response.')
|
log.error('No verses found in the CrossWalk response.')
|
||||||
send_error_message('parse')
|
send_error_message('parse')
|
||||||
return None
|
return None
|
||||||
verses = {}
|
verses = {}
|
||||||
for verse in html_verses:
|
for verse in verses_div:
|
||||||
self.application.process_events()
|
self.application.process_events()
|
||||||
verse_number = int(verse.contents[0].contents[0])
|
verse_number = int(verse.find('strong').contents[0])
|
||||||
verse_text = ''
|
verse_span = verse.find('span')
|
||||||
for part in verse.contents:
|
tags_to_remove = verse_span.find_all(['a', 'sup'])
|
||||||
self.application.process_events()
|
for tag in tags_to_remove:
|
||||||
if isinstance(part, NavigableString):
|
tag.decompose()
|
||||||
verse_text += part
|
verse_text = verse_span.get_text()
|
||||||
elif part and part.attrMap and \
|
|
||||||
(part.attrMap['class'] == 'WordsOfChrist' or part.attrMap['class'] == 'strongs'):
|
|
||||||
for subpart in part.contents:
|
|
||||||
self.application.process_events()
|
|
||||||
if isinstance(subpart, NavigableString):
|
|
||||||
verse_text += subpart
|
|
||||||
elif subpart and subpart.attrMap and subpart.attrMap['class'] == 'strongs':
|
|
||||||
for subsub in subpart.contents:
|
|
||||||
self.application.process_events()
|
|
||||||
if isinstance(subsub, NavigableString):
|
|
||||||
verse_text += subsub
|
|
||||||
self.application.process_events()
|
self.application.process_events()
|
||||||
# Fix up leading and trailing spaces, multiple spaces, and spaces between text and , and .
|
# Fix up leading and trailing spaces, multiple spaces, and spaces between text and , and .
|
||||||
verse_text = verse_text.strip('\n\r\t ')
|
verse_text = verse_text.strip('\n\r\t ')
|
||||||
@ -409,19 +505,59 @@ class CWExtract(RegistryProperties):
|
|||||||
soup = get_soup_for_bible_ref(chapter_url)
|
soup = get_soup_for_bible_ref(chapter_url)
|
||||||
if not soup:
|
if not soup:
|
||||||
return None
|
return None
|
||||||
content = soup.find('div', {'class': 'Body'})
|
content = soup.find_all(('h4', {'class': 'small-header'}))
|
||||||
content = content.find('ul', {'class': 'parent'})
|
|
||||||
if not content:
|
if not content:
|
||||||
log.error('No books found in the Crosswalk response.')
|
log.error('No books found in the Crosswalk response.')
|
||||||
send_error_message('parse')
|
send_error_message('parse')
|
||||||
return None
|
return None
|
||||||
content = content.find_all('li')
|
|
||||||
books = []
|
books = []
|
||||||
for book in content:
|
for book in content:
|
||||||
book = book.find('a')
|
|
||||||
books.append(book.contents[0])
|
books.append(book.contents[0])
|
||||||
return books
|
return books
|
||||||
|
|
||||||
|
def get_bibles_from_http(self):
|
||||||
|
"""
|
||||||
|
Load a list of bibles from Crosswalk website.
|
||||||
|
returns a list in the form [(biblename, biblekey, language_code)]
|
||||||
|
"""
|
||||||
|
log.debug('CWExtract.get_bibles_from_http')
|
||||||
|
bible_url = 'http://www.biblestudytools.com/search/bible-search.part/'
|
||||||
|
soup = get_soup_for_bible_ref(bible_url)
|
||||||
|
if not soup:
|
||||||
|
return None
|
||||||
|
bible_select = soup.find('select')
|
||||||
|
if not bible_select:
|
||||||
|
log.debug('No select tags found - did site change?')
|
||||||
|
return None
|
||||||
|
option_tags = bible_select.find_all('option')
|
||||||
|
if not option_tags:
|
||||||
|
log.debug('No option tags found - did site change?')
|
||||||
|
return None
|
||||||
|
bibles = []
|
||||||
|
for ot in option_tags:
|
||||||
|
tag_text = ot.get_text().strip()
|
||||||
|
try:
|
||||||
|
tag_value = ot['value']
|
||||||
|
except KeyError:
|
||||||
|
log.exception('No value attribute found - did site change?')
|
||||||
|
return None
|
||||||
|
if not tag_value:
|
||||||
|
continue
|
||||||
|
# The names of non-english bibles has their language in parentheses at the end
|
||||||
|
if tag_text.endswith(')'):
|
||||||
|
language = tag_text[tag_text.rfind('(') + 1:-1]
|
||||||
|
if language in CROSSWALK_LANGUAGES:
|
||||||
|
language_code = CROSSWALK_LANGUAGES[language]
|
||||||
|
else:
|
||||||
|
language_code = ''
|
||||||
|
# ... except for the latin vulgate
|
||||||
|
elif 'latin' in tag_text.lower():
|
||||||
|
language_code = 'la'
|
||||||
|
else:
|
||||||
|
language_code = 'en'
|
||||||
|
bibles.append((tag_text, tag_value, language_code))
|
||||||
|
return bibles
|
||||||
|
|
||||||
|
|
||||||
class HTTPBible(BibleDB, RegistryProperties):
|
class HTTPBible(BibleDB, RegistryProperties):
|
||||||
log.info('%s HTTPBible loaded', __name__)
|
log.info('%s HTTPBible loaded', __name__)
|
||||||
@ -442,6 +578,7 @@ class HTTPBible(BibleDB, RegistryProperties):
|
|||||||
self.proxy_server = None
|
self.proxy_server = None
|
||||||
self.proxy_username = None
|
self.proxy_username = None
|
||||||
self.proxy_password = None
|
self.proxy_password = None
|
||||||
|
self.language_id = None
|
||||||
if 'path' in kwargs:
|
if 'path' in kwargs:
|
||||||
self.path = kwargs['path']
|
self.path = kwargs['path']
|
||||||
if 'proxy_server' in kwargs:
|
if 'proxy_server' in kwargs:
|
||||||
@ -450,6 +587,8 @@ class HTTPBible(BibleDB, RegistryProperties):
|
|||||||
self.proxy_username = kwargs['proxy_username']
|
self.proxy_username = kwargs['proxy_username']
|
||||||
if 'proxy_password' in kwargs:
|
if 'proxy_password' in kwargs:
|
||||||
self.proxy_password = kwargs['proxy_password']
|
self.proxy_password = kwargs['proxy_password']
|
||||||
|
if 'language_id' in kwargs:
|
||||||
|
self.language_id = kwargs['language_id']
|
||||||
|
|
||||||
def do_import(self, bible_name=None):
|
def do_import(self, bible_name=None):
|
||||||
"""
|
"""
|
||||||
@ -482,13 +621,11 @@ class HTTPBible(BibleDB, RegistryProperties):
|
|||||||
return False
|
return False
|
||||||
self.wizard.progress_bar.setMaximum(len(books) + 2)
|
self.wizard.progress_bar.setMaximum(len(books) + 2)
|
||||||
self.wizard.increment_progress_bar(translate('BiblesPlugin.HTTPBible', 'Registering Language...'))
|
self.wizard.increment_progress_bar(translate('BiblesPlugin.HTTPBible', 'Registering Language...'))
|
||||||
bible = BiblesResourcesDB.get_webbible(self.download_name, self.download_source.lower())
|
if self.language_id:
|
||||||
if bible['language_id']:
|
self.save_meta('language_id', self.language_id)
|
||||||
language_id = bible['language_id']
|
|
||||||
self.save_meta('language_id', language_id)
|
|
||||||
else:
|
else:
|
||||||
language_id = self.get_language(bible_name)
|
self.language_id = self.get_language(bible_name)
|
||||||
if not language_id:
|
if not self.language_id:
|
||||||
log.error('Importing books from %s failed' % self.filename)
|
log.error('Importing books from %s failed' % self.filename)
|
||||||
return False
|
return False
|
||||||
for book in books:
|
for book in books:
|
||||||
@ -496,7 +633,7 @@ class HTTPBible(BibleDB, RegistryProperties):
|
|||||||
break
|
break
|
||||||
self.wizard.increment_progress_bar(translate(
|
self.wizard.increment_progress_bar(translate(
|
||||||
'BiblesPlugin.HTTPBible', 'Importing %s...', 'Importing <book name>...') % book)
|
'BiblesPlugin.HTTPBible', 'Importing %s...', 'Importing <book name>...') % book)
|
||||||
book_ref_id = self.get_book_ref_id_by_name(book, len(books), language_id)
|
book_ref_id = self.get_book_ref_id_by_name(book, len(books), self.language_id)
|
||||||
if not book_ref_id:
|
if not book_ref_id:
|
||||||
log.error('Importing books from %s - download name: "%s" failed' %
|
log.error('Importing books from %s - download name: "%s" failed' %
|
||||||
(self.download_source, self.download_name))
|
(self.download_source, self.download_name))
|
||||||
|
@ -121,6 +121,8 @@ class BibleManager(RegistryProperties):
|
|||||||
self.old_bible_databases = []
|
self.old_bible_databases = []
|
||||||
for filename in files:
|
for filename in files:
|
||||||
bible = BibleDB(self.parent, path=self.path, file=filename)
|
bible = BibleDB(self.parent, path=self.path, file=filename)
|
||||||
|
if not bible.session:
|
||||||
|
continue
|
||||||
name = bible.get_name()
|
name = bible.get_name()
|
||||||
# Remove corrupted files.
|
# Remove corrupted files.
|
||||||
if name is None:
|
if name is None:
|
||||||
|
@ -849,7 +849,7 @@ class BibleMediaItem(MediaManagerItem):
|
|||||||
service_item.add_capability(ItemCapabilities.CanWordSplit)
|
service_item.add_capability(ItemCapabilities.CanWordSplit)
|
||||||
service_item.add_capability(ItemCapabilities.CanEditTitle)
|
service_item.add_capability(ItemCapabilities.CanEditTitle)
|
||||||
# Service Item: Title
|
# Service Item: Title
|
||||||
service_item.title = create_separated_list(raw_title)
|
service_item.title = '%s %s' % (verses.format_verses(), verses.format_versions())
|
||||||
# Service Item: Theme
|
# Service Item: Theme
|
||||||
if not self.settings.bible_theme:
|
if not self.settings.bible_theme:
|
||||||
service_item.theme = None
|
service_item.theme = None
|
||||||
|
@ -123,8 +123,8 @@ class OpenSongBible(BibleDB):
|
|||||||
verse_number += 1
|
verse_number += 1
|
||||||
self.create_verse(db_book.id, chapter_number, verse_number, self.get_text(verse))
|
self.create_verse(db_book.id, chapter_number, verse_number, self.get_text(verse))
|
||||||
self.wizard.increment_progress_bar(
|
self.wizard.increment_progress_bar(
|
||||||
translate('BiblesPlugin.Opensong', 'Importing %(bookname)s %(chapter)s...' %
|
translate('BiblesPlugin.Opensong', 'Importing %(bookname)s %(chapter)s...') %
|
||||||
{'bookname': db_book.name, 'chapter': chapter_number}))
|
{'bookname': db_book.name, 'chapter': chapter_number})
|
||||||
self.session.commit()
|
self.session.commit()
|
||||||
self.application.process_events()
|
self.application.process_events()
|
||||||
except etree.XMLSyntaxError as inst:
|
except etree.XMLSyntaxError as inst:
|
||||||
|
@ -58,7 +58,7 @@ class OSISBible(BibleDB):
|
|||||||
# NOTE: We don't need to do any of the normal encoding detection here, because lxml does it's own encoding
|
# NOTE: We don't need to do any of the normal encoding detection here, because lxml does it's own encoding
|
||||||
# detection, and the two mechanisms together interfere with each other.
|
# detection, and the two mechanisms together interfere with each other.
|
||||||
import_file = open(self.filename, 'rb')
|
import_file = open(self.filename, 'rb')
|
||||||
osis_bible_tree = etree.parse(import_file)
|
osis_bible_tree = etree.parse(import_file, parser=etree.XMLParser(recover=True))
|
||||||
namespace = {'ns': 'http://www.bibletechnologies.net/2003/OSIS/namespace'}
|
namespace = {'ns': 'http://www.bibletechnologies.net/2003/OSIS/namespace'}
|
||||||
# Find bible language
|
# Find bible language
|
||||||
language_id = None
|
language_id = None
|
||||||
@ -71,6 +71,7 @@ class OSISBible(BibleDB):
|
|||||||
if not language_id:
|
if not language_id:
|
||||||
log.error('Importing books from "%s" failed' % self.filename)
|
log.error('Importing books from "%s" failed' % self.filename)
|
||||||
return False
|
return False
|
||||||
|
self.save_meta('language_id', language_id)
|
||||||
num_books = int(osis_bible_tree.xpath("count(//ns:div[@type='book'])", namespaces=namespace))
|
num_books = int(osis_bible_tree.xpath("count(//ns:div[@type='book'])", namespaces=namespace))
|
||||||
self.wizard.increment_progress_bar(translate('BiblesPlugin.OsisImport',
|
self.wizard.increment_progress_bar(translate('BiblesPlugin.OsisImport',
|
||||||
'Removing unused tags (this may take a few minutes)...'))
|
'Removing unused tags (this may take a few minutes)...'))
|
||||||
@ -124,7 +125,7 @@ class OSISBible(BibleDB):
|
|||||||
break
|
break
|
||||||
# Remove div-tags in the book
|
# Remove div-tags in the book
|
||||||
etree.strip_tags(book, ('{http://www.bibletechnologies.net/2003/OSIS/namespace}div'))
|
etree.strip_tags(book, ('{http://www.bibletechnologies.net/2003/OSIS/namespace}div'))
|
||||||
book_ref_id = self.get_book_ref_id_by_name(book.get('osisID'), num_books)
|
book_ref_id = self.get_book_ref_id_by_name(book.get('osisID'), num_books, language_id)
|
||||||
if not book_ref_id:
|
if not book_ref_id:
|
||||||
book_ref_id = self.get_book_ref_id_by_localised_name(book.get('osisID'))
|
book_ref_id = self.get_book_ref_id_by_localised_name(book.get('osisID'))
|
||||||
if not book_ref_id:
|
if not book_ref_id:
|
||||||
@ -154,8 +155,8 @@ class OSISBible(BibleDB):
|
|||||||
verse_number = verse.get("osisID").split('.')[2]
|
verse_number = verse.get("osisID").split('.')[2]
|
||||||
self.create_verse(db_book.id, chapter_number, verse_number, verse.text.strip())
|
self.create_verse(db_book.id, chapter_number, verse_number, verse.text.strip())
|
||||||
self.wizard.increment_progress_bar(
|
self.wizard.increment_progress_bar(
|
||||||
translate('BiblesPlugin.OsisImport', 'Importing %(bookname)s %(chapter)s...' %
|
translate('BiblesPlugin.OsisImport', 'Importing %(bookname)s %(chapter)s...') %
|
||||||
{'bookname': db_book.name, 'chapter': chapter_number}))
|
{'bookname': db_book.name, 'chapter': chapter_number})
|
||||||
else:
|
else:
|
||||||
# The chapter tags is used as milestones. For now we assume verses is also milestones
|
# The chapter tags is used as milestones. For now we assume verses is also milestones
|
||||||
chapter_number = 0
|
chapter_number = 0
|
||||||
@ -164,8 +165,8 @@ class OSISBible(BibleDB):
|
|||||||
and element.get('sID'):
|
and element.get('sID'):
|
||||||
chapter_number = element.get("osisID").split('.')[1]
|
chapter_number = element.get("osisID").split('.')[1]
|
||||||
self.wizard.increment_progress_bar(
|
self.wizard.increment_progress_bar(
|
||||||
translate('BiblesPlugin.OsisImport', 'Importing %(bookname)s %(chapter)s...' %
|
translate('BiblesPlugin.OsisImport', 'Importing %(bookname)s %(chapter)s...') %
|
||||||
{'bookname': db_book.name, 'chapter': chapter_number}))
|
{'bookname': db_book.name, 'chapter': chapter_number})
|
||||||
elif element.tag == '{http://www.bibletechnologies.net/2003/OSIS/namespace}verse' \
|
elif element.tag == '{http://www.bibletechnologies.net/2003/OSIS/namespace}verse' \
|
||||||
and element.get('sID'):
|
and element.get('sID'):
|
||||||
# If this tag marks the start of a verse, the verse text is between this tag and
|
# If this tag marks the start of a verse, the verse text is between this tag and
|
||||||
|
@ -24,14 +24,177 @@ The :mod:`upgrade` module provides a way for the database and schema that is the
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from sqlalchemy import delete, func, insert, select
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
__version__ = 1
|
__version__ = 1
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: When removing an upgrade path the ftw-data needs updating to the minimum supported version
|
||||||
def upgrade_1(session, metadata):
|
def upgrade_1(session, metadata):
|
||||||
"""
|
"""
|
||||||
Version 1 upgrade.
|
Version 1 upgrade.
|
||||||
|
|
||||||
This upgrade renames a number of keys to a single naming convention.
|
This upgrade renames a number of keys to a single naming convention.
|
||||||
"""
|
"""
|
||||||
log.info('No upgrades to perform')
|
metadata_table = metadata.tables['metadata']
|
||||||
|
# Copy "Version" to "name" ("version" used by upgrade system)
|
||||||
|
try:
|
||||||
|
session.execute(insert(metadata_table).values(
|
||||||
|
key='name',
|
||||||
|
value=select(
|
||||||
|
[metadata_table.c.value],
|
||||||
|
metadata_table.c.key == 'Version'
|
||||||
|
).as_scalar()
|
||||||
|
))
|
||||||
|
session.execute(delete(metadata_table).where(metadata_table.c.key == 'Version'))
|
||||||
|
except:
|
||||||
|
log.exception('Exception when upgrading Version')
|
||||||
|
# Copy "Copyright" to "copyright"
|
||||||
|
try:
|
||||||
|
session.execute(insert(metadata_table).values(
|
||||||
|
key='copyright',
|
||||||
|
value=select(
|
||||||
|
[metadata_table.c.value],
|
||||||
|
metadata_table.c.key == 'Copyright'
|
||||||
|
).as_scalar()
|
||||||
|
))
|
||||||
|
session.execute(delete(metadata_table).where(metadata_table.c.key == 'Copyright'))
|
||||||
|
except:
|
||||||
|
log.exception('Exception when upgrading Copyright')
|
||||||
|
# Copy "Permissions" to "permissions"
|
||||||
|
try:
|
||||||
|
session.execute(insert(metadata_table).values(
|
||||||
|
key='permissions',
|
||||||
|
value=select(
|
||||||
|
[metadata_table.c.value],
|
||||||
|
metadata_table.c.key == 'Permissions'
|
||||||
|
).as_scalar()
|
||||||
|
))
|
||||||
|
session.execute(delete(metadata_table).where(metadata_table.c.key == 'Permissions'))
|
||||||
|
except:
|
||||||
|
log.exception('Exception when upgrading Permissions')
|
||||||
|
# Copy "Bookname language" to "book_name_language"
|
||||||
|
try:
|
||||||
|
value_count = session.execute(
|
||||||
|
select(
|
||||||
|
[func.count(metadata_table.c.value)],
|
||||||
|
metadata_table.c.key == 'Bookname language'
|
||||||
|
)
|
||||||
|
).scalar()
|
||||||
|
if value_count > 0:
|
||||||
|
session.execute(insert(metadata_table).values(
|
||||||
|
key='book_name_language',
|
||||||
|
value=select(
|
||||||
|
[metadata_table.c.value],
|
||||||
|
metadata_table.c.key == 'Bookname language'
|
||||||
|
).as_scalar()
|
||||||
|
))
|
||||||
|
session.execute(delete(metadata_table).where(metadata_table.c.key == 'Bookname language'))
|
||||||
|
except:
|
||||||
|
log.exception('Exception when upgrading Bookname language')
|
||||||
|
# Copy "download source" to "download_source"
|
||||||
|
try:
|
||||||
|
value_count = session.execute(
|
||||||
|
select(
|
||||||
|
[func.count(metadata_table.c.value)],
|
||||||
|
metadata_table.c.key == 'download source'
|
||||||
|
)
|
||||||
|
).scalar()
|
||||||
|
log.debug('download source: %s', value_count)
|
||||||
|
if value_count > 0:
|
||||||
|
session.execute(insert(metadata_table).values(
|
||||||
|
key='download_source',
|
||||||
|
value=select(
|
||||||
|
[metadata_table.c.value],
|
||||||
|
metadata_table.c.key == 'download source'
|
||||||
|
).as_scalar()
|
||||||
|
))
|
||||||
|
session.execute(delete(metadata_table).where(metadata_table.c.key == 'download source'))
|
||||||
|
except:
|
||||||
|
log.exception('Exception when upgrading download source')
|
||||||
|
# Copy "download name" to "download_name"
|
||||||
|
try:
|
||||||
|
value_count = session.execute(
|
||||||
|
select(
|
||||||
|
[func.count(metadata_table.c.value)],
|
||||||
|
metadata_table.c.key == 'download name'
|
||||||
|
)
|
||||||
|
).scalar()
|
||||||
|
log.debug('download name: %s', value_count)
|
||||||
|
if value_count > 0:
|
||||||
|
session.execute(insert(metadata_table).values(
|
||||||
|
key='download_name',
|
||||||
|
value=select(
|
||||||
|
[metadata_table.c.value],
|
||||||
|
metadata_table.c.key == 'download name'
|
||||||
|
).as_scalar()
|
||||||
|
))
|
||||||
|
session.execute(delete(metadata_table).where(metadata_table.c.key == 'download name'))
|
||||||
|
except:
|
||||||
|
log.exception('Exception when upgrading download name')
|
||||||
|
# Copy "proxy server" to "proxy_server"
|
||||||
|
try:
|
||||||
|
value_count = session.execute(
|
||||||
|
select(
|
||||||
|
[func.count(metadata_table.c.value)],
|
||||||
|
metadata_table.c.key == 'proxy server'
|
||||||
|
)
|
||||||
|
).scalar()
|
||||||
|
log.debug('proxy server: %s', value_count)
|
||||||
|
if value_count > 0:
|
||||||
|
session.execute(insert(metadata_table).values(
|
||||||
|
key='proxy_server',
|
||||||
|
value=select(
|
||||||
|
[metadata_table.c.value],
|
||||||
|
metadata_table.c.key == 'proxy server'
|
||||||
|
).as_scalar()
|
||||||
|
))
|
||||||
|
session.execute(delete(metadata_table).where(metadata_table.c.key == 'proxy server'))
|
||||||
|
except:
|
||||||
|
log.exception('Exception when upgrading proxy server')
|
||||||
|
# Copy "proxy username" to "proxy_username"
|
||||||
|
try:
|
||||||
|
value_count = session.execute(
|
||||||
|
select(
|
||||||
|
[func.count(metadata_table.c.value)],
|
||||||
|
metadata_table.c.key == 'proxy username'
|
||||||
|
)
|
||||||
|
).scalar()
|
||||||
|
log.debug('proxy username: %s', value_count)
|
||||||
|
if value_count > 0:
|
||||||
|
session.execute(insert(metadata_table).values(
|
||||||
|
key='proxy_username',
|
||||||
|
value=select(
|
||||||
|
[metadata_table.c.value],
|
||||||
|
metadata_table.c.key == 'proxy username'
|
||||||
|
).as_scalar()
|
||||||
|
))
|
||||||
|
session.execute(delete(metadata_table).where(metadata_table.c.key == 'proxy username'))
|
||||||
|
except:
|
||||||
|
log.exception('Exception when upgrading proxy username')
|
||||||
|
# Copy "proxy password" to "proxy_password"
|
||||||
|
try:
|
||||||
|
value_count = session.execute(
|
||||||
|
select(
|
||||||
|
[func.count(metadata_table.c.value)],
|
||||||
|
metadata_table.c.key == 'proxy password'
|
||||||
|
)
|
||||||
|
).scalar()
|
||||||
|
log.debug('proxy password: %s', value_count)
|
||||||
|
if value_count > 0:
|
||||||
|
session.execute(insert(metadata_table).values(
|
||||||
|
key='proxy_password',
|
||||||
|
value=select(
|
||||||
|
[metadata_table.c.value],
|
||||||
|
metadata_table.c.key == 'proxy password'
|
||||||
|
).as_scalar()
|
||||||
|
))
|
||||||
|
session.execute(delete(metadata_table).where(metadata_table.c.key == 'proxy password'))
|
||||||
|
except:
|
||||||
|
log.exception('Exception when upgrading proxy password')
|
||||||
|
try:
|
||||||
|
session.execute(delete(metadata_table).where(metadata_table.c.key == 'dbversion'))
|
||||||
|
except:
|
||||||
|
log.exception('Exception when deleting dbversion')
|
||||||
|
session.commit()
|
||||||
|
@ -20,6 +20,8 @@
|
|||||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
from openlp.plugins.bibles.lib import get_reference_separator
|
||||||
|
|
||||||
|
|
||||||
class VerseReferenceList(object):
|
class VerseReferenceList(object):
|
||||||
"""
|
"""
|
||||||
@ -53,38 +55,50 @@ class VerseReferenceList(object):
|
|||||||
self.version_list.append({'version': version, 'copyright': copyright, 'permission': permission})
|
self.version_list.append({'version': version, 'copyright': copyright, 'permission': permission})
|
||||||
|
|
||||||
def format_verses(self):
|
def format_verses(self):
|
||||||
|
verse_sep = get_reference_separator('sep_v_display')
|
||||||
|
range_sep = get_reference_separator('sep_r_display')
|
||||||
|
list_sep = get_reference_separator('sep_l_display')
|
||||||
result = ''
|
result = ''
|
||||||
for index, verse in enumerate(self.verse_list):
|
for index, verse in enumerate(self.verse_list):
|
||||||
if index == 0:
|
if index == 0:
|
||||||
result = '%s %s:%s' % (verse['book'], verse['chapter'], verse['start'])
|
result = '%s %s%s%s' % (verse['book'], verse['chapter'], verse_sep, verse['start'])
|
||||||
if verse['start'] != verse['end']:
|
if verse['start'] != verse['end']:
|
||||||
result = '%s-%s' % (result, verse['end'])
|
result = '%s%s%s' % (result, range_sep, verse['end'])
|
||||||
continue
|
continue
|
||||||
prev = index - 1
|
prev = index - 1
|
||||||
if self.verse_list[prev]['version'] != verse['version']:
|
if self.verse_list[prev]['version'] != verse['version']:
|
||||||
result = '%s (%s)' % (result, self.verse_list[prev]['version'])
|
result = '%s (%s)' % (result, self.verse_list[prev]['version'])
|
||||||
result += ', '
|
result += '%s ' % list_sep
|
||||||
if self.verse_list[prev]['book'] != verse['book']:
|
if self.verse_list[prev]['book'] != verse['book']:
|
||||||
result = '%s%s %s:' % (result, verse['book'], verse['chapter'])
|
result = '%s%s %s%s' % (result, verse['book'], verse['chapter'], verse_sep)
|
||||||
elif self.verse_list[prev]['chapter'] != verse['chapter']:
|
elif self.verse_list[prev]['chapter'] != verse['chapter']:
|
||||||
result = '%s%s:' % (result, verse['chapter'])
|
result = '%s%s%s' % (result, verse['chapter'], verse_sep)
|
||||||
result += str(verse['start'])
|
result += str(verse['start'])
|
||||||
if verse['start'] != verse['end']:
|
if verse['start'] != verse['end']:
|
||||||
result = '%s-%s' % (result, verse['end'])
|
result = '%s%s%s' % (result, range_sep, verse['end'])
|
||||||
if len(self.version_list) > 1:
|
if len(self.version_list) > 1:
|
||||||
result = '%s (%s)' % (result, verse['version'])
|
result = '%s (%s)' % (result, verse['version'])
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def format_versions(self):
|
def format_versions(self, copyright=True, permission=True):
|
||||||
|
"""
|
||||||
|
Format a string with the bible versions used
|
||||||
|
|
||||||
|
:param copyright: If the copyright info should be included, default is true.
|
||||||
|
:param permission: If the permission info should be included, default is true.
|
||||||
|
:return: A formatted string with the bible versions used
|
||||||
|
"""
|
||||||
result = ''
|
result = ''
|
||||||
for index, version in enumerate(self.version_list):
|
for index, version in enumerate(self.version_list):
|
||||||
if index > 0:
|
if index > 0:
|
||||||
if result[-1] not in [';', ',', '.']:
|
if result[-1] not in [';', ',', '.']:
|
||||||
result += ';'
|
result += ';'
|
||||||
result += ' '
|
result += ' '
|
||||||
result = '%s%s, %s' % (result, version['version'], version['copyright'])
|
result += version['version']
|
||||||
if version['permission'].strip():
|
if copyright and version['copyright'].strip():
|
||||||
result = result + ', ' + version['permission']
|
result += ', ' + version['copyright']
|
||||||
|
if permission and version['permission'].strip():
|
||||||
|
result += ', ' + version['permission']
|
||||||
result = result.rstrip()
|
result = result.rstrip()
|
||||||
if result.endswith(','):
|
if result.endswith(','):
|
||||||
return result[:len(result) - 1]
|
return result[:len(result) - 1]
|
||||||
|
@ -57,7 +57,7 @@ class ZefaniaBible(BibleDB):
|
|||||||
# NOTE: We don't need to do any of the normal encoding detection here, because lxml does it's own encoding
|
# NOTE: We don't need to do any of the normal encoding detection here, because lxml does it's own encoding
|
||||||
# detection, and the two mechanisms together interfere with each other.
|
# detection, and the two mechanisms together interfere with each other.
|
||||||
import_file = open(self.filename, 'rb')
|
import_file = open(self.filename, 'rb')
|
||||||
zefania_bible_tree = etree.parse(import_file)
|
zefania_bible_tree = etree.parse(import_file, parser=etree.XMLParser(recover=True))
|
||||||
# Find bible language
|
# Find bible language
|
||||||
language_id = None
|
language_id = None
|
||||||
language = zefania_bible_tree.xpath("/XMLBIBLE/INFORMATION/language/text()")
|
language = zefania_bible_tree.xpath("/XMLBIBLE/INFORMATION/language/text()")
|
||||||
@ -69,6 +69,7 @@ class ZefaniaBible(BibleDB):
|
|||||||
if not language_id:
|
if not language_id:
|
||||||
log.error('Importing books from "%s" failed' % self.filename)
|
log.error('Importing books from "%s" failed' % self.filename)
|
||||||
return False
|
return False
|
||||||
|
self.save_meta('language_id', language_id)
|
||||||
num_books = int(zefania_bible_tree.xpath("count(//BIBLEBOOK)"))
|
num_books = int(zefania_bible_tree.xpath("count(//BIBLEBOOK)"))
|
||||||
# Strip tags we don't use - keep content
|
# Strip tags we don't use - keep content
|
||||||
etree.strip_tags(zefania_bible_tree, ('STYLE', 'GRAM', 'NOTE', 'SUP', 'XREF'))
|
etree.strip_tags(zefania_bible_tree, ('STYLE', 'GRAM', 'NOTE', 'SUP', 'XREF'))
|
||||||
@ -76,9 +77,19 @@ class ZefaniaBible(BibleDB):
|
|||||||
etree.strip_elements(zefania_bible_tree, ('PROLOG', 'REMARK', 'CAPTION', 'MEDIA'), with_tail=False)
|
etree.strip_elements(zefania_bible_tree, ('PROLOG', 'REMARK', 'CAPTION', 'MEDIA'), with_tail=False)
|
||||||
xmlbible = zefania_bible_tree.getroot()
|
xmlbible = zefania_bible_tree.getroot()
|
||||||
for BIBLEBOOK in xmlbible:
|
for BIBLEBOOK in xmlbible:
|
||||||
book_ref_id = self.get_book_ref_id_by_name(str(BIBLEBOOK.get('bname')), num_books)
|
if self.stop_import_flag:
|
||||||
if not book_ref_id:
|
break
|
||||||
book_ref_id = self.get_book_ref_id_by_localised_name(str(BIBLEBOOK.get('bname')))
|
bname = BIBLEBOOK.get('bname')
|
||||||
|
bnumber = BIBLEBOOK.get('bnumber')
|
||||||
|
if not bname and not bnumber:
|
||||||
|
continue
|
||||||
|
if bname:
|
||||||
|
book_ref_id = self.get_book_ref_id_by_name(bname, num_books, language_id)
|
||||||
|
if not book_ref_id:
|
||||||
|
book_ref_id = self.get_book_ref_id_by_localised_name(bname)
|
||||||
|
else:
|
||||||
|
log.debug('Could not find a name, will use number, basically a guess.')
|
||||||
|
book_ref_id = int(bnumber)
|
||||||
if not book_ref_id:
|
if not book_ref_id:
|
||||||
log.error('Importing books from "%s" failed' % self.filename)
|
log.error('Importing books from "%s" failed' % self.filename)
|
||||||
return False
|
return False
|
||||||
@ -92,9 +103,9 @@ class ZefaniaBible(BibleDB):
|
|||||||
verse_number = VERS.get("vnumber")
|
verse_number = VERS.get("vnumber")
|
||||||
self.create_verse(db_book.id, chapter_number, verse_number, VERS.text.replace('<BR/>', '\n'))
|
self.create_verse(db_book.id, chapter_number, verse_number, VERS.text.replace('<BR/>', '\n'))
|
||||||
self.wizard.increment_progress_bar(
|
self.wizard.increment_progress_bar(
|
||||||
translate('BiblesPlugin.Zefnia', 'Importing %(bookname)s %(chapter)s...' %
|
translate('BiblesPlugin.Zefnia', 'Importing %(bookname)s %(chapter)s...') %
|
||||||
{'bookname': db_book.name, 'chapter': chapter_number}))
|
{'bookname': db_book.name, 'chapter': chapter_number})
|
||||||
self.session.commit()
|
self.session.commit()
|
||||||
self.application.process_events()
|
self.application.process_events()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
critical_error_message_box(
|
critical_error_message_box(
|
||||||
|
@ -36,6 +36,10 @@ log = logging.getLogger(__name__)
|
|||||||
|
|
||||||
__default_settings__ = {
|
__default_settings__ = {
|
||||||
'custom/db type': 'sqlite',
|
'custom/db type': 'sqlite',
|
||||||
|
'custom/db username': '',
|
||||||
|
'custom/db password': '',
|
||||||
|
'custom/db hostname': '',
|
||||||
|
'custom/db database': '',
|
||||||
'custom/last search type': CustomSearch.Titles,
|
'custom/last search type': CustomSearch.Titles,
|
||||||
'custom/display footer': True,
|
'custom/display footer': True,
|
||||||
'custom/add custom from service': True
|
'custom/add custom from service': True
|
||||||
|
@ -158,6 +158,10 @@ class CustomMediaItem(MediaManagerItem):
|
|||||||
self.remote_triggered = None
|
self.remote_triggered = None
|
||||||
self.remote_custom = 1
|
self.remote_custom = 1
|
||||||
if item:
|
if item:
|
||||||
|
if preview:
|
||||||
|
# A custom slide can only be edited if it comes from plugin,
|
||||||
|
# so we set it again for the new item.
|
||||||
|
item.from_plugin = True
|
||||||
return item
|
return item
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -35,6 +35,10 @@ log = logging.getLogger(__name__)
|
|||||||
|
|
||||||
__default_settings__ = {
|
__default_settings__ = {
|
||||||
'images/db type': 'sqlite',
|
'images/db type': 'sqlite',
|
||||||
|
'images/db username': '',
|
||||||
|
'images/db password': '',
|
||||||
|
'images/db hostname': '',
|
||||||
|
'images/db database': '',
|
||||||
'images/background color': '#000000',
|
'images/background color': '#000000',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,6 +205,7 @@ class ImageMediaItem(MediaManagerItem):
|
|||||||
images = self.manager.get_all_objects(ImageFilenames, ImageFilenames.group_id == image_group.id)
|
images = self.manager.get_all_objects(ImageFilenames, ImageFilenames.group_id == image_group.id)
|
||||||
for image in images:
|
for image in images:
|
||||||
delete_file(os.path.join(self.service_path, os.path.split(image.filename)[1]))
|
delete_file(os.path.join(self.service_path, os.path.split(image.filename)[1]))
|
||||||
|
delete_file(self.generate_thumbnail_path(image))
|
||||||
self.manager.delete_object(ImageFilenames, image.id)
|
self.manager.delete_object(ImageFilenames, image.id)
|
||||||
image_groups = self.manager.get_all_objects(ImageGroups, ImageGroups.parent_id == image_group.id)
|
image_groups = self.manager.get_all_objects(ImageGroups, ImageGroups.parent_id == image_group.id)
|
||||||
for group in image_groups:
|
for group in image_groups:
|
||||||
@ -227,6 +228,7 @@ class ImageMediaItem(MediaManagerItem):
|
|||||||
item_data = row_item.data(0, QtCore.Qt.UserRole)
|
item_data = row_item.data(0, QtCore.Qt.UserRole)
|
||||||
if isinstance(item_data, ImageFilenames):
|
if isinstance(item_data, ImageFilenames):
|
||||||
delete_file(os.path.join(self.service_path, row_item.text(0)))
|
delete_file(os.path.join(self.service_path, row_item.text(0)))
|
||||||
|
delete_file(self.generate_thumbnail_path(item_data))
|
||||||
if item_data.group_id == 0:
|
if item_data.group_id == 0:
|
||||||
self.list_view.takeTopLevelItem(self.list_view.indexOfTopLevelItem(row_item))
|
self.list_view.takeTopLevelItem(self.list_view.indexOfTopLevelItem(row_item))
|
||||||
else:
|
else:
|
||||||
@ -549,6 +551,7 @@ class ImageMediaItem(MediaManagerItem):
|
|||||||
service_item.title = items[0].text(0)
|
service_item.title = items[0].text(0)
|
||||||
else:
|
else:
|
||||||
service_item.title = str(self.plugin.name_strings['plural'])
|
service_item.title = str(self.plugin.name_strings['plural'])
|
||||||
|
|
||||||
service_item.add_capability(ItemCapabilities.CanMaintain)
|
service_item.add_capability(ItemCapabilities.CanMaintain)
|
||||||
service_item.add_capability(ItemCapabilities.CanPreview)
|
service_item.add_capability(ItemCapabilities.CanPreview)
|
||||||
service_item.add_capability(ItemCapabilities.CanLoop)
|
service_item.add_capability(ItemCapabilities.CanLoop)
|
||||||
@ -695,3 +698,15 @@ class ImageMediaItem(MediaManagerItem):
|
|||||||
filename = os.path.split(str(file_object.filename))[1]
|
filename = os.path.split(str(file_object.filename))[1]
|
||||||
results.append([file_object.filename, filename])
|
results.append([file_object.filename, filename])
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
def create_item_from_id(self, item_id):
|
||||||
|
"""
|
||||||
|
Create a media item from an item id. Overridden from the parent method to change the item type.
|
||||||
|
|
||||||
|
:param item_id: Id to make live
|
||||||
|
"""
|
||||||
|
item = QtGui.QTreeWidgetItem()
|
||||||
|
item_data = self.manager.get_object_filtered(ImageFilenames, ImageFilenames.filename == item_id)
|
||||||
|
item.setText(0, os.path.basename(item_data.filename))
|
||||||
|
item.setData(0, QtCore.Qt.UserRole, item_data)
|
||||||
|
return item
|
||||||
|
@ -33,8 +33,8 @@ from openlp.core.lib.ui import critical_error_message_box, create_horizontal_adj
|
|||||||
from openlp.core.ui import DisplayController, Display, DisplayControllerType
|
from openlp.core.ui import DisplayController, Display, DisplayControllerType
|
||||||
from openlp.core.ui.media import get_media_players, set_media_players, parse_optical_path, format_milliseconds
|
from openlp.core.ui.media import get_media_players, set_media_players, parse_optical_path, format_milliseconds
|
||||||
from openlp.core.utils import get_locale_key
|
from openlp.core.utils import get_locale_key
|
||||||
from openlp.core.ui.media.vlcplayer import VLC_AVAILABLE
|
from openlp.core.ui.media.vlcplayer import get_vlc
|
||||||
if VLC_AVAILABLE:
|
if get_vlc() is not None:
|
||||||
from openlp.plugins.media.forms.mediaclipselectorform import MediaClipSelectorForm
|
from openlp.plugins.media.forms.mediaclipselectorform import MediaClipSelectorForm
|
||||||
|
|
||||||
|
|
||||||
@ -91,7 +91,10 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
self.on_new_prompt = translate('MediaPlugin.MediaItem', 'Select Media')
|
self.on_new_prompt = translate('MediaPlugin.MediaItem', 'Select Media')
|
||||||
self.replace_action.setText(UiStrings().ReplaceBG)
|
self.replace_action.setText(UiStrings().ReplaceBG)
|
||||||
self.replace_action.setToolTip(UiStrings().ReplaceLiveBG)
|
if 'webkit' in get_media_players()[0]:
|
||||||
|
self.replace_action.setToolTip(UiStrings().ReplaceLiveBG)
|
||||||
|
else:
|
||||||
|
self.replace_action.setToolTip(UiStrings().ReplaceLiveBGDisabled)
|
||||||
self.reset_action.setText(UiStrings().ResetBG)
|
self.reset_action.setText(UiStrings().ResetBG)
|
||||||
self.reset_action.setToolTip(UiStrings().ResetLiveBG)
|
self.reset_action.setToolTip(UiStrings().ResetLiveBG)
|
||||||
self.automatic = UiStrings().Automatic
|
self.automatic = UiStrings().Automatic
|
||||||
@ -139,6 +142,8 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
|
|||||||
# Replace backgrounds do not work at present so remove functionality.
|
# Replace backgrounds do not work at present so remove functionality.
|
||||||
self.replace_action = self.toolbar.add_toolbar_action('replace_action', icon=':/slides/slide_blank.png',
|
self.replace_action = self.toolbar.add_toolbar_action('replace_action', icon=':/slides/slide_blank.png',
|
||||||
triggers=self.on_replace_click)
|
triggers=self.on_replace_click)
|
||||||
|
if 'webkit' not in get_media_players()[0]:
|
||||||
|
self.replace_action.setDisabled(True)
|
||||||
self.reset_action = self.toolbar.add_toolbar_action('reset_action', icon=':/system/system_close.png',
|
self.reset_action = self.toolbar.add_toolbar_action('reset_action', icon=':/system/system_close.png',
|
||||||
visible=False, triggers=self.on_reset_click)
|
visible=False, triggers=self.on_reset_click)
|
||||||
self.media_widget = QtGui.QWidget(self)
|
self.media_widget = QtGui.QWidget(self)
|
||||||
@ -340,6 +345,7 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
|
|||||||
media.sort(key=lambda file_name: get_locale_key(os.path.split(str(file_name))[1]))
|
media.sort(key=lambda file_name: get_locale_key(os.path.split(str(file_name))[1]))
|
||||||
for track in media:
|
for track in media:
|
||||||
track_info = QtCore.QFileInfo(track)
|
track_info = QtCore.QFileInfo(track)
|
||||||
|
item_name = None
|
||||||
if track.startswith('optical:'):
|
if track.startswith('optical:'):
|
||||||
# Handle optical based item
|
# Handle optical based item
|
||||||
(file_name, title, audio_track, subtitle_track, start, end, clip_name) = parse_optical_path(track)
|
(file_name, title, audio_track, subtitle_track, start, end, clip_name) = parse_optical_path(track)
|
||||||
@ -364,7 +370,8 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
|
|||||||
item_name.setIcon(VIDEO_ICON)
|
item_name.setIcon(VIDEO_ICON)
|
||||||
item_name.setData(QtCore.Qt.UserRole, track)
|
item_name.setData(QtCore.Qt.UserRole, track)
|
||||||
item_name.setToolTip(track)
|
item_name.setToolTip(track)
|
||||||
self.list_view.addItem(item_name)
|
if item_name:
|
||||||
|
self.list_view.addItem(item_name)
|
||||||
|
|
||||||
def get_list(self, type=MediaType.Audio):
|
def get_list(self, type=MediaType.Audio):
|
||||||
"""
|
"""
|
||||||
|
@ -19,12 +19,15 @@
|
|||||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
"""
|
||||||
|
The Media plugin
|
||||||
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from PyQt4 import QtCore
|
from PyQt4 import QtCore
|
||||||
|
|
||||||
from openlp.core.common import translate
|
from openlp.core.common import Settings, translate
|
||||||
from openlp.core.lib import Plugin, StringContent, build_icon
|
from openlp.core.lib import Plugin, StringContent, build_icon
|
||||||
from openlp.plugins.media.lib import MediaMediaItem, MediaTab
|
from openlp.plugins.media.lib import MediaMediaItem, MediaTab
|
||||||
|
|
||||||
@ -40,6 +43,9 @@ __default_settings__ = {
|
|||||||
|
|
||||||
|
|
||||||
class MediaPlugin(Plugin):
|
class MediaPlugin(Plugin):
|
||||||
|
"""
|
||||||
|
The media plugin adds the ability to playback audio and video content.
|
||||||
|
"""
|
||||||
log.info('%s MediaPlugin loaded', __name__)
|
log.info('%s MediaPlugin loaded', __name__)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -50,14 +56,38 @@ class MediaPlugin(Plugin):
|
|||||||
# passed with drag and drop messages
|
# passed with drag and drop messages
|
||||||
self.dnd_id = 'Media'
|
self.dnd_id = 'Media'
|
||||||
|
|
||||||
|
def initialise(self):
|
||||||
|
"""
|
||||||
|
Override the inherited initialise() method in order to upgrade the media before trying to load it
|
||||||
|
"""
|
||||||
|
# FIXME: Remove after 2.2 release.
|
||||||
|
# This is needed to load the list of media from the config saved before the settings rewrite.
|
||||||
|
if self.media_item_class is not None:
|
||||||
|
loaded_list = Settings().get_files_from_config(self)
|
||||||
|
# Now save the list to the config using our Settings class.
|
||||||
|
if loaded_list:
|
||||||
|
Settings().setValue('%s/%s files' % (self.settings_section, self.name), loaded_list)
|
||||||
|
super().initialise()
|
||||||
|
|
||||||
|
def app_startup(self):
|
||||||
|
"""
|
||||||
|
Override app_startup() in order to do nothing
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
def create_settings_tab(self, parent):
|
def create_settings_tab(self, parent):
|
||||||
"""
|
"""
|
||||||
Create the settings Tab
|
Create the settings Tab
|
||||||
|
|
||||||
|
:param parent:
|
||||||
"""
|
"""
|
||||||
visible_name = self.get_string(StringContent.VisibleName)
|
visible_name = self.get_string(StringContent.VisibleName)
|
||||||
self.settings_tab = MediaTab(parent, self.name, visible_name['title'], self.icon_path)
|
self.settings_tab = MediaTab(parent, self.name, visible_name['title'], self.icon_path)
|
||||||
|
|
||||||
def about(self):
|
def about(self):
|
||||||
|
"""
|
||||||
|
Return the about text for the plugin manager
|
||||||
|
"""
|
||||||
about_text = translate('MediaPlugin', '<strong>Media Plugin</strong>'
|
about_text = translate('MediaPlugin', '<strong>Media Plugin</strong>'
|
||||||
'<br />The media plugin provides playback of audio and video.')
|
'<br />The media plugin provides playback of audio and video.')
|
||||||
return about_text
|
return about_text
|
||||||
|
@ -428,7 +428,7 @@ class ImpressDocument(PresentationDocument):
|
|||||||
"""
|
"""
|
||||||
Triggers the previous slide on the running presentation.
|
Triggers the previous slide on the running presentation.
|
||||||
"""
|
"""
|
||||||
self.control.gotoPreviousSlide()
|
self.control.gotoPreviousEffect()
|
||||||
|
|
||||||
def get_slide_text(self, slide_no):
|
def get_slide_text(self, slide_no):
|
||||||
"""
|
"""
|
||||||
|
@ -230,17 +230,27 @@ class PresentationMediaItem(MediaManagerItem):
|
|||||||
Settings().setValue(self.settings_section + '/presentations files', self.get_file_list())
|
Settings().setValue(self.settings_section + '/presentations files', self.get_file_list())
|
||||||
self.application.set_normal_cursor()
|
self.application.set_normal_cursor()
|
||||||
|
|
||||||
def clean_up_thumbnails(self, filepath):
|
def clean_up_thumbnails(self, filepath, clean_for_update=False):
|
||||||
"""
|
"""
|
||||||
Clean up the files created such as thumbnails
|
Clean up the files created such as thumbnails
|
||||||
|
|
||||||
:param filepath: File path of the presention to clean up after
|
:param filepath: File path of the presention to clean up after
|
||||||
|
:param clean_for_update: Only clean thumbnails if update is needed
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
for cidx in self.controllers:
|
for cidx in self.controllers:
|
||||||
doc = self.controllers[cidx].add_document(filepath)
|
root, file_ext = os.path.splitext(filepath)
|
||||||
doc.presentation_deleted()
|
file_ext = file_ext[1:]
|
||||||
doc.close_presentation()
|
if file_ext in self.controllers[cidx].supports or file_ext in self.controllers[cidx].also_supports:
|
||||||
|
doc = self.controllers[cidx].add_document(filepath)
|
||||||
|
if clean_for_update:
|
||||||
|
thumb_path = doc.get_thumbnail_path(1, True)
|
||||||
|
if not thumb_path or not os.path.exists(filepath) or os.path.getmtime(
|
||||||
|
thumb_path) < os.path.getmtime(filepath):
|
||||||
|
doc.presentation_deleted()
|
||||||
|
else:
|
||||||
|
doc.presentation_deleted()
|
||||||
|
doc.close_presentation()
|
||||||
|
|
||||||
def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False,
|
def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False,
|
||||||
context=ServiceItemContext.Service, presentation_file=None):
|
context=ServiceItemContext.Service, presentation_file=None):
|
||||||
|
@ -93,7 +93,7 @@ class Controller(object):
|
|||||||
return True
|
return True
|
||||||
if not self.doc.is_loaded():
|
if not self.doc.is_loaded():
|
||||||
if not self.doc.load_presentation():
|
if not self.doc.load_presentation():
|
||||||
log.warning('Failed to activate %s' % self.doc.filepath)
|
log.warning('Failed to activate %s' % self.doc.file_path)
|
||||||
return False
|
return False
|
||||||
if self.is_live:
|
if self.is_live:
|
||||||
self.doc.start_presentation()
|
self.doc.start_presentation()
|
||||||
@ -104,7 +104,7 @@ class Controller(object):
|
|||||||
if self.doc.is_active():
|
if self.doc.is_active():
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
log.warning('Failed to activate %s' % self.doc.filepath)
|
log.warning('Failed to activate %s' % self.doc.file_path)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def slide(self, slide):
|
def slide(self, slide):
|
||||||
|
@ -22,11 +22,14 @@
|
|||||||
"""
|
"""
|
||||||
This module is for controlling powerpoint. PPT API documentation:
|
This module is for controlling powerpoint. PPT API documentation:
|
||||||
`http://msdn.microsoft.com/en-us/library/aa269321(office.10).aspx`_
|
`http://msdn.microsoft.com/en-us/library/aa269321(office.10).aspx`_
|
||||||
|
2010: https://msdn.microsoft.com/en-us/library/office/ff743835%28v=office.14%29.aspx
|
||||||
|
2013: https://msdn.microsoft.com/en-us/library/office/ff743835.aspx
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
|
import time
|
||||||
|
|
||||||
from openlp.core.common import is_win
|
from openlp.core.common import is_win, Settings
|
||||||
|
|
||||||
if is_win():
|
if is_win():
|
||||||
from win32com.client import Dispatch
|
from win32com.client import Dispatch
|
||||||
@ -36,9 +39,8 @@ if is_win():
|
|||||||
import pywintypes
|
import pywintypes
|
||||||
|
|
||||||
from openlp.core.lib import ScreenList
|
from openlp.core.lib import ScreenList
|
||||||
from openlp.core.common import Registry
|
|
||||||
from openlp.core.lib.ui import UiStrings, critical_error_message_box, translate
|
from openlp.core.lib.ui import UiStrings, critical_error_message_box, translate
|
||||||
from openlp.core.common import trace_error_handler
|
from openlp.core.common import trace_error_handler, Registry
|
||||||
from .presentationcontroller import PresentationController, PresentationDocument
|
from .presentationcontroller import PresentationController, PresentationDocument
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@ -82,6 +84,7 @@ class PowerpointController(PresentationController):
|
|||||||
if not self.process:
|
if not self.process:
|
||||||
self.process = Dispatch('PowerPoint.Application')
|
self.process = Dispatch('PowerPoint.Application')
|
||||||
self.process.Visible = True
|
self.process.Visible = True
|
||||||
|
# ppWindowMinimized = 2
|
||||||
self.process.WindowState = 2
|
self.process.WindowState = 2
|
||||||
|
|
||||||
def kill(self):
|
def kill(self):
|
||||||
@ -97,8 +100,10 @@ class PowerpointController(PresentationController):
|
|||||||
if self.process.Presentations.Count > 0:
|
if self.process.Presentations.Count > 0:
|
||||||
return
|
return
|
||||||
self.process.Quit()
|
self.process.Quit()
|
||||||
except (AttributeError, pywintypes.com_error):
|
except (AttributeError, pywintypes.com_error) as e:
|
||||||
pass
|
log.exception('Exception caught while killing powerpoint process')
|
||||||
|
log.exception(e)
|
||||||
|
trace_error_handler(log)
|
||||||
self.process = None
|
self.process = None
|
||||||
|
|
||||||
|
|
||||||
@ -117,6 +122,8 @@ class PowerpointDocument(PresentationDocument):
|
|||||||
log.debug('Init Presentation Powerpoint')
|
log.debug('Init Presentation Powerpoint')
|
||||||
super(PowerpointDocument, self).__init__(controller, presentation)
|
super(PowerpointDocument, self).__init__(controller, presentation)
|
||||||
self.presentation = None
|
self.presentation = None
|
||||||
|
self.index_map = {}
|
||||||
|
self.slide_count = 0
|
||||||
|
|
||||||
def load_presentation(self):
|
def load_presentation(self):
|
||||||
"""
|
"""
|
||||||
@ -131,16 +138,21 @@ class PowerpointDocument(PresentationDocument):
|
|||||||
self.presentation = self.controller.process.Presentations(self.controller.process.Presentations.Count)
|
self.presentation = self.controller.process.Presentations(self.controller.process.Presentations.Count)
|
||||||
self.create_thumbnails()
|
self.create_thumbnails()
|
||||||
self.create_titles_and_notes()
|
self.create_titles_and_notes()
|
||||||
# Powerpoint 2013 pops up when loading a file, so we minimize it again
|
# Powerpoint 2010 and 2013 pops up when loading a file, so we minimize it again
|
||||||
if self.presentation.Application.Version == u'15.0':
|
if float(self.presentation.Application.Version) >= 14.0:
|
||||||
try:
|
try:
|
||||||
|
# ppWindowMinimized = 2
|
||||||
self.presentation.Application.WindowState = 2
|
self.presentation.Application.WindowState = 2
|
||||||
except:
|
except (AttributeError, pywintypes.com_error) as e:
|
||||||
log.error('Failed to minimize main powerpoint window')
|
log.exception('Failed to minimize main powerpoint window')
|
||||||
|
log.exception(e)
|
||||||
trace_error_handler(log)
|
trace_error_handler(log)
|
||||||
|
# Make sure powerpoint doesn't steal focus
|
||||||
|
Registry().get('main_window').activateWindow()
|
||||||
return True
|
return True
|
||||||
except pywintypes.com_error:
|
except (AttributeError, pywintypes.com_error) as e:
|
||||||
log.error('PPT open failed')
|
log.exception('Exception caught while loading Powerpoint presentation')
|
||||||
|
log.exception(e)
|
||||||
trace_error_handler(log)
|
trace_error_handler(log)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -158,9 +170,14 @@ class PowerpointDocument(PresentationDocument):
|
|||||||
log.debug('create_thumbnails')
|
log.debug('create_thumbnails')
|
||||||
if self.check_thumbnails():
|
if self.check_thumbnails():
|
||||||
return
|
return
|
||||||
|
key = 1
|
||||||
for num in range(self.presentation.Slides.Count):
|
for num in range(self.presentation.Slides.Count):
|
||||||
self.presentation.Slides(num + 1).Export(
|
if not self.presentation.Slides(num + 1).SlideShowTransition.Hidden:
|
||||||
os.path.join(self.get_thumbnail_folder(), 'slide%d.png' % (num + 1)), 'png', 320, 240)
|
self.index_map[key] = num + 1
|
||||||
|
self.presentation.Slides(num + 1).Export(
|
||||||
|
os.path.join(self.get_thumbnail_folder(), 'slide%d.png' % (key)), 'png', 320, 240)
|
||||||
|
key += 1
|
||||||
|
self.slide_count = key - 1
|
||||||
|
|
||||||
def close_presentation(self):
|
def close_presentation(self):
|
||||||
"""
|
"""
|
||||||
@ -171,10 +188,14 @@ class PowerpointDocument(PresentationDocument):
|
|||||||
if self.presentation:
|
if self.presentation:
|
||||||
try:
|
try:
|
||||||
self.presentation.Close()
|
self.presentation.Close()
|
||||||
except pywintypes.com_error:
|
except (AttributeError, pywintypes.com_error) as e:
|
||||||
pass
|
log.exception('Caught exception while closing powerpoint presentation')
|
||||||
|
log.exception(e)
|
||||||
|
trace_error_handler(log)
|
||||||
self.presentation = None
|
self.presentation = None
|
||||||
self.controller.remove_doc(self)
|
self.controller.remove_doc(self)
|
||||||
|
# Make sure powerpoint doesn't steal focus
|
||||||
|
Registry().get('main_window').activateWindow()
|
||||||
|
|
||||||
def is_loaded(self):
|
def is_loaded(self):
|
||||||
"""
|
"""
|
||||||
@ -188,7 +209,10 @@ class PowerpointDocument(PresentationDocument):
|
|||||||
return False
|
return False
|
||||||
if self.controller.process.Presentations.Count == 0:
|
if self.controller.process.Presentations.Count == 0:
|
||||||
return False
|
return False
|
||||||
except (AttributeError, pywintypes.com_error):
|
except (AttributeError, pywintypes.com_error) as e:
|
||||||
|
log.exception('Caught exception while in is_loaded')
|
||||||
|
log.exception(e)
|
||||||
|
trace_error_handler(log)
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -204,7 +228,10 @@ class PowerpointDocument(PresentationDocument):
|
|||||||
return False
|
return False
|
||||||
if self.presentation.SlideShowWindow.View is None:
|
if self.presentation.SlideShowWindow.View is None:
|
||||||
return False
|
return False
|
||||||
except (AttributeError, pywintypes.com_error):
|
except (AttributeError, pywintypes.com_error) as e:
|
||||||
|
log.exception('Caught exception while in is_active')
|
||||||
|
log.exception(e)
|
||||||
|
trace_error_handler(log)
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -215,19 +242,21 @@ class PowerpointDocument(PresentationDocument):
|
|||||||
log.debug('unblank_screen')
|
log.debug('unblank_screen')
|
||||||
try:
|
try:
|
||||||
self.presentation.SlideShowSettings.Run()
|
self.presentation.SlideShowSettings.Run()
|
||||||
|
# ppSlideShowRunning = 1
|
||||||
self.presentation.SlideShowWindow.View.State = 1
|
self.presentation.SlideShowWindow.View.State = 1
|
||||||
self.presentation.SlideShowWindow.Activate()
|
self.presentation.SlideShowWindow.Activate()
|
||||||
if self.presentation.Application.Version == '14.0':
|
# Unblanking is broken in PowerPoint 2010 and 2013, need to redisplay
|
||||||
# Unblanking is broken in PowerPoint 2010, need to redisplay
|
if float(self.presentation.Application.Version) >= 14.0:
|
||||||
slide = self.presentation.SlideShowWindow.View.CurrentShowPosition
|
self.presentation.SlideShowWindow.View.GotoSlide(self.blank_slide, False)
|
||||||
click = self.presentation.SlideShowWindow.View.GetClickIndex()
|
if self.blank_click:
|
||||||
self.presentation.SlideShowWindow.View.GotoSlide(slide)
|
self.presentation.SlideShowWindow.View.GotoClick(self.blank_click)
|
||||||
if click:
|
except (AttributeError, pywintypes.com_error) as e:
|
||||||
self.presentation.SlideShowWindow.View.GotoClick(click)
|
log.exception('Caught exception while in unblank_screen')
|
||||||
except pywintypes.com_error:
|
log.exception(e)
|
||||||
log.error('COM error while in unblank_screen')
|
|
||||||
trace_error_handler(log)
|
trace_error_handler(log)
|
||||||
self.show_error_msg()
|
self.show_error_msg()
|
||||||
|
# Make sure powerpoint doesn't steal focus
|
||||||
|
Registry().get('main_window').activateWindow()
|
||||||
|
|
||||||
def blank_screen(self):
|
def blank_screen(self):
|
||||||
"""
|
"""
|
||||||
@ -235,9 +264,15 @@ class PowerpointDocument(PresentationDocument):
|
|||||||
"""
|
"""
|
||||||
log.debug('blank_screen')
|
log.debug('blank_screen')
|
||||||
try:
|
try:
|
||||||
|
# Unblanking is broken in PowerPoint 2010 and 2013, need to save info for later
|
||||||
|
if float(self.presentation.Application.Version) >= 14.0:
|
||||||
|
self.blank_slide = self.get_slide_number()
|
||||||
|
self.blank_click = self.presentation.SlideShowWindow.View.GetClickIndex()
|
||||||
|
# ppSlideShowBlackScreen = 3
|
||||||
self.presentation.SlideShowWindow.View.State = 3
|
self.presentation.SlideShowWindow.View.State = 3
|
||||||
except pywintypes.com_error:
|
except (AttributeError, pywintypes.com_error) as e:
|
||||||
log.error('COM error while in blank_screen')
|
log.exception('Caught exception while in blank_screen')
|
||||||
|
log.exception(e)
|
||||||
trace_error_handler(log)
|
trace_error_handler(log)
|
||||||
self.show_error_msg()
|
self.show_error_msg()
|
||||||
|
|
||||||
@ -248,9 +283,11 @@ class PowerpointDocument(PresentationDocument):
|
|||||||
log.debug('is_blank')
|
log.debug('is_blank')
|
||||||
if self.is_active():
|
if self.is_active():
|
||||||
try:
|
try:
|
||||||
|
# ppSlideShowBlackScreen = 3
|
||||||
return self.presentation.SlideShowWindow.View.State == 3
|
return self.presentation.SlideShowWindow.View.State == 3
|
||||||
except pywintypes.com_error:
|
except (AttributeError, pywintypes.com_error) as e:
|
||||||
log.error('COM error while in is_blank')
|
log.exception('Caught exception while in is_blank')
|
||||||
|
log.exception(e)
|
||||||
trace_error_handler(log)
|
trace_error_handler(log)
|
||||||
self.show_error_msg()
|
self.show_error_msg()
|
||||||
else:
|
else:
|
||||||
@ -263,8 +300,9 @@ class PowerpointDocument(PresentationDocument):
|
|||||||
log.debug('stop_presentation')
|
log.debug('stop_presentation')
|
||||||
try:
|
try:
|
||||||
self.presentation.SlideShowWindow.View.Exit()
|
self.presentation.SlideShowWindow.View.Exit()
|
||||||
except pywintypes.com_error:
|
except (AttributeError, pywintypes.com_error) as e:
|
||||||
log.error('COM error while in stop_presentation')
|
log.exception('Caught exception while in stop_presentation')
|
||||||
|
log.exception(e)
|
||||||
trace_error_handler(log)
|
trace_error_handler(log)
|
||||||
self.show_error_msg()
|
self.show_error_msg()
|
||||||
|
|
||||||
@ -292,15 +330,19 @@ class PowerpointDocument(PresentationDocument):
|
|||||||
ppt_window.Left = size.x() * 72 / dpi
|
ppt_window.Left = size.x() * 72 / dpi
|
||||||
ppt_window.Width = size.width() * 72 / dpi
|
ppt_window.Width = size.width() * 72 / dpi
|
||||||
except AttributeError as e:
|
except AttributeError as e:
|
||||||
log.error('AttributeError while in start_presentation')
|
log.exception('AttributeError while in start_presentation')
|
||||||
log.error(e)
|
log.exception(e)
|
||||||
# Powerpoint 2013 pops up when starting a file, so we minimize it again
|
# Powerpoint 2010 and 2013 pops up when starting a file, so we minimize it again
|
||||||
if self.presentation.Application.Version == u'15.0':
|
if float(self.presentation.Application.Version) >= 14.0:
|
||||||
try:
|
try:
|
||||||
|
# ppWindowMinimized = 2
|
||||||
self.presentation.Application.WindowState = 2
|
self.presentation.Application.WindowState = 2
|
||||||
except:
|
except (AttributeError, pywintypes.com_error) as e:
|
||||||
log.error('Failed to minimize main powerpoint window')
|
log.exception('Failed to minimize main powerpoint window')
|
||||||
|
log.exception(e)
|
||||||
trace_error_handler(log)
|
trace_error_handler(log)
|
||||||
|
# Make sure powerpoint doesn't steal focus
|
||||||
|
Registry().get('main_window').activateWindow()
|
||||||
|
|
||||||
def get_slide_number(self):
|
def get_slide_number(self):
|
||||||
"""
|
"""
|
||||||
@ -309,9 +351,19 @@ class PowerpointDocument(PresentationDocument):
|
|||||||
log.debug('get_slide_number')
|
log.debug('get_slide_number')
|
||||||
ret = 0
|
ret = 0
|
||||||
try:
|
try:
|
||||||
ret = self.presentation.SlideShowWindow.View.CurrentShowPosition
|
# We need 2 approaches to getting the current slide number, because
|
||||||
except pywintypes.com_error:
|
# SlideShowWindow.View.Slide.SlideIndex wont work on the end-slide where Slide isn't available, and
|
||||||
log.error('COM error while in get_slide_number')
|
# SlideShowWindow.View.CurrentShowPosition returns 0 when called when a transistion is executing (in 2013)
|
||||||
|
# So we use SlideShowWindow.View.Slide.SlideIndex unless the state is done (ppSlideShowDone = 5)
|
||||||
|
if self.presentation.SlideShowWindow.View.State != 5:
|
||||||
|
ret = self.presentation.SlideShowWindow.View.Slide.SlideNumber
|
||||||
|
# Do reverse lookup in the index_map to find the slide number to return
|
||||||
|
ret = next((key for key, slidenum in self.index_map.items() if slidenum == ret), None)
|
||||||
|
else:
|
||||||
|
ret = self.presentation.SlideShowWindow.View.CurrentShowPosition
|
||||||
|
except (AttributeError, pywintypes.com_error) as e:
|
||||||
|
log.exception('Caught exception while in get_slide_number')
|
||||||
|
log.exception(e)
|
||||||
trace_error_handler(log)
|
trace_error_handler(log)
|
||||||
self.show_error_msg()
|
self.show_error_msg()
|
||||||
return ret
|
return ret
|
||||||
@ -321,14 +373,7 @@ class PowerpointDocument(PresentationDocument):
|
|||||||
Returns total number of slides.
|
Returns total number of slides.
|
||||||
"""
|
"""
|
||||||
log.debug('get_slide_count')
|
log.debug('get_slide_count')
|
||||||
ret = 0
|
return self.slide_count
|
||||||
try:
|
|
||||||
ret = self.presentation.Slides.Count
|
|
||||||
except pywintypes.com_error:
|
|
||||||
log.error('COM error while in get_slide_count')
|
|
||||||
trace_error_handler(log)
|
|
||||||
self.show_error_msg()
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def goto_slide(self, slide_no):
|
def goto_slide(self, slide_no):
|
||||||
"""
|
"""
|
||||||
@ -338,9 +383,19 @@ class PowerpointDocument(PresentationDocument):
|
|||||||
"""
|
"""
|
||||||
log.debug('goto_slide')
|
log.debug('goto_slide')
|
||||||
try:
|
try:
|
||||||
self.presentation.SlideShowWindow.View.GotoSlide(slide_no)
|
if Settings().value('presentations/powerpoint slide click advance') \
|
||||||
except pywintypes.com_error:
|
and self.get_slide_number() == self.index_map[slide_no]:
|
||||||
log.error('COM error while in goto_slide')
|
click_index = self.presentation.SlideShowWindow.View.GetClickIndex()
|
||||||
|
click_count = self.presentation.SlideShowWindow.View.GetClickCount()
|
||||||
|
log.debug('We are already on this slide - go to next effect if any left, idx: %d, count: %d'
|
||||||
|
% (click_index, click_count))
|
||||||
|
if click_index < click_count:
|
||||||
|
self.next_step()
|
||||||
|
else:
|
||||||
|
self.presentation.SlideShowWindow.View.GotoSlide(self.index_map[slide_no])
|
||||||
|
except (AttributeError, pywintypes.com_error) as e:
|
||||||
|
log.exception('Caught exception while in goto_slide')
|
||||||
|
log.exception(e)
|
||||||
trace_error_handler(log)
|
trace_error_handler(log)
|
||||||
self.show_error_msg()
|
self.show_error_msg()
|
||||||
|
|
||||||
@ -351,12 +406,14 @@ class PowerpointDocument(PresentationDocument):
|
|||||||
log.debug('next_step')
|
log.debug('next_step')
|
||||||
try:
|
try:
|
||||||
self.presentation.SlideShowWindow.View.Next()
|
self.presentation.SlideShowWindow.View.Next()
|
||||||
except pywintypes.com_error:
|
except (AttributeError, pywintypes.com_error) as e:
|
||||||
log.error('COM error while in next_step')
|
log.exception('Caught exception while in next_step')
|
||||||
|
log.exception(e)
|
||||||
trace_error_handler(log)
|
trace_error_handler(log)
|
||||||
self.show_error_msg()
|
self.show_error_msg()
|
||||||
return
|
return
|
||||||
if self.get_slide_number() > self.get_slide_count():
|
if self.get_slide_number() > self.get_slide_count():
|
||||||
|
log.debug('past end, stepping back to previous')
|
||||||
self.previous_step()
|
self.previous_step()
|
||||||
|
|
||||||
def previous_step(self):
|
def previous_step(self):
|
||||||
@ -366,8 +423,9 @@ class PowerpointDocument(PresentationDocument):
|
|||||||
log.debug('previous_step')
|
log.debug('previous_step')
|
||||||
try:
|
try:
|
||||||
self.presentation.SlideShowWindow.View.Previous()
|
self.presentation.SlideShowWindow.View.Previous()
|
||||||
except pywintypes.com_error:
|
except (AttributeError, pywintypes.com_error) as e:
|
||||||
log.error('COM error while in previous_step')
|
log.exception('Caught exception while in previous_step')
|
||||||
|
log.exception(e)
|
||||||
trace_error_handler(log)
|
trace_error_handler(log)
|
||||||
self.show_error_msg()
|
self.show_error_msg()
|
||||||
|
|
||||||
@ -377,7 +435,7 @@ class PowerpointDocument(PresentationDocument):
|
|||||||
|
|
||||||
:param slide_no: The slide the text is required for, starting at 1
|
:param slide_no: The slide the text is required for, starting at 1
|
||||||
"""
|
"""
|
||||||
return _get_text_from_shapes(self.presentation.Slides(slide_no).Shapes)
|
return _get_text_from_shapes(self.presentation.Slides(self.index_map[slide_no]).Shapes)
|
||||||
|
|
||||||
def get_slide_notes(self, slide_no):
|
def get_slide_notes(self, slide_no):
|
||||||
"""
|
"""
|
||||||
@ -385,7 +443,7 @@ class PowerpointDocument(PresentationDocument):
|
|||||||
|
|
||||||
:param slide_no: The slide the text is required for, starting at 1
|
:param slide_no: The slide the text is required for, starting at 1
|
||||||
"""
|
"""
|
||||||
return _get_text_from_shapes(self.presentation.Slides(slide_no).NotesPage.Shapes)
|
return _get_text_from_shapes(self.presentation.Slides(self.index_map[slide_no]).NotesPage.Shapes)
|
||||||
|
|
||||||
def create_titles_and_notes(self):
|
def create_titles_and_notes(self):
|
||||||
"""
|
"""
|
||||||
@ -396,7 +454,8 @@ class PowerpointDocument(PresentationDocument):
|
|||||||
"""
|
"""
|
||||||
titles = []
|
titles = []
|
||||||
notes = []
|
notes = []
|
||||||
for slide in self.presentation.Slides:
|
for num in range(self.get_slide_count()):
|
||||||
|
slide = self.presentation.Slides(self.index_map[num + 1])
|
||||||
try:
|
try:
|
||||||
text = slide.Shapes.Title.TextFrame.TextRange.Text
|
text = slide.Shapes.Title.TextFrame.TextRange.Text
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -413,7 +472,11 @@ class PowerpointDocument(PresentationDocument):
|
|||||||
"""
|
"""
|
||||||
Stop presentation and display an error message.
|
Stop presentation and display an error message.
|
||||||
"""
|
"""
|
||||||
self.stop_presentation()
|
try:
|
||||||
|
self.presentation.SlideShowWindow.View.Exit()
|
||||||
|
except (AttributeError, pywintypes.com_error) as e:
|
||||||
|
log.exception('Failed to exit Powerpoint presentation after error')
|
||||||
|
log.exception(e)
|
||||||
critical_error_message_box(UiStrings().Error, translate('PresentationPlugin.PowerpointDocument',
|
critical_error_message_box(UiStrings().Error, translate('PresentationPlugin.PowerpointDocument',
|
||||||
'An error occurred in the Powerpoint integration '
|
'An error occurred in the Powerpoint integration '
|
||||||
'and the presentation will be stopped. '
|
'and the presentation will be stopped. '
|
||||||
|
@ -132,9 +132,9 @@ class PresentationDocument(object):
|
|||||||
"""
|
"""
|
||||||
The location where thumbnail images will be stored
|
The location where thumbnail images will be stored
|
||||||
"""
|
"""
|
||||||
# TODO: If statment can be removed when the upgrade path from 2.0.x to 2.2.x is no longer needed
|
# TODO: If statement can be removed when the upgrade path from 2.0.x to 2.2.x is no longer needed
|
||||||
if Settings().value('presentations/thumbnail_scheme') == 'md5':
|
if Settings().value('presentations/thumbnail_scheme') == 'md5':
|
||||||
folder = md5_hash(self.file_path)
|
folder = md5_hash(self.file_path.encode('utf-8'))
|
||||||
else:
|
else:
|
||||||
folder = self.get_file_name()
|
folder = self.get_file_name()
|
||||||
return os.path.join(self.controller.thumbnail_folder, folder)
|
return os.path.join(self.controller.thumbnail_folder, folder)
|
||||||
@ -143,9 +143,9 @@ class PresentationDocument(object):
|
|||||||
"""
|
"""
|
||||||
The location where thumbnail images will be stored
|
The location where thumbnail images will be stored
|
||||||
"""
|
"""
|
||||||
# TODO: If statment can be removed when the upgrade path from 2.0.x to 2.2.x is no longer needed
|
# TODO: If statement can be removed when the upgrade path from 2.0.x to 2.2.x is no longer needed
|
||||||
if Settings().value('presentations/thumbnail_scheme') == 'md5':
|
if Settings().value('presentations/thumbnail_scheme') == 'md5':
|
||||||
folder = md5_hash(self.file_path)
|
folder = md5_hash(self.file_path.encode('utf-8'))
|
||||||
else:
|
else:
|
||||||
folder = folder = self.get_file_name()
|
folder = folder = self.get_file_name()
|
||||||
return os.path.join(self.controller.temp_folder, folder)
|
return os.path.join(self.controller.temp_folder, folder)
|
||||||
@ -306,7 +306,7 @@ class PresentationDocument(object):
|
|||||||
titles_file = os.path.join(self.get_thumbnail_folder(), 'titles.txt')
|
titles_file = os.path.join(self.get_thumbnail_folder(), 'titles.txt')
|
||||||
if os.path.exists(titles_file):
|
if os.path.exists(titles_file):
|
||||||
try:
|
try:
|
||||||
with open(titles_file) as fi:
|
with open(titles_file, encoding='utf-8') as fi:
|
||||||
titles = fi.read().splitlines()
|
titles = fi.read().splitlines()
|
||||||
except:
|
except:
|
||||||
log.exception('Failed to open/read existing titles file')
|
log.exception('Failed to open/read existing titles file')
|
||||||
@ -316,7 +316,7 @@ class PresentationDocument(object):
|
|||||||
note = ''
|
note = ''
|
||||||
if os.path.exists(notes_file):
|
if os.path.exists(notes_file):
|
||||||
try:
|
try:
|
||||||
with open(notes_file) as fn:
|
with open(notes_file, encoding='utf-8') as fn:
|
||||||
note = fn.read()
|
note = fn.read()
|
||||||
except:
|
except:
|
||||||
log.exception('Failed to open/read notes file')
|
log.exception('Failed to open/read notes file')
|
||||||
@ -331,12 +331,12 @@ class PresentationDocument(object):
|
|||||||
"""
|
"""
|
||||||
if titles:
|
if titles:
|
||||||
titles_file = os.path.join(self.get_thumbnail_folder(), 'titles.txt')
|
titles_file = os.path.join(self.get_thumbnail_folder(), 'titles.txt')
|
||||||
with open(titles_file, mode='w') as fo:
|
with open(titles_file, mode='wt', encoding='utf-8') as fo:
|
||||||
fo.writelines(titles)
|
fo.writelines(titles)
|
||||||
if notes:
|
if notes:
|
||||||
for slide_no, note in enumerate(notes, 1):
|
for slide_no, note in enumerate(notes, 1):
|
||||||
notes_file = os.path.join(self.get_thumbnail_folder(), 'slideNotes%d.txt' % slide_no)
|
notes_file = os.path.join(self.get_thumbnail_folder(), 'slideNotes%d.txt' % slide_no)
|
||||||
with open(notes_file, mode='w') as fn:
|
with open(notes_file, mode='wt', encoding='utf-8') as fn:
|
||||||
fn.write(note)
|
fn.write(note)
|
||||||
|
|
||||||
|
|
||||||
|
@ -68,6 +68,15 @@ class PresentationTab(SettingsTab):
|
|||||||
self.override_app_check_box.setObjectName('override_app_check_box')
|
self.override_app_check_box.setObjectName('override_app_check_box')
|
||||||
self.advanced_layout.addWidget(self.override_app_check_box)
|
self.advanced_layout.addWidget(self.override_app_check_box)
|
||||||
self.left_layout.addWidget(self.advanced_group_box)
|
self.left_layout.addWidget(self.advanced_group_box)
|
||||||
|
# PowerPoint
|
||||||
|
self.powerpoint_group_box = QtGui.QGroupBox(self.left_column)
|
||||||
|
self.powerpoint_group_box.setObjectName('powerpoint_group_box')
|
||||||
|
self.powerpoint_layout = QtGui.QVBoxLayout(self.powerpoint_group_box)
|
||||||
|
self.powerpoint_layout.setObjectName('powerpoint_layout')
|
||||||
|
self.ppt_slide_click_check_box = QtGui.QCheckBox(self.powerpoint_group_box)
|
||||||
|
self.powerpoint_group_box.setObjectName('ppt_slide_click_check_box')
|
||||||
|
self.powerpoint_layout.addWidget(self.ppt_slide_click_check_box)
|
||||||
|
self.left_layout.addWidget(self.powerpoint_group_box)
|
||||||
# Pdf options
|
# Pdf options
|
||||||
self.pdf_group_box = QtGui.QGroupBox(self.left_column)
|
self.pdf_group_box = QtGui.QGroupBox(self.left_column)
|
||||||
self.pdf_group_box.setObjectName('pdf_group_box')
|
self.pdf_group_box.setObjectName('pdf_group_box')
|
||||||
@ -108,8 +117,12 @@ class PresentationTab(SettingsTab):
|
|||||||
self.set_controller_text(checkbox, controller)
|
self.set_controller_text(checkbox, controller)
|
||||||
self.advanced_group_box.setTitle(UiStrings().Advanced)
|
self.advanced_group_box.setTitle(UiStrings().Advanced)
|
||||||
self.pdf_group_box.setTitle(translate('PresentationPlugin.PresentationTab', 'PDF options'))
|
self.pdf_group_box.setTitle(translate('PresentationPlugin.PresentationTab', 'PDF options'))
|
||||||
|
self.powerpoint_group_box.setTitle(translate('PresentationPlugin.PresentationTab', 'PowerPoint options'))
|
||||||
self.override_app_check_box.setText(
|
self.override_app_check_box.setText(
|
||||||
translate('PresentationPlugin.PresentationTab', 'Allow presentation application to be overridden'))
|
translate('PresentationPlugin.PresentationTab', 'Allow presentation application to be overridden'))
|
||||||
|
self.ppt_slide_click_check_box.setText(
|
||||||
|
translate('PresentationPlugin.PresentationTab',
|
||||||
|
'Clicking on a selected slide in the slidecontroller advances to next effect.'))
|
||||||
self.pdf_program_check_box.setText(
|
self.pdf_program_check_box.setText(
|
||||||
translate('PresentationPlugin.PresentationTab', 'Use given full path for mudraw or ghostscript binary:'))
|
translate('PresentationPlugin.PresentationTab', 'Use given full path for mudraw or ghostscript binary:'))
|
||||||
|
|
||||||
@ -123,11 +136,18 @@ class PresentationTab(SettingsTab):
|
|||||||
"""
|
"""
|
||||||
Load the settings.
|
Load the settings.
|
||||||
"""
|
"""
|
||||||
|
powerpoint_available = False
|
||||||
for key in self.controllers:
|
for key in self.controllers:
|
||||||
controller = self.controllers[key]
|
controller = self.controllers[key]
|
||||||
checkbox = self.presenter_check_boxes[controller.name]
|
checkbox = self.presenter_check_boxes[controller.name]
|
||||||
checkbox.setChecked(Settings().value(self.settings_section + '/' + controller.name))
|
checkbox.setChecked(Settings().value(self.settings_section + '/' + controller.name))
|
||||||
|
if controller.name == 'Powerpoint' and controller.is_available():
|
||||||
|
powerpoint_available = True
|
||||||
self.override_app_check_box.setChecked(Settings().value(self.settings_section + '/override app'))
|
self.override_app_check_box.setChecked(Settings().value(self.settings_section + '/override app'))
|
||||||
|
# Load Powerpoint settings
|
||||||
|
self.ppt_slide_click_check_box.setChecked(Settings().value(self.settings_section +
|
||||||
|
'/powerpoint slide click advance'))
|
||||||
|
self.ppt_slide_click_check_box.setEnabled(powerpoint_available)
|
||||||
# load pdf-program settings
|
# load pdf-program settings
|
||||||
enable_pdf_program = Settings().value(self.settings_section + '/enable_pdf_program')
|
enable_pdf_program = Settings().value(self.settings_section + '/enable_pdf_program')
|
||||||
self.pdf_program_check_box.setChecked(enable_pdf_program)
|
self.pdf_program_check_box.setChecked(enable_pdf_program)
|
||||||
@ -161,6 +181,11 @@ class PresentationTab(SettingsTab):
|
|||||||
if Settings().value(setting_key) != self.override_app_check_box.checkState():
|
if Settings().value(setting_key) != self.override_app_check_box.checkState():
|
||||||
Settings().setValue(setting_key, self.override_app_check_box.checkState())
|
Settings().setValue(setting_key, self.override_app_check_box.checkState())
|
||||||
changed = True
|
changed = True
|
||||||
|
# Save powerpoint settings
|
||||||
|
setting_key = self.settings_section + '/powerpoint slide click advance'
|
||||||
|
if Settings().value(setting_key) != self.ppt_slide_click_check_box.checkState():
|
||||||
|
Settings().setValue(setting_key, self.ppt_slide_click_check_box.checkState())
|
||||||
|
changed = True
|
||||||
# Save pdf-settings
|
# Save pdf-settings
|
||||||
pdf_program = self.pdf_program_path.text()
|
pdf_program = self.pdf_program_path.text()
|
||||||
enable_pdf_program = self.pdf_program_check_box.checkState()
|
enable_pdf_program = self.pdf_program_check_box.checkState()
|
||||||
|
@ -44,7 +44,8 @@ __default_settings__ = {'presentations/override app': QtCore.Qt.Unchecked,
|
|||||||
'presentations/Powerpoint Viewer': QtCore.Qt.Checked,
|
'presentations/Powerpoint Viewer': QtCore.Qt.Checked,
|
||||||
'presentations/Pdf': QtCore.Qt.Checked,
|
'presentations/Pdf': QtCore.Qt.Checked,
|
||||||
'presentations/presentations files': [],
|
'presentations/presentations files': [],
|
||||||
'presentations/thumbnail_scheme': ''
|
'presentations/thumbnail_scheme': '',
|
||||||
|
'presentations/powerpoint slide click advance': QtCore.Qt.Unchecked
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -144,7 +145,7 @@ class PresentationPlugin(Plugin):
|
|||||||
files_from_config = Settings().value('presentations/presentations files')
|
files_from_config = Settings().value('presentations/presentations files')
|
||||||
for file in files_from_config:
|
for file in files_from_config:
|
||||||
try:
|
try:
|
||||||
self.media_item.clean_up_thumbnails(file)
|
self.media_item.clean_up_thumbnails(file, True)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
self.media_item.list_view.clear()
|
self.media_item.list_view.clear()
|
||||||
|
@ -117,7 +117,7 @@ from urllib.parse import urlparse, parse_qs
|
|||||||
from mako.template import Template
|
from mako.template import Template
|
||||||
from PyQt4 import QtCore
|
from PyQt4 import QtCore
|
||||||
|
|
||||||
from openlp.core.common import Registry, RegistryProperties, AppLocation, Settings, translate
|
from openlp.core.common import RegistryProperties, AppLocation, Settings, translate, UiStrings
|
||||||
from openlp.core.lib import PluginStatus, StringContent, image_to_byte, ItemCapabilities, create_thumb
|
from openlp.core.lib import PluginStatus, StringContent, image_to_byte, ItemCapabilities, create_thumb
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@ -232,12 +232,18 @@ class HttpRouter(RegistryProperties):
|
|||||||
return func, args
|
return func, args
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
|
def set_cache_headers(self):
|
||||||
|
self.send_header("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||||
|
self.send_header("Pragma", "no-cache")
|
||||||
|
self.send_header("Expires", "0")
|
||||||
|
|
||||||
def do_http_success(self):
|
def do_http_success(self):
|
||||||
"""
|
"""
|
||||||
Create a success http header.
|
Create a success http header.
|
||||||
"""
|
"""
|
||||||
self.send_response(200)
|
self.send_response(200)
|
||||||
self.send_header('Content-type', 'text/html')
|
self.send_header('Content-type', 'text/html')
|
||||||
|
self.set_cache_headers()
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
|
|
||||||
def do_json_header(self):
|
def do_json_header(self):
|
||||||
@ -246,6 +252,7 @@ class HttpRouter(RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
self.send_response(200)
|
self.send_response(200)
|
||||||
self.send_header('Content-type', 'application/json')
|
self.send_header('Content-type', 'application/json')
|
||||||
|
self.set_cache_headers()
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
|
|
||||||
def do_http_error(self):
|
def do_http_error(self):
|
||||||
@ -254,6 +261,7 @@ class HttpRouter(RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
self.send_response(404)
|
self.send_response(404)
|
||||||
self.send_header('Content-type', 'text/html')
|
self.send_header('Content-type', 'text/html')
|
||||||
|
self.set_cache_headers()
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
|
|
||||||
def do_authorisation(self):
|
def do_authorisation(self):
|
||||||
@ -261,8 +269,10 @@ class HttpRouter(RegistryProperties):
|
|||||||
Create a needs authorisation http header.
|
Create a needs authorisation http header.
|
||||||
"""
|
"""
|
||||||
self.send_response(401)
|
self.send_response(401)
|
||||||
self.send_header('WWW-Authenticate', 'Basic realm=\"Test\"')
|
header = 'Basic realm=\"{}\"'.format(UiStrings().OLPV2)
|
||||||
|
self.send_header('WWW-Authenticate', header)
|
||||||
self.send_header('Content-type', 'text/html')
|
self.send_header('Content-type', 'text/html')
|
||||||
|
self.set_cache_headers()
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
|
|
||||||
def do_not_found(self):
|
def do_not_found(self):
|
||||||
@ -271,6 +281,7 @@ class HttpRouter(RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
self.send_response(404)
|
self.send_response(404)
|
||||||
self.send_header('Content-type', 'text/html')
|
self.send_header('Content-type', 'text/html')
|
||||||
|
self.set_cache_headers()
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
self.wfile.write(bytes('<html><body>Sorry, an error occurred </body></html>', 'UTF-8'))
|
self.wfile.write(bytes('<html><body>Sorry, an error occurred </body></html>', 'UTF-8'))
|
||||||
|
|
||||||
|
@ -43,6 +43,7 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog):
|
|||||||
"""
|
"""
|
||||||
super(EditVerseForm, self).__init__(parent)
|
super(EditVerseForm, self).__init__(parent)
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
|
self.has_single_verse = False
|
||||||
self.insert_button.clicked.connect(self.on_insert_button_clicked)
|
self.insert_button.clicked.connect(self.on_insert_button_clicked)
|
||||||
self.split_button.clicked.connect(self.on_split_button_clicked)
|
self.split_button.clicked.connect(self.on_split_button_clicked)
|
||||||
self.verse_text_edit.cursorPositionChanged.connect(self.on_cursor_position_changed)
|
self.verse_text_edit.cursorPositionChanged.connect(self.on_cursor_position_changed)
|
||||||
@ -98,6 +99,8 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog):
|
|||||||
"""
|
"""
|
||||||
Adjusts the verse number SpinBox in regard to the selected verse type and the cursor's position.
|
Adjusts the verse number SpinBox in regard to the selected verse type and the cursor's position.
|
||||||
"""
|
"""
|
||||||
|
if self.has_single_verse:
|
||||||
|
return
|
||||||
position = self.verse_text_edit.textCursor().position()
|
position = self.verse_text_edit.textCursor().position()
|
||||||
text = self.verse_text_edit.toPlainText()
|
text = self.verse_text_edit.toPlainText()
|
||||||
verse_name = VerseType.translated_names[
|
verse_name = VerseType.translated_names[
|
||||||
|
@ -244,12 +244,16 @@ class SongExportForm(OpenLPWizard):
|
|||||||
for song in self._find_list_widget_items(self.selected_list_widget)
|
for song in self._find_list_widget_items(self.selected_list_widget)
|
||||||
]
|
]
|
||||||
exporter = OpenLyricsExport(self, songs, self.directory_line_edit.text())
|
exporter = OpenLyricsExport(self, songs, self.directory_line_edit.text())
|
||||||
if exporter.do_export():
|
try:
|
||||||
self.progress_label.setText(
|
if exporter.do_export():
|
||||||
translate('SongsPlugin.SongExportForm',
|
self.progress_label.setText(
|
||||||
'Finished export. To import these files use the <strong>OpenLyrics</strong> importer.'))
|
translate('SongsPlugin.SongExportForm',
|
||||||
else:
|
'Finished export. To import these files use the <strong>OpenLyrics</strong> importer.'))
|
||||||
self.progress_label.setText(translate('SongsPlugin.SongExportForm', 'Your song export failed.'))
|
else:
|
||||||
|
self.progress_label.setText(translate('SongsPlugin.SongExportForm', 'Your song export failed.'))
|
||||||
|
except OSError as ose:
|
||||||
|
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=''):
|
def _find_list_widget_items(self, list_widget, text=''):
|
||||||
"""
|
"""
|
||||||
|
@ -187,6 +187,14 @@ class SongSelectForm(QtGui.QDialog, Ui_SongSelectDialog):
|
|||||||
self.application.process_events()
|
self.application.process_events()
|
||||||
# Get the full song
|
# Get the full song
|
||||||
song = self.song_select_importer.get_song(song, self._update_song_progress)
|
song = self.song_select_importer.get_song(song, self._update_song_progress)
|
||||||
|
if not song:
|
||||||
|
QtGui.QMessageBox.critical(
|
||||||
|
self, translate('SongsPlugin.SongSelectForm', 'Incomplete song'),
|
||||||
|
translate('SongsPlugin.SongSelectForm', 'This song is missing some information, like the lyrics, '
|
||||||
|
'and cannot be imported.'),
|
||||||
|
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok), QtGui.QMessageBox.Ok)
|
||||||
|
self.stacked_widget.setCurrentIndex(1)
|
||||||
|
return
|
||||||
# Update the UI
|
# Update the UI
|
||||||
self.title_edit.setText(song['title'])
|
self.title_edit.setText(song['title'])
|
||||||
self.copyright_edit.setText(song['copyright'])
|
self.copyright_edit.setText(song['copyright'])
|
||||||
@ -359,15 +367,12 @@ class SongSelectForm(QtGui.QDialog, Ui_SongSelectDialog):
|
|||||||
Import a song from SongSelect.
|
Import a song from SongSelect.
|
||||||
"""
|
"""
|
||||||
self.song_select_importer.save_song(self.song)
|
self.song_select_importer.save_song(self.song)
|
||||||
question_dialog = QtGui.QMessageBox()
|
self.song = None
|
||||||
question_dialog.setWindowTitle(translate('SongsPlugin.SongSelectForm', 'Song Imported'))
|
if QtGui.QMessageBox.question(self, translate('SongsPlugin.SongSelectForm', 'Song Imported'),
|
||||||
question_dialog.setText(translate('SongsPlugin.SongSelectForm', 'Your song has been imported, would you like '
|
translate('SongsPlugin.SongSelectForm', 'Your song has been imported, would you '
|
||||||
'to exit now, or import more songs?'))
|
'like to import more songs?'),
|
||||||
question_dialog.addButton(QtGui.QPushButton(translate('SongsPlugin.SongSelectForm', 'Import More Songs')),
|
QtGui.QMessageBox.Yes | QtGui.QMessageBox.No,
|
||||||
QtGui.QMessageBox.YesRole)
|
QtGui.QMessageBox.Yes) == QtGui.QMessageBox.Yes:
|
||||||
question_dialog.addButton(QtGui.QPushButton(translate('SongsPlugin.SongSelectForm', 'Exit Now')),
|
|
||||||
QtGui.QMessageBox.NoRole)
|
|
||||||
if question_dialog.exec_() == QtGui.QMessageBox.Yes:
|
|
||||||
self.on_back_button_clicked()
|
self.on_back_button_clicked()
|
||||||
else:
|
else:
|
||||||
self.application.process_events()
|
self.application.process_events()
|
||||||
|
@ -312,7 +312,7 @@ def init_schema(url):
|
|||||||
'authors_songs', metadata,
|
'authors_songs', metadata,
|
||||||
Column('author_id', types.Integer(), ForeignKey('authors.id'), primary_key=True),
|
Column('author_id', types.Integer(), ForeignKey('authors.id'), primary_key=True),
|
||||||
Column('song_id', types.Integer(), ForeignKey('songs.id'), primary_key=True),
|
Column('song_id', types.Integer(), ForeignKey('songs.id'), primary_key=True),
|
||||||
Column('author_type', types.String(), primary_key=True, nullable=False, server_default=text('""'))
|
Column('author_type', types.Unicode(255), primary_key=True, nullable=False, server_default=text('""'))
|
||||||
)
|
)
|
||||||
|
|
||||||
# Definition of the "songs_topics" table
|
# Definition of the "songs_topics" table
|
||||||
|
@ -179,6 +179,6 @@ class WorshipAssistantImport(SongImport):
|
|||||||
cleaned_verse_order_list.append(verse)
|
cleaned_verse_order_list.append(verse)
|
||||||
self.verse_order_list = cleaned_verse_order_list
|
self.verse_order_list = cleaned_verse_order_list
|
||||||
if not self.finish():
|
if not self.finish():
|
||||||
self.log_error(translate('SongsPlugin.WorshipAssistantImport', 'Record %d') % index
|
self.log_error(translate('SongsPlugin.WorshipAssistantImport', 'Record %d') % index +
|
||||||
+ (': "' + self.title + '"' if self.title else ''))
|
(': "' + self.title + '"' if self.title else ''))
|
||||||
songs_file.close()
|
songs_file.close()
|
||||||
|
@ -118,8 +118,8 @@ class ZionWorxImport(SongImport):
|
|||||||
self.add_verse(verse)
|
self.add_verse(verse)
|
||||||
title = self.title
|
title = self.title
|
||||||
if not self.finish():
|
if not self.finish():
|
||||||
self.log_error(translate('SongsPlugin.ZionWorxImport', 'Record %d') % index
|
self.log_error(translate('SongsPlugin.ZionWorxImport', 'Record %d') % index +
|
||||||
+ (': "' + title + '"' if title else ''))
|
(': "' + title + '"' if title else ''))
|
||||||
|
|
||||||
def _decode(self, str):
|
def _decode(self, str):
|
||||||
"""
|
"""
|
||||||
|
@ -55,7 +55,7 @@ The XML of an `OpenLyrics <http://openlyrics.info/>`_ song looks like this::
|
|||||||
</lyrics>
|
</lyrics>
|
||||||
</song>
|
</song>
|
||||||
"""
|
"""
|
||||||
import cgi
|
import html
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
@ -318,7 +318,7 @@ class OpenLyrics(object):
|
|||||||
if 'lang' in verse[0]:
|
if 'lang' in verse[0]:
|
||||||
verse_element.set('lang', verse[0]['lang'])
|
verse_element.set('lang', verse[0]['lang'])
|
||||||
# Create a list with all "optional" verses.
|
# Create a list with all "optional" verses.
|
||||||
optional_verses = cgi.escape(verse[1])
|
optional_verses = html.escape(verse[1])
|
||||||
optional_verses = optional_verses.split('\n[---]\n')
|
optional_verses = optional_verses.split('\n[---]\n')
|
||||||
start_tags = ''
|
start_tags = ''
|
||||||
end_tags = ''
|
end_tags = ''
|
||||||
|
@ -35,6 +35,7 @@ log = logging.getLogger(__name__)
|
|||||||
__version__ = 4
|
__version__ = 4
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: When removing an upgrade path the ftw-data needs updating to the minimum supported version
|
||||||
def upgrade_1(session, metadata):
|
def upgrade_1(session, metadata):
|
||||||
"""
|
"""
|
||||||
Version 1 upgrade.
|
Version 1 upgrade.
|
||||||
@ -109,7 +110,7 @@ def upgrade_4(session, metadata):
|
|||||||
op.create_table('authors_songs_tmp',
|
op.create_table('authors_songs_tmp',
|
||||||
Column('author_id', types.Integer(), ForeignKey('authors.id'), primary_key=True),
|
Column('author_id', types.Integer(), ForeignKey('authors.id'), primary_key=True),
|
||||||
Column('song_id', types.Integer(), ForeignKey('songs.id'), primary_key=True),
|
Column('song_id', types.Integer(), ForeignKey('songs.id'), primary_key=True),
|
||||||
Column('author_type', types.String(), primary_key=True,
|
Column('author_type', types.Unicode(255), primary_key=True,
|
||||||
nullable=False, server_default=text('""')))
|
nullable=False, server_default=text('""')))
|
||||||
op.execute('INSERT INTO authors_songs_tmp SELECT author_id, song_id, "" FROM authors_songs')
|
op.execute('INSERT INTO authors_songs_tmp SELECT author_id, song_id, "" FROM authors_songs')
|
||||||
op.drop_table('authors_songs')
|
op.drop_table('authors_songs')
|
||||||
|
@ -50,6 +50,10 @@ from openlp.plugins.songs.lib.songstab import SongsTab
|
|||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
__default_settings__ = {
|
__default_settings__ = {
|
||||||
'songs/db type': 'sqlite',
|
'songs/db type': 'sqlite',
|
||||||
|
'songs/db username': '',
|
||||||
|
'songs/db password': '',
|
||||||
|
'songs/db hostname': '',
|
||||||
|
'songs/db database': '',
|
||||||
'songs/last search type': SongSearch.Entire,
|
'songs/last search type': SongSearch.Entire,
|
||||||
'songs/last import type': SongFormat.OpenLyrics,
|
'songs/last import type': SongFormat.OpenLyrics,
|
||||||
'songs/update service on edit': False,
|
'songs/update service on edit': False,
|
||||||
|
@ -27,6 +27,7 @@ from PyQt4 import QtGui
|
|||||||
from sqlalchemy.sql import and_
|
from sqlalchemy.sql import and_
|
||||||
|
|
||||||
from openlp.core.common import RegistryProperties, Settings, check_directory_exists, translate
|
from openlp.core.common import RegistryProperties, Settings, check_directory_exists, translate
|
||||||
|
from openlp.core.lib.ui import critical_error_message_box
|
||||||
from openlp.plugins.songusage.lib.db import SongUsageItem
|
from openlp.plugins.songusage.lib.db import SongUsageItem
|
||||||
from .songusagedetaildialog import Ui_SongUsageDetailDialog
|
from .songusagedetaildialog import Ui_SongUsageDetailDialog
|
||||||
|
|
||||||
@ -104,8 +105,11 @@ class SongUsageDetailForm(QtGui.QDialog, Ui_SongUsageDetailDialog, RegistryPrope
|
|||||||
translate('SongUsagePlugin.SongUsageDetailForm',
|
translate('SongUsagePlugin.SongUsageDetailForm',
|
||||||
'Report \n%s \nhas been successfully created. ') % report_file_name
|
'Report \n%s \nhas been successfully created. ') % report_file_name
|
||||||
)
|
)
|
||||||
except IOError:
|
except OSError as ose:
|
||||||
log.exception('Failed to write out song usage records')
|
log.exception('Failed to write out song usage records')
|
||||||
|
critical_error_message_box(translate('SongUsagePlugin.SongUsageDetailForm', 'Report Creation Failed'),
|
||||||
|
translate('SongUsagePlugin.SongUsageDetailForm',
|
||||||
|
'An error occurred while creating the report: %s') % ose.strerror)
|
||||||
finally:
|
finally:
|
||||||
if file_handle:
|
if file_handle:
|
||||||
file_handle.close()
|
file_handle.close()
|
||||||
|
@ -43,6 +43,10 @@ if QtCore.QDate().currentDate().month() < 9:
|
|||||||
|
|
||||||
__default_settings__ = {
|
__default_settings__ = {
|
||||||
'songusage/db type': 'sqlite',
|
'songusage/db type': 'sqlite',
|
||||||
|
'songusage/db username': '',
|
||||||
|
'songuasge/db password': '',
|
||||||
|
'songuasge/db hostname': '',
|
||||||
|
'songuasge/db database': '',
|
||||||
'songusage/active': False,
|
'songusage/active': False,
|
||||||
'songusage/to date': QtCore.QDate(YEAR, 8, 31),
|
'songusage/to date': QtCore.QDate(YEAR, 8, 31),
|
||||||
'songusage/from date': QtCore.QDate(YEAR - 1, 9, 1),
|
'songusage/from date': QtCore.QDate(YEAR - 1, 9, 1),
|
||||||
|
File diff suppressed because it is too large
Load Diff
9580
resources/i18n/ar.ts
Normal file
9580
resources/i18n/ar.ts
Normal file
File diff suppressed because it is too large
Load Diff
9580
resources/i18n/ar_EG.ts
Normal file
9580
resources/i18n/ar_EG.ts
Normal file
File diff suppressed because it is too large
Load Diff
9623
resources/i18n/bg.ts
Normal file
9623
resources/i18n/bg.ts
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1197
resources/i18n/da.ts
1197
resources/i18n/da.ts
File diff suppressed because it is too large
Load Diff
1081
resources/i18n/de.ts
1081
resources/i18n/de.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
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1353
resources/i18n/es.ts
1353
resources/i18n/es.ts
File diff suppressed because it is too large
Load Diff
9572
resources/i18n/es_CL.ts
Normal file
9572
resources/i18n/es_CL.ts
Normal file
File diff suppressed because it is too large
Load Diff
9572
resources/i18n/es_CO.ts
Normal file
9572
resources/i18n/es_CO.ts
Normal file
File diff suppressed because it is too large
Load Diff
1424
resources/i18n/et.ts
1424
resources/i18n/et.ts
File diff suppressed because it is too large
Load Diff
1304
resources/i18n/fi.ts
1304
resources/i18n/fi.ts
File diff suppressed because it is too large
Load Diff
1077
resources/i18n/fr.ts
1077
resources/i18n/fr.ts
File diff suppressed because it is too large
Load Diff
1095
resources/i18n/hu.ts
1095
resources/i18n/hu.ts
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
9588
resources/i18n/it.ts
Normal file
9588
resources/i18n/it.ts
Normal file
File diff suppressed because it is too large
Load Diff
1070
resources/i18n/ja.ts
1070
resources/i18n/ja.ts
File diff suppressed because it is too large
Load Diff
9580
resources/i18n/ko.ts
Normal file
9580
resources/i18n/ko.ts
Normal file
File diff suppressed because it is too large
Load Diff
9570
resources/i18n/ko_KR.ts
Normal file
9570
resources/i18n/ko_KR.ts
Normal file
File diff suppressed because it is too large
Load Diff
9767
resources/i18n/lt.ts
Normal file
9767
resources/i18n/lt.ts
Normal file
File diff suppressed because it is too large
Load Diff
9574
resources/i18n/lv.ts
Normal file
9574
resources/i18n/lv.ts
Normal file
File diff suppressed because it is too large
Load Diff
9587
resources/i18n/mk.ts
Normal file
9587
resources/i18n/mk.ts
Normal file
File diff suppressed because it is too large
Load Diff
9572
resources/i18n/ml.ts
Normal file
9572
resources/i18n/ml.ts
Normal file
File diff suppressed because it is too large
Load Diff
2037
resources/i18n/nb.ts
2037
resources/i18n/nb.ts
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
9574
resources/i18n/nn.ts
Normal file
9574
resources/i18n/nn.ts
Normal file
File diff suppressed because it is too large
Load Diff
9570
resources/i18n/pap.ts
Normal file
9570
resources/i18n/pap.ts
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user