Merge HEAD

This commit is contained in:
Arjan Schrijver 2013-03-03 17:53:33 +01:00
commit c02098faa8
79 changed files with 2897 additions and 1637 deletions

View File

@ -184,10 +184,8 @@ class OpenLP(QtGui.QApplication):
``traceback``
A traceback object with the details of where the exception occurred.
"""
if not hasattr(self, u'mainWindow'):
log.exception(''.join(format_exception(exctype, value, traceback)))
return
if not hasattr(self, u'exceptionForm'):
log.exception(''.join(format_exception(exctype, value, traceback)))
if not hasattr(self, u'exception_form'):
self.exception_form = ExceptionForm(self.main_window)
self.exception_form.exceptionTextEdit.setPlainText(''.join(format_exception(exctype, value, traceback)))
self.set_normal_cursor()

View File

@ -30,6 +30,7 @@
The :mod:`lib` module contains most of the components and libraries that make
OpenLP work.
"""
from distutils.version import LooseVersion
import logging
import os
@ -366,23 +367,23 @@ def create_separated_list(stringlist):
``stringlist``
List of unicode strings
"""
if Qt.PYQT_VERSION_STR >= u'4.9' and Qt.qVersion() >= u'4.8':
if LooseVersion(Qt.PYQT_VERSION_STR) >= LooseVersion(u'4.9') and \
LooseVersion(Qt.qVersion()) >= LooseVersion(u'4.8'):
return QtCore.QLocale().createSeparatedList(stringlist)
if not stringlist:
return u''
elif len(stringlist) == 1:
return stringlist[0]
elif len(stringlist) == 2:
return translate('OpenLP.core.lib', '%1 and %2',
return translate('OpenLP.core.lib', '%s and %s',
'Locale list separator: 2 items') % (stringlist[0], stringlist[1])
else:
merged = translate('OpenLP.core.lib', '%1, and %2',
merged = translate('OpenLP.core.lib', '%s, and %s',
u'Locale list separator: end') % (stringlist[-2], stringlist[-1])
for index in reversed(range(1, len(stringlist) - 2)):
merged = translate('OpenLP.core.lib', '%1, %2',
u'Locale list separator: middle') % (stringlist[index], merged)
return translate('OpenLP.core.lib', '%1, %2',
u'Locale list separator: start') % (stringlist[0], merged)
merged = translate('OpenLP.core.lib', '%s, %s',
u'Locale list separator: middle') % (stringlist[index], merged)
return translate('OpenLP.core.lib', '%s, %s', u'Locale list separator: start') % (stringlist[0], merged)
from registry import Registry
@ -393,7 +394,6 @@ from listwidgetwithdnd import ListWidgetWithDnD
from treewidgetwithdnd import TreeWidgetWithDnD
from formattingtags import FormattingTags
from spelltextedit import SpellTextEdit
from settingsmanager import SettingsManager
from plugin import PluginStatus, StringContent, Plugin
from pluginmanager import PluginManager
from settingstab import SettingsTab

View File

@ -36,8 +36,7 @@ from openlp.core.lib import Settings, translate
class FormattingTags(object):
"""
Static Class to HTML Tags to be access around the code the list is managed
by the Options Tab.
Static Class to HTML Tags to be access around the code the list is managed by the Options Tab.
"""
html_expands = []
@ -56,12 +55,11 @@ class FormattingTags(object):
tags = []
for tag in FormattingTags.html_expands:
if not tag[u'protected'] and not tag.get(u'temporary'):
# Using dict ensures that copy is made and encoding of values
# a little later does not affect tags in the original list
# Using dict ensures that copy is made and encoding of values a little later does not affect tags in
# the original list
tags.append(dict(tag))
tag = tags[-1]
# Remove key 'temporary' from tags.
# It is not needed to be saved.
# Remove key 'temporary' from tags. It is not needed to be saved.
if u'temporary' in tag:
del tag[u'temporary']
for element in tag:
@ -73,15 +71,12 @@ class FormattingTags(object):
@staticmethod
def load_tags():
"""
Load the Tags from store so can be used in the system or used to
update the display.
Load the Tags from store so can be used in the system or used to update the display.
"""
temporary_tags = [tag for tag in FormattingTags.html_expands
if tag.get(u'temporary')]
temporary_tags = [tag for tag in FormattingTags.html_expands if tag.get(u'temporary')]
FormattingTags.html_expands = []
base_tags = []
# Append the base tags.
# Hex Color tags from http://www.w3schools.com/html/html_colornames.asp
base_tags.append({u'desc': translate('OpenLP.FormattingTags', 'Red'),
u'start tag': u'{r}',
u'start html': u'<span style="-webkit-text-fill-color:red">',
@ -195,19 +190,17 @@ class FormattingTags(object):
The end tag, e. g. ``{/r}``
* start html
The start html tag. For instance ``<span style="
-webkit-text-fill-color:red">``
The start html tag. For instance ``<span style="-webkit-text-fill-color:red">``
* end html
The end html tag. For example ``</span>``
* protected
A boolean stating whether this is a build-in tag or not. Should be
``True`` in most cases.
A boolean stating whether this is a build-in tag or not. Should be ``True`` in most cases.
* temporary
A temporary tag will not be saved, but is also considered when
displaying text containing the tag. It has to be a ``boolean``.
A temporary tag will not be saved, but is also considered when displaying text containing the tag. It has
to be a ``boolean``.
"""
FormattingTags.html_expands.extend(tags)

View File

@ -229,25 +229,28 @@ class MediaManagerItem(QtGui.QWidget):
create_widget_action(self.listView, separator=True)
if self.hasDeleteIcon:
create_widget_action(self.listView,
u'listView%s%sItem' % (self.plugin.name.title(), StringContent.Delete.title()),
text=self.plugin.getString(StringContent.Delete)[u'title'],
icon=u':/general/general_delete.png',
shortcuts=[QtCore.Qt.Key_Delete], triggers=self.onDeleteClick)
can_shortcuts=True, triggers=self.onDeleteClick)
create_widget_action(self.listView, separator=True)
create_widget_action(self.listView,
u'listView%s%sItem' % (self.plugin.name.title(), StringContent.Preview.title()),
text=self.plugin.getString(StringContent.Preview)[u'title'],
icon=u':/general/general_preview.png',
shortcuts=[QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return],
can_shortcuts=True,
triggers=self.onPreviewClick)
create_widget_action(self.listView,
u'listView%s%sItem' % (self.plugin.name.title(), StringContent.Live.title()),
text=self.plugin.getString(StringContent.Live)[u'title'],
icon=u':/general/general_live.png',
shortcuts=[QtCore.Qt.ShiftModifier | QtCore.Qt.Key_Enter,
QtCore.Qt.ShiftModifier | QtCore.Qt.Key_Return],
can_shortcuts=True,
triggers=self.onLiveClick)
create_widget_action(self.listView,
u'listView%s%sItem' % (self.plugin.name.title(), StringContent.Service.title()),
can_shortcuts=True,
text=self.plugin.getString(StringContent.Service)[u'title'],
icon=u':/general/general_add.png',
shortcuts=[QtCore.Qt.Key_Plus, QtCore.Qt.Key_Equal],
triggers=self.onAddClick)
if self.addToServiceItem:
create_widget_action(self.listView, separator=True)

View File

@ -32,8 +32,10 @@ Provide plugin management
import os
import sys
import logging
import imp
from openlp.core.lib import Plugin, PluginStatus, Registry
from openlp.core.utils import AppLocation
log = logging.getLogger(__name__)
@ -45,65 +47,51 @@ class PluginManager(object):
"""
log.info(u'Plugin manager loaded')
def __init__(self, plugin_dir):
def __init__(self):
"""
The constructor for the plugin manager. Passes the controllers on to
the plugins for them to interact with via their ServiceItems.
``plugin_dir``
The directory to search for plugins.
"""
log.info(u'Plugin manager Initialising')
Registry().register(u'plugin_manager', self)
if not plugin_dir in sys.path:
log.debug(u'Inserting %s into sys.path', plugin_dir)
sys.path.insert(0, plugin_dir)
self.basepath = os.path.abspath(plugin_dir)
log.debug(u'Base path %s ', self.basepath)
self.base_path = os.path.abspath(AppLocation.get_directory(AppLocation.PluginsDir))
log.debug(u'Base path %s ', self.base_path)
self.plugins = []
log.info(u'Plugin manager Initialised')
def find_plugins(self, plugin_dir):
def find_plugins(self):
"""
Scan the directory ``plugin_dir`` for objects inheriting from the
``Plugin`` class.
``plugin_dir``
The directory to scan.
Scan a directory for objects inheriting from the ``Plugin`` class.
"""
log.info(u'Finding plugins')
startdepth = len(os.path.abspath(plugin_dir).split(os.sep))
log.debug(u'finding plugins in %s at depth %d',
unicode(plugin_dir), startdepth)
for root, dirs, files in os.walk(plugin_dir):
# TODO Presentation plugin is not yet working on Mac OS X.
# For now just ignore it. The following code will hide it
# in settings dialog.
if sys.platform == 'darwin':
present_plugin_dir = os.path.join(plugin_dir, 'presentations')
# Ignore files from the presentation plugin directory.
if root.startswith(present_plugin_dir):
continue
start_depth = len(os.path.abspath(self.base_path).split(os.sep))
present_plugin_dir = os.path.join(self.base_path, 'presentations')
log.debug(u'finding plugins in %s at depth %d', unicode(self.base_path), start_depth)
for root, dirs, files in os.walk(self.base_path):
if sys.platform == 'darwin' and root.startswith(present_plugin_dir):
# TODO Presentation plugin is not yet working on Mac OS X.
# For now just ignore it. The following code will ignore files from the presentation plugin directory
# and thereby never import the plugin.
continue
for name in files:
if name.endswith(u'.py') and not name.startswith(u'__'):
path = os.path.abspath(os.path.join(root, name))
thisdepth = len(path.split(os.sep))
if thisdepth - startdepth > 2:
this_depth = len(path.split(os.sep))
if this_depth - start_depth > 2:
# skip anything lower down
break
modulename = os.path.splitext(path)[0]
prefix = os.path.commonprefix([self.basepath, path])
# hack off the plugin base path
modulename = modulename[len(prefix) + 1:]
modulename = modulename.replace(os.path.sep, '.')
module_name = name[:-3]
# import the modules
log.debug(u'Importing %s from %s. Depth %d', modulename, path, thisdepth)
log.debug(u'Importing %s from %s. Depth %d', module_name, root, this_depth)
try:
__import__(modulename, globals(), locals(), [])
# Use the "imp" library to try to get around a problem with the PyUNO library which
# monkey-patches the __import__ function to do some magic. This causes issues with our tests.
# First, try to find the module we want to import, searching the directory in root
fp, path_name, description = imp.find_module(module_name, [root])
# Then load the module (do the actual import) using the details from find_module()
imp.load_module(module_name, fp, path_name, description)
except ImportError, e:
log.exception(u'Failed to import module %s on path %s for reason %s',
modulename, path, e.args[0])
log.exception(u'Failed to import module %s on path %s: %s', module_name, path, e.args[0])
plugin_classes = Plugin.__subclasses__()
plugin_objects = []
for p in plugin_classes:
@ -142,7 +130,8 @@ class PluginManager(object):
for plugin in self.plugins:
if plugin.status is not PluginStatus.Disabled:
plugin.createSettingsTab(settings_form)
settings_form.plugins = self.plugins
if settings_form:
settings_form.plugins = self.plugins
def hook_import_menu(self, import_menu):
"""

View File

@ -634,7 +634,7 @@ class ServiceItem(object):
"""
self.is_valid = True
for frame in self._raw_frames:
if self.is_image() and not os.path.exists((frame[u'path'])):
if self.is_image() and not os.path.exists(frame[u'path']):
self.is_valid = False
elif self.is_command():
file_name = os.path.join(frame[u'path'], frame[u'title'])

View File

@ -36,9 +36,8 @@ import sys
from PyQt4 import QtCore, QtGui
from openlp.core.lib import SlideLimits
from openlp.core.lib import SlideLimits, UiStrings
from openlp.core.lib.theme import ThemeLevel
from openlp.core.lib import UiStrings
log = logging.getLogger(__name__)
@ -70,7 +69,8 @@ class Settings(QtCore.QSettings):
``__obsolete_settings__``
Each entry is structured in the following way::
(u'general/enable slide loop', u'advanced/slide limits', [(SlideLimits.Wrap, True), (SlideLimits.End, False)])
(u'general/enable slide loop', u'advanced/slide limits',
[(SlideLimits.Wrap, True), (SlideLimits.End, False)])
The first entry is the *old key*; it will be removed.
@ -140,6 +140,7 @@ class Settings(QtCore.QSettings):
# circular dependency.
u'general/display on monitor': True,
u'general/override position': False,
u'images/background color': u'#000000',
u'media/players': u'webkit',
u'media/override player': QtCore.Qt.Unchecked,
u'players/background color': u'#000000',
@ -151,42 +152,99 @@ class Settings(QtCore.QSettings):
u'SettingsImport/type': u'OpenLP_settings_export',
u'SettingsImport/version': u'',
u'shortcuts/aboutItem': [QtGui.QKeySequence(u'Ctrl+F1')],
u'shortcuts/addToService': [],
u'shortcuts/audioPauseItem': [],
u'shortcuts/displayTagItem': [],
u'shortcuts/blankScreen': [QtCore.Qt.Key_Period],
u'shortcuts/collapse': [QtCore.Qt.Key_Minus],
u'shortcuts/blankScreen': [QtGui.QKeySequence(QtCore.Qt.Key_Period)],
u'shortcuts/collapse': [QtGui.QKeySequence(QtCore.Qt.Key_Minus)],
u'shortcuts/desktopScreen': [QtGui.QKeySequence(u'D')],
u'shortcuts/down': [QtCore.Qt.Key_Down],
u'shortcuts/escapeItem': [QtCore.Qt.Key_Escape],
u'shortcuts/expand': [QtCore.Qt.Key_Plus],
u'shortcuts/delete': [],
u'shortcuts/down': [QtGui.QKeySequence(QtCore.Qt.Key_Down)],
u'shortcuts/editSong': [],
u'shortcuts/escapeItem': [QtGui.QKeySequence(QtCore.Qt.Key_Escape)],
u'shortcuts/expand': [QtGui.QKeySequence(QtCore.Qt.Key_Plus)],
u'shortcuts/exportThemeItem': [],
u'shortcuts/fileNewItem': [QtGui.QKeySequence(u'Ctrl+N')],
u'shortcuts/fileSaveAsItem': [QtGui.QKeySequence(u'Ctrl+Shift+S')],
u'shortcuts/fileExitItem': [QtGui.QKeySequence(u'Alt+F4')],
u'shortcuts/fileSaveItem': [QtGui.QKeySequence(u'Ctrl+S')],
u'shortcuts/fileOpenItem': [QtGui.QKeySequence(u'Ctrl+O')],
u'shortcuts/goLive': [],
u'shortcuts/importThemeItem': [],
u'shortcuts/importBibleItem': [],
u'shortcuts/listViewBiblesDeleteItem': [QtGui.QKeySequence(QtCore.Qt.Key_Delete)],
u'shortcuts/listViewBiblesPreviewItem': [QtGui.QKeySequence(QtCore.Qt.Key_Enter),
QtGui.QKeySequence(QtCore.Qt.Key_Return)],
u'shortcuts/listViewBiblesLiveItem': [QtGui.QKeySequence(QtCore.Qt.ShiftModifier | QtCore.Qt.Key_Enter),
QtGui.QKeySequence(QtCore.Qt.ShiftModifier | QtCore.Qt.Key_Return)],
u'shortcuts/listViewBiblesServiceItem': [QtGui.QKeySequence(QtCore.Qt.Key_Plus),
QtGui.QKeySequence(QtCore.Qt.Key_Equal)],
u'shortcuts/listViewCustomDeleteItem': [QtGui.QKeySequence(QtCore.Qt.Key_Delete)],
u'shortcuts/listViewCustomPreviewItem': [QtGui.QKeySequence(QtCore.Qt.Key_Enter),
QtGui.QKeySequence(QtCore.Qt.Key_Return)],
u'shortcuts/listViewCustomLiveItem': [QtGui.QKeySequence(QtCore.Qt.ShiftModifier | QtCore.Qt.Key_Enter),
QtGui.QKeySequence(QtCore.Qt.ShiftModifier | QtCore.Qt.Key_Return)],
u'shortcuts/listViewCustomServiceItem': [QtGui.QKeySequence(QtCore.Qt.Key_Plus),
QtGui.QKeySequence(QtCore.Qt.Key_Equal)],
u'shortcuts/listViewImagesDeleteItem': [QtGui.QKeySequence(QtCore.Qt.Key_Delete)],
u'shortcuts/listViewImagesPreviewItem': [QtGui.QKeySequence(QtCore.Qt.Key_Enter),
QtGui.QKeySequence(QtCore.Qt.Key_Return)],
u'shortcuts/listViewImagesLiveItem': [QtGui.QKeySequence(QtCore.Qt.ShiftModifier | QtCore.Qt.Key_Enter),
QtGui.QKeySequence(QtCore.Qt.ShiftModifier | QtCore.Qt.Key_Return)],
u'shortcuts/listViewImagesServiceItem': [QtGui.QKeySequence(QtCore.Qt.Key_Plus),
QtGui.QKeySequence(QtCore.Qt.Key_Equal)],
u'shortcuts/listViewMediaDeleteItem': [QtGui.QKeySequence(QtCore.Qt.Key_Delete)],
u'shortcuts/listViewMediaPreviewItem': [QtGui.QKeySequence(QtCore.Qt.Key_Enter),
QtGui.QKeySequence(QtCore.Qt.Key_Return)],
u'shortcuts/listViewMediaLiveItem': [QtGui.QKeySequence(QtCore.Qt.ShiftModifier | QtCore.Qt.Key_Enter),
QtGui.QKeySequence(QtCore.Qt.ShiftModifier | QtCore.Qt.Key_Return)],
u'shortcuts/listViewMediaServiceItem': [QtGui.QKeySequence(QtCore.Qt.Key_Plus),
QtGui.QKeySequence(QtCore.Qt.Key_Equal)],
u'shortcuts/listViewPresentationsDeleteItem': [QtGui.QKeySequence(QtCore.Qt.Key_Delete)],
u'shortcuts/listViewPresentationsPreviewItem': [QtGui.QKeySequence(QtCore.Qt.Key_Enter),
QtGui.QKeySequence(QtCore.Qt.Key_Return)],
u'shortcuts/listViewPresentationsLiveItem': [QtGui.QKeySequence(QtCore.Qt.ShiftModifier | QtCore.Qt.Key_Enter),
QtGui.QKeySequence(QtCore.Qt.ShiftModifier | QtCore.Qt.Key_Return)],
u'shortcuts/listViewPresentationsServiceItem': [QtGui.QKeySequence(QtCore.Qt.Key_Plus),
QtGui.QKeySequence(QtCore.Qt.Key_Equal)],
u'shortcuts/listViewSongsDeleteItem': [QtGui.QKeySequence(QtCore.Qt.Key_Delete)],
u'shortcuts/listViewSongsPreviewItem': [QtGui.QKeySequence(QtCore.Qt.Key_Enter),
QtGui.QKeySequence(QtCore.Qt.Key_Return)],
u'shortcuts/listViewSongsLiveItem': [QtGui.QKeySequence(QtCore.Qt.ShiftModifier | QtCore.Qt.Key_Enter),
QtGui.QKeySequence(QtCore.Qt.ShiftModifier | QtCore.Qt.Key_Return)],
u'shortcuts/listViewSongsServiceItem': [QtGui.QKeySequence(QtCore.Qt.Key_Plus),
QtGui.QKeySequence(QtCore.Qt.Key_Equal)],
u'shortcuts/lockPanel': [],
u'shortcuts/modeDefaultItem': [],
u'shortcuts/modeLiveItem': [],
u'shortcuts/make_live': [QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return],
u'shortcuts/moveUp': [QtCore.Qt.Key_PageUp],
u'shortcuts/moveTop': [QtCore.Qt.Key_Home],
u'shortcuts/make_live': [QtGui.QKeySequence(QtCore.Qt.Key_Enter), QtGui.QKeySequence(QtCore.Qt.Key_Return)],
u'shortcuts/moveUp': [QtGui.QKeySequence(QtCore.Qt.Key_PageUp)],
u'shortcuts/moveTop': [QtGui.QKeySequence(QtCore.Qt.Key_Home)],
u'shortcuts/modeSetupItem': [],
u'shortcuts/moveBottom': [QtCore.Qt.Key_End],
u'shortcuts/moveDown': [QtCore.Qt.Key_PageDown],
u'shortcuts/moveBottom': [QtGui.QKeySequence(QtCore.Qt.Key_End)],
u'shortcuts/moveDown': [QtGui.QKeySequence(QtCore.Qt.Key_PageDown)],
u'shortcuts/nextTrackItem': [],
u'shortcuts/nextItem_live': [QtCore.Qt.Key_Down, QtCore.Qt.Key_PageDown],
u'shortcuts/nextService': [QtCore.Qt.Key_Right],
u'shortcuts/nextItem_live': [QtGui.QKeySequence(QtCore.Qt.Key_Down),
QtGui.QKeySequence(QtCore.Qt.Key_PageDown)],
u'shortcuts/nextItem_preview': [],
u'shortcuts/nextService': [QtGui.QKeySequence(QtCore.Qt.Key_Right)],
u'shortcuts/newService': [],
u'shortcuts/offlineHelpItem': [],
u'shortcuts/onlineHelpItem': [QtGui.QKeySequence(u'Alt+F1')],
u'shortcuts/previousItem_live': [QtCore.Qt.Key_Up, QtCore.Qt.Key_PageUp],
u'shortcuts/openService': [],
u'shortcuts/saveService': [],
u'shortcuts/previousItem_live': [QtGui.QKeySequence(QtCore.Qt.Key_Up),
QtGui.QKeySequence(QtCore.Qt.Key_PageUp)],
u'shortcuts/playbackPause': [],
u'shortcuts/playbackPlay': [],
u'shortcuts/playbackStop': [],
u'shortcuts/playSlidesLoop': [],
u'shortcuts/playSlidesOnce': [],
u'shortcuts/previousService': [QtCore.Qt.Key_Left],
u'shortcuts/previousService': [QtGui.QKeySequence(QtCore.Qt.Key_Left)],
u'shortcuts/previousItem_preview': [],
u'shortcuts/printServiceItem': [QtGui.QKeySequence(u'Ctrl+P')],
u'shortcuts/songExportItem': [],
u'shortcuts/songUsageStatus': [QtCore.Qt.Key_F4],
u'shortcuts/songUsageStatus': [QtGui.QKeySequence(QtCore.Qt.Key_F4)],
u'shortcuts/settingsShortcutsItem': [],
u'shortcuts/settingsImportItem': [],
u'shortcuts/settingsPluginListItem': [QtGui.QKeySequence(u'Alt+F7')],
@ -199,17 +257,27 @@ class Settings(QtCore.QSettings):
u'shortcuts/shortcutAction_O': [QtGui.QKeySequence(u'O')],
u'shortcuts/shortcutAction_P': [QtGui.QKeySequence(u'P')],
u'shortcuts/shortcutAction_V': [QtGui.QKeySequence(u'V')],
u'shortcuts/shortcutAction_0': [QtGui.QKeySequence(u'0')],
u'shortcuts/shortcutAction_1': [QtGui.QKeySequence(u'1')],
u'shortcuts/shortcutAction_2': [QtGui.QKeySequence(u'2')],
u'shortcuts/shortcutAction_3': [QtGui.QKeySequence(u'3')],
u'shortcuts/shortcutAction_4': [QtGui.QKeySequence(u'4')],
u'shortcuts/shortcutAction_5': [QtGui.QKeySequence(u'5')],
u'shortcuts/shortcutAction_6': [QtGui.QKeySequence(u'6')],
u'shortcuts/shortcutAction_7': [QtGui.QKeySequence(u'7')],
u'shortcuts/shortcutAction_8': [QtGui.QKeySequence(u'8')],
u'shortcuts/shortcutAction_9': [QtGui.QKeySequence(u'9')],
u'shortcuts/settingsExportItem': [],
u'shortcuts/songUsageReport': [],
u'shortcuts/songImportItem': [],
u'shortcuts/themeScreen': [QtGui.QKeySequence(u'T')],
u'shortcuts/toolsReindexItem': [],
u'shortcuts/toolsAlertItem': [u'F7'],
u'shortcuts/toolsAlertItem': [QtGui.QKeySequence(u'F7')],
u'shortcuts/toolsFirstTimeWizard': [],
u'shortcuts/toolsOpenDataFolder': [],
u'shortcuts/toolsAddToolItem': [],
u'shortcuts/updateThemeImages': [],
u'shortcuts/up': [QtCore.Qt.Key_Up],
u'shortcuts/up': [QtGui.QKeySequence(QtCore.Qt.Key_Up)],
u'shortcuts/viewThemeManagerItem': [QtGui.QKeySequence(u'F10')],
u'shortcuts/viewMediaManagerItem': [QtGui.QKeySequence(u'F8')],
u'shortcuts/viewPreviewPanel': [QtGui.QKeySequence(u'F11')],
@ -259,7 +327,6 @@ class Settings(QtCore.QSettings):
"""
Settings.__default_settings__ = dict(default_values.items() + Settings.__default_settings__.items())
@staticmethod
def set_filename(ini_file):
"""
@ -287,6 +354,14 @@ class Settings(QtCore.QSettings):
else:
QtCore.QSettings.__init__(self, *args)
def get_default_value(self, key):
"""
Get the default value of the given key
"""
if self.group():
key = self.group() + u'/' + key
return Settings.__default_settings__[key]
def remove_obsolete_settings(self):
"""
This method is only called to clean up the config. It removes old settings and it renames settings. See

View File

@ -1,66 +0,0 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2013 Raoul Snyman #
# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
Provide handling for persisting OpenLP settings. OpenLP uses QSettings to manage settings persistence. QSettings
provides a single API for saving and retrieving settings from the application but writes to disk in an OS dependant
format.
"""
import os
from openlp.core.utils import AppLocation
class SettingsManager(object):
"""
Class to provide helper functions for the loading and saving of application settings.
"""
@staticmethod
def get_files(section=None, extension=None):
"""
Get a list of files from the data files path.
``section``
Defaults to *None*. The section of code getting the files - used to load from a section's data subdirectory.
``extension``
Defaults to *None*. The extension to search for.
"""
path = AppLocation.get_data_path()
if section:
path = os.path.join(path, section)
try:
files = os.listdir(path)
except OSError:
return []
if extension:
return [filename for filename in files if extension == os.path.splitext(filename)[1]]
else:
# no filtering required
return files

View File

@ -69,9 +69,8 @@ def add_welcome_page(parent, image):
def create_button_box(dialog, name, standard_buttons, custom_buttons=None):
"""
Creates a QDialogButtonBox with the given buttons. The ``accepted()`` and
``rejected()`` signals of the button box are connected with the dialogs
``accept()`` and ``reject()`` slots.
Creates a QDialogButtonBox with the given buttons. The ``accepted()`` and ``rejected()`` signals of the button box
are connected with the dialogs ``accept()`` and ``reject()`` slots.
``dialog``
The parent object. This has to be a ``QDialog`` descendant.
@ -80,13 +79,12 @@ def create_button_box(dialog, name, standard_buttons, custom_buttons=None):
A string which is set as object name.
``standard_buttons``
A list of strings for the used buttons. It might contain: ``ok``,
``save``, ``cancel``, ``close``, and ``defaults``.
A list of strings for the used buttons. It might contain: ``ok``, ``save``, ``cancel``, ``close``, and
``defaults``.
``custom_buttons``
A list of additional buttons. If a item is a instance of
QtGui.QAbstractButton it is added with QDialogButtonBox.ActionRole.
Otherwhise the item has to be a tuple of a button and a ButtonRole.
A list of additional buttons. If a item is a instance of QtGui.QAbstractButton it is added with
QDialogButtonBox.ActionRole. Otherwhise the item has to be a tuple of a button and a ButtonRole.
"""
if custom_buttons is None:
custom_buttons = []
@ -109,15 +107,14 @@ def create_button_box(dialog, name, standard_buttons, custom_buttons=None):
button_box.addButton(button, QtGui.QDialogButtonBox.ActionRole)
else:
button_box.addButton(*button)
QtCore.QObject.connect(button_box, QtCore.SIGNAL(u'accepted()'), dialog.accept)
QtCore.QObject.connect(button_box, QtCore.SIGNAL(u'rejected()'), dialog.reject)
button_box.accepted.connect(dialog.accept)
button_box.rejected.connect(dialog.reject)
return button_box
def critical_error_message_box(title=None, message=None, parent=None, question=False):
"""
Provides a standard critical message box for errors that OpenLP displays
to users.
Provides a standard critical message box for errors that OpenLP displays to users.
``title``
The title for the message box.
@ -134,7 +131,6 @@ def critical_error_message_box(title=None, message=None, parent=None, question=F
if question:
return QtGui.QMessageBox.critical(parent, UiStrings().Error, message,
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No))
data = {u'message': message}
return Registry().get(u'main_window').error_message(title if title else UiStrings().Error, message)
@ -166,16 +162,14 @@ def create_button(parent, name, **kwargs):
A string which is set as object name (required).
``role``
A string which can have one value out of ``delete``, ``up``, and
``down``. This decides about default values for properties like text,
icon, or tooltip.
A string which can have one value out of ``delete``, ``up``, and ``down``. This decides about default values
for properties like text, icon, or tooltip.
``text``
A string for the action text.
``icon``
Either a QIcon, a resource string, or a file location string for the
action icon.
Either a QIcon, a resource string, or a file location string for the action icon.
``tooltip``
A string for the action tool tip.
@ -195,8 +189,7 @@ def create_button(parent, name, **kwargs):
kwargs.setdefault(u'icon', u':/services/service_down.png')
kwargs.setdefault(u'tooltip', translate('OpenLP.Ui', 'Move selection down one position.'))
else:
log.warn(u'The role "%s" is not defined in create_push_button().',
role)
log.warn(u'The role "%s" is not defined in create_push_button().', role)
if kwargs.pop(u'class', u'') == u'toolbutton':
button = QtGui.QToolButton(parent)
else:
@ -211,7 +204,7 @@ def create_button(parent, name, **kwargs):
if not kwargs.pop(u'enabled', True):
button.setEnabled(False)
if kwargs.get(u'click'):
QtCore.QObject.connect(button, QtCore.SIGNAL(u'clicked()'), kwargs.pop(u'click'))
button.clicked.connect(kwargs.pop(u'click'))
for key in kwargs.keys():
if key not in [u'text', u'icon', u'tooltip', u'click']:
log.warn(u'Parameter %s was not consumed in create_button().', key)
@ -256,8 +249,10 @@ def create_action(parent, name, **kwargs):
``data``
The action's data.
``shortcuts``
A QList<QKeySequence> (or a list of strings) which are set as shortcuts.
``can_shortcuts``
Capability stating if this action can have shortcuts. If ``True`` the action is added to shortcut dialog
otherwise it it not. Define your shortcut in the :class:`~openlp.core.lib.Settings` class. *Note*: When *not*
``True`` you *must not* set a shortcuts at all.
``context``
A context for the shortcut execution.
@ -289,27 +284,24 @@ def create_action(parent, name, **kwargs):
action.setSeparator(True)
if u'data' in kwargs:
action.setData(kwargs.pop(u'data'))
if kwargs.get(u'shortcuts'):
action.setShortcuts(kwargs.pop(u'shortcuts'))
if kwargs.pop(u'can_shortcuts', False):
action_list = ActionList.get_instance()
action_list.add_action(action, kwargs.pop(u'category', None))
if u'context' in kwargs:
action.setShortcutContext(kwargs.pop(u'context'))
if kwargs.get(u'category'):
action_list = ActionList.get_instance()
action_list.add_action(action, unicode(kwargs.pop(u'category')))
if kwargs.get(u'triggers'):
QtCore.QObject.connect(action, QtCore.SIGNAL(u'triggered(bool)'),
kwargs.pop(u'triggers'))
action.triggered.connect(kwargs.pop(u'triggers'))
for key in kwargs.keys():
if key not in [u'text', u'icon', u'tooltip', u'statustip', u'checked', u'shortcuts', u'category', u'triggers']:
if key not in [u'text', u'icon', u'tooltip', u'statustip', u'checked', u'can_shortcuts',
u'category', u'triggers']:
log.warn(u'Parameter %s was not consumed in create_action().', key)
return action
def create_widget_action(parent, name=u'', **kwargs):
"""
Return a new QAction by calling ``create_action(parent, name, **kwargs)``.
The shortcut context defaults to ``QtCore.Qt.WidgetShortcut`` and the action
is added to the parents action list.
Return a new QAction by calling ``create_action(parent, name, **kwargs)``. The shortcut context defaults to
``QtCore.Qt.WidgetShortcut`` and the action is added to the parents action list.
"""
kwargs.setdefault(u'context', QtCore.Qt.WidgetShortcut)
action = create_action(parent, name, **kwargs)
@ -334,8 +326,7 @@ def set_case_insensitive_completer(cache, widget):
def create_valign_selection_widgets(parent):
"""
Creates a standard label and combo box for asking users to select a
vertical alignment.
Creates a standard label and combo box for asking users to select a vertical alignment.
``parent``
The parent object. This should be a ``QWidget`` descendant.

View File

@ -106,7 +106,7 @@ class ExceptionForm(QtGui.QDialog, Ui_ExceptionDialog):
"""
QtGui.QDialog.__init__(self, parent)
self.setupUi(self)
self.settingsSection = u'crashreport'
self.settings_section = u'crashreport'
def exec_(self):
"""
@ -159,12 +159,11 @@ class ExceptionForm(QtGui.QDialog, Ui_ExceptionDialog):
'--- Library Versions ---\n%s\n')
filename = QtGui.QFileDialog.getSaveFileName(self,
translate('OpenLP.ExceptionForm', 'Save Crash Report'),
Settings().value(self.settingsSection + u'/last directory'),
translate('OpenLP.ExceptionForm',
'Text files (*.txt *.log *.text)'))
Settings().value(self.settings_section + u'/last directory'),
translate('OpenLP.ExceptionForm', 'Text files (*.txt *.log *.text)'))
if filename:
filename = unicode(filename).replace(u'/', os.path.sep)
Settings().setValue(self.settingsSection + u'/last directory', os.path.dirname(filename))
Settings().setValue(self.settings_section + u'/last directory', os.path.dirname(filename))
report_text = report_text % self._createReport()
try:
report_file = open(filename, u'w')
@ -230,7 +229,7 @@ class ExceptionForm(QtGui.QDialog, Ui_ExceptionDialog):
"""
files = QtGui.QFileDialog.getOpenFileName(
self, translate('ImagePlugin.ExceptionDialog', 'Select Attachment'),
Settings().value(self.settingsSection + u'/last directory'), u'%s (*.*) (*)' % UiStrings().AllFiles)
Settings().value(self.settings_section + u'/last directory'), u'%s (*.*) (*)' % UiStrings().AllFiles)
log.info(u'New files(s) %s', unicode(files))
if files:
self.fileAttachment = unicode(files)

View File

@ -34,18 +34,18 @@ from PyQt4 import QtGui
from filerenamedialog import Ui_FileRenameDialog
from openlp.core.lib import translate
from openlp.core.lib import translate, Registry
class FileRenameForm(QtGui.QDialog, Ui_FileRenameDialog):
"""
The file rename dialog
"""
def __init__(self, parent):
def __init__(self):
"""
Constructor
"""
QtGui.QDialog.__init__(self, parent)
QtGui.QDialog.__init__(self, self.main_window)
self.setupUi(self)
def exec_(self, copy=False):
@ -56,4 +56,15 @@ class FileRenameForm(QtGui.QDialog, Ui_FileRenameDialog):
self.setWindowTitle(translate('OpenLP.FileRenameForm', 'File Copy'))
else:
self.setWindowTitle(translate('OpenLP.FileRenameForm', 'File Rename'))
self.fileNameEdit.setFocus()
return QtGui.QDialog.exec_(self)
def _get_main_window(self):
"""
Adds the main window to the class dynamically
"""
if not hasattr(self, u'_main_window'):
self._main_window = Registry().get(u'main_window')
return self._main_window
main_window = property(_get_main_window)

View File

@ -300,7 +300,7 @@ class MainDisplay(Display):
self.image(path)
# Update the preview frame.
if self.isLive:
self.parent().updatePreview()
self.live_controller.updatePreview()
return True
def image(self, path):
@ -513,6 +513,16 @@ class MainDisplay(Display):
application = property(_get_application)
def _get_live_controller(self):
"""
Adds the live controller to the class dynamically
"""
if not hasattr(self, u'_live_controller'):
self._live_controller = Registry().get(u'live_controller')
return self._live_controller
live_controller = property(_get_live_controller)
class AudioPlayer(QtCore.QObject):
"""

View File

@ -174,99 +174,101 @@ class Ui_MainWindow(object):
main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.themeManagerDock)
# Create the menu items
action_list = ActionList.get_instance()
action_list.add_category(UiStrings().File, CategoryOrder.standardMenu)
action_list.add_category(UiStrings().File, CategoryOrder.standard_menu)
self.fileNewItem = create_action(main_window, u'fileNewItem',
icon=u':/general/general_new.png',
shortcuts=[QtGui.QKeySequence(u'Ctrl+N')],
can_shortcuts=True,
category=UiStrings().File,
triggers=self.serviceManagerContents.on_new_service_clicked)
self.fileOpenItem = create_action(main_window, u'fileOpenItem',
icon=u':/general/general_open.png',
shortcuts=[QtGui.QKeySequence(u'Ctrl+O')],
can_shortcuts=True,
category=UiStrings().File,
triggers=self.serviceManagerContents.on_load_service_clicked)
self.fileSaveItem = create_action(main_window, u'fileSaveItem',
icon=u':/general/general_save.png',
shortcuts=[QtGui.QKeySequence(u'Ctrl+S')],
can_shortcuts=True,
category=UiStrings().File,
triggers=self.serviceManagerContents.save_file)
self.fileSaveAsItem = create_action(main_window, u'fileSaveAsItem',
shortcuts=[QtGui.QKeySequence(u'Ctrl+Shift+S')],
can_shortcuts=True,
category=UiStrings().File,
triggers=self.serviceManagerContents.save_file_as)
self.printServiceOrderItem = create_action(main_window,
u'printServiceItem', shortcuts=[QtGui.QKeySequence(u'Ctrl+P')],
u'printServiceItem', can_shortcuts=True,
category=UiStrings().File,
triggers=self.serviceManagerContents.print_service_order)
self.fileExitItem = create_action(main_window, u'fileExitItem',
icon=u':/system/system_exit.png',
shortcuts=[QtGui.QKeySequence(u'Alt+F4')],
can_shortcuts=True,
category=UiStrings().File, triggers=main_window.close)
# Give QT Extra Hint that this is the Exit Menu Item
self.fileExitItem.setMenuRole(QtGui.QAction.QuitRole)
action_list.add_category(UiStrings().Import, CategoryOrder.standardMenu)
self.importThemeItem = create_action(main_window, u'importThemeItem', category=UiStrings().Import)
action_list.add_category(UiStrings().Import, CategoryOrder.standard_menu)
self.importThemeItem = create_action(
main_window, u'importThemeItem', category=UiStrings().Import, can_shortcuts=True)
self.importLanguageItem = create_action(main_window, u'importLanguageItem')
action_list.add_category(UiStrings().Export, CategoryOrder.standardMenu)
self.exportThemeItem = create_action(main_window, u'exportThemeItem', category=UiStrings().Export)
action_list.add_category(UiStrings().Export, CategoryOrder.standard_menu)
self.exportThemeItem = create_action(
main_window, u'exportThemeItem', category=UiStrings().Export, can_shortcuts=True)
self.exportLanguageItem = create_action(main_window, u'exportLanguageItem')
action_list.add_category(UiStrings().View, CategoryOrder.standardMenu)
action_list.add_category(UiStrings().View, CategoryOrder.standard_menu)
self.viewMediaManagerItem = create_action(main_window,
u'viewMediaManagerItem', shortcuts=[QtGui.QKeySequence(u'F8')],
u'viewMediaManagerItem',
icon=u':/system/system_mediamanager.png',
checked=self.mediaManagerDock.isVisible(),
can_shortcuts=True,
category=UiStrings().View, triggers=self.toggleMediaManager)
self.viewThemeManagerItem = create_action(main_window,
u'viewThemeManagerItem', shortcuts=[QtGui.QKeySequence(u'F10')],
u'viewThemeManagerItem', can_shortcuts=True,
icon=u':/system/system_thememanager.png',
checked=self.themeManagerDock.isVisible(),
category=UiStrings().View, triggers=self.toggleThemeManager)
self.viewServiceManagerItem = create_action(main_window,
u'viewServiceManagerItem', shortcuts=[QtGui.QKeySequence(u'F9')],
u'viewServiceManagerItem', can_shortcuts=True,
icon=u':/system/system_servicemanager.png',
checked=self.serviceManagerDock.isVisible(),
category=UiStrings().View, triggers=self.toggleServiceManager)
self.viewPreviewPanel = create_action(main_window, u'viewPreviewPanel',
shortcuts=[QtGui.QKeySequence(u'F11')], checked=previewVisible,
can_shortcuts=True, checked=previewVisible,
category=UiStrings().View, triggers=self.setPreviewPanelVisibility)
self.viewLivePanel = create_action(main_window, u'viewLivePanel',
shortcuts=[QtGui.QKeySequence(u'F12')], checked=liveVisible,
can_shortcuts=True, checked=liveVisible,
category=UiStrings().View, triggers=self.setLivePanelVisibility)
self.lockPanel = create_action(main_window, u'lockPanel',
checked=panelLocked, triggers=self.setLockPanel)
action_list.add_category(UiStrings().ViewMode,
CategoryOrder.standardMenu)
self.modeDefaultItem = create_action(main_window, u'modeDefaultItem', checked=False,
category=UiStrings().ViewMode)
self.modeSetupItem = create_action(main_window, u'modeSetupItem', checked=False, category=UiStrings().ViewMode)
self.modeLiveItem = create_action(main_window, u'modeLiveItem', checked=True, category=UiStrings().ViewMode)
can_shortcuts=True, checked=panelLocked,
category=UiStrings().View,
triggers=self.setLockPanel)
action_list.add_category(UiStrings().ViewMode, CategoryOrder.standard_menu)
self.modeDefaultItem = create_action(
main_window, u'modeDefaultItem', checked=False, category=UiStrings().ViewMode, can_shortcuts=True)
self.modeSetupItem = create_action(
main_window, u'modeSetupItem', checked=False, category=UiStrings().ViewMode, can_shortcuts=True)
self.modeLiveItem = create_action(
main_window, u'modeLiveItem', checked=True, category=UiStrings().ViewMode, can_shortcuts=True)
self.modeGroup = QtGui.QActionGroup(main_window)
self.modeGroup.addAction(self.modeDefaultItem)
self.modeGroup.addAction(self.modeSetupItem)
self.modeGroup.addAction(self.modeLiveItem)
self.modeDefaultItem.setChecked(True)
action_list.add_category(UiStrings().Tools, CategoryOrder.standardMenu)
action_list.add_category(UiStrings().Tools, CategoryOrder.standard_menu)
self.toolsAddToolItem = create_action(main_window,
u'toolsAddToolItem', icon=u':/tools/tools_add.png',
category=UiStrings().Tools)
u'toolsAddToolItem', icon=u':/tools/tools_add.png', category=UiStrings().Tools, can_shortcuts=True)
self.toolsOpenDataFolder = create_action(main_window,
u'toolsOpenDataFolder', icon=u':/general/general_open.png',
category=UiStrings().Tools)
u'toolsOpenDataFolder', icon=u':/general/general_open.png', category=UiStrings().Tools, can_shortcuts=True)
self.toolsFirstTimeWizard = create_action(main_window,
u'toolsFirstTimeWizard', icon=u':/general/general_revert.png',
category=UiStrings().Tools)
category=UiStrings().Tools, can_shortcuts=True)
self.updateThemeImages = create_action(main_window,
u'updateThemeImages', category=UiStrings().Tools)
action_list.add_category(UiStrings().Settings,
CategoryOrder.standardMenu)
u'updateThemeImages', category=UiStrings().Tools, can_shortcuts=True)
action_list.add_category(UiStrings().Settings, CategoryOrder.standard_menu)
self.settingsPluginListItem = create_action(main_window,
u'settingsPluginListItem',
icon=u':/system/settings_plugin_list.png',
shortcuts=[QtGui.QKeySequence(u'Alt+F7')],
can_shortcuts=True,
category=UiStrings().Settings, triggers=self.onPluginItemClicked)
# i18n Language Items
self.autoLanguageItem = create_action(main_window, u'autoLanguageItem',
checked=LanguageManager.auto_language)
self.autoLanguageItem = create_action(main_window, u'autoLanguageItem', checked=LanguageManager.auto_language)
self.languageGroup = QtGui.QActionGroup(main_window)
self.languageGroup.setExclusive(True)
self.languageGroup.setObjectName(u'languageGroup')
@ -277,20 +279,21 @@ class Ui_MainWindow(object):
languageItem = create_action(main_window, key, checked=qmList[key] == savedLanguage)
add_actions(self.languageGroup, [languageItem])
self.settingsShortcutsItem = create_action(main_window, u'settingsShortcutsItem',
icon=u':/system/system_configure_shortcuts.png', category=UiStrings().Settings)
icon=u':/system/system_configure_shortcuts.png', category=UiStrings().Settings, can_shortcuts=True)
# Formatting Tags were also known as display tags.
self.formattingTagItem = create_action(main_window, u'displayTagItem',
icon=u':/system/tag_editor.png', category=UiStrings().Settings)
icon=u':/system/tag_editor.png', category=UiStrings().Settings, can_shortcuts=True)
self.settingsConfigureItem = create_action(main_window, u'settingsConfigureItem',
icon=u':/system/system_settings.png', category=UiStrings().Settings)
icon=u':/system/system_settings.png', can_shortcuts=True, category=UiStrings().Settings)
# Give QT Extra Hint that this is the Preferences Menu Item
self.settingsConfigureItem.setMenuRole(QtGui.QAction.PreferencesRole)
self.settingsImportItem = create_action(main_window, u'settingsImportItem', category=UiStrings().Settings)
self.settingsExportItem = create_action(main_window, u'settingsExportItem', category=UiStrings().Settings)
action_list.add_category(UiStrings().Help, CategoryOrder.standardMenu)
self.settingsImportItem = create_action(
main_window, u'settingsImportItem', category=UiStrings().Import, can_shortcuts=True)
self.settingsExportItem = create_action(
main_window, u'settingsExportItem', category=UiStrings().Export, can_shortcuts=True)
action_list.add_category(UiStrings().Help, CategoryOrder.standard_menu)
self.aboutItem = create_action(main_window, u'aboutItem', icon=u':/system/system_about.png',
shortcuts=[QtGui.QKeySequence(u'Ctrl+F1')],
category=UiStrings().Help, triggers=self.onAboutItemClicked)
can_shortcuts=True, category=UiStrings().Help, triggers=self.onAboutItemClicked)
# Give QT Extra Hint that this is an About Menu Item
self.aboutItem.setMenuRole(QtGui.QAction.AboutRole)
if os.name == u'nt':
@ -298,13 +301,13 @@ class Ui_MainWindow(object):
AppLocation.get_directory(AppLocation.AppDir), 'OpenLP.chm')
self.offlineHelpItem = create_action(main_window, u'offlineHelpItem',
icon=u':/system/system_help_contents.png',
shortcuts=[QtGui.QKeySequence(u'F1')],
can_shortcuts=True,
category=UiStrings().Help, triggers=self.onOfflineHelpClicked)
self.onlineHelpItem = create_action(main_window, u'onlineHelpItem',
icon=u':/system/system_online_help.png',
shortcuts=[QtGui.QKeySequence(u'Alt+F1')],
can_shortcuts=True,
category=UiStrings().Help, triggers=self.onOnlineHelpClicked)
self.webSiteItem = create_action(main_window, u'webSiteItem', category=UiStrings().Help)
self.webSiteItem = create_action(main_window, u'webSiteItem', can_shortcuts=True, category=UiStrings().Help)
add_actions(self.fileImportMenu, (self.settingsImportItem, None, self.importThemeItem, self.importLanguageItem))
add_actions(self.fileExportMenu, (self.settingsExportItem, None, self.exportThemeItem, self.exportLanguageItem))
add_actions(self.fileMenu, (self.fileNewItem, self.fileOpenItem,
@ -487,8 +490,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
self.timer_id = 0
self.timer_version_id = 0
# Set up the path with plugins
plugin_path = AppLocation.get_directory(AppLocation.PluginsDir)
self.plugin_manager = PluginManager(plugin_path)
self.plugin_manager = PluginManager()
self.imageManager = ImageManager()
# Set up the interface
self.setupUi(self)
@ -541,7 +543,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
# Define the media Dock Manager
self.mediaDockManager = MediaDockManager(self.mediaToolBox)
log.info(u'Load Plugins')
self.plugin_manager.find_plugins(plugin_path)
self.plugin_manager.find_plugins()
# hook methods have to happen after find_plugins. Find plugins needs
# the controllers hence the hooks have moved from setupUI() to here
# Find and insert settings tabs
@ -1342,7 +1344,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
self.version_text)
else:
# the thread has not confirmed it is running or it has not yet sent any data so lets keep waiting
if not hasattr(self,u'version_update_running') or self.version_update_running:
if not hasattr(self, u'version_update_running') or self.version_update_running:
self.timer_version_id = self.startTimer(1000)
self.application.process_events()

View File

@ -2,7 +2,7 @@
# Python ctypes bindings for VLC
#
# Copyright (C) 2009-2013 the VideoLAN team
# Copyright (C) 2009-2012 the VideoLAN team
# $Id: $
#
# Authors: Olivier Aubert <olivier.aubert at liris.cnrs.fr>
@ -48,7 +48,7 @@ import sys
from inspect import getargspec
__version__ = "N/A"
build_date = "Fri Oct 5 21:35:59 2012"
build_date = "Wed Feb 13 18:40:24 2013"
if sys.version_info[0] > 2:
str = str
@ -1000,6 +1000,50 @@ class MediaTrackInfo(_Cstruct):
('rate_or_width', ctypes.c_uint ),
]
class AudioTrack(_Cstruct):
_fields_ = [
('channels', ctypes.c_uint),
('rate', ctypes.c_uint),
]
class VideoTrack(_Cstruct):
_fields_ = [
('height', ctypes.c_uint),
('width', ctypes.c_uint),
('sar_num', ctypes.c_uint),
('sar_den', ctypes.c_uint),
('frame_rate_num', ctypes.c_uint),
('frame_rate_den', ctypes.c_uint),
]
class SubtitleTrack(_Cstruct):
_fields_ = [
('encoding', ctypes.c_char_p),
]
class MediaTrackTracks(ctypes.Union):
_fields_ = [
('audio', ctypes.POINTER(AudioTrack)),
('video', ctypes.POINTER(VideoTrack)),
('subtitle', ctypes.POINTER(SubtitleTrack)),
]
class MediaTrack(_Cstruct):
_anonymous_ = ("u",)
_fields_ = [
('codec', ctypes.c_uint32),
('original_fourcc', ctypes.c_uint32),
('id', ctypes.c_int ),
('type', TrackType ),
('profile', ctypes.c_int ),
('level', ctypes.c_int ),
('u', MediaTrackTracks),
('bitrate', ctypes.c_uint),
('language', ctypes.c_char_p),
('description', ctypes.c_char_p),
]
class PlaylistItem(_Cstruct):
_fields_ = [
('id', ctypes.c_int ),
@ -1122,6 +1166,17 @@ def module_description_list(head):
libvlc_module_description_list_release(head)
return r
class AudioOutputDevice(_Cstruct):
def __str__(self):
return '%s(%d:%s)' % (self.__class__.__name__, self.id, self.name)
AudioOutputDevice._fields_ = [ # recursive struct
('next', ctypes.POINTER(AudioOutputDevice)),
('device', ctypes.c_char_p ),
('description', ctypes.c_char_p),
]
# End of header.py #
class EventManager(_Ctype):
@ -1282,7 +1337,7 @@ class Instance(_Ctype):
m = libvlc_media_new_location(self, str_to_bytes(mrl))
else:
# Else it should be a local path.
m = libvlc_media_new_path(self, str_to_bytes(mrl))
m = libvlc_media_new_path(self, str_to_bytes(os.path.normpath(mrl)))
for o in options:
libvlc_media_add_option(m, str_to_bytes(o))
m._instance = self
@ -1361,27 +1416,6 @@ class Instance(_Ctype):
'''
return libvlc_set_user_agent(self, name, http)
def get_log_verbosity(self):
'''Always returns minus one.
This function is only provided for backward compatibility.
@return: always -1.
'''
return libvlc_get_log_verbosity(self)
def set_log_verbosity(self, level):
'''This function does nothing.
It is only provided for backward compatibility.
@param level: ignored.
'''
return libvlc_set_log_verbosity(self, level)
def log_open(self):
'''This function does nothing useful.
It is only provided for backward compatibility.
@return: an unique pointer or NULL on error.
'''
return libvlc_log_open(self)
def media_new_location(self, psz_mrl):
'''Create a media with a certain given media resource location,
for instance a valid URL.
@ -1445,34 +1479,26 @@ class Instance(_Ctype):
return libvlc_media_library_new(self)
def audio_output_list_get(self):
'''Get the list of available audio outputs.
'''Gets the list of available audio outputs.
@return: list of available audio outputs. It must be freed it with In case of error, NULL is returned.
'''
return libvlc_audio_output_list_get(self)
def audio_output_device_count(self, psz_audio_output):
'''Get count of devices for audio output, these devices are hardware oriented
like analor or digital output of sound card.
@param psz_audio_output: - name of audio output, See L{AudioOutput}.
@return: number of devices.
def audio_output_device_list_get(self, aout):
'''Gets a list of audio output devices for a given audio output.
See L{audio_output_device_set}().
@note: Not all audio outputs support this. In particular, an empty (NULL)
list of devices does B{not} imply that the specified audio output does
not work.
@note: The list might not be exhaustive.
@warning: Some audio output devices in the list might not actually work in
some circumstances. By default, it is recommended to not specify any
explicit audio device.
@param psz_aout: audio output name (as returned by L{audio_output_list_get}()).
@return: A NULL-terminated linked list of potential audio output devices. It must be freed it with L{audio_output_device_list_release}().
@version: LibVLC 2.1.0 or later.
'''
return libvlc_audio_output_device_count(self, psz_audio_output)
def audio_output_device_longname(self, psz_audio_output, i_device):
'''Get long name of device, if not available short name given.
@param psz_audio_output: - name of audio output, See L{AudioOutput}.
@param i_device: device index.
@return: long name of device.
'''
return libvlc_audio_output_device_longname(self, psz_audio_output, i_device)
def audio_output_device_id(self, psz_audio_output, i_device):
'''Get id name of device.
@param psz_audio_output: - name of audio output, See L{AudioOutput}.
@param i_device: device index.
@return: id name of device, use for setting device, need to be free after use.
'''
return libvlc_audio_output_device_id(self, psz_audio_output, i_device)
return libvlc_audio_output_device_list_get(self, aout)
def vlm_release(self):
'''Release the vlm instance related to the given L{Instance}.
@ -1683,87 +1709,6 @@ class Instance(_Ctype):
'''
return libvlc_vlm_get_event_manager(self)
class Log(_Ctype):
'''Create a new VLC log instance.
'''
def __new__(cls, ptr=_internal_guard):
'''(INTERNAL) ctypes wrapper constructor.
'''
return _Constructor(cls, ptr)
def __iter__(self):
return self.get_iterator()
def dump(self):
return [ str(m) for m in self ]
def close(self):
'''Frees memory allocated by L{open}().
'''
return libvlc_log_close(self)
def count(self):
'''Always returns zero.
This function is only provided for backward compatibility.
@return: always zero.
'''
return libvlc_log_count(self)
def __len__(self):
return libvlc_log_count(self)
def clear(self):
'''This function does nothing.
It is only provided for backward compatibility.
'''
return libvlc_log_clear(self)
def get_iterator(self):
'''This function does nothing useful.
It is only provided for backward compatibility.
@return: an unique pointer or NULL on error or if the parameter was NULL.
'''
return libvlc_log_get_iterator(self)
class LogIterator(_Ctype):
'''Create a new VLC log iterator.
'''
def __new__(cls, ptr=_internal_guard):
'''(INTERNAL) ctypes wrapper constructor.
'''
return _Constructor(cls, ptr)
def __iter__(self):
return self
def next(self):
if self.has_next():
b = LogMessage()
i = libvlc_log_iterator_next(self, b)
return i.contents
raise StopIteration
def __next__(self):
return self.next()
def free(self):
'''Frees memory allocated by L{log_get_iterator}().
'''
return libvlc_log_iterator_free(self)
def has_next(self):
'''Always returns zero.
This function is only provided for backward compatibility.
@return: always zero.
'''
return libvlc_log_iterator_has_next(self)
class Media(_Ctype):
'''Create a new Media instance.
@ -1809,11 +1754,13 @@ class Media(_Ctype):
This option will be used to determine how the media_player will
read the media. This allows to use VLC's advanced
reading/streaming options on a per-media basis.
The options are detailed in vlc --long-help, for instance
"--sout-all". Note that all options are not usable on medias:
specifically, due to architectural issues, video-related options
such as text renderer options cannot be set on a single media. They
must be set on the whole libvlc instance instead.
@note: The options are listed in 'vlc --long-help' from the command line,
e.g. "-sout-all". Keep in mind that available options and their semantics
vary across LibVLC versions and builds.
@warning: Not all options affects L{Media} objects:
Specifically, due to architectural issues most audio and video options,
such as text renderer options, have no effects on an individual media.
These options must be set through L{new}() instead.
@param ppsz_options: the options (as a string).
'''
return libvlc_media_add_option(self, ppsz_options)
@ -1906,6 +1853,14 @@ class Media(_Ctype):
'''
return libvlc_media_get_stats(self, p_stats)
def subitems(self):
'''Get subitems of media descriptor object. This will increment
the reference count of supplied media descriptor object. Use
L{list_release}() to decrement the reference counting.
@return: list of media descriptor subitems or NULL.
'''
return libvlc_media_subitems(self)
def event_manager(self):
'''Get event manager from media descriptor object.
NOTE: this function doesn't increment reference counting.
@ -1925,7 +1880,7 @@ class Media(_Ctype):
The method is synchronous.
See L{parse_async}
See L{get_meta}
See L{get_tracks_info}.
See libvlc_media_get_tracks_info.
'''
return libvlc_media_parse(self)
@ -1939,7 +1894,7 @@ class Media(_Ctype):
See L{parse}
See libvlc_MediaParsedChanged
See L{get_meta}
See L{get_tracks_info}.
See libvlc_media_get_tracks_info.
'''
return libvlc_media_parse_async(self)
@ -1965,15 +1920,16 @@ class Media(_Ctype):
'''
return libvlc_media_get_user_data(self)
def get_tracks_info(self):
def tracks_get(self, tracks):
'''Get media descriptor's elementary streams description
Note, you need to call L{parse}() or play the media at least once
before calling this function.
Not doing this will result in an empty array.
@param tracks: address to store an allocated array of Elementary Streams descriptions (must be freed by the caller) [OUT].
@return: the number of Elementary Streams.
@param tracks: address to store an allocated array of Elementary Streams descriptions (must be freed with L{tracks_release}.
@return: the number of Elementary Streams (zero on error).
@version: LibVLC 2.1.0 and later.
'''
return libvlc_media_get_tracks_info(self)
return libvlc_media_tracks_get(self, tracks)
def player_new_from_media(self):
'''Create a Media Player object from a Media.
@ -2255,7 +2211,7 @@ class MediaListPlayer(_Ctype):
return libvlc_media_list_player_play(self)
def pause(self):
'''Pause media list.
'''Toggle pause (or resume) media list.
'''
return libvlc_media_list_player_pause(self)
@ -2681,13 +2637,14 @@ class MediaPlayer(_Ctype):
return libvlc_media_player_set_time(self, i_time)
def get_position(self):
'''Get movie position.
'''Get movie position as percentage between 0.0 and 1.0.
@return: movie position, or -1. in case of error.
'''
return libvlc_media_player_get_position(self)
def set_position(self, f_pos):
'''Set movie position. This has no effect if playback is not enabled.
'''Set movie position as percentage between 0.0 and 1.0.
This has no effect if playback is not enabled.
This might not work depending on the underlying input format and protocol.
@param f_pos: the position.
'''
@ -2968,13 +2925,13 @@ class MediaPlayer(_Ctype):
def video_get_track(self):
'''Get current video track.
@return: the video track (int) or -1 if none.
@return: the video track ID (int) or -1 if no active input.
'''
return libvlc_video_get_track(self)
def video_set_track(self, i_track):
'''Set video track.
@param i_track: the track (int).
@param i_track: the track ID (i_id field from track description).
@return: 0 on success, -1 if out of range.
'''
return libvlc_video_set_track(self, i_track)
@ -3084,33 +3041,30 @@ class MediaPlayer(_Ctype):
return libvlc_video_set_adjust_float(self, option, value)
def audio_output_set(self, psz_name):
'''Set the audio output.
Change will be applied after stop and play.
'''Sets the audio output.
@note: Any change will take be effect only after playback is stopped and
restarted. Audio output cannot be changed while playing.
@param psz_name: name of audio output, use psz_name of See L{AudioOutput}.
@return: 0 if function succeded, -1 on error.
'''
return libvlc_audio_output_set(self, psz_name)
def audio_output_device_set(self, psz_audio_output, psz_device_id):
'''Set audio output device. Changes are only effective after stop and play.
'''Configures an explicit audio output device for a given audio output plugin.
A list of possible devices can be obtained with
L{audio_output_device_list_get}().
@note: This function does not select the specified audio output plugin.
L{audio_output_set}() is used for that purpose.
@warning: The syntax for the device parameter depends on the audio output.
This is not portable. Only use this function if you know what you are doing.
Some audio outputs do not support this function (e.g. PulseAudio, WASAPI).
Some audio outputs require further parameters (e.g. ALSA: channels map).
@param psz_audio_output: - name of audio output, See L{AudioOutput}.
@param psz_device_id: device.
@return: Nothing. Errors are ignored.
'''
return libvlc_audio_output_device_set(self, psz_audio_output, psz_device_id)
def audio_output_get_device_type(self):
'''Get current audio device type. Device type describes something like
character of output sound - stereo sound, 2.1, 5.1 etc.
@return: the audio devices type See libvlc_audio_output_device_types_t.
'''
return libvlc_audio_output_get_device_type(self)
def audio_output_set_device_type(self, device_type):
'''Set current audio device type.
@param device_type: the audio device type,
'''
return libvlc_audio_output_set_device_type(self, device_type)
def audio_toggle_mute(self):
'''Toggle mute status.
'''
@ -3118,13 +3072,13 @@ class MediaPlayer(_Ctype):
def audio_get_mute(self):
'''Get current mute status.
@return: the mute status (boolean) \libvlc_return_bool.
@return: the mute status (boolean) if defined, -1 if undefined/unapplicable.
'''
return libvlc_audio_get_mute(self)
def audio_set_mute(self, status):
'''Set mute status.
@param status: If status is true then mute, otherwise unmute.
@param status: If status is true then mute, otherwise unmute @warning This function does not always work. If there are no active audio playback stream, the mute status might not be available. If digital pass-through (S/PDIF, HDMI...) is in use, muting may be unapplicable. Also some audio output plugins do not support muting at all. @note To force silent playback, disable all audio tracks. This is more efficient and reliable than mute.
'''
return libvlc_audio_set_mute(self, status)
@ -3149,13 +3103,13 @@ class MediaPlayer(_Ctype):
def audio_get_track(self):
'''Get current audio track.
@return: the audio track (int), or -1 if none.
@return: the audio track ID or -1 if no active input.
'''
return libvlc_audio_get_track(self)
def audio_set_track(self, i_track):
'''Set current audio track.
@param i_track: the track (int).
@param i_track: the track ID (i_id field from track description).
@return: 0 on success, -1 on error.
'''
return libvlc_audio_set_track(self, i_track)
@ -3397,112 +3351,6 @@ def libvlc_log_unsubscribe(sub):
None, ctypes.c_void_p)
return f(sub)
def libvlc_get_log_verbosity(p_instance):
'''Always returns minus one.
This function is only provided for backward compatibility.
@param p_instance: ignored.
@return: always -1.
'''
f = _Cfunctions.get('libvlc_get_log_verbosity', None) or \
_Cfunction('libvlc_get_log_verbosity', ((1,),), None,
ctypes.c_uint, Instance)
return f(p_instance)
def libvlc_set_log_verbosity(p_instance, level):
'''This function does nothing.
It is only provided for backward compatibility.
@param p_instance: ignored.
@param level: ignored.
'''
f = _Cfunctions.get('libvlc_set_log_verbosity', None) or \
_Cfunction('libvlc_set_log_verbosity', ((1,), (1,),), None,
None, Instance, ctypes.c_uint)
return f(p_instance, level)
def libvlc_log_open(p_instance):
'''This function does nothing useful.
It is only provided for backward compatibility.
@param p_instance: libvlc instance.
@return: an unique pointer or NULL on error.
'''
f = _Cfunctions.get('libvlc_log_open', None) or \
_Cfunction('libvlc_log_open', ((1,),), class_result(Log),
ctypes.c_void_p, Instance)
return f(p_instance)
def libvlc_log_close(p_log):
'''Frees memory allocated by L{libvlc_log_open}().
@param p_log: libvlc log instance or NULL.
'''
f = _Cfunctions.get('libvlc_log_close', None) or \
_Cfunction('libvlc_log_close', ((1,),), None,
None, Log)
return f(p_log)
def libvlc_log_count(p_log):
'''Always returns zero.
This function is only provided for backward compatibility.
@param p_log: ignored.
@return: always zero.
'''
f = _Cfunctions.get('libvlc_log_count', None) or \
_Cfunction('libvlc_log_count', ((1,),), None,
ctypes.c_uint, Log)
return f(p_log)
def libvlc_log_clear(p_log):
'''This function does nothing.
It is only provided for backward compatibility.
@param p_log: ignored.
'''
f = _Cfunctions.get('libvlc_log_clear', None) or \
_Cfunction('libvlc_log_clear', ((1,),), None,
None, Log)
return f(p_log)
def libvlc_log_get_iterator(p_log):
'''This function does nothing useful.
It is only provided for backward compatibility.
@param p_log: ignored.
@return: an unique pointer or NULL on error or if the parameter was NULL.
'''
f = _Cfunctions.get('libvlc_log_get_iterator', None) or \
_Cfunction('libvlc_log_get_iterator', ((1,),), class_result(LogIterator),
ctypes.c_void_p, Log)
return f(p_log)
def libvlc_log_iterator_free(p_iter):
'''Frees memory allocated by L{libvlc_log_get_iterator}().
@param p_iter: libvlc log iterator or NULL.
'''
f = _Cfunctions.get('libvlc_log_iterator_free', None) or \
_Cfunction('libvlc_log_iterator_free', ((1,),), None,
None, LogIterator)
return f(p_iter)
def libvlc_log_iterator_has_next(p_iter):
'''Always returns zero.
This function is only provided for backward compatibility.
@param p_iter: ignored.
@return: always zero.
'''
f = _Cfunctions.get('libvlc_log_iterator_has_next', None) or \
_Cfunction('libvlc_log_iterator_has_next', ((1,),), None,
ctypes.c_int, LogIterator)
return f(p_iter)
def libvlc_log_iterator_next(p_iter, p_buffer):
'''Always returns NULL.
This function is only provided for backward compatibility.
@param p_iter: libvlc log iterator or NULL.
@param p_buffer: ignored.
@return: always NULL.
'''
f = _Cfunctions.get('libvlc_log_iterator_next', None) or \
_Cfunction('libvlc_log_iterator_next', ((1,), (1,),), None,
ctypes.POINTER(LogMessage), LogIterator, ctypes.POINTER(LogMessage))
return f(p_iter, p_buffer)
def libvlc_module_description_list_release(p_list):
'''Release a list of module descriptions.
@param p_list: the list to be released.
@ -3615,11 +3463,13 @@ def libvlc_media_add_option(p_md, ppsz_options):
This option will be used to determine how the media_player will
read the media. This allows to use VLC's advanced
reading/streaming options on a per-media basis.
The options are detailed in vlc --long-help, for instance
"--sout-all". Note that all options are not usable on medias:
specifically, due to architectural issues, video-related options
such as text renderer options cannot be set on a single media. They
must be set on the whole libvlc instance instead.
@note: The options are listed in 'vlc --long-help' from the command line,
e.g. "-sout-all". Keep in mind that available options and their semantics
vary across LibVLC versions and builds.
@warning: Not all options affects L{Media} objects:
Specifically, due to architectural issues most audio and video options,
such as text renderer options, have no effects on an individual media.
These options must be set through L{libvlc_new}() instead.
@param p_md: the media descriptor.
@param ppsz_options: the options (as a string).
'''
@ -3756,6 +3606,18 @@ def libvlc_media_get_stats(p_md, p_stats):
ctypes.c_int, Media, ctypes.POINTER(MediaStats))
return f(p_md, p_stats)
def libvlc_media_subitems(p_md):
'''Get subitems of media descriptor object. This will increment
the reference count of supplied media descriptor object. Use
L{libvlc_media_list_release}() to decrement the reference counting.
@param p_md: media descriptor object.
@return: list of media descriptor subitems or NULL.
'''
f = _Cfunctions.get('libvlc_media_subitems', None) or \
_Cfunction('libvlc_media_subitems', ((1,),), class_result(MediaList),
ctypes.c_void_p, Media)
return f(p_md)
def libvlc_media_event_manager(p_md):
'''Get event manager from media descriptor object.
NOTE: this function doesn't increment reference counting.
@ -3783,7 +3645,7 @@ def libvlc_media_parse(p_md):
The method is synchronous.
See L{libvlc_media_parse_async}
See L{libvlc_media_get_meta}
See L{libvlc_media_get_tracks_info}.
See libvlc_media_get_tracks_info.
@param p_md: media descriptor object.
'''
f = _Cfunctions.get('libvlc_media_parse', None) or \
@ -3801,7 +3663,7 @@ def libvlc_media_parse_async(p_md):
See L{libvlc_media_parse}
See libvlc_MediaParsedChanged
See L{libvlc_media_get_meta}
See L{libvlc_media_get_tracks_info}.
See libvlc_media_get_tracks_info.
@param p_md: media descriptor object.
'''
f = _Cfunctions.get('libvlc_media_parse_async', None) or \
@ -3843,19 +3705,31 @@ def libvlc_media_get_user_data(p_md):
ctypes.c_void_p, Media)
return f(p_md)
def libvlc_media_get_tracks_info(p_md):
def libvlc_media_tracks_get(p_md, tracks):
'''Get media descriptor's elementary streams description
Note, you need to call L{libvlc_media_parse}() or play the media at least once
before calling this function.
Not doing this will result in an empty array.
@param p_md: media descriptor object.
@param tracks: address to store an allocated array of Elementary Streams descriptions (must be freed by the caller) [OUT].
@return: the number of Elementary Streams.
@param tracks: address to store an allocated array of Elementary Streams descriptions (must be freed with L{libvlc_media_tracks_release}.
@return: the number of Elementary Streams (zero on error).
@version: LibVLC 2.1.0 and later.
'''
f = _Cfunctions.get('libvlc_media_get_tracks_info', None) or \
_Cfunction('libvlc_media_get_tracks_info', ((1,), (2,),), None,
ctypes.c_int, Media, ctypes.POINTER(ctypes.c_void_p))
return f(p_md)
f = _Cfunctions.get('libvlc_media_tracks_get', None) or \
_Cfunction('libvlc_media_tracks_get', ((1,), (1,),), None,
ctypes.c_uint, Media, ctypes.POINTER(ctypes.POINTER(MediaTrack)))
return f(p_md, tracks)
def libvlc_media_tracks_release(p_tracks, i_count):
'''Release media descriptor's elementary streams description array.
@param p_tracks: tracks info array to release.
@param i_count: number of elements in the array.
@version: LibVLC 2.1.0 and later.
'''
f = _Cfunctions.get('libvlc_media_tracks_release', None) or \
_Cfunction('libvlc_media_tracks_release', ((1,), (1,),), None,
None, ctypes.POINTER(MediaTrack), ctypes.c_uint)
return f(p_tracks, i_count)
def libvlc_media_discoverer_new_from_name(p_inst, psz_name):
'''Discover media service by name.
@ -4208,7 +4082,7 @@ def libvlc_media_list_player_play(p_mlp):
return f(p_mlp)
def libvlc_media_list_player_pause(p_mlp):
'''Pause media list.
'''Toggle pause (or resume) media list.
@param p_mlp: media list player instance.
'''
f = _Cfunctions.get('libvlc_media_list_player_pause', None) or \
@ -4673,7 +4547,7 @@ def libvlc_media_player_set_time(p_mi, i_time):
return f(p_mi, i_time)
def libvlc_media_player_get_position(p_mi):
'''Get movie position.
'''Get movie position as percentage between 0.0 and 1.0.
@param p_mi: the Media Player.
@return: movie position, or -1. in case of error.
'''
@ -4683,7 +4557,8 @@ def libvlc_media_player_get_position(p_mi):
return f(p_mi)
def libvlc_media_player_set_position(p_mi, f_pos):
'''Set movie position. This has no effect if playback is not enabled.
'''Set movie position as percentage between 0.0 and 1.0.
This has no effect if playback is not enabled.
This might not work depending on the underlying input format and protocol.
@param p_mi: the Media Player.
@param f_pos: the position.
@ -4894,14 +4769,6 @@ def libvlc_track_description_list_release(p_track_description):
None, ctypes.POINTER(TrackDescription))
return f(p_track_description)
def libvlc_track_description_release(p_track_description):
'''\deprecated Use L{libvlc_track_description_list_release} instead.
'''
f = _Cfunctions.get('libvlc_track_description_release', None) or \
_Cfunction('libvlc_track_description_release', ((1,),), None,
None, ctypes.POINTER(TrackDescription))
return f(p_track_description)
def libvlc_toggle_fullscreen(p_mi):
'''Toggle fullscreen status on non-embedded video outputs.
@warning: The same limitations applies to this function
@ -5219,7 +5086,7 @@ def libvlc_video_get_track_description(p_mi):
def libvlc_video_get_track(p_mi):
'''Get current video track.
@param p_mi: media player.
@return: the video track (int) or -1 if none.
@return: the video track ID (int) or -1 if no active input.
'''
f = _Cfunctions.get('libvlc_video_get_track', None) or \
_Cfunction('libvlc_video_get_track', ((1,),), None,
@ -5229,7 +5096,7 @@ def libvlc_video_get_track(p_mi):
def libvlc_video_set_track(p_mi, i_track):
'''Set video track.
@param p_mi: media player.
@param i_track: the track (int).
@param i_track: the track ID (i_id field from track description).
@return: 0 on success, -1 if out of range.
'''
f = _Cfunctions.get('libvlc_video_set_track', None) or \
@ -5394,7 +5261,7 @@ def libvlc_video_set_adjust_float(p_mi, option, value):
return f(p_mi, option, value)
def libvlc_audio_output_list_get(p_instance):
'''Get the list of available audio outputs.
'''Gets the list of available audio outputs.
@param p_instance: libvlc instance.
@return: list of available audio outputs. It must be freed it with In case of error, NULL is returned.
'''
@ -5404,7 +5271,7 @@ def libvlc_audio_output_list_get(p_instance):
return f(p_instance)
def libvlc_audio_output_list_release(p_list):
'''Free the list of available audio outputs.
'''Frees the list of available audio outputs.
@param p_list: list with audio outputs for release.
'''
f = _Cfunctions.get('libvlc_audio_output_list_release', None) or \
@ -5413,8 +5280,9 @@ def libvlc_audio_output_list_release(p_list):
return f(p_list)
def libvlc_audio_output_set(p_mi, psz_name):
'''Set the audio output.
Change will be applied after stop and play.
'''Sets the audio output.
@note: Any change will take be effect only after playback is stopped and
restarted. Audio output cannot be changed while playing.
@param p_mi: media player.
@param psz_name: name of audio output, use psz_name of See L{AudioOutput}.
@return: 0 if function succeded, -1 on error.
@ -5424,77 +5292,59 @@ def libvlc_audio_output_set(p_mi, psz_name):
ctypes.c_int, MediaPlayer, ctypes.c_char_p)
return f(p_mi, psz_name)
def libvlc_audio_output_device_count(p_instance, psz_audio_output):
'''Get count of devices for audio output, these devices are hardware oriented
like analor or digital output of sound card.
def libvlc_audio_output_device_list_get(p_instance, aout):
'''Gets a list of audio output devices for a given audio output.
See L{libvlc_audio_output_device_set}().
@note: Not all audio outputs support this. In particular, an empty (NULL)
list of devices does B{not} imply that the specified audio output does
not work.
@note: The list might not be exhaustive.
@warning: Some audio output devices in the list might not actually work in
some circumstances. By default, it is recommended to not specify any
explicit audio device.
@param p_instance: libvlc instance.
@param psz_audio_output: - name of audio output, See L{AudioOutput}.
@return: number of devices.
@param psz_aout: audio output name (as returned by L{libvlc_audio_output_list_get}()).
@return: A NULL-terminated linked list of potential audio output devices. It must be freed it with L{libvlc_audio_output_device_list_release}().
@version: LibVLC 2.1.0 or later.
'''
f = _Cfunctions.get('libvlc_audio_output_device_count', None) or \
_Cfunction('libvlc_audio_output_device_count', ((1,), (1,),), None,
ctypes.c_int, Instance, ctypes.c_char_p)
return f(p_instance, psz_audio_output)
f = _Cfunctions.get('libvlc_audio_output_device_list_get', None) or \
_Cfunction('libvlc_audio_output_device_list_get', ((1,), (1,),), None,
ctypes.POINTER(AudioOutputDevice), Instance, ctypes.c_char_p)
return f(p_instance, aout)
def libvlc_audio_output_device_longname(p_instance, psz_audio_output, i_device):
'''Get long name of device, if not available short name given.
@param p_instance: libvlc instance.
@param psz_audio_output: - name of audio output, See L{AudioOutput}.
@param i_device: device index.
@return: long name of device.
def libvlc_audio_output_device_list_release(p_list):
'''Frees a list of available audio output devices.
@param p_list: list with audio outputs for release.
@version: LibVLC 2.1.0 or later.
'''
f = _Cfunctions.get('libvlc_audio_output_device_longname', None) or \
_Cfunction('libvlc_audio_output_device_longname', ((1,), (1,), (1,),), string_result,
ctypes.c_void_p, Instance, ctypes.c_char_p, ctypes.c_int)
return f(p_instance, psz_audio_output, i_device)
def libvlc_audio_output_device_id(p_instance, psz_audio_output, i_device):
'''Get id name of device.
@param p_instance: libvlc instance.
@param psz_audio_output: - name of audio output, See L{AudioOutput}.
@param i_device: device index.
@return: id name of device, use for setting device, need to be free after use.
'''
f = _Cfunctions.get('libvlc_audio_output_device_id', None) or \
_Cfunction('libvlc_audio_output_device_id', ((1,), (1,), (1,),), string_result,
ctypes.c_void_p, Instance, ctypes.c_char_p, ctypes.c_int)
return f(p_instance, psz_audio_output, i_device)
f = _Cfunctions.get('libvlc_audio_output_device_list_release', None) or \
_Cfunction('libvlc_audio_output_device_list_release', ((1,),), None,
None, ctypes.POINTER(AudioOutputDevice))
return f(p_list)
def libvlc_audio_output_device_set(p_mi, psz_audio_output, psz_device_id):
'''Set audio output device. Changes are only effective after stop and play.
'''Configures an explicit audio output device for a given audio output plugin.
A list of possible devices can be obtained with
L{libvlc_audio_output_device_list_get}().
@note: This function does not select the specified audio output plugin.
L{libvlc_audio_output_set}() is used for that purpose.
@warning: The syntax for the device parameter depends on the audio output.
This is not portable. Only use this function if you know what you are doing.
Some audio outputs do not support this function (e.g. PulseAudio, WASAPI).
Some audio outputs require further parameters (e.g. ALSA: channels map).
@param p_mi: media player.
@param psz_audio_output: - name of audio output, See L{AudioOutput}.
@param psz_device_id: device.
@return: Nothing. Errors are ignored.
'''
f = _Cfunctions.get('libvlc_audio_output_device_set', None) or \
_Cfunction('libvlc_audio_output_device_set', ((1,), (1,), (1,),), None,
None, MediaPlayer, ctypes.c_char_p, ctypes.c_char_p)
return f(p_mi, psz_audio_output, psz_device_id)
def libvlc_audio_output_get_device_type(p_mi):
'''Get current audio device type. Device type describes something like
character of output sound - stereo sound, 2.1, 5.1 etc.
@param p_mi: media player.
@return: the audio devices type See libvlc_audio_output_device_types_t.
'''
f = _Cfunctions.get('libvlc_audio_output_get_device_type', None) or \
_Cfunction('libvlc_audio_output_get_device_type', ((1,),), None,
ctypes.c_int, MediaPlayer)
return f(p_mi)
def libvlc_audio_output_set_device_type(p_mi, device_type):
'''Set current audio device type.
@param p_mi: vlc instance.
@param device_type: the audio device type,
'''
f = _Cfunctions.get('libvlc_audio_output_set_device_type', None) or \
_Cfunction('libvlc_audio_output_set_device_type', ((1,), (1,),), None,
None, MediaPlayer, ctypes.c_int)
return f(p_mi, device_type)
def libvlc_audio_toggle_mute(p_mi):
'''Toggle mute status.
@param p_mi: media player.
@param p_mi: media player @warning Toggling mute atomically is not always possible: On some platforms, other processes can mute the VLC audio playback stream asynchronously. Thus, there is a small race condition where toggling will not work. See also the limitations of L{libvlc_audio_set_mute}().
'''
f = _Cfunctions.get('libvlc_audio_toggle_mute', None) or \
_Cfunction('libvlc_audio_toggle_mute', ((1,),), None,
@ -5504,7 +5354,7 @@ def libvlc_audio_toggle_mute(p_mi):
def libvlc_audio_get_mute(p_mi):
'''Get current mute status.
@param p_mi: media player.
@return: the mute status (boolean) \libvlc_return_bool.
@return: the mute status (boolean) if defined, -1 if undefined/unapplicable.
'''
f = _Cfunctions.get('libvlc_audio_get_mute', None) or \
_Cfunction('libvlc_audio_get_mute', ((1,),), None,
@ -5514,7 +5364,7 @@ def libvlc_audio_get_mute(p_mi):
def libvlc_audio_set_mute(p_mi, status):
'''Set mute status.
@param p_mi: media player.
@param status: If status is true then mute, otherwise unmute.
@param status: If status is true then mute, otherwise unmute @warning This function does not always work. If there are no active audio playback stream, the mute status might not be available. If digital pass-through (S/PDIF, HDMI...) is in use, muting may be unapplicable. Also some audio output plugins do not support muting at all. @note To force silent playback, disable all audio tracks. This is more efficient and reliable than mute.
'''
f = _Cfunctions.get('libvlc_audio_set_mute', None) or \
_Cfunction('libvlc_audio_set_mute', ((1,), (1,),), None,
@ -5565,7 +5415,7 @@ def libvlc_audio_get_track_description(p_mi):
def libvlc_audio_get_track(p_mi):
'''Get current audio track.
@param p_mi: media player.
@return: the audio track (int), or -1 if none.
@return: the audio track ID or -1 if no active input.
'''
f = _Cfunctions.get('libvlc_audio_get_track', None) or \
_Cfunction('libvlc_audio_get_track', ((1,),), None,
@ -5575,7 +5425,7 @@ def libvlc_audio_get_track(p_mi):
def libvlc_audio_set_track(p_mi, i_track):
'''Set current audio track.
@param p_mi: media player.
@param i_track: the track (int).
@param i_track: the track ID (i_id field from track description).
@return: 0 on success, -1 on error.
'''
f = _Cfunctions.get('libvlc_audio_set_track', None) or \
@ -5933,11 +5783,14 @@ def libvlc_vlm_get_event_manager(p_instance):
return f(p_instance)
# 2 function(s) blacklisted:
# 4 function(s) blacklisted:
# libvlc_audio_output_get_device_type
# libvlc_audio_output_set_device_type
# libvlc_printerr
# libvlc_set_exit_handler
# 17 function(s) not wrapped as methods:
# 18 function(s) not wrapped as methods:
# libvlc_audio_output_device_list_release
# libvlc_audio_output_list_release
# libvlc_clearerr
# libvlc_clock
@ -5950,10 +5803,10 @@ def libvlc_vlm_get_event_manager(p_instance):
# libvlc_log_subscribe
# libvlc_log_subscribe_file
# libvlc_log_unsubscribe
# libvlc_media_tracks_release
# libvlc_module_description_list_release
# libvlc_new
# libvlc_track_description_list_release
# libvlc_track_description_release
# libvlc_vprinterr
# Start of footer.py #
@ -6075,9 +5928,9 @@ if __name__ == '__main__':
print('Error: %s file not readable' % movie)
sys.exit(1)
instance = Instance()
instance = Instance("--sub-source marq")
try:
media = instance.media_new(movie, 'sub-filter=marq') # load marqee option
media = instance.media_new(movie)
except NameError:
print('NameError: %s (%s vs LibVLC %s)' % (sys.exc_info()[1],
__version__,
@ -6087,13 +5940,12 @@ if __name__ == '__main__':
player.set_media(media)
player.play()
# Some marquee examples. Marquee requires 'sub-filter=marq' in the
# media_new() call above. See also the Media.add_options method
# and <http://www.videolan.org/doc/play-howto/en/ch04.html>
# Some marquee examples. Marquee requires '--sub-source marq' in the
# Instance() call above. See <http://www.videolan.org/doc/play-howto/en/ch04.html>
player.video_set_marquee_int(VideoMarqueeOption.Enable, 1)
player.video_set_marquee_int(VideoMarqueeOption.Size, 24) # pixels
player.video_set_marquee_int(VideoMarqueeOption.Position, Position.Bottom)
if True: # only one marquee can be specified
if False: # only one marquee can be specified
player.video_set_marquee_int(VideoMarqueeOption.Timeout, 5000) # millisec, 0==forever
t = media.get_mrl() # movie
else: # update marquee text periodically

View File

@ -125,7 +125,6 @@ class VlcPlayer(MediaPlayer):
if Settings().value(u'advanced/hide mouse') and display.controller.isLive:
command_line_options += u' --mouse-hide-timeout=0'
display.vlcInstance = vlc.Instance(command_line_options)
display.vlcInstance.set_log_verbosity(2)
# creating an empty vlc media player
display.vlcMediaPlayer = display.vlcInstance.media_player_new()
display.vlcWidget.resize(display.size())

View File

@ -406,7 +406,7 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog):
# Only continue when we include the song's text.
if not self.slideTextCheckBox.isChecked():
return
for item in self.service_manager.serviceItems:
for item in self.service_manager.service_items:
# Trigger Audit requests
Registry().register_function(u'print_service_started', [item[u'service_item']])

View File

@ -142,8 +142,7 @@ class ServiceManagerDialog(object):
self.service_manager_list.setHeaderHidden(True)
self.service_manager_list.setExpandsOnDoubleClick(False)
self.service_manager_list.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
QtCore.QObject.connect(self.service_manager_list, QtCore.SIGNAL('customContextMenuRequested(QPoint)'),
self.context_menu)
self.service_manager_list.customContextMenuRequested.connect(self.context_menu)
self.service_manager_list.setObjectName(u'service_manager_list')
# enable drop
self.service_manager_list.__class__.dragEnterEvent = self.drag_enter_event
@ -153,67 +152,59 @@ class ServiceManagerDialog(object):
# Add the bottom toolbar
self.order_toolbar = OpenLPToolbar(self)
action_list = ActionList.get_instance()
action_list.add_category(UiStrings().Service, CategoryOrder.standardToolbar)
action_list.add_category(UiStrings().Service, CategoryOrder.standard_toolbar)
self.service_manager_list.moveTop = self.order_toolbar.addToolbarAction(u'moveTop',
text=translate('OpenLP.ServiceManager', 'Move to &top'), icon=u':/services/service_top.png',
tooltip=translate('OpenLP.ServiceManager', 'Move item to the top of the service.'),
shortcuts=[QtCore.Qt.Key_Home], category=UiStrings().Service, triggers=self.onServiceTop)
can_shortcuts=True, category=UiStrings().Service, triggers=self.onServiceTop)
self.service_manager_list.moveUp = self.order_toolbar.addToolbarAction(u'moveUp',
text=translate('OpenLP.ServiceManager', 'Move &up'), icon=u':/services/service_up.png',
tooltip=translate('OpenLP.ServiceManager', 'Move item up one position in the service.'),
shortcuts=[QtCore.Qt.Key_PageUp], category=UiStrings().Service, triggers=self.onServiceUp)
can_shortcuts=True, category=UiStrings().Service, triggers=self.onServiceUp)
self.service_manager_list.moveDown = self.order_toolbar.addToolbarAction(u'moveDown',
text=translate('OpenLP.ServiceManager', 'Move &down'), icon=u':/services/service_down.png',
tooltip=translate('OpenLP.ServiceManager', 'Move item down one position in the service.'),
shortcuts=[QtCore.Qt.Key_PageDown], category=UiStrings().Service, triggers=self.onServiceDown)
can_shortcuts=True, category=UiStrings().Service, triggers=self.onServiceDown)
self.service_manager_list.moveBottom = self.order_toolbar.addToolbarAction(u'moveBottom',
text=translate('OpenLP.ServiceManager', 'Move to &bottom'), icon=u':/services/service_bottom.png',
tooltip=translate('OpenLP.ServiceManager', 'Move item to the end of the service.'),
shortcuts=[QtCore.Qt.Key_End], category=UiStrings().Service, triggers=self.onServiceEnd)
can_shortcuts=True, category=UiStrings().Service, triggers=self.onServiceEnd)
self.service_manager_list.down = self.order_toolbar.addToolbarAction(u'down',
text=translate('OpenLP.ServiceManager', 'Move &down'),
text=translate('OpenLP.ServiceManager', 'Move &down'), can_shortcuts=True,
tooltip=translate('OpenLP.ServiceManager', 'Moves the selection down the window.'), visible=False,
shortcuts=[QtCore.Qt.Key_Down], triggers=self.on_move_selection_down)
triggers=self.on_move_selection_down)
action_list.add_action(self.service_manager_list.down)
self.service_manager_list.up = self.order_toolbar.addToolbarAction(u'up',
text=translate('OpenLP.ServiceManager', 'Move up'), tooltip=translate('OpenLP.ServiceManager',
'Moves the selection up the window.'), visible=False, shortcuts=[QtCore.Qt.Key_Up],
text=translate('OpenLP.ServiceManager', 'Move up'), can_shortcuts=True,
tooltip=translate('OpenLP.ServiceManager', 'Moves the selection up the window.'), visible=False,
triggers=self.on_move_selection_up)
action_list.add_action(self.service_manager_list.up)
self.order_toolbar.addSeparator()
self.service_manager_list.delete = self.order_toolbar.addToolbarAction(u'delete',
self.service_manager_list.delete = self.order_toolbar.addToolbarAction(u'delete', can_shortcuts=True,
text=translate('OpenLP.ServiceManager', '&Delete From Service'), icon=u':/general/general_delete.png',
tooltip=translate('OpenLP.ServiceManager', 'Delete the selected item from the service.'),
shortcuts=[QtCore.Qt.Key_Delete], triggers=self.onDeleteFromService)
triggers=self.onDeleteFromService)
self.order_toolbar.addSeparator()
self.service_manager_list.expand = self.order_toolbar.addToolbarAction(u'expand',
self.service_manager_list.expand = self.order_toolbar.addToolbarAction(u'expand', can_shortcuts=True,
text=translate('OpenLP.ServiceManager', '&Expand all'), icon=u':/services/service_expand_all.png',
tooltip=translate('OpenLP.ServiceManager', 'Expand all the service items.'),
shortcuts=[QtCore.Qt.Key_Plus], category=UiStrings().Service, triggers=self.onExpandAll)
self.service_manager_list.collapse = self.order_toolbar.addToolbarAction(u'collapse',
category=UiStrings().Service, triggers=self.onExpandAll)
self.service_manager_list.collapse = self.order_toolbar.addToolbarAction(u'collapse', can_shortcuts=True,
text=translate('OpenLP.ServiceManager', '&Collapse all'), icon=u':/services/service_collapse_all.png',
tooltip=translate('OpenLP.ServiceManager', 'Collapse all the service items.'),
shortcuts=[QtCore.Qt.Key_Minus], category=UiStrings().Service, triggers=self.onCollapseAll)
category=UiStrings().Service, triggers=self.onCollapseAll)
self.order_toolbar.addSeparator()
self.service_manager_list.make_live = self.order_toolbar.addToolbarAction(u'make_live',
self.service_manager_list.make_live = self.order_toolbar.addToolbarAction(u'make_live', can_shortcuts=True,
text=translate('OpenLP.ServiceManager', 'Go Live'), icon=u':/general/general_live.png',
tooltip=translate('OpenLP.ServiceManager', 'Send the selected item to Live.'),
shortcuts=[QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return], category=UiStrings().Service,
category=UiStrings().Service,
triggers=self.make_live)
self.layout.addWidget(self.order_toolbar)
# Connect up our signals and slots
QtCore.QObject.connect(self.theme_combo_box, QtCore.SIGNAL(u'activated(int)'),
self.on_theme_combo_box_selected)
QtCore.QObject.connect(self.service_manager_list, QtCore.SIGNAL(u'doubleClicked(QModelIndex)'),
self.on_make_live)
QtCore.QObject.connect(self.service_manager_list, QtCore.SIGNAL(u'itemCollapsed(QTreeWidgetItem*)'),
self.collapsed)
QtCore.QObject.connect(self.service_manager_list, QtCore.SIGNAL(u'itemExpanded(QTreeWidgetItem*)'),
self.expanded)
Registry().register_function(u'theme_update_list', self.update_theme_list)
Registry().register_function(u'config_updated', self.config_updated)
Registry().register_function(u'config_screen_changed', self.regenerate_service_Items)
Registry().register_function(u'theme_update_global', self.theme_change)
self.theme_combo_box.activated.connect(self.on_theme_combo_box_selected)
self.service_manager_list.doubleClicked.connect(self.on_make_live)
self.service_manager_list.itemCollapsed.connect(self.collapsed)
self.service_manager_list.itemExpanded.connect(self.expanded)
# Last little bits of setting up
self.service_theme = Settings().value(self.main_window.serviceManagerSettingsSection + u'/service theme')
self.servicePath = AppLocation.get_section_data_path(u'servicemanager')
@ -273,6 +264,10 @@ class ServiceManagerDialog(object):
self.service_manager_list.expand,
self.service_manager_list.collapse
])
Registry().register_function(u'theme_update_list', self.update_theme_list)
Registry().register_function(u'config_updated', self.config_updated)
Registry().register_function(u'config_screen_changed', self.regenerate_service_Items)
Registry().register_function(u'theme_update_global', self.theme_change)
def drag_enter_event(self, event):
"""

View File

@ -56,8 +56,8 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog):
self.setupUi(self)
self.changedActions = {}
self.action_list = ActionList.get_instance()
QtCore.QObject.connect(self.primaryPushButton, QtCore.SIGNAL(u'toggled(bool)'),
self.onPrimaryPushButtonClicked)
self.dialog_was_shown = False
QtCore.QObject.connect(self.primaryPushButton, QtCore.SIGNAL(u'toggled(bool)'), self.onPrimaryPushButtonClicked)
QtCore.QObject.connect(self.alternatePushButton, QtCore.SIGNAL(u'toggled(bool)'),
self.onAlternatePushButtonClicked)
QtCore.QObject.connect(self.treeWidget,
@ -72,8 +72,7 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog):
self.onRestoreDefaultsClicked)
QtCore.QObject.connect(self.defaultRadioButton, QtCore.SIGNAL(u'clicked(bool)'),
self.onDefaultRadioButtonClicked)
QtCore.QObject.connect(self.customRadioButton, QtCore.SIGNAL(u'clicked(bool)'),
self.onCustomRadioButtonClicked)
QtCore.QObject.connect(self.customRadioButton, QtCore.SIGNAL(u'clicked(bool)'), self.onCustomRadioButtonClicked)
def keyPressEvent(self, event):
"""
@ -93,9 +92,12 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog):
"""
if not self.primaryPushButton.isChecked() and not self.alternatePushButton.isChecked():
return
# Do not continue, as the event is for the dialog (close it).
if self.dialog_was_shown and event.key() in (QtCore.Qt.Key_Escape, QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return):
self.dialog_was_shown = False
return
key = event.key()
if key == QtCore.Qt.Key_Shift or key == QtCore.Qt.Key_Control or \
key == QtCore.Qt.Key_Meta or key == QtCore.Qt.Key_Alt:
if key in (QtCore.Qt.Key_Shift, QtCore.Qt.Key_Control, QtCore.Qt.Key_Meta, QtCore.Qt.Key_Alt):
return
key_string = QtGui.QKeySequence(key).toString()
if event.modifiers() & QtCore.Qt.ControlModifier == QtCore.Qt.ControlModifier:
@ -109,11 +111,9 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog):
key_sequence = QtGui.QKeySequence(key_string)
if self._validiate_shortcut(self._currentItemAction(), key_sequence):
if self.primaryPushButton.isChecked():
self._adjustButton(self.primaryPushButton,
False, text=key_sequence.toString())
self._adjustButton(self.primaryPushButton, False, text=key_sequence.toString())
elif self.alternatePushButton.isChecked():
self._adjustButton(self.alternatePushButton,
False, text=key_sequence.toString())
self._adjustButton(self.alternatePushButton, False, text=key_sequence.toString())
def exec_(self):
"""
@ -147,8 +147,8 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog):
def refreshShortcutList(self):
"""
This refreshes the item's shortcuts shown in the list. Note, this
neither adds new actions nor removes old actions.
This refreshes the item's shortcuts shown in the list. Note, this neither adds new actions nor removes old
actions.
"""
iterator = QtGui.QTreeWidgetItemIterator(self.treeWidget)
while iterator.value():
@ -204,21 +204,19 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog):
new_shortcuts = []
if shortcuts:
new_shortcuts.append(shortcuts[0])
new_shortcuts.append(
QtGui.QKeySequence(self.alternatePushButton.text()))
new_shortcuts.append(QtGui.QKeySequence(self.alternatePushButton.text()))
self.changedActions[action] = new_shortcuts
if not self.primaryPushButton.text():
# When we do not have a primary shortcut, the just entered alternate
# shortcut will automatically become the primary shortcut. That is
# why we have to adjust the primary button's text.
# When we do not have a primary shortcut, the just entered alternate shortcut will automatically become the
# primary shortcut. That is why we have to adjust the primary button's text.
self.primaryPushButton.setText(self.alternatePushButton.text())
self.alternatePushButton.setText(u'')
self.refreshShortcutList()
def onItemDoubleClicked(self, item, column):
"""
A item has been double clicked. The ``primaryPushButton`` will be
checked and the item's shortcut will be displayed.
A item has been double clicked. The ``primaryPushButton`` will be checked and the item's shortcut will be
displayed.
"""
action = self._currentItemAction(item)
if action is None:
@ -234,8 +232,7 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog):
def onCurrentItemChanged(self, item=None, previousItem=None):
"""
A item has been pressed. We adjust the button's text to the action's
shortcut which is encapsulate in the item.
A item has been pressed. We adjust the button's text to the action's shortcut which is encapsulate in the item.
"""
action = self._currentItemAction(item)
self.primaryPushButton.setEnabled(action is not None)
@ -253,9 +250,8 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog):
if len(action.defaultShortcuts) == 2:
alternate_label_text = action.defaultShortcuts[1].toString()
shortcuts = self._actionShortcuts(action)
# We do not want to loose pending changes, that is why we have to
# keep the text when, this function has not been triggered by a
# signal.
# We do not want to loose pending changes, that is why we have to keep the text when, this function has not
# been triggered by a signal.
if item is None:
primary_text = self.primaryPushButton.text()
alternate_text = self.alternatePushButton.text()
@ -264,8 +260,7 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog):
elif len(shortcuts) == 2:
primary_text = shortcuts[0].toString()
alternate_text = shortcuts[1].toString()
# When we are capturing a new shortcut, we do not want, the buttons to
# display the current shortcut.
# When we are capturing a new shortcut, we do not want, the buttons to display the current shortcut.
if self.primaryPushButton.isChecked():
primary_text = u''
if self.alternatePushButton.isChecked():
@ -274,8 +269,7 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog):
self.alternatePushButton.setText(alternate_text)
self.primaryLabel.setText(primary_label_text)
self.alternateLabel.setText(alternate_label_text)
# We do not want to toggle and radio button, as the function has not
# been triggered by a signal.
# We do not want to toggle and radio button, as the function has not been triggered by a signal.
if item is None:
return
if primary_label_text == primary_text and alternate_label_text == alternate_text:
@ -303,8 +297,8 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog):
def onDefaultRadioButtonClicked(self, toggled):
"""
The default radio button has been clicked, which means we have to make
sure, that we use the default shortcuts for the action.
The default radio button has been clicked, which means we have to make sure, that we use the default shortcuts
for the action.
"""
if not toggled:
return
@ -325,9 +319,8 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog):
def onCustomRadioButtonClicked(self, toggled):
"""
The custom shortcut radio button was clicked, thus we have to restore
the custom shortcuts by calling those functions triggered by button
clicks.
The custom shortcut radio button was clicked, thus we have to restore the custom shortcuts by calling those
functions triggered by button clicks.
"""
if not toggled:
return
@ -337,8 +330,8 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog):
def save(self):
"""
Save the shortcuts. **Note**, that we do not have to load the shortcuts,
as they are loaded in :class:`~openlp.core.utils.ActionList`.
Save the shortcuts. **Note**, that we do not have to load the shortcuts, as they are loaded in
:class:`~openlp.core.utils.ActionList`.
"""
settings = Settings()
settings.beginGroup(u'shortcuts')
@ -348,8 +341,7 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog):
continue
for action in category.actions:
if action in self.changedActions:
old_shortcuts = map(unicode,
map(QtGui.QKeySequence.toString, action.shortcuts()))
old_shortcuts = map(QtGui.QKeySequence.toString, action.shortcuts())
action.setShortcuts(self.changedActions[action])
self.action_list.update_shortcut_map(action, old_shortcuts)
settings.setValue(action.objectName(), action.shortcuts())
@ -367,13 +359,10 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog):
new_shortcuts = []
if action.defaultShortcuts:
new_shortcuts.append(action.defaultShortcuts[0])
# We have to check if the primary default shortcut is available. But
# we only have to check, if the action has a default primary
# shortcut (an "empty" shortcut is always valid and if the action
# does not have a default primary shortcut, then the alternative
# shortcut (not the default one) will become primary shortcut, thus
# the check will assume that an action were going to have the same
# shortcut twice.
# We have to check if the primary default shortcut is available. But we only have to check, if the action
# has a default primary shortcut (an "empty" shortcut is always valid and if the action does not have a
# default primary shortcut, then the alternative shortcut (not the default one) will become primary
# shortcut, thus the check will assume that an action were going to have the same shortcut twice.
if not self._validiate_shortcut(action, new_shortcuts[0]) and new_shortcuts[0] != shortcuts[0]:
return
if len(shortcuts) == 2:
@ -405,9 +394,8 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog):
def _validiate_shortcut(self, changing_action, key_sequence):
"""
Checks if the given ``changing_action `` can use the given
``key_sequence``. Returns ``True`` if the ``key_sequence`` can be used
by the action, otherwise displays a dialog and returns ``False``.
Checks if the given ``changing_action `` can use the given ``key_sequence``. Returns ``True`` if the
``key_sequence`` can be used by the action, otherwise displays a dialog and returns ``False``.
``changing_action``
The action which wants to use the ``key_sequence``.
@ -429,27 +417,25 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog):
# Have the same parent, thus they cannot have the same shortcut.
if action.parent() is changing_action.parent():
is_valid = False
# The new shortcut is already assigned, but if both shortcuts
# are only valid in a different widget the new shortcut is
# vaild, because they will not interfere.
if action.shortcutContext() in [QtCore.Qt.WindowShortcut,
QtCore.Qt.ApplicationShortcut]:
# The new shortcut is already assigned, but if both shortcuts are only valid in a different widget the
# new shortcut is vaild, because they will not interfere.
if action.shortcutContext() in [QtCore.Qt.WindowShortcut, QtCore.Qt.ApplicationShortcut]:
is_valid = False
if changing_action.shortcutContext() in [QtCore.Qt.WindowShortcut, QtCore.Qt.ApplicationShortcut]:
is_valid = False
if not is_valid:
self.main_window.warning_message( translate('OpenLP.ShortcutListDialog', 'Duplicate Shortcut'),
self.main_window.warning_message(translate('OpenLP.ShortcutListDialog', 'Duplicate Shortcut'),
translate('OpenLP.ShortcutListDialog',
'The shortcut "%s" is already assigned to another action, please use a different shortcut.') %
key_sequence.toString()
)
self.dialog_was_shown = True
return is_valid
def _actionShortcuts(self, action):
"""
This returns the shortcuts for the given ``action``, which also includes
those shortcuts which are not saved yet but already assigned (as changes
are applied when closing the dialog).
This returns the shortcuts for the given ``action``, which also includes those shortcuts which are not saved
yet but already assigned (as changes yre applied when closing the dialog).
"""
if action in self.changedActions:
return self.changedActions[action]
@ -457,8 +443,8 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog):
def _currentItemAction(self, item=None):
"""
Returns the action of the given ``item``. If no item is given, we return
the action of the current item of the ``treeWidget``.
Returns the action of the given ``item``. If no item is given, we return the action of the current item of
the ``treeWidget``.
"""
if item is None:
item = self.treeWidget.currentItem()
@ -486,4 +472,5 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog):
self._main_window = Registry().get(u'main_window')
return self._main_window
main_window = property(_get_main_window)
main_window = property(_get_main_window)

View File

@ -44,6 +44,14 @@ from openlp.core.utils.actions import ActionList, CategoryOrder
log = logging.getLogger(__name__)
AUDIO_TIME_LABEL_STYLESHEET = u'background-color: palette(background); ' \
u'border-top-color: palette(shadow); ' \
u'border-left-color: palette(shadow); ' \
u'border-bottom-color: palette(light); ' \
u'border-right-color: palette(light); ' \
u'border-radius: 3px; border-style: inset; ' \
u'border-width: 1; font-family: monospace; margin: 2px;'
class DisplayController(QtGui.QWidget):
"""
@ -90,7 +98,6 @@ class SlideController(DisplayController):
u'delaySpinBox'
]
self.audioList = [
u'songMenu',
u'audioPauseItem',
u'audioTimeLabel'
]
@ -124,7 +131,7 @@ class SlideController(DisplayController):
self.keypress_queue = deque()
self.keypress_loop = False
self.category = UiStrings().LiveToolbar
ActionList.get_instance().add_category(unicode(self.category), CategoryOrder.standardToolbar)
ActionList.get_instance().add_category(unicode(self.category), CategoryOrder.standard_toolbar)
else:
Registry().register(u'preview_controller', self)
self.typeLabel.setText(UiStrings().Preview)
@ -168,13 +175,13 @@ class SlideController(DisplayController):
self.previousItem = create_action(self, u'previousItem_' + self.typePrefix,
text=translate('OpenLP.SlideController', 'Previous Slide'), icon=u':/slides/slide_previous.png',
tooltip=translate('OpenLP.SlideController', 'Move to previous.'),
shortcuts=[QtCore.Qt.Key_Up, QtCore.Qt.Key_PageUp], context=QtCore.Qt.WidgetWithChildrenShortcut,
can_shortcuts=True, context=QtCore.Qt.WidgetWithChildrenShortcut,
category=self.category, triggers=self.on_slide_selected_previous)
self.toolbar.addAction(self.previousItem)
self.nextItem = create_action(self, u'nextItem_' + self.typePrefix,
text=translate('OpenLP.SlideController', 'Next Slide'), icon=u':/slides/slide_next.png',
tooltip=translate('OpenLP.SlideController', 'Move to next.'),
shortcuts=[QtCore.Qt.Key_Down, QtCore.Qt.Key_PageDown], context=QtCore.Qt.WidgetWithChildrenShortcut,
can_shortcuts=True, context=QtCore.Qt.WidgetWithChildrenShortcut,
category=self.category, triggers=self.on_slide_selected_next_action)
self.toolbar.addAction(self.nextItem)
self.toolbar.addSeparator()
@ -190,14 +197,14 @@ class SlideController(DisplayController):
self.toolbar.addToolbarWidget(self.hideMenu)
self.blankScreen = create_action(self, u'blankScreen',
text=translate('OpenLP.SlideController', 'Blank Screen'), icon=u':/slides/slide_blank.png',
checked=False, shortcuts=[QtCore.Qt.Key_Period], category=self.category, triggers=self.onBlankDisplay)
checked=False, can_shortcuts=True, category=self.category, triggers=self.onBlankDisplay)
self.themeScreen = create_action(self, u'themeScreen',
text=translate('OpenLP.SlideController', 'Blank to Theme'), icon=u':/slides/slide_theme.png',
checked=False, shortcuts=[QtGui.QKeySequence(u'T')], category=self.category,
checked=False, can_shortcuts=True, category=self.category,
triggers=self.onThemeDisplay)
self.desktopScreen = create_action(self, u'desktopScreen',
text=translate('OpenLP.SlideController', 'Show Desktop'), icon=u':/slides/slide_desktop.png',
checked=False, shortcuts=[QtGui.QKeySequence(u'D')], category=self.category,
checked=False, can_shortcuts=True, category=self.category,
triggers=self.onHideDisplay)
self.hideMenu.setDefaultAction(self.blankScreen)
self.hideMenu.menu().addAction(self.blankScreen)
@ -225,12 +232,12 @@ class SlideController(DisplayController):
self.playSlidesMenu.setMenu(QtGui.QMenu(translate('OpenLP.SlideController', 'Play Slides'), self.toolbar))
self.toolbar.addToolbarWidget(self.playSlidesMenu)
self.playSlidesLoop = create_action(self, u'playSlidesLoop', text=UiStrings().PlaySlidesInLoop,
icon=u':/media/media_time.png', checked=False, shortcuts=[],
icon=u':/media/media_time.png', checked=False, can_shortcuts=True,
category=self.category, triggers=self.onPlaySlidesLoop)
self.playSlidesOnce = create_action(self, u'playSlidesOnce', text=UiStrings().PlaySlidesToEnd,
icon=u':/media/media_time.png', checked=False, shortcuts=[],
icon=u':/media/media_time.png', checked=False, can_shortcuts=True,
category=self.category, triggers=self.onPlaySlidesOnce)
if Settings().value(self.parent().advancedSettingsSection + u'/slide limits') == SlideLimits.Wrap:
if Settings().value(self.main_window.advancedSettingsSection + u'/slide limits') == SlideLimits.Wrap:
self.playSlidesMenu.setDefaultAction(self.playSlidesLoop)
else:
self.playSlidesMenu.setDefaultAction(self.playSlidesOnce)
@ -267,7 +274,7 @@ class SlideController(DisplayController):
icon=u':/slides/media_playback_pause.png', text=translate('OpenLP.SlideController', 'Pause Audio'),
tooltip=translate('OpenLP.SlideController', 'Pause audio.'),
checked=False, visible=False, category=self.category, context=QtCore.Qt.WindowShortcut,
shortcuts=[], triggers=self.onAudioPauseClicked)
can_shortcuts=True, triggers=self.onAudioPauseClicked)
self.audioMenu = QtGui.QMenu(translate('OpenLP.SlideController', 'Background Audio'), self.toolbar)
self.audioPauseItem.setMenu(self.audioMenu)
self.audioPauseItem.setParent(self.toolbar)
@ -276,23 +283,16 @@ class SlideController(DisplayController):
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, shortcuts=[], triggers=self.onNextTrackClicked)
category=self.category, can_shortcuts=True, triggers=self.onNextTrackClicked)
self.audioMenu.addAction(self.nextTrackItem)
self.trackMenu = self.audioMenu.addMenu(translate('OpenLP.SlideController', 'Tracks'))
self.audioTimeLabel = QtGui.QLabel(u' 00:00 ', self.toolbar)
self.audioTimeLabel.setAlignment(QtCore.Qt.AlignCenter | QtCore.Qt.AlignHCenter)
self.audioTimeLabel.setStyleSheet(
u'background-color: palette(background); '
u'border-top-color: palette(shadow); '
u'border-left-color: palette(shadow); '
u'border-bottom-color: palette(light); '
u'border-right-color: palette(light); '
u'border-radius: 3px; border-style: inset; '
u'border-width: 1; font-family: monospace; margin: 2px;'
)
self.audioTimeLabel.setStyleSheet(AUDIO_TIME_LABEL_STYLESHEET)
self.audioTimeLabel.setObjectName(u'audioTimeLabel')
self.toolbar.addToolbarWidget(self.audioTimeLabel)
self.toolbar.setWidgetVisible(self.audioList, False)
self.toolbar.setWidgetVisible([u'songMenu'], False)
# Screen preview area
self.previewFrame = QtGui.QFrame(self.splitter)
self.previewFrame.setGeometry(QtCore.QRect(0, 0, 300, 300 * self.ratio))
@ -334,25 +334,20 @@ class SlideController(DisplayController):
self.shortcutTimer = QtCore.QTimer()
self.shortcutTimer.setObjectName(u'shortcutTimer')
self.shortcutTimer.setSingleShot(True)
shortcuts = [{u'key': u'V', u'configurable': True,
u'text': translate('OpenLP.SlideController', 'Go to "Verse"')},
{u'key': u'C', u'configurable': True,
u'text': translate('OpenLP.SlideController', 'Go to "Chorus"')},
{u'key': u'B', u'configurable': True,
u'text': translate('OpenLP.SlideController', 'Go to "Bridge"')},
shortcuts = [
{u'key': u'V', u'configurable': True, u'text': translate('OpenLP.SlideController', 'Go to "Verse"')},
{u'key': u'C', u'configurable': True, u'text': translate('OpenLP.SlideController', 'Go to "Chorus"')},
{u'key': u'B', u'configurable': True, u'text': translate('OpenLP.SlideController', 'Go to "Bridge"')},
{u'key': u'P', u'configurable': True,
u'text': translate('OpenLP.SlideController',
'Go to "Pre-Chorus"')},
{u'key': u'I', u'configurable': True,
u'text': translate('OpenLP.SlideController', 'Go to "Intro"')},
{u'key': u'E', u'configurable': True,
u'text': translate('OpenLP.SlideController', 'Go to "Ending"')},
{u'key': u'O', u'configurable': True,
u'text': translate('OpenLP.SlideController', 'Go to "Other"')}]
shortcuts += [{u'key': unicode(number)} for number in range(10)]
u'text': translate('OpenLP.SlideController', 'Go to "Pre-Chorus"')},
{u'key': u'I', u'configurable': True, u'text': translate('OpenLP.SlideController', 'Go to "Intro"')},
{u'key': u'E', u'configurable': True, u'text': translate('OpenLP.SlideController', 'Go to "Ending"')},
{u'key': u'O', u'configurable': True, u'text': translate('OpenLP.SlideController', 'Go to "Other"')}
]
shortcuts.extend([{u'key': unicode(number)} for number in range(10)])
self.previewListWidget.addActions([create_action(self,
u'shortcutAction_%s' % s[u'key'], text=s.get(u'text'),
shortcuts=[QtGui.QKeySequence(s[u'key'])],
can_shortcuts=True,
context=QtCore.Qt.WidgetWithChildrenShortcut,
category=self.category if s.get(u'configurable') else None,
triggers=self._slideShortcutActivated) for s in shortcuts])
@ -404,19 +399,19 @@ class SlideController(DisplayController):
verse_type = sender_name[15:] if sender_name[:15] == u'shortcutAction_' else u''
if SONGS_PLUGIN_AVAILABLE:
if verse_type == u'V':
self.current_shortcut = VerseType.TranslatedTags[VerseType.Verse]
self.current_shortcut = VerseType.translated_tags[VerseType.Verse]
elif verse_type == u'C':
self.current_shortcut = VerseType.TranslatedTags[VerseType.Chorus]
self.current_shortcut = VerseType.translated_tags[VerseType.Chorus]
elif verse_type == u'B':
self.current_shortcut = VerseType.TranslatedTags[VerseType.Bridge]
self.current_shortcut = VerseType.translated_tags[VerseType.Bridge]
elif verse_type == u'P':
self.current_shortcut = VerseType.TranslatedTags[VerseType.PreChorus]
self.current_shortcut = VerseType.translated_tags[VerseType.PreChorus]
elif verse_type == u'I':
self.current_shortcut = VerseType.TranslatedTags[VerseType.Intro]
self.current_shortcut = VerseType.translated_tags[VerseType.Intro]
elif verse_type == u'E':
self.current_shortcut = VerseType.TranslatedTags[VerseType.Ending]
self.current_shortcut = VerseType.translated_tags[VerseType.Ending]
elif verse_type == u'O':
self.current_shortcut = VerseType.TranslatedTags[VerseType.Other]
self.current_shortcut = VerseType.translated_tags[VerseType.Other]
elif verse_type.isnumeric():
self.current_shortcut += verse_type
self.current_shortcut = self.current_shortcut.upper()
@ -451,15 +446,15 @@ class SlideController(DisplayController):
"""
self.previousService = create_action(parent, u'previousService',
text=translate('OpenLP.SlideController', 'Previous Service'),
shortcuts=[QtCore.Qt.Key_Left], context=QtCore.Qt.WidgetWithChildrenShortcut, category=self.category,
can_shortcuts=True, context=QtCore.Qt.WidgetWithChildrenShortcut, category=self.category,
triggers=self.servicePrevious)
self.nextService = create_action(parent, 'nextService',
text=translate('OpenLP.SlideController', 'Next Service'),
shortcuts=[QtCore.Qt.Key_Right], context=QtCore.Qt.WidgetWithChildrenShortcut, category=self.category,
can_shortcuts=True, context=QtCore.Qt.WidgetWithChildrenShortcut, category=self.category,
triggers=self.serviceNext)
self.escapeItem = create_action(parent, 'escapeItem',
text=translate('OpenLP.SlideController', 'Escape Item'),
shortcuts=[QtCore.Qt.Key_Escape], context=QtCore.Qt.WidgetWithChildrenShortcut, category=self.category,
can_shortcuts=True, context=QtCore.Qt.WidgetWithChildrenShortcut, category=self.category,
triggers=self.liveEscape)
def liveEscape(self):
@ -582,7 +577,7 @@ class SlideController(DisplayController):
self.previewListWidget.resizeRowsToContents()
else:
# Sort out image heights.
width = self.parent().controlSplitter.sizes()[self.split]
width = self.main_window.controlSplitter.sizes()[self.split]
for framenumber in range(len(self.serviceItem.get_frames())):
self.previewListWidget.setRowHeight(framenumber, width / self.ratio)
self.onControllerSizeChanged(self.controller.width(), self.controller.height())
@ -592,10 +587,14 @@ class SlideController(DisplayController):
Change layout of display control buttons on controller size change
"""
if self.isLive:
if width > 300 and self.hideMenu.isVisible():
# Space used by the toolbar.
used_space = self.toolbar.size().width() + self.hideMenu.size().width()
# The + 40 is needed to prevent flickering. This can be considered a "buffer".
if width > used_space + 40 and self.hideMenu.isVisible():
self.toolbar.setWidgetVisible(self.hideMenuList, False)
self.toolbar.setWidgetVisible(self.wideMenu)
elif width < 300 and not self.hideMenu.isVisible():
# The - 40 is needed to prevent flickering. This can be considered a "buffer".
elif width < used_space - 40 and not self.hideMenu.isVisible():
self.toolbar.setWidgetVisible(self.wideMenu, False)
self.toolbar.setWidgetVisible(self.hideMenuList)
@ -618,7 +617,7 @@ class SlideController(DisplayController):
"""
Updates the Slide Limits variable from the settings.
"""
self.slide_limits = Settings().value(self.parent().advancedSettingsSection + u'/slide limits')
self.slide_limits = Settings().value(self.main_window.advancedSettingsSection + u'/slide limits')
def enableToolBar(self, item):
"""
@ -640,14 +639,15 @@ class SlideController(DisplayController):
self.mediabar.hide()
self.songMenu.hide()
self.toolbar.setWidgetVisible(self.loopList, False)
self.toolbar.setWidgetVisible([u'songMenu'], False)
# Reset the button
self.playSlidesOnce.setChecked(False)
self.playSlidesOnce.setIcon(build_icon(u':/media/media_time.png'))
self.playSlidesLoop.setChecked(False)
self.playSlidesLoop.setIcon(build_icon(u':/media/media_time.png'))
if item.is_text():
if Settings().value(self.parent().songsSettingsSection + u'/display songbar') and self.slideList:
self.songMenu.show()
if Settings().value(self.main_window.songsSettingsSection + u'/display songbar') and self.slideList:
self.toolbar.setWidgetVisible([u'songMenu'], True)
if item.is_capable(ItemCapabilities.CanLoop) and len(item.get_frames()) > 1:
self.toolbar.setWidgetVisible(self.loopList)
if item.is_media():
@ -748,7 +748,7 @@ class SlideController(DisplayController):
self._resetBlank()
Registry().execute(u'%s_start' % serviceItem.name.lower(), [serviceItem, self.isLive, self.hideMode(), slideno])
self.slideList = {}
width = self.parent().controlSplitter.sizes()[self.split]
width = self.main_window.controlSplitter.sizes()[self.split]
self.previewListWidget.clear()
self.previewListWidget.setRowCount(0)
self.previewListWidget.setColumnWidth(0, width)
@ -767,8 +767,8 @@ class SlideController(DisplayController):
action.setData(counter)
QtCore.QObject.connect(action, QtCore.SIGNAL(u'triggered(bool)'), self.onTrackTriggered)
self.display.audioPlayer.repeat = Settings().value(
self.parent().generalSettingsSection + u'/audio repeat list')
if Settings().value(self.parent().generalSettingsSection + u'/audio start paused'):
self.main_window.generalSettingsSection + u'/audio repeat list')
if Settings().value(self.main_window.generalSettingsSection + u'/audio start paused'):
self.audioPauseItem.setChecked(True)
self.display.audioPlayer.pause()
else:
@ -877,7 +877,7 @@ class SlideController(DisplayController):
Allow the main display to blank the main display at startup time
"""
log.debug(u'mainDisplaySetBackground live = %s' % self.isLive)
display_type = Settings().value(self.parent().generalSettingsSection + u'/screen blank')
display_type = Settings().value(self.main_window.generalSettingsSection + u'/screen blank')
if self.screens.which_screen(self.window()) != self.screens.which_screen(self.display):
# Order done to handle initial conversion
if display_type == u'themed':
@ -915,9 +915,9 @@ class SlideController(DisplayController):
self.themeScreen.setChecked(False)
self.desktopScreen.setChecked(False)
if checked:
Settings().setValue(self.parent().generalSettingsSection + u'/screen blank', u'blanked')
Settings().setValue(self.main_window.generalSettingsSection + u'/screen blank', u'blanked')
else:
Settings().remove(self.parent().generalSettingsSection + u'/screen blank')
Settings().remove(self.main_window.generalSettingsSection + u'/screen blank')
self.blankPlugin()
self.updatePreview()
self.onToggleLoop()
@ -934,9 +934,9 @@ class SlideController(DisplayController):
self.themeScreen.setChecked(checked)
self.desktopScreen.setChecked(False)
if checked:
Settings().setValue(self.parent().generalSettingsSection + u'/screen blank', u'themed')
Settings().setValue(self.main_window.generalSettingsSection + u'/screen blank', u'themed')
else:
Settings().remove(self.parent().generalSettingsSection + u'/screen blank')
Settings().remove(self.main_window.generalSettingsSection + u'/screen blank')
self.blankPlugin()
self.updatePreview()
self.onToggleLoop()
@ -953,9 +953,9 @@ class SlideController(DisplayController):
self.themeScreen.setChecked(False)
self.desktopScreen.setChecked(checked)
if checked:
Settings().setValue(self.parent().generalSettingsSection + u'/screen blank', u'hidden')
Settings().setValue(self.main_window.generalSettingsSection + u'/screen blank', u'hidden')
else:
Settings().remove(self.parent().generalSettingsSection + u'/screen blank')
Settings().remove(self.main_window.generalSettingsSection + u'/screen blank')
self.hidePlugin(checked)
self.updatePreview()
self.onToggleLoop()
@ -1255,7 +1255,7 @@ class SlideController(DisplayController):
def onGoLive(self):
"""
If preview copy slide item to live
If preview copy slide item to live controller from Preview Controller
"""
row = self.previewListWidget.currentRow()
if -1 < row < self.previewListWidget.rowCount():
@ -1384,3 +1384,14 @@ class SlideController(DisplayController):
return self._live_controller
live_controller = property(_get_live_controller)
def _get_main_window(self):
"""
Adds the main window to the class dynamically
"""
if not hasattr(self, u'_main_window'):
self._main_window = Registry().get(u'main_window')
return self._main_window
main_window = property(_get_main_window)

View File

@ -38,9 +38,8 @@ import re
from xml.etree.ElementTree import ElementTree, XML
from PyQt4 import QtCore, QtGui
from openlp.core.lib import ImageSource, OpenLPToolbar, Registry, SettingsManager, Settings, UiStrings, \
get_text_file_string, build_icon, translate, check_item_selected, check_directory_exists, create_thumb, \
validate_thumb
from openlp.core.lib import ImageSource, OpenLPToolbar, Registry, Settings, UiStrings, get_text_file_string, \
build_icon, translate, check_item_selected, check_directory_exists, create_thumb, validate_thumb
from openlp.core.lib.theme import ThemeXML, BackgroundType, VerticalType, BackgroundGradientType
from openlp.core.lib.ui import critical_error_message_box, create_widget_action
from openlp.core.theme import Theme
@ -62,7 +61,7 @@ class ThemeManager(QtGui.QWidget):
Registry().register(u'theme_manager', self)
self.settingsSection = u'themes'
self.themeForm = ThemeForm(self)
self.fileRenameForm = FileRenameForm(self)
self.fileRenameForm = FileRenameForm()
# start with the layout
self.layout = QtGui.QVBoxLayout(self)
self.layout.setSpacing(0)
@ -105,8 +104,7 @@ class ThemeManager(QtGui.QWidget):
self.theme_list_widget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.theme_list_widget.setObjectName(u'theme_list_widget')
self.layout.addWidget(self.theme_list_widget)
QtCore.QObject.connect(self.theme_list_widget, QtCore.SIGNAL('customContextMenuRequested(QPoint)'),
self.context_menu)
self.theme_list_widget.customContextMenuRequested.connect(self.context_menu)
# build the context menu
self.menu = QtGui.QMenu()
self.edit_action = create_widget_action(self.menu,
@ -130,10 +128,8 @@ class ThemeManager(QtGui.QWidget):
text=translate('OpenLP.ThemeManager', '&Export Theme'),
icon=u':/general/general_export.png', triggers=self.on_export_theme)
# Signals
QtCore.QObject.connect(self.theme_list_widget,
QtCore.SIGNAL(u'doubleClicked(QModelIndex)'), self.change_global_from_screen)
QtCore.QObject.connect(self.theme_list_widget,
QtCore.SIGNAL(u'currentItemChanged(QListWidgetItem *, QListWidgetItem *)'), self.check_list_state)
self.theme_list_widget.doubleClicked.connect(self.change_global_from_screen)
self.theme_list_widget.currentItemChanged.connect(self.check_list_state)
Registry().register_function(u'theme_update_global', self.change_global_from_tab)
Registry().register_function(u'config_updated', self.config_updated)
# Variables
@ -153,14 +149,13 @@ class ThemeManager(QtGui.QWidget):
Import new themes downloaded by the first time wizard
"""
self.application.set_busy_cursor()
files = SettingsManager.get_files(self.settingsSection, u'.otz')
files = AppLocation.get_files(self.settingsSection, u'.otz')
for theme_file in files:
theme_file = os.path.join(self.path, theme_file)
self.unzip_theme(theme_file, self.path)
delete_file(theme_file)
self.application.set_normal_cursor()
def config_updated(self):
"""
Triggered when Config dialog is updated.
@ -423,10 +418,10 @@ class ThemeManager(QtGui.QWidget):
log.debug(u'Load themes from dir')
self.theme_list = []
self.theme_list_widget.clear()
files = SettingsManager.get_files(self.settingsSection, u'.png')
files = AppLocation.get_files(self.settingsSection, u'.png')
if first_time:
self.first_time()
files = SettingsManager.get_files(self.settingsSection, u'.png')
files = AppLocation.get_files(self.settingsSection, u'.png')
# No themes have been found so create one
if not files:
theme = ThemeXML()
@ -434,7 +429,7 @@ class ThemeManager(QtGui.QWidget):
self._write_theme(theme, None, None)
Settings().setValue(self.settingsSection + u'/global theme', theme.theme_name)
self.config_updated()
files = SettingsManager.get_files(self.settingsSection, u'.png')
files = AppLocation.get_files(self.settingsSection, u'.png')
# Sort the themes by its name considering language specific
files.sort(key=lambda file_name: unicode(file_name), cmp=locale_compare)
# now process the file list of png files

View File

@ -39,9 +39,10 @@ from subprocess import Popen, PIPE
import sys
import urllib2
from PyQt4 import QtGui, QtCore
from openlp.core.lib import Registry, Settings
from PyQt4 import QtGui, QtCore
if sys.platform != u'win32' and sys.platform != u'darwin':
try:
@ -50,8 +51,7 @@ if sys.platform != u'win32' and sys.platform != u'darwin':
except ImportError:
XDG_BASE_AVAILABLE = False
import openlp
from openlp.core.lib import translate, check_directory_exists
from openlp.core.lib import translate
log = logging.getLogger(__name__)
APPLICATION_VERSION = {}
@ -78,107 +78,6 @@ class VersionThread(QtCore.QThread):
if LooseVersion(str(version)) > LooseVersion(str(app_version[u'full'])):
Registry().execute(u'openlp_version_check', u'%s' % version)
class AppLocation(object):
"""
The :class:`AppLocation` class is a static class which retrieves a
directory based on the directory type.
"""
AppDir = 1
ConfigDir = 2
DataDir = 3
PluginsDir = 4
VersionDir = 5
CacheDir = 6
LanguageDir = 7
# Base path where data/config/cache dir is located
BaseDir = None
@staticmethod
def get_directory(dir_type=1):
"""
Return the appropriate directory according to the directory type.
``dir_type``
The directory type you want, for instance the data directory.
"""
if dir_type == AppLocation.AppDir:
return _get_frozen_path(os.path.abspath(os.path.split(sys.argv[0])[0]), os.path.split(openlp.__file__)[0])
elif dir_type == AppLocation.PluginsDir:
app_path = os.path.abspath(os.path.split(sys.argv[0])[0])
return _get_frozen_path(os.path.join(app_path, u'plugins'),
os.path.join(os.path.split(openlp.__file__)[0], u'plugins'))
elif dir_type == AppLocation.VersionDir:
return _get_frozen_path(os.path.abspath(os.path.split(sys.argv[0])[0]), os.path.split(openlp.__file__)[0])
elif dir_type == AppLocation.LanguageDir:
app_path = _get_frozen_path(os.path.abspath(os.path.split(sys.argv[0])[0]), _get_os_dir_path(dir_type))
return os.path.join(app_path, u'i18n')
elif dir_type == AppLocation.DataDir and AppLocation.BaseDir:
return os.path.join(AppLocation.BaseDir, 'data')
else:
return _get_os_dir_path(dir_type)
@staticmethod
def get_data_path():
"""
Return the path OpenLP stores all its data under.
"""
# Check if we have a different data location.
if Settings().contains(u'advanced/data path'):
path = Settings().value(u'advanced/data path')
else:
path = AppLocation.get_directory(AppLocation.DataDir)
check_directory_exists(path)
return os.path.normpath(path)
@staticmethod
def get_section_data_path(section):
"""
Return the path a particular module stores its data under.
"""
data_path = AppLocation.get_data_path()
path = os.path.join(data_path, section)
check_directory_exists(path)
return path
def _get_os_dir_path(dir_type):
"""
Return a path based on which OS and environment we are running in.
"""
encoding = sys.getfilesystemencoding()
if sys.platform == u'win32':
if dir_type == AppLocation.DataDir:
return os.path.join(unicode(os.getenv(u'APPDATA'), encoding), u'openlp', u'data')
elif dir_type == AppLocation.LanguageDir:
return os.path.split(openlp.__file__)[0]
return os.path.join(unicode(os.getenv(u'APPDATA'), encoding), u'openlp')
elif sys.platform == u'darwin':
if dir_type == AppLocation.DataDir:
return os.path.join(unicode(os.getenv(u'HOME'), encoding),
u'Library', u'Application Support', u'openlp', u'Data')
elif dir_type == AppLocation.LanguageDir:
return os.path.split(openlp.__file__)[0]
return os.path.join(unicode(os.getenv(u'HOME'), encoding), u'Library', u'Application Support', u'openlp')
else:
if dir_type == AppLocation.LanguageDir:
prefixes = [u'/usr/local', u'/usr']
for prefix in prefixes:
directory = os.path.join(prefix, u'share', u'openlp')
if os.path.exists(directory):
return directory
return os.path.join(u'/usr', u'share', u'openlp')
if XDG_BASE_AVAILABLE:
if dir_type == AppLocation.ConfigDir:
return os.path.join(unicode(BaseDirectory.xdg_config_home, encoding), u'openlp')
elif dir_type == AppLocation.DataDir:
return os.path.join(unicode(BaseDirectory.xdg_data_home, encoding), u'openlp')
elif dir_type == AppLocation.CacheDir:
return os.path.join(unicode(BaseDirectory.xdg_cache_home, encoding), u'openlp')
if dir_type == AppLocation.DataDir:
return os.path.join(unicode(os.getenv(u'HOME'), encoding), u'.openlp', u'data')
return os.path.join(unicode(os.getenv(u'HOME'), encoding), u'.openlp')
def _get_frozen_path(frozen_option, non_frozen_option):
"""
@ -497,9 +396,11 @@ def locale_compare(string1, string2):
locale_direct_compare = locale.strcoll
from applocation import AppLocation
from languagemanager import LanguageManager
from actions import ActionList
__all__ = [u'AppLocation', u'ActionList', u'LanguageManager', u'get_application_version', u'check_latest_version',
u'add_actions', u'get_filesystem_encoding', u'get_web_page', u'get_uno_command', u'get_uno_instance',
u'delete_file', u'clean_filename', u'format_time', u'locale_compare', u'locale_direct_compare']

View File

@ -37,8 +37,8 @@ from openlp.core.lib import Settings
class ActionCategory(object):
"""
The :class:`~openlp.core.utils.ActionCategory` class encapsulates a
category for the :class:`~openlp.core.utils.CategoryList` class.
The :class:`~openlp.core.utils.ActionCategory` class encapsulates a category for the
:class:`~openlp.core.utils.CategoryList` class.
"""
def __init__(self, name, weight=0):
"""
@ -51,8 +51,7 @@ class ActionCategory(object):
class CategoryActionList(object):
"""
The :class:`~openlp.core.utils.CategoryActionList` class provides a sorted
list of actions within a category.
The :class:`~openlp.core.utils.CategoryActionList` class provides a sorted list of actions within a category.
"""
def __init__(self):
"""
@ -142,9 +141,9 @@ class CategoryActionList(object):
class CategoryList(object):
"""
The :class:`~openlp.core.utils.CategoryList` class encapsulates a category
list for the :class:`~openlp.core.utils.ActionList` class and provides an
iterator interface for walking through the list of actions in this category.
The :class:`~openlp.core.utils.CategoryList` class encapsulates a category list for the
:class:`~openlp.core.utils.ActionList` class and provides an iterator interface for walking through the list of
actions in this category.
"""
def __init__(self):
@ -244,10 +243,9 @@ class CategoryList(object):
class ActionList(object):
"""
The :class:`~openlp.core.utils.ActionList` class contains a list of menu
actions and categories associated with those actions. Each category also
has a weight by which it is sorted when iterating through the list of
actions or categories.
The :class:`~openlp.core.utils.ActionList` class contains a list of menu actions and categories associated with
those actions. Each category also has a weight by which it is sorted when iterating through the list of actions or
categories.
"""
instance = None
shortcut_map = {}
@ -271,48 +269,44 @@ class ActionList(object):
"""
Add an action to the list of actions.
**Note**: The action's objectName must be set when you want to add it!
``action``
The action to add (QAction). **Note**, the action must not have an
empty ``objectName``.
The action to add (QAction). **Note**, the action must not have an empty ``objectName``.
``category``
The category this action belongs to. The category has to be a python
string. . **Note**, if the category is ``None``, the category and
its actions are being hidden in the shortcut dialog. However, if
they are added, it is possible to avoid assigning shortcuts twice,
which is important.
The category this action belongs to. The category has to be a python string. . **Note**, if the category
is ``None``, the category and its actions are being hidden in the shortcut dialog. However, if they are
added, it is possible to avoid assigning shortcuts twice, which is important.
``weight``
The weight specifies how important a category is. However, this only
has an impact on the order the categories are displayed.
The weight specifies how important a category is. However, this only has an impact on the order the
categories are displayed.
"""
if category not in self.categories:
self.categories.append(category)
action.defaultShortcuts = action.shortcuts()
settings = Settings()
settings.beginGroup(u'shortcuts')
# Get the default shortcut from the config.
action.defaultShortcuts = settings.get_default_value(action.objectName())
if weight is None:
self.categories[category].actions.append(action)
else:
self.categories[category].actions.add(action, weight)
# Load the shortcut from the config.
settings = Settings()
settings.beginGroup(u'shortcuts')
shortcuts = settings.value(action.objectName())
settings.endGroup()
if not shortcuts:
action.setShortcuts([])
return
# We have to do this to ensure that the loaded shortcut list e. g.
# STRG+O (German) is converted to CTRL+O, which is only done when we
# convert the strings in this way (QKeySequence -> QString -> unicode).
shortcuts = map(QtGui.QKeySequence, shortcuts)
shortcuts = map(unicode, map(QtGui.QKeySequence.toString, shortcuts))
# Check the alternate shortcut first, to avoid problems when the
# alternate shortcut becomes the primary shortcut after removing the
# (initial) primary shortcut due to conflicts.
# We have to do this to ensure that the loaded shortcut list e. g. STRG+O (German) is converted to CTRL+O,
# which is only done when we convert the strings in this way (QKeySequencet -> uncode).
shortcuts = map(QtGui.QKeySequence.toString, map(QtGui.QKeySequence, shortcuts))
# Check the alternate shortcut first, to avoid problems when the alternate shortcut becomes the primary shortcut
# after removing the (initial) primary shortcut due to conflicts.
if len(shortcuts) == 2:
existing_actions = ActionList.shortcut_map.get(shortcuts[1], [])
# Check for conflicts with other actions considering the shortcut
# context.
# Check for conflicts with other actions considering the shortcut context.
if self._is_shortcut_available(existing_actions, action):
actions = ActionList.shortcut_map.get(shortcuts[1], [])
actions.append(action)
@ -321,28 +315,24 @@ class ActionList(object):
shortcuts.remove(shortcuts[1])
# Check the primary shortcut.
existing_actions = ActionList.shortcut_map.get(shortcuts[0], [])
# Check for conflicts with other actions considering the shortcut
# context.
# Check for conflicts with other actions considering the shortcut context.
if self._is_shortcut_available(existing_actions, action):
actions = ActionList.shortcut_map.get(shortcuts[0], [])
actions.append(action)
ActionList.shortcut_map[shortcuts[0]] = actions
else:
shortcuts.remove(shortcuts[0])
action.setShortcuts(
[QtGui.QKeySequence(shortcut) for shortcut in shortcuts])
action.setShortcuts([QtGui.QKeySequence(shortcut) for shortcut in shortcuts])
def remove_action(self, action, category=None):
"""
This removes an action from its category. Empty categories are
automatically removed.
This removes an action from its category. Empty categories are automatically removed.
``action``
The ``QAction`` object to be removed.
``category``
The name (unicode string) of the category, which contains the
action. Defaults to None.
The name (unicode string) of the category, which contains the action. Defaults to None.
"""
if category not in self.categories:
return
@ -350,10 +340,9 @@ class ActionList(object):
# Remove empty categories.
if not self.categories[category].actions:
self.categories.remove(category)
shortcuts = map(unicode, map(QtGui.QKeySequence.toString, action.shortcuts()))
shortcuts = map(QtGui.QKeySequence.toString, action.shortcuts())
for shortcut in shortcuts:
# Remove action from the list of actions which are using this
# shortcut.
# Remove action from the list of actions which are using this shortcut.
ActionList.shortcut_map[shortcut].remove(action)
# Remove empty entries.
if not ActionList.shortcut_map[shortcut]:
@ -361,8 +350,7 @@ class ActionList(object):
def add_category(self, name, weight):
"""
Add an empty category to the list of categories. This is ony convenient
for categories with a given weight.
Add an empty category to the list of categories. This is only convenient for categories with a given weight.
``name``
The category's name.
@ -381,27 +369,24 @@ class ActionList(object):
def update_shortcut_map(self, action, old_shortcuts):
"""
Remove the action for the given ``old_shortcuts`` from the
``shortcut_map`` to ensure its up-to-dateness.
Remove the action for the given ``old_shortcuts`` from the ``shortcut_map`` to ensure its up-to-dateness.
**Note**: The new action's shortcuts **must** be assigned to the given
``action`` **before** calling this method.
**Note**: The new action's shortcuts **must** be assigned to the given ``action`` **before** calling this
method.
``action``
The action whose shortcuts are supposed to be updated in the
``shortcut_map``.
The action whose shortcuts are supposed to be updated in the ``shortcut_map``.
``old_shortcuts``
A list of unicode keysequences.
"""
for old_shortcut in old_shortcuts:
# Remove action from the list of actions which are using this
# shortcut.
# Remove action from the list of actions which are using this shortcut.
ActionList.shortcut_map[old_shortcut].remove(action)
# Remove empty entries.
if not ActionList.shortcut_map[old_shortcut]:
del ActionList.shortcut_map[old_shortcut]
new_shortcuts = map(unicode, map(QtGui.QKeySequence.toString, action.shortcuts()))
new_shortcuts = map(QtGui.QKeySequence.toString, action.shortcuts())
# Add the new shortcuts to the map.
for new_shortcut in new_shortcuts:
existing_actions = ActionList.shortcut_map.get(new_shortcut, [])
@ -410,8 +395,7 @@ class ActionList(object):
def _is_shortcut_available(self, existing_actions, action):
"""
Checks if the given ``action`` may use its assigned shortcut(s) or not.
Returns ``True`` or ``False.
Checks if the given ``action`` may use its assigned shortcut(s) or not. Returns ``True`` or ``False.
``existing_actions``
A list of actions which already use a particular shortcut.
@ -419,28 +403,29 @@ class ActionList(object):
``action``
The action which wants to use a particular shortcut.
"""
local = action.shortcutContext() in [QtCore.Qt.WindowShortcut, QtCore.Qt.ApplicationShortcut]
affected_actions = filter(lambda a: isinstance(a, QtGui.QAction),
self.getAllChildObjects(action.parent())) if local else []
global_context = action.shortcutContext() in [QtCore.Qt.WindowShortcut, QtCore.Qt.ApplicationShortcut]
affected_actions = []
if global_context:
affected_actions = filter(
lambda a: isinstance(a, QtGui.QAction), self.get_all_child_objects(action.parent()))
for existing_action in existing_actions:
if action is existing_action:
continue
if not local or existing_action in affected_actions:
if existing_action in affected_actions:
return False
if existing_action.shortcutContext() in [QtCore.Qt.WindowShortcut, QtCore.Qt.ApplicationShortcut]:
return False
elif action in self.getAllChildObjects(existing_action.parent()):
elif action in self.get_all_child_objects(existing_action.parent()):
return False
return True
def getAllChildObjects(self, qobject):
def get_all_child_objects(self, qobject):
"""
Goes recursively through the children of ``qobject`` and returns a list
of all child objects.
Goes recursively through the children of ``qobject`` and returns a list of all child objects.
"""
children = [child for child in qobject.children()]
for child in qobject.children():
children.append(self.getAllChildObjects(child))
children = qobject.children()
# Append the children's children.
children.extend(map(self.get_all_child_objects, children))
return children
@ -448,5 +433,5 @@ class CategoryOrder(object):
"""
An enumeration class for category weights.
"""
standardMenu = -20
standardToolbar = -10
standard_menu = -20
standard_toolbar = -10

View File

@ -0,0 +1,180 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2013 Raoul Snyman #
# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
The :mod:`openlp.core.utils.applocation` module provides an utility for OpenLP receiving the data path etc.
"""
import logging
import os
import sys
from openlp.core.lib import Settings
from openlp.core.utils import _get_frozen_path
if sys.platform != u'win32' and sys.platform != u'darwin':
try:
from xdg import BaseDirectory
XDG_BASE_AVAILABLE = True
except ImportError:
XDG_BASE_AVAILABLE = False
import openlp
from openlp.core.lib import check_directory_exists
log = logging.getLogger(__name__)
class AppLocation(object):
"""
The :class:`AppLocation` class is a static class which retrieves a
directory based on the directory type.
"""
AppDir = 1
ConfigDir = 2
DataDir = 3
PluginsDir = 4
VersionDir = 5
CacheDir = 6
LanguageDir = 7
# Base path where data/config/cache dir is located
BaseDir = None
@staticmethod
def get_directory(dir_type=1):
"""
Return the appropriate directory according to the directory type.
``dir_type``
The directory type you want, for instance the data directory.
"""
if dir_type == AppLocation.AppDir:
return _get_frozen_path(os.path.abspath(os.path.split(sys.argv[0])[0]), os.path.split(openlp.__file__)[0])
elif dir_type == AppLocation.PluginsDir:
app_path = os.path.abspath(os.path.split(sys.argv[0])[0])
return _get_frozen_path(os.path.join(app_path, u'plugins'),
os.path.join(os.path.split(openlp.__file__)[0], u'plugins'))
elif dir_type == AppLocation.VersionDir:
return _get_frozen_path(os.path.abspath(os.path.split(sys.argv[0])[0]), os.path.split(openlp.__file__)[0])
elif dir_type == AppLocation.LanguageDir:
app_path = _get_frozen_path(os.path.abspath(os.path.split(sys.argv[0])[0]), _get_os_dir_path(dir_type))
return os.path.join(app_path, u'i18n')
elif dir_type == AppLocation.DataDir and AppLocation.BaseDir:
return os.path.join(AppLocation.BaseDir, 'data')
else:
return _get_os_dir_path(dir_type)
@staticmethod
def get_data_path():
"""
Return the path OpenLP stores all its data under.
"""
# Check if we have a different data location.
if Settings().contains(u'advanced/data path'):
path = Settings().value(u'advanced/data path')
else:
path = AppLocation.get_directory(AppLocation.DataDir)
check_directory_exists(path)
return os.path.normpath(path)
@staticmethod
def get_files(section=None, extension=None):
"""
Get a list of files from the data files path.
``section``
Defaults to *None*. The section of code getting the files - used to load from a section's data subdirectory.
``extension``
Defaults to *None*. The extension to search for. For example::
u'.png'
"""
path = AppLocation.get_data_path()
if section:
path = os.path.join(path, section)
try:
files = os.listdir(path)
except OSError:
return []
if extension:
return [filename for filename in files if extension == os.path.splitext(filename)[1]]
else:
# no filtering required
return files
@staticmethod
def get_section_data_path(section):
"""
Return the path a particular module stores its data under.
"""
data_path = AppLocation.get_data_path()
path = os.path.join(data_path, section)
check_directory_exists(path)
return path
def _get_os_dir_path(dir_type):
"""
Return a path based on which OS and environment we are running in.
"""
encoding = sys.getfilesystemencoding()
if sys.platform == u'win32':
if dir_type == AppLocation.DataDir:
return os.path.join(unicode(os.getenv(u'APPDATA'), encoding), u'openlp', u'data')
elif dir_type == AppLocation.LanguageDir:
return os.path.split(openlp.__file__)[0]
return os.path.join(unicode(os.getenv(u'APPDATA'), encoding), u'openlp')
elif sys.platform == u'darwin':
if dir_type == AppLocation.DataDir:
return os.path.join(unicode(os.getenv(u'HOME'), encoding),
u'Library', u'Application Support', u'openlp', u'Data')
elif dir_type == AppLocation.LanguageDir:
return os.path.split(openlp.__file__)[0]
return os.path.join(unicode(os.getenv(u'HOME'), encoding), u'Library', u'Application Support', u'openlp')
else:
if dir_type == AppLocation.LanguageDir:
prefixes = [u'/usr/local', u'/usr']
for prefix in prefixes:
directory = os.path.join(prefix, u'share', u'openlp')
if os.path.exists(directory):
return directory
return os.path.join(u'/usr', u'share', u'openlp')
if XDG_BASE_AVAILABLE:
if dir_type == AppLocation.ConfigDir:
return os.path.join(unicode(BaseDirectory.xdg_config_home, encoding), u'openlp')
elif dir_type == AppLocation.DataDir:
return os.path.join(unicode(BaseDirectory.xdg_data_home, encoding), u'openlp')
elif dir_type == AppLocation.CacheDir:
return os.path.join(unicode(BaseDirectory.xdg_cache_home, encoding), u'openlp')
if dir_type == AppLocation.DataDir:
return os.path.join(unicode(os.getenv(u'HOME'), encoding), u'.openlp', u'data')
return os.path.join(unicode(os.getenv(u'HOME'), encoding), u'.openlp')

View File

@ -122,7 +122,7 @@ __default_settings__ = {
u'alerts/background color': u'#660000',
u'alerts/font color': u'#ffffff',
u'alerts/timeout': 5
}
}
class AlertsPlugin(Plugin):
@ -133,7 +133,7 @@ class AlertsPlugin(Plugin):
self.weight = -3
self.iconPath = u':/plugins/plugin_alerts.png'
self.icon = build_icon(self.iconPath)
self.alertsmanager = AlertsManager(self)
self.alerts_manager = AlertsManager(self)
self.manager = Manager(u'alerts', init_schema)
self.alertForm = AlertForm(self)
@ -150,7 +150,7 @@ class AlertsPlugin(Plugin):
self.toolsAlertItem = create_action(tools_menu, u'toolsAlertItem',
text=translate('AlertsPlugin', '&Alert'), icon=u':/plugins/plugin_alerts.png',
statustip=translate('AlertsPlugin', 'Show an alert message.'),
visible=False, shortcuts=[u'F7'], triggers=self.onAlertsTrigger)
visible=False, can_shortcuts=True, triggers=self.onAlertsTrigger)
self.main_window.toolsMenu.addAction(self.toolsAlertItem)
def initialise(self):
@ -176,7 +176,7 @@ class AlertsPlugin(Plugin):
Settings().setValue(self.settingsSection + u'/active', self.alertsActive)
def onAlertsTrigger(self):
self.alertForm.loadList()
self.alertForm.load_list()
self.alertForm.exec_()
def about(self):
@ -194,7 +194,8 @@ class AlertsPlugin(Plugin):
u'plural': translate('AlertsPlugin', 'Alerts', 'name plural')
}
## Name for MediaDockManager, SettingsManager ##
self.textStrings[StringContent.VisibleName] = {u'title': translate('AlertsPlugin', 'Alerts', 'container title')
self.textStrings[StringContent.VisibleName] = {
u'title': translate('AlertsPlugin', 'Alerts', 'container title')
}
def getDisplayJavaScript(self):

View File

@ -32,61 +32,63 @@ from PyQt4 import QtGui
from openlp.core.lib import build_icon, translate
from openlp.core.lib.ui import create_button, create_button_box
class Ui_AlertDialog(object):
def setupUi(self, alertDialog):
alertDialog.setObjectName(u'alertDialog')
alertDialog.resize(400, 300)
alertDialog.setWindowIcon(build_icon(u':/icon/openlp-logo-16x16.png'))
self.alertDialogLayout = QtGui.QGridLayout(alertDialog)
self.alertDialogLayout.setObjectName(u'alertDialogLayout')
self.alertTextLayout = QtGui.QFormLayout()
self.alertTextLayout.setObjectName(u'alertTextLayout')
self.alertEntryLabel = QtGui.QLabel(alertDialog)
self.alertEntryLabel.setObjectName(u'alertEntryLabel')
self.alertTextEdit = QtGui.QLineEdit(alertDialog)
self.alertTextEdit.setObjectName(u'alertTextEdit')
self.alertEntryLabel.setBuddy(self.alertTextEdit)
self.alertTextLayout.addRow(self.alertEntryLabel, self.alertTextEdit)
self.alertParameter = QtGui.QLabel(alertDialog)
self.alertParameter.setObjectName(u'alertParameter')
self.parameterEdit = QtGui.QLineEdit(alertDialog)
self.parameterEdit.setObjectName(u'parameterEdit')
self.alertParameter.setBuddy(self.parameterEdit)
self.alertTextLayout.addRow(self.alertParameter, self.parameterEdit)
self.alertDialogLayout.addLayout(self.alertTextLayout, 0, 0, 1, 2)
self.alertListWidget = QtGui.QListWidget(alertDialog)
self.alertListWidget.setAlternatingRowColors(True)
self.alertListWidget.setObjectName(u'alertListWidget')
self.alertDialogLayout.addWidget(self.alertListWidget, 1, 0)
self.manageButtonLayout = QtGui.QVBoxLayout()
self.manageButtonLayout.setObjectName(u'manageButtonLayout')
self.newButton = QtGui.QPushButton(alertDialog)
self.newButton.setIcon(build_icon(u':/general/general_new.png'))
self.newButton.setObjectName(u'newButton')
self.manageButtonLayout.addWidget(self.newButton)
self.saveButton = QtGui.QPushButton(alertDialog)
self.saveButton.setEnabled(False)
self.saveButton.setIcon(build_icon(u':/general/general_save.png'))
self.saveButton.setObjectName(u'saveButton')
self.manageButtonLayout.addWidget(self.saveButton)
self.deleteButton = create_button(alertDialog, u'deleteButton', role=u'delete', enabled=False,
click=alertDialog.onDeleteButtonClicked)
self.manageButtonLayout.addWidget(self.deleteButton)
self.manageButtonLayout.addStretch()
self.alertDialogLayout.addLayout(self.manageButtonLayout, 1, 1)
displayIcon = build_icon(u':/general/general_live.png')
self.displayButton = create_button(alertDialog, u'displayButton', icon=displayIcon, enabled=False)
self.displayCloseButton = create_button(alertDialog, u'displayCloseButton', icon=displayIcon, enabled=False)
self.button_box = create_button_box(alertDialog, u'button_box', [u'close'],
[self.displayButton, self.displayCloseButton])
self.alertDialogLayout.addWidget(self.button_box, 2, 0, 1, 2)
self.retranslateUi(alertDialog)
def retranslateUi(self, alertDialog):
alertDialog.setWindowTitle(translate('AlertsPlugin.AlertForm', 'Alert Message'))
self.alertEntryLabel.setText(translate('AlertsPlugin.AlertForm', 'Alert &text:'))
self.alertParameter.setText(translate('AlertsPlugin.AlertForm', '&Parameter:'))
self.newButton.setText(translate('AlertsPlugin.AlertForm', '&New'))
self.saveButton.setText(translate('AlertsPlugin.AlertForm', '&Save'))
self.displayButton.setText(translate('AlertsPlugin.AlertForm', 'Displ&ay'))
self.displayCloseButton.setText(translate('AlertsPlugin.AlertForm', 'Display && Cl&ose'))
class Ui_AlertDialog(object):
def setupUi(self, alert_dialog):
alert_dialog.setObjectName(u'alert_dialog')
alert_dialog.resize(400, 300)
alert_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo-16x16.png'))
self.alert_dialog_layout = QtGui.QGridLayout(alert_dialog)
self.alert_dialog_layout.setObjectName(u'alert_dialog_layout')
self.alert_text_layout = QtGui.QFormLayout()
self.alert_text_layout.setObjectName(u'alert_text_layout')
self.alert_entry_label = QtGui.QLabel(alert_dialog)
self.alert_entry_label.setObjectName(u'alert_entry_label')
self.alert_text_edit = QtGui.QLineEdit(alert_dialog)
self.alert_text_edit.setObjectName(u'alert_text_edit')
self.alert_entry_label.setBuddy(self.alert_text_edit)
self.alert_text_layout.addRow(self.alert_entry_label, self.alert_text_edit)
self.alert_parameter = QtGui.QLabel(alert_dialog)
self.alert_parameter.setObjectName(u'alert_parameter')
self.parameter_edit = QtGui.QLineEdit(alert_dialog)
self.parameter_edit.setObjectName(u'parameter_edit')
self.alert_parameter.setBuddy(self.parameter_edit)
self.alert_text_layout.addRow(self.alert_parameter, self.parameter_edit)
self.alert_dialog_layout.addLayout(self.alert_text_layout, 0, 0, 1, 2)
self.alert_list_widget = QtGui.QListWidget(alert_dialog)
self.alert_list_widget.setAlternatingRowColors(True)
self.alert_list_widget.setObjectName(u'alert_list_widget')
self.alert_dialog_layout.addWidget(self.alert_list_widget, 1, 0)
self.manage_button_layout = QtGui.QVBoxLayout()
self.manage_button_layout.setObjectName(u'manage_button_layout')
self.new_button = QtGui.QPushButton(alert_dialog)
self.new_button.setIcon(build_icon(u':/general/general_new.png'))
self.new_button.setObjectName(u'new_button')
self.manage_button_layout.addWidget(self.new_button)
self.save_button = QtGui.QPushButton(alert_dialog)
self.save_button.setEnabled(False)
self.save_button.setIcon(build_icon(u':/general/general_save.png'))
self.save_button.setObjectName(u'save_button')
self.manage_button_layout.addWidget(self.save_button)
self.delete_button = create_button(alert_dialog, u'delete_button', role=u'delete', enabled=False,
click=alert_dialog.onDeleteButtonClicked)
self.manage_button_layout.addWidget(self.delete_button)
self.manage_button_layout.addStretch()
self.alert_dialog_layout.addLayout(self.manage_button_layout, 1, 1)
displayIcon = build_icon(u':/general/general_live.png')
self.display_button = create_button(alert_dialog, u'display_button', icon=displayIcon, enabled=False)
self.display_close_button = create_button(alert_dialog, u'display_close_button', icon=displayIcon,
enabled=False)
self.button_box = create_button_box(alert_dialog, u'button_box', [u'close'],
[self.display_button, self.display_close_button])
self.alert_dialog_layout.addWidget(self.button_box, 2, 0, 1, 2)
self.retranslateUi(alert_dialog)
def retranslateUi(self, alert_dialog):
alert_dialog.setWindowTitle(translate('AlertsPlugin.AlertForm', 'Alert Message'))
self.alert_entry_label.setText(translate('AlertsPlugin.AlertForm', 'Alert &text:'))
self.alert_parameter.setText(translate('AlertsPlugin.AlertForm', '&Parameter:'))
self.new_button.setText(translate('AlertsPlugin.AlertForm', '&New'))
self.save_button.setText(translate('AlertsPlugin.AlertForm', '&Save'))
self.display_button.setText(translate('AlertsPlugin.AlertForm', 'Displ&ay'))
self.display_close_button.setText(translate('AlertsPlugin.AlertForm', 'Display && Cl&ose'))

View File

@ -48,131 +48,131 @@ class AlertForm(QtGui.QDialog, Ui_AlertDialog):
self.item_id = None
super(AlertForm, self).__init__(self.plugin.main_window)
self.setupUi(self)
QtCore.QObject.connect(self.displayButton, QtCore.SIGNAL(u'clicked()'), self.onDisplayClicked)
QtCore.QObject.connect(self.displayCloseButton, QtCore.SIGNAL(u'clicked()'), self.onDisplayCloseClicked)
QtCore.QObject.connect(self.alertTextEdit, QtCore.SIGNAL(u'textChanged(const QString&)'), self.onTextChanged)
QtCore.QObject.connect(self.newButton, QtCore.SIGNAL(u'clicked()'), self.onNewClick)
QtCore.QObject.connect(self.saveButton, QtCore.SIGNAL(u'clicked()'), self.onSaveClick)
QtCore.QObject.connect(self.alertListWidget, QtCore.SIGNAL(u'doubleClicked(QModelIndex)'), self.onDoubleClick)
QtCore.QObject.connect(self.alertListWidget, QtCore.SIGNAL(u'clicked(QModelIndex)'), self.onSingleClick)
QtCore.QObject.connect(self.alertListWidget, QtCore.SIGNAL(u'currentRowChanged(int)'), self.onCurrentRowChanged)
self.display_button.clicked.connect(self.on_display_clicked)
self.display_close_button.clicked.connect(self.on_display_close_clicked)
self.alert_text_edit.textChanged.connect(self.on_text_changed)
self.new_button.clicked.connect(self.on_new_click)
self.save_button.clicked.connect(self.on_save_all)
self.alert_list_widget.doubleClicked.connect(self.on_double_click)
self.alert_list_widget.clicked.connect(self.on_single_click)
self.alert_list_widget.currentRowChanged.connect(self.on_current_row_changed)
def exec_(self):
"""
Execute the dialog and return the exit code.
"""
self.displayButton.setEnabled(False)
self.displayCloseButton.setEnabled(False)
self.alertTextEdit.setText(u'')
self.display_button.setEnabled(False)
self.display_close_button.setEnabled(False)
self.alert_text_edit.setText(u'')
return QtGui.QDialog.exec_(self)
def loadList(self):
def load_list(self):
"""
Loads the list with alerts.
"""
self.alertListWidget.clear()
self.alert_list_widget.clear()
alerts = self.manager.get_all_objects(AlertItem, order_by_ref=AlertItem.text)
for alert in alerts:
item_name = QtGui.QListWidgetItem(alert.text)
item_name.setData(QtCore.Qt.UserRole, alert.id)
self.alertListWidget.addItem(item_name)
if alert.text == unicode(self.alertTextEdit.text()):
self.alert_list_widget.addItem(item_name)
if alert.text == unicode(self.alert_text_edit.text()):
self.item_id = alert.id
self.alertListWidget.setCurrentRow(self.alertListWidget.row(item_name))
self.alert_list_widget.setCurrentRow(self.alert_list_widget.row(item_name))
def onDisplayClicked(self):
def on_display_clicked(self):
"""
Display the current alert text.
"""
self.triggerAlert(self.alertTextEdit.text())
self.trigger_alert(self.alert_text_edit.text())
def onDisplayCloseClicked(self):
def on_display_close_clicked(self):
"""
Close the alert preview.
"""
if self.triggerAlert(self.alertTextEdit.text()):
if self.trigger_alert(self.alert_text_edit.text()):
self.close()
def onDeleteButtonClicked(self):
"""
Deletes the selected item.
"""
item = self.alertListWidget.currentItem()
item = self.alert_list_widget.currentItem()
if item:
item_id = item.data(QtCore.Qt.UserRole)
self.manager.delete_object(AlertItem, item_id)
row = self.alertListWidget.row(item)
self.alertListWidget.takeItem(row)
row = self.alert_list_widget.row(item)
self.alert_list_widget.takeItem(row)
self.item_id = None
self.alertTextEdit.setText(u'')
self.alert_text_edit.setText(u'')
def onNewClick(self):
def on_new_click(self):
"""
Create a new alert.
"""
if not self.alertTextEdit.text():
if not self.alert_text_edit.text():
QtGui.QMessageBox.information(self,
translate('AlertsPlugin.AlertForm', 'New Alert'),
translate('AlertsPlugin.AlertForm', 'You haven\'t specified any text for your alert. \n'
'Please type in some text before clicking New.'))
else:
alert = AlertItem()
alert.text = self.alertTextEdit.text()
alert.text = self.alert_text_edit.text()
self.manager.save_object(alert)
self.loadList()
self.load_list()
def onSaveClick(self):
def on_save_all(self):
"""
Save the alert, we are editing.
"""
if self.item_id:
alert = self.manager.get_object(AlertItem, self.item_id)
alert.text = self.alertTextEdit.text()
alert.text = self.alert_text_edit.text()
self.manager.save_object(alert)
self.item_id = None
self.loadList()
self.saveButton.setEnabled(False)
self.load_list()
self.save_button.setEnabled(False)
def onTextChanged(self):
def on_text_changed(self):
"""
Enable save button when data has been changed by editing the form.
"""
# Only enable the button, if we are editing an item.
if self.item_id:
self.saveButton.setEnabled(True)
if self.alertTextEdit.text():
self.displayButton.setEnabled(True)
self.displayCloseButton.setEnabled(True)
self.save_button.setEnabled(True)
if self.alert_text_edit.text():
self.display_button.setEnabled(True)
self.display_close_button.setEnabled(True)
else:
self.displayButton.setEnabled(False)
self.displayCloseButton.setEnabled(False)
self.display_button.setEnabled(False)
self.display_close_button.setEnabled(False)
def onDoubleClick(self):
def on_double_click(self):
"""
List item has been double clicked to display it.
"""
item = self.alertListWidget.selectedIndexes()[0]
bitem = self.alertListWidget.item(item.row())
self.triggerAlert(bitem.text())
self.alertTextEdit.setText(bitem.text())
item = self.alert_list_widget.selectedIndexes()[0]
bitem = self.alert_list_widget.item(item.row())
self.trigger_alert(bitem.text())
self.alert_text_edit.setText(bitem.text())
self.item_id = bitem.data(QtCore.Qt.UserRole)
self.saveButton.setEnabled(False)
self.save_button.setEnabled(False)
def onSingleClick(self):
def on_single_click(self):
"""
List item has been single clicked to add it to the edit field so it can
be changed.
"""
item = self.alertListWidget.selectedIndexes()[0]
bitem = self.alertListWidget.item(item.row())
self.alertTextEdit.setText(bitem.text())
item = self.alert_list_widget.selectedIndexes()[0]
bitem = self.alert_list_widget.item(item.row())
self.alert_text_edit.setText(bitem.text())
self.item_id = bitem.data(QtCore.Qt.UserRole)
# If the alert does not contain '<>' we clear the ParameterEdit field.
if self.alertTextEdit.text().find(u'<>') == -1:
self.parameterEdit.setText(u'')
self.saveButton.setEnabled(False)
if self.alert_text_edit.text().find(u'<>') == -1:
self.parameter_edit.setText(u'')
self.save_button.setEnabled(False)
def triggerAlert(self, text):
def trigger_alert(self, text):
"""
Prepares the alert text for displaying.
@ -182,42 +182,44 @@ class AlertForm(QtGui.QDialog, Ui_AlertDialog):
if not text:
return False
# We found '<>' in the alert text, but the ParameterEdit field is empty.
if text.find(u'<>') != -1 and not self.parameterEdit.text() and QtGui.QMessageBox.question(self,
if text.find(u'<>') != -1 and not self.parameter_edit.text() and QtGui.QMessageBox.question(self,
translate('AlertsPlugin.AlertForm', 'No Parameter Found'),
translate('AlertsPlugin.AlertForm', 'You have not entered a parameter to be replaced.\n'
'Do you want to continue anyway?'),
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.No | QtGui.QMessageBox.Yes)) == QtGui.QMessageBox.No:
self.parameterEdit.setFocus()
self.parameter_edit.setFocus()
return False
# The ParameterEdit field is not empty, but we have not found '<>'
# in the alert text.
elif text.find(u'<>') == -1 and self.parameterEdit.text() and QtGui.QMessageBox.question(self,
elif text.find(u'<>') == -1 and self.parameter_edit.text() and QtGui.QMessageBox.question(self,
translate('AlertsPlugin.AlertForm', 'No Placeholder Found'),
translate('AlertsPlugin.AlertForm', 'The alert text does not contain \'<>\'.\n'
'Do you want to continue anyway?'),
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.No | QtGui.QMessageBox.Yes)) == QtGui.QMessageBox.No:
self.parameterEdit.setFocus()
self.parameter_edit.setFocus()
return False
text = text.replace(u'<>', self.parameterEdit.text())
self.plugin.alertsmanager.displayAlert(text)
text = text.replace(u'<>', self.parameter_edit.text())
self.plugin.alerts_manager.display_alert(text)
self.plugin.alertsmanager.display_alert(text)
return True
def onCurrentRowChanged(self, row):
def on_current_row_changed(self, row):
"""
Called when the *alertListWidget*'s current row has been changed. This
Called when the *alert_list_widget*'s current row has been changed. This
enables or disables buttons which require an item to act on.
``row``
The row (int). If there is no current row, the value is -1.
"""
if row == -1:
self.displayButton.setEnabled(False)
self.displayCloseButton.setEnabled(False)
self.saveButton.setEnabled(False)
self.deleteButton.setEnabled(False)
self.display_button.setEnabled(False)
self.display_close_button.setEnabled(False)
self.save_button.setEnabled(False)
self.delete_button.setEnabled(False)
else:
self.displayButton.setEnabled(True)
self.displayCloseButton.setEnabled(True)
self.deleteButton.setEnabled(True)
self.display_button.setEnabled(True)
self.display_close_button.setEnabled(True)
self.delete_button.setEnabled(True)
# We do not need to enable the save button, as it is only enabled
# when typing text in the "alertTextEdit".
# when typing text in the "alert_text_edit".

View File

@ -37,8 +37,10 @@ from PyQt4 import QtCore
from openlp.core.lib import Registry, translate
log = logging.getLogger(__name__)
class AlertsManager(QtCore.QObject):
"""
AlertsManager manages the settings of Alerts.
@ -47,7 +49,6 @@ class AlertsManager(QtCore.QObject):
def __init__(self, parent):
QtCore.QObject.__init__(self, parent)
self.screen = None
self.timer_id = 0
self.alert_list = []
Registry().register_function(u'live_display_active', self.generate_alert)
@ -87,7 +88,7 @@ class AlertsManager(QtCore.QObject):
return
text = self.alert_list.pop(0)
alertTab = self.parent().settingsTab
self.parent().liveController.display.alert(text, alertTab.location)
self.live_controller.display.alert(text, alertTab.location)
# Check to see if we have a timer running.
if self.timer_id == 0:
self.timer_id = self.startTimer(int(alertTab.timeout) * 1000)
@ -103,11 +104,21 @@ class AlertsManager(QtCore.QObject):
log.debug(u'timer event')
if event.timerId() == self.timer_id:
alertTab = self.parent().settingsTab
self.parent().liveController.display.alert(u'', alertTab.location)
self.live_controller.display.alert(u'', alertTab.location)
self.killTimer(self.timer_id)
self.timer_id = 0
self.generate_alert()
def _get_live_controller(self):
"""
Adds the live controller to the class dynamically
"""
if not hasattr(self, u'_live_controller'):
self._live_controller = Registry().get(u'live_controller')
return self._live_controller
live_controller = property(_get_live_controller)
def _get_main_window(self):
"""
Adds the main window to the class dynamically
@ -116,4 +127,4 @@ class AlertsManager(QtCore.QObject):
self._main_window = Registry().get(u'main_window')
return self._main_window
main_window = property(_get_main_window)
main_window = property(_get_main_window)

View File

@ -30,7 +30,7 @@
import logging
import os
from openlp.core.lib import Registry, SettingsManager, Settings, translate
from openlp.core.lib import Registry, Settings, translate
from openlp.core.utils import AppLocation, delete_file
from openlp.plugins.bibles.lib import parse_reference, get_reference_separator, LanguageSelection
from openlp.plugins.bibles.lib.db import BibleDB, BibleMeta
@ -137,7 +137,7 @@ class BibleManager(object):
BibleDB class.
"""
log.debug(u'Reload bibles')
files = SettingsManager.get_files(self.settingsSection, self.suffix)
files = AppLocation.get_files(self.settingsSection, self.suffix)
if u'alternative_book_names.sqlite' in files:
files.remove(u'alternative_book_names.sqlite')
log.debug(u'Bible Files %s', files)

View File

@ -27,7 +27,7 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
from PyQt4 import QtCore, QtGui
from PyQt4 import QtGui
import logging
@ -100,4 +100,14 @@ class ImagePlugin(Plugin):
last part of saving the config.
"""
background = QtGui.QColor(Settings().value(self.settingsSection + u'/background color'))
self.liveController.imageManager.update_images_border(ImageSource.ImagePlugin, background)
self.image_manager.update_images_border(ImageSource.ImagePlugin, background)
def _get_image_manager(self):
"""
Adds the image manager to the class dynamically
"""
if not hasattr(self, u'_image_manager'):
self._image_manager = Registry().get(u'image_manager')
return self._image_manager
image_manager = property(_get_image_manager)

View File

@ -33,8 +33,8 @@ import os
from PyQt4 import QtCore, QtGui
from openlp.core.lib import ItemCapabilities, MediaManagerItem, Registry, ServiceItemContext, Settings, \
SettingsManager, StringContent, TreeWidgetWithDnD, UiStrings, build_icon, check_directory_exists, \
check_item_selected, create_thumb, translate, validate_thumb
StringContent, TreeWidgetWithDnD, UiStrings, build_icon, check_directory_exists, check_item_selected, \
create_thumb, translate, validate_thumb
from openlp.core.lib.ui import create_widget_action, critical_error_message_box
from openlp.core.utils import AppLocation, delete_file, locale_compare, get_images_filter
from openlp.plugins.images.forms import AddGroupForm, ChooseGroupForm

View File

@ -58,6 +58,7 @@ else:
from PyQt4 import QtCore
from openlp.core.lib import ScreenList
from openlp.core.utils import delete_file, get_uno_command, get_uno_instance
from presentationcontroller import PresentationController, PresentationDocument
@ -254,7 +255,7 @@ class ImpressDocument(PresentationDocument):
window = self.document.getCurrentController().getFrame().getContainerWindow()
window.setVisible(False)
self.presentation = self.document.getPresentation()
self.presentation.Display = self.controller.plugin.renderer.screens.current[u'number'] + 1
self.presentation.Display = ScreenList().current[u'number'] + 1
self.control = None
self.create_thumbnails()
return True

View File

@ -36,6 +36,7 @@ if os.name == u'nt':
import win32ui
import pywintypes
from openlp.core.lib import ScreenList
from presentationcontroller import PresentationController, PresentationDocument
log = logging.getLogger(__name__)
@ -252,8 +253,7 @@ class PowerpointDocument(PresentationDocument):
dpi = win32ui.GetForegroundWindow().GetDC().GetDeviceCaps(88)
except win32ui.error:
dpi = 96
renderer = self.controller.plugin.renderer
rect = renderer.screens.current[u'size']
rect = ScreenList().current[u'size']
ppt_window = self.presentation.SlideShowSettings.Run()
if not ppt_window:
return

View File

@ -34,6 +34,7 @@ if os.name == u'nt':
from ctypes import cdll
from ctypes.wintypes import RECT
from openlp.core.lib import ScreenList
from presentationcontroller import PresentationController, PresentationDocument
log = logging.getLogger(__name__)
@ -83,7 +84,7 @@ class PptviewController(PresentationController):
if self.process:
return
log.debug(u'start PPTView')
dllpath = os.path.join(self.plugin_manager.basepath, u'presentations', u'lib', u'pptviewlib',
dllpath = os.path.join(self.plugin_manager.base_path, u'presentations', u'lib', u'pptviewlib',
u'pptviewlib.dll')
self.process = cdll.LoadLibrary(dllpath)
if log.isEnabledFor(logging.DEBUG):
@ -120,8 +121,7 @@ class PptviewDocument(PresentationDocument):
PptView task started earlier.
"""
log.debug(u'LoadPresentation')
renderer = self.controller.plugin.renderer
rect = renderer.screens.current[u'size']
rect = ScreenList().current[u'size']
rect = RECT(rect.x(), rect.y(), rect.right(), rect.bottom())
filepath = str(self.filepath.replace(u'/', u'\\'))
if not os.path.isdir(self.get_temp_folder()):

View File

@ -124,6 +124,7 @@ from PyQt4 import QtCore, QtNetwork
from mako.template import Template
from openlp.core.lib import Registry, Settings, PluginStatus, StringContent
from openlp.core.utils import AppLocation, translate
log = logging.getLogger(__name__)
@ -250,12 +251,11 @@ class HttpConnection(object):
def _get_service_items(self):
service_items = []
service_manager = self.parent.plugin.serviceManager
if self.parent.current_item:
current_unique_identifier = self.parent.current_item.unique_identifier
else:
current_unique_identifier = None
for item in service_manager.serviceItems:
for item in self.service_manager.service_items:
service_item = item[u'service_item']
service_items.append({
u'id': unicode(service_item.unique_identifier),
@ -386,13 +386,13 @@ class HttpConnection(object):
Poll OpenLP to determine the current slide number and item name.
"""
result = {
u'service': self.parent.plugin.serviceManager.service_id,
u'service': self.service_manager.service_id,
u'slide': self.parent.current_slide or 0,
u'item': self.parent.current_item.unique_identifier if self.parent.current_item else u'',
u'twelve':Settings().value(u'remotes/twelve hour'),
u'blank': self.parent.plugin.liveController.blankScreen.isChecked(),
u'theme': self.parent.plugin.liveController.themeScreen.isChecked(),
u'display': self.parent.plugin.liveController.desktopScreen.isChecked()
u'blank': self.live_controller.blankScreen.isChecked(),
u'theme': self.live_controller.themeScreen.isChecked(),
u'display': self.live_controller.desktopScreen.isChecked()
}
return HttpResponse(json.dumps({u'results': result}),
{u'Content-Type': u'application/json'})
@ -412,7 +412,7 @@ class HttpConnection(object):
"""
Send an alert.
"""
plugin = self.parent.plugin.pluginManager.get_plugin_by_name("alerts")
plugin = self.plugin_manager.get_plugin_by_name("alerts")
if plugin.status == PluginStatus.Active:
try:
text = json.loads(self.url_params[u'data'][0])[u'request'][u'text']
@ -504,7 +504,7 @@ class HttpConnection(object):
"""
if action == u'search':
searches = []
for plugin in self.parent.plugin.pluginManager.plugins:
for plugin in self.plugin_manager.plugins:
if plugin.status == PluginStatus.Active and plugin.mediaItem and plugin.mediaItem.hasSearch:
searches.append([plugin.name, unicode(plugin.textStrings[StringContent.Name][u'plural'])])
return HttpResponse(
@ -523,7 +523,7 @@ class HttpConnection(object):
except KeyError, ValueError:
return HttpResponse(code=u'400 Bad Request')
text = urllib.unquote(text)
plugin = self.parent.plugin.pluginManager.get_plugin_by_name(type)
plugin = self.plugin_manager.get_plugin_by_name(type)
if plugin.status == PluginStatus.Active and plugin.mediaItem and plugin.mediaItem.hasSearch:
results = plugin.mediaItem.search(text, False)
else:
@ -539,7 +539,7 @@ class HttpConnection(object):
id = json.loads(self.url_params[u'data'][0])[u'request'][u'id']
except KeyError, ValueError:
return HttpResponse(code=u'400 Bad Request')
plugin = self.parent.plugin.pluginManager.get_plugin_by_name(type)
plugin = self.plugin_manager.get_plugin_by_name(type)
if plugin.status == PluginStatus.Active and plugin.mediaItem:
plugin.mediaItem.goLive(id, remote=True)
return HttpResponse(code=u'200 OK')
@ -552,7 +552,7 @@ class HttpConnection(object):
id = json.loads(self.url_params[u'data'][0])[u'request'][u'id']
except KeyError, ValueError:
return HttpResponse(code=u'400 Bad Request')
plugin = self.parent.plugin.pluginManager.get_plugin_by_name(type)
plugin = self.plugin_manager.get_plugin_by_name(type)
if plugin.status == PluginStatus.Active and plugin.mediaItem:
item_id = plugin.mediaItem.createItemFromId(id)
plugin.mediaItem.addToService(item_id, remote=True)
@ -583,3 +583,33 @@ class HttpConnection(object):
self.socket.close()
self.socket = None
self.parent.close_connection(self)
def _get_service_manager(self):
"""
Adds the service manager to the class dynamically
"""
if not hasattr(self, u'_service_manager'):
self._service_manager = Registry().get(u'service_manager')
return self._service_manager
service_manager = property(_get_service_manager)
def _get_live_controller(self):
"""
Adds the live controller to the class dynamically
"""
if not hasattr(self, u'_live_controller'):
self._live_controller = Registry().get(u'live_controller')
return self._live_controller
live_controller = property(_get_live_controller)
def _get_plugin_manager(self):
"""
Adds the plugin manager to the class dynamically
"""
if not hasattr(self, u'_plugin_manager'):
self._plugin_manager = Registry().get(u'plugin_manager')
return self._plugin_manager
plugin_manager = property(_get_plugin_manager)

View File

@ -116,6 +116,23 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.findVerseSplit = re.compile(u'---\[\]---\n', re.UNICODE)
self.whitespace = re.compile(r'\W+', re.UNICODE)
def keyPressEvent(self, event):
"""
Reimplement the keyPressEvent to react on Return/Enter keys. When some combo boxes have focus we do not want
dialog's default action be triggered but instead our own.
``event``
A QtGui.QKeyEvent event.
"""
if event.key() in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return):
if self.authorsComboBox.hasFocus() and self.authorsComboBox.currentText():
self.onAuthorAddButtonClicked()
return
if self.topicsComboBox.hasFocus() and self.topicsComboBox.currentText():
self.onTopicAddButtonClicked()
return
QtGui.QDialog.keyPressEvent(self, event)
def initialise(self):
"""
Set up the form for when it is displayed.
@ -274,7 +291,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
verse_tags_translated = True
if index is None:
index = VerseType.from_tag(verse_tag)
verse[0][u'type'] = VerseType.Tags[index]
verse[0][u'type'] = VerseType.tags[index]
if verse[0][u'label'] == u'':
verse[0][u'label'] = u'1'
verse_def = u'%s%s' % (verse[0][u'type'], verse[0][u'label'])
@ -286,7 +303,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
for count, verse in enumerate(verses):
self.verseListWidget.setRowCount(self.verseListWidget.rowCount() + 1)
item = QtGui.QTableWidgetItem(verse)
verse_def = u'%s%s' % (VerseType.Tags[VerseType.Verse], unicode(count + 1))
verse_def = u'%s%s' % (VerseType.tags[VerseType.Verse], unicode(count + 1))
item.setData(QtCore.Qt.UserRole, verse_def)
self.verseListWidget.setItem(count, 0, item)
if self.song.verse_order:
@ -298,7 +315,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
verse_index = VerseType.from_translated_tag(verse_def[0], None)
if verse_index is None:
verse_index = VerseType.from_tag(verse_def[0])
verse_tag = VerseType.TranslatedTags[verse_index].upper()
verse_tag = VerseType.translated_tags[verse_index].upper()
translated.append(u'%s%s' % (verse_tag, verse_def[1:]))
self.verseOrderEdit.setText(u' '.join(translated))
else:
@ -530,7 +547,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
verse_name = parts
verse_num = u'1'
verse_index = VerseType.from_loose_input(verse_name)
verse_tag = VerseType.Tags[verse_index]
verse_tag = VerseType.tags[verse_index]
# Later we need to handle v1a as well.
#regex = re.compile(r'(\d+\w.)')
regex = re.compile(r'\D*(\d+)\D*')
@ -582,7 +599,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
if len(item) == 1:
verse_index = VerseType.from_translated_tag(item, None)
if verse_index is not None:
order.append(VerseType.Tags[verse_index] + u'1')
order.append(VerseType.tags[verse_index] + u'1')
else:
# it matches no verses anyway
order.append(u'')
@ -592,7 +609,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
# it matches no verses anyway
order.append(u'')
else:
verse_tag = VerseType.Tags[verse_index]
verse_tag = VerseType.tags[verse_index]
verse_num = item[1:].lower()
order.append(verse_tag + verse_num)
return order
@ -814,7 +831,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
ordertext = self.verseOrderEdit.text()
order = []
for item in ordertext.split():
verse_tag = VerseType.Tags[VerseType.from_translated_tag(item[0])]
verse_tag = VerseType.tags[VerseType.from_translated_tag(item[0])]
verse_num = item[1:].lower()
order.append(u'%s%s' % (verse_tag, verse_num))
self.song.verse_order = u' '.join(order)

View File

@ -74,13 +74,13 @@ class Ui_EditVerseDialog(object):
def retranslateUi(self, editVerseDialog):
editVerseDialog.setWindowTitle(translate('SongsPlugin.EditVerseForm', 'Edit Verse'))
self.verseTypeLabel.setText(translate('SongsPlugin.EditVerseForm', '&Verse type:'))
self.verseTypeComboBox.setItemText(VerseType.Verse, VerseType.TranslatedNames[VerseType.Verse])
self.verseTypeComboBox.setItemText(VerseType.Chorus, VerseType.TranslatedNames[VerseType.Chorus])
self.verseTypeComboBox.setItemText(VerseType.Bridge, VerseType.TranslatedNames[VerseType.Bridge])
self.verseTypeComboBox.setItemText(VerseType.PreChorus, VerseType.TranslatedNames[VerseType.PreChorus])
self.verseTypeComboBox.setItemText(VerseType.Intro, VerseType.TranslatedNames[VerseType.Intro])
self.verseTypeComboBox.setItemText(VerseType.Ending, VerseType.TranslatedNames[VerseType.Ending])
self.verseTypeComboBox.setItemText(VerseType.Other, VerseType.TranslatedNames[VerseType.Other])
self.verseTypeComboBox.setItemText(VerseType.Verse, VerseType.translated_names[VerseType.Verse])
self.verseTypeComboBox.setItemText(VerseType.Chorus, VerseType.translated_names[VerseType.Chorus])
self.verseTypeComboBox.setItemText(VerseType.Bridge, VerseType.translated_names[VerseType.Bridge])
self.verseTypeComboBox.setItemText(VerseType.PreChorus, VerseType.translated_names[VerseType.PreChorus])
self.verseTypeComboBox.setItemText(VerseType.Intro, VerseType.translated_names[VerseType.Intro])
self.verseTypeComboBox.setItemText(VerseType.Ending, VerseType.translated_names[VerseType.Ending])
self.verseTypeComboBox.setItemText(VerseType.Other, VerseType.translated_names[VerseType.Other])
self.splitButton.setText(UiStrings().Split)
self.splitButton.setToolTip(UiStrings().SplitToolTip)
self.insertButton.setText(translate('SongsPlugin.EditVerseForm', '&Insert'))

View File

@ -82,8 +82,7 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog):
def onInsertButtonClicked(self):
verse_type_index = self.verseTypeComboBox.currentIndex()
self.insertVerse(VerseType.Tags[verse_type_index],
self.verseNumberBox.value())
self.insertVerse(VerseType.tags[verse_type_index], self.verseNumberBox.value())
def onVerseTypeComboBoxChanged(self):
self.updateSuggestedVerseNumber()
@ -93,12 +92,11 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog):
def updateSuggestedVerseNumber(self):
"""
Adjusts the verse number SpinBox in regard to the selected verse type
and the cursor's position.
Adjusts the verse number SpinBox in regard to the selected verse type and the cursor's position.
"""
position = self.verseTextEdit.textCursor().position()
text = self.verseTextEdit.toPlainText()
verse_name = VerseType.TranslatedNames[
verse_name = VerseType.translated_names[
self.verseTypeComboBox.currentIndex()]
if not text:
return
@ -120,8 +118,7 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog):
verse_num = 1
self.verseNumberBox.setValue(verse_num)
def setVerse(self, text, single=False,
tag=u'%s1' % VerseType.Tags[VerseType.Verse]):
def setVerse(self, text, single=False, tag=u'%s1' % VerseType.tags[VerseType.Verse]):
self.hasSingleVerse = single
if single:
verse_type_index = VerseType.from_tag(tag[0], None)
@ -132,7 +129,7 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog):
self.insertButton.setVisible(False)
else:
if not text:
text = u'---[%s:1]---\n' % VerseType.TranslatedNames[VerseType.Verse]
text = u'---[%s:1]---\n' % VerseType.translated_names[VerseType.Verse]
self.verseTypeComboBox.setCurrentIndex(0)
self.verseNumberBox.setValue(1)
self.insertButton.setVisible(True)
@ -141,12 +138,12 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog):
self.verseTextEdit.moveCursor(QtGui.QTextCursor.End)
def getVerse(self):
return self.verseTextEdit.toPlainText(), VerseType.Tags[self.verseTypeComboBox.currentIndex()], \
return self.verseTextEdit.toPlainText(), VerseType.tags[self.verseTypeComboBox.currentIndex()], \
unicode(self.verseNumberBox.value())
def getVerseAll(self):
text = self.verseTextEdit.toPlainText()
if not text.startswith(u'---['):
text = u'---[%s:1]---\n%s' % (VerseType.TranslatedNames[VerseType.Verse], text)
text = u'---[%s:1]---\n%s' % (VerseType.translated_names[VerseType.Verse], text)
return text

View File

@ -35,7 +35,7 @@ import os
from PyQt4 import QtCore, QtGui
from openlp.core.lib import Settings, UiStrings, translate
from openlp.core.lib import Registry, Settings, UiStrings, translate
from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui.wizard import OpenLPWizard, WizardStrings
from openlp.plugins.songs.lib.importer import SongFormat, SongFormatSelect
@ -489,6 +489,16 @@ class SongImportForm(OpenLPWizard):
self.formatWidgets[this_format][u'importWidget'] = importWidget
return importWidget
def _get_main_window(self):
"""
Adds the main window to the class dynamically
"""
if not hasattr(self, u'_main_window'):
self._main_window = Registry().get(u'main_window')
return self._main_window
main_window = property(_get_main_window)
class SongImportSourcePage(QtGui.QWizardPage):
"""

View File

@ -37,8 +37,7 @@ from ui import SongStrings
WHITESPACE = re.compile(r'[\W_]+', re.UNICODE)
APOSTROPHE = re.compile(u'[\'`ʻ]', re.UNICODE)
PATTERN = re.compile(r"\\([a-z]{1,32})(-?\d{1,10})?[ ]?|\\'"
r"([0-9a-f]{2})|\\([^a-z])|([{}])|[\r\n]+|(.)", re.I)
PATTERN = re.compile(r"\\([a-z]{1,32})(-?\d{1,10})?[ ]?|\\'([0-9a-f]{2})|\\([^a-z])|([{}])|[\r\n]+|(.)", re.I)
# RTF control words which specify a "destination" to be ignored.
DESTINATIONS = frozenset((
u'aftncn', u'aftnsep', u'aftnsepc', u'annotation', u'atnauthor',
@ -138,8 +137,7 @@ CHARSET_MAPPING = {
class VerseType(object):
"""
VerseType provides an enumeration for the tags that may be associated
with verses in songs.
VerseType provides an enumeration for the tags that may be associated with verses in songs.
"""
Verse = 0
Chorus = 1
@ -149,7 +147,7 @@ class VerseType(object):
Ending = 5
Other = 6
Names = [
names = [
u'Verse',
u'Chorus',
u'Bridge',
@ -157,9 +155,9 @@ class VerseType(object):
u'Intro',
u'Ending',
u'Other']
Tags = [name[0].lower() for name in Names]
tags = [name[0].lower() for name in names]
TranslatedNames = [
translated_names = [
translate('SongsPlugin.VerseType', 'Verse'),
translate('SongsPlugin.VerseType', 'Chorus'),
translate('SongsPlugin.VerseType', 'Bridge'),
@ -167,13 +165,12 @@ class VerseType(object):
translate('SongsPlugin.VerseType', 'Intro'),
translate('SongsPlugin.VerseType', 'Ending'),
translate('SongsPlugin.VerseType', 'Other')]
TranslatedTags = [name[0].lower() for name in TranslatedNames]
translated_tags = [name[0].lower() for name in translated_names]
@staticmethod
def translated_tag(verse_tag, default=Other):
"""
Return the translated UPPERCASE tag for a given tag,
used to show translated verse tags in UI
Return the translated UPPERCASE tag for a given tag, used to show translated verse tags in UI
``verse_tag``
The string to return a VerseType for
@ -182,11 +179,11 @@ class VerseType(object):
Default return value if no matching tag is found
"""
verse_tag = verse_tag[0].lower()
for num, tag in enumerate(VerseType.Tags):
for num, tag in enumerate(VerseType.tags):
if verse_tag == tag:
return VerseType.TranslatedTags[num].upper()
if default in VerseType.TranslatedTags:
return VerseType.TranslatedTags[default].upper()
return VerseType.translated_tags[num].upper()
if default in VerseType.translated_tags:
return VerseType.translated_tags[default].upper()
@staticmethod
def translated_name(verse_tag, default=Other):
@ -200,11 +197,11 @@ class VerseType(object):
Default return value if no matching tag is found
"""
verse_tag = verse_tag[0].lower()
for num, tag in enumerate(VerseType.Tags):
for num, tag in enumerate(VerseType.tags):
if verse_tag == tag:
return VerseType.TranslatedNames[num]
if default in VerseType.TranslatedNames:
return VerseType.TranslatedNames[default]
return VerseType.translated_names[num]
if default in VerseType.translated_names:
return VerseType.translated_names[default]
@staticmethod
def from_tag(verse_tag, default=Other):
@ -218,7 +215,7 @@ class VerseType(object):
Default return value if no matching tag is found
"""
verse_tag = verse_tag[0].lower()
for num, tag in enumerate(VerseType.Tags):
for num, tag in enumerate(VerseType.tags):
if verse_tag == tag:
return num
return default
@ -235,7 +232,7 @@ class VerseType(object):
Default return value if no matching tag is found
"""
verse_tag = verse_tag[0].lower()
for num, tag in enumerate(VerseType.TranslatedTags):
for num, tag in enumerate(VerseType.translated_tags):
if verse_tag == tag:
return num
return default
@ -252,7 +249,7 @@ class VerseType(object):
Default return value if no matching tag is found
"""
verse_name = verse_name.lower()
for num, name in enumerate(VerseType.Names):
for num, name in enumerate(VerseType.names):
if verse_name == name.lower():
return num
return default
@ -266,7 +263,7 @@ class VerseType(object):
The string to return a VerseType for
"""
verse_name = verse_name.lower()
for num, translation in enumerate(VerseType.TranslatedNames):
for num, translation in enumerate(VerseType.translated_names):
if verse_name == translation.lower():
return num
@ -296,13 +293,11 @@ class VerseType(object):
def retrieve_windows_encoding(recommendation=None):
"""
Determines which encoding to use on an information source. The process uses
both automated detection, which is passed to this method as a
recommendation, and user confirmation to return an encoding.
Determines which encoding to use on an information source. The process uses both automated detection, which is
passed to this method as a recommendation, and user confirmation to return an encoding.
``recommendation``
A recommended encoding discovered programmatically for the user to
confirm.
A recommended encoding discovered programmatically for the user to confirm.
"""
# map chardet result to compatible windows standard code page
codepage_mapping = {'IBM866': u'cp866', 'TIS-620': u'cp874',
@ -355,24 +350,22 @@ def retrieve_windows_encoding(recommendation=None):
def clean_string(string):
"""
Strips punctuation from the passed string to assist searching
Strips punctuation from the passed string to assist searching.
"""
return WHITESPACE.sub(u' ', APOSTROPHE.sub(u'', string)).lower()
def clean_title(title):
"""
Cleans the song title by removing Unicode control chars groups C0 & C1,
as well as any trailing spaces
Cleans the song title by removing Unicode control chars groups C0 & C1, as well as any trailing spaces.
"""
return CONTROL_CHARS.sub(u'', title).rstrip()
def clean_song(manager, song):
"""
Cleans the search title, rebuilds the search lyrics, adds a default author
if the song does not have one and other clean ups. This should always
called when a new song is added or changed.
Cleans the search title, rebuilds the search lyrics, adds a default author if the song does not have one and other
clean ups. This should always called when a new song is added or changed.
``manager``
The song's manager.
@ -397,21 +390,20 @@ def clean_song(manager, song):
song.search_title = clean_string(song.title) + u'@' + clean_string(song.alternate_title)
# Only do this, if we the song is a 1.9.4 song (or older).
if song.lyrics.find(u'<lyrics language="en">') != -1:
# Remove the old "language" attribute from lyrics tag (prior to 1.9.5).
# This is not very important, but this keeps the database clean. This
# can be removed when everybody has cleaned his songs.
# Remove the old "language" attribute from lyrics tag (prior to 1.9.5). This is not very important, but this
# keeps the database clean. This can be removed when everybody has cleaned his songs.
song.lyrics = song.lyrics.replace(u'<lyrics language="en">', u'<lyrics>')
verses = SongXML().get_verses(song.lyrics)
song.search_lyrics = u' '.join([clean_string(verse[1])
for verse in verses])
# We need a new and clean SongXML instance.
sxml = SongXML()
# Rebuild the song's verses, to remove any wrong verse names (for
# example translated ones), which might have been added prior to 1.9.5.
# Rebuild the song's verses, to remove any wrong verse names (for example translated ones), which might have
# been added prior to 1.9.5.
# List for later comparison.
compare_order = []
for verse in verses:
verse_type = VerseType.Tags[VerseType.from_loose_input(verse[0][u'type'])]
verse_type = VerseType.tags[VerseType.from_loose_input(verse[0][u'type'])]
sxml.add_verse_to_lyrics(
verse_type,
verse[0][u'label'],
@ -422,15 +414,14 @@ def clean_song(manager, song):
if verse[0][u'label'] == u'1':
compare_order.append(verse_type.upper())
song.lyrics = unicode(sxml.extract_xml(), u'utf-8')
# Rebuild the verse order, to convert translated verse tags, which might
# have been added prior to 1.9.5.
# Rebuild the verse order, to convert translated verse tags, which might have been added prior to 1.9.5.
if song.verse_order:
order = CONTROL_CHARS.sub(u'', song.verse_order).strip().split()
else:
order = []
new_order = []
for verse_def in order:
verse_type = VerseType.Tags[
verse_type = VerseType.tags[
VerseType.from_loose_input(verse_def[0])]
if len(verse_def) > 1:
new_order.append((u'%s%s' % (verse_type, verse_def[1:])).upper())
@ -463,16 +454,16 @@ def get_encoding(font, font_table, default_encoding, failed=False):
Finds an encoding to use. Asks user, if necessary.
``font``
The number of currently active font.
The number of currently active font.
``font_table``
Dictionary of fonts and respective encodings.
Dictionary of fonts and respective encodings.
``default_encoding``
The default encoding to use when font_table is empty or no font is used.
The default encoding to use when font_table is empty or no font is used.
``failed``
A boolean indicating whether the previous encoding didn't work.
A boolean indicating whether the previous encoding didn't work.
"""
encoding = None
if font in font_table:
@ -494,10 +485,10 @@ def strip_rtf(text, default_encoding=None):
http://stackoverflow.com/questions/188545
``text``
RTF-encoded text, a string.
RTF-encoded text, a string.
``default_encoding``
Default encoding to use when no encoding is specified.
Default encoding to use when no encoding is specified.
"""
# Current font is the font tag we last met.
font = u''
@ -589,8 +580,7 @@ def strip_rtf(text, default_encoding=None):
def natcmp(a, b):
"""
Natural string comparison which mimics the behaviour of Python's internal
cmp function.
Natural string comparison which mimics the behaviour of Python's internal cmp function.
"""
if len(a) <= len(b):
for i, key in enumerate(a):

View File

@ -188,13 +188,13 @@ class CCLIFileImport(SongImport):
words_list = song_words.split(u'/t')
for counter in range(len(field_list)):
if field_list[counter].startswith(u'Ver'):
verse_type = VerseType.Tags[VerseType.Verse]
verse_type = VerseType.tags[VerseType.Verse]
elif field_list[counter].startswith(u'Ch'):
verse_type = VerseType.Tags[VerseType.Chorus]
verse_type = VerseType.tags[VerseType.Chorus]
elif field_list[counter].startswith(u'Br'):
verse_type = VerseType.Tags[VerseType.Bridge]
verse_type = VerseType.tags[VerseType.Bridge]
else:
verse_type = VerseType.Tags[VerseType.Other]
verse_type = VerseType.tags[VerseType.Other]
check_first_verse_line = True
verse_text = unicode(words_list[counter])
verse_text = verse_text.replace(u'/n', u'\n')
@ -202,15 +202,15 @@ class CCLIFileImport(SongImport):
verse_lines = verse_text.split(u'\n', 1)
if check_first_verse_line:
if verse_lines[0].startswith(u'(PRE-CHORUS'):
verse_type = VerseType.Tags[VerseType.PreChorus]
verse_type = VerseType.tags[VerseType.PreChorus]
log.debug(u'USR verse PRE-CHORUS: %s', verse_lines[0])
verse_text = verse_lines[1]
elif verse_lines[0].startswith(u'(BRIDGE'):
verse_type = VerseType.Tags[VerseType.Bridge]
verse_type = VerseType.tags[VerseType.Bridge]
log.debug(u'USR verse BRIDGE')
verse_text = verse_lines[1]
elif verse_lines[0].startswith(u'('):
verse_type = VerseType.Tags[VerseType.Other]
verse_type = VerseType.tags[VerseType.Other]
verse_text = verse_lines[1]
if verse_text:
self.addVerse(verse_text, verse_type)
@ -292,31 +292,31 @@ class CCLIFileImport(SongImport):
verse_desc_parts = clean_line.split(u' ')
if len(verse_desc_parts) == 2:
if verse_desc_parts[0].startswith(u'Ver'):
verse_type = VerseType.Tags[VerseType.Verse]
verse_type = VerseType.tags[VerseType.Verse]
elif verse_desc_parts[0].startswith(u'Ch'):
verse_type = VerseType.Tags[VerseType.Chorus]
verse_type = VerseType.tags[VerseType.Chorus]
elif verse_desc_parts[0].startswith(u'Br'):
verse_type = VerseType.Tags[VerseType.Bridge]
verse_type = VerseType.tags[VerseType.Bridge]
else:
# we need to analyse the next line for
# verse type, so set flag
verse_type = VerseType.Tags[VerseType.Other]
verse_type = VerseType.tags[VerseType.Other]
check_first_verse_line = True
verse_number = verse_desc_parts[1]
else:
verse_type = VerseType.Tags[VerseType.Other]
verse_type = VerseType.tags[VerseType.Other]
verse_number = 1
verse_start = True
else:
# check first line for verse type
if check_first_verse_line:
if line.startswith(u'(PRE-CHORUS'):
verse_type = VerseType.Tags[VerseType.PreChorus]
verse_type = VerseType.tags[VerseType.PreChorus]
elif line.startswith(u'(BRIDGE'):
verse_type = VerseType.Tags[VerseType.Bridge]
verse_type = VerseType.tags[VerseType.Bridge]
# Handle all other misc types
elif line.startswith(u'('):
verse_type = VerseType.Tags[VerseType.Other]
verse_type = VerseType.tags[VerseType.Other]
else:
verse_text = verse_text + line
check_first_verse_line = False

View File

@ -175,12 +175,12 @@ class EasySlidesImport(SongImport):
# if the regions are inside verses
regionsInVerses = (regions and regionlines[regionlines.keys()[0]] > 1)
MarkTypes = {
u'CHORUS': VerseType.Tags[VerseType.Chorus],
u'VERSE': VerseType.Tags[VerseType.Verse],
u'INTRO': VerseType.Tags[VerseType.Intro],
u'ENDING': VerseType.Tags[VerseType.Ending],
u'BRIDGE': VerseType.Tags[VerseType.Bridge],
u'PRECHORUS': VerseType.Tags[VerseType.PreChorus]
u'CHORUS': VerseType.tags[VerseType.Chorus],
u'VERSE': VerseType.tags[VerseType.Verse],
u'INTRO': VerseType.tags[VerseType.Intro],
u'ENDING': VerseType.tags[VerseType.Ending],
u'BRIDGE': VerseType.tags[VerseType.Bridge],
u'PRECHORUS': VerseType.tags[VerseType.PreChorus]
}
verses = {}
# list as [region, versetype, versenum, instance]

View File

@ -178,7 +178,7 @@ class EasyWorshipSongImport(SongImport):
if result is None:
return
words, self.encoding = result
verse_type = VerseType.Tags[VerseType.Verse]
verse_type = VerseType.tags[VerseType.Verse]
for verse in SLIDE_BREAK_REGEX.split(words):
verse = verse.strip()
if not verse:
@ -187,17 +187,17 @@ class EasyWorshipSongImport(SongImport):
first_line_is_tag = False
# EW tags: verse, chorus, pre-chorus, bridge, tag,
# intro, ending, slide
for type in VerseType.Names+[u'tag', u'slide']:
type = type.lower()
for tag in VerseType.tags + [u'tag', u'slide']:
tag = tag.lower()
ew_tag = verse_split[0].strip().lower()
if ew_tag.startswith(type):
verse_type = type[0]
if type == u'tag' or type == u'slide':
verse_type = VerseType.Tags[VerseType.Other]
if ew_tag.startswith(tag):
verse_type = tag[0]
if tag == u'tag' or tag == u'slide':
verse_type = VerseType.tags[VerseType.Other]
first_line_is_tag = True
number_found = False
# check if tag is followed by number and/or note
if len(ew_tag) > len(type):
if len(ew_tag) > len(tag):
match = NUMBER_REGEX.search(ew_tag)
if match:
number = match.group()
@ -209,10 +209,7 @@ class EasyWorshipSongImport(SongImport):
if not number_found:
verse_type += u'1'
break
self.addVerse(
verse_split[-1].strip() \
if first_line_is_tag else verse,
verse_type)
self.addVerse(verse_split[-1].strip() if first_line_is_tag else verse, verse_type)
if len(self.comments) > 5:
self.comments += unicode(translate('SongsPlugin.EasyWorshipSongImport',
'\n[above are Song Tags with notes imported from EasyWorship]'))
@ -224,8 +221,7 @@ class EasyWorshipSongImport(SongImport):
self.memoFile.close()
def findField(self, field_name):
return [i for i, x in enumerate(self.fieldDescs)
if x.name == field_name][0]
return [i for i, x in enumerate(self.fieldDescs) if x.name == field_name][0]
def setRecordStruct(self, field_descs):
# Begin with empty field struct list

View File

@ -412,13 +412,13 @@ class FoilPresenter(object):
temp_sortnr_backup = 1
temp_sortnr_liste = []
verse_count = {
VerseType.Tags[VerseType.Verse]: 1,
VerseType.Tags[VerseType.Chorus]: 1,
VerseType.Tags[VerseType.Bridge]: 1,
VerseType.Tags[VerseType.Ending]: 1,
VerseType.Tags[VerseType.Other]: 1,
VerseType.Tags[VerseType.Intro]: 1,
VerseType.Tags[VerseType.PreChorus]: 1
VerseType.tags[VerseType.Verse]: 1,
VerseType.tags[VerseType.Chorus]: 1,
VerseType.tags[VerseType.Bridge]: 1,
VerseType.tags[VerseType.Ending]: 1,
VerseType.tags[VerseType.Other]: 1,
VerseType.tags[VerseType.Intro]: 1,
VerseType.tags[VerseType.PreChorus]: 1
}
for strophe in foilpresenterfolie.strophen.strophe:
text = self._child(strophe.text_) if hasattr(strophe, u'text_') else u''
@ -438,25 +438,25 @@ class FoilPresenter(object):
temp_verse_name = re.compile(u'[0-9].*').sub(u'', verse_name)
temp_verse_name = temp_verse_name[:3].lower()
if temp_verse_name == u'ref':
verse_type = VerseType.Tags[VerseType.Chorus]
verse_type = VerseType.tags[VerseType.Chorus]
elif temp_verse_name == u'r':
verse_type = VerseType.Tags[VerseType.Chorus]
verse_type = VerseType.tags[VerseType.Chorus]
elif temp_verse_name == u'':
verse_type = VerseType.Tags[VerseType.Verse]
verse_type = VerseType.tags[VerseType.Verse]
elif temp_verse_name == u'v':
verse_type = VerseType.Tags[VerseType.Verse]
verse_type = VerseType.tags[VerseType.Verse]
elif temp_verse_name == u'bri':
verse_type = VerseType.Tags[VerseType.Bridge]
verse_type = VerseType.tags[VerseType.Bridge]
elif temp_verse_name == u'cod':
verse_type = VerseType.Tags[VerseType.Ending]
verse_type = VerseType.tags[VerseType.Ending]
elif temp_verse_name == u'sch':
verse_type = VerseType.Tags[VerseType.Ending]
verse_type = VerseType.tags[VerseType.Ending]
elif temp_verse_name == u'pre':
verse_type = VerseType.Tags[VerseType.PreChorus]
verse_type = VerseType.tags[VerseType.PreChorus]
elif temp_verse_name == u'int':
verse_type = VerseType.Tags[VerseType.Intro]
verse_type = VerseType.tags[VerseType.Intro]
else:
verse_type = VerseType.Tags[VerseType.Other]
verse_type = VerseType.tags[VerseType.Other]
verse_number = re.compile(u'[a-zA-Z.+-_ ]*').sub(u'', verse_name)
# Foilpresenter allows e. g. "C", but we need "C1".
if not verse_number:
@ -469,7 +469,7 @@ class FoilPresenter(object):
if value == u''.join((verse_type, verse_number)):
verse_number = unicode(int(verse_number) + 1)
verse_type_index = VerseType.from_tag(verse_type[0])
verse_type = VerseType.Names[verse_type_index]
verse_type = VerseType.tags[verse_type_index]
temp_verse_order[verse_sortnr] = u''.join((verse_type[0],
verse_number))
temp_verse_order_backup.append(u''.join((verse_type[0],

View File

@ -430,7 +430,7 @@ class SongMediaItem(MediaManagerItem):
verse_index = VerseType.from_string(verse_tag, None)
if verse_index is None:
verse_index = VerseType.from_tag(verse_tag)
verse_tag = VerseType.TranslatedTags[verse_index].upper()
verse_tag = VerseType.translated_tags[verse_index].upper()
verse_def = u'%s%s' % (verse_tag, verse[0][u'label'])
service_item.add_from_text(unicode(verse[1]), verse_def)
else:
@ -445,7 +445,7 @@ class SongMediaItem(MediaManagerItem):
verse_index = VerseType.from_translated_tag(verse[0][u'type'])
else:
verse_index = VerseType.from_tag(verse[0][u'type'])
verse_tag = VerseType.TranslatedTags[verse_index]
verse_tag = VerseType.translated_tags[verse_index]
verse_def = u'%s%s' % (verse_tag, verse[0][u'label'])
service_item.add_from_text(verse[1], verse_def)
else:

View File

@ -160,7 +160,7 @@ class OpenSongImport(SongImport):
# keep track of verses appearance order
our_verse_order = []
# default verse
verse_tag = VerseType.Tags[VerseType.Verse]
verse_tag = VerseType.tags[VerseType.Verse]
verse_num = u'1'
# for the case where song has several sections with same marker
inst = 1
@ -184,21 +184,18 @@ class OpenSongImport(SongImport):
# drop the square brackets
right_bracket = this_line.find(u']')
content = this_line[1:right_bracket].lower()
# have we got any digits?
# If so, verse number is everything from the digits
# to the end (openlp does not have concept of part verses, so
# just ignore any non integers on the end (including floats))
# have we got any digits? If so, verse number is everything from the digits to the end (openlp does not
# have concept of part verses, so just ignore any non integers on the end (including floats))
match = re.match(u'(\D*)(\d+)', content)
if match is not None:
verse_tag = match.group(1)
verse_num = match.group(2)
else:
# otherwise we assume number 1 and take the whole prefix as
# the verse tag
# otherwise we assume number 1 and take the whole prefix as the verse tag
verse_tag = content
verse_num = u'1'
verse_index = VerseType.from_loose_input(verse_tag) if verse_tag else 0
verse_tag = VerseType.Tags[verse_index]
verse_tag = VerseType.tags[verse_index]
inst = 1
if [verse_tag, verse_num, inst] in our_verse_order and verse_num in verses.get(verse_tag, {}):
inst = len(verses[verse_tag][verse_num]) + 1
@ -236,8 +233,8 @@ class OpenSongImport(SongImport):
# figure out the presentation order, if present
if u'presentation' in fields and root.presentation:
order = unicode(root.presentation)
# We make all the tags in the lyrics lower case, so match that here
# and then split into a list on the whitespace
# We make all the tags in the lyrics lower case, so match that here and then split into a list on the
# whitespace.
order = order.lower().split()
for verse_def in order:
match = re.match(u'(\D*)(\d+.*)', verse_def)
@ -245,7 +242,7 @@ class OpenSongImport(SongImport):
verse_tag = match.group(1)
verse_num = match.group(2)
if not verse_tag:
verse_tag = VerseType.Tags[VerseType.Verse]
verse_tag = VerseType.tags[VerseType.Verse]
else:
# Assume it's no.1 if there are no digits
verse_tag = verse_def

View File

@ -27,8 +27,7 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
The :mod:`songbeamerimport` module provides the functionality for importing
SongBeamer songs into the OpenLP database.
The :mod:`songbeamerimport` module provides the functionality for importing SongBeamer songs into the OpenLP database.
"""
import chardet
import codecs
@ -43,32 +42,31 @@ log = logging.getLogger(__name__)
class SongBeamerTypes(object):
MarkTypes = {
u'Refrain': VerseType.Tags[VerseType.Chorus],
u'Chorus': VerseType.Tags[VerseType.Chorus],
u'Vers': VerseType.Tags[VerseType.Verse],
u'Verse': VerseType.Tags[VerseType.Verse],
u'Strophe': VerseType.Tags[VerseType.Verse],
u'Intro': VerseType.Tags[VerseType.Intro],
u'Coda': VerseType.Tags[VerseType.Ending],
u'Ending': VerseType.Tags[VerseType.Ending],
u'Bridge': VerseType.Tags[VerseType.Bridge],
u'Interlude': VerseType.Tags[VerseType.Bridge],
u'Zwischenspiel': VerseType.Tags[VerseType.Bridge],
u'Pre-Chorus': VerseType.Tags[VerseType.PreChorus],
u'Pre-Refrain': VerseType.Tags[VerseType.PreChorus],
u'Pre-Bridge': VerseType.Tags[VerseType.Other],
u'Pre-Coda': VerseType.Tags[VerseType.Other],
u'Unbekannt': VerseType.Tags[VerseType.Other],
u'Unknown': VerseType.Tags[VerseType.Other],
u'Unbenannt': VerseType.Tags[VerseType.Other]
u'Refrain': VerseType.tags[VerseType.Chorus],
u'Chorus': VerseType.tags[VerseType.Chorus],
u'Vers': VerseType.tags[VerseType.Verse],
u'Verse': VerseType.tags[VerseType.Verse],
u'Strophe': VerseType.tags[VerseType.Verse],
u'Intro': VerseType.tags[VerseType.Intro],
u'Coda': VerseType.tags[VerseType.Ending],
u'Ending': VerseType.tags[VerseType.Ending],
u'Bridge': VerseType.tags[VerseType.Bridge],
u'Interlude': VerseType.tags[VerseType.Bridge],
u'Zwischenspiel': VerseType.tags[VerseType.Bridge],
u'Pre-Chorus': VerseType.tags[VerseType.PreChorus],
u'Pre-Refrain': VerseType.tags[VerseType.PreChorus],
u'Pre-Bridge': VerseType.tags[VerseType.Other],
u'Pre-Coda': VerseType.tags[VerseType.Other],
u'Unbekannt': VerseType.tags[VerseType.Other],
u'Unknown': VerseType.tags[VerseType.Other],
u'Unbenannt': VerseType.tags[VerseType.Other]
}
class SongBeamerImport(SongImport):
"""
Import Song Beamer files(s)
Song Beamer file format is text based
in the beginning are one or more control tags written
Import Song Beamer files(s). Song Beamer file format is text based in the beginning are one or more control tags
written.
"""
HTML_TAG_PAIRS = [
(re.compile(u'<b>'), u'{st}'),
@ -113,7 +111,7 @@ class SongBeamerImport(SongImport):
return
self.setDefaults()
self.currentVerse = u''
self.currentVerseType = VerseType.Tags[VerseType.Verse]
self.currentVerseType = VerseType.tags[VerseType.Verse]
read_verses = False
file_name = os.path.split(file)[1]
if os.path.isfile(file):
@ -137,7 +135,7 @@ class SongBeamerImport(SongImport):
self.replaceHtmlTags()
self.addVerse(self.currentVerse, self.currentVerseType)
self.currentVerse = u''
self.currentVerseType = VerseType.Tags[VerseType.Verse]
self.currentVerseType = VerseType.tags[VerseType.Verse]
read_verses = True
verse_start = True
elif read_verses:
@ -155,8 +153,7 @@ class SongBeamerImport(SongImport):
def replaceHtmlTags(self):
"""
This can be called to replace SongBeamer's specific (html) tags with
OpenLP's specific (html) tags.
This can be called to replace SongBeamer's specific (html) tags with OpenLP's specific (html) tags.
"""
for pair in SongBeamerImport.HTML_TAG_PAIRS:
self.currentVerse = pair[0].sub(pair[1], self.currentVerse)
@ -166,8 +163,7 @@ class SongBeamerImport(SongImport):
Parses a meta data line.
``line``
The line in the file. It should consist of a tag and a value
for this tag (unicode)::
The line in the file. It should consist of a tag and a value for this tag (unicode)::
u'#Title=Nearer my God to Thee'
"""
@ -272,8 +268,8 @@ class SongBeamerImport(SongImport):
def checkVerseMarks(self, line):
"""
Check and add the verse's MarkType. Returns ``True`` if the given line
contains a correct verse mark otherwise ``False``.
Check and add the verse's MarkType. Returns ``True`` if the given linE contains a correct verse mark otherwise
``False``.
``line``
The line to check for marks (unicode).

View File

@ -303,13 +303,13 @@ class SongImport(QtCore.QObject):
sxml = SongXML()
other_count = 1
for (verse_def, verse_text, lang) in self.verses:
if verse_def[0].lower() in VerseType.Tags:
if verse_def[0].lower() in VerseType.tags:
verse_tag = verse_def[0].lower()
else:
new_verse_def = u'%s%d' % (VerseType.Tags[VerseType.Other], other_count)
new_verse_def = u'%s%d' % (VerseType.tags[VerseType.Other], other_count)
verses_changed_to_other[verse_def] = new_verse_def
other_count += 1
verse_tag = VerseType.Tags[VerseType.Other]
verse_tag = VerseType.tags[VerseType.Other]
log.info(u'Versetype %s changing to %s', verse_def, new_verse_def)
verse_def = new_verse_def
sxml.add_verse_to_lyrics(verse_tag, verse_def[1:], verse_text, lang)

View File

@ -152,11 +152,11 @@ class SongShowPlusImport(SongImport):
elif block_key == CCLI_NO:
self.ccliNumber = int(data)
elif block_key == VERSE:
self.addVerse(unicode(data, u'cp1252'), "%s%s" % (VerseType.Tags[VerseType.Verse], verse_no))
self.addVerse(unicode(data, u'cp1252'), "%s%s" % (VerseType.tags[VerseType.Verse], verse_no))
elif block_key == CHORUS:
self.addVerse(unicode(data, u'cp1252'), "%s%s" % (VerseType.Tags[VerseType.Chorus], verse_no))
self.addVerse(unicode(data, u'cp1252'), "%s%s" % (VerseType.tags[VerseType.Chorus], verse_no))
elif block_key == BRIDGE:
self.addVerse(unicode(data, u'cp1252'), "%s%s" % (VerseType.Tags[VerseType.Bridge], verse_no))
self.addVerse(unicode(data, u'cp1252'), "%s%s" % (VerseType.tags[VerseType.Bridge], verse_no))
elif block_key == TOPIC:
self.topics.append(unicode(data, u'cp1252'))
elif block_key == COMMENTS:
@ -192,19 +192,19 @@ class SongShowPlusImport(SongImport):
verse_number = "1"
verse_type = verse_type.lower()
if verse_type == "verse":
verse_tag = VerseType.Tags[VerseType.Verse]
verse_tag = VerseType.tags[VerseType.Verse]
elif verse_type == "chorus":
verse_tag = VerseType.Tags[VerseType.Chorus]
verse_tag = VerseType.tags[VerseType.Chorus]
elif verse_type == "bridge":
verse_tag = VerseType.Tags[VerseType.Bridge]
verse_tag = VerseType.tags[VerseType.Bridge]
elif verse_type == "pre-chorus":
verse_tag = VerseType.Tags[VerseType.PreChorus]
verse_tag = VerseType.tags[VerseType.PreChorus]
else:
if verse_name not in self.otherList:
if ignore_unique:
return None
self.otherCount += 1
self.otherList[verse_name] = str(self.otherCount)
verse_tag = VerseType.Tags[VerseType.Other]
verse_tag = VerseType.tags[VerseType.Other]
verse_number = self.otherList[verse_name]
return verse_tag + verse_number

View File

@ -52,8 +52,7 @@ class SundayPlusImport(SongImport):
"""
Import Sunday Plus songs
The format examples can be found attached to bug report at
<http://support.openlp.org/issues/395>
The format examples can be found attached to bug report at <http://support.openlp.org/issues/395>
"""
def __init__(self, manager, **kwargs):
@ -90,7 +89,7 @@ class SundayPlusImport(SongImport):
self.logError(u'File is malformed')
return False
i = 1
verse_type = VerseType.Tags[VerseType.Verse]
verse_type = VerseType.tags[VerseType.Verse]
while i < len(data):
# Data is held as #name: value pairs inside groups marked as [].
# Now we are looking for the name.
@ -137,8 +136,7 @@ class SundayPlusImport(SongImport):
if name == 'MARKER_NAME':
value = value.strip()
if len(value):
verse_type = VerseType.Tags[
VerseType.from_loose_input(value[0])]
verse_type = VerseType.tags[VerseType.from_loose_input(value[0])]
if len(value) >= 2 and value[-1] in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']:
verse_type = "%s%s" % (verse_type, value[-1])
elif name == 'Hotkey':
@ -168,8 +166,7 @@ class SundayPlusImport(SongImport):
self.copyright = u'Public Domain'
continue
processed_lines.append(line)
self.addVerse('\n'.join(processed_lines).strip(),
verse_type)
self.addVerse('\n'.join(processed_lines).strip(), verse_type)
if end == -1:
break
i = end + 1

View File

@ -97,10 +97,8 @@ class SongXML(object):
Add a verse to the ``<lyrics>`` tag.
``type``
A string denoting the type of verse. Possible values are *v*,
*c*, *b*, *p*, *i*, *e* and *o*.
Any other type is **not** allowed, this also includes translated
types.
A string denoting the type of verse. Possible values are *v*, *c*, *b*, *p*, *i*, *e* and *o*. Any other
type is **not** allowed, this also includes translated types.
``number``
An integer denoting the number of the item, for example: verse 1.
@ -109,8 +107,7 @@ class SongXML(object):
The actual text of the verse to be stored.
``lang``
The verse's language code (ISO-639). This is not required, but
should be added if available.
The verse's language code (ISO-639). This is not required, but should be added if available.
"""
verse = etree.Element(u'verse', type=unicode(type),
label=unicode(number))
@ -128,24 +125,21 @@ class SongXML(object):
def get_verses(self, xml):
"""
Iterates through the verses in the XML and returns a list of verses
and their attributes.
Iterates through the verses in the XML and returns a list of verses and their attributes.
``xml``
The XML of the song to be parsed.
The returned list has the following format::
[[{'type': 'v', 'label': '1'},
u"optional slide split 1[---]optional slide split 2"],
[[{'type': 'v', 'label': '1'}, u"optional slide split 1[---]optional slide split 2"],
[{'lang': 'en', 'type': 'c', 'label': '1'}, u"English chorus"]]
"""
self.song_xml = None
verse_list = []
if not xml.startswith(u'<?xml') and not xml.startswith(u'<song'):
# This is an old style song, without XML. Let's handle it correctly
# by iterating through the verses, and then recreating the internal
# xml object as well.
# This is an old style song, without XML. Let's handle it correctly by iterating through the verses, and
# then recreating the internal xml object as well.
self.song_xml = objectify.fromstring(u'<song version="1.0" />')
self.lyrics = etree.SubElement(self.song_xml, u'lyrics')
verses = xml.split(u'\n\n')
@ -176,11 +170,10 @@ class SongXML(object):
class OpenLyrics(object):
"""
This class represents the converter for OpenLyrics XML (version 0.8)
to/from a song.
This class represents the converter for OpenLyrics XML (version 0.8) to/from a song.
As OpenLyrics has a rich set of different features, we cannot support them
all. The following features are supported by the :class:`OpenLyrics` class:
As OpenLyrics has a rich set of different features, we cannot support them all. The following features are
supported by the :class:`OpenLyrics` class:
``<authors>``
OpenLP does not support the attribute *type* and *lang*.
@ -189,8 +182,7 @@ class OpenLyrics(object):
This property is not supported.
``<comments>``
The ``<comments>`` property is fully supported. But comments in lyrics
are not supported.
The ``<comments>`` property is fully supported. But comments in lyrics are not supported.
``<copyright>``
This property is fully supported.
@ -208,23 +200,20 @@ class OpenLyrics(object):
This property is not supported.
``<lines>``
The attribute *part* is not supported. The *break* attribute is
supported.
The attribute *part* is not supported. The *break* attribute is supported.
``<publisher>``
This property is not supported.
``<songbooks>``
As OpenLP does only support one songbook, we cannot consider more than
one songbook.
As OpenLP does only support one songbook, we cannot consider more than one songbook.
``<tempo>``
This property is not supported.
``<themes>``
Topics, as they are called in OpenLP, are fully supported, whereby only
the topic text (e. g. Grace) is considered, but neither the *id* nor
*lang*.
Topics, as they are called in OpenLP, are fully supported, whereby only the topic text (e. g. Grace) is
considered, but neither the *id* nor *lang*.
``<transposition>``
This property is not supported.
@ -233,9 +222,8 @@ class OpenLyrics(object):
This property is not supported.
``<verse name="v1a" lang="he" translit="en">``
The attribute *translit* is not supported. Note, the attribute *lang* is
considered, but there is not further functionality implemented yet. The
following verse "types" are supported by OpenLP:
The attribute *translit* is not supported. Note, the attribute *lang* is considered, but there is not further
functionality implemented yet. The following verse "types" are supported by OpenLP:
* v
* c
@ -245,13 +233,10 @@ class OpenLyrics(object):
* e
* o
The verse "types" stand for *Verse*, *Chorus*, *Bridge*, *Pre-Chorus*,
*Intro*, *Ending* and *Other*. Any numeric value is allowed after the
verse type. The complete verse name in OpenLP always consists of the
verse type and the verse number. If not number is present *1* is
assumed.
OpenLP will merge verses which are split up by appending a letter to the
verse name, such as *v1a*.
The verse "types" stand for *Verse*, *Chorus*, *Bridge*, *Pre-Chorus*, *Intro*, *Ending* and *Other*. Any
numeric value is allowed after the verse type. The complete verse name in OpenLP always consists of the verse
type and the verse number. If not number is present *1* is assumed. OpenLP will merge verses which are split
up by appending a letter to the verse name, such as *v1a*.
``<verseOrder>``
OpenLP supports this property.
@ -359,17 +344,14 @@ class OpenLyrics(object):
def _get_missing_tags(self, text):
"""
Tests the given text for not closed formatting tags and returns a tuple
consisting of two unicode strings::
Tests the given text for not closed formatting tags and returns a tuple consisting of two unicode strings::
(u'{st}{r}', u'{/r}{/st}')
The first unicode string are the start tags (for the next slide). The
second unicode string are the end tags.
The first unicode string are the start tags (for the next slide). The second unicode string are the end tags.
``text``
The text to test. The text must **not** contain html tags, only
OpenLP formatting tags are allowed::
The text to test. The text must **not** contain html tags, only OpenLP formatting tags are allowed::
{st}{r}Text text text
"""
@ -379,9 +361,8 @@ class OpenLyrics(object):
continue
if text.count(tag[u'start tag']) != text.count(tag[u'end tag']):
tags.append((text.find(tag[u'start tag']), tag[u'start tag'], tag[u'end tag']))
# Sort the lists, so that the tags which were opened first on the first
# slide (the text we are checking) will be opened first on the next
# slide as well.
# Sort the lists, so that the tags which were opened first on the first slide (the text we are checking) will
# be opened first on the next slide as well.
tags.sort(key=lambda tag: tag[0])
end_tags = []
start_tags = []
@ -393,16 +374,15 @@ class OpenLyrics(object):
def xml_to_song(self, xml, parse_and_temporary_save=False):
"""
Create and save a song from OpenLyrics format xml to the database. Since
we also export XML from external sources (e. g. OpenLyrics import), we
cannot ensure, that it completely conforms to the OpenLyrics standard.
Create and save a song from OpenLyrics format xml to the database. Since we also export XML from external
sources (e. g. OpenLyrics import), we cannot ensure, that it completely conforms to the OpenLyrics standard.
``xml``
The XML to parse (unicode).
``parse_and_temporary_save``
Switch to skip processing the whole song and storing the songs in
the database with a temporary flag. Defaults to ``False``.
Switch to skip processing the whole song and storing the songs in the database with a temporary flag.
Defaults to ``False``.
"""
# No xml get out of here.
if not xml:
@ -448,8 +428,7 @@ class OpenLyrics(object):
def _add_tag_to_formatting(self, tag_name, tags_element):
"""
Add new formatting tag to the element ``<format>`` if the tag is not
present yet.
Add new formatting tag to the element ``<format>`` if the tag is not present yet.
"""
available_tags = FormattingTags.get_html_tags()
start_tag = '{%s}' % tag_name
@ -469,8 +448,7 @@ class OpenLyrics(object):
def _add_text_with_tags_to_lines(self, verse_element, text, tags_element):
"""
Convert text with formatting tags from OpenLP format to OpenLyrics
format and append it to element ``<lines>``.
Convert text with formatting tags from OpenLP format to OpenLyrics format and append it to element ``<lines>``.
"""
start_tags = OpenLyrics.START_TAGS_REGEX.findall(text)
end_tags = OpenLyrics.END_TAGS_REGEX.findall(text)
@ -478,8 +456,7 @@ class OpenLyrics(object):
for tag in start_tags:
# Tags already converted to xml structure.
xml_tags = tags_element.xpath(u'tag/attribute::name')
# Some formatting tag has only starting part e.g. <br>.
# Handle this case.
# Some formatting tag has only starting part e.g. <br>. Handle this case.
if tag in end_tags:
text = text.replace(u'{%s}' % tag, u'<tag name="%s">' % tag)
else:
@ -586,8 +563,8 @@ class OpenLyrics(object):
def _process_formatting_tags(self, song_xml, temporary):
"""
Process the formatting tags from the song and either add missing tags
temporary or permanently to the formatting tag list.
Process the formatting tags from the song and either add missing tags temporary or permanently to the
formatting tag list.
"""
if not hasattr(song_xml, u'format'):
return
@ -608,8 +585,8 @@ class OpenLyrics(object):
u'end html': tag.close.text if hasattr(tag, 'close') else u'',
u'protected': False,
}
# Add 'temporary' key in case the formatting tag should not be
# saved otherwise it is supposed that formatting tag is permanent.
# Add 'temporary' key in case the formatting tag should not be saved otherwise it is supposed that
# formatting tag is permanent.
if temporary:
openlp_tag[u'temporary'] = temporary
found_tags.append(openlp_tag)
@ -620,15 +597,14 @@ class OpenLyrics(object):
def _process_lines_mixed_content(self, element, newlines=True):
"""
Converts the xml text with mixed content to OpenLP representation.
Chords are skipped and formatting tags are converted.
Converts the xml text with mixed content to OpenLP representation. Chords are skipped and formatting tags are
converted.
``element``
The property object (lxml.etree.Element).
``newlines``
The switch to enable/disable processing of line breaks <br/>.
The <br/> is used since OpenLyrics 0.8.
The switch to enable/disable processing of line breaks <br/>. The <br/> is used since OpenLyrics 0.8.
"""
text = u''
use_endtag = True
@ -684,12 +660,10 @@ class OpenLyrics(object):
lines = etree.tostring(lines)
element = etree.XML(lines)
# OpenLyrics 0.8 uses <br/> for new lines.
# Append text from "lines" element to verse text.
# OpenLyrics 0.8 uses <br/> for new lines. Append text from "lines" element to verse text.
if version > '0.7':
text = self._process_lines_mixed_content(element)
# OpenLyrics version <= 0.7 contais <line> elements to represent lines.
# First child element is tested.
# OpenLyrics version <= 0.7 contais <line> elements to represent lines. First child element is tested.
else:
# Loop over the "line" elements removing comments and chords.
for line in element:
@ -742,16 +716,15 @@ class OpenLyrics(object):
text += u'\n[---]'
verse_def = verse.get(u'name', u' ').lower()
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]
# OpenLyrics allows e. g. "c", but we need "c1". However, this does
# not correct the verse order.
if verse_tag not in VerseType.tags:
verse_tag = VerseType.tags[VerseType.Other]
# OpenLyrics allows e. g. "c", but we need "c1". However, this does not correct the verse order.
if not verse_number:
verse_number = u'1'
lang = verse.get(u'lang')
translit = verse.get(u'translit')
# In OpenLP 1.9.6 we used v1a, v1b ... to represent visual slide
# breaks. In OpenLyrics 0.7 an attribute has been added.
# In OpenLP 1.9.6 we used v1a, v1b ... to represent visual slide breaks. In OpenLyrics 0.7 an attribute has
# been added.
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, translit) in verses:
verses[(verse_tag, verse_number, lang, translit, None)] += u'\n[---]\n' + text

View File

@ -32,29 +32,30 @@ from PyQt4 import QtCore, QtGui
from openlp.core.lib import translate
from openlp.core.lib.ui import create_button_box
class Ui_SongUsageDeleteDialog(object):
def setupUi(self, songUsageDeleteDialog):
songUsageDeleteDialog.setObjectName(u'songUsageDeleteDialog')
songUsageDeleteDialog.resize(291, 243)
self.verticalLayout = QtGui.QVBoxLayout(songUsageDeleteDialog)
self.verticalLayout.setSpacing(8)
self.verticalLayout.setContentsMargins(8, 8, 8, 8)
self.verticalLayout.setObjectName(u'verticalLayout')
self.deleteLabel = QtGui.QLabel(songUsageDeleteDialog)
self.deleteLabel.setObjectName(u'deleteLabel')
self.verticalLayout.addWidget(self.deleteLabel)
self.deleteCalendar = QtGui.QCalendarWidget(songUsageDeleteDialog)
self.deleteCalendar.setFirstDayOfWeek(QtCore.Qt.Sunday)
self.deleteCalendar.setGridVisible(True)
self.deleteCalendar.setVerticalHeaderFormat(QtGui.QCalendarWidget.NoVerticalHeader)
self.deleteCalendar.setObjectName(u'deleteCalendar')
self.verticalLayout.addWidget(self.deleteCalendar)
self.button_box = create_button_box(songUsageDeleteDialog, u'button_box', [u'cancel', u'ok'])
self.verticalLayout.addWidget(self.button_box)
self.retranslateUi(songUsageDeleteDialog)
def retranslateUi(self, songUsageDeleteDialog):
songUsageDeleteDialog.setWindowTitle(translate('SongUsagePlugin.SongUsageDeleteForm', 'Delete Song Usage Data'))
self.deleteLabel.setText(
class Ui_SongUsageDeleteDialog(object):
def setupUi(self, song_usage_delete_dialog):
song_usage_delete_dialog.setObjectName(u'song_usage_delete_dialog')
song_usage_delete_dialog.resize(291, 243)
self.vertical_layout = QtGui.QVBoxLayout(song_usage_delete_dialog)
self.vertical_layout.setSpacing(8)
self.vertical_layout.setContentsMargins(8, 8, 8, 8)
self.vertical_layout.setObjectName(u'vertical_layout')
self.delete_label = QtGui.QLabel(song_usage_delete_dialog)
self.delete_label.setObjectName(u'delete_label')
self.vertical_layout.addWidget(self.delete_label)
self.delete_calendar = QtGui.QCalendarWidget(song_usage_delete_dialog)
self.delete_calendar.setFirstDayOfWeek(QtCore.Qt.Sunday)
self.delete_calendar.setGridVisible(True)
self.delete_calendar.setVerticalHeaderFormat(QtGui.QCalendarWidget.NoVerticalHeader)
self.delete_calendar.setObjectName(u'delete_calendar')
self.vertical_layout.addWidget(self.delete_calendar)
self.button_box = create_button_box(song_usage_delete_dialog, u'button_box', [u'cancel', u'ok'])
self.vertical_layout.addWidget(self.button_box)
self.retranslateUi(song_usage_delete_dialog)
def retranslateUi(self, song_usage_delete_dialog):
song_usage_delete_dialog.setWindowTitle(translate('SongUsagePlugin.SongUsageDeleteForm', 'Delete Song Usage Data'))
self.delete_label.setText(
translate('SongUsagePlugin.SongUsageDeleteForm', 'Select the date up to which the song usage data '
'should be deleted. All data recorded before this date will be permanently deleted.'))

View File

@ -45,10 +45,9 @@ class SongUsageDeleteForm(QtGui.QDialog, Ui_SongUsageDeleteDialog):
self.manager = manager
QtGui.QDialog.__init__(self, parent)
self.setupUi(self)
QtCore.QObject.connect(self.button_box, QtCore.SIGNAL(u'clicked(QAbstractButton*)'),
self.onButtonBoxClicked)
self.button_box.clicked.connect(self.on_button_box_clicked)
def onButtonBoxClicked(self, button):
def on_button_box_clicked(self, button):
if self.button_box.standardButton(button) == QtGui.QDialogButtonBox.Ok:
ret = QtGui.QMessageBox.question(self,
translate('SongUsagePlugin.SongUsageDeleteForm', 'Delete Selected Song Usage Events?'),
@ -56,12 +55,12 @@ class SongUsageDeleteForm(QtGui.QDialog, Ui_SongUsageDeleteDialog):
'Are you sure you want to delete selected Song Usage data?'),
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No), QtGui.QMessageBox.No)
if ret == QtGui.QMessageBox.Yes:
deleteDate = self.deleteCalendar.selectedDate().toPyDate()
self.manager.delete_all_objects(SongUsageItem, SongUsageItem.usagedate <= deleteDate)
delete_date = self.delete_calendar.selectedDate().toPyDate()
self.manager.delete_all_objects(SongUsageItem, SongUsageItem.usagedate <= delete_date)
self.main_window.information_message(
translate('SongUsagePlugin.SongUsageDeleteForm', 'Deletion Successful'),
translate(
'SongUsagePlugin.SongUsageDeleteForm', 'All requested data has been deleted successfully. ')
'SongUsagePlugin.SongUsageDeleteForm', 'All requested data has been deleted successfully.')
)
self.accept()
else:
@ -75,4 +74,4 @@ class SongUsageDeleteForm(QtGui.QDialog, Ui_SongUsageDeleteDialog):
self._main_window = Registry().get(u'main_window')
return self._main_window
main_window = property(_get_main_window)
main_window = property(_get_main_window)

View File

@ -32,56 +32,56 @@ from PyQt4 import QtCore, QtGui
from openlp.core.lib import build_icon, translate
from openlp.core.lib.ui import create_button_box
class Ui_SongUsageDetailDialog(object):
def setupUi(self, songUsageDetailDialog):
songUsageDetailDialog.setObjectName(u'songUsageDetailDialog')
songUsageDetailDialog.resize(609, 413)
self.verticalLayout = QtGui.QVBoxLayout(songUsageDetailDialog)
self.verticalLayout.setSpacing(8)
self.verticalLayout.setContentsMargins(8, 8, 8, 8)
self.verticalLayout.setObjectName(u'verticalLayout')
self.dateRangeGroupBox = QtGui.QGroupBox(songUsageDetailDialog)
self.dateRangeGroupBox.setObjectName(u'dateRangeGroupBox')
self.dateHorizontalLayout = QtGui.QHBoxLayout(self.dateRangeGroupBox)
self.dateHorizontalLayout.setSpacing(8)
self.dateHorizontalLayout.setContentsMargins(8, 8, 8, 8)
self.dateHorizontalLayout.setObjectName(u'dateHorizontalLayout')
self.fromDate = QtGui.QCalendarWidget(self.dateRangeGroupBox)
self.fromDate.setObjectName(u'fromDate')
self.dateHorizontalLayout.addWidget(self.fromDate)
self.toLabel = QtGui.QLabel(self.dateRangeGroupBox)
self.toLabel.setScaledContents(False)
self.toLabel.setAlignment(QtCore.Qt.AlignCenter)
self.toLabel.setObjectName(u'toLabel')
self.dateHorizontalLayout.addWidget(self.toLabel)
self.toDate = QtGui.QCalendarWidget(self.dateRangeGroupBox)
self.toDate.setObjectName(u'toDate')
self.dateHorizontalLayout.addWidget(self.toDate)
self.verticalLayout.addWidget(self.dateRangeGroupBox)
self.fileGroupBox = QtGui.QGroupBox(self.dateRangeGroupBox)
self.fileGroupBox.setObjectName(u'fileGroupBox')
self.fileHorizontalLayout = QtGui.QHBoxLayout(self.fileGroupBox)
self.fileHorizontalLayout.setSpacing(8)
self.fileHorizontalLayout.setContentsMargins(8, 8, 8, 8)
self.fileHorizontalLayout.setObjectName(u'fileHorizontalLayout')
self.fileLineEdit = QtGui.QLineEdit(self.fileGroupBox)
self.fileLineEdit.setObjectName(u'fileLineEdit')
self.fileLineEdit.setReadOnly(True)
self.fileHorizontalLayout.addWidget(self.fileLineEdit)
self.saveFilePushButton = QtGui.QPushButton(self.fileGroupBox)
self.saveFilePushButton.setMaximumWidth(self.saveFilePushButton.size().height())
self.saveFilePushButton.setIcon(build_icon(u':/general/general_open.png'))
self.saveFilePushButton.setObjectName(u'saveFilePushButton')
self.fileHorizontalLayout.addWidget(self.saveFilePushButton)
self.verticalLayout.addWidget(self.fileGroupBox)
self.button_box = create_button_box(songUsageDetailDialog, u'button_box', [u'cancel', u'ok'])
self.verticalLayout.addWidget(self.button_box)
self.retranslateUi(songUsageDetailDialog)
QtCore.QObject.connect(self.saveFilePushButton, QtCore.SIGNAL(u'clicked()'),
songUsageDetailDialog.defineOutputLocation)
def retranslateUi(self, songUsageDetailDialog):
songUsageDetailDialog.setWindowTitle(translate('SongUsagePlugin.SongUsageDetailForm', 'Song Usage Extraction'))
self.dateRangeGroupBox.setTitle(translate('SongUsagePlugin.SongUsageDetailForm', 'Select Date Range'))
self.toLabel.setText(translate('SongUsagePlugin.SongUsageDetailForm', 'to'))
self.fileGroupBox.setTitle(translate('SongUsagePlugin.SongUsageDetailForm', 'Report Location'))
class Ui_SongUsageDetailDialog(object):
def setupUi(self, song_usage_detail_dialog):
song_usage_detail_dialog.setObjectName(u'song_usage_detail_dialog')
song_usage_detail_dialog.resize(609, 413)
self.vertical_layout = QtGui.QVBoxLayout(song_usage_detail_dialog)
self.vertical_layout.setSpacing(8)
self.vertical_layout.setContentsMargins(8, 8, 8, 8)
self.vertical_layout.setObjectName(u'vertical_layout')
self.date_range_group_box = QtGui.QGroupBox(song_usage_detail_dialog)
self.date_range_group_box.setObjectName(u'date_range_group_box')
self.date_horizontal_layout = QtGui.QHBoxLayout(self.date_range_group_box)
self.date_horizontal_layout.setSpacing(8)
self.date_horizontal_layout.setContentsMargins(8, 8, 8, 8)
self.date_horizontal_layout.setObjectName(u'date_horizontal_layout')
self.from_date_calendar = QtGui.QCalendarWidget(self.date_range_group_box)
self.from_date_calendar.setObjectName(u'from_date_calendar')
self.date_horizontal_layout.addWidget(self.from_date_calendar)
self.to_label = QtGui.QLabel(self.date_range_group_box)
self.to_label.setScaledContents(False)
self.to_label.setAlignment(QtCore.Qt.AlignCenter)
self.to_label.setObjectName(u'to_label')
self.date_horizontal_layout.addWidget(self.to_label)
self.to_date_calendar = QtGui.QCalendarWidget(self.date_range_group_box)
self.to_date_calendar.setObjectName(u'to_date_calendar')
self.date_horizontal_layout.addWidget(self.to_date_calendar)
self.vertical_layout.addWidget(self.date_range_group_box)
self.file_group_box = QtGui.QGroupBox(self.date_range_group_box)
self.file_group_box.setObjectName(u'file_group_box')
self.file_horizontal_layout = QtGui.QHBoxLayout(self.file_group_box)
self.file_horizontal_layout.setSpacing(8)
self.file_horizontal_layout.setContentsMargins(8, 8, 8, 8)
self.file_horizontal_layout.setObjectName(u'file_horizontal_layout')
self.file_line_edit = QtGui.QLineEdit(self.file_group_box)
self.file_line_edit.setObjectName(u'file_line_edit')
self.file_line_edit.setReadOnly(True)
self.file_horizontal_layout.addWidget(self.file_line_edit)
self.save_file_push_button = QtGui.QPushButton(self.file_group_box)
self.save_file_push_button.setMaximumWidth(self.save_file_push_button.size().height())
self.save_file_push_button.setIcon(build_icon(u':/general/general_open.png'))
self.save_file_push_button.setObjectName(u'save_file_push_button')
self.file_horizontal_layout.addWidget(self.save_file_push_button)
self.vertical_layout.addWidget(self.file_group_box)
self.button_box = create_button_box(song_usage_detail_dialog, u'button_box', [u'cancel', u'ok'])
self.vertical_layout.addWidget(self.button_box)
self.retranslateUi(song_usage_detail_dialog)
self.save_file_push_button.clicked.connect(song_usage_detail_dialog.define_output_location)
def retranslateUi(self, song_usage_detail_dialog):
song_usage_detail_dialog.setWindowTitle(translate('SongUsagePlugin.SongUsageDetailForm', 'Song Usage Extraction'))
self.date_range_group_box.setTitle(translate('SongUsagePlugin.SongUsageDetailForm', 'Select Date Range'))
self.to_label.setText(translate('SongUsagePlugin.SongUsageDetailForm', 'to'))
self.file_group_box.setTitle(translate('SongUsagePlugin.SongUsageDetailForm', 'Report Location'))

View File

@ -39,6 +39,7 @@ from songusagedetaildialog import Ui_SongUsageDetailDialog
log = logging.getLogger(__name__)
class SongUsageDetailForm(QtGui.QDialog, Ui_SongUsageDetailDialog):
"""
Class documentation goes here.
@ -57,13 +58,11 @@ class SongUsageDetailForm(QtGui.QDialog, Ui_SongUsageDetailDialog):
"""
We need to set up the screen
"""
toDate = Settings().value(self.plugin.settingsSection + u'/to date')
fromDate = Settings().value(self.plugin.settingsSection + u'/from date')
self.fromDate.setSelectedDate(fromDate)
self.toDate.setSelectedDate(toDate)
self.fileLineEdit.setText(Settings().value(self.plugin.settingsSection + u'/last directory export'))
self.from_date_calendar.setSelectedDate(Settings().value(self.plugin.settingsSection + u'/from date'))
self.to_date_calendar.setSelectedDate(Settings().value(self.plugin.settingsSection + u'/to date'))
self.file_line_edit.setText(Settings().value(self.plugin.settingsSection + u'/last directory export'))
def defineOutputLocation(self):
def define_output_location(self):
"""
Triggered when the Directory selection button is clicked
"""
@ -72,14 +71,14 @@ class SongUsageDetailForm(QtGui.QDialog, Ui_SongUsageDetailDialog):
Settings().value(self.plugin.settingsSection + u'/last directory export'))
if path:
Settings().setValue(self.plugin.settingsSection + u'/last directory export', path)
self.fileLineEdit.setText(path)
self.file_line_edit.setText(path)
def accept(self):
"""
Ok was triggered so lets save the data and run the report
"""
log.debug(u'accept')
path = self.fileLineEdit.text()
path = self.file_line_edit.text()
if not path:
self.main_window.error_message(
translate('SongUsagePlugin.SongUsageDetailForm', 'Output Path Not Selected'),
@ -88,36 +87,36 @@ class SongUsageDetailForm(QtGui.QDialog, Ui_SongUsageDetailDialog):
)
return
check_directory_exists(path)
filename = translate('SongUsagePlugin.SongUsageDetailForm', 'usage_detail_%s_%s.txt') % (
self.fromDate.selectedDate().toString(u'ddMMyyyy'),
self.toDate.selectedDate().toString(u'ddMMyyyy'))
Settings().setValue(u'songusage/from date', self.fromDate.selectedDate())
Settings().setValue(u'songusage/to date', self.toDate.selectedDate())
file_name = translate('SongUsagePlugin.SongUsageDetailForm', 'usage_detail_%s_%s.txt') % (
self.from_date_calendar.selectedDate().toString(u'ddMMyyyy'),
self.to_date_calendar.selectedDate().toString(u'ddMMyyyy'))
Settings().setValue(self.plugin.settingsSection + u'/from date', self.from_date_calendar.selectedDate())
Settings().setValue(self.plugin.settingsSection + u'/to date', self.to_date_calendar.selectedDate())
usage = self.plugin.manager.get_all_objects(
SongUsageItem, and_(
SongUsageItem.usagedate >= self.fromDate.selectedDate().toPyDate(),
SongUsageItem.usagedate < self.toDate.selectedDate().toPyDate()),
SongUsageItem.usagedate >= self.from_date_calendar.selectedDate().toPyDate(),
SongUsageItem.usagedate < self.to_date_calendar.selectedDate().toPyDate()),
[SongUsageItem.usagedate, SongUsageItem.usagetime])
outname = os.path.join(path, filename)
fileHandle = None
report_file_name = os.path.join(path, file_name)
file_handle = None
try:
fileHandle = open(outname, u'w')
file_handle = open(report_file_name, u'w')
for instance in usage:
record = u'\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",' \
u'\"%s\",\"%s\"\n' % (instance.usagedate,
instance.usagetime, instance.title, instance.copyright,
instance.ccl_number, instance.authors, instance.plugin_name, instance.source)
fileHandle.write(record.encode(u'utf-8'))
file_handle.write(record.encode(u'utf-8'))
self.main_window.information_message(
translate('SongUsagePlugin.SongUsageDetailForm', 'Report Creation'),
translate('SongUsagePlugin.SongUsageDetailForm', 'Report \n%s \n'
'has been successfully created. ') % outname
'has been successfully created. ') % report_file_name
)
except IOError:
log.exception(u'Failed to write out song usage records')
finally:
if fileHandle:
fileHandle.close()
if file_handle:
file_handle.close()
self.close()
def _get_main_window(self):
@ -128,4 +127,4 @@ class SongUsageDetailForm(QtGui.QDialog, Ui_SongUsageDetailDialog):
self._main_window = Registry().get(u'main_window')
return self._main_window
main_window = property(_get_main_window)
main_window = property(_get_main_window)

View File

@ -42,7 +42,6 @@ from openlp.plugins.songusage.lib.db import init_schema, SongUsageItem
log = logging.getLogger(__name__)
YEAR = QtCore.QDate().currentDate().year()
if QtCore.QDate().currentDate().month() < 9:
YEAR -= 1
@ -54,7 +53,7 @@ __default_settings__ = {
u'songusage/to date': QtCore.QDate(YEAR, 8, 31),
u'songusage/from date': QtCore.QDate(YEAR - 1, 9, 1),
u'songusage/last directory export': u''
}
}
class SongUsagePlugin(Plugin):
@ -67,7 +66,7 @@ class SongUsagePlugin(Plugin):
self.icon = build_icon(u':/plugins/plugin_songusage.png')
self.activeIcon = build_icon(u':/songusage/song_usage_active.png')
self.inactiveIcon = build_icon(u':/songusage/song_usage_inactive.png')
self.songUsageActive = False
self.song_usage_active = False
def checkPreConditions(self):
return self.manager.session is not None
@ -83,59 +82,59 @@ class SongUsagePlugin(Plugin):
"""
log.info(u'add tools menu')
self.toolsMenu = tools_menu
self.songUsageMenu = QtGui.QMenu(tools_menu)
self.songUsageMenu.setObjectName(u'songUsageMenu')
self.songUsageMenu.setTitle(translate('SongUsagePlugin', '&Song Usage Tracking'))
self.song_usage_menu = QtGui.QMenu(tools_menu)
self.song_usage_menu.setObjectName(u'song_usage_menu')
self.song_usage_menu.setTitle(translate('SongUsagePlugin', '&Song Usage Tracking'))
# SongUsage Delete
self.songUsageDelete = create_action(tools_menu, u'songUsageDelete',
self.song_usage_delete = create_action(tools_menu, u'songUsageDelete',
text=translate('SongUsagePlugin', '&Delete Tracking Data'),
statustip=translate('SongUsagePlugin', 'Delete song usage data up to a specified date.'),
triggers=self.onSongUsageDelete)
triggers=self.on_song_usage_delete)
# SongUsage Report
self.songUsageReport = create_action(tools_menu, u'songUsageReport',
self.song_usage_report = create_action(tools_menu, u'songUsageReport',
text=translate('SongUsagePlugin', '&Extract Tracking Data'),
statustip=translate('SongUsagePlugin', 'Generate a report on song usage.'),
triggers=self.onSongUsageReport)
triggers=self.on_song_usage_report)
# SongUsage activation
self.songUsageStatus = create_action(tools_menu, u'songUsageStatus',
self.song_usage_status = create_action(tools_menu, u'songUsageStatus',
text=translate('SongUsagePlugin', 'Toggle Tracking'),
statustip=translate('SongUsagePlugin', 'Toggle the tracking of song usage.'), checked=False,
shortcuts=[QtCore.Qt.Key_F4], triggers=self.toggleSongUsageState)
can_shortcuts=True, triggers=self.toggle_song_usage_state)
# Add Menus together
self.toolsMenu.addAction(self.songUsageMenu.menuAction())
self.songUsageMenu.addAction(self.songUsageStatus)
self.songUsageMenu.addSeparator()
self.songUsageMenu.addAction(self.songUsageReport)
self.songUsageMenu.addAction(self.songUsageDelete)
self.songUsageActiveButton = QtGui.QToolButton(self.main_window.statusBar)
self.songUsageActiveButton.setCheckable(True)
self.songUsageActiveButton.setAutoRaise(True)
self.songUsageActiveButton.setStatusTip(translate('SongUsagePlugin', 'Toggle the tracking of song usage.'))
self.songUsageActiveButton.setObjectName(u'songUsageActiveButton')
self.main_window.statusBar.insertPermanentWidget(1, self.songUsageActiveButton)
self.songUsageActiveButton.hide()
self.toolsMenu.addAction(self.song_usage_menu.menuAction())
self.song_usage_menu.addAction(self.song_usage_status)
self.song_usage_menu.addSeparator()
self.song_usage_menu.addAction(self.song_usage_report)
self.song_usage_menu.addAction(self.song_usage_delete)
self.song_usage_active_button = QtGui.QToolButton(self.main_window.statusBar)
self.song_usage_active_button.setCheckable(True)
self.song_usage_active_button.setAutoRaise(True)
self.song_usage_active_button.setStatusTip(translate('SongUsagePlugin', 'Toggle the tracking of song usage.'))
self.song_usage_active_button.setObjectName(u'song_usage_active_button')
self.main_window.statusBar.insertPermanentWidget(1, self.song_usage_active_button)
self.song_usage_active_button.hide()
# Signals and slots
QtCore.QObject.connect(self.songUsageStatus, QtCore.SIGNAL(u'visibilityChanged(bool)'),
self.songUsageStatus.setChecked)
QtCore.QObject.connect(self.songUsageActiveButton, QtCore.SIGNAL(u'toggled(bool)'), self.toggleSongUsageState)
self.songUsageMenu.menuAction().setVisible(False)
QtCore.QObject.connect(self.song_usage_status, QtCore.SIGNAL(u'visibilityChanged(bool)'),
self.song_usage_status.setChecked)
self.song_usage_active_button.toggled.connect(self.toggle_song_usage_state)
self.song_usage_menu.menuAction().setVisible(False)
def initialise(self):
log.info(u'SongUsage Initialising')
Plugin.initialise(self)
Registry().register_function(u'slidecontroller_live_started', self.display_song_usage)
Registry().register_function(u'print_service_started', self.print_song_usage)
self.songUsageActive = Settings().value(self.settingsSection + u'/active')
self.song_usage_active = Settings().value(self.settingsSection + u'/active')
# Set the button and checkbox state
self.setButtonState()
self.set_button_state()
action_list = ActionList.get_instance()
action_list.add_action(self.songUsageStatus, translate('SongUsagePlugin', 'Song Usage'))
action_list.add_action(self.songUsageDelete, translate('SongUsagePlugin', 'Song Usage'))
action_list.add_action(self.songUsageReport, translate('SongUsagePlugin', 'Song Usage'))
self.songUsageDeleteForm = SongUsageDeleteForm(self.manager, self.main_window)
self.songUsageDetailForm = SongUsageDetailForm(self, self.main_window)
self.songUsageMenu.menuAction().setVisible(True)
self.songUsageActiveButton.show()
action_list.add_action(self.song_usage_status, translate('SongUsagePlugin', 'Song Usage'))
action_list.add_action(self.song_usage_delete, translate('SongUsagePlugin', 'Song Usage'))
action_list.add_action(self.song_usage_report, translate('SongUsagePlugin', 'Song Usage'))
self.song_usage_delete_form = SongUsageDeleteForm(self.manager, self.main_window)
self.song_usage_detail_form = SongUsageDetailForm(self, self.main_window)
self.song_usage_menu.menuAction().setVisible(True)
self.song_usage_active_button.show()
def finalise(self):
"""
@ -144,44 +143,43 @@ class SongUsagePlugin(Plugin):
log.info(u'Plugin Finalise')
self.manager.finalise()
Plugin.finalise(self)
self.songUsageMenu.menuAction().setVisible(False)
self.song_usage_menu.menuAction().setVisible(False)
action_list = ActionList.get_instance()
action_list.remove_action(self.songUsageStatus, translate('SongUsagePlugin', 'Song Usage'))
action_list.remove_action(self.songUsageDelete, translate('SongUsagePlugin', 'Song Usage'))
action_list.remove_action(self.songUsageReport, translate('SongUsagePlugin', 'Song Usage'))
self.songUsageActiveButton.hide()
action_list.remove_action(self.song_usage_status, translate('SongUsagePlugin', 'Song Usage'))
action_list.remove_action(self.song_usage_delete, translate('SongUsagePlugin', 'Song Usage'))
action_list.remove_action(self.song_usage_report, translate('SongUsagePlugin', 'Song Usage'))
self.song_usage_active_button.hide()
# stop any events being processed
self.songUsageActive = False
self.song_usage_active = False
def toggleSongUsageState(self):
def toggle_song_usage_state(self):
"""
Manage the state of the audit collection and amend
the UI when necessary,
"""
self.songUsageActive = not self.songUsageActive
Settings().setValue(self.settingsSection + u'/active', self.songUsageActive)
self.setButtonState()
self.song_usage_active = not self.song_usage_active
Settings().setValue(self.settingsSection + u'/active', self.song_usage_active)
self.set_button_state()
def setButtonState(self):
def set_button_state(self):
"""
Keep buttons inline. Turn of signals to stop dead loop but we need the
button and check box set correctly.
"""
self.songUsageActiveButton.blockSignals(True)
self.songUsageStatus.blockSignals(True)
if self.songUsageActive:
self.songUsageActiveButton.setIcon(self.activeIcon)
self.songUsageStatus.setChecked(True)
self.songUsageActiveButton.setChecked(True)
self.songUsageActiveButton.setToolTip(translate('SongUsagePlugin', 'Song usage tracking is active.'))
self.song_usage_active_button.blockSignals(True)
self.song_usage_status.blockSignals(True)
if self.song_usage_active:
self.song_usage_active_button.setIcon(self.activeIcon)
self.song_usage_status.setChecked(True)
self.song_usage_active_button.setChecked(True)
self.song_usage_active_button.setToolTip(translate('SongUsagePlugin', 'Song usage tracking is active.'))
else:
self.songUsageActiveButton.setIcon(self.inactiveIcon)
self.songUsageStatus.setChecked(False)
self.songUsageActiveButton.setChecked(False)
self.songUsageActiveButton.setToolTip(translate('SongUsagePlugin', 'Song usage tracking is inactive.'))
self.songUsageActiveButton.blockSignals(False)
self.songUsageStatus.blockSignals(False)
self.song_usage_active_button.setIcon(self.inactiveIcon)
self.song_usage_status.setChecked(False)
self.song_usage_active_button.setChecked(False)
self.song_usage_active_button.setToolTip(translate('SongUsagePlugin', 'Song usage tracking is inactive.'))
self.song_usage_active_button.blockSignals(False)
self.song_usage_status.blockSignals(False)
def display_song_usage(self, item):
"""
@ -197,7 +195,7 @@ class SongUsagePlugin(Plugin):
def _add_song_usage(self, source, item):
audit = item[0].audit
if self.songUsageActive and audit:
if self.song_usage_active and audit:
song_usage_item = SongUsageItem()
song_usage_item.usagedate = datetime.today()
song_usage_item.usagetime = datetime.now().time()
@ -209,12 +207,12 @@ class SongUsagePlugin(Plugin):
song_usage_item.source = source
self.manager.save_object(song_usage_item)
def onSongUsageDelete(self):
self.songUsageDeleteForm.exec_()
def on_song_usage_delete(self):
self.song_usage_delete_form.exec_()
def onSongUsageReport(self):
self.songUsageDetailForm.initialise()
self.songUsageDetailForm.exec_()
def on_song_usage_report(self):
self.song_usage_detail_form.initialise()
self.song_usage_detail_form.exec_()
def about(self):
about_text = translate('SongUsagePlugin', '<strong>SongUsage Plugin'

View File

@ -40,7 +40,13 @@ import os
import sys
from distutils.version import LooseVersion
is_win = sys.platform.startswith('win')
# If we try to import uno before nose this will greate a warning. Just try to import nose first to supress the warning.
try:
import nose
except ImportError:
pass
IS_WIN = sys.platform.startswith('win')
VERS = {
'Python': '2.6',
@ -48,7 +54,7 @@ VERS = {
'Qt4': '4.6',
'sqlalchemy': '0.5',
# pyenchant 1.6 required on Windows
'enchant': '1.6' if is_win else '1.3'
'enchant': '1.6' if IS_WIN else '1.3'
}
# pywin32
@ -84,7 +90,8 @@ OPTIONAL_MODULES = [
('sqlite', ' (SQLite 2 support)'),
('MySQLdb', ' (MySQL support)'),
('psycopg2', ' (PostgreSQL support)'),
('pytest', ' (testing framework)'),
('nose', ' (testing framework)'),
('mock', ' (testing module)'),
]
w = sys.stdout.write
@ -176,7 +183,7 @@ def main():
for m in OPTIONAL_MODULES:
check_module(m[0], text=m[1])
if is_win:
if IS_WIN:
print('Checking for Windows specific modules...')
for m in WIN32_MODULES:
check_module(m)

View File

@ -0,0 +1,13 @@
import sip
sip.setapi(u'QDate', 2)
sip.setapi(u'QDateTime', 2)
sip.setapi(u'QString', 2)
sip.setapi(u'QTextStream', 2)
sip.setapi(u'QTime', 2)
sip.setapi(u'QUrl', 2)
sip.setapi(u'QVariant', 2)
from PyQt4 import QtGui
# Only one QApplication can be created. Use QtGui.QApplication.instance() when you need to "create" a QApplication.
application = QtGui.QApplication([])

View File

@ -0,0 +1,84 @@
"""
Package to test the openlp.core.lib.formattingtags package.
"""
import copy
from unittest import TestCase
from mock import patch
from openlp.core.lib import FormattingTags
TAG = {
u'end tag': '{/aa}',
u'start html': '<span>',
u'start tag': '{aa}',
u'protected': False,
u'end html': '</span>',
u'desc': 'name'
}
class TestFormattingTags(TestCase):
def tearDown(self):
"""
Clean up the FormattingTags class.
"""
FormattingTags.html_expands = []
def get_html_tags_no_user_tags_test(self):
"""
Test the FormattingTags class' get_html_tags static method.
"""
with patch(u'openlp.core.lib.translate') as mocked_translate, \
patch(u'openlp.core.lib.settings') as mocked_settings, \
patch(u'openlp.core.lib.formattingtags.cPickle') as mocked_cPickle:
# GIVEN: Our mocked modules and functions.
mocked_translate.side_effect = lambda module, string_to_translate, comment: string_to_translate
mocked_settings.value.return_value = u''
mocked_cPickle.load.return_value = []
# WHEN: Get the display tags.
FormattingTags.load_tags()
old_tags_list = copy.deepcopy(FormattingTags.get_html_tags())
FormattingTags.load_tags()
new_tags_list = FormattingTags.get_html_tags()
# THEN: Lists should be identical.
assert old_tags_list == new_tags_list, u'The formatting tag lists should be identical.'
def get_html_tags_with_user_tags_test(self):
"""
FormattingTags class - test the get_html_tags(), add_html_tags() and remove_html_tag() methods.
"""
with patch(u'openlp.core.lib.translate') as mocked_translate, \
patch(u'openlp.core.lib.settings') as mocked_settings, \
patch(u'openlp.core.lib.formattingtags.cPickle') as mocked_cPickle:
# GIVEN: Our mocked modules and functions.
mocked_translate.side_effect = lambda module, string_to_translate: string_to_translate
mocked_settings.value.return_value = u''
mocked_cPickle.loads.side_effect = [[], [TAG]]
# WHEN: Get the display tags.
FormattingTags.load_tags()
old_tags_list = copy.deepcopy(FormattingTags.get_html_tags())
# WHEN: Add our tag and get the tags again.
FormattingTags.load_tags()
FormattingTags.add_html_tags([TAG])
new_tags_list = copy.deepcopy(FormattingTags.get_html_tags())
# THEN: Lists should not be identical.
assert old_tags_list != new_tags_list, u'The lists should be different.'
# THEN: Added tag and last tag should be the same.
new_tag = new_tags_list.pop()
assert TAG == new_tag, u'Tags should be identical.'
# WHEN: Remove the new tag.
FormattingTags.remove_html_tag(len(new_tags_list))
# THEN: The lists should now be identical.
assert old_tags_list == FormattingTags.get_html_tags(), u'The lists should be identical.'

View File

@ -0,0 +1,50 @@
"""
Package to test the openlp.core.ui package.
"""
import os
from unittest import TestCase
from PyQt4 import QtGui
from openlp.core.lib import Registry, ImageManager, ScreenList
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), u'..', u'..', u'resources'))
class TestImageManager(TestCase):
def setUp(self):
"""
Create the UI
"""
Registry.create()
self.app = QtGui.QApplication.instance()
ScreenList.create(self.app.desktop())
self.image_manager = ImageManager()
def tearDown(self):
"""
Delete all the C++ objects at the end so that we don't have a segfault
"""
del self.app
def basic_image_manager_test(self):
"""
Test the Image Manager setup basic functionality
"""
# GIVEN: the an image add to the image manager
self.image_manager.add_image(TEST_PATH, u'church.jpg', None)
# WHEN the image is retrieved
image = self.image_manager.get_image(TEST_PATH, u'church.jpg')
# THEN returned record is a type of image
self.assertEqual(isinstance(image, QtGui.QImage), True, u'The returned object should be a QImage')
# WHEN the image is retrieved has not been loaded
# THEN a KeyError is thrown
with self.assertRaises(KeyError) as context:
self.image_manager.get_image(TEST_PATH, u'church1.jpg')
self.assertNotEquals(context.exception[0], u'', u'KeyError exception should have been thrown for missing image')

View File

@ -7,7 +7,7 @@ from datetime import datetime, timedelta
from mock import MagicMock, patch
from openlp.core.lib import str_to_bool, translate, check_directory_exists, get_text_file_string, build_icon, \
image_to_byte, check_item_selected, validate_thumb
image_to_byte, check_item_selected, validate_thumb, create_separated_list
class TestLib(TestCase):
@ -308,14 +308,14 @@ class TestLib(TestCase):
file_path = u'path/to/file'
thumb_path = u'path/to/thumb'
mocked_os.path.exists.return_value = False
# WHEN: we run the validate_thumb() function
result = validate_thumb(file_path, thumb_path)
# THEN: we should have called a few functions, and the result should be False
mocked_os.path.exists.assert_called_with(thumb_path)
assert result is False, u'The result should be False'
def validate_thumb_file_exists_and_newer_test(self):
"""
Test the validate_thumb() function when the thumbnail exists and has a newer timestamp than the file
@ -350,7 +350,7 @@ class TestLib(TestCase):
thumb_mocked_stat.st_mtime = datetime.now() - timedelta(seconds=10)
mocked_os.path.exists.return_value = True
mocked_os.stat.side_effect = lambda fname: file_mocked_stat if fname == file_path else thumb_mocked_stat
# WHEN: we run the validate_thumb() function
result = validate_thumb(file_path, thumb_path)
@ -359,3 +359,90 @@ class TestLib(TestCase):
mocked_os.stat.assert_any_call(file_path)
mocked_os.stat.assert_any_call(thumb_path)
assert result is False, u'The result should be False'
def create_separated_list_qlocate_test(self):
"""
Test the create_separated_list function using the Qt provided method.
"""
with patch(u'openlp.core.lib.Qt') as mocked_qt, \
patch(u'openlp.core.lib.QtCore.QLocale.createSeparatedList') as mocked_createSeparatedList:
# GIVEN: A list of strings and the mocked Qt module.
mocked_qt.PYQT_VERSION_STR = u'4.9'
mocked_qt.qVersion.return_value = u'4.8'
mocked_createSeparatedList.return_value = u'Author 1, Author 2, and Author 3'
string_list = [u'Author 1', u'Author 2', u'Author 3']
# WHEN: We get a string build from the entries it the list and a seperator.
string_result = create_separated_list(string_list)
# THEN: We should have "Author 1, Author 2, and Author 3"
assert string_result == u'Author 1, Author 2, and Author 3', u'The string should be u\'Author 1, ' \
'Author 2, and Author 3\'.'
def create_separated_list_empty_list_test(self):
"""
Test the create_separated_list function with an empty list.
"""
with patch(u'openlp.core.lib.Qt') as mocked_qt:
# GIVEN: An empty list and the mocked Qt module.
mocked_qt.PYQT_VERSION_STR = u'4.8'
mocked_qt.qVersion.return_value = u'4.7'
string_list = []
# WHEN: We get a string build from the entries it the list and a seperator.
string_result = create_separated_list(string_list)
# THEN: We shoud have an emptry string.
assert string_result == u'', u'The string sould be empty.'
def create_separated_list_with_one_item_test(self):
"""
Test the create_separated_list function with a list consisting of only one entry.
"""
with patch(u'openlp.core.lib.Qt') as mocked_qt:
# GIVEN: A list with a string and the mocked Qt module.
mocked_qt.PYQT_VERSION_STR = u'4.8'
mocked_qt.qVersion.return_value = u'4.7'
string_list = [u'Author 1']
# WHEN: We get a string build from the entries it the list and a seperator.
string_result = create_separated_list(string_list)
# THEN: We should have "Author 1"
assert string_result == u'Author 1', u'The string should be u\'Author 1\'.'
def create_separated_list_with_two_items_test(self):
"""
Test the create_separated_list function with a list of two entries.
"""
with patch(u'openlp.core.lib.Qt') as mocked_qt, patch(u'openlp.core.lib.translate') as mocked_translate:
# GIVEN: A list of strings and the mocked Qt module.
mocked_qt.PYQT_VERSION_STR = u'4.8'
mocked_qt.qVersion.return_value = u'4.7'
mocked_translate.return_value = u'%s and %s'
string_list = [u'Author 1', u'Author 2']
# WHEN: We get a string build from the entries it the list and a seperator.
string_result = create_separated_list(string_list)
# THEN: We should have "Author 1 and Author 2"
assert string_result == u'Author 1 and Author 2', u'The string should be u\'Author 1 and Author 2\'.'
def create_separated_list_with_three_items_test(self):
"""
Test the create_separated_list function with a list of three items.
"""
with patch(u'openlp.core.lib.Qt') as mocked_qt, patch(u'openlp.core.lib.translate') as mocked_translate:
# GIVEN: A list with a string and the mocked Qt module.
mocked_qt.PYQT_VERSION_STR = u'4.8'
mocked_qt.qVersion.return_value = u'4.7'
# Always return the untranslated string.
mocked_translate.side_effect = lambda module, string_to_translate, comment: string_to_translate
string_list = [u'Author 1', u'Author 2', u'Author 3']
# WHEN: We get a string build from the entries it the list and a seperator.
string_result = create_separated_list(string_list)
# THEN: We should have "Author 1, Author 2, and Author 3"
assert string_result == u'Author 1, Author 2, and Author 3', u'The string should be u\'Author 1, ' \
'Author 2, and Author 3\'.'

View File

@ -0,0 +1,373 @@
"""
Package to test the openlp.core.lib.pluginmanager package.
"""
from unittest import TestCase
from mock import MagicMock
from openlp.core.lib.pluginmanager import PluginManager
from openlp.core.lib import Registry, PluginStatus
class TestPluginManager(TestCase):
"""
Test the PluginManager class
"""
def setUp(self):
"""
Some pre-test setup required.
"""
Registry.create()
Registry().register(u'service_list', MagicMock())
def hook_media_manager_with_disabled_plugin_test(self):
"""
Test running the hook_media_manager() method with a disabled plugin
"""
# GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Disabled
mocked_plugin = MagicMock()
mocked_plugin.status = PluginStatus.Disabled
plugin_manager = PluginManager()
plugin_manager.plugins = [mocked_plugin]
# WHEN: We run hook_media_manager()
plugin_manager.hook_media_manager()
# THEN: The createMediaManagerItem() method should have been called
assert mocked_plugin.createMediaManagerItem.call_count == 0, \
u'The createMediaManagerItem() method should not have been called.'
def hook_media_manager_with_active_plugin_test(self):
"""
Test running the hook_media_manager() method with an active plugin
"""
# GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Active
mocked_plugin = MagicMock()
mocked_plugin.status = PluginStatus.Active
plugin_manager = PluginManager()
plugin_manager.plugins = [mocked_plugin]
# WHEN: We run hook_media_manager()
plugin_manager.hook_media_manager()
# THEN: The createMediaManagerItem() method should have been called
mocked_plugin.createMediaManagerItem.assert_called_with()
def hook_settings_tabs_with_disabled_plugin_and_no_form_test(self):
"""
Test running the hook_settings_tabs() method with a disabled plugin and no form
"""
# GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Disabled
mocked_plugin = MagicMock()
mocked_plugin.status = PluginStatus.Disabled
plugin_manager = PluginManager()
plugin_manager.plugins = [mocked_plugin]
# WHEN: We run hook_settings_tabs()
plugin_manager.hook_settings_tabs()
# THEN: The createSettingsTab() method should have been called
assert mocked_plugin.createMediaManagerItem.call_count == 0, \
u'The createMediaManagerItem() method should not have been called.'
def hook_settings_tabs_with_disabled_plugin_and_mocked_form_test(self):
"""
Test running the hook_settings_tabs() method with a disabled plugin and a mocked form
"""
# GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Disabled
mocked_plugin = MagicMock()
mocked_plugin.status = PluginStatus.Disabled
mocked_settings_form = MagicMock()
plugin_manager = PluginManager()
plugin_manager.plugins = [mocked_plugin]
# WHEN: We run hook_settings_tabs()
plugin_manager.hook_settings_tabs(mocked_settings_form)
# THEN: The createSettingsTab() method should not have been called, but the plugins lists should be the same
assert mocked_plugin.createSettingsTab.call_count == 0, \
u'The createMediaManagerItem() method should not have been called.'
self.assertEqual(mocked_settings_form.plugins, plugin_manager.plugins,
u'The plugins on the settings form should be the same as the plugins in the plugin manager')
def hook_settings_tabs_with_active_plugin_and_no_form_test(self):
"""
Test running the hook_settings_tabs() method with an active plugin and no settings form
"""
# GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Active
mocked_plugin = MagicMock()
mocked_plugin.status = PluginStatus.Active
plugin_manager = PluginManager()
plugin_manager.plugins = [mocked_plugin]
# WHEN: We run hook_settings_tabs()
plugin_manager.hook_settings_tabs()
# THEN: The createSettingsTab() method should have been called
mocked_plugin.createSettingsTab.assert_called_with(None)
def hook_settings_tabs_with_active_plugin_and_mocked_form_test(self):
"""
Test running the hook_settings_tabs() method with an active plugin and a mocked settings form
"""
# GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Active
mocked_plugin = MagicMock()
mocked_plugin.status = PluginStatus.Active
mocked_settings_form = MagicMock()
plugin_manager = PluginManager()
plugin_manager.plugins = [mocked_plugin]
# WHEN: We run hook_settings_tabs()
plugin_manager.hook_settings_tabs(mocked_settings_form)
# THEN: The createMediaManagerItem() method should have been called with the mocked settings form
mocked_plugin.createSettingsTab.assert_called_with(mocked_settings_form)
self.assertEqual(mocked_settings_form.plugins, plugin_manager.plugins,
u'The plugins on the settings form should be the same as the plugins in the plugin manager')
def hook_import_menu_with_disabled_plugin_test(self):
"""
Test running the hook_import_menu() method with a disabled plugin
"""
# GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Disabled
mocked_plugin = MagicMock()
mocked_plugin.status = PluginStatus.Disabled
mocked_import_menu = MagicMock()
plugin_manager = PluginManager()
plugin_manager.plugins = [mocked_plugin]
# WHEN: We run hook_import_menu()
plugin_manager.hook_import_menu(mocked_import_menu)
# THEN: The createMediaManagerItem() method should have been called
assert mocked_plugin.addImportMenuItem.call_count == 0, \
u'The addImportMenuItem() method should not have been called.'
def hook_import_menu_with_active_plugin_test(self):
"""
Test running the hook_import_menu() method with an active plugin
"""
# GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Active
mocked_plugin = MagicMock()
mocked_plugin.status = PluginStatus.Active
mocked_import_menu = MagicMock()
plugin_manager = PluginManager()
plugin_manager.plugins = [mocked_plugin]
# WHEN: We run hook_import_menu()
plugin_manager.hook_import_menu(mocked_import_menu)
# THEN: The addImportMenuItem() method should have been called
mocked_plugin.addImportMenuItem.assert_called_with(mocked_import_menu)
def hook_export_menu_with_disabled_plugin_test(self):
"""
Test running the hook_export_menu() method with a disabled plugin
"""
# GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Disabled
mocked_plugin = MagicMock()
mocked_plugin.status = PluginStatus.Disabled
mocked_export_menu = MagicMock()
plugin_manager = PluginManager()
plugin_manager.plugins = [mocked_plugin]
# WHEN: We run hook_export_menu()
plugin_manager.hook_export_menu(mocked_export_menu)
# THEN: The addExportMenuItem() method should have been called
assert mocked_plugin.addExportMenuItem.call_count == 0, \
u'The addExportMenuItem() method should not have been called.'
def hook_export_menu_with_active_plugin_test(self):
"""
Test running the hook_export_menu() method with an active plugin
"""
# GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Active
mocked_plugin = MagicMock()
mocked_plugin.status = PluginStatus.Active
mocked_export_menu = MagicMock()
plugin_manager = PluginManager()
plugin_manager.plugins = [mocked_plugin]
# WHEN: We run hook_export_menu()
plugin_manager.hook_export_menu(mocked_export_menu)
# THEN: The addExportMenuItem() method should have been called
mocked_plugin.addExportMenuItem.assert_called_with(mocked_export_menu)
def hook_tools_menu_with_disabled_plugin_test(self):
"""
Test running the hook_tools_menu() method with a disabled plugin
"""
# GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Disabled
mocked_plugin = MagicMock()
mocked_plugin.status = PluginStatus.Disabled
mocked_tools_menu = MagicMock()
plugin_manager = PluginManager()
plugin_manager.plugins = [mocked_plugin]
# WHEN: We run hook_tools_menu()
plugin_manager.hook_tools_menu(mocked_tools_menu)
# THEN: The addToolsMenuItem() method should have been called
assert mocked_plugin.addToolsMenuItem.call_count == 0, \
u'The addToolsMenuItem() method should not have been called.'
def hook_tools_menu_with_active_plugin_test(self):
"""
Test running the hook_tools_menu() method with an active plugin
"""
# GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Active
mocked_plugin = MagicMock()
mocked_plugin.status = PluginStatus.Active
mocked_tools_menu = MagicMock()
plugin_manager = PluginManager()
plugin_manager.plugins = [mocked_plugin]
# WHEN: We run hook_tools_menu()
plugin_manager.hook_tools_menu(mocked_tools_menu)
# THEN: The addToolsMenuItem() method should have been called
mocked_plugin.addToolsMenuItem.assert_called_with(mocked_tools_menu)
def initialise_plugins_with_disabled_plugin_test(self):
"""
Test running the initialise_plugins() method with a disabled plugin
"""
# GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Disabled
mocked_plugin = MagicMock()
mocked_plugin.status = PluginStatus.Disabled
mocked_plugin.isActive.return_value = False
plugin_manager = PluginManager()
plugin_manager.plugins = [mocked_plugin]
# WHEN: We run initialise_plugins()
plugin_manager.initialise_plugins()
# THEN: The isActive() method should have been called, and initialise() method should NOT have been called
mocked_plugin.isActive.assert_called_with()
assert mocked_plugin.initialise.call_count == 0, u'The initialise() method should not have been called.'
def initialise_plugins_with_active_plugin_test(self):
"""
Test running the initialise_plugins() method with an active plugin
"""
# GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Active
mocked_plugin = MagicMock()
mocked_plugin.status = PluginStatus.Active
mocked_plugin.isActive.return_value = True
plugin_manager = PluginManager()
plugin_manager.plugins = [mocked_plugin]
# WHEN: We run initialise_plugins()
plugin_manager.initialise_plugins()
# THEN: The isActive() and initialise() methods should have been called
mocked_plugin.isActive.assert_called_with()
mocked_plugin.initialise.assert_called_with()
def finalise_plugins_with_disabled_plugin_test(self):
"""
Test running the finalise_plugins() method with a disabled plugin
"""
# GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Disabled
mocked_plugin = MagicMock()
mocked_plugin.status = PluginStatus.Disabled
mocked_plugin.isActive.return_value = False
plugin_manager = PluginManager()
plugin_manager.plugins = [mocked_plugin]
# WHEN: We run finalise_plugins()
plugin_manager.finalise_plugins()
# THEN: The isActive() method should have been called, and initialise() method should NOT have been called
mocked_plugin.isActive.assert_called_with()
assert mocked_plugin.finalise.call_count == 0, u'The finalise() method should not have been called.'
def finalise_plugins_with_active_plugin_test(self):
"""
Test running the finalise_plugins() method with an active plugin
"""
# GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Active
mocked_plugin = MagicMock()
mocked_plugin.status = PluginStatus.Active
mocked_plugin.isActive.return_value = True
plugin_manager = PluginManager()
plugin_manager.plugins = [mocked_plugin]
# WHEN: We run finalise_plugins()
plugin_manager.finalise_plugins()
# THEN: The isActive() and finalise() methods should have been called
mocked_plugin.isActive.assert_called_with()
mocked_plugin.finalise.assert_called_with()
def get_plugin_by_name_does_not_exist_test(self):
"""
Test running the get_plugin_by_name() method to find a plugin that does not exist
"""
# GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Active
mocked_plugin = MagicMock()
mocked_plugin.name = 'Mocked Plugin'
plugin_manager = PluginManager()
plugin_manager.plugins = [mocked_plugin]
# WHEN: We run finalise_plugins()
result = plugin_manager.get_plugin_by_name('Missing Plugin')
# THEN: The isActive() and finalise() methods should have been called
self.assertIsNone(result, u'The result for get_plugin_by_name should be None')
def get_plugin_by_name_exists_test(self):
"""
Test running the get_plugin_by_name() method to find a plugin that exists
"""
# GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Active
mocked_plugin = MagicMock()
mocked_plugin.name = 'Mocked Plugin'
plugin_manager = PluginManager()
plugin_manager.plugins = [mocked_plugin]
# WHEN: We run finalise_plugins()
result = plugin_manager.get_plugin_by_name('Mocked Plugin')
# THEN: The isActive() and finalise() methods should have been called
self.assertEqual(result, mocked_plugin, u'The result for get_plugin_by_name should be the mocked plugin')
def new_service_created_with_disabled_plugin_test(self):
"""
Test running the new_service_created() method with a disabled plugin
"""
# GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Disabled
mocked_plugin = MagicMock()
mocked_plugin.status = PluginStatus.Disabled
mocked_plugin.isActive.return_value = False
plugin_manager = PluginManager()
plugin_manager.plugins = [mocked_plugin]
# WHEN: We run finalise_plugins()
plugin_manager.new_service_created()
# THEN: The isActive() method should have been called, and initialise() method should NOT have been called
mocked_plugin.isActive.assert_called_with()
assert mocked_plugin.new_service_created.call_count == 0,\
u'The new_service_created() method should not have been called.'
def new_service_created_with_active_plugin_test(self):
"""
Test running the new_service_created() method with an active plugin
"""
# GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Active
mocked_plugin = MagicMock()
mocked_plugin.status = PluginStatus.Active
mocked_plugin.isActive.return_value = True
plugin_manager = PluginManager()
plugin_manager.plugins = [mocked_plugin]
# WHEN: We run new_service_created()
plugin_manager.new_service_created()
# THEN: The isActive() and finalise() methods should have been called
mocked_plugin.isActive.assert_called_with()
mocked_plugin.new_service_created.assert_called_with()

View File

@ -1,13 +1,15 @@
"""
Package to test the openlp.core.lib package.
Package to test the openlp.core.lib package.
"""
import os
from unittest import TestCase
from mock import MagicMock
from openlp.core.lib import Registry
TESTPATH = os.path.abspath(os.path.join(os.path.dirname(__file__), u'..', u'..', u'resources'))
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), u'..', u'..', u'resources'))
class TestRegistry(TestCase):

View File

@ -0,0 +1,58 @@
"""
Package to test the openlp.core.lib.screenlist package.
"""
import copy
from unittest import TestCase
from mock import MagicMock
from PyQt4 import QtGui, QtCore
from openlp.core.lib import Registry, ScreenList
SCREEN = {
u'primary': False,
u'number': 1,
u'size': QtCore.QRect(0, 0, 1024, 768)
}
class TestScreenList(TestCase):
def setUp(self):
"""
Set up the components need for all tests.
"""
self.application = QtGui.QApplication.instance()
Registry.create()
self.application.setOrganizationName(u'OpenLP-tests')
self.application.setOrganizationDomain(u'openlp.org')
self.screens = ScreenList.create(self.application.desktop())
def tearDown(self):
"""
Delete QApplication.
"""
del self.screens
del self.application
def add_desktop_test(self):
"""
Test the ScreenList class' - screen_count_changed method to check if new monitors are detected by OpenLP.
"""
# GIVEN: The screen list.
old_screens = copy.deepcopy(self.screens.screen_list)
# Mock the attributes.
self.screens.desktop.primaryScreen = MagicMock(return_value=SCREEN[u'primary'])
self.screens.desktop.screenCount = MagicMock(return_value=SCREEN[u'number'] + 1)
self.screens.desktop.screenGeometry = MagicMock(return_value=SCREEN[u'size'])
# WHEN: Add a new screen.
self.screens.screen_count_changed(len(old_screens))
# THEN: The screen should have been added.
new_screens = self.screens.screen_list
assert len(old_screens) + 1 == len(new_screens), u'The new_screens list should be bigger.'
# THEN: The screens should be identical.
assert SCREEN == new_screens.pop(), u'The new screen should be identical to the screen defined above.'

View File

@ -3,11 +3,10 @@
"""
import os
import cPickle
from unittest import TestCase
from mock import MagicMock, patch
from mock import MagicMock
from openlp.core.lib import ServiceItem, Registry
from openlp.core.lib import ItemCapabilities, ServiceItem, Registry
VERSE = u'The Lord said to {r}Noah{/r}: \n'\
@ -19,7 +18,7 @@ VERSE = u'The Lord said to {r}Noah{/r}: \n'\
'r{/pk}{o}e{/o}{pp}n{/pp} of the Lord\n'
FOOTER = [u'Arky Arky (Unknown)', u'Public Domain', u'CCLI 123456']
TESTPATH = os.path.abspath(os.path.join(os.path.dirname(__file__), u'..', u'..', u'resources'))
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), u'..', u'..', u'resources'))
class TestServiceItem(TestCase):
@ -28,7 +27,7 @@ class TestServiceItem(TestCase):
"""
Set up the Registry
"""
registry = Registry.create()
Registry.create()
mocked_renderer = MagicMock()
mocked_renderer.format_slide.return_value = [VERSE]
Registry().register(u'renderer', mocked_renderer)
@ -40,7 +39,7 @@ class TestServiceItem(TestCase):
"""
# GIVEN: A new service item
# WHEN:A service item is created (without a plugin)
# WHEN: A service item is created (without a plugin)
service_item = ServiceItem(None)
# THEN: We should get back a valid service item
@ -79,7 +78,7 @@ class TestServiceItem(TestCase):
service_item.name = u'test'
# WHEN: adding image to a service item
test_image = os.path.join(TESTPATH, u'church.jpg')
test_image = os.path.join(TEST_PATH, u'church.jpg')
service_item.add_from_image(test_image, u'Image Title')
# THEN: We should get back a valid service item
@ -102,12 +101,12 @@ class TestServiceItem(TestCase):
service = service_item.get_service_repr(True)
# THEN: We should have two parts of the service.
assert len(service) == 2, u'A saved service has two parts'
assert service[u'header'][u'name'] == u'test' , u'A test plugin was returned'
assert service[u'data'][0][u'title'] == u'Image Title' , u'The first title name matches the request'
assert service[u'data'][0][u'path'] == test_image , u'The first image name matches'
assert len(service) == 2, u'A saved service should have two parts'
assert service[u'header'][u'name'] == u'test', u'A test plugin should have been returned'
assert service[u'data'][0][u'title'] == u'Image Title', u'"Image Title" should be returned as the title'
assert service[u'data'][0][u'path'] == test_image, u'The returned path should match the inputted path'
assert service[u'data'][0][u'title'] != service[u'data'][1][u'title'], \
u'The individual titles should not match'
u'The individual slide titles should not match'
assert service[u'data'][0][u'path'] == service[u'data'][1][u'path'], u'The file paths should match'
# WHEN validating a service item
@ -123,7 +122,7 @@ class TestServiceItem(TestCase):
service_item.validate_item([u'jpg'])
# THEN the service item should be valid
assert service_item.is_valid is False, u'The service item is not valid due to validation changes'
assert service_item.is_valid is False, u'The service item should not be valid due to validation changes'
def serviceitem_add_command_test(self):
"""
@ -134,26 +133,26 @@ class TestServiceItem(TestCase):
service_item.name = u'test'
# WHEN: adding image to a service item
test_file = os.path.join(TESTPATH, u'church.jpg')
service_item.add_from_command(TESTPATH, u'church.jpg', test_file)
test_file = os.path.join(TEST_PATH, u'church.jpg')
service_item.add_from_command(TEST_PATH, u'church.jpg', test_file)
# THEN: We should get back a valid service item
assert service_item.is_valid is True, u'The new service item should be valid'
assert len(service_item._display_frames) == 0, u'The service item has no display frames '
assert len(service_item._display_frames) == 0, u'The service item should have no display frames '
# THEN: We should have a page of output.
assert len(service_item._raw_frames) == 1, u'A valid rendered Service Item has one raw frame'
assert service_item.get_rendered_frame(0) == test_file, u'The image matches the input'
assert len(service_item._raw_frames) == 1, u'A valid rendered Service Item should have one raw frame'
assert service_item.get_rendered_frame(0) == test_file, u'The image should match the input'
# WHEN requesting a saved service item
service = service_item.get_service_repr(True)
# THEN: We should have two parts of the service.
assert len(service) == 2, u'A saved service has two parts'
assert service[u'header'][u'name'] == u'test' , u'A test plugin'
assert service[u'data'][0][u'title'] == u'church.jpg' , u'The first title name '
assert service[u'data'][0][u'path'] == TESTPATH , u'The first image name'
assert service[u'data'][0][u'image'] == test_file , u'The first image name'
assert len(service) == 2, u'The saved service should have two parts'
assert service[u'header'][u'name'] == u'test', u'A test plugin should be returned'
assert service[u'data'][0][u'title'] == u'church.jpg', u'The first title name should be "church,jpg"'
assert service[u'data'][0][u'path'] == TEST_PATH, u'The path should match the input path'
assert service[u'data'][0][u'image'] == test_file, u'The image should match the full path to image'
# WHEN validating a service item
service_item.validate_item([u'jpg'])
@ -165,7 +164,7 @@ class TestServiceItem(TestCase):
service_item.validate_item([u'png'])
# THEN the service item should not be valid
assert service_item.is_valid is False, u'The service item is not valid'
assert service_item.is_valid is False, u'The service item should not be valid'
def serviceitem_load_custom_from_service_test(self):
"""
@ -173,41 +172,103 @@ class TestServiceItem(TestCase):
"""
# GIVEN: A new service item and a mocked add icon function
service_item = ServiceItem(None)
mocked_add_icon = MagicMock()
service_item.add_icon = mocked_add_icon
service_item.add_icon = MagicMock()
# WHEN: adding a custom from a saved Service
line = self.convert_file_service_item(u'serviceitem_custom1.osd')
line = self.convert_file_service_item(u'serviceitem_custom_1.osd')
service_item.set_from_service(line)
# THEN: We should get back a valid service item
assert service_item.is_valid is True, u'The new service item should be valid'
assert len(service_item._display_frames) == 0, u'The service item has no display frames'
assert len(service_item.capabilities) == 5, u'There are 5 default custom item capabilities'
assert len(service_item._display_frames) == 0, u'The service item should have no display frames'
assert len(service_item.capabilities) == 5, u'There should be 5 default custom item capabilities'
service_item.render(True)
assert (service_item.get_display_title()) == u'Test Custom', u'The custom title should be correct'
assert service_item.get_display_title() == u'Test Custom', u'The title should be "Test Custom"'
assert service_item.get_frames()[0][u'text'] == VERSE[:-1], \
u'The returned text matches the input, except the last line feed'
assert service_item.get_rendered_frame(1) == VERSE.split(u'\n', 1)[0], u'The first line has been returned'
assert service_item.get_frame_title(0) == u'Slide 1', u'"Slide 1" has been returned as the title'
assert service_item.get_frame_title(1) == u'Slide 2', u'"Slide 2" has been returned as the title'
assert service_item.get_frame_title(2) == u'', u'Blank has been returned as the title of slide 3'
def serviceitem_load_image_from_service_test(self):
"""
Test the Service Item - adding an image from a saved service
"""
# GIVEN: A new service item and a mocked add icon function
service_item = ServiceItem(None)
mocked_add_icon = MagicMock()
service_item.add_icon = mocked_add_icon
image_name = u'image_1.jpg'
test_file = os.path.join(TEST_PATH, image_name)
frame_array = {u'path': test_file, u'title': image_name}
# WHEN: adding a custom from a saved Service
service_item = ServiceItem(None)
service_item.add_icon = MagicMock()
# WHEN: adding an image from a saved Service and mocked exists
line = self.convert_file_service_item(u'serviceitem_image_1.osd')
with patch('os.path.exists'):
service_item.set_from_service(line, TEST_PATH)
# THEN: We should get back a valid service item
assert service_item.is_valid is True, u'The new service item should be valid'
print service_item.get_rendered_frame(0)
assert service_item.get_rendered_frame(0) == test_file, u'The first frame should match the path to the image'
assert service_item.get_frames()[0] == frame_array, u'The return should match frame array1'
assert service_item.get_frame_path(0) == test_file, u'The frame path should match the full path to the image'
assert service_item.get_frame_title(0) == image_name, u'The frame title should match the image name'
assert service_item.get_display_title() == image_name, u'The display title should match the first image name'
assert service_item.is_image() is True, u'This service item is an Image'
assert service_item.is_capable(ItemCapabilities.CanMaintain) is True, u'This service item can be Maintained'
assert service_item.is_capable(ItemCapabilities.CanPreview) is True, u'This service item can be Previewed'
assert service_item.is_capable(ItemCapabilities.CanLoop) is True, u'This service item can be made to Loop'
assert service_item.is_capable(ItemCapabilities.CanAppend) is True, \
u'This service item can have new items added'
def serviceitem_load_image_from_local_service_test(self):
"""
Test the Service Item - adding an image from a saved local service
"""
# GIVEN: A new service item and a mocked add icon function
image_name1 = u'image_1.jpg'
image_name2 = u'image_2.jpg'
test_file1 = os.path.join(u'/home/openlp', image_name1)
test_file2 = os.path.join(u'/home/openlp', image_name2)
frame_array1 = {u'path': test_file1, u'title': image_name1}
frame_array2 = {u'path': test_file2, u'title': image_name2}
service_item = ServiceItem(None)
service_item.add_icon = MagicMock()
# WHEN: adding an image from a saved Service and mocked exists
line = self.convert_file_service_item(u'serviceitem_image_2.osd')
with patch('os.path.exists'):
service_item.set_from_service(line)
# THEN: We should get back a valid service item
assert service_item.is_valid is True, u'The new service item should be valid'
assert service_item.get_rendered_frame(0) == test_file1, u'The first frame should match the path to the image'
assert service_item.get_rendered_frame(1) == test_file2, u'The Second frame should match the path to the image'
assert service_item.get_frames()[0] == frame_array1, u'The return should match the frame array1'
assert service_item.get_frames()[1] == frame_array2, u'The return should match the frame array2'
assert service_item.get_frame_path(0) == test_file1, u'The frame path should match the full path to the image'
assert service_item.get_frame_path(1) == test_file2, u'The frame path should match the full path to the image'
assert service_item.get_frame_title(0) == image_name1, u'The 1st frame title should match the image name'
assert service_item.get_frame_title(1) == image_name2, u'The 2nd frame title should match the image name'
assert service_item.get_display_title().lower() == service_item.name, \
u'The plugin name should match the display title, as there are > 1 Images'
assert service_item.is_image() is True, u'This service item should be of an "image" type'
assert service_item.is_capable(ItemCapabilities.CanMaintain) is True, u'This service item can be Maintained'
assert service_item.is_capable(ItemCapabilities.CanPreview) is True, u'This service item can be Previewed'
assert service_item.is_capable(ItemCapabilities.CanLoop) is True, u'This service item can be made to Loop'
assert service_item.is_capable(ItemCapabilities.CanAppend) is True, \
u'This service item can have new items added'
def convert_file_service_item(self, name):
service_file = os.path.join(TESTPATH, name)
service_file = os.path.join(TEST_PATH, name)
try:
open_file = open(service_file, u'r')
items = cPickle.load(open_file)
first_line = items[0]
except:
except IOError:
first_line = u''
return first_line
return first_line

View File

@ -0,0 +1,93 @@
"""
Package to test the openlp.core.lib.settings package.
"""
import os
from unittest import TestCase
from tempfile import mkstemp
from openlp.core.lib import Settings
from PyQt4 import QtGui
class TestSettings(TestCase):
def setUp(self):
"""
Create the UI
"""
fd, self.ini_file = mkstemp(u'.ini')
Settings().set_filename(self.ini_file)
self.application = QtGui.QApplication.instance()
def tearDown(self):
"""
Delete all the C++ objects at the end so that we don't have a segfault
"""
del self.application
os.unlink(self.ini_file)
os.unlink(Settings().fileName())
def settings_basic_test(self):
"""
Test the Settings creation and its default usage
"""
# GIVEN: A new Settings setup
# WHEN reading a setting for the first time
default_value = Settings().value(u'general/has run wizard')
# THEN the default value is returned
assert default_value is False, u'The default value should be False'
# WHEN a new value is saved into config
Settings().setValue(u'general/has run wizard', True)
# THEN the new value is returned when re-read
assert Settings().value(u'general/has run wizard') is True, u'The saved value should have been returned'
def settings_override_test(self):
"""
Test the Settings creation and its override usage
"""
# GIVEN: an override for the settings
screen_settings = {
u'test/extend': u'very wide',
}
Settings().extend_default_settings(screen_settings)
# WHEN reading a setting for the first time
extend = Settings().value(u'test/extend')
# THEN the default value is returned
assert extend == u'very wide', u'The default value of "very wide" should be returned'
# WHEN a new value is saved into config
Settings().setValue(u'test/extend', u'very short')
# THEN the new value is returned when re-read
assert Settings().value(u'test/extend') == u'very short', u'The saved value should be returned'
def settings_override_with_group_test(self):
"""
Test the Settings creation and its override usage - with groups
"""
# GIVEN: an override for the settings
screen_settings = {
u'test/extend': u'very wide',
}
Settings.extend_default_settings(screen_settings)
# WHEN reading a setting for the first time
settings = Settings()
settings.beginGroup(u'test')
extend = settings.value(u'extend')
# THEN the default value is returned
assert extend == u'very wide', u'The default value defined should be returned'
# WHEN a new value is saved into config
Settings().setValue(u'test/extend', u'very short')
# THEN the new value is returned when re-read
assert Settings().value(u'test/extend') == u'very short', u'The saved value should be returned'

View File

@ -0,0 +1,22 @@
"""
Package to test the openlp.core.lib.uistrings package.
"""
from unittest import TestCase
from openlp.core.lib import UiStrings
class TestUiStrings(TestCase):
def check_same_instance_test(self):
"""
Test the UiStrings class - we always should have only one instance of the UiStrings class.
"""
# WHEN: Create two instances of the UiStrings class.
first_instance = UiStrings()
second_instance = UiStrings()
# THEN: Check if the instances are the same.
assert first_instance is second_instance, "They should be the same instance!"

View File

@ -0,0 +1,124 @@
"""
Package to test the openlp.core.utils.actions package.
"""
import os
from tempfile import mkstemp
from unittest import TestCase
from PyQt4 import QtGui, QtCore
from openlp.core.lib import Settings
from openlp.core.utils import ActionList
class TestActionList(TestCase):
def setUp(self):
"""
Prepare the tests
"""
self.action_list = ActionList.get_instance()
self.settings = Settings()
fd, self.ini_file = mkstemp(u'.ini')
self.settings.set_filename(self.ini_file)
self.settings.beginGroup(u'shortcuts')
def tearDown(self):
"""
Clean up
"""
self.settings.endGroup()
os.unlink(self.ini_file)
def test_add_action_same_parent(self):
"""
ActionList test - Tests the add_action method. The actions have the same parent, the same shortcuts and both
have the QtCore.Qt.WindowShortcut shortcut context set.
"""
# GIVEN: Two actions with the same shortcuts.
parent = QtCore.QObject()
action1 = QtGui.QAction(parent)
action1.setObjectName(u'action1')
action_with_same_shortcuts1 = QtGui.QAction(parent)
action_with_same_shortcuts1.setObjectName(u'action_with_same_shortcuts1')
# Add default shortcuts to Settings class.
default_shortcuts = {
u'shortcuts/action1': [QtGui.QKeySequence(u'a'), QtGui.QKeySequence(u'b')],
u'shortcuts/action_with_same_shortcuts1': [QtGui.QKeySequence(u'b'), QtGui.QKeySequence(u'a')]
}
Settings.extend_default_settings(default_shortcuts)
# WHEN: Add the two actions to the action list.
self.action_list.add_action(action1, u'example_category')
self.action_list.add_action(action_with_same_shortcuts1, u'example_category')
# Remove the actions again.
self.action_list.remove_action(action1, u'example_category')
self.action_list.remove_action(action_with_same_shortcuts1, u'example_category')
# THEN: As both actions have the same shortcuts, they should be removed from one action.
assert len(action1.shortcuts()) == 2, u'The action should have two shortcut assigned.'
assert len(action_with_same_shortcuts1.shortcuts()) == 0, u'The action should not have a shortcut assigned.'
def test_add_action_different_parent(self):
"""
ActionList test - Tests the add_action method. The actions have the different parent, the same shortcuts and
both have the QtCore.Qt.WindowShortcut shortcut context set.
"""
# GIVEN: Two actions with the same shortcuts.
parent = QtCore.QObject()
action2 = QtGui.QAction(parent)
action2.setObjectName(u'action2')
second_parent = QtCore.QObject()
action_with_same_shortcuts2 = QtGui.QAction(second_parent)
action_with_same_shortcuts2.setObjectName(u'action_with_same_shortcuts2')
# Add default shortcuts to Settings class.
default_shortcuts = {
u'shortcuts/action2': [QtGui.QKeySequence(u'c'), QtGui.QKeySequence(u'd')],
u'shortcuts/action_with_same_shortcuts2': [QtGui.QKeySequence(u'd'), QtGui.QKeySequence(u'c')]
}
Settings.extend_default_settings(default_shortcuts)
# WHEN: Add the two actions to the action list.
self.action_list.add_action(action2, u'example_category')
self.action_list.add_action(action_with_same_shortcuts2, u'example_category')
# Remove the actions again.
self.action_list.remove_action(action2, u'example_category')
self.action_list.remove_action(action_with_same_shortcuts2, u'example_category')
# THEN: As both actions have the same shortcuts, they should be removed from one action.
assert len(action2.shortcuts()) == 2, u'The action should have two shortcut assigned.'
assert len(action_with_same_shortcuts2.shortcuts()) == 0, u'The action should not have a shortcut assigned.'
def test_add_action_different_context(self):
"""
ActionList test - Tests the add_action method. The actions have the different parent, the same shortcuts and
both have the QtCore.Qt.WidgetShortcut shortcut context set.
"""
# GIVEN: Two actions with the same shortcuts.
parent = QtCore.QObject()
action3 = QtGui.QAction(parent)
action3.setObjectName(u'action3')
action3.setShortcutContext(QtCore.Qt.WidgetShortcut)
second_parent = QtCore.QObject()
action_with_same_shortcuts3 = QtGui.QAction(second_parent)
action_with_same_shortcuts3.setObjectName(u'action_with_same_shortcuts3')
action_with_same_shortcuts3.setShortcutContext(QtCore.Qt.WidgetShortcut)
# Add default shortcuts to Settings class.
default_shortcuts = {
u'shortcuts/action3': [QtGui.QKeySequence(u'e'), QtGui.QKeySequence(u'f')],
u'shortcuts/action_with_same_shortcuts3': [QtGui.QKeySequence(u'e'), QtGui.QKeySequence(u'f')]
}
Settings.extend_default_settings(default_shortcuts)
# WHEN: Add the two actions to the action list.
self.action_list.add_action(action3, u'example_category2')
self.action_list.add_action(action_with_same_shortcuts3, u'example_category2')
# Remove the actions again.
self.action_list.remove_action(action3, u'example_category2')
self.action_list.remove_action(action_with_same_shortcuts3, u'example_category2')
# THEN: Both action should keep their shortcuts.
assert len(action3.shortcuts()) == 2, u'The action should have two shortcut assigned.'
assert len(action_with_same_shortcuts3.shortcuts()) == 2, u'The action should have two shortcuts assigned.'

View File

@ -1,12 +1,17 @@
"""
Functional tests to test the AppLocation class and related methods.
"""
import copy
from unittest import TestCase
from mock import patch
from openlp.core.utils import AppLocation
FILE_LIST = [u'file1', u'file2', u'file3.txt', u'file4.txt', u'file5.mp3', u'file6.mp3']
class TestAppLocation(TestCase):
"""
A test suite to test out various methods around the AppLocation class.
@ -15,10 +20,10 @@ class TestAppLocation(TestCase):
"""
Test the AppLocation.get_data_path() method
"""
with patch(u'openlp.core.utils.Settings') as mocked_class, \
patch(u'openlp.core.utils.AppLocation.get_directory') as mocked_get_directory, \
patch(u'openlp.core.utils.check_directory_exists') as mocked_check_directory_exists, \
patch(u'openlp.core.utils.os') as mocked_os:
with patch(u'openlp.core.utils.applocation.Settings') as mocked_class, \
patch(u'openlp.core.utils.AppLocation.get_directory') as mocked_get_directory, \
patch(u'openlp.core.utils.applocation.check_directory_exists') as mocked_check_directory_exists, \
patch(u'openlp.core.utils.applocation.os') as mocked_os:
# GIVEN: A mocked out Settings class and a mocked out AppLocation.get_directory()
mocked_settings = mocked_class.return_value
mocked_settings.contains.return_value = False
@ -37,8 +42,8 @@ class TestAppLocation(TestCase):
"""
Test the AppLocation.get_data_path() method when a custom location is set in the settings
"""
with patch(u'openlp.core.utils.Settings') as mocked_class,\
patch(u'openlp.core.utils.os') as mocked_os:
with patch(u'openlp.core.utils.applocation.Settings') as mocked_class,\
patch(u'openlp.core.utils.applocation.os') as mocked_os:
# GIVEN: A mocked out Settings class which returns a custom data location
mocked_settings = mocked_class.return_value
mocked_settings.contains.return_value = True
@ -51,12 +56,47 @@ class TestAppLocation(TestCase):
mocked_settings.value.assert_called_with(u'advanced/data path')
assert data_path == u'custom/dir', u'Result should be "custom/dir"'
def get_files_no_section_no_extension_test(self):
"""
Test the AppLocation.get_files() method with no parameters passed.
"""
with patch(u'openlp.core.utils.AppLocation.get_data_path') as mocked_get_data_path, \
patch(u'openlp.core.utils.applocation.os.listdir') as mocked_listdir:
# GIVEN: Our mocked modules/methods.
mocked_get_data_path.return_value = u'test/dir'
mocked_listdir.return_value = copy.deepcopy(FILE_LIST)
# When: Get the list of files.
result = AppLocation.get_files()
# Then: check if the file lists are identical.
assert result == FILE_LIST, u'The file lists should be identical.'
def get_files_test(self):
"""
Test the AppLocation.get_files() method with all parameters passed.
"""
with patch(u'openlp.core.utils.AppLocation.get_data_path') as mocked_get_data_path, \
patch(u'openlp.core.utils.applocation.os.listdir') as mocked_listdir:
# GIVEN: Our mocked modules/methods.
mocked_get_data_path.return_value = u'test/dir'
mocked_listdir.return_value = copy.deepcopy(FILE_LIST)
# When: Get the list of files.
result = AppLocation.get_files(u'section', u'.mp3')
# Then: Check if the section parameter was used correctly.
mocked_listdir.assert_called_with(u'test/dir/section')
# Then: check if the file lists are identical.
assert result == [u'file5.mp3', u'file6.mp3'], u'The file lists should be identical.'
def get_section_data_path_test(self):
"""
Test the AppLocation.get_section_data_path() method
"""
with patch(u'openlp.core.utils.AppLocation.get_data_path') as mocked_get_data_path, \
patch(u'openlp.core.utils.check_directory_exists') as mocked_check_directory_exists:
patch(u'openlp.core.utils.applocation.check_directory_exists') as mocked_check_directory_exists:
# GIVEN: A mocked out AppLocation.get_data_path()
mocked_get_data_path.return_value = u'test/dir'
mocked_check_directory_exists.return_value = True
@ -70,7 +110,7 @@ class TestAppLocation(TestCase):
"""
Test the AppLocation.get_directory() method for AppLocation.AppDir
"""
with patch(u'openlp.core.utils._get_frozen_path') as mocked_get_frozen_path:
with patch(u'openlp.core.utils.applocation._get_frozen_path') as mocked_get_frozen_path:
mocked_get_frozen_path.return_value = u'app/dir'
# WHEN: We call AppLocation.get_directory
directory = AppLocation.get_directory(AppLocation.AppDir)
@ -81,10 +121,10 @@ class TestAppLocation(TestCase):
"""
Test the AppLocation.get_directory() method for AppLocation.PluginsDir
"""
with patch(u'openlp.core.utils._get_frozen_path') as mocked_get_frozen_path, \
patch(u'openlp.core.utils.os.path.abspath') as mocked_abspath, \
patch(u'openlp.core.utils.os.path.split') as mocked_split, \
patch(u'openlp.core.utils.sys') as mocked_sys:
with patch(u'openlp.core.utils.applocation._get_frozen_path') as mocked_get_frozen_path, \
patch(u'openlp.core.utils.applocation.os.path.abspath') as mocked_abspath, \
patch(u'openlp.core.utils.applocation.os.path.split') as mocked_split, \
patch(u'openlp.core.utils.applocation.sys') as mocked_sys:
mocked_abspath.return_value = u'plugins/dir'
mocked_split.return_value = [u'openlp']
mocked_get_frozen_path.return_value = u'plugins/dir'

View File

@ -0,0 +1,13 @@
import sip
sip.setapi(u'QDate', 2)
sip.setapi(u'QDateTime', 2)
sip.setapi(u'QString', 2)
sip.setapi(u'QTextStream', 2)
sip.setapi(u'QTime', 2)
sip.setapi(u'QUrl', 2)
sip.setapi(u'QVariant', 2)
from PyQt4 import QtGui
# Only one QApplication can be created. Use QtGui.QApplication.instance() when you need to "create" a QApplication.
application = QtGui.QApplication([])

View File

@ -0,0 +1,61 @@
"""
Package to test the openlp.core.lib.pluginmanager package.
"""
import os
import sys
from tempfile import mkstemp
from unittest import TestCase
from mock import MagicMock
from PyQt4 import QtGui
from openlp.core.lib.pluginmanager import PluginManager
from openlp.core.lib import Registry, Settings
class TestPluginManager(TestCase):
"""
Test the PluginManager class
"""
def setUp(self):
"""
Some pre-test setup required.
"""
fd, self.ini_file = mkstemp(u'.ini')
Settings().set_filename(self.ini_file)
Registry.create()
Registry().register(u'service_list', MagicMock())
self.app = QtGui.QApplication.instance()
self.main_window = QtGui.QMainWindow()
Registry().register(u'main_window', self.main_window)
def tearDown(self):
os.unlink(self.ini_file)
del self.app
del self.main_window
def find_plugins_test(self):
"""
Test the find_plugins() method to ensure it imports the correct plugins.
"""
# GIVEN: A plugin manager
plugin_manager = PluginManager()
# WHEN: We mock out sys.platform to make it return "darwin" and then find the plugins
old_platform = sys.platform
sys.platform = u'darwin'
plugin_manager.find_plugins()
sys.platform = old_platform
# THEN: We should find the "Songs", "Bibles", etc in the plugins list
plugin_names = [plugin.name for plugin in plugin_manager.plugins]
assert u'songs' in plugin_names, u'There should be a "songs" plugin.'
assert u'bibles' in plugin_names, u'There should be a "bibles" plugin.'
assert u'presentations' not in plugin_names, u'There should NOT be a "presentations" plugin.'
assert u'images' in plugin_names, u'There should be a "images" plugin.'
assert u'media' in plugin_names, u'There should be a "media" plugin.'
assert u'custom' in plugin_names, u'There should be a "custom" plugin.'
assert u'songusage' in plugin_names, u'There should be a "songusage" plugin.'
assert u'alerts' in plugin_names, u'There should be a "alerts" plugin.'
assert u'remotes' in plugin_names, u'There should be a "remotes" plugin.'

View File

@ -0,0 +1,83 @@
"""
Package to test the openlp.core.ui package.
"""
from unittest import TestCase
from mock import MagicMock, patch
from openlp.core.lib import Registry
from openlp.core.ui import filerenameform
from PyQt4 import QtGui, QtTest
class TestStartFileRenameForm(TestCase):
def setUp(self):
"""
Create the UI
"""
Registry.create()
self.app = QtGui.QApplication.instance()
self.main_window = QtGui.QMainWindow()
Registry().register(u'main_window', self.main_window)
self.form = filerenameform.FileRenameForm()
def tearDown(self):
"""
Delete all the C++ objects at the end so that we don't have a segfault
"""
del self.form
del self.main_window
del self.app
def window_title_test(self):
"""
Test the windowTitle of the FileRenameDialog
"""
# GIVEN: A mocked QDialog.exec_() method
with patch(u'PyQt4.QtGui.QDialog.exec_') as mocked_exec:
# WHEN: The form is executed with no args
self.form.exec_()
# THEN: the window title is set correctly
self.assertEqual(self.form.windowTitle(), u'File Rename', u'The window title should be "File Rename"')
# WHEN: The form is executed with False arg
self.form.exec_(False)
# THEN: the window title is set correctly
self.assertEqual(self.form.windowTitle(), u'File Rename', u'The window title should be "File Rename"')
# WHEN: The form is executed with True arg
self.form.exec_(True)
# THEN: the window title is set correctly
self.assertEqual(self.form.windowTitle(), u'File Copy', u'The window title should be "File Copy"')
def line_edit_focus_test(self):
"""
Regression test for bug1067251
Test that the fileNameEdit setFocus has called with True when executed
"""
# GIVEN: A mocked QDialog.exec_() method and mocked fileNameEdit.setFocus() method.
with patch(u'PyQt4.QtGui.QDialog.exec_') as mocked_exec:
mocked_set_focus = MagicMock()
self.form.fileNameEdit.setFocus = mocked_set_focus
# WHEN: The form is executed
self.form.exec_()
# THEN: the setFocus method of the fileNameEdit has been called with True
mocked_set_focus.assert_called_with()
def file_name_validation_test(self):
"""
Test the fileNameEdit validation
"""
# GIVEN: QLineEdit with a validator set with illegal file name characters.
# WHEN: 'Typing' a string containing invalid file characters.
QtTest.QTest.keyClicks(self.form.fileNameEdit, u'I/n\\v?a*l|i<d> \F[i\l]e" :N+a%me')
# THEN: The text in the QLineEdit should be the same as the input string with the invalid characters filtered
# out.
self.assertEqual(self.form.fileNameEdit.text(), u'Invalid File Name')

View File

@ -0,0 +1,45 @@
"""
Package to test the openlp.core.lib package.
"""
from unittest import TestCase
from mock import MagicMock, patch
from PyQt4 import QtGui
from openlp.core.lib import Registry, ScreenList
from openlp.core.ui.mainwindow import MainWindow
class TestServiceManager(TestCase):
def setUp(self):
"""
Create the UI
"""
Registry.create()
self.app = QtGui.QApplication.instance()
ScreenList.create(self.app.desktop())
Registry().register(u'application', MagicMock())
#with patch(u'openlp.core.lib.PluginManager'):
# self.main_window = MainWindow()
#self.service_manager = Registry().get(u'service_manager')
def tearDown(self):
"""
Delete all the C++ objects at the end so that we don't have a segfault
"""
#del self.main_window
del self.app
def basic_service_manager_test(self):
"""
Test the Service Manager display functionality
"""
# GIVEN: A New Service Manager instance
# WHEN I have an empty display
# THEN the count of items should be zero
#self.assertEqual(self.service_manager.service_manager_list.topLevelItemCount(), 0,
# u'The service manager list should be empty ')
pass

View File

@ -2,11 +2,13 @@
Package to test the openlp.core.ui package.
"""
from unittest import TestCase
from mock import patch
from PyQt4 import QtCore, QtGui, QtTest
from openlp.core.lib import Registry
from openlp.core.ui import servicenoteform
from PyQt4 import QtCore, QtGui, QtTest
class TestStartNoteDialog(TestCase):
@ -15,7 +17,7 @@ class TestStartNoteDialog(TestCase):
Create the UI
"""
Registry.create()
self.app = QtGui.QApplication([])
self.app = QtGui.QApplication.instance()
self.main_window = QtGui.QMainWindow()
Registry().register(u'main_window', self.main_window)
self.form = servicenoteform.ServiceNoteForm()
@ -64,4 +66,5 @@ class TestStartNoteDialog(TestCase):
QtTest.QTest.mouseClick(okWidget, QtCore.Qt.LeftButton)
# THEN the following text is returned
self.assertEqual(self.form.text_edit.toPlainText(), text, u'The new text should be returned')
self.assertEqual(self.form.text_edit.toPlainText(), text, u'The new text should be returned')

View File

@ -2,11 +2,13 @@
Package to test the openlp.core.ui package.
"""
from unittest import TestCase
from mock import MagicMock, patch
from PyQt4 import QtCore, QtGui, QtTest
from openlp.core.lib import Registry
from openlp.core.ui import starttimeform
from PyQt4 import QtCore, QtGui, QtTest
class TestStartTimeDialog(TestCase):
@ -15,7 +17,7 @@ class TestStartTimeDialog(TestCase):
Create the UI
"""
Registry.create()
self.app = QtGui.QApplication([])
self.app = QtGui.QApplication.instance()
self.main_window = QtGui.QMainWindow()
Registry().register(u'main_window', self.main_window)
self.form = starttimeform.StartTimeForm()
@ -91,4 +93,4 @@ class TestStartTimeDialog(TestCase):
self.assertEqual(self.form.hourSpinBox.value(), 0)
self.assertEqual(self.form.minuteSpinBox.value(), 2)
self.assertEqual(self.form.secondSpinBox.value(), 3)
self.assertEqual(self.form.item[u'service_item'].start_time, 123, u'The start time should have changed')
self.assertEqual(self.form.item[u'service_item'].start_time, 123, u'The start time should have changed')

View File

@ -74,6 +74,6 @@ V:/plugins/plugin_images.png
p32
ssg15
(lp33
VIMG_7453.JPG
Vimage_1.jpg
p34
assa.

View File

@ -0,0 +1,101 @@
(lp1
(dp2
Vserviceitem
p3
(dp4
Vheader
p5
(dp6
Vxml_version
p7
NsVauto_play_slides_loop
p8
I00
sVauto_play_slides_once
p9
I00
sVwill_auto_start
p10
I00
sVtitle
p11
VImages
p12
sVcapabilities
p13
(lp14
I3
aI1
aI5
aI6
asVtheme
p15
I-1
sVbackground_audio
p16
(lp17
sVicon
p18
V:/plugins/plugin_images.png
p19
sVtype
p20
I2
sVstart_time
p21
I0
sVfrom_plugin
p22
I00
sVmedia_length
p23
I0
sVdata
p24
V
sVtimed_slide_interval
p25
I0
sVaudit
p26
V
sVsearch
p27
V
sVname
p28
Vimages
p29
sVfooter
p30
(lp31
sVnotes
p32
V
sVplugin
p33
g29
sVtheme_overwritten
p34
I00
sVend_time
p35
I0
ssg24
(lp36
(dp37
Vpath
p38
V/home/openlp/image_1.jpg
p39
sg11
Vimage_1.jpg
p40
sa(dp41
g38
V/home/openlp/image_2.jpg
p42
sg11
Vimage_2.jpg
p43
sassa.