forked from openlp/openlp
Merge with trunk.
This commit is contained in:
commit
869d1c6fd7
@ -1 +1 @@
|
||||
1.9.11
|
||||
1.9.12
|
||||
|
@ -35,6 +35,7 @@ logging and a plugin framework are contained within the openlp.core module.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import platform
|
||||
import logging
|
||||
from optparse import OptionParser
|
||||
from traceback import format_exception
|
||||
@ -102,8 +103,13 @@ class OpenLP(QtGui.QApplication):
|
||||
Run the OpenLP application.
|
||||
"""
|
||||
self.eventLoopIsActive = False
|
||||
# On Windows, the args passed into the constructor are
|
||||
# ignored. Not very handy, so set the ones we want to use.
|
||||
# On Windows, the args passed into the constructor are ignored. Not
|
||||
# very handy, so set the ones we want to use. On Linux and FreeBSD, in
|
||||
# order to set the WM_CLASS property for X11, we pass "OpenLP" in as a
|
||||
# command line argument. This interferes with files being passed in as
|
||||
# command line arguments, so we remove it from the list.
|
||||
if 'OpenLP' in args:
|
||||
args.remove('OpenLP')
|
||||
self.args.extend(args)
|
||||
# provide a listener for widgets to reqest a screen update.
|
||||
QtCore.QObject.connect(Receiver.get_receiver(),
|
||||
@ -266,6 +272,9 @@ def main(args=None):
|
||||
qt_args.extend(['-style', options.style])
|
||||
# Throw the rest of the arguments at Qt, just in case.
|
||||
qt_args.extend(args)
|
||||
# Bug #1018855: Set the WM_CLASS property in X11
|
||||
if platform.system() not in ['Windows', 'Darwin']:
|
||||
qt_args.append('OpenLP')
|
||||
# Initialise the resources
|
||||
qInitResources()
|
||||
# Now create and actually run the application.
|
||||
|
@ -256,7 +256,7 @@ class Renderer(object):
|
||||
if not self.force_page:
|
||||
self.display.buildHtml(serviceItem)
|
||||
raw_html = serviceItem.get_rendered_frame(0)
|
||||
self.display.text(raw_html)
|
||||
self.display.text(raw_html, False)
|
||||
preview = self.display.preview()
|
||||
return preview
|
||||
self.force_page = False
|
||||
@ -365,7 +365,7 @@ class Renderer(object):
|
||||
The theme information
|
||||
"""
|
||||
if not theme_data.font_main_override:
|
||||
return QtCore.QRect(10, 0, self.width, self.footer_start)
|
||||
return QtCore.QRect(10, 0, self.width - 20, self.footer_start)
|
||||
else:
|
||||
return QtCore.QRect(theme_data.font_main_x, theme_data.font_main_y,
|
||||
theme_data.font_main_width - 1, theme_data.font_main_height - 1)
|
||||
@ -406,7 +406,14 @@ class Renderer(object):
|
||||
if theme_data.font_main_shadow:
|
||||
self.page_width -= int(theme_data.font_main_shadow_size)
|
||||
self.page_height -= int(theme_data.font_main_shadow_size)
|
||||
# For the life of my I don't know why we have to completely kill the
|
||||
# QWebView in order for the display to work properly, but we do. See
|
||||
# bug #1041366 for an example of what happens if we take this out.
|
||||
self.web = None
|
||||
self.web = QtWebKit.QWebView()
|
||||
self.web.setVisible(False)
|
||||
self.web.resize(self.page_width, self.page_height)
|
||||
self.web_frame = self.web.page().mainFrame()
|
||||
# Adjust width and height to account for shadow. outline done in css.
|
||||
html = u"""<!DOCTYPE html><html><head><script>
|
||||
function show_text(newtext) {
|
||||
@ -450,8 +457,7 @@ class Renderer(object):
|
||||
previous_html, previous_raw, html_lines, lines, separator, u'')
|
||||
else:
|
||||
previous_raw = separator.join(lines)
|
||||
if previous_raw:
|
||||
formatted.append(previous_raw)
|
||||
formatted.append(previous_raw)
|
||||
log.debug(u'_paginate_slide - End')
|
||||
return formatted
|
||||
|
||||
|
@ -29,16 +29,16 @@
|
||||
The :mod:`advancedtab` provides an advanced settings facility.
|
||||
"""
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from PyQt4 import QtCore, QtGui
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
from PyQt4 import QtCore, QtGui
|
||||
|
||||
from openlp.core.lib import SettingsTab, translate, build_icon, Receiver
|
||||
from openlp.core.lib.settings import Settings
|
||||
from openlp.core.lib.ui import UiStrings
|
||||
from openlp.core.utils import get_images_filter, AppLocation
|
||||
from openlp.core.utils import get_images_filter, AppLocation, format_time
|
||||
from openlp.core.lib import SlideLimits
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@ -432,8 +432,7 @@ class AdvancedTab(SettingsTab):
|
||||
translate('OpenLP.AdvancedTab',
|
||||
'<strong>WARNING:</strong> New data directory location contains '
|
||||
'OpenLP data files. These files WILL be replaced during a copy.'))
|
||||
self.x11GroupBox.setTitle(translate('OpenLP.AdvancedTab',
|
||||
'X11'))
|
||||
self.x11GroupBox.setTitle(translate('OpenLP.AdvancedTab', 'X11'))
|
||||
self.x11BypassCheckBox.setText(translate('OpenLP.AdvancedTab',
|
||||
'Bypass X11 Window Manager'))
|
||||
# Slide Limits
|
||||
@ -493,8 +492,14 @@ class AdvancedTab(SettingsTab):
|
||||
QtCore.QVariant(True)).toBool()
|
||||
self.serviceNameCheckBox.setChecked(default_service_enabled)
|
||||
self.serviceNameCheckBoxToggled(default_service_enabled)
|
||||
self.x11BypassCheckBox.setChecked(
|
||||
settings.value(u'x11 bypass wm', QtCore.QVariant(True)).toBool())
|
||||
# Fix for bug #1014422.
|
||||
x11_bypass_default = True
|
||||
if sys.platform.startswith(u'linux'):
|
||||
# Default to False on Gnome.
|
||||
x11_bypass_default = bool(not
|
||||
os.environ.get(u'GNOME_DESKTOP_SESSION_ID'))
|
||||
self.x11BypassCheckBox.setChecked(settings.value(
|
||||
u'x11 bypass wm', QtCore.QVariant(x11_bypass_default)).toBool())
|
||||
self.defaultColor = settings.value(u'default color',
|
||||
QtCore.QVariant(u'#ffffff')).toString()
|
||||
self.defaultFileEdit.setText(settings.value(u'default image',
|
||||
@ -527,7 +532,7 @@ class AdvancedTab(SettingsTab):
|
||||
'Click "No" to stop loading OpenLP. allowing you to fix '
|
||||
'the the problem.\n\n'
|
||||
'Click "Yes" to reset the data directory to the default '
|
||||
'location.' % self.currentDataPath),
|
||||
'location.').replace('%s', self.currentDataPath),
|
||||
QtGui.QMessageBox.StandardButtons(
|
||||
QtGui.QMessageBox.Yes |
|
||||
QtGui.QMessageBox.No),
|
||||
@ -612,18 +617,18 @@ class AdvancedTab(SettingsTab):
|
||||
def generateServiceNameExample(self):
|
||||
preset_is_valid = True
|
||||
if self.serviceNameDay.currentIndex() == 7:
|
||||
time = datetime.now()
|
||||
local_time = datetime.now()
|
||||
else:
|
||||
now = datetime.now()
|
||||
day_delta = self.serviceNameDay.currentIndex() - now.weekday()
|
||||
if day_delta < 0:
|
||||
day_delta += 7
|
||||
time = now + timedelta(days=day_delta)
|
||||
time = time.replace(hour = self.serviceNameTime.time().hour(),
|
||||
local_time = time.replace(hour = self.serviceNameTime.time().hour(),
|
||||
minute = self.serviceNameTime.time().minute())
|
||||
try:
|
||||
service_name_example = time.strftime(unicode(
|
||||
self.serviceNameEdit.text()))
|
||||
service_name_example = format_time(unicode(
|
||||
self.serviceNameEdit.text()), local_time)
|
||||
except ValueError:
|
||||
preset_is_valid = False
|
||||
service_name_example = translate('OpenLP.AdvancedTab',
|
||||
@ -674,6 +679,7 @@ class AdvancedTab(SettingsTab):
|
||||
options = QtGui.QFileDialog.ShowDirsOnly))
|
||||
# Set the new data path.
|
||||
if new_data_path:
|
||||
new_data_path = os.path.normpath(new_data_path)
|
||||
if self.currentDataPath.lower() == new_data_path.lower():
|
||||
self.onDataDirectoryCancelButtonClicked()
|
||||
return
|
||||
@ -686,7 +692,7 @@ class AdvancedTab(SettingsTab):
|
||||
'Are you sure you want to change the location of the OpenLP '
|
||||
'data directory to:\n\n%s\n\n'
|
||||
'The data directory will be changed when OpenLP is closed.'
|
||||
% new_data_path),
|
||||
).replace('%s', new_data_path),
|
||||
QtGui.QMessageBox.StandardButtons(
|
||||
QtGui.QMessageBox.Yes |
|
||||
QtGui.QMessageBox.No),
|
||||
@ -750,7 +756,7 @@ class AdvancedTab(SettingsTab):
|
||||
'The location you have selected \n\n%s\n\n'
|
||||
'appears to contain OpenLP data files. Do you wish to replace '
|
||||
'these files with the current data files?'
|
||||
% os.path.abspath(data_path,)),
|
||||
).replace('%s', os.path.abspath(data_path,)),
|
||||
QtGui.QMessageBox.StandardButtons(
|
||||
QtGui.QMessageBox.Yes |
|
||||
QtGui.QMessageBox.No),
|
||||
@ -765,7 +771,7 @@ class AdvancedTab(SettingsTab):
|
||||
self.dataExists = False
|
||||
self.dataDirectoryCopyCheckBox.setChecked(True)
|
||||
self.newDataDirectoryHasFilesLabel.hide()
|
||||
|
||||
|
||||
def onDataDirectoryCancelButtonClicked(self):
|
||||
"""
|
||||
Cancel the data directory location change
|
||||
|
@ -177,8 +177,10 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
|
||||
return FirstTimePage.Progress
|
||||
elif self.currentId() == FirstTimePage.Themes:
|
||||
Receiver.send_message(u'cursor_busy')
|
||||
Receiver.send_message(u'openlp_process_events')
|
||||
while not self.themeScreenshotThread.isFinished():
|
||||
time.sleep(0.1)
|
||||
Receiver.send_message(u'openlp_process_events')
|
||||
# Build the screenshot icons, as this can not be done in the thread.
|
||||
self._buildThemeScreenshots()
|
||||
Receiver.send_message(u'cursor_normal')
|
||||
@ -188,10 +190,11 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
|
||||
|
||||
def onCurrentIdChanged(self, pageId):
|
||||
"""
|
||||
Detects Page changes and updates as approprate.
|
||||
Detects Page changes and updates as appropriate.
|
||||
"""
|
||||
# Keep track of the page we are at. Triggering "Cancel" causes pageId
|
||||
# to be a -1.
|
||||
Receiver.send_message(u'openlp_process_events')
|
||||
if pageId != -1:
|
||||
self.lastId = pageId
|
||||
if pageId == FirstTimePage.Plugins:
|
||||
@ -227,10 +230,12 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
|
||||
self.cancelButton.setVisible(False)
|
||||
elif pageId == FirstTimePage.Progress:
|
||||
Receiver.send_message(u'cursor_busy')
|
||||
self.repaint()
|
||||
Receiver.send_message(u'openlp_process_events')
|
||||
# Try to give the wizard a chance to redraw itself
|
||||
time.sleep(0.2)
|
||||
self._preWizard()
|
||||
Receiver.send_message(u'openlp_process_events')
|
||||
self._performWizard()
|
||||
Receiver.send_message(u'openlp_process_events')
|
||||
self._postWizard()
|
||||
Receiver.send_message(u'cursor_normal')
|
||||
Receiver.send_message(u'openlp_process_events')
|
||||
@ -263,8 +268,8 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
|
||||
"""
|
||||
Receiver.send_message(u'cursor_busy')
|
||||
self._performWizard()
|
||||
Receiver.send_message(u'openlp_process_events')
|
||||
Receiver.send_message(u'cursor_normal')
|
||||
Receiver.send_message(u'openlp_process_events')
|
||||
Settings().setValue(u'general/has run wizard',
|
||||
QtCore.QVariant(True))
|
||||
self.close()
|
||||
@ -344,6 +349,7 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
|
||||
Receiver.send_message(u'openlp_process_events')
|
||||
# Loop through the songs list and increase for each selected item
|
||||
for i in xrange(self.songsListWidget.count()):
|
||||
Receiver.send_message(u'openlp_process_events')
|
||||
item = self.songsListWidget.item(i)
|
||||
if item.checkState() == QtCore.Qt.Checked:
|
||||
filename = item.data(QtCore.Qt.UserRole).toString()
|
||||
@ -352,6 +358,7 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
|
||||
# Loop through the Bibles list and increase for each selected item
|
||||
iterator = QtGui.QTreeWidgetItemIterator(self.biblesTreeWidget)
|
||||
while iterator.value():
|
||||
Receiver.send_message(u'openlp_process_events')
|
||||
item = iterator.value()
|
||||
if item.parent() and item.checkState(0) == QtCore.Qt.Checked:
|
||||
filename = item.data(0, QtCore.Qt.UserRole).toString()
|
||||
@ -360,6 +367,7 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
|
||||
iterator += 1
|
||||
# Loop through the themes list and increase for each selected item
|
||||
for i in xrange(self.themesListWidget.count()):
|
||||
Receiver.send_message(u'openlp_process_events')
|
||||
item = self.themesListWidget.item(i)
|
||||
if item.checkState() == QtCore.Qt.Checked:
|
||||
filename = item.data(QtCore.Qt.UserRole).toString()
|
||||
@ -381,6 +389,10 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
|
||||
self.progressPage.setTitle(translate('OpenLP.FirstTimeWizard',
|
||||
'Setting Up'))
|
||||
self.progressPage.setSubTitle(u'Setup complete.')
|
||||
self.repaint()
|
||||
Receiver.send_message(u'openlp_process_events')
|
||||
# Try to give the wizard a chance to repaint itself
|
||||
time.sleep(0.1)
|
||||
|
||||
def _postWizard(self):
|
||||
"""
|
||||
|
@ -31,6 +31,7 @@ and play multimedia within OpenLP.
|
||||
"""
|
||||
import cgi
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
from PyQt4 import QtCore, QtGui, QtWebKit, QtOpenGL
|
||||
@ -135,14 +136,25 @@ class MainDisplay(Display):
|
||||
self.setStyleSheet(u'border: 0px; margin: 0px; padding: 0px;')
|
||||
windowFlags = QtCore.Qt.FramelessWindowHint | QtCore.Qt.Tool | \
|
||||
QtCore.Qt.WindowStaysOnTopHint
|
||||
# Fix for bug #1014422.
|
||||
x11_bypass_default = True
|
||||
if sys.platform.startswith(u'linux'):
|
||||
# Default to False on Gnome.
|
||||
x11_bypass_default = bool(not
|
||||
os.environ.get(u'GNOME_DESKTOP_SESSION_ID'))
|
||||
if Settings().value(u'advanced/x11 bypass wm',
|
||||
QtCore.QVariant(True)).toBool():
|
||||
QtCore.QVariant(x11_bypass_default)).toBool():
|
||||
windowFlags |= QtCore.Qt.X11BypassWindowManagerHint
|
||||
# FIXME: QtCore.Qt.SplashScreen is workaround to make display screen
|
||||
# stay always on top on Mac OS X. For details see bug 906926.
|
||||
# It needs more investigation to fix it properly.
|
||||
# TODO: The following combination of windowFlags works correctly
|
||||
# on Mac OS X. For next OpenLP version we should test it on other
|
||||
# platforms. For OpenLP 2.0 keep it only for OS X to not cause any
|
||||
# regressions on other platforms.
|
||||
if sys.platform == 'darwin':
|
||||
windowFlags |= QtCore.Qt.SplashScreen
|
||||
windowFlags = QtCore.Qt.FramelessWindowHint | QtCore.Qt.Window
|
||||
# For primary screen ensure it stays above the OS X dock
|
||||
# and menu bar
|
||||
if self.screens.current[u'primary']:
|
||||
self.setWindowState(QtCore.Qt.WindowFullScreen)
|
||||
self.setWindowFlags(windowFlags)
|
||||
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
|
||||
self.setTransparency(False)
|
||||
@ -224,20 +236,33 @@ class MainDisplay(Display):
|
||||
self.__hideMouse()
|
||||
log.debug(u'Finished MainDisplay setup')
|
||||
|
||||
def text(self, slide):
|
||||
def text(self, slide, animate=True):
|
||||
"""
|
||||
Add the slide text from slideController
|
||||
|
||||
``slide``
|
||||
The slide text to be displayed
|
||||
|
||||
``animate``
|
||||
Perform transitions if applicable when setting the text
|
||||
"""
|
||||
log.debug(u'text to display')
|
||||
# Wait for the webview to update before displaying text.
|
||||
while not self.webLoaded:
|
||||
Receiver.send_message(u'openlp_process_events')
|
||||
self.setGeometry(self.screen[u'size'])
|
||||
self.frame.evaluateJavaScript(u'show_text("%s")' %
|
||||
slide.replace(u'\\', u'\\\\').replace(u'\"', u'\\\"'))
|
||||
if animate:
|
||||
self.frame.evaluateJavaScript(u'show_text("%s")' %
|
||||
slide.replace(u'\\', u'\\\\').replace(u'\"', u'\\\"'))
|
||||
else:
|
||||
# This exists for https://bugs.launchpad.net/openlp/+bug/1016843
|
||||
# For unknown reasons if evaluateJavaScript is called
|
||||
# from the themewizard, then it causes a crash on
|
||||
# Windows if there are many items in the service to re-render.
|
||||
# Setting the div elements direct seems to solve the issue
|
||||
self.frame.findFirstElement("#lyricsmain").setInnerXml(slide)
|
||||
self.frame.findFirstElement("#lyricsoutline").setInnerXml(slide)
|
||||
self.frame.findFirstElement("#lyricsshadow").setInnerXml(slide)
|
||||
|
||||
def alert(self, text, location):
|
||||
"""
|
||||
|
@ -971,6 +971,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
|
||||
setting_sections.extend([self.themesSettingsSection])
|
||||
setting_sections.extend([self.displayTagsSection])
|
||||
setting_sections.extend([self.headerSection])
|
||||
setting_sections.extend([u'crashreport'])
|
||||
# Add plugin sections.
|
||||
for plugin in self.pluginManager.plugins:
|
||||
setting_sections.extend([plugin.name])
|
||||
@ -998,7 +999,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
|
||||
'settings file.\n\n'
|
||||
'Section [%s] is not valid \n\n'
|
||||
'Processing has terminated and no changed have been made.'
|
||||
% section),
|
||||
).replace('%s', section),
|
||||
QtGui.QMessageBox.StandardButtons(
|
||||
QtGui.QMessageBox.Ok))
|
||||
return
|
||||
@ -1517,7 +1518,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
|
||||
translate('OpenLP.MainWindow',
|
||||
'Copying OpenLP data to new data directory location - %s '
|
||||
'- Please wait for copy to finish'
|
||||
% self.newDataPath))
|
||||
).replace('%s', self.newDataPath))
|
||||
dir_util.copy_tree(old_data_path, self.newDataPath)
|
||||
log.info(u'Copy sucessful')
|
||||
except (IOError, os.error, DistutilsFileError), why:
|
||||
@ -1527,7 +1528,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
|
||||
translate('OpenLP.MainWindow', 'New Data Directory Error'),
|
||||
translate('OpenLP.MainWindow',
|
||||
'OpenLP Data directory copy failed\n\n%s'
|
||||
% unicode(why)),
|
||||
).replace('%s', unicode(why)),
|
||||
QtGui.QMessageBox.StandardButtons(
|
||||
QtGui.QMessageBox.Ok))
|
||||
return False
|
||||
|
@ -28,6 +28,7 @@
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from PyQt4 import QtCore, QtGui
|
||||
|
||||
from openlp.core.lib import OpenLPToolbar, Receiver, translate
|
||||
@ -105,8 +106,12 @@ class MediaController(object):
|
||||
AppLocation.get_directory(AppLocation.AppDir),
|
||||
u'core', u'ui', u'media')
|
||||
for filename in os.listdir(controller_dir):
|
||||
if filename.endswith(u'player.py') and not \
|
||||
filename == 'media_player.py':
|
||||
# TODO vlc backend is not yet working on Mac OS X.
|
||||
# For now just ignore vlc backend on Mac OS X.
|
||||
if sys.platform == 'darwin' and filename == 'vlcplayer.py':
|
||||
log.warn(u'Disabling vlc media player')
|
||||
continue
|
||||
if filename.endswith(u'player.py'):
|
||||
path = os.path.join(controller_dir, filename)
|
||||
if os.path.isfile(path):
|
||||
modulename = u'openlp.core.ui.media.' + \
|
||||
@ -114,7 +119,9 @@ class MediaController(object):
|
||||
log.debug(u'Importing controller %s', modulename)
|
||||
try:
|
||||
__import__(modulename, globals(), locals(), [])
|
||||
except ImportError:
|
||||
# On some platforms importing vlc.py might cause
|
||||
# also OSError exceptions. (e.g. Mac OS X)
|
||||
except (ImportError, OSError):
|
||||
log.warn(u'Failed to import %s on path %s',
|
||||
modulename, path)
|
||||
controller_classes = MediaPlayer.__subclasses__()
|
||||
@ -287,7 +294,7 @@ class MediaController(object):
|
||||
"""
|
||||
player.resize(display)
|
||||
|
||||
def video(self, controller, file, muted, isBackground):
|
||||
def video(self, controller, file, muted, isBackground, hidden=False):
|
||||
"""
|
||||
Loads and starts a video to run with the option of sound
|
||||
"""
|
||||
@ -333,11 +340,19 @@ class MediaController(object):
|
||||
if controller.isLive and not controller.media_info.is_background:
|
||||
display.frame.evaluateJavaScript(u'show_video( \
|
||||
"setBackBoard", null, null, null,"visible");')
|
||||
# now start playing
|
||||
if controller.isLive and \
|
||||
(Settings().value(u'general/auto unblank',
|
||||
QtCore.QVariant(False)).toBool() or \
|
||||
controller.media_info.is_background) or not controller.isLive:
|
||||
# now start playing - Preview is autoplay!
|
||||
autoplay = False
|
||||
# Preview requested
|
||||
if not controller.isLive:
|
||||
autoplay = True
|
||||
# Visible or background requested
|
||||
elif not hidden or controller.media_info.is_background:
|
||||
autoplay = True
|
||||
# Unblank on load set
|
||||
elif Settings().value(u'general/auto unblank',
|
||||
QtCore.QVariant(False)).toBool():
|
||||
autoplay = True
|
||||
if autoplay:
|
||||
if not self.video_play([controller]):
|
||||
critical_error_message_box(
|
||||
translate('MediaPlugin.MediaItem', 'Unsupported File'),
|
||||
|
@ -48,7 +48,48 @@ import sys
|
||||
from inspect import getargspec
|
||||
|
||||
__version__ = "N/A"
|
||||
build_date = "Thu Jun 14 15:22:46 2012"
|
||||
build_date = "Fri Oct 5 21:35:59 2012"
|
||||
|
||||
if sys.version_info[0] > 2:
|
||||
str = str
|
||||
unicode = str
|
||||
bytes = bytes
|
||||
basestring = (str, bytes)
|
||||
PYTHON3 = True
|
||||
def str_to_bytes(s):
|
||||
"""Translate string or bytes to bytes.
|
||||
"""
|
||||
if isinstance(s, str):
|
||||
return bytes(s, sys.getfilesystemencoding())
|
||||
else:
|
||||
return s
|
||||
|
||||
def bytes_to_str(b):
|
||||
"""Translate bytes to string.
|
||||
"""
|
||||
if isinstance(b, bytes):
|
||||
return b.decode(sys.getfilesystemencoding())
|
||||
else:
|
||||
return b
|
||||
else:
|
||||
str = str
|
||||
unicode = unicode
|
||||
bytes = str
|
||||
basestring = basestring
|
||||
PYTHON3 = False
|
||||
def str_to_bytes(s):
|
||||
"""Translate string or bytes to bytes.
|
||||
"""
|
||||
if isinstance(s, unicode):
|
||||
return s.encode(sys.getfilesystemencoding())
|
||||
else:
|
||||
return s
|
||||
|
||||
def bytes_to_str(b):
|
||||
"""Translate bytes to unicode string.
|
||||
"""
|
||||
if isinstance(b, str):
|
||||
return unicode(b, sys.getfilesystemencoding())
|
||||
|
||||
# Internal guard to prevent internal classes to be directly
|
||||
# instanciated.
|
||||
@ -220,7 +261,7 @@ def string_result(result, func, arguments):
|
||||
"""
|
||||
if result:
|
||||
# make a python string copy
|
||||
s = ctypes.string_at(result)
|
||||
s = bytes_to_str(ctypes.string_at(result))
|
||||
# free original string ptr
|
||||
libvlc_free(result)
|
||||
return s
|
||||
@ -241,16 +282,32 @@ class FILE(ctypes.Structure):
|
||||
pass
|
||||
FILE_ptr = ctypes.POINTER(FILE)
|
||||
|
||||
PyFile_FromFile = ctypes.pythonapi.PyFile_FromFile
|
||||
PyFile_FromFile.restype = ctypes.py_object
|
||||
PyFile_FromFile.argtypes = [FILE_ptr,
|
||||
ctypes.c_char_p,
|
||||
ctypes.c_char_p,
|
||||
ctypes.CFUNCTYPE(ctypes.c_int, FILE_ptr)]
|
||||
if PYTHON3:
|
||||
PyFile_FromFd = ctypes.pythonapi.PyFile_FromFd
|
||||
PyFile_FromFd.restype = ctypes.py_object
|
||||
PyFile_FromFd.argtypes = [ctypes.c_int,
|
||||
ctypes.c_char_p,
|
||||
ctypes.c_char_p,
|
||||
ctypes.c_int,
|
||||
ctypes.c_char_p,
|
||||
ctypes.c_char_p,
|
||||
ctypes.c_char_p,
|
||||
ctypes.c_int ]
|
||||
|
||||
PyFile_AsFile = ctypes.pythonapi.PyFile_AsFile
|
||||
PyFile_AsFile.restype = FILE_ptr
|
||||
PyFile_AsFile.argtypes = [ctypes.py_object]
|
||||
PyFile_AsFd = ctypes.pythonapi.PyObject_AsFileDescriptor
|
||||
PyFile_AsFd.restype = ctypes.c_int
|
||||
PyFile_AsFd.argtypes = [ctypes.py_object]
|
||||
else:
|
||||
PyFile_FromFile = ctypes.pythonapi.PyFile_FromFile
|
||||
PyFile_FromFile.restype = ctypes.py_object
|
||||
PyFile_FromFile.argtypes = [FILE_ptr,
|
||||
ctypes.c_char_p,
|
||||
ctypes.c_char_p,
|
||||
ctypes.CFUNCTYPE(ctypes.c_int, FILE_ptr)]
|
||||
|
||||
PyFile_AsFile = ctypes.pythonapi.PyFile_AsFile
|
||||
PyFile_AsFile.restype = FILE_ptr
|
||||
PyFile_AsFile.argtypes = [ctypes.py_object]
|
||||
|
||||
# Generated enum types #
|
||||
|
||||
@ -623,6 +680,19 @@ class LogCb(ctypes.c_void_p):
|
||||
\note Log message handlers <b>must</b> be thread-safe.
|
||||
"""
|
||||
pass
|
||||
class VideoLockCb(ctypes.c_void_p):
|
||||
"""Callback prototype to allocate and lock a picture buffer.
|
||||
Whenever a new video frame needs to be decoded, the lock callback is
|
||||
invoked. Depending on the video chroma, one or three pixel planes of
|
||||
adequate dimensions must be returned via the second parameter. Those
|
||||
planes must be aligned on 32-bytes boundaries.
|
||||
\param opaque private pointer as passed to L{libvlc_video_set_callbacks}() [IN]
|
||||
\param planes start address of the pixel planes (LibVLC allocates the array
|
||||
of void pointers, this callback must initialize the array) [OUT]
|
||||
\return a private pointer for the display and unlock callbacks to identify
|
||||
the picture buffers
|
||||
"""
|
||||
pass
|
||||
class VideoUnlockCb(ctypes.c_void_p):
|
||||
"""Callback prototype to unlock a picture buffer.
|
||||
When the video frame decoding is complete, the unlock callback is invoked.
|
||||
@ -630,7 +700,7 @@ This callback might not be needed at all. It is only an indication that the
|
||||
application can now read the pixel values if it needs to.
|
||||
\warning A picture buffer is unlocked after the picture is decoded,
|
||||
but before the picture is displayed.
|
||||
\param opaque private pointer as passed to libvlc_video_set_callbacks() [IN]
|
||||
\param opaque private pointer as passed to L{libvlc_video_set_callbacks}() [IN]
|
||||
\param picture private pointer returned from the @ref libvlc_video_lock_cb
|
||||
callback [IN]
|
||||
\param planes pixel planes as defined by the @ref libvlc_video_lock_cb
|
||||
@ -641,7 +711,7 @@ class VideoDisplayCb(ctypes.c_void_p):
|
||||
"""Callback prototype to display a picture.
|
||||
When the video frame needs to be shown, as determined by the media playback
|
||||
clock, the display callback is invoked.
|
||||
\param opaque private pointer as passed to libvlc_video_set_callbacks() [IN]
|
||||
\param opaque private pointer as passed to L{libvlc_video_set_callbacks}() [IN]
|
||||
\param picture private pointer returned from the @ref libvlc_video_lock_cb
|
||||
callback [IN]
|
||||
"""
|
||||
@ -653,7 +723,7 @@ and the chain of video filters (if any). It can opt to change any parameter
|
||||
as it needs. In that case, LibVLC will attempt to convert the video format
|
||||
(rescaling and chroma conversion) but these operations can be CPU intensive.
|
||||
\param opaque pointer to the private pointer passed to
|
||||
libvlc_video_set_callbacks() [IN/OUT]
|
||||
L{libvlc_video_set_callbacks}() [IN/OUT]
|
||||
\param chroma pointer to the 4 bytes video format identifier [IN/OUT]
|
||||
\param width pointer to the pixel width [IN/OUT]
|
||||
\param height pointer to the pixel height [IN/OUT]
|
||||
@ -673,7 +743,7 @@ in the video decoders, video filters and/or video converters.
|
||||
pass
|
||||
class VideoCleanupCb(ctypes.c_void_p):
|
||||
"""Callback prototype to configure picture buffers format.
|
||||
\param opaque private pointer as passed to libvlc_video_set_callbacks()
|
||||
\param opaque private pointer as passed to L{libvlc_video_set_callbacks}()
|
||||
(and possibly modified by @ref libvlc_video_format_cb) [IN]
|
||||
"""
|
||||
pass
|
||||
@ -749,6 +819,18 @@ class CallbackDecorators(object):
|
||||
\param args variable argument list for the format
|
||||
\note Log message handlers <b>must</b> be thread-safe.
|
||||
'''
|
||||
VideoLockCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ListPOINTER(ctypes.c_void_p))
|
||||
VideoLockCb.__doc__ = '''Callback prototype to allocate and lock a picture buffer.
|
||||
Whenever a new video frame needs to be decoded, the lock callback is
|
||||
invoked. Depending on the video chroma, one or three pixel planes of
|
||||
adequate dimensions must be returned via the second parameter. Those
|
||||
planes must be aligned on 32-bytes boundaries.
|
||||
\param opaque private pointer as passed to L{libvlc_video_set_callbacks}() [IN]
|
||||
\param planes start address of the pixel planes (LibVLC allocates the array
|
||||
of void pointers, this callback must initialize the array) [OUT]
|
||||
\return a private pointer for the display and unlock callbacks to identify
|
||||
the picture buffers
|
||||
'''
|
||||
VideoUnlockCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ListPOINTER(ctypes.c_void_p))
|
||||
VideoUnlockCb.__doc__ = '''Callback prototype to unlock a picture buffer.
|
||||
When the video frame decoding is complete, the unlock callback is invoked.
|
||||
@ -756,7 +838,7 @@ This callback might not be needed at all. It is only an indication that the
|
||||
application can now read the pixel values if it needs to.
|
||||
\warning A picture buffer is unlocked after the picture is decoded,
|
||||
but before the picture is displayed.
|
||||
\param opaque private pointer as passed to libvlc_video_set_callbacks() [IN]
|
||||
\param opaque private pointer as passed to L{libvlc_video_set_callbacks}() [IN]
|
||||
\param picture private pointer returned from the @ref libvlc_video_lock_cb
|
||||
callback [IN]
|
||||
\param planes pixel planes as defined by the @ref libvlc_video_lock_cb
|
||||
@ -766,7 +848,7 @@ but before the picture is displayed.
|
||||
VideoDisplayCb.__doc__ = '''Callback prototype to display a picture.
|
||||
When the video frame needs to be shown, as determined by the media playback
|
||||
clock, the display callback is invoked.
|
||||
\param opaque private pointer as passed to libvlc_video_set_callbacks() [IN]
|
||||
\param opaque private pointer as passed to L{libvlc_video_set_callbacks}() [IN]
|
||||
\param picture private pointer returned from the @ref libvlc_video_lock_cb
|
||||
callback [IN]
|
||||
'''
|
||||
@ -777,7 +859,7 @@ and the chain of video filters (if any). It can opt to change any parameter
|
||||
as it needs. In that case, LibVLC will attempt to convert the video format
|
||||
(rescaling and chroma conversion) but these operations can be CPU intensive.
|
||||
\param opaque pointer to the private pointer passed to
|
||||
libvlc_video_set_callbacks() [IN/OUT]
|
||||
L{libvlc_video_set_callbacks}() [IN/OUT]
|
||||
\param chroma pointer to the 4 bytes video format identifier [IN/OUT]
|
||||
\param width pointer to the pixel width [IN/OUT]
|
||||
\param height pointer to the pixel height [IN/OUT]
|
||||
@ -796,7 +878,7 @@ in the video decoders, video filters and/or video converters.
|
||||
'''
|
||||
VideoCleanupCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p)
|
||||
VideoCleanupCb.__doc__ = '''Callback prototype to configure picture buffers format.
|
||||
\param opaque private pointer as passed to libvlc_video_set_callbacks()
|
||||
\param opaque private pointer as passed to L{libvlc_video_set_callbacks}()
|
||||
(and possibly modified by @ref libvlc_video_format_cb) [IN]
|
||||
'''
|
||||
AudioPlayCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint, ctypes.c_int64)
|
||||
@ -1155,6 +1237,8 @@ class Instance(_Ctype):
|
||||
# no parameters passed, for win32 and MacOS,
|
||||
# specify the plugin_path if detected earlier
|
||||
args = ['vlc', '--plugin-path=' + plugin_path]
|
||||
if PYTHON3:
|
||||
args = [ str_to_bytes(a) for a in args ]
|
||||
return libvlc_new(len(args), args)
|
||||
|
||||
def media_player_new(self, uri=None):
|
||||
@ -1195,12 +1279,12 @@ class Instance(_Ctype):
|
||||
"""
|
||||
if ':' in mrl and mrl.index(':') > 1:
|
||||
# Assume it is a URL
|
||||
m = libvlc_media_new_location(self, mrl)
|
||||
m = libvlc_media_new_location(self, str_to_bytes(mrl))
|
||||
else:
|
||||
# Else it should be a local path.
|
||||
m = libvlc_media_new_path(self, mrl)
|
||||
m = libvlc_media_new_path(self, str_to_bytes(mrl))
|
||||
for o in options:
|
||||
libvlc_media_add_option(m, o)
|
||||
libvlc_media_add_option(m, str_to_bytes(o))
|
||||
m._instance = self
|
||||
return m
|
||||
|
||||
@ -1664,6 +1748,9 @@ class LogIterator(_Ctype):
|
||||
return i.contents
|
||||
raise StopIteration
|
||||
|
||||
def __next__(self):
|
||||
return self.next()
|
||||
|
||||
|
||||
def free(self):
|
||||
'''Frees memory allocated by L{log_get_iterator}().
|
||||
@ -2421,9 +2508,22 @@ class MediaPlayer(_Ctype):
|
||||
'''
|
||||
return libvlc_media_player_stop(self)
|
||||
|
||||
def video_set_callbacks(self, lock, unlock, display, opaque):
|
||||
'''Set callbacks and private data to render decoded video to a custom area
|
||||
in memory.
|
||||
Use L{video_set_format}() or L{video_set_format_callbacks}()
|
||||
to configure the decoded format.
|
||||
@param lock: callback to lock video memory (must not be NULL).
|
||||
@param unlock: callback to unlock video memory (or NULL if not needed).
|
||||
@param display: callback to display video (or NULL if not needed).
|
||||
@param opaque: private pointer for the three callbacks (as first parameter).
|
||||
@version: LibVLC 1.1.1 or later.
|
||||
'''
|
||||
return libvlc_video_set_callbacks(self, lock, unlock, display, opaque)
|
||||
|
||||
def video_set_format(self, chroma, width, height, pitch):
|
||||
'''Set decoded video chroma and dimensions.
|
||||
This only works in combination with libvlc_video_set_callbacks(),
|
||||
This only works in combination with L{video_set_callbacks}(),
|
||||
and is mutually exclusive with L{video_set_format_callbacks}().
|
||||
@param chroma: a four-characters string identifying the chroma (e.g. "RV32" or "YUYV").
|
||||
@param width: pixel width.
|
||||
@ -2436,7 +2536,7 @@ class MediaPlayer(_Ctype):
|
||||
|
||||
def video_set_format_callbacks(self, setup, cleanup):
|
||||
'''Set decoded video chroma and dimensions. This only works in combination with
|
||||
libvlc_video_set_callbacks().
|
||||
L{video_set_callbacks}().
|
||||
@param setup: callback to select the video format (cannot be NULL).
|
||||
@param cleanup: callback to release any allocated resources (or NULL).
|
||||
@version: LibVLC 2.0.0 or later.
|
||||
@ -3125,18 +3225,6 @@ def libvlc_vprinterr(fmt, ap):
|
||||
ctypes.c_char_p, ctypes.c_char_p, ctypes.c_void_p)
|
||||
return f(fmt, ap)
|
||||
|
||||
def libvlc_printerr(fmt, args):
|
||||
'''Sets the LibVLC error status and message for the current thread.
|
||||
Any previous error is overridden.
|
||||
@param fmt: the format string.
|
||||
@param args: the arguments.
|
||||
@return: a nul terminated string in any case.
|
||||
'''
|
||||
f = _Cfunctions.get('libvlc_printerr', None) or \
|
||||
_Cfunction('libvlc_printerr', ((1,), (1,),), None,
|
||||
ctypes.c_char_p, ctypes.c_char_p, ctypes.c_void_p)
|
||||
return f(fmt, args)
|
||||
|
||||
def libvlc_new(argc, argv):
|
||||
'''Create and initialize a libvlc instance.
|
||||
This functions accept a list of "command line" arguments similar to the
|
||||
@ -4332,9 +4420,26 @@ def libvlc_media_player_stop(p_mi):
|
||||
None, MediaPlayer)
|
||||
return f(p_mi)
|
||||
|
||||
def libvlc_video_set_callbacks(mp, lock, unlock, display, opaque):
|
||||
'''Set callbacks and private data to render decoded video to a custom area
|
||||
in memory.
|
||||
Use L{libvlc_video_set_format}() or L{libvlc_video_set_format_callbacks}()
|
||||
to configure the decoded format.
|
||||
@param mp: the media player.
|
||||
@param lock: callback to lock video memory (must not be NULL).
|
||||
@param unlock: callback to unlock video memory (or NULL if not needed).
|
||||
@param display: callback to display video (or NULL if not needed).
|
||||
@param opaque: private pointer for the three callbacks (as first parameter).
|
||||
@version: LibVLC 1.1.1 or later.
|
||||
'''
|
||||
f = _Cfunctions.get('libvlc_video_set_callbacks', None) or \
|
||||
_Cfunction('libvlc_video_set_callbacks', ((1,), (1,), (1,), (1,), (1,),), None,
|
||||
None, MediaPlayer, VideoLockCb, VideoUnlockCb, VideoDisplayCb, ctypes.c_void_p)
|
||||
return f(mp, lock, unlock, display, opaque)
|
||||
|
||||
def libvlc_video_set_format(mp, chroma, width, height, pitch):
|
||||
'''Set decoded video chroma and dimensions.
|
||||
This only works in combination with libvlc_video_set_callbacks(),
|
||||
This only works in combination with L{libvlc_video_set_callbacks}(),
|
||||
and is mutually exclusive with L{libvlc_video_set_format_callbacks}().
|
||||
@param mp: the media player.
|
||||
@param chroma: a four-characters string identifying the chroma (e.g. "RV32" or "YUYV").
|
||||
@ -4351,7 +4456,7 @@ def libvlc_video_set_format(mp, chroma, width, height, pitch):
|
||||
|
||||
def libvlc_video_set_format_callbacks(mp, setup, cleanup):
|
||||
'''Set decoded video chroma and dimensions. This only works in combination with
|
||||
libvlc_video_set_callbacks().
|
||||
L{libvlc_video_set_callbacks}().
|
||||
@param mp: the media player.
|
||||
@param setup: callback to select the video format (cannot be NULL).
|
||||
@param cleanup: callback to release any allocated resources (or NULL).
|
||||
@ -5829,10 +5934,10 @@ def libvlc_vlm_get_event_manager(p_instance):
|
||||
|
||||
|
||||
# 2 function(s) blacklisted:
|
||||
# libvlc_printerr
|
||||
# libvlc_set_exit_handler
|
||||
# libvlc_video_set_callbacks
|
||||
|
||||
# 18 function(s) not wrapped as methods:
|
||||
# 17 function(s) not wrapped as methods:
|
||||
# libvlc_audio_output_list_release
|
||||
# libvlc_clearerr
|
||||
# libvlc_clock
|
||||
@ -5847,7 +5952,6 @@ def libvlc_vlm_get_event_manager(p_instance):
|
||||
# libvlc_log_unsubscribe
|
||||
# libvlc_module_description_list_release
|
||||
# libvlc_new
|
||||
# libvlc_printerr
|
||||
# libvlc_track_description_list_release
|
||||
# libvlc_track_description_release
|
||||
# libvlc_vprinterr
|
||||
@ -5908,7 +6012,7 @@ def libvlc_hex_version():
|
||||
"""Return the libvlc version in hex or 0 if unavailable.
|
||||
"""
|
||||
try:
|
||||
return _dot2int(libvlc_get_version().split()[0])
|
||||
return _dot2int(bytes_to_str(libvlc_get_version()).split()[0])
|
||||
except ValueError:
|
||||
return 0
|
||||
|
||||
@ -5957,8 +6061,8 @@ if __name__ == '__main__':
|
||||
"""Print libvlc version"""
|
||||
try:
|
||||
print('Build date: %s (%#x)' % (build_date, hex_version()))
|
||||
print('LibVLC version: %s (%#x)' % (libvlc_get_version(), libvlc_hex_version()))
|
||||
print('LibVLC compiler: %s' % libvlc_get_compiler())
|
||||
print('LibVLC version: %s (%#x)' % (bytes_to_str(libvlc_get_version()), libvlc_hex_version()))
|
||||
print('LibVLC compiler: %s' % bytes_to_str(libvlc_get_compiler()))
|
||||
if plugin_path:
|
||||
print('Plugin path: %s' % plugin_path)
|
||||
except:
|
||||
@ -5997,7 +6101,7 @@ if __name__ == '__main__':
|
||||
player.video_set_marquee_int(VideoMarqueeOption.Refresh, 1000) # millisec (or sec?)
|
||||
##t = '$L / $D or $P at $T'
|
||||
t = '%Y-%m-%d %H:%M:%S'
|
||||
player.video_set_marquee_string(VideoMarqueeOption.Text, t)
|
||||
player.video_set_marquee_string(VideoMarqueeOption.Text, str_to_bytes(t))
|
||||
|
||||
# Some event manager examples. Note, the callback can be any Python
|
||||
# callable and does not need to be decorated. Optionally, specify
|
||||
@ -6017,7 +6121,7 @@ if __name__ == '__main__':
|
||||
print_version()
|
||||
media = player.get_media()
|
||||
print('State: %s' % player.get_state())
|
||||
print('Media: %s' % media.get_mrl())
|
||||
print('Media: %s' % bytes_to_str(media.get_mrl()))
|
||||
print('Track: %s/%s' % (player.video_get_track(), player.video_get_track_count()))
|
||||
print('Current time: %s/%s' % (player.get_time(), media.get_duration()))
|
||||
print('Position: %s' % player.get_position())
|
||||
@ -6078,7 +6182,7 @@ if __name__ == '__main__':
|
||||
|
||||
print('Press q to quit, ? to get help.%s' % os.linesep)
|
||||
while True:
|
||||
k = getch().decode('utf8') # Python 3+
|
||||
k = getch()
|
||||
print('> %s' % k)
|
||||
if k in keybindings:
|
||||
keybindings[k]()
|
||||
|
@ -63,37 +63,32 @@ if VLC_AVAILABLE:
|
||||
VLC_AVAILABLE = False
|
||||
log.debug(u'VLC could not be loaded: %s' % version)
|
||||
|
||||
AUDIO_EXT = [
|
||||
u'*.mp3'
|
||||
, u'*.wav'
|
||||
, u'*.wma'
|
||||
, u'*.ogg'
|
||||
]
|
||||
AUDIO_EXT = [u'*.mp3', u'*.wav', u'*.wma', u'*.ogg']
|
||||
|
||||
VIDEO_EXT = [
|
||||
u'*.3gp'
|
||||
, u'*.asf', u'*.wmv'
|
||||
, u'*.au'
|
||||
, u'*.avi'
|
||||
, u'*.flv'
|
||||
, u'*.mov'
|
||||
, u'*.mp4'
|
||||
, u'*.ogm'
|
||||
, u'*.mkv', u'*.mka'
|
||||
, u'*.ts', u'*.mpg'
|
||||
, u'*.mpg', u'*.mp2'
|
||||
, u'*.nsc'
|
||||
, u'*.nsv'
|
||||
, u'*.nut'
|
||||
, u'*.ra', u'*.ram', u'*.rm', u'*.rv' ,u'*.rmbv'
|
||||
, u'*.a52', u'*.dts', u'*.aac', u'*.flac' ,u'*.dv', u'*.vid'
|
||||
, u'*.tta', u'*.tac'
|
||||
, u'*.ty'
|
||||
, u'*.dts'
|
||||
, u'*.xa'
|
||||
, u'*.iso'
|
||||
, u'*.vob'
|
||||
]
|
||||
u'*.3gp',
|
||||
u'*.asf', u'*.wmv',
|
||||
u'*.au',
|
||||
u'*.avi',
|
||||
u'*.flv',
|
||||
u'*.mov',
|
||||
u'*.mp4',
|
||||
u'*.ogm', u'*.ogv',
|
||||
u'*.mkv', u'*.mka',
|
||||
u'*.ts', u'*.mpg',
|
||||
u'*.mpg', u'*.mp2',
|
||||
u'*.nsc',
|
||||
u'*.nsv',
|
||||
u'*.nut',
|
||||
u'*.ra', u'*.ram', u'*.rm', u'*.rv' ,u'*.rmbv',
|
||||
u'*.a52', u'*.dts', u'*.aac', u'*.flac' ,u'*.dv', u'*.vid',
|
||||
u'*.tta', u'*.tac',
|
||||
u'*.ty',
|
||||
u'*.dts',
|
||||
u'*.xa',
|
||||
u'*.iso',
|
||||
u'*.vob'
|
||||
]
|
||||
|
||||
|
||||
class VlcPlayer(MediaPlayer):
|
||||
|
@ -47,7 +47,8 @@ from openlp.core.lib.ui import UiStrings, critical_error_message_box, \
|
||||
create_widget_action, find_and_set_in_combo_box
|
||||
from openlp.core.ui import ServiceNoteForm, ServiceItemEditForm, StartTimeForm
|
||||
from openlp.core.ui.printserviceform import PrintServiceForm
|
||||
from openlp.core.utils import AppLocation, delete_file, split_filename
|
||||
from openlp.core.utils import AppLocation, delete_file, split_filename, \
|
||||
format_time
|
||||
from openlp.core.utils.actions import ActionList, CategoryOrder
|
||||
|
||||
class ServiceManagerList(QtGui.QTreeWidget):
|
||||
@ -379,6 +380,12 @@ class ServiceManager(QtGui.QWidget):
|
||||
QtCore.QVariant(u'False')).toBool()
|
||||
|
||||
def supportedSuffixes(self, suffix):
|
||||
"""
|
||||
Adds Suffixes supported to the master list. Called from Plugins.
|
||||
|
||||
``suffix``
|
||||
New Suffix to be supported
|
||||
"""
|
||||
self.suffixes.append(suffix)
|
||||
|
||||
def onNewServiceClicked(self):
|
||||
@ -513,7 +520,7 @@ class ServiceManager(QtGui.QWidget):
|
||||
'Service File Missing'))
|
||||
message = unicode(translate('OpenLP.ServiceManager',
|
||||
'File missing from service\n\n %s \n\n'
|
||||
'Continue saving?' % path_from ))
|
||||
'Continue saving?')) % path_from
|
||||
answer = QtGui.QMessageBox.critical(self, title,
|
||||
message,
|
||||
QtGui.QMessageBox.StandardButtons(
|
||||
@ -596,7 +603,7 @@ class ServiceManager(QtGui.QWidget):
|
||||
service_day = Settings().value(
|
||||
u'advanced/default service day', 7).toInt()[0]
|
||||
if service_day == 7:
|
||||
time = datetime.now()
|
||||
local_time = datetime.now()
|
||||
else:
|
||||
service_hour = Settings().value(
|
||||
u'advanced/default service hour', 11).toInt()[0]
|
||||
@ -607,7 +614,7 @@ class ServiceManager(QtGui.QWidget):
|
||||
if day_delta < 0:
|
||||
day_delta += 7
|
||||
time = now + timedelta(days=day_delta)
|
||||
time = time.replace(hour=service_hour, minute=service_minute)
|
||||
local_time = time.replace(hour=service_hour, minute=service_minute)
|
||||
default_pattern = unicode(Settings().value(
|
||||
u'advanced/default service name',
|
||||
translate('OpenLP.AdvancedTab', 'Service %Y-%m-%d %H-%M',
|
||||
@ -615,7 +622,7 @@ class ServiceManager(QtGui.QWidget):
|
||||
'/\\?*|<>\[\]":+\nSee http://docs.python.org/library/'
|
||||
'datetime.html#strftime-strptime-behavior for more '
|
||||
'information.')).toString())
|
||||
default_filename = time.strftime(default_pattern)
|
||||
default_filename = format_time(default_pattern, local_time)
|
||||
else:
|
||||
default_filename = u''
|
||||
directory = unicode(SettingsManager.get_last_dir(
|
||||
@ -795,6 +802,10 @@ class ServiceManager(QtGui.QWidget):
|
||||
self.repaintServiceList(item, -1)
|
||||
|
||||
def onServiceItemEditForm(self):
|
||||
"""
|
||||
Opens a dialog to edit the service item and update the service
|
||||
display if changes are saved.
|
||||
"""
|
||||
item = self.findServiceItem()[0]
|
||||
self.serviceItemEditForm.setServiceItem(
|
||||
self.serviceItems[item][u'service_item'])
|
||||
@ -805,7 +816,7 @@ class ServiceManager(QtGui.QWidget):
|
||||
def previewLive(self, message):
|
||||
"""
|
||||
Called by the SlideController to request a preview item be made live
|
||||
and allows the next preview to be updated if relevent.
|
||||
and allows the next preview to be updated if relevant.
|
||||
"""
|
||||
uuid, row = message.split(u':')
|
||||
for sitem in self.serviceItems:
|
||||
@ -1082,12 +1093,12 @@ class ServiceManager(QtGui.QWidget):
|
||||
"""
|
||||
if serviceItem.is_command():
|
||||
type = serviceItem._raw_frames[0][u'title'].split(u'.')[-1]
|
||||
if type not in self.suffixes:
|
||||
if type.lower() not in self.suffixes:
|
||||
serviceItem.is_valid = False
|
||||
|
||||
def cleanUp(self):
|
||||
"""
|
||||
Empties the servicePath of temporary files.
|
||||
Empties the servicePath of temporary files on system exit.
|
||||
"""
|
||||
log.debug(u'Cleaning up servicePath')
|
||||
for file in os.listdir(self.servicePath):
|
||||
|
@ -286,19 +286,20 @@ class SlideController(Controller):
|
||||
text=translate('OpenLP.SlideController', 'Pause Audio'),
|
||||
tooltip=translate('OpenLP.SlideController', 'Pause audio.'),
|
||||
checked=False, visible=False, category=self.category,
|
||||
triggers=self.onAudioPauseClicked)
|
||||
context=QtCore.Qt.WindowShortcut,
|
||||
shortcuts=[], triggers=self.onAudioPauseClicked)
|
||||
self.audioMenu = QtGui.QMenu(
|
||||
translate('OpenLP.SlideController', 'Background Audio'), self)
|
||||
translate('OpenLP.SlideController', 'Background Audio'), self.toolbar)
|
||||
self.audioPauseItem.setMenu(self.audioMenu)
|
||||
self.audioPauseItem.setParent(self)
|
||||
self.audioPauseItem.setParent(self.toolbar)
|
||||
self.toolbar.widgetForAction(self.audioPauseItem).setPopupMode(
|
||||
QtGui.QToolButton.MenuButtonPopup)
|
||||
self.nextTrackItem = create_action(self, u'nextTrackItem',
|
||||
text=UiStrings().NextTrack,
|
||||
icon=u':/slides/media_playback_next.png', tooltip=translate(
|
||||
'OpenLP.SlideController', 'Go to next audio track.'),
|
||||
category=self.category, context=QtCore.Qt.WindowShortcut,
|
||||
triggers=self.onNextTrackClicked)
|
||||
category=self.category,
|
||||
shortcuts=[], triggers=self.onNextTrackClicked)
|
||||
self.audioMenu.addAction(self.nextTrackItem)
|
||||
self.trackMenu = self.audioMenu.addMenu(
|
||||
translate('OpenLP.SlideController', 'Tracks'))
|
||||
@ -854,7 +855,11 @@ class SlideController(Controller):
|
||||
else:
|
||||
label = QtGui.QLabel()
|
||||
label.setMargin(4)
|
||||
label.setScaledContents(True)
|
||||
if serviceItem.is_media():
|
||||
label.setAlignment(QtCore.Qt.AlignHCenter |
|
||||
QtCore.Qt.AlignVCenter)
|
||||
else:
|
||||
label.setScaledContents(True)
|
||||
if self.serviceItem.is_command():
|
||||
label.setPixmap(QtGui.QPixmap(frame[u'image']))
|
||||
else:
|
||||
@ -1346,7 +1351,7 @@ class SlideController(Controller):
|
||||
"""
|
||||
log.debug(u'SlideController onMediaStart')
|
||||
file = os.path.join(item.get_frame_path(), item.get_frame_title())
|
||||
self.mediaController.video(self, file, False, False)
|
||||
self.mediaController.video(self, file, False, False, self.hideMode())
|
||||
if not self.isLive or self.mediaController.withLivePreview:
|
||||
self.previewDisplay.show()
|
||||
self.slidePreview.hide()
|
||||
|
@ -157,7 +157,8 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard):
|
||||
u'outlineColorButton', self.outlineColorButton)
|
||||
self.mainAreaPage.registerField(
|
||||
u'outlineSizeSpinBox', self.outlineSizeSpinBox)
|
||||
self.mainAreaPage.registerField(u'shadowCheckBox', self.shadowCheckBox)
|
||||
self.mainAreaPage.registerField(
|
||||
u'shadowCheckBox', self.shadowCheckBox)
|
||||
self.mainAreaPage.registerField(
|
||||
u'mainBoldCheckBox', self.mainBoldCheckBox)
|
||||
self.mainAreaPage.registerField(
|
||||
@ -168,8 +169,10 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard):
|
||||
u'shadowSizeSpinBox', self.shadowSizeSpinBox)
|
||||
self.mainAreaPage.registerField(
|
||||
u'footerSizeSpinBox', self.footerSizeSpinBox)
|
||||
self.areaPositionPage.registerField(u'mainPositionX', self.mainXSpinBox)
|
||||
self.areaPositionPage.registerField(u'mainPositionY', self.mainYSpinBox)
|
||||
self.areaPositionPage.registerField(
|
||||
u'mainPositionX', self.mainXSpinBox)
|
||||
self.areaPositionPage.registerField(
|
||||
u'mainPositionY', self.mainYSpinBox)
|
||||
self.areaPositionPage.registerField(
|
||||
u'mainPositionWidth', self.mainWidthSpinBox)
|
||||
self.areaPositionPage.registerField(
|
||||
@ -202,7 +205,8 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard):
|
||||
"""
|
||||
Updates the lines on a page on the wizard
|
||||
"""
|
||||
self.mainLineCountLabel.setText(unicode(translate('OpenLP.ThemeForm',
|
||||
self.mainLineCountLabel.setText(unicode(translate(
|
||||
'OpenLP.ThemeWizard',
|
||||
'(approximately %d lines per slide)')) % int(lines))
|
||||
|
||||
def resizeEvent(self, event=None):
|
||||
@ -224,6 +228,19 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard):
|
||||
self.previewBoxLabel.setFixedSize(pixmapWidth + 2 * frameWidth,
|
||||
pixmapHeight + 2 * frameWidth)
|
||||
|
||||
def validateCurrentPage(self):
|
||||
background_image = BackgroundType.to_string(BackgroundType.Image)
|
||||
if self.page(self.currentId()) == self.backgroundPage and \
|
||||
self.theme.background_type == background_image and \
|
||||
self.imageFileEdit.text().isEmpty():
|
||||
QtGui.QMessageBox.critical(self,
|
||||
translate('OpenLP.ThemeWizard', 'Background Image Empty'),
|
||||
translate('OpenLP.ThemeWizard', 'You have not selected a '
|
||||
'background image. Please select one before continuing.'))
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def onCurrentIdChanged(self, pageId):
|
||||
"""
|
||||
Detects Page changes and updates as appropriate.
|
||||
@ -521,7 +538,7 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard):
|
||||
images_filter = u'%s;;%s (*.*) (*)' % (
|
||||
images_filter, UiStrings().AllFiles)
|
||||
filename = QtGui.QFileDialog.getOpenFileName(self,
|
||||
translate('OpenLP.ThemeForm', 'Select Image'), u'',
|
||||
translate('OpenLP.ThemeWizard', 'Select Image'), u'',
|
||||
images_filter)
|
||||
if filename:
|
||||
self.theme.background_filename = unicode(filename)
|
||||
@ -603,14 +620,14 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard):
|
||||
self.theme.theme_name = unicode(self.field(u'name').toString())
|
||||
if not self.theme.theme_name:
|
||||
critical_error_message_box(
|
||||
translate('OpenLP.ThemeForm', 'Theme Name Missing'),
|
||||
translate('OpenLP.ThemeForm',
|
||||
translate('OpenLP.ThemeWizard', 'Theme Name Missing'),
|
||||
translate('OpenLP.ThemeWizard',
|
||||
'There is no name for this theme. Please enter one.'))
|
||||
return
|
||||
if self.theme.theme_name == u'-1' or self.theme.theme_name == u'None':
|
||||
critical_error_message_box(
|
||||
translate('OpenLP.ThemeForm', 'Theme Name Invalid'),
|
||||
translate('OpenLP.ThemeForm',
|
||||
translate('OpenLP.ThemeWizard', 'Theme Name Invalid'),
|
||||
translate('OpenLP.ThemeWizard',
|
||||
'Invalid theme name. Please enter one.'))
|
||||
return
|
||||
saveFrom = None
|
||||
|
@ -30,7 +30,6 @@ import os
|
||||
import zipfile
|
||||
import shutil
|
||||
import logging
|
||||
import locale
|
||||
import re
|
||||
|
||||
from xml.etree.ElementTree import ElementTree, XML
|
||||
@ -46,7 +45,8 @@ from openlp.core.lib.ui import UiStrings, critical_error_message_box, \
|
||||
create_widget_action
|
||||
from openlp.core.theme import Theme
|
||||
from openlp.core.ui import FileRenameForm, ThemeForm
|
||||
from openlp.core.utils import AppLocation, delete_file, get_filesystem_encoding
|
||||
from openlp.core.utils import AppLocation, delete_file, locale_compare, \
|
||||
get_filesystem_encoding
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@ -257,6 +257,7 @@ class ThemeManager(QtGui.QWidget):
|
||||
theme.set_default_header_footer()
|
||||
self.themeForm.theme = theme
|
||||
self.themeForm.exec_()
|
||||
self.loadThemes()
|
||||
|
||||
def onRenameTheme(self):
|
||||
"""
|
||||
@ -281,7 +282,6 @@ class ThemeManager(QtGui.QWidget):
|
||||
for plugin in self.mainwindow.pluginManager.plugins:
|
||||
if plugin.usesTheme(old_theme_name):
|
||||
plugin.renameTheme(old_theme_name, new_theme_name)
|
||||
self.loadThemes()
|
||||
self.mainwindow.renderer.update_theme(
|
||||
new_theme_name, old_theme_name)
|
||||
|
||||
@ -314,6 +314,7 @@ class ThemeManager(QtGui.QWidget):
|
||||
theme_data.theme_name = new_theme_name
|
||||
theme_data.extend_image_filename(self.path)
|
||||
self.saveTheme(theme_data, save_from, save_to)
|
||||
self.loadThemes()
|
||||
|
||||
def onEditTheme(self):
|
||||
"""
|
||||
@ -331,6 +332,7 @@ class ThemeManager(QtGui.QWidget):
|
||||
self.themeForm.exec_(True)
|
||||
self.oldBackgroundImage = None
|
||||
self.mainwindow.renderer.update_theme(theme.theme_name)
|
||||
self.loadThemes()
|
||||
|
||||
def onDeleteTheme(self):
|
||||
"""
|
||||
@ -345,10 +347,10 @@ class ThemeManager(QtGui.QWidget):
|
||||
row = self.themeListWidget.row(item)
|
||||
self.themeListWidget.takeItem(row)
|
||||
self.deleteTheme(theme)
|
||||
self.mainwindow.renderer.update_theme(theme, only_delete=True)
|
||||
# As we do not reload the themes, push out the change. Reload the
|
||||
# list as the internal lists and events need to be triggered.
|
||||
self._pushThemes()
|
||||
self.mainwindow.renderer.update_theme(theme, only_delete=True)
|
||||
|
||||
def deleteTheme(self, theme):
|
||||
"""
|
||||
@ -455,9 +457,8 @@ class ThemeManager(QtGui.QWidget):
|
||||
self.configUpdated()
|
||||
files = SettingsManager.get_files(self.settingsSection, u'.png')
|
||||
# Sort the themes by its name considering language specific characters.
|
||||
# lower() is needed for windows!
|
||||
files.sort(key=lambda file_name: unicode(file_name).lower(),
|
||||
cmp=locale.strcoll)
|
||||
files.sort(key=lambda file_name: unicode(file_name),
|
||||
cmp=locale_compare)
|
||||
# now process the file list of png files
|
||||
for name in files:
|
||||
# check to see file is in theme root directory
|
||||
@ -516,7 +517,7 @@ class ThemeManager(QtGui.QWidget):
|
||||
translate('OpenLP.ThemeManager', 'Theme Already Exists'),
|
||||
translate('OpenLP.ThemeManager',
|
||||
'Theme %s already exists. Do you want to replace it?'
|
||||
% theme_name),
|
||||
).replace('%s', theme_name),
|
||||
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Yes |
|
||||
QtGui.QMessageBox.No),
|
||||
QtGui.QMessageBox.No)
|
||||
@ -672,7 +673,6 @@ class ThemeManager(QtGui.QWidget):
|
||||
theme.background_filename,
|
||||
ImageSource.Theme, QtGui.QColor(theme.background_border_color))
|
||||
self.mainwindow.imageManager.processUpdates()
|
||||
self.loadThemes()
|
||||
|
||||
def _writeTheme(self, theme, image_from, image_to):
|
||||
"""
|
||||
|
@ -136,7 +136,7 @@ class AppLocation(object):
|
||||
else:
|
||||
path = AppLocation.get_directory(AppLocation.DataDir)
|
||||
check_directory_exists(path)
|
||||
return path
|
||||
return os.path.normpath(path)
|
||||
|
||||
@staticmethod
|
||||
def get_section_data_path(section):
|
||||
@ -469,10 +469,43 @@ def get_uno_instance(resolver):
|
||||
return resolver.resolve(u'uno:socket,host=localhost,port=2002;' \
|
||||
+ u'urp;StarOffice.ComponentContext')
|
||||
|
||||
|
||||
def format_time(text, local_time):
|
||||
"""
|
||||
Workaround for Python built-in time formatting fuction time.strftime().
|
||||
|
||||
time.strftime() accepts only ascii characters. This function accepts
|
||||
unicode string and passes individual % placeholders to time.strftime().
|
||||
This ensures only ascii characters are passed to time.strftime().
|
||||
|
||||
``text``
|
||||
The text to be processed.
|
||||
``local_time``
|
||||
The time to be used to add to the string. This is a time object
|
||||
"""
|
||||
def match_formatting(match):
|
||||
return local_time.strftime(match.group())
|
||||
return re.sub('\%[a-zA-Z]', match_formatting, text)
|
||||
|
||||
|
||||
def locale_compare(string1, string2):
|
||||
"""
|
||||
Compares two strings according to the current locale settings.
|
||||
|
||||
As any other compare function, returns a negative, or a positive value,
|
||||
or 0, depending on whether string1 collates before or after string2 or
|
||||
is equal to it. Comparison is case insensitive.
|
||||
"""
|
||||
# Function locale.strcol() from standard Python library does not work
|
||||
# properly on Windows and probably somewhere else.
|
||||
return int(QtCore.QString.localeAwareCompare(
|
||||
QtCore.QString(string1).toLower(), QtCore.QString(string2).toLower()))
|
||||
|
||||
|
||||
from languagemanager import LanguageManager
|
||||
from actions import ActionList
|
||||
|
||||
__all__ = [u'AppLocation', u'get_application_version', u'check_latest_version',
|
||||
u'add_actions', u'get_filesystem_encoding', u'LanguageManager',
|
||||
u'ActionList', u'get_web_page', u'get_uno_command', u'get_uno_instance',
|
||||
u'delete_file', u'clean_filename']
|
||||
u'delete_file', u'clean_filename', u'format_time', u'locale_compare']
|
||||
|
@ -105,6 +105,7 @@ CSS = """
|
||||
font-size: %spt;
|
||||
color: %s;
|
||||
background-color: %s;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
"""
|
||||
|
||||
|
@ -35,7 +35,7 @@ class Ui_AlertDialog(object):
|
||||
def setupUi(self, alertDialog):
|
||||
alertDialog.setObjectName(u'alertDialog')
|
||||
alertDialog.resize(400, 300)
|
||||
alertDialog.setWindowIcon(build_icon(u':/icon/openlp.org-icon-32.bmp'))
|
||||
alertDialog.setWindowIcon(build_icon(u':/icon/openlp-logo-16x16.png'))
|
||||
self.alertDialogLayout = QtGui.QGridLayout(alertDialog)
|
||||
self.alertDialogLayout.setObjectName(u'alertDialogLayout')
|
||||
self.alertTextLayout = QtGui.QFormLayout()
|
||||
|
@ -80,6 +80,10 @@ class AlertForm(QtGui.QDialog, Ui_AlertDialog):
|
||||
item_name = QtGui.QListWidgetItem(alert.text)
|
||||
item_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(alert.id))
|
||||
self.alertListWidget.addItem(item_name)
|
||||
if alert.text == unicode(self.alertTextEdit.text()):
|
||||
self.item_id = alert.id
|
||||
self.alertListWidget.setCurrentRow(
|
||||
self.alertListWidget.row(item_name))
|
||||
|
||||
def onDisplayClicked(self):
|
||||
self.triggerAlert(unicode(self.alertTextEdit.text()))
|
||||
@ -112,7 +116,6 @@ class AlertForm(QtGui.QDialog, Ui_AlertDialog):
|
||||
alert = AlertItem()
|
||||
alert.text = unicode(self.alertTextEdit.text())
|
||||
self.manager.save_object(alert)
|
||||
self.alertTextEdit.setText(u'')
|
||||
self.loadList()
|
||||
|
||||
def onSaveClick(self):
|
||||
@ -125,6 +128,7 @@ class AlertForm(QtGui.QDialog, Ui_AlertDialog):
|
||||
self.manager.save_object(alert)
|
||||
self.item_id = None
|
||||
self.loadList()
|
||||
self.saveButton.setEnabled(False)
|
||||
|
||||
def onTextChanged(self):
|
||||
"""
|
||||
|
@ -57,8 +57,6 @@ class AlertsManager(QtCore.QObject):
|
||||
"""
|
||||
if message:
|
||||
self.displayAlert(message[0])
|
||||
else:
|
||||
self.displayAlert(u'')
|
||||
|
||||
def displayAlert(self, text=u''):
|
||||
"""
|
||||
@ -68,14 +66,15 @@ class AlertsManager(QtCore.QObject):
|
||||
display text
|
||||
"""
|
||||
log.debug(u'display alert called %s' % text)
|
||||
self.alertList.append(text)
|
||||
if self.timer_id != 0:
|
||||
Receiver.send_message(u'mainwindow_status_text',
|
||||
translate('AlertsPlugin.AlertsManager',
|
||||
'Alert message created and displayed.'))
|
||||
return
|
||||
Receiver.send_message(u'mainwindow_status_text', u'')
|
||||
self.generateAlert()
|
||||
if text:
|
||||
self.alertList.append(text)
|
||||
if self.timer_id != 0:
|
||||
Receiver.send_message(u'mainwindow_status_text',
|
||||
translate('AlertsPlugin.AlertsManager',
|
||||
'Alert message created and displayed.'))
|
||||
return
|
||||
Receiver.send_message(u'mainwindow_status_text', u'')
|
||||
self.generateAlert()
|
||||
|
||||
def generateAlert(self):
|
||||
"""
|
||||
|
@ -30,7 +30,6 @@ The bible import functions for OpenLP
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
import locale
|
||||
|
||||
from PyQt4 import QtCore, QtGui
|
||||
|
||||
@ -39,7 +38,7 @@ from openlp.core.lib.db import delete_database
|
||||
from openlp.core.lib.ui import UiStrings, critical_error_message_box
|
||||
from openlp.core.lib.settings import Settings
|
||||
from openlp.core.ui.wizard import OpenLPWizard, WizardStrings
|
||||
from openlp.core.utils import AppLocation
|
||||
from openlp.core.utils import AppLocation, locale_compare
|
||||
from openlp.plugins.bibles.lib.manager import BibleFormat
|
||||
from openlp.plugins.bibles.lib.db import BiblesResourcesDB, clean_filename
|
||||
|
||||
@ -523,7 +522,7 @@ class BibleImportForm(OpenLPWizard):
|
||||
"""
|
||||
self.webTranslationComboBox.clear()
|
||||
bibles = self.web_bible_list[index].keys()
|
||||
bibles.sort(cmp=locale.strcoll)
|
||||
bibles.sort(cmp=locale_compare)
|
||||
self.webTranslationComboBox.addItems(bibles)
|
||||
|
||||
def onOsisBrowseButtonClicked(self):
|
||||
|
@ -355,37 +355,8 @@ def parse_reference(reference, bible, language_selection, book_ref_id=False):
|
||||
log.debug(u'Matched reference %s' % reference)
|
||||
book = match.group(u'book')
|
||||
if not book_ref_id:
|
||||
book_names = BibleStrings().BookNames
|
||||
# escape reserved characters
|
||||
book_escaped = book
|
||||
for character in u'\\.^$*+?{}[]()':
|
||||
book_escaped = book_escaped.replace(
|
||||
character, u'\\' + character)
|
||||
regex_book = re.compile(u'\s*%s\s*' % u'\s*'.join(
|
||||
book_escaped.split()), re.UNICODE | re.IGNORECASE)
|
||||
if language_selection == LanguageSelection.Bible:
|
||||
db_book = bible.get_book(book)
|
||||
if db_book:
|
||||
book_ref_id = db_book.book_reference_id
|
||||
elif language_selection == LanguageSelection.Application:
|
||||
books = filter(lambda key:
|
||||
regex_book.match(unicode(book_names[key])), book_names.keys())
|
||||
books = filter(None, map(BiblesResourcesDB.get_book, books))
|
||||
for value in books:
|
||||
if bible.get_book_by_book_ref_id(value[u'id']):
|
||||
book_ref_id = value[u'id']
|
||||
break
|
||||
elif language_selection == LanguageSelection.English:
|
||||
books = BiblesResourcesDB.get_books_like(book)
|
||||
if books:
|
||||
book_list = filter(
|
||||
lambda value: regex_book.match(value[u'name']), books)
|
||||
if not book_list:
|
||||
book_list = books
|
||||
for value in book_list:
|
||||
if bible.get_book_by_book_ref_id(value[u'id']):
|
||||
book_ref_id = value[u'id']
|
||||
break
|
||||
book_ref_id = bible.get_book_ref_id_by_localised_name(
|
||||
book, language_selection)
|
||||
elif not bible.get_book_by_book_ref_id(book_ref_id):
|
||||
book_ref_id = False
|
||||
ranges = match.group(u'ranges')
|
||||
|
@ -29,6 +29,7 @@
|
||||
import chardet
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import sqlite3
|
||||
|
||||
from PyQt4 import QtCore
|
||||
@ -44,6 +45,8 @@ import upgrade
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
RESERVED_CHARACTERS = u'\\.^$*+?{}[]()'
|
||||
|
||||
class BibleMeta(BaseModel):
|
||||
"""
|
||||
Bible Meta Data
|
||||
@ -352,6 +355,53 @@ class BibleDB(QtCore.QObject, Manager):
|
||||
book, book_id, language_id)
|
||||
return book_id
|
||||
|
||||
def get_book_ref_id_by_localised_name(self, book,
|
||||
language_selection):
|
||||
"""
|
||||
Return the id of a named book.
|
||||
|
||||
``book``
|
||||
The name of the book, according to the selected language.
|
||||
|
||||
``language_selection``
|
||||
The language selection the user has chosen in the settings
|
||||
section of the Bible.
|
||||
"""
|
||||
log.debug(u'get_book_ref_id_by_localised_name("%s", "%s")',
|
||||
book, language_selection)
|
||||
from openlp.plugins.bibles.lib import LanguageSelection, \
|
||||
BibleStrings
|
||||
book_names = BibleStrings().BookNames
|
||||
# escape reserved characters
|
||||
book_escaped = book
|
||||
for character in RESERVED_CHARACTERS:
|
||||
book_escaped = book_escaped.replace(
|
||||
character, u'\\' + character)
|
||||
regex_book = re.compile(u'\s*%s\s*' % u'\s*'.join(
|
||||
book_escaped.split()), re.UNICODE | re.IGNORECASE)
|
||||
if language_selection == LanguageSelection.Bible:
|
||||
db_book = self.get_book(book)
|
||||
if db_book:
|
||||
return db_book.book_reference_id
|
||||
elif language_selection == LanguageSelection.Application:
|
||||
books = filter(lambda key:
|
||||
regex_book.match(unicode(book_names[key])), book_names.keys())
|
||||
books = filter(None, map(BiblesResourcesDB.get_book, books))
|
||||
for value in books:
|
||||
if self.get_book_by_book_ref_id(value[u'id']):
|
||||
return value[u'id']
|
||||
elif language_selection == LanguageSelection.English:
|
||||
books = BiblesResourcesDB.get_books_like(book)
|
||||
if books:
|
||||
book_list = filter(
|
||||
lambda value: regex_book.match(value[u'name']), books)
|
||||
if not book_list:
|
||||
book_list = books
|
||||
for value in book_list:
|
||||
if self.get_book_by_book_ref_id(value[u'id']):
|
||||
return value[u'id']
|
||||
return False
|
||||
|
||||
def get_verses(self, reference_list, show_error=True):
|
||||
"""
|
||||
This is probably the most used function. It retrieves the list of
|
||||
|
@ -124,6 +124,8 @@ class BGExtract(object):
|
||||
self._remove_elements(tag, 'div', 'footnotes')
|
||||
self._remove_elements(tag, 'div', 'crossrefs')
|
||||
self._remove_elements(tag, 'h3')
|
||||
self._remove_elements(tag, 'h4')
|
||||
self._remove_elements(tag, 'h5')
|
||||
|
||||
def _extract_verses(self, tags):
|
||||
"""
|
||||
@ -155,12 +157,65 @@ class BGExtract(object):
|
||||
text = text.replace(old, new)
|
||||
text = u' '.join(text.split())
|
||||
if verse and text:
|
||||
verses.append((int(verse.strip()), text))
|
||||
verse = verse.strip()
|
||||
try:
|
||||
verse = int(verse)
|
||||
except ValueError:
|
||||
verse_parts = verse.split(u'-')
|
||||
if len(verse_parts) > 1:
|
||||
verse = int(verse_parts[0])
|
||||
except TypeError:
|
||||
log.warn(u'Illegal verse number: %s', unicode(raw_verse_num))
|
||||
verses.append((verse, text))
|
||||
verse_list = {}
|
||||
for verse, text in verses[::-1]:
|
||||
verse_list[verse] = text
|
||||
return verse_list
|
||||
|
||||
def _extract_verses_old(self, div):
|
||||
"""
|
||||
Use the old style of parsing for those Bibles on BG who mysteriously
|
||||
have not been migrated to the new (still broken) HTML.
|
||||
|
||||
``div``
|
||||
The parent div.
|
||||
"""
|
||||
verse_list = {}
|
||||
# Cater for inconsistent mark up in the first verse of a chapter.
|
||||
first_verse = div.find(u'versenum')
|
||||
if first_verse and first_verse.contents:
|
||||
verse_list[1] = unicode(first_verse.contents[0])
|
||||
for verse in div(u'sup', u'versenum'):
|
||||
raw_verse_num = verse.next
|
||||
clean_verse_num = 0
|
||||
# Not all verses exist in all translations and may or may not be
|
||||
# represented by a verse number. If they are not fine, if they are
|
||||
# it will probably be in a format that breaks int(). We will then
|
||||
# have no idea what garbage may be sucked in to the verse text so
|
||||
# if we do not get a clean int() then ignore the verse completely.
|
||||
try:
|
||||
clean_verse_num = int(str(raw_verse_num))
|
||||
except ValueError:
|
||||
verse_parts = str(raw_verse_num).split(u'-')
|
||||
if len(verse_parts) > 1:
|
||||
clean_verse_num = int(verse_parts[0])
|
||||
except TypeError:
|
||||
log.warn(u'Illegal verse number: %s', unicode(raw_verse_num))
|
||||
if clean_verse_num:
|
||||
verse_text = raw_verse_num.next
|
||||
part = raw_verse_num.next.next
|
||||
while not (isinstance(part, Tag) and
|
||||
part.get(u'class') == u'versenum'):
|
||||
# While we are still in the same verse grab all the text.
|
||||
if isinstance(part, NavigableString):
|
||||
verse_text += part
|
||||
if isinstance(part.next, Tag) and part.next.name == u'div':
|
||||
# Run out of verses so stop.
|
||||
break
|
||||
part = part.next
|
||||
verse_list[clean_verse_num] = unicode(verse_text)
|
||||
return verse_list
|
||||
|
||||
def get_bible_chapter(self, version, book_name, chapter):
|
||||
"""
|
||||
Access and decode Bibles via the BibleGateway website.
|
||||
@ -189,7 +244,13 @@ class BGExtract(object):
|
||||
Receiver.send_message(u'openlp_process_events')
|
||||
div = soup.find('div', 'result-text-style-normal')
|
||||
self._clean_soup(div)
|
||||
verse_list = self._extract_verses(div.findAll('span', 'text'))
|
||||
span_list = div.findAll('span', 'text')
|
||||
log.debug('Span list: %s', span_list)
|
||||
if not span_list:
|
||||
# If we don't get any spans then we must have the old HTML format
|
||||
verse_list = self._extract_verses_old(div)
|
||||
else:
|
||||
verse_list = self._extract_verses(span_list)
|
||||
if not verse_list:
|
||||
log.debug(u'No content found in the BibleGateway response.')
|
||||
send_error_message(u'parse')
|
||||
|
@ -208,24 +208,16 @@ class BibleManager(object):
|
||||
|
||||
def delete_bible(self, name):
|
||||
"""
|
||||
Delete a bible completly.
|
||||
Delete a bible completely.
|
||||
|
||||
``name``
|
||||
The name of the bible.
|
||||
"""
|
||||
log.debug(u'BibleManager.delete_bible("%s")', name)
|
||||
files = SettingsManager.get_files(self.settingsSection,
|
||||
self.suffix)
|
||||
if u'alternative_book_names.sqlite' in files:
|
||||
files.remove(u'alternative_book_names.sqlite')
|
||||
for filename in files:
|
||||
bible = BibleDB(self.parent, path=self.path, file=filename)
|
||||
# Remove the bible files
|
||||
if name == bible.get_name():
|
||||
bible.session.close()
|
||||
if delete_file(os.path.join(self.path, filename)):
|
||||
return True
|
||||
return False
|
||||
bible = self.db_cache[name]
|
||||
bible.session.close()
|
||||
bible.session = None
|
||||
return delete_file(os.path.join(bible.path, bible.file))
|
||||
|
||||
def get_bibles(self):
|
||||
"""
|
||||
@ -285,8 +277,9 @@ class BibleManager(object):
|
||||
"""
|
||||
log.debug(u'BibleManager.get_verse_count("%s", "%s", %s)',
|
||||
bible, book, chapter)
|
||||
db_book = self.db_cache[bible].get_book(book)
|
||||
book_ref_id = db_book.book_reference_id
|
||||
language_selection = self.get_language_selection(bible)
|
||||
book_ref_id = self.db_cache[bible].get_book_ref_id_by_localised_name(
|
||||
book, language_selection)
|
||||
return self.db_cache[bible].get_verse_count(book_ref_id, chapter)
|
||||
|
||||
def get_verse_count_by_book_ref_id(self, bible, book_ref_id, chapter):
|
||||
@ -374,15 +367,20 @@ class BibleManager(object):
|
||||
"""
|
||||
log.debug(u'BibleManager.get_language_selection("%s")', bible)
|
||||
language_selection = self.get_meta_data(bible, u'book_name_language')
|
||||
if language_selection:
|
||||
try:
|
||||
language_selection = int(language_selection.value)
|
||||
except (ValueError, TypeError):
|
||||
language_selection = LanguageSelection.Application
|
||||
if language_selection is None or language_selection == -1:
|
||||
if not language_selection or \
|
||||
language_selection.value == "None" or \
|
||||
language_selection.value == "-1":
|
||||
# If None is returned, it's not the singleton object but a
|
||||
# BibleMeta object with the value "None"
|
||||
language_selection = Settings().value(
|
||||
self.settingsSection + u'/bookname language',
|
||||
self.settingsSection + u'/book name language',
|
||||
QtCore.QVariant(0)).toInt()[0]
|
||||
else:
|
||||
language_selection = language_selection.value
|
||||
try:
|
||||
language_selection = int(language_selection)
|
||||
except (ValueError, TypeError):
|
||||
language_selection = LanguageSelection.Application
|
||||
return language_selection
|
||||
|
||||
def verse_search(self, bible, second_bible, text):
|
||||
|
@ -27,7 +27,6 @@
|
||||
###############################################################################
|
||||
|
||||
import logging
|
||||
import locale
|
||||
|
||||
from PyQt4 import QtCore, QtGui
|
||||
|
||||
@ -38,6 +37,7 @@ from openlp.core.lib.settings import Settings
|
||||
from openlp.core.lib.ui import UiStrings, set_case_insensitive_completer, \
|
||||
create_horizontal_adjusting_combo_box, critical_error_message_box, \
|
||||
find_and_set_in_combo_box, build_icon
|
||||
from openlp.core.utils import locale_compare
|
||||
from openlp.plugins.bibles.forms import BibleImportForm, EditBibleForm
|
||||
from openlp.plugins.bibles.lib import LayoutStyle, DisplayStyle, \
|
||||
VerseReferenceList, get_reference_separator, LanguageSelection, \
|
||||
@ -381,7 +381,7 @@ class BibleMediaItem(MediaManagerItem):
|
||||
# Get all bibles and sort the list.
|
||||
bibles = self.plugin.manager.get_bibles().keys()
|
||||
bibles = filter(None, bibles)
|
||||
bibles.sort(cmp=locale.strcoll)
|
||||
bibles.sort(cmp=locale_compare)
|
||||
# Load the bibles into the combo boxes.
|
||||
self.quickVersionComboBox.addItems(bibles)
|
||||
self.quickSecondComboBox.addItems(bibles)
|
||||
@ -538,7 +538,7 @@ class BibleMediaItem(MediaManagerItem):
|
||||
data = BiblesResourcesDB.get_book_by_id(
|
||||
book.book_reference_id)
|
||||
books.append(data[u'name'] + u' ')
|
||||
books.sort(cmp=locale.strcoll)
|
||||
books.sort(cmp=locale_compare)
|
||||
set_case_insensitive_completer(books, self.quickSearchEdit)
|
||||
|
||||
def onImportClick(self):
|
||||
|
@ -103,10 +103,20 @@ class OpenSongBible(BibleDB):
|
||||
for verse in chapter.v:
|
||||
if self.stop_import_flag:
|
||||
break
|
||||
verse_number = 0
|
||||
try:
|
||||
verse_number = int(verse.attrib[u'n'])
|
||||
except ValueError:
|
||||
verse_parts = verse.attrib[u'n'].split(u'-')
|
||||
if len(verse_parts) > 1:
|
||||
verse_number = int(verse_parts[0])
|
||||
except TypeError:
|
||||
log.warn(u'Illegal verse number: %s',
|
||||
unicode(verse.attrib[u'n']))
|
||||
self.create_verse(
|
||||
db_book.id,
|
||||
int(chapter.attrib[u'n'].split()[-1]),
|
||||
int(verse.attrib[u'n']),
|
||||
verse_number,
|
||||
unicode(self.get_text(verse)))
|
||||
self.wizard.incrementProgressBar(unicode(translate(
|
||||
'BiblesPlugin.Opensong', 'Importing %s %s...',
|
||||
|
@ -36,7 +36,7 @@ class Ui_CustomEditDialog(object):
|
||||
customEditDialog.setObjectName(u'customEditDialog')
|
||||
customEditDialog.resize(450, 350)
|
||||
customEditDialog.setWindowIcon(
|
||||
build_icon(u':/icon/openlp.org-icon-32.bmp'))
|
||||
build_icon(u':/icon/openlp-logo-16x16.png'))
|
||||
self.dialogLayout = QtGui.QVBoxLayout(customEditDialog)
|
||||
self.dialogLayout.setObjectName(u'dialogLayout')
|
||||
self.titleLayout = QtGui.QHBoxLayout()
|
||||
|
@ -34,12 +34,21 @@ from sqlalchemy import Column, Table, types
|
||||
from sqlalchemy.orm import mapper
|
||||
|
||||
from openlp.core.lib.db import BaseModel, init_db
|
||||
from openlp.core.utils import locale_compare
|
||||
|
||||
class CustomSlide(BaseModel):
|
||||
"""
|
||||
CustomSlide model
|
||||
"""
|
||||
pass
|
||||
# By default sort the customs by its title considering language specific
|
||||
# characters.
|
||||
def __lt__(self, other):
|
||||
r = locale_compare(self.title, other.title)
|
||||
return True if r < 0 else False
|
||||
|
||||
def __eq__(self, other):
|
||||
return 0 == locale_compare(self.title, other.title)
|
||||
|
||||
|
||||
def init_schema(url):
|
||||
"""
|
||||
|
@ -27,7 +27,6 @@
|
||||
###############################################################################
|
||||
|
||||
import logging
|
||||
import locale
|
||||
|
||||
from PyQt4 import QtCore, QtGui
|
||||
from sqlalchemy.sql import or_, func
|
||||
@ -109,10 +108,7 @@ class CustomMediaItem(MediaManagerItem):
|
||||
# Sort out what custom we want to select after loading the list.
|
||||
self.saveAutoSelectId()
|
||||
self.listView.clear()
|
||||
# Sort the customs by its title considering language specific
|
||||
# characters. lower() is needed for windows!
|
||||
custom_slides.sort(
|
||||
cmp=locale.strcoll, key=lambda custom: custom.title.lower())
|
||||
custom_slides.sort()
|
||||
for custom_slide in custom_slides:
|
||||
custom_name = QtGui.QListWidgetItem(custom_slide.title)
|
||||
custom_name.setData(
|
||||
|
@ -28,7 +28,6 @@
|
||||
|
||||
import logging
|
||||
import os
|
||||
import locale
|
||||
|
||||
from PyQt4 import QtCore, QtGui
|
||||
|
||||
@ -37,7 +36,8 @@ from openlp.core.lib import MediaManagerItem, build_icon, ItemCapabilities, \
|
||||
Receiver, create_thumb, validate_thumb
|
||||
from openlp.core.lib.ui import UiStrings, critical_error_message_box
|
||||
from openlp.core.lib.settings import Settings
|
||||
from openlp.core.utils import AppLocation, delete_file, get_images_filter
|
||||
from openlp.core.utils import AppLocation, delete_file, locale_compare, \
|
||||
get_images_filter
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@ -126,10 +126,10 @@ class ImageMediaItem(MediaManagerItem):
|
||||
if not initialLoad:
|
||||
Receiver.send_message(u'cursor_busy')
|
||||
self.plugin.formParent.displayProgressBar(len(images))
|
||||
# Sort the themes by its filename considering language specific
|
||||
# characters. lower() is needed for windows!
|
||||
images.sort(cmp=locale.strcoll,
|
||||
key=lambda filename: os.path.split(unicode(filename))[1].lower())
|
||||
# Sort the images by its filename considering language specific
|
||||
# characters.
|
||||
images.sort(cmp=locale_compare,
|
||||
key=lambda filename: os.path.split(unicode(filename))[1])
|
||||
for imageFile in images:
|
||||
filename = os.path.split(unicode(imageFile))[1]
|
||||
thumb = os.path.join(self.servicePath, filename)
|
||||
|
@ -28,7 +28,6 @@
|
||||
|
||||
import logging
|
||||
import os
|
||||
import locale
|
||||
|
||||
from PyQt4 import QtCore, QtGui
|
||||
|
||||
@ -39,11 +38,13 @@ from openlp.core.lib.ui import UiStrings, critical_error_message_box, \
|
||||
create_horizontal_adjusting_combo_box
|
||||
from openlp.core.ui import Controller, Display
|
||||
from openlp.core.ui.media import get_media_players, set_media_players
|
||||
from openlp.core.utils import locale_compare
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
CLAPPERBOARD = QtGui.QImage(u':/media/media_video.png')
|
||||
#TODO: Add an appropriate Icon for DVDs, CDs, ...
|
||||
CLAPPERBOARD = u':/media/slidecontroller_multimedia.png'
|
||||
VIDEO = QtGui.QImage(u':/media/media_video.png')
|
||||
AUDIO = QtGui.QImage(u':/media/media_audio.png')
|
||||
DVD_ICON = QtGui.QImage(u':/media/media_video.png')
|
||||
|
||||
class MediaMediaItem(MediaManagerItem):
|
||||
@ -218,7 +219,7 @@ class MediaMediaItem(MediaManagerItem):
|
||||
service_item.add_capability(ItemCapabilities.RequiresMedia)
|
||||
# force a non-existent theme
|
||||
service_item.theme = -1
|
||||
frame = u':/media/image_clapperboard.png'
|
||||
frame = CLAPPERBOARD
|
||||
(path, name) = os.path.split(filename)
|
||||
service_item.add_from_command(path, name, frame)
|
||||
return True
|
||||
@ -284,16 +285,16 @@ class MediaMediaItem(MediaManagerItem):
|
||||
u'media', self.getFileList())
|
||||
|
||||
def loadList(self, media):
|
||||
# Sort the themes by its filename considering language specific
|
||||
# characters. lower() is needed for windows!
|
||||
media.sort(cmp=locale.strcoll,
|
||||
key=lambda filename: os.path.split(unicode(filename))[1].lower())
|
||||
# Sort the media by its filename considering language specific
|
||||
# characters.
|
||||
media.sort(cmp=locale_compare,
|
||||
key=lambda filename: os.path.split(unicode(filename))[1])
|
||||
for track in media:
|
||||
track_info = QtCore.QFileInfo(track)
|
||||
if not track_info.isFile():
|
||||
if track_info.isFile():
|
||||
filename = os.path.split(unicode(track))[1]
|
||||
item_name = QtGui.QListWidgetItem(filename)
|
||||
item_name.setIcon(build_icon(CLAPPERBOARD))
|
||||
item_name.setIcon(build_icon(VIDEO))
|
||||
item_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(track))
|
||||
else:
|
||||
filename = os.path.split(unicode(track))[1]
|
||||
@ -306,8 +307,8 @@ class MediaMediaItem(MediaManagerItem):
|
||||
|
||||
def getList(self, type=MediaType.Audio):
|
||||
media = SettingsManager.load_list(self.settingsSection, u'media')
|
||||
media.sort(cmp=locale.strcoll,
|
||||
key=lambda filename: os.path.split(unicode(filename))[1].lower())
|
||||
media.sort(cmp=locale_compare,
|
||||
key=lambda filename: os.path.split(unicode(filename))[1])
|
||||
ext = []
|
||||
if type == MediaType.Audio:
|
||||
ext = self.plugin.audio_extensions_list
|
||||
|
@ -28,7 +28,6 @@
|
||||
|
||||
import logging
|
||||
import os
|
||||
import locale
|
||||
|
||||
from PyQt4 import QtCore, QtGui
|
||||
|
||||
@ -38,6 +37,7 @@ from openlp.core.lib import MediaManagerItem, build_icon, SettingsManager, \
|
||||
from openlp.core.lib.ui import UiStrings, critical_error_message_box, \
|
||||
create_horizontal_adjusting_combo_box
|
||||
from openlp.core.lib.settings import Settings
|
||||
from openlp.core.utils import locale_compare
|
||||
from openlp.plugins.presentations.lib import MessageListener
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@ -169,10 +169,10 @@ class PresentationMediaItem(MediaManagerItem):
|
||||
if not initialLoad:
|
||||
Receiver.send_message(u'cursor_busy')
|
||||
self.plugin.formParent.displayProgressBar(len(files))
|
||||
# Sort the themes by its filename considering language specific
|
||||
# characters. lower() is needed for windows!
|
||||
files.sort(cmp=locale.strcoll,
|
||||
key=lambda filename: os.path.split(unicode(filename))[1].lower())
|
||||
# Sort the presentations by its filename considering language specific
|
||||
# characters.
|
||||
files.sort(cmp=locale_compare,
|
||||
key=lambda filename: os.path.split(unicode(filename))[1])
|
||||
for file in files:
|
||||
if not initialLoad:
|
||||
self.plugin.formParent.incrementProgressBar()
|
||||
|
@ -66,10 +66,10 @@
|
||||
<a href="#" id="service-refresh" data-role="button" data-icon="refresh">${refresh}</a>
|
||||
<div data-role="navbar">
|
||||
<ul>
|
||||
<li><a href="#service-manager" data-theme="e">Service</a></li>
|
||||
<li><a href="#slide-controller">Slides</a></li>
|
||||
<li><a href="#alerts">Alerts</a></li>
|
||||
<li><a href="#search">Search</a></li>
|
||||
<li><a href="#service-manager" data-theme="e">${service}</a></li>
|
||||
<li><a href="#slide-controller">${slides}</a></li>
|
||||
<li><a href="#alerts">${alerts}</a></li>
|
||||
<li><a href="#search">${search}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@ -97,10 +97,10 @@
|
||||
<a href="#" id="controller-refresh" data-role="button" data-icon="refresh">${refresh}</a>
|
||||
<div data-role="navbar">
|
||||
<ul>
|
||||
<li><a href="#service-manager">Service</a></li>
|
||||
<li><a href="#slide-controller" data-theme="e">Slides</a></li>
|
||||
<li><a href="#alerts">Alerts</a></li>
|
||||
<li><a href="#search">Search</a></li>
|
||||
<li><a href="#service-manager">${service}</a></li>
|
||||
<li><a href="#slide-controller" data-theme="e">${slides}</a></li>
|
||||
<li><a href="#alerts">${alerts}</a></li>
|
||||
<li><a href="#search">${search}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@ -127,10 +127,10 @@
|
||||
<h1>${alerts}</h1>
|
||||
<div data-role="navbar">
|
||||
<ul>
|
||||
<li><a href="#service-manager">Service</a></li>
|
||||
<li><a href="#slide-controller">Slides</a></li>
|
||||
<li><a href="#alerts" data-theme="e">Alerts</a></li>
|
||||
<li><a href="#search">Search</a></li>
|
||||
<li><a href="#service-manager">${service}</a></li>
|
||||
<li><a href="#slide-controller">${slides}</a></li>
|
||||
<li><a href="#alerts" data-theme="e">${alerts}</a></li>
|
||||
<li><a href="#search">${search}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@ -148,10 +148,10 @@
|
||||
<h1>${search}</h1>
|
||||
<div data-role="navbar">
|
||||
<ul>
|
||||
<li><a href="#service-manager">Service</a></li>
|
||||
<li><a href="#slide-controller">Slides</a></li>
|
||||
<li><a href="#alerts">Alerts</a></li>
|
||||
<li><a href="#search" data-theme="e">Search</a></li>
|
||||
<li><a href="#service-manager">${service}</a></li>
|
||||
<li><a href="#slide-controller">${slides}</a></li>
|
||||
<li><a href="#alerts">${alerts}</a></li>
|
||||
<li><a href="#search" data-theme="e">${search}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -55,7 +55,9 @@ window.OpenLP = {
|
||||
);
|
||||
},
|
||||
loadService: function (event) {
|
||||
event.preventDefault();
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
$.getJSON(
|
||||
"/api/service/list",
|
||||
function (data, status) {
|
||||
@ -150,6 +152,10 @@ window.OpenLP = {
|
||||
OpenLP.currentSlide = data.results.slide;
|
||||
OpenLP.currentItem = data.results.item;
|
||||
if ($("#service-manager").is(":visible")) {
|
||||
if (OpenLP.currentService != data.results.service) {
|
||||
OpenLP.currentService = data.results.service;
|
||||
OpenLP.loadService();
|
||||
}
|
||||
$("#service-manager div[data-role=content] ul[data-role=listview] li").attr("data-theme", "c").removeClass("ui-btn-up-e").addClass("ui-btn-up-c");
|
||||
$("#service-manager div[data-role=content] ul[data-role=listview] li a").each(function () {
|
||||
var item = $(this);
|
||||
@ -307,7 +313,7 @@ window.OpenLP = {
|
||||
}
|
||||
);
|
||||
},
|
||||
escapeString: function (string) {
|
||||
escapeString: function (string) {
|
||||
return string.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")
|
||||
}
|
||||
}
|
||||
|
@ -139,8 +139,10 @@ window.OpenLP = {
|
||||
"/api/poll",
|
||||
function (data, status) {
|
||||
OpenLP.updateClock(data);
|
||||
if (OpenLP.currentItem != data.results.item) {
|
||||
if (OpenLP.currentItem != data.results.item ||
|
||||
OpenLP.currentService != data.results.service) {
|
||||
OpenLP.currentItem = data.results.item;
|
||||
OpenLP.currentService = data.results.service;
|
||||
OpenLP.loadSlides();
|
||||
}
|
||||
else if (OpenLP.currentSlide != data.results.slide) {
|
||||
|
@ -308,7 +308,9 @@ class HttpConnection(object):
|
||||
'add_and_go_to_service': translate('RemotePlugin.Mobile',
|
||||
'Add & Go to Service'),
|
||||
'no_results': translate('RemotePlugin.Mobile', 'No Results'),
|
||||
'options': translate('RemotePlugin.Mobile', 'Options')
|
||||
'options': translate('RemotePlugin.Mobile', 'Options'),
|
||||
'service': translate('RemotePlugin.Mobile', 'Service'),
|
||||
'slides': translate('RemotePlugin.Mobile', 'Slides')
|
||||
}
|
||||
|
||||
def ready_read(self):
|
||||
|
@ -528,9 +528,9 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||
for row in self.findVerseSplit.split(verse_list):
|
||||
for match in row.split(u'---['):
|
||||
for count, parts in enumerate(match.split(u']---\n')):
|
||||
if len(parts) <= 1:
|
||||
continue
|
||||
if count == 0:
|
||||
if len(parts) == 0:
|
||||
continue
|
||||
# handling carefully user inputted versetags
|
||||
separator = parts.find(u':')
|
||||
if separator >= 0:
|
||||
|
@ -67,7 +67,7 @@ class Ui_EditVerseDialog(object):
|
||||
self.verseTypeLayout.addStretch()
|
||||
self.dialogLayout.addLayout(self.verseTypeLayout)
|
||||
self.buttonBox = create_button_box(editVerseDialog, u'buttonBox',
|
||||
[u'cancel', u'save'])
|
||||
[u'cancel', u'ok'])
|
||||
self.dialogLayout.addWidget(self.buttonBox)
|
||||
self.retranslateUi(editVerseDialog)
|
||||
|
||||
|
@ -185,22 +185,3 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog):
|
||||
text = u'---[%s:1]---\n%s' % \
|
||||
(VerseType.TranslatedNames[VerseType.Verse], text)
|
||||
return text
|
||||
|
||||
def accept(self):
|
||||
if self.hasSingleVerse:
|
||||
value = unicode(self.getVerse()[0])
|
||||
else:
|
||||
log.debug(unicode(self.getVerse()[0]).split(u'\n'))
|
||||
value = unicode(self.getVerse()[0]).split(u'\n')[1]
|
||||
if not value:
|
||||
lines = unicode(self.getVerse()[0]).split(u'\n')
|
||||
index = 2
|
||||
while index < len(lines) and not value:
|
||||
value = lines[index]
|
||||
index += 1
|
||||
if not value:
|
||||
critical_error_message_box(
|
||||
message=translate('SongsPlugin.EditSongForm',
|
||||
'You need to type some text in to the verse.'))
|
||||
return False
|
||||
QtGui.QDialog.accept(self)
|
||||
|
@ -29,7 +29,6 @@
|
||||
The :mod:`songexportform` module provides the wizard for exporting songs to the
|
||||
OpenLyrics format.
|
||||
"""
|
||||
import locale
|
||||
import logging
|
||||
|
||||
from PyQt4 import QtCore, QtGui
|
||||
@ -252,7 +251,7 @@ class SongExportForm(OpenLPWizard):
|
||||
# Load the list of songs.
|
||||
Receiver.send_message(u'cursor_busy')
|
||||
songs = self.plugin.manager.get_all_objects(Song)
|
||||
songs.sort(cmp=locale.strcoll, key=lambda song: song.title.lower())
|
||||
songs.sort()
|
||||
for song in songs:
|
||||
# No need to export temporary songs.
|
||||
if song.temporary:
|
||||
|
@ -35,6 +35,7 @@ from sqlalchemy.orm import mapper, relation
|
||||
from sqlalchemy.sql.expression import func
|
||||
|
||||
from openlp.core.lib.db import BaseModel, init_db
|
||||
from openlp.core.utils import locale_compare
|
||||
|
||||
class Author(BaseModel):
|
||||
"""
|
||||
@ -63,7 +64,14 @@ class Song(BaseModel):
|
||||
"""
|
||||
Song model
|
||||
"""
|
||||
pass
|
||||
# By default sort the songs by its title considering language specific
|
||||
# characters.
|
||||
def __lt__(self, other):
|
||||
r = locale_compare(self.title, other.title)
|
||||
return True if r < 0 else False
|
||||
|
||||
def __eq__(self, other):
|
||||
return 0 == locale_compare(self.title, other.title)
|
||||
|
||||
|
||||
class Topic(BaseModel):
|
||||
|
@ -27,7 +27,6 @@
|
||||
###############################################################################
|
||||
|
||||
import logging
|
||||
import locale
|
||||
import re
|
||||
import os
|
||||
import shutil
|
||||
@ -260,10 +259,7 @@ class SongMediaItem(MediaManagerItem):
|
||||
log.debug(u'display results Song')
|
||||
self.saveAutoSelectId()
|
||||
self.listView.clear()
|
||||
# Sort the songs by its title considering language specific characters.
|
||||
# lower() is needed for windows!
|
||||
searchresults.sort(
|
||||
cmp=locale.strcoll, key=lambda song: song.title.lower())
|
||||
searchresults.sort()
|
||||
for song in searchresults:
|
||||
# Do not display temporary songs
|
||||
if song.temporary:
|
||||
|
@ -63,10 +63,14 @@ class OpenLPSongImport(SongImport):
|
||||
SongImport.__init__(self, manager, **kwargs)
|
||||
self.sourceSession = None
|
||||
|
||||
def doImport(self):
|
||||
def doImport(self, progressDialog=None):
|
||||
"""
|
||||
Run the import for an OpenLP version 2 song database.
|
||||
|
||||
``progressDialog``
|
||||
The QProgressDialog used when importing songs from the FRW.
|
||||
"""
|
||||
|
||||
class OldAuthor(BaseModel):
|
||||
"""
|
||||
Author model
|
||||
@ -101,13 +105,14 @@ class OpenLPSongImport(SongImport):
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
# Check the file type
|
||||
if not self.importSource.endswith(u'.sqlite'):
|
||||
self.logError(self.importSource,
|
||||
translate('SongsPlugin.OpenLPSongImport',
|
||||
'Not a valid OpenLP 2.0 song database.'))
|
||||
return
|
||||
self.importSource = u'sqlite:///%s' % self.importSource
|
||||
# Load the db file
|
||||
engine = create_engine(self.importSource)
|
||||
source_meta = MetaData()
|
||||
source_meta.reflect(engine)
|
||||
@ -224,7 +229,11 @@ class OpenLPSongImport(SongImport):
|
||||
file_name=media_file.file_name))
|
||||
clean_song(self.manager, new_song)
|
||||
self.manager.save_object(new_song)
|
||||
if self.importWizard:
|
||||
if progressDialog:
|
||||
progressDialog.setValue(progressDialog.value() + 1)
|
||||
progressDialog.setLabelText(
|
||||
WizardStrings.ImportingType % new_song.title)
|
||||
else:
|
||||
self.importWizard.incrementProgressBar(
|
||||
WizardStrings.ImportingType % new_song.title)
|
||||
if self.stopImportFlag:
|
||||
|
@ -103,7 +103,7 @@ class PowerSongImport(SongImport):
|
||||
self.logError(unicode(translate('SongsPlugin.PowerSongImport',
|
||||
'No songs to import.')),
|
||||
unicode(translate('SongsPlugin.PowerSongImport',
|
||||
'No %s files found.' % PS_string)))
|
||||
'No %s files found.')) % PS_string)
|
||||
return
|
||||
self.importWizard.progressBar.setMaximum(len(self.importSource))
|
||||
for file in self.importSource:
|
||||
@ -122,8 +122,8 @@ class PowerSongImport(SongImport):
|
||||
parse_error = True
|
||||
self.logError(os.path.basename(file), unicode(
|
||||
translate('SongsPlugin.PowerSongImport',
|
||||
'Invalid %s file. Unexpected byte value.'
|
||||
% PS_string)))
|
||||
'Invalid %s file. Unexpected byte value.'))
|
||||
% PS_string)
|
||||
break
|
||||
else:
|
||||
if label == u'TITLE':
|
||||
@ -141,14 +141,14 @@ class PowerSongImport(SongImport):
|
||||
if not self.title:
|
||||
self.logError(os.path.basename(file), unicode(
|
||||
translate('SongsPlugin.PowerSongImport',
|
||||
'Invalid %s file. Missing "TITLE" header.' % PS_string)))
|
||||
'Invalid %s file. Missing "TITLE" header.')) % PS_string)
|
||||
continue
|
||||
# Check that file had COPYRIGHTLINE label
|
||||
if not found_copyright:
|
||||
self.logError(self.title, unicode(
|
||||
translate('SongsPlugin.PowerSongImport',
|
||||
'Invalid %s file. Missing "COPYRIGHTLINE" '
|
||||
'header.' % PS_string)))
|
||||
'header.')) % PS_string)
|
||||
continue
|
||||
# Check that file had at least one verse
|
||||
if not self.verses:
|
||||
|
@ -154,18 +154,20 @@ class SundayPlusImport(SongImport):
|
||||
# If any line inside any verse contains CCLI or
|
||||
# only Public Domain, we treat this as special data:
|
||||
# we remove that line and add data to specific field.
|
||||
processed_lines = []
|
||||
for i in xrange(len(lines)):
|
||||
lines[i] = lines[i].strip()
|
||||
line = lines[i]
|
||||
if line[:4].lower() == u'ccli':
|
||||
line = lines[i].strip()
|
||||
if line[:3].lower() == u'ccl':
|
||||
m = re.search(r'[0-9]+', line)
|
||||
if m:
|
||||
self.ccliNumber = int(m.group(0))
|
||||
lines.pop(i)
|
||||
continue
|
||||
elif line.lower() == u'public domain':
|
||||
self.copyright = u'Public Domain'
|
||||
lines.pop(i)
|
||||
self.addVerse('\n'.join(lines).strip(), verse_type)
|
||||
continue
|
||||
processed_lines.append(line)
|
||||
self.addVerse('\n'.join(processed_lines).strip(),
|
||||
verse_type)
|
||||
if end == -1:
|
||||
break
|
||||
i = end + 1
|
||||
|
@ -260,7 +260,7 @@ class OpenLyrics(object):
|
||||
IMPLEMENTED_VERSION = u'0.8'
|
||||
START_TAGS_REGEX = re.compile(r'\{(\w+)\}')
|
||||
END_TAGS_REGEX = re.compile(r'\{\/(\w+)\}')
|
||||
VERSE_NUMBER_REGEX = re.compile(u'[a-zA-Z]*')
|
||||
VERSE_TAG_SPLITTER = re.compile(u'([a-zA-Z]+)([0-9]*)([a-zA-Z]?)')
|
||||
|
||||
def __init__(self, manager):
|
||||
self.manager = manager
|
||||
@ -325,10 +325,22 @@ class OpenLyrics(object):
|
||||
# Process the song's lyrics.
|
||||
lyrics = etree.SubElement(song_xml, u'lyrics')
|
||||
verse_list = sxml.get_verses(song.lyrics)
|
||||
# Add a suffix letter to each verse
|
||||
verse_tags = []
|
||||
for verse in verse_list:
|
||||
verse_tag = verse[0][u'type'][0].lower()
|
||||
verse_number = verse[0][u'label']
|
||||
verse_def = verse_tag + verse_number
|
||||
verse_tags.append(verse_def)
|
||||
# Create the letter from the number of duplicates
|
||||
verse[0][u'suffix'] = chr(96 + verse_tags.count(verse_def))
|
||||
# If the verse tag is a duplicate use the suffix letter
|
||||
for verse in verse_list:
|
||||
verse_tag = verse[0][u'type'][0].lower()
|
||||
verse_number = verse[0][u'label']
|
||||
verse_def = verse_tag + verse_number
|
||||
if verse_tags.count(verse_def) > 1:
|
||||
verse_def += verse[0][u'suffix']
|
||||
verse_element = \
|
||||
self._add_text_to_element(u'verse', lyrics, None, verse_def)
|
||||
if u'lang' in verse[0]:
|
||||
@ -742,11 +754,10 @@ class OpenLyrics(object):
|
||||
if lines.get(u'break') is not None:
|
||||
text += u'\n[---]'
|
||||
verse_def = verse.get(u'name', u' ').lower()
|
||||
if verse_def[0] in VerseType.Tags:
|
||||
verse_tag = verse_def[0]
|
||||
else:
|
||||
verse_tag, verse_number, verse_part = \
|
||||
OpenLyrics.VERSE_TAG_SPLITTER.search(verse_def).groups()
|
||||
if verse_tag not in VerseType.Tags:
|
||||
verse_tag = VerseType.Tags[VerseType.Other]
|
||||
verse_number = OpenLyrics.VERSE_NUMBER_REGEX.sub(u'', verse_def)
|
||||
# OpenLyrics allows e. g. "c", but we need "c1". However, this does
|
||||
# not correct the verse order.
|
||||
if not verse_number:
|
||||
@ -757,13 +768,13 @@ class OpenLyrics(object):
|
||||
if song_xml.get(u'modifiedIn') in (u'1.9.6', u'OpenLP 1.9.6') and \
|
||||
song_xml.get(u'version') == u'0.7' and \
|
||||
(verse_tag, verse_number, lang) in verses:
|
||||
verses[(verse_tag, verse_number, lang)] += u'\n[---]\n' + text
|
||||
verses[(verse_tag, verse_number, lang, None)] += u'\n[---]\n' + text
|
||||
# Merge v1a, v1b, .... to v1.
|
||||
elif (verse_tag, verse_number, lang) in verses:
|
||||
elif (verse_tag, verse_number, lang, verse_part) in verses:
|
||||
verses[(verse_tag, verse_number, lang)] += u'\n' + text
|
||||
else:
|
||||
verses[(verse_tag, verse_number, lang)] = text
|
||||
verse_def_list.append((verse_tag, verse_number, lang))
|
||||
verses[(verse_tag, verse_number, lang, verse_part)] = text
|
||||
verse_def_list.append((verse_tag, verse_number, lang, verse_part))
|
||||
# We have to use a list to keep the order, as dicts are not sorted.
|
||||
for verse in verse_def_list:
|
||||
sxml.add_verse_to_lyrics(
|
||||
|
@ -91,7 +91,7 @@ class ZionWorxImport(SongImport):
|
||||
self.logError(unicode(translate('SongsPlugin.ZionWorxImport',
|
||||
'Error reading CSV file.')),
|
||||
unicode(translate('SongsPlugin.ZionWorxImport',
|
||||
'Line %d: %s' % (songs_reader.line_num, e))))
|
||||
'Line %d: %s')) % (songs_reader.line_num, e))
|
||||
return
|
||||
num_records = len(records)
|
||||
log.info(u'%s records found in CSV file' % num_records)
|
||||
@ -111,7 +111,7 @@ class ZionWorxImport(SongImport):
|
||||
self.logError(unicode(translate(
|
||||
'SongsPlugin.ZionWorxImport', 'Record %d' % index)),
|
||||
unicode(translate('SongsPlugin.ZionWorxImport',
|
||||
'Decoding error: %s' % e)))
|
||||
'Decoding error: %s')) % e)
|
||||
continue
|
||||
except TypeError, e:
|
||||
self.logError(unicode(translate(
|
||||
@ -130,7 +130,7 @@ class ZionWorxImport(SongImport):
|
||||
title = self.title
|
||||
if not self.finish():
|
||||
self.logError(unicode(translate(
|
||||
'SongsPlugin.ZionWorxImport', 'Record %d' % index))
|
||||
'SongsPlugin.ZionWorxImport', 'Record %d')) % index
|
||||
+ (u': "' + title + u'"' if title else u''))
|
||||
|
||||
def _decode(self, str):
|
||||
|
@ -29,6 +29,7 @@
|
||||
import logging
|
||||
import os
|
||||
from tempfile import gettempdir
|
||||
import sqlite3
|
||||
|
||||
from PyQt4 import QtCore, QtGui
|
||||
|
||||
@ -82,7 +83,7 @@ class SongsPlugin(Plugin):
|
||||
unicode(UiStrings().Tools))
|
||||
QtCore.QObject.connect(Receiver.get_receiver(),
|
||||
QtCore.SIGNAL(u'servicemanager_new_service'),
|
||||
self.clearTemporarySongs)
|
||||
self.clearTemporarySongs)
|
||||
|
||||
|
||||
def addImportMenuItem(self, import_menu):
|
||||
@ -235,31 +236,37 @@ class SongsPlugin(Plugin):
|
||||
If the first time wizard has run, this function is run to import all the
|
||||
new songs into the database.
|
||||
"""
|
||||
Receiver.send_message(u'openlp_process_events')
|
||||
self.onToolsReindexItemTriggered()
|
||||
Receiver.send_message(u'openlp_process_events')
|
||||
db_dir = unicode(os.path.join(
|
||||
unicode(gettempdir(), get_filesystem_encoding()), u'openlp'))
|
||||
if not os.path.exists(db_dir):
|
||||
return
|
||||
song_dbs = []
|
||||
song_count = 0
|
||||
for sfile in os.listdir(db_dir):
|
||||
if sfile.startswith(u'songs_') and sfile.endswith(u'.sqlite'):
|
||||
Receiver.send_message(u'openlp_process_events')
|
||||
song_dbs.append(os.path.join(db_dir, sfile))
|
||||
song_count += self._countSongs(os.path.join(db_dir, sfile))
|
||||
if not song_dbs:
|
||||
return
|
||||
Receiver.send_message(u'openlp_process_events')
|
||||
progress = QtGui.QProgressDialog(self.formParent)
|
||||
progress.setWindowModality(QtCore.Qt.WindowModal)
|
||||
progress.setWindowTitle(translate('OpenLP.Ui', 'Importing Songs'))
|
||||
progress.setLabelText(translate('OpenLP.Ui', 'Starting import...'))
|
||||
progress.setCancelButton(None)
|
||||
progress.setRange(0, len(song_dbs))
|
||||
progress.setRange(0, song_count)
|
||||
progress.setMinimumDuration(0)
|
||||
progress.forceShow()
|
||||
for idx, db in enumerate(song_dbs):
|
||||
progress.setValue(idx)
|
||||
Receiver.send_message(u'openlp_process_events')
|
||||
Receiver.send_message(u'openlp_process_events')
|
||||
for db in song_dbs:
|
||||
importer = OpenLPSongImport(self.manager, filename=db)
|
||||
importer.doImport()
|
||||
progress.setValue(len(song_dbs))
|
||||
importer.doImport(progress)
|
||||
Receiver.send_message(u'openlp_process_events')
|
||||
progress.setValue(song_count)
|
||||
self.mediaItem.onSearchTextButtonClicked()
|
||||
|
||||
def finalise(self):
|
||||
@ -287,3 +294,15 @@ class SongsPlugin(Plugin):
|
||||
songs = self.manager.get_all_objects(Song, Song.temporary == True)
|
||||
for song in songs:
|
||||
self.manager.delete_object(Song, song.id)
|
||||
|
||||
def _countSongs(self, db_file):
|
||||
connection = sqlite3.connect(db_file)
|
||||
cursor = connection.cursor()
|
||||
cursor.execute(u'SELECT COUNT(id) AS song_count FROM songs')
|
||||
song_count = cursor.fetchone()[0]
|
||||
connection.close()
|
||||
try:
|
||||
song_count = int(song_count)
|
||||
except (TypeError, ValueError):
|
||||
song_count = 0
|
||||
return song_count
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1571
resources/i18n/da.ts
1571
resources/i18n/da.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
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
3201
resources/i18n/fi.ts
3201
resources/i18n/fi.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
2286
resources/i18n/id.ts
2286
resources/i18n/id.ts
File diff suppressed because it is too large
Load Diff
3424
resources/i18n/it.ts
3424
resources/i18n/it.ts
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
3308
resources/i18n/ko.ts
3308
resources/i18n/ko.ts
File diff suppressed because it is too large
Load Diff
3184
resources/i18n/nb.ts
3184
resources/i18n/nb.ts
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
3534
resources/i18n/pl.ts
3534
resources/i18n/pl.ts
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1668
resources/i18n/ru.ts
1668
resources/i18n/ru.ts
File diff suppressed because it is too large
Load Diff
2912
resources/i18n/sk.ts
2912
resources/i18n/sk.ts
File diff suppressed because it is too large
Load Diff
3542
resources/i18n/sq.ts
3542
resources/i18n/sq.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
BIN
resources/images/media_audio.png
Normal file
BIN
resources/images/media_audio.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
BIN
resources/images/media_video.png
Normal file
BIN
resources/images/media_video.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
@ -130,7 +130,9 @@
|
||||
<qresource prefix="media">
|
||||
<file>media_time.png</file>
|
||||
<file>media_stop.png</file>
|
||||
<file>image_clapperboard.png</file>
|
||||
<file>media_audio.png</file>
|
||||
<file>media_video.png</file>
|
||||
<file>slidecontroller_multimedia.png</file>
|
||||
</qresource>
|
||||
<qresource prefix="messagebox">
|
||||
<file>messagebox_critical.png</file>
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 11 KiB |
BIN
resources/images/slidecontroller_multimedia.png
Normal file
BIN
resources/images/slidecontroller_multimedia.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
Loading…
Reference in New Issue
Block a user