Merge with trunk, resolve conflicts

This commit is contained in:
Stevan Pettit 2012-06-09 19:14:28 -04:00
commit 245c0acd89
30 changed files with 1356 additions and 1103 deletions

View File

@ -268,7 +268,7 @@ class Receiver(object):
<<ACTION>>
)``
"""
eventreceiver = EventReceiver()
__eventreceiver__ = EventReceiver()
@staticmethod
def send_message(event, msg=None):
@ -281,11 +281,11 @@ class Receiver(object):
``msg``
Defaults to *None*. The message to send with the event.
"""
Receiver.eventreceiver.send_message(event, msg)
Receiver.__eventreceiver__.send_message(event, msg)
@staticmethod
def get_receiver():
"""
Get the global ``eventreceiver`` instance.
Get the global ``__eventreceiver__`` instance.
"""
return Receiver.eventreceiver
return Receiver.__eventreceiver__

View File

@ -41,13 +41,13 @@ class PluginManager(object):
and executes all the hooks, as and when necessary.
"""
log.info(u'Plugin manager loaded')
__instance__ = None
@staticmethod
def get_instance():
"""
Obtain a single instance of class.
"""
return PluginManager.instance
return PluginManager.__instance__
def __init__(self, plugin_dir):
"""
@ -58,7 +58,7 @@ class PluginManager(object):
The directory to search for plugins.
"""
log.info(u'Plugin manager Initialising')
PluginManager.instance = self
PluginManager.__instance__ = self
if not plugin_dir in sys.path:
log.debug(u'Inserting %s into sys.path', plugin_dir)
sys.path.insert(0, plugin_dir)

View File

@ -55,29 +55,32 @@ class Renderer(object):
"""
log.info(u'Renderer Loaded')
def __init__(self, imageManager, themeManager):
def __init__(self, image_manager, theme_manager):
"""
Initialise the renderer.
``imageManager``
A imageManager instance which takes care of e. g. caching and resizing
images.
``image_manager``
A image_manager instance which takes care of e. g. caching and
resizing images.
``themeManager``
The themeManager instance, used to get the current theme details.
``theme_manager``
The theme_manager instance, used to get the current theme details.
"""
log.debug(u'Initialisation started')
self.themeManager = themeManager
self.imageManager = imageManager
self.theme_manager = theme_manager
self.image_manager = image_manager
self.screens = ScreenList()
self.service_theme = u''
self.theme_level = u''
self.override_background = None
self.theme_data = None
self.bg_frame = None
self.theme_level = ThemeLevel.Global
self.global_theme_name = u''
self.service_theme_name = u''
self.item_theme_name = u''
self.force_page = False
self.display = MainDisplay(None, self.imageManager, False, self)
self.display = MainDisplay(None, self.image_manager, False, self)
self.display.setup()
self._theme_dimensions = {}
self._calculate_default()
QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'theme_update_global'), self.set_global_theme)
def update_display(self):
"""
@ -87,100 +90,132 @@ class Renderer(object):
self._calculate_default()
if self.display:
self.display.close()
self.display = MainDisplay(None, self.imageManager, False, self)
self.display = MainDisplay(None, self.image_manager, False, self)
self.display.setup()
self.bg_frame = None
self.theme_data = None
self._theme_dimensions = {}
def set_global_theme(self, global_theme, theme_level=ThemeLevel.Global):
def update_theme(self, theme_name, old_theme_name=None, only_delete=False):
"""
Set the global-level theme and the theme level.
This method updates the theme in ``_theme_dimensions`` when a theme
has been edited or renamed.
``global_theme``
The global-level theme to be set.
``theme_name``
The current theme name.
``old_theme_name``
The old theme name. Has only to be passed, when the theme has been
renamed. Defaults to *None*.
``only_delete``
Only remove the given ``theme_name`` from the ``_theme_dimensions``
list. This can be used when a theme is permanently deleted.
"""
if old_theme_name is not None and \
old_theme_name in self._theme_dimensions:
del self._theme_dimensions[old_theme_name]
if theme_name in self._theme_dimensions:
del self._theme_dimensions[theme_name]
if not only_delete:
self._set_theme(theme_name)
def _set_theme(self, theme_name):
"""
Helper method to save theme names and theme data.
``theme_name``
The theme name.
"""
if theme_name not in self._theme_dimensions:
theme_data = self.theme_manager.getThemeData(theme_name)
main_rect = self.get_main_rectangle(theme_data)
footer_rect = self.get_footer_rectangle(theme_data)
self._theme_dimensions[theme_name] = \
[theme_data, main_rect, footer_rect]
else:
theme_data, main_rect, footer_rect = \
self._theme_dimensions[theme_name]
# if No file do not update cache
if theme_data.background_filename:
self.image_manager.addImage(theme_data.theme_name,
theme_data.background_filename, u'theme',
QtGui.QColor(theme_data.background_border_color))
def pre_render(self, override_theme_data=None):
"""
Set up the theme to be used before rendering an item.
``override_theme_data``
The theme data should be passed, when we want to use our own theme
data, regardless of the theme level. This should for example be used
in the theme manager. **Note**, this is **not** to be mixed up with
the ``set_item_theme`` method.
"""
# Just assume we use the global theme.
theme_to_use = self.global_theme_name
# The theme level is either set to Service or Item. Use the service
# theme if one is set. We also have to use the service theme, even when
# the theme level is set to Item, because the item does not necessarily
# have to have a theme.
if self.theme_level != ThemeLevel.Global:
# When the theme level is at Service and we actually have a service
# theme then use it.
if self.service_theme_name:
theme_to_use = self.service_theme_name
# If we have Item level and have an item theme then use it.
if self.theme_level == ThemeLevel.Song and self.item_theme_name:
theme_to_use = self.item_theme_name
if override_theme_data is None:
if theme_to_use not in self._theme_dimensions:
self._set_theme(theme_to_use)
theme_data, main_rect, footer_rect = \
self._theme_dimensions[theme_to_use]
else:
# Ignore everything and use own theme data.
theme_data = override_theme_data
main_rect = self.get_main_rectangle(override_theme_data)
footer_rect = self.get_footer_rectangle(override_theme_data)
self._set_text_rectangle(theme_data, main_rect, footer_rect)
return theme_data, self._rect, self._rect_footer
def set_theme_level(self, theme_level):
"""
Sets the theme level.
``theme_level``
Defaults to ``ThemeLevel.Global``. The theme level, can be
``ThemeLevel.Global``, ``ThemeLevel.Service`` or
``ThemeLevel.Song``.
The theme level to be used.
"""
self.global_theme = global_theme
self.theme_level = theme_level
self.global_theme_data = \
self.themeManager.getThemeData(self.global_theme)
self.theme_data = None
self._cache_background_image(self.global_theme_data)
def set_service_theme(self, service_theme):
def set_global_theme(self, global_theme_name):
"""
Set the global-level theme name.
``global_theme_name``
The global-level theme's name.
"""
self._set_theme(global_theme_name)
self.global_theme_name = global_theme_name
def set_service_theme(self, service_theme_name):
"""
Set the service-level theme.
``service_theme``
The service-level theme to be set.
``service_theme_name``
The service level theme's name.
"""
self.service_theme = service_theme
self.theme_data = None
self._cache_background_image(self.themeManager.getThemeData
(service_theme))
self._set_theme(service_theme_name)
self.service_theme_name = service_theme_name
def _cache_background_image(self, temp_theme):
def set_item_theme(self, item_theme_name):
"""
Adds a background image to the image cache if necessary.
Set the item-level theme. **Note**, this has to be done for each item we
are rendering.
``temp_theme``
The theme object containing the theme data.
``item_theme_name``
The item theme's name.
"""
# if No file do not update cache
if temp_theme.background_filename:
self.imageManager.addImage(temp_theme.theme_name,
temp_theme.background_filename, u'theme',
QtGui.QColor(temp_theme.background_border_color))
def set_override_theme(self, override_theme, override_levels=False):
"""
Set the appropriate theme depending on the theme level.
Called by the service item when building a display frame
``override_theme``
The name of the song-level theme. None means the service
item wants to use the given value.
``override_levels``
Used to force the theme data passed in to be used.
"""
log.debug(u'set override theme to %s', override_theme)
theme_level = self.theme_level
if override_levels:
theme_level = ThemeLevel.Song
if theme_level == ThemeLevel.Global:
theme = self.global_theme
elif theme_level == ThemeLevel.Service:
if self.service_theme == u'':
theme = self.global_theme
else:
theme = self.service_theme
else:
# Images have a theme of -1
if override_theme and override_theme != -1:
theme = override_theme
elif theme_level == ThemeLevel.Song or \
theme_level == ThemeLevel.Service:
if self.service_theme == u'':
theme = self.global_theme
else:
theme = self.service_theme
else:
theme = self.global_theme
log.debug(u'theme is now %s', theme)
# Force the theme to be the one passed in.
if override_levels:
self.theme_data = override_theme
else:
self.theme_data = self.themeManager.getThemeData(theme)
self._calculate_default()
self._build_text_rectangle(self.theme_data)
self._cache_background_image(self.theme_data)
return self._rect, self._rect_footer
self._set_theme(item_theme_name)
self.item_theme_name = item_theme_name
def generate_preview(self, theme_data, force_page=False):
"""
@ -195,27 +230,31 @@ class Renderer(object):
log.debug(u'generate preview')
# save value for use in format_slide
self.force_page = force_page
# set the default image size for previews
self._calculate_default()
# build a service item to generate preview
serviceItem = ServiceItem()
serviceItem.theme = theme_data
if self.force_page:
# make big page for theme edit dialog to get line count
serviceItem.add_from_text(u'', VERSE_FOR_LINE_COUNT)
else:
self.imageManager.deleteImage(theme_data.theme_name)
self.image_manager.deleteImage(theme_data.theme_name)
serviceItem.add_from_text(u'', VERSE)
serviceItem.renderer = self
serviceItem.raw_footer = FOOTER
# if No file do not update cache
if theme_data.background_filename:
self.image_manager.addImage(theme_data.theme_name,
theme_data.background_filename, u'theme',
QtGui.QColor(theme_data.background_border_color))
theme_data, main, footer = self.pre_render(theme_data)
serviceItem.themedata = theme_data
serviceItem.main = main
serviceItem.footer = footer
serviceItem.render(True)
if not self.force_page:
self.display.buildHtml(serviceItem)
raw_html = serviceItem.get_rendered_frame(0)
self.display.text(raw_html)
preview = self.display.preview()
# Reset the real screen size for subsequent render requests
self._calculate_default()
return preview
self.force_page = False
@ -264,7 +303,7 @@ class Renderer(object):
try:
text_to_render, text = \
text.split(u'\n[---]\n', 1)
except:
except ValueError:
text_to_render = text.split(u'\n[---]\n')[0]
text = u''
text_to_render, raw_tags, html_tags = \
@ -315,52 +354,41 @@ class Renderer(object):
# 90% is start of footer
self.footer_start = int(self.height * 0.90)
def _build_text_rectangle(self, theme):
"""
Builds a text block using the settings in ``theme``
and the size of the display screen.height.
Note the system has a 10 pixel border round the screen
``theme``
The theme to build a text block for.
"""
log.debug(u'_build_text_rectangle')
main_rect = self.get_main_rectangle(theme)
footer_rect = self.get_footer_rectangle(theme)
self._set_text_rectangle(main_rect, footer_rect)
def get_main_rectangle(self, theme):
def get_main_rectangle(self, theme_data):
"""
Calculates the placement and size of the main rectangle.
``theme``
``theme_data``
The theme information
"""
if not theme.font_main_override:
return QtCore.QRect(10, 0, self.width - 20, self.footer_start)
if not theme_data.font_main_override:
return QtCore.QRect(10, 0, self.width, self.footer_start)
else:
return QtCore.QRect(theme.font_main_x, theme.font_main_y,
theme.font_main_width - 1, theme.font_main_height - 1)
return QtCore.QRect(theme_data.font_main_x, theme_data.font_main_y,
theme_data.font_main_width - 1, theme_data.font_main_height - 1)
def get_footer_rectangle(self, theme):
def get_footer_rectangle(self, theme_data):
"""
Calculates the placement and size of the footer rectangle.
``theme``
The theme information
``theme_data``
The theme data.
"""
if not theme.font_footer_override:
if not theme_data.font_footer_override:
return QtCore.QRect(10, self.footer_start, self.width - 20,
self.height - self.footer_start)
else:
return QtCore.QRect(theme.font_footer_x,
theme.font_footer_y, theme.font_footer_width - 1,
theme.font_footer_height - 1)
return QtCore.QRect(theme_data.font_footer_x,
theme_data.font_footer_y, theme_data.font_footer_width - 1,
theme_data.font_footer_height - 1)
def _set_text_rectangle(self, rect_main, rect_footer):
def _set_text_rectangle(self, theme_data, rect_main, rect_footer):
"""
Sets the rectangle within which text should be rendered.
``theme_data``
The theme data.
``rect_main``
The main text block.
@ -372,9 +400,9 @@ class Renderer(object):
self._rect_footer = rect_footer
self.page_width = self._rect.width()
self.page_height = self._rect.height()
if self.theme_data.font_main_shadow:
self.page_width -= int(self.theme_data.font_main_shadow_size)
self.page_height -= int(self.theme_data.font_main_shadow_size)
if theme_data.font_main_shadow:
self.page_width -= int(theme_data.font_main_shadow_size)
self.page_height -= int(theme_data.font_main_shadow_size)
self.web = QtWebKit.QWebView()
self.web.setVisible(False)
self.web.resize(self.page_width, self.page_height)
@ -392,8 +420,8 @@ class Renderer(object):
</script><style>*{margin: 0; padding: 0; border: 0;}
#main {position: absolute; top: 0px; %s %s}</style></head><body>
<div id="main"></div></body></html>""" % \
(build_lyrics_format_css(self.theme_data, self.page_width,
self.page_height), build_lyrics_outline_css(self.theme_data))
(build_lyrics_format_css(theme_data, self.page_width,
self.page_height), build_lyrics_outline_css(theme_data))
self.web.setHtml(html)
self.empty_height = self.web_frame.contentsSize().height()

View File

@ -158,19 +158,24 @@ class ServiceItem(object):
self.icon = icon
self.iconic_representation = build_icon(icon)
def render(self, use_override=False):
def render(self, provides_own_theme_data=False):
"""
The render method is what generates the frames for the screen and
obtains the display information from the renderer. At this point all
slides are built for the given display size.
``provides_own_theme_data``
This switch disables the usage of the item's theme. However, this is
disabled by default. If this is used, it has to be taken care, that
the renderer knows the correct theme data. However, this is needed
for the theme manager.
"""
log.debug(u'Render called')
self._display_frames = []
self.bg_image_bytes = None
theme = self.theme if self.theme else None
self.main, self.footer = \
self.renderer.set_override_theme(theme, use_override)
self.themedata = self.renderer.theme_data
if not provides_own_theme_data:
self.renderer.set_item_theme(self.theme)
self.themedata, self.main, self.footer = self.renderer.pre_render()
if self.service_item_type == ServiceItemType.Text:
log.debug(u'Formatting slides')
for slide in self._raw_frames:
@ -211,7 +216,7 @@ class ServiceItem(object):
self.image_border = background
self.service_item_type = ServiceItemType.Image
self._raw_frames.append({u'title': title, u'path': path})
self.renderer.imageManager.addImage(title, path, u'image',
self.renderer.image_manager.addImage(title, path, u'image',
self.image_border)
self._new_item()

View File

@ -80,6 +80,10 @@ class UiStrings(object):
self.Help = translate('OpenLP.Ui', 'Help')
self.Hours = translate('OpenLP.Ui', 'h',
'The abbreviated unit for hours')
self.IFdSs = translate('OpenLP.Ui', 'Invalid Folder Selected',
'Singular')
self.IFSs = translate('OpenLP.Ui', 'Invalid File Selected', 'Singular')
self.IFSp = translate('OpenLP.Ui', 'Invalid Files Selected', 'Plural')
self.Image = translate('OpenLP.Ui', 'Image')
self.Import = translate('OpenLP.Ui', 'Import')
self.LayoutStyle = translate('OpenLP.Ui', 'Layout style:')

View File

@ -31,11 +31,16 @@ from datetime import datetime, timedelta
from PyQt4 import QtCore, QtGui
import logging
import os
import sys
from openlp.core.lib import SettingsTab, translate, build_icon, Receiver
from openlp.core.lib.settings import Settings
from openlp.core.lib.ui import UiStrings
from openlp.core.utils import get_images_filter, AppLocation
from openlp.core.lib import SlideLimits
from openlp.core.utils import get_images_filter
log = logging.getLogger(__name__)
class AdvancedTab(SettingsTab):
"""
@ -60,6 +65,7 @@ class AdvancedTab(SettingsTab):
'#strftime-strptime-behavior for more information.'))
self.defaultImage = u':/graphics/openlp-splash-screen.png'
self.defaultColor = u'#ffffff'
self.dataExists = False
self.iconPath = u':/system/system_settings.png'
advanced_translated = translate('OpenLP.AdvancedTab', 'Advanced')
SettingsTab.__init__(self, parent, u'Advanced', advanced_translated)
@ -152,6 +158,71 @@ class AdvancedTab(SettingsTab):
self.serviceNameLayout.addRow(self.serviceNameExampleLabel,
self.serviceNameExample)
self.leftLayout.addWidget(self.serviceNameGroupBox)
# Data Directory
self.dataDirectoryGroupBox = QtGui.QGroupBox(self.leftColumn)
self.dataDirectoryGroupBox.setObjectName(u'dataDirectoryGroupBox')
self.dataDirectoryLayout = QtGui.QFormLayout(self.dataDirectoryGroupBox)
self.dataDirectoryLayout.setObjectName(u'dataDirectoryLayout')
self.dataDirectoryCurrentLabel = QtGui.QLabel(self.dataDirectoryGroupBox)
self.dataDirectoryCurrentLabel.setObjectName(
u'dataDirectoryCurrentLabel')
self.dataDirectoryLabel = QtGui.QLabel(self.dataDirectoryGroupBox)
self.dataDirectoryLabel.setObjectName(u'dataDirectoryLabel')
self.dataDirectoryNewLabel = QtGui.QLabel(self.dataDirectoryGroupBox)
self.dataDirectoryNewLabel.setObjectName(u'dataDirectoryCurrentLabel')
self.newDataDirectoryEdit = QtGui.QLineEdit(self.dataDirectoryGroupBox)
self.newDataDirectoryEdit.setObjectName(u'newDataDirectoryEdit')
self.newDataDirectoryEdit.setReadOnly(True)
self.newDataDirectoryHasFilesLabel = QtGui.QLabel(
self.dataDirectoryGroupBox)
self.newDataDirectoryHasFilesLabel.setObjectName(
u'newDataDirectoryHasFilesLabel')
self.newDataDirectoryHasFilesLabel.setWordWrap(True)
self.dataDirectoryBrowseButton = QtGui.QToolButton(
self.dataDirectoryGroupBox)
self.dataDirectoryBrowseButton.setObjectName(
u'dataDirectoryBrowseButton')
self.dataDirectoryBrowseButton.setIcon(
build_icon(u':/general/general_open.png'))
self.dataDirectoryDefaultButton = QtGui.QToolButton(
self.dataDirectoryGroupBox)
self.dataDirectoryDefaultButton.setObjectName(
u'dataDirectoryDefaultButton')
self.dataDirectoryDefaultButton.setIcon(
build_icon(u':/general/general_revert.png'))
self.dataDirectoryCancelButton = QtGui.QToolButton(
self.dataDirectoryGroupBox)
self.dataDirectoryCancelButton.setObjectName(
u'dataDirectoryCancelButton')
self.dataDirectoryCancelButton.setIcon(
build_icon(u':/general/general_delete.png'))
self.newDataDirectoryLabelHBox = QtGui.QHBoxLayout()
self.newDataDirectoryLabelHBox.setObjectName(
u'newDataDirectoryLabelHBox')
self.newDataDirectoryLabelHBox.addWidget(self.newDataDirectoryEdit)
self.newDataDirectoryLabelHBox.addWidget(
self.dataDirectoryBrowseButton)
self.newDataDirectoryLabelHBox.addWidget(
self.dataDirectoryDefaultButton)
self.dataDirectoryCopyCheckHBox = QtGui.QHBoxLayout()
self.dataDirectoryCopyCheckHBox.setObjectName(
u'dataDirectoryCopyCheckHBox')
self.dataDirectoryCopyCheckBox = QtGui.QCheckBox(
self.dataDirectoryGroupBox)
self.dataDirectoryCopyCheckBox.setObjectName(
u'dataDirectoryCopyCheckBox')
self.dataDirectoryCopyCheckHBox.addWidget(
self.dataDirectoryCopyCheckBox)
self.dataDirectoryCopyCheckHBox.addStretch()
self.dataDirectoryCopyCheckHBox.addWidget(
self.dataDirectoryCancelButton)
self.dataDirectoryLayout.addRow(self.dataDirectoryCurrentLabel,
self.dataDirectoryLabel)
self.dataDirectoryLayout.addRow(self.dataDirectoryNewLabel,
self.newDataDirectoryLabelHBox)
self.dataDirectoryLayout.addRow(self.dataDirectoryCopyCheckHBox)
self.dataDirectoryLayout.addRow(self.newDataDirectoryHasFilesLabel)
self.leftLayout.addWidget(self.dataDirectoryGroupBox)
self.leftLayout.addStretch()
# Default Image
self.defaultImageGroupBox = QtGui.QGroupBox(self.rightColumn)
@ -220,7 +291,6 @@ class AdvancedTab(SettingsTab):
self.x11Layout.addWidget(self.x11BypassCheckBox)
self.rightLayout.addWidget(self.x11GroupBox)
self.rightLayout.addStretch()
self.shouldUpdateServiceNameExample = False
QtCore.QObject.connect(self.serviceNameCheckBox,
QtCore.SIGNAL(u'toggled(bool)'), self.serviceNameCheckBoxToggled)
@ -244,6 +314,18 @@ class AdvancedTab(SettingsTab):
QtCore.SIGNAL(u'clicked()'), self.onDefaultRevertButtonClicked)
QtCore.QObject.connect(self.x11BypassCheckBox,
QtCore.SIGNAL(u'toggled(bool)'), self.onX11BypassCheckBoxToggled)
QtCore.QObject.connect(self.dataDirectoryBrowseButton,
QtCore.SIGNAL(u'clicked()'),
self.onDataDirectoryBrowseButtonClicked)
QtCore.QObject.connect(self.dataDirectoryDefaultButton,
QtCore.SIGNAL(u'clicked()'),
self.onDataDirectoryDefaultButtonClicked)
QtCore.QObject.connect(self.dataDirectoryCancelButton,
QtCore.SIGNAL(u'clicked()'),
self.onDataDirectoryCancelButtonClicked)
QtCore.QObject.connect(self.dataDirectoryCopyCheckBox,
QtCore.SIGNAL(u'toggled(bool)'),
self.onDataDirectoryCopyCheckBoxToggled)
QtCore.QObject.connect(self.endSlideRadioButton,
QtCore.SIGNAL(u'clicked()'), self.onEndSlideButtonClicked)
QtCore.QObject.connect(self.wrapSlideRadioButton,
@ -258,6 +340,8 @@ class AdvancedTab(SettingsTab):
self.tabTitleVisible = UiStrings().Advanced
self.uiGroupBox.setTitle(
translate('OpenLP.AdvancedTab', 'UI Settings'))
self.dataDirectoryGroupBox.setTitle(
translate('OpenLP.AdvancedTab', 'Data Location'))
self.recentLabel.setText(
translate('OpenLP.AdvancedTab',
'Number of recent files to display:'))
@ -321,6 +405,32 @@ class AdvancedTab(SettingsTab):
'Browse for an image file to display.'))
self.defaultRevertButton.setToolTip(translate('OpenLP.AdvancedTab',
'Revert to the default OpenLP logo.'))
self.dataDirectoryCurrentLabel.setText(translate('OpenLP.AdvancedTab',
'Current path:'))
self.dataDirectoryNewLabel.setText(translate('OpenLP.AdvancedTab',
'Custom path:'))
self.dataDirectoryBrowseButton.setToolTip(
translate('OpenLP.AdvancedTab',
'Browse for new data file location.'))
self.dataDirectoryDefaultButton.setToolTip(
translate('OpenLP.AdvancedTab',
'Set the data location to the default.'))
self.dataDirectoryCancelButton.setText(
translate('OpenLP.AdvancedTab',
'Cancel'))
self.dataDirectoryCancelButton.setToolTip(
translate('OpenLP.AdvancedTab',
'Cancel OpenLP data directory location change.'))
self.dataDirectoryCopyCheckBox.setText(
translate('OpenLP.AdvancedTab',
'Copy data to new location.'))
self.dataDirectoryCopyCheckBox.setToolTip(
translate('OpenLP.AdvancedTab',
'Copy the OpenLP data files to the new location.'))
self.newDataDirectoryHasFilesLabel.setText(
translate('OpenLP.AdvancedTab',
'<strong>WARNING:</strong> New data directory location contains '
'OpenLP data files. These files WILL be replaced during a copy.'))
self.x11GroupBox.setTitle(translate('OpenLP.AdvancedTab',
'X11'))
self.x11BypassCheckBox.setText(translate('OpenLP.AdvancedTab',
@ -398,6 +508,40 @@ class AdvancedTab(SettingsTab):
else:
self.nextItemRadioButton.setChecked(True)
settings.endGroup()
self.dataDirectoryCopyCheckBox.hide()
self.newDataDirectoryHasFilesLabel.hide()
self.dataDirectoryCancelButton.hide()
# Since data location can be changed, make sure the path is present.
self.currentDataPath = AppLocation.get_data_path()
if not os.path.exists(self.currentDataPath):
log.error(u'Data path not found %s' % self.currentDataPath)
answer = QtGui.QMessageBox.critical(self,
translate('OpenLP.AdvancedTab',
'Data Directory Error'),
translate('OpenLP.AdvancedTab',
'OpenLP data directory was not found\n\n%s\n\n'
'This data directory was previously changed from the OpenLP '
'default location. If the new location was on removable '
'media, that media needs to be made available.\n\n'
'Click "No" to stop loading OpenLP. allowing you to fix '
'the the problem.\n\n'
'Click "Yes" to reset the data directory to the default '
'location.' % self.currentDataPath),
QtGui.QMessageBox.StandardButtons(
QtGui.QMessageBox.Yes |
QtGui.QMessageBox.No),
QtGui.QMessageBox.No)
if answer == QtGui.QMessageBox.No:
log.info(u'User requested termination')
Receiver.send_message(u'cleanup')
sys.exit()
# Set data location to default.
settings.remove(u'advanced/data path')
self.currentDataPath = AppLocation.get_data_path()
log.warning(u'User requested data path set to default %s'
% self.currentDataPath)
self.dataDirectoryLabel.setText(os.path.abspath(
self.currentDataPath))
self.defaultColorButton.setStyleSheet(
u'background-color: %s' % self.defaultColor)
@ -447,6 +591,11 @@ class AdvancedTab(SettingsTab):
self.displayChanged = False
Receiver.send_message(u'slidecontroller_update_slide_limits')
def cancel(self):
# Dialogue was cancelled, remove any pending data path change.
self.onDataDirectoryCancelButtonClicked()
SettingsTab.cancel(self)
def serviceNameCheckBoxToggled(self, default_service_enabled):
self.serviceNameDay.setEnabled(default_service_enabled)
time_enabled = default_service_enabled and \
@ -508,6 +657,122 @@ class AdvancedTab(SettingsTab):
self.defaultFileEdit.setText(filename)
self.defaultFileEdit.setFocus()
def onDataDirectoryBrowseButtonClicked(self):
"""
Browse for a new data directory location.
"""
old_root_path = unicode(self.dataDirectoryLabel.text())
# Get the new directory location.
new_data_path = unicode(QtGui.QFileDialog.getExistingDirectory(self,
translate('OpenLP.AdvancedTab',
'Select Data Directory Location'), old_root_path,
options = QtGui.QFileDialog.ShowDirsOnly))
# Set the new data path.
if new_data_path:
if self.currentDataPath.lower() == new_data_path.lower():
self.onDataDirectoryCancelButtonClicked()
return
else:
return
# Make sure they want to change the data.
answer = QtGui.QMessageBox.question(self,
translate('OpenLP.AdvancedTab', 'Confirm Data Directory Change'),
translate('OpenLP.AdvancedTab',
'Are you sure you want to change the location of the OpenLP '
'data directory to:\n\n%s\n\n'
'The data directory will be changed when OpenLP is closed.'
% new_data_path),
QtGui.QMessageBox.StandardButtons(
QtGui.QMessageBox.Yes |
QtGui.QMessageBox.No),
QtGui.QMessageBox.No)
if answer != QtGui.QMessageBox.Yes:
return
# Check if data already exists here.
self.checkDataOverwrite(new_data_path)
# Save the new location.
Receiver.send_message(u'set_new_data_path', new_data_path)
self.newDataDirectoryEdit.setText(new_data_path)
self.dataDirectoryCancelButton.show()
def onDataDirectoryDefaultButtonClicked(self):
"""
Re-set the data directory location to the 'default' location.
"""
new_data_path = AppLocation.get_directory(AppLocation.DataDir)
if self.currentDataPath.lower() != new_data_path.lower():
# Make sure they want to change the data location back to the default.
answer = QtGui.QMessageBox.question(self,
translate('OpenLP.AdvancedTab', 'Reset Data Directory'),
translate('OpenLP.AdvancedTab',
'Are you sure you want to change the location of the OpenLP '
'data directory to the default location?\n\n'
'This location will be used after OpenLP is closed.'),
QtGui.QMessageBox.StandardButtons(
QtGui.QMessageBox.Yes |
QtGui.QMessageBox.No),
QtGui.QMessageBox.No)
if answer != QtGui.QMessageBox.Yes:
return
self.checkDataOverwrite(new_data_path)
# Save the new location.
Receiver.send_message(u'set_new_data_path', new_data_path)
self.newDataDirectoryEdit.setText(os.path.abspath(new_data_path))
self.dataDirectoryCancelButton.show()
else:
# We cancel the change in case user changed their mind.
self.onDataDirectoryCancelButtonClicked()
def onDataDirectoryCopyCheckBoxToggled(self):
Receiver.send_message(u'set_copy_data',
self.dataDirectoryCopyCheckBox.isChecked())
if self.dataExists:
if self.dataDirectoryCopyCheckBox.isChecked():
self.newDataDirectoryHasFilesLabel.show()
else:
self.newDataDirectoryHasFilesLabel.hide()
def checkDataOverwrite(self, data_path ):
test_path = os.path.join(data_path, u'songs')
self.dataDirectoryCopyCheckBox.show()
if os.path.exists(test_path):
self.dataExists = True
# Check is they want to replace existing data.
answer = QtGui.QMessageBox.warning(self,
translate('OpenLP.AdvancedTab', 'Overwrite Existing Data'),
translate('OpenLP.AdvancedTab',
'WARNING: \n\n'
'The location you have selected \n\n%s\n\n'
'appears to contain OpenLP data files. Do you wish to replace '
'these files with the current data files?'
% os.path.abspath(data_path,)),
QtGui.QMessageBox.StandardButtons(
QtGui.QMessageBox.Yes |
QtGui.QMessageBox.No),
QtGui.QMessageBox.No)
if answer == QtGui.QMessageBox.Yes:
self.dataDirectoryCopyCheckBox.setChecked(True)
self.newDataDirectoryHasFilesLabel.show()
else:
self.dataDirectoryCopyCheckBox.setChecked(False)
self.newDataDirectoryHasFilesLabel.hide()
else:
self.dataExists = False
self.dataDirectoryCopyCheckBox.setChecked(True)
self.newDataDirectoryHasFilesLabel.hide()
def onDataDirectoryCancelButtonClicked(self):
"""
Cancel the data directory location change
"""
self.newDataDirectoryEdit.clear()
self.dataDirectoryCopyCheckBox.setChecked(False)
Receiver.send_message(u'set_new_data_path', u'')
Receiver.send_message(u'set_copy_data', False)
self.dataDirectoryCopyCheckBox.hide()
self.dataDirectoryCancelButton.hide()
self.newDataDirectoryHasFilesLabel.hide()
def onDefaultRevertButtonClicked(self):
self.defaultFileEdit.setText(u':/graphics/openlp-splash-screen.png')
self.defaultFileEdit.setFocus()

View File

@ -101,9 +101,8 @@ class Display(QtGui.QGraphicsView):
self.frame.setScrollBarPolicy(QtCore.Qt.Horizontal,
QtCore.Qt.ScrollBarAlwaysOff)
def resizeEvent(self, ev):
self.webView.setGeometry(0, 0,
self.width(), self.height())
def resizeEvent(self, event):
self.webView.setGeometry(0, 0, self.width(), self.height())
def isWebLoaded(self):
"""
@ -121,7 +120,6 @@ class MainDisplay(Display):
Display.__init__(self, parent, live, controller)
self.imageManager = imageManager
self.screens = ScreenList()
self.plugins = PluginManager.get_instance().plugins
self.rebuildCSS = False
self.hideMode = None
self.override = {}

View File

@ -29,6 +29,8 @@ import logging
import os
import sys
import shutil
from distutils import dir_util
from distutils.errors import DistutilsFileError
from tempfile import gettempdir
import time
from datetime import datetime
@ -583,6 +585,8 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
# Once settings are loaded update the menu with the recent files.
self.updateRecentFilesMenu()
self.pluginForm = PluginForm(self)
self.newDataPath = u''
self.copyData = False
# Set up signals and slots
QtCore.QObject.connect(self.importThemeItem,
QtCore.SIGNAL(u'triggered()'),
@ -635,6 +639,8 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
QtCore.SIGNAL(u'config_screen_changed'), self.screenChanged)
QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'mainwindow_status_text'), self.showStatusMessage)
QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'cleanup'), self.cleanUp)
# Media Manager
QtCore.QObject.connect(self.mediaToolBox,
QtCore.SIGNAL(u'currentChanged(int)'), self.onMediaToolBoxChanged)
@ -647,6 +653,10 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'openlp_information_message'),
self.onInformationMessage)
QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'set_new_data_path'), self.setNewDataPath)
QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'set_copy_data'), self.setCopyData)
# warning cyclic dependency
# renderer needs to call ThemeManager and
# ThemeManager needs to call Renderer
@ -1199,6 +1209,9 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
if save_settings:
# Save settings
self.saveSettings()
# Check if we need to change the data directory
if self.newDataPath:
self.changeDataDirectory()
# Close down the display
if self.liveController.display:
self.liveController.display.close()
@ -1475,3 +1488,45 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
self.timer_id = 0
self.loadProgressBar.hide()
Receiver.send_message(u'openlp_process_events')
def setNewDataPath(self, new_data_path):
self.newDataPath = new_data_path
def setCopyData(self, copy_data):
self.copyData = copy_data
def changeDataDirectory(self):
log.info(u'Changing data path to %s' % self.newDataPath )
old_data_path = unicode(AppLocation.get_data_path())
# Copy OpenLP data to new location if requested.
if self.copyData:
log.info(u'Copying data to new path')
try:
Receiver.send_message(u'openlp_process_events')
Receiver.send_message(u'cursor_busy')
self.showStatusMessage(
translate('OpenLP.MainWindow',
'Copying OpenLP data to new data directory location - %s '
'- Please wait for copy to finish'
% self.newDataPath))
dir_util.copy_tree(old_data_path, self.newDataPath)
log.info(u'Copy sucessful')
except (IOError, os.error, DistutilsFileError), why:
Receiver.send_message(u'cursor_normal')
log.exception(u'Data copy failed %s' % unicode(why))
QtGui.QMessageBox.critical(self,
translate('OpenLP.MainWindow', 'New Data Directory Error'),
translate('OpenLP.MainWindow',
'OpenLP Data directory copy failed\n\n%s'
% unicode(why)),
QtGui.QMessageBox.StandardButtons(
QtGui.QMessageBox.Ok))
return False
else:
log.info(u'No data copy requested')
# Change the location of data directory in config file.
settings = QtCore.QSettings()
settings.setValue(u'advanced/data path', self.newDataPath)
# Check if the new data path is our default.
if self.newDataPath == AppLocation.get_directory(AppLocation.DataDir):
settings.remove(u'advanced/data path')

View File

@ -336,8 +336,7 @@ class MediaController(object):
if controller.isLive and \
(Settings().value(u'general/auto unblank',
QtCore.QVariant(False)).toBool() or \
controller.media_info.is_background == True) or \
controller.isLive == False:
controller.media_info.is_background) or not controller.isLive:
if not self.video_play([controller]):
critical_error_message_box(
translate('MediaPlugin.MediaItem', 'Unsupported File'),
@ -496,7 +495,7 @@ class MediaController(object):
return
controller = self.parent.liveController
for display in self.curDisplayMediaPlayer.keys():
if display.controller != controller or \
if display.controller != controller or \
self.curDisplayMediaPlayer[display].state != MediaState.Playing:
continue
self.curDisplayMediaPlayer[display].pause(display)

View File

@ -38,21 +38,21 @@ from openlp.core.ui.media import MediaState
log = logging.getLogger(__name__)
ADDITIONAL_EXT = {
u'audio/ac3': [u'.ac3'],
u'audio/flac': [u'.flac'],
u'audio/x-m4a': [u'.m4a'],
u'audio/midi': [u'.mid', u'.midi'],
u'audio/x-mp3': [u'.mp3'],
u'audio/mpeg': [u'.mp3', u'.mp2', u'.mpga', u'.mpega', u'.m4a'],
u'audio/qcelp': [u'.qcp'],
u'audio/x-wma': [u'.wma'],
u'audio/x-ms-wma': [u'.wma'],
u'video/x-flv': [u'.flv'],
u'video/x-matroska': [u'.mpv', u'.mkv'],
u'video/x-wmv': [u'.wmv'],
u'video/x-mpg': [u'.mpg'],
u'video/mpeg' : [u'.mp4', u'.mts'],
u'video/x-ms-wmv': [u'.wmv']}
u'audio/ac3': [u'.ac3'],
u'audio/flac': [u'.flac'],
u'audio/x-m4a': [u'.m4a'],
u'audio/midi': [u'.mid', u'.midi'],
u'audio/x-mp3': [u'.mp3'],
u'audio/mpeg': [u'.mp3', u'.mp2', u'.mpga', u'.mpega', u'.m4a'],
u'audio/qcelp': [u'.qcp'],
u'audio/x-wma': [u'.wma'],
u'audio/x-ms-wma': [u'.wma'],
u'video/x-flv': [u'.flv'],
u'video/x-matroska': [u'.mpv', u'.mkv'],
u'video/x-wmv': [u'.wmv'],
u'video/x-mpg': [u'.mpg'],
u'video/mpeg' : [u'.mp4', u'.mts'],
u'video/x-ms-wmv': [u'.wmv']}
class PhononPlayer(MediaPlayer):
@ -101,7 +101,7 @@ class PhononPlayer(MediaPlayer):
display.mediaObject = Phonon.MediaObject(display)
Phonon.createPath(display.mediaObject, display.phononWidget)
if display.hasAudio:
display.audio = Phonon.AudioOutput( \
display.audio = Phonon.AudioOutput(
Phonon.VideoCategory, display.mediaObject)
Phonon.createPath(display.mediaObject, display.audio)
display.phononWidget.raise_()
@ -148,18 +148,17 @@ class PhononPlayer(MediaPlayer):
controller.media_info.start_time > 0:
start_time = controller.media_info.start_time
display.mediaObject.play()
if self.media_state_wait(display, Phonon.PlayingState):
if start_time > 0:
self.seek(display, controller.media_info.start_time*1000)
self.volume(display, controller.media_info.volume)
controller.media_info.length = \
int(display.mediaObject.totalTime()/1000)
controller.seekSlider.setMaximum(controller.media_info.length*1000)
self.state = MediaState.Playing
display.phononWidget.raise_()
return True
else:
if not self.media_state_wait(display, Phonon.PlayingState):
return False
if start_time > 0:
self.seek(display, controller.media_info.start_time * 1000)
self.volume(display, controller.media_info.volume)
controller.media_info.length = \
int(display.mediaObject.totalTime() / 1000)
controller.seekSlider.setMaximum(controller.media_info.length * 1000)
self.state = MediaState.Playing
display.phononWidget.raise_()
return True
def pause(self, display):
display.mediaObject.pause()
@ -198,9 +197,9 @@ class PhononPlayer(MediaPlayer):
controller = display.controller
if controller.media_info.end_time > 0:
if display.mediaObject.currentTime() > \
controller.media_info.end_time*1000:
controller.media_info.end_time * 1000:
self.stop(display)
self.set_visible(display, False)
if not controller.seekSlider.isSliderDown():
controller.seekSlider.setSliderPosition( \
controller.seekSlider.setSliderPosition(
display.mediaObject.currentTime())

View File

@ -48,7 +48,7 @@ import sys
from inspect import getargspec
__version__ = "N/A"
build_date = "Fri Apr 27 16:47:21 2012"
build_date = "Fri Jun 8 09:31:07 2012"
# Internal guard to prevent internal classes to be directly
# instanciated.
@ -905,10 +905,10 @@ class Instance(_Ctype):
def media_new(self, mrl, *options):
"""Create a new Media instance.
If mrl contains a colon (:), it will be treated as a
URL. Else, it will be considered as a local path. If you need
more control, directly use media_new_location/media_new_path
methods.
If mrl contains a colon (:) preceded by more than 1 letter, it
will be treated as a URL. Else, it will be considered as a
local path. If you need more control, directly use
media_new_location/media_new_path methods.
Options can be specified as supplementary string parameters, e.g.
@ -920,7 +920,7 @@ class Instance(_Ctype):
@param options: optional media option=value strings
"""
if ':' in mrl:
if ':' in mrl and mrl.index(':') > 1:
# Assume it is a URL
m = libvlc_media_new_location(self, mrl)
else:
@ -1031,6 +1031,13 @@ class Instance(_Ctype):
'''
return libvlc_log_open(self)
def media_discoverer_new_from_name(self, psz_name):
'''Discover media service by name.
@param psz_name: service name.
@return: media discover object or NULL in case of error.
'''
return libvlc_media_discoverer_new_from_name(self, psz_name)
def media_new_location(self, psz_mrl):
'''Create a media with a certain given media resource location,
for instance a valid URL.
@ -1080,13 +1087,6 @@ class Instance(_Ctype):
'''
return libvlc_media_new_as_node(self, psz_name)
def media_discoverer_new_from_name(self, psz_name):
'''Discover media service by name.
@param psz_name: service name.
@return: media discover object or NULL in case of error.
'''
return libvlc_media_discoverer_new_from_name(self, psz_name)
def media_library_new(self):
'''Create an new Media Library object.
@return: a new object or NULL on error.
@ -1522,7 +1522,7 @@ class Media(_Ctype):
def save_meta(self):
'''Save the meta previously set.
@return: true if the write operation was successful.
@return: true if the write operation was successfull.
'''
return libvlc_media_save_meta(self)
@ -3088,6 +3088,67 @@ def libvlc_clock():
ctypes.c_int64)
return f()
def libvlc_media_discoverer_new_from_name(p_inst, psz_name):
'''Discover media service by name.
@param p_inst: libvlc instance.
@param psz_name: service name.
@return: media discover object or NULL in case of error.
'''
f = _Cfunctions.get('libvlc_media_discoverer_new_from_name', None) or \
_Cfunction('libvlc_media_discoverer_new_from_name', ((1,), (1,),), class_result(MediaDiscoverer),
ctypes.c_void_p, Instance, ctypes.c_char_p)
return f(p_inst, psz_name)
def libvlc_media_discoverer_release(p_mdis):
'''Release media discover object. If the reference count reaches 0, then
the object will be released.
@param p_mdis: media service discover object.
'''
f = _Cfunctions.get('libvlc_media_discoverer_release', None) or \
_Cfunction('libvlc_media_discoverer_release', ((1,),), None,
None, MediaDiscoverer)
return f(p_mdis)
def libvlc_media_discoverer_localized_name(p_mdis):
'''Get media service discover object its localized name.
@param p_mdis: media discover object.
@return: localized name.
'''
f = _Cfunctions.get('libvlc_media_discoverer_localized_name', None) or \
_Cfunction('libvlc_media_discoverer_localized_name', ((1,),), string_result,
ctypes.c_void_p, MediaDiscoverer)
return f(p_mdis)
def libvlc_media_discoverer_media_list(p_mdis):
'''Get media service discover media list.
@param p_mdis: media service discover object.
@return: list of media items.
'''
f = _Cfunctions.get('libvlc_media_discoverer_media_list', None) or \
_Cfunction('libvlc_media_discoverer_media_list', ((1,),), class_result(MediaList),
ctypes.c_void_p, MediaDiscoverer)
return f(p_mdis)
def libvlc_media_discoverer_event_manager(p_mdis):
'''Get event manager from media service discover object.
@param p_mdis: media service discover object.
@return: event manager object.
'''
f = _Cfunctions.get('libvlc_media_discoverer_event_manager', None) or \
_Cfunction('libvlc_media_discoverer_event_manager', ((1,),), class_result(EventManager),
ctypes.c_void_p, MediaDiscoverer)
return f(p_mdis)
def libvlc_media_discoverer_is_running(p_mdis):
'''Query if media service discover object is running.
@param p_mdis: media service discover object.
@return: true if running, false if not \libvlc_return_bool.
'''
f = _Cfunctions.get('libvlc_media_discoverer_is_running', None) or \
_Cfunction('libvlc_media_discoverer_is_running', ((1,),), None,
ctypes.c_int, MediaDiscoverer)
return f(p_mdis)
def libvlc_media_new_location(p_instance, psz_mrl):
'''Create a media with a certain given media resource location,
for instance a valid URL.
@ -3258,7 +3319,7 @@ def libvlc_media_set_meta(p_md, e_meta, psz_value):
def libvlc_media_save_meta(p_md):
'''Save the meta previously set.
@param p_md: the media desriptor.
@return: true if the write operation was successful.
@return: true if the write operation was successfull.
'''
f = _Cfunctions.get('libvlc_media_save_meta', None) or \
_Cfunction('libvlc_media_save_meta', ((1,),), None,
@ -3392,67 +3453,6 @@ def libvlc_media_get_tracks_info(p_md):
ctypes.c_int, Media, ctypes.POINTER(ctypes.c_void_p))
return f(p_md)
def libvlc_media_discoverer_new_from_name(p_inst, psz_name):
'''Discover media service by name.
@param p_inst: libvlc instance.
@param psz_name: service name.
@return: media discover object or NULL in case of error.
'''
f = _Cfunctions.get('libvlc_media_discoverer_new_from_name', None) or \
_Cfunction('libvlc_media_discoverer_new_from_name', ((1,), (1,),), class_result(MediaDiscoverer),
ctypes.c_void_p, Instance, ctypes.c_char_p)
return f(p_inst, psz_name)
def libvlc_media_discoverer_release(p_mdis):
'''Release media discover object. If the reference count reaches 0, then
the object will be released.
@param p_mdis: media service discover object.
'''
f = _Cfunctions.get('libvlc_media_discoverer_release', None) or \
_Cfunction('libvlc_media_discoverer_release', ((1,),), None,
None, MediaDiscoverer)
return f(p_mdis)
def libvlc_media_discoverer_localized_name(p_mdis):
'''Get media service discover object its localized name.
@param p_mdis: media discover object.
@return: localized name.
'''
f = _Cfunctions.get('libvlc_media_discoverer_localized_name', None) or \
_Cfunction('libvlc_media_discoverer_localized_name', ((1,),), string_result,
ctypes.c_void_p, MediaDiscoverer)
return f(p_mdis)
def libvlc_media_discoverer_media_list(p_mdis):
'''Get media service discover media list.
@param p_mdis: media service discover object.
@return: list of media items.
'''
f = _Cfunctions.get('libvlc_media_discoverer_media_list', None) or \
_Cfunction('libvlc_media_discoverer_media_list', ((1,),), class_result(MediaList),
ctypes.c_void_p, MediaDiscoverer)
return f(p_mdis)
def libvlc_media_discoverer_event_manager(p_mdis):
'''Get event manager from media service discover object.
@param p_mdis: media service discover object.
@return: event manager object.
'''
f = _Cfunctions.get('libvlc_media_discoverer_event_manager', None) or \
_Cfunction('libvlc_media_discoverer_event_manager', ((1,),), class_result(EventManager),
ctypes.c_void_p, MediaDiscoverer)
return f(p_mdis)
def libvlc_media_discoverer_is_running(p_mdis):
'''Query if media service discover object is running.
@param p_mdis: media service discover object.
@return: true if running, false if not \libvlc_return_bool.
'''
f = _Cfunctions.get('libvlc_media_discoverer_is_running', None) or \
_Cfunction('libvlc_media_discoverer_is_running', ((1,),), None,
ctypes.c_int, MediaDiscoverer)
return f(p_mdis)
def libvlc_media_library_new(p_instance):
'''Create an new Media Library object.
@param p_instance: the libvlc instance.
@ -5595,7 +5595,7 @@ if __name__ == '__main__':
print('Aspect ratio: %s' % player.video_get_aspect_ratio())
#print('Window:' % player.get_hwnd()
except Exception:
print('Error: %s', sys.exc_info()[1])
print('Error: %s' % sys.exc_info()[1])
def sec_forward():
"""Go forward one sec"""

View File

@ -63,7 +63,7 @@ if VLC_AVAILABLE:
log.debug(u'VLC could not be loaded: %s' % version)
AUDIO_EXT = [
u'*.mp3'
u'*.mp3'
, u'*.wav'
, u'*.ogg'
]
@ -131,9 +131,9 @@ class VlcPlayer(MediaPlayer):
# this is platform specific!
# you have to give the id of the QFrame (or similar object) to
# vlc, different platforms have different functions for this
if sys.platform == "win32": # for Windows
if sys.platform == "win32":
display.vlcMediaPlayer.set_hwnd(int(display.vlcWidget.winId()))
elif sys.platform == "darwin": # for MacOS
elif sys.platform == "darwin":
display.vlcMediaPlayer.set_agl(int(display.vlcWidget.winId()))
else:
# for Linux using the X Server
@ -182,17 +182,16 @@ class VlcPlayer(MediaPlayer):
if controller.media_info.start_time > 0:
start_time = controller.media_info.start_time
display.vlcMediaPlayer.play()
if self.media_state_wait(display, vlc.State.Playing):
if start_time > 0:
self.seek(display, controller.media_info.start_time * 1000)
controller.media_info.length = \
int(display.vlcMediaPlayer.get_media().get_duration() / 1000)
controller.seekSlider.setMaximum(controller.media_info.length * 1000)
self.state = MediaState.Playing
display.vlcWidget.raise_()
return True
else:
if not self.media_state_wait(display, vlc.State.Playing):
return False
if start_time > 0:
self.seek(display, controller.media_info.start_time * 1000)
controller.media_info.length = \
int(display.vlcMediaPlayer.get_media().get_duration() / 1000)
controller.seekSlider.setMaximum(controller.media_info.length * 1000)
self.state = MediaState.Playing
display.vlcWidget.raise_()
return True
def pause(self, display):
if display.vlcMedia.get_state() != vlc.State.Playing:

View File

@ -227,33 +227,33 @@ FLASH_HTML = u"""
"""
VIDEO_EXT = [
u'*.3gp'
, u'*.3gpp'
, u'*.3g2'
, u'*.3gpp2'
, u'*.aac'
, u'*.flv'
, u'*.f4a'
, u'*.f4b'
, u'*.f4p'
, u'*.f4v'
, u'*.mov'
, u'*.m4a'
, u'*.m4b'
, u'*.m4p'
, u'*.m4v'
, u'*.mkv'
, u'*.mp4'
, u'*.ogv'
, u'*.webm'
, u'*.mpg', u'*.wmv', u'*.mpeg', u'*.avi'
, u'*.swf'
]
u'*.3gp'
, u'*.3gpp'
, u'*.3g2'
, u'*.3gpp2'
, u'*.aac'
, u'*.flv'
, u'*.f4a'
, u'*.f4b'
, u'*.f4p'
, u'*.f4v'
, u'*.mov'
, u'*.m4a'
, u'*.m4b'
, u'*.m4p'
, u'*.m4v'
, u'*.mkv'
, u'*.mp4'
, u'*.ogv'
, u'*.webm'
, u'*.mpg', u'*.wmv', u'*.mpeg', u'*.avi'
, u'*.swf'
]
AUDIO_EXT = [
u'*.mp3'
, u'*.ogg'
]
u'*.mp3'
, u'*.ogg'
]
class WebkitPlayer(MediaPlayer):
@ -339,7 +339,7 @@ class WebkitPlayer(MediaPlayer):
else:
display.frame.evaluateJavaScript(u'show_video("play");')
if start_time > 0:
self.seek(display, controller.media_info.start_time*1000)
self.seek(display, controller.media_info.start_time * 1000)
# TODO add playing check and get the correct media length
controller.media_info.length = length
self.state = MediaState.Playing
@ -375,11 +375,11 @@ class WebkitPlayer(MediaPlayer):
controller = display.controller
if controller.media_info.is_flash:
seek = seekVal
display.frame.evaluateJavaScript( \
display.frame.evaluateJavaScript(
u'show_flash("seek", null, null, "%s");' % (seek))
else:
seek = float(seekVal)/1000
display.frame.evaluateJavaScript( \
seek = float(seekVal) / 1000
display.frame.evaluateJavaScript(
u'show_video("seek", null, null, null, "%f");' % (seek))
def reset(self, display):
@ -406,24 +406,24 @@ class WebkitPlayer(MediaPlayer):
def update_ui(self, display):
controller = display.controller
if controller.media_info.is_flash:
currentTime = display.frame.evaluateJavaScript( \
currentTime = display.frame.evaluateJavaScript(
u'show_flash("currentTime");').toInt()[0]
length = display.frame.evaluateJavaScript( \
length = display.frame.evaluateJavaScript(
u'show_flash("length");').toInt()[0]
else:
if display.frame.evaluateJavaScript( \
if display.frame.evaluateJavaScript(
u'show_video("isEnded");').toString() == 'true':
self.stop(display)
(currentTime, ok) = display.frame.evaluateJavaScript( \
(currentTime, ok) = display.frame.evaluateJavaScript(
u'show_video("currentTime");').toFloat()
# check if conversion was ok and value is not 'NaN'
if ok and currentTime != float('inf'):
currentTime = int(currentTime*1000)
(length, ok) = display.frame.evaluateJavaScript( \
currentTime = int(currentTime * 1000)
(length, ok) = display.frame.evaluateJavaScript(
u'show_video("length");').toFloat()
# check if conversion was ok and value is not 'NaN'
if ok and length != float('inf'):
length = int(length*1000)
length = int(length * 1000)
if currentTime > 0:
controller.media_info.length = length
controller.seekSlider.setMaximum(length)

View File

@ -102,9 +102,9 @@ class PluginForm(QtGui.QDialog, Ui_PluginViewDialog):
self.versionNumberLabel.setText(self.activePlugin.version)
self.aboutTextBrowser.setHtml(self.activePlugin.about())
self.programaticChange = True
status = 1
status = PluginStatus.Active
if self.activePlugin.status == PluginStatus.Active:
status = 0
status = PluginStatus.Inactive
self.statusComboBox.setCurrentIndex(status)
self.statusComboBox.setEnabled(True)
self.programaticChange = False
@ -129,7 +129,7 @@ class PluginForm(QtGui.QDialog, Ui_PluginViewDialog):
def onStatusComboBoxChanged(self, status):
if self.programaticChange or status == PluginStatus.Disabled:
return
if status == 0:
if status == PluginStatus.Inactive:
Receiver.send_message(u'cursor_busy')
self.activePlugin.toggleStatus(PluginStatus.Active)
Receiver.send_message(u'cursor_normal')

View File

@ -581,10 +581,7 @@ class ServiceManager(QtGui.QWidget):
return self.saveFileAs()
self.mainwindow.addRecentFile(path_file_name)
self.setModified(False)
try:
delete_file(temp_file_name)
except:
pass
delete_file(temp_file_name)
return success
def saveFileAs(self):

View File

@ -29,7 +29,7 @@ The :mod:`settingsform` provides a user interface for the OpenLP settings
"""
import logging
from PyQt4 import QtGui
from PyQt4 import QtCore, QtGui
from openlp.core.lib import Receiver, build_icon, PluginStatus
from openlp.core.ui import AdvancedTab, GeneralTab, ThemesTab

View File

@ -864,7 +864,7 @@ class SlideController(Controller):
image = self.imageManager.getImage(frame[u'title'])
label.setPixmap(QtGui.QPixmap.fromImage(image))
self.previewListWidget.setCellWidget(framenumber, 0, label)
slideHeight = width * self.parent().renderer.screen_ratio
slideHeight = width * (1 / self.ratio)
row += 1
self.slideList[unicode(row)] = row - 1
text.append(unicode(row))

View File

@ -248,8 +248,7 @@ class ThemeManager(QtGui.QWidget):
Settings().setValue(
self.settingsSection + u'/global theme',
QtCore.QVariant(self.global_theme))
Receiver.send_message(u'theme_update_global',
self.global_theme)
Receiver.send_message(u'theme_update_global', self.global_theme)
self._pushThemes()
def onAddTheme(self):
@ -286,6 +285,8 @@ class ThemeManager(QtGui.QWidget):
if plugin.usesTheme(old_theme_name):
plugin.renameTheme(old_theme_name, new_theme_name)
self.loadThemes()
self.mainwindow.renderer.update_theme(
new_theme_name, old_theme_name)
def onCopyTheme(self):
"""
@ -322,9 +323,8 @@ class ThemeManager(QtGui.QWidget):
Loads the settings for the theme that is to be edited and launches the
theme editing form so the user can make their changes.
"""
if check_item_selected(self.themeListWidget,
translate('OpenLP.ThemeManager',
'You must select a theme to edit.')):
if check_item_selected(self.themeListWidget, translate(
'OpenLP.ThemeManager', 'You must select a theme to edit.')):
item = self.themeListWidget.currentItem()
theme = self.getThemeData(
unicode(item.data(QtCore.Qt.UserRole).toString()))
@ -333,6 +333,7 @@ class ThemeManager(QtGui.QWidget):
self.themeForm.theme = theme
self.themeForm.exec_(True)
self.old_background_image = None
self.mainwindow.renderer.update_theme(theme.theme_name)
def onDeleteTheme(self):
"""
@ -350,6 +351,7 @@ class ThemeManager(QtGui.QWidget):
# As we do not reload the themes, push out the change. Reload the
# list as the internal lists and events need to be triggered.
self._pushThemes()
self.mainwindow.renderer.update_theme(theme, only_delete=True)
def deleteTheme(self, theme):
"""

View File

@ -152,8 +152,8 @@ class ThemesTab(SettingsTab):
settings.setValue(u'theme level', QtCore.QVariant(self.theme_level))
settings.setValue(u'global theme', QtCore.QVariant(self.global_theme))
settings.endGroup()
self.mainwindow.renderer.set_global_theme(
self.global_theme, self.theme_level)
self.mainwindow.renderer.set_global_theme(self.global_theme)
self.mainwindow.renderer.set_theme_level(self.theme_level)
Receiver.send_message(u'theme_update_global', self.global_theme)
def postSetUp(self):
@ -170,8 +170,8 @@ class ThemesTab(SettingsTab):
def onDefaultComboBoxChanged(self, value):
self.global_theme = unicode(self.DefaultComboBox.currentText())
self.mainwindow.renderer.set_global_theme(
self.global_theme, self.theme_level)
self.mainwindow.renderer.set_global_theme(self.global_theme)
self.mainwindow.renderer.set_theme_level(self.theme_level)
self.__previewGlobalTheme()
def updateThemeList(self, theme_list):
@ -190,8 +190,8 @@ class ThemesTab(SettingsTab):
self.DefaultComboBox.clear()
self.DefaultComboBox.addItems(theme_list)
find_and_set_in_combo_box(self.DefaultComboBox, self.global_theme)
self.mainwindow.renderer.set_global_theme(
self.global_theme, self.theme_level)
self.mainwindow.renderer.set_global_theme(self.global_theme)
self.mainwindow.renderer.set_theme_level(self.theme_level)
if self.global_theme is not u'':
self.__previewGlobalTheme()

View File

@ -44,20 +44,9 @@ class WizardStrings(object):
# Applications/Formats we import from or export to. These get used in
# multiple places but do not need translating unless you find evidence of
# the writers translating their own product name.
CCLI = u'CCLI/SongSelect'
CSV = u'CSV'
DB = u'DreamBeam'
EW = u'EasyWorship'
ES = u'EasySlides'
FP = u'Foilpresenter'
OL = u'OpenLyrics'
OS = u'OpenSong'
OSIS = u'OSIS'
PS = u'PowerSong 1.0'
SB = u'SongBeamer'
SoF = u'Songs of Fellowship'
SSP = u'SongShow Plus'
WoW = u'Words of Worship'
# These strings should need a good reason to be retranslated elsewhere.
FinishedImport = translate('OpenLP.Ui', 'Finished import.')
FormatLabel = translate('OpenLP.Ui', 'Format:')
@ -76,10 +65,12 @@ class WizardStrings(object):
PercentSymbolFormat = unicode(translate('OpenLP.Ui', '%p%'))
Ready = translate('OpenLP.Ui', 'Ready.')
StartingImport = translate('OpenLP.Ui', 'Starting import...')
YouSpecifyFile = unicode(translate('OpenLP.Ui', 'You need to specify at '
YouSpecifyFile = unicode(translate('OpenLP.Ui', 'You need to specify one '
'%s file to import from.', 'A file type e.g. OpenSong'))
YouSpecifyFiles = unicode(translate('OpenLP.Ui', 'You need to specify at '
'least one %s file to import from.', 'A file type e.g. OpenSong'))
YouSpecifyFolder = unicode(translate('OpenLP.Ui', 'You need to specify a '
'%s folder to import from.', 'A file type e.g. OpenSong'))
YouSpecifyFolder = unicode(translate('OpenLP.Ui', 'You need to specify one '
'%s folder to import from.', 'A song format e.g. PowerSong'))
class OpenLPWizard(QtGui.QWizard):
@ -108,7 +99,7 @@ class OpenLPWizard(QtGui.QWizard):
def setupUi(self, image):
"""
Set up the wizard UI
Set up the wizard UI.
"""
self.setModal(True)
self.setWizardStyle(QtGui.QWizard.ModernStyle)

View File

@ -89,7 +89,7 @@ class AppLocation(object):
VersionDir = 5
CacheDir = 6
LanguageDir = 7
# Base path where data/config/cache dir is located
BaseDir = None

View File

@ -73,7 +73,7 @@ class CSVBible(BibleDB):
def __init__(self, parent, **kwargs):
"""
Loads a Bible from a set of CVS files.
Loads a Bible from a set of CSV files.
This class assumes the files contain all the information and
a clean bible is being loaded.
"""

View File

@ -198,9 +198,6 @@ class CustomMediaItem(MediaManagerItem):
def generateSlideData(self, service_item, item=None, xmlVersion=False,
remote=False):
raw_footer = []
slide = None
theme = None
item_id = self._getIdOfItemToGenerate(item, self.remoteCustom)
service_item.add_capability(ItemCapabilities.CanEdit)
service_item.add_capability(ItemCapabilities.CanPreview)
@ -221,10 +218,9 @@ class CustomMediaItem(MediaManagerItem):
service_item.add_from_text(slide[:30], slide)
if Settings().value(self.settingsSection + u'/display footer',
QtCore.QVariant(True)).toBool() or credit:
raw_footer.append(title + u' ' + credit)
service_item.raw_footer.append(u' '.join([title, credit]))
else:
raw_footer.append(u'')
service_item.raw_footer = raw_footer
service_item.raw_footer.append(u'')
return True
def onSearchTextButtonClicked(self):

View File

@ -97,7 +97,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.onVerseOrderTextChanged)
QtCore.QObject.connect(self.themeAddButton,
QtCore.SIGNAL(u'clicked()'),
self.mediaitem.plugin.renderer.themeManager.onAddTheme)
self.mediaitem.plugin.renderer.theme_manager.onAddTheme)
QtCore.QObject.connect(self.maintenanceButton,
QtCore.SIGNAL(u'clicked()'), self.onMaintenanceButtonClicked)
QtCore.QObject.connect(self.audioAddFromFileButton,
@ -172,17 +172,14 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
def loadThemes(self, theme_list):
self.themeComboBox.clear()
self.themeComboBox.addItem(u'')
self.themes = []
for theme in theme_list:
self.themeComboBox.addItem(theme)
self.themes.append(theme)
self.themes = theme_list
self.themeComboBox.addItems(theme_list)
set_case_insensitive_completer(self.themes, self.themeComboBox)
def loadMediaFiles(self):
self.audioAddFromMediaButton.setVisible(False)
for plugin in self.parent().pluginManager.plugins:
if plugin.name == u'media' and \
plugin.status == PluginStatus.Active:
if plugin.name == u'media' and plugin.status == PluginStatus.Active:
self.audioAddFromMediaButton.setVisible(True)
self.mediaForm.populateFiles(
plugin.mediaItem.getList(MediaType.Audio))

File diff suppressed because it is too large Load Diff

View File

@ -29,6 +29,9 @@ The :mod:`importer` modules provides the general song import functionality.
"""
import logging
from openlp.core.lib import translate
from openlp.core.lib.ui import UiStrings
from openlp.core.ui.wizard import WizardStrings
from opensongimport import OpenSongImport
from easyslidesimport import EasySlidesImport
from olpimport import OpenLPSongImport
@ -41,6 +44,7 @@ from ewimport import EasyWorshipSongImport
from songbeamerimport import SongBeamerImport
from songshowplusimport import SongShowPlusImport
from foilpresenterimport import FoilPresenterImport
from zionworximport import ZionWorxImport
# Imports that might fail
log = logging.getLogger(__name__)
try:
@ -62,13 +66,58 @@ except ImportError:
log.exception('Error importing %s', 'OooImport')
HAS_OOO = False
class SongFormatSelect(object):
"""
This is a special enumeration class listing available file selection modes.
"""
SingleFile = 0
MultipleFiles = 1
SingleFolder = 2
class SongFormat(object):
"""
This is a special enumeration class that holds the various types of songs,
plus a few helper functions to facilitate generic handling of song types
for importing.
This is a special static class that holds an enumeration of the various
song formats handled by the importer, the attributes of each song format,
and a few helper functions.
Required attributes for each song format:
``u'class'``
Import class, e.g. ``OpenLyricsImport``
``u'name'``
Name of the format, e.g. ``u'OpenLyrics'``
``u'prefix'``
Prefix for Qt objects. Use mixedCase, e.g. ``u'openLyrics'``
See ``SongImportForm.addFileSelectItem()``
Optional attributes for each song format:
``u'canDisable'``
Whether song format importer is disablable.
``u'availability'``
Whether song format importer is available.
``u'selectMode'``
Whether format accepts single file, multiple files, or single folder
(as per ``SongFormatSelect`` options).
``u'filter'``
File extension filter for ``QFileDialog``.
Optional/custom text Strings for ``SongImportForm`` widgets:
``u'comboBoxText'``
Combo box selector (default value is the format's ``u'name'``).
``u'disabledLabelText'``
Required for disablable song formats.
``u'getFilesTitle'``
Title for ``QFileDialog`` (default includes the format's ``u'name'``).
``u'invalidSourceMsg'``
Message displayed if ``isValidSource()`` returns ``False``.
``u'descriptionText'``
Short description (1-2 lines) about the song format.
"""
_format_availability = {}
# Song formats (ordered alphabetically after Generic)
# * Numerical order of song formats is significant as it determines the
# order used by formatComboBox.
Unknown = -1
OpenLyrics = 0
OpenLP2 = 1
@ -85,50 +134,164 @@ class SongFormat(object):
SongShowPlus = 12
SongsOfFellowship = 13
WordsOfWorship = 14
#CSV = 15
ZionWorx = 15
#CSV = 16
# Set optional attribute defaults
__defaults__ = {
u'canDisable': False,
u'availability': True,
u'selectMode': SongFormatSelect.MultipleFiles,
u'filter': u'',
u'comboBoxText': None,
u'disabledLabelText': u'',
u'getFilesTitle': None,
u'invalidSourceMsg': None,
u'descriptionText': None
}
# Set attribute values for each Song Format
__attributes__ = {
OpenLyrics: {
u'class': OpenLyricsImport,
u'name': u'OpenLyrics',
u'prefix': u'openLyrics',
u'filter': u'%s (*.xml)' % translate('SongsPlugin.ImportWizardForm',
'OpenLyrics Files'),
u'comboBoxText': translate('SongsPlugin.ImportWizardForm',
'OpenLyrics or OpenLP 2.0 Exported Song')
},
OpenLP2: {
u'class': OpenLPSongImport,
u'name': UiStrings().OLPV2,
u'prefix': u'openLP2',
u'selectMode': SongFormatSelect.SingleFile,
u'filter': u'%s (*.sqlite)' % (translate(
'SongsPlugin.ImportWizardForm', 'OpenLP 2.0 Databases'))
},
OpenLP1: {
u'name': UiStrings().OLPV1,
u'prefix': u'openLP1',
u'canDisable': True,
u'selectMode': SongFormatSelect.SingleFile,
u'filter': u'%s (*.olp)' % translate('SongsPlugin.ImportWizardForm',
'openlp.org v1.x Databases'),
u'disabledLabelText': WizardStrings.NoSqlite
},
Generic: {
u'name': translate('SongsPlugin.ImportWizardForm',
'Generic Document/Presentation'),
u'prefix': u'generic',
u'canDisable': True,
u'disabledLabelText': translate('SongsPlugin.ImportWizardForm',
'The generic document/presentation importer has been disabled '
'because OpenLP cannot access OpenOffice or LibreOffice.'),
u'getFilesTitle': translate('SongsPlugin.ImportWizardForm',
'Select Document/Presentation Files')
},
CCLI: {
u'class': CCLIFileImport,
u'name': u'CCLI/SongSelect',
u'prefix': u'ccli',
u'filter': u'%s (*.usr *.txt)' % translate(
'SongsPlugin.ImportWizardForm', 'CCLI SongSelect Files')
},
DreamBeam: {
u'class': DreamBeamImport,
u'name': u'DreamBeam',
u'prefix': u'dreamBeam',
u'filter': u'%s (*.xml)' % translate('SongsPlugin.ImportWizardForm',
'DreamBeam Song Files')
},
EasySlides: {
u'class': EasySlidesImport,
u'name': u'EasySlides',
u'prefix': u'easySlides',
u'selectMode': SongFormatSelect.SingleFile,
u'filter': u'%s (*.xml)' % translate('SongsPlugin.ImportWizardForm',
'EasySlides XML File')
},
EasyWorship: {
u'class': EasyWorshipSongImport,
u'name': u'EasyWorship',
u'prefix': u'ew',
u'selectMode': SongFormatSelect.SingleFile,
u'filter': u'%s (*.db)' % translate('SongsPlugin.ImportWizardForm',
'EasyWorship Song Database')
},
FoilPresenter: {
u'class': FoilPresenterImport,
u'name': u'Foilpresenter',
u'prefix': u'foilPresenter',
u'filter': u'%s (*.foil)' % translate(
'SongsPlugin.ImportWizardForm', 'Foilpresenter Song Files')
},
OpenSong: {
u'class': OpenSongImport,
u'name': WizardStrings.OS,
u'prefix': u'openSong'
},
PowerSong: {
u'class': PowerSongImport,
u'name': u'PowerSong 1.0',
u'prefix': u'powerSong',
u'selectMode': SongFormatSelect.SingleFolder,
u'invalidSourceMsg': translate('SongsPlugin.ImportWizardForm',
'You need to specify a valid PowerSong 1.0 database folder.')
},
SongBeamer: {
u'class': SongBeamerImport,
u'name': u'SongBeamer',
u'prefix': u'songBeamer',
u'filter': u'%s (*.sng)' % translate('SongsPlugin.ImportWizardForm',
'SongBeamer Files')
},
SongShowPlus: {
u'class': SongShowPlusImport,
u'name': u'SongShow Plus',
u'prefix': u'songShowPlus',
u'filter': u'%s (*.sbsong)' % translate(
'SongsPlugin.ImportWizardForm', 'SongShow Plus Song Files')
},
SongsOfFellowship: {
u'name': u'Songs of Fellowship',
u'prefix': u'songsOfFellowship',
u'canDisable': True,
u'filter': u'%s (*.rtf)' % translate('SongsPlugin.ImportWizardForm',
'Songs Of Fellowship Song Files'),
u'disabledLabelText': translate('SongsPlugin.ImportWizardForm',
'The Songs of Fellowship importer has been disabled because '
'OpenLP cannot access OpenOffice or LibreOffice.')
},
WordsOfWorship: {
u'class': WowImport,
u'name': u'Words of Worship',
u'prefix': u'wordsOfWorship',
u'filter': u'%s (*.wsg *.wow-song)' % translate(
'SongsPlugin.ImportWizardForm', 'Words Of Worship Song Files')
},
ZionWorx: {
u'class': ZionWorxImport,
u'name': u'ZionWorx',
u'prefix': u'zionWorx',
u'selectMode': SongFormatSelect.SingleFile,
u'comboBoxText': translate('SongsPlugin.ImportWizardForm',
'ZionWorx (CSV)'),
u'descriptionText': translate('SongsPlugin.ImportWizardForm',
'First convert your ZionWorx database to a CSV text file, as '
'explained in the <a href="http://manual.openlp.org/songs.html'
'#importing-from-zionworx">User Manual</a>.')
# },
# CSV: {
# u'class': CSVImport,
# u'name': WizardStrings.CSV,
# u'prefix': u'csv',
# u'selectMode': SongFormatSelect.SingleFile
}
}
@staticmethod
def get_class(format):
"""
Return the appropriate implementation class.
``format``
The song format.
"""
if format == SongFormat.OpenLP2:
return OpenLPSongImport
elif format == SongFormat.OpenLP1:
return OpenLP1SongImport
elif format == SongFormat.OpenLyrics:
return OpenLyricsImport
elif format == SongFormat.OpenSong:
return OpenSongImport
elif format == SongFormat.SongsOfFellowship:
return SofImport
elif format == SongFormat.WordsOfWorship:
return WowImport
elif format == SongFormat.Generic:
return OooImport
elif format == SongFormat.CCLI:
return CCLIFileImport
elif format == SongFormat.DreamBeam:
return DreamBeamImport
elif format == SongFormat.PowerSong:
return PowerSongImport
elif format == SongFormat.EasySlides:
return EasySlidesImport
elif format == SongFormat.EasyWorship:
return EasyWorshipSongImport
elif format == SongFormat.SongBeamer:
return SongBeamerImport
elif format == SongFormat.SongShowPlus:
return SongShowPlusImport
elif format == SongFormat.FoilPresenter:
return FoilPresenterImport
return None
@staticmethod
def get_formats_list():
def get_format_list():
"""
Return a list of the supported song formats.
"""
@ -138,7 +301,7 @@ class SongFormat(object):
SongFormat.OpenLP1,
SongFormat.Generic,
SongFormat.CCLI,
SongFormat.DreamBeam,
SongFormat.DreamBeam,
SongFormat.EasySlides,
SongFormat.EasyWorship,
SongFormat.FoilPresenter,
@ -147,26 +310,55 @@ class SongFormat(object):
SongFormat.SongBeamer,
SongFormat.SongShowPlus,
SongFormat.SongsOfFellowship,
SongFormat.WordsOfWorship
SongFormat.WordsOfWorship,
SongFormat.ZionWorx
]
@staticmethod
def get(format, *attributes):
"""
Return requested song format attribute(s).
``format``
A song format from SongFormat.
``*attributes``
Zero or more song format attributes from SongFormat.
Return type depends on number of supplied attributes:
:0: Return dict containing all defined attributes for the format.
:1: Return the attribute value.
:>1: Return tuple of requested attribute values.
"""
if not attributes:
return SongFormat.__attributes__.get(format)
elif len(attributes) == 1:
default = SongFormat.__defaults__.get(attributes[0])
return SongFormat.__attributes__[format].get(attributes[0],
default)
else:
values = []
for attr in attributes:
default = SongFormat.__defaults__.get(attr)
values.append(SongFormat.__attributes__[format].get(attr,
default))
return tuple(values)
@staticmethod
def set_availability(format, available):
def set(format, attribute, value):
"""
Set the availability for a given song format.
Set specified song format attribute to the supplied value.
"""
SongFormat._format_availability[format] = available
SongFormat.__attributes__[format][attribute] = value
@staticmethod
def get_availability(format):
"""
Return the availability of a given song format.
"""
return SongFormat._format_availability.get(format, True)
SongFormat.set_availability(SongFormat.OpenLP1, HAS_OPENLP1)
SongFormat.set_availability(SongFormat.SongsOfFellowship, HAS_SOF)
SongFormat.set_availability(SongFormat.Generic, HAS_OOO)
__all__ = [u'SongFormat']
SongFormat.set(SongFormat.OpenLP1, u'availability', HAS_OPENLP1)
if HAS_OPENLP1:
SongFormat.set(SongFormat.OpenLP1, u'class', OpenLP1SongImport)
SongFormat.set(SongFormat.SongsOfFellowship, u'availability', HAS_SOF)
if HAS_SOF:
SongFormat.set(SongFormat.SongsOfFellowship, u'class', SofImport)
SongFormat.set(SongFormat.Generic, u'availability', HAS_OOO)
if HAS_OOO:
SongFormat.set(SongFormat.Generic, u'class', OooImport)
__all__ = [u'SongFormat', u'SongFormatSelect']

View File

@ -33,7 +33,6 @@ import fnmatch
import os
from openlp.core.lib import translate
from openlp.core.ui.wizard import WizardStrings
from openlp.plugins.songs.lib.songimport import SongImport
log = logging.getLogger(__name__)
@ -71,26 +70,25 @@ class PowerSongImport(SongImport):
* .song
"""
@staticmethod
def isValidSource(**kwargs):
def isValidSource(import_source):
"""
Checks if source is a PowerSong 1.0 folder:
* is a directory
* contains at least one *.song file
"""
if u'folder' in kwargs:
dir = kwargs[u'folder']
if os.path.isdir(dir):
for file in os.listdir(dir):
if fnmatch.fnmatch(file, u'*.song'):
return True
if os.path.isdir(import_source):
for file in os.listdir(import_source):
if fnmatch.fnmatch(file, u'*.song'):
return True
return False
def doImport(self):
"""
Receive either a list of files or a folder (unicode) to import.
"""
from importer import SongFormat
PS_string = SongFormat.get(SongFormat.PowerSong, u'name')
if isinstance(self.importSource, unicode):
if os.path.isdir(self.importSource):
dir = self.importSource
@ -104,7 +102,7 @@ class PowerSongImport(SongImport):
self.logError(unicode(translate('SongsPlugin.PowerSongImport',
'No songs to import.')),
unicode(translate('SongsPlugin.PowerSongImport',
'No %s files found.' % WizardStrings.PS)))
'No %s files found.' % PS_string)))
return
self.importWizard.progressBar.setMaximum(len(self.importSource))
for file in self.importSource:
@ -124,7 +122,7 @@ class PowerSongImport(SongImport):
self.logError(os.path.basename(file), unicode(
translate('SongsPlugin.PowerSongImport',
'Invalid %s file. Unexpected byte value.'
% WizardStrings.PS)))
% PS_string)))
break
else:
if label == u'TITLE':
@ -142,15 +140,14 @@ class PowerSongImport(SongImport):
if not self.title:
self.logError(os.path.basename(file), unicode(
translate('SongsPlugin.PowerSongImport',
'Invalid %s file. Missing "TITLE" header.'
% WizardStrings.PS)))
'Invalid %s file. Missing "TITLE" header.' % PS_string)))
continue
# Check that file had COPYRIGHTLINE label
if not found_copyright:
self.logError(self.title, unicode(
translate('SongsPlugin.PowerSongImport',
'Invalid %s file. Missing "COPYRIGHTLINE" '
'header.' % WizardStrings.PS)))
'header.' % PS_string)))
continue
# Check that file had at least one verse
if not self.verses:

View File

@ -51,11 +51,11 @@ class SongImport(QtCore.QObject):
as necessary
"""
@staticmethod
def isValidSource(**kwargs):
def isValidSource(import_source):
"""
Override this method to validate the source prior to import.
"""
pass
return True
def __init__(self, manager, **kwargs):
"""

View File

@ -0,0 +1,142 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2012 Raoul Snyman #
# Portions copyright (c) 2008-2012 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, #
# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias #
# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund #
# --------------------------------------------------------------------------- #
# 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:`zionworximport` module provides the functionality for importing
ZionWorx songs into the OpenLP database.
"""
import csv
import logging
from openlp.core.lib import translate
from openlp.plugins.songs.lib.songimport import SongImport
log = logging.getLogger(__name__)
class ZionWorxImport(SongImport):
"""
The :class:`ZionWorxImport` class provides the ability to import songs
from ZionWorx, via a dump of the ZionWorx database to a CSV file.
ZionWorx song database fields:
* ``SongNum`` Song ID. (Discarded by importer)
* ``Title1`` Main Title.
* ``Title2`` Alternate Title.
* ``Lyrics`` Song verses, separated by blank lines.
* ``Writer`` Song author(s).
* ``Copyright`` Copyright information
* ``Keywords`` (Discarded by importer)
* ``DefaultStyle`` (Discarded by importer)
ZionWorx has no native export function; it uses the proprietary TurboDB
database engine. The TurboDB vendor, dataWeb, provides tools which can
export TurboDB tables to other formats, such as freeware console tool
TurboDB Data Exchange which is available for Windows and Linux. This command
exports the ZionWorx songs table to a CSV file:
``tdbdatax MainTable.dat songstable.csv -fsdf -s, -qd``
* -f Table format: ``sdf`` denotes text file.
* -s Separator character between fields.
* -q Quote character surrounding fields. ``d`` denotes double-quote.
CSV format expected by importer:
* Field separator character is comma ``,``
* Fields surrounded by double-quotes ``"``. This enables fields (such as
Lyrics) to include new-lines and commas. Double-quotes within a field
are denoted by two double-quotes ``""``
* Note: This is the default format of the Python ``csv`` module.
"""
def doImport(self):
"""
Receive a CSV file (from a ZionWorx database dump) to import.
"""
# Used to strip control chars (10=LF, 13=CR, 127=DEL)
self.control_chars_map = dict.fromkeys(
range(10) + [11, 12] + range(14,32) + [127])
with open(self.importSource, 'rb') as songs_file:
fieldnames = [u'SongNum', u'Title1', u'Title2', u'Lyrics',
u'Writer', u'Copyright', u'Keywords', u'DefaultStyle']
songs_reader = csv.DictReader(songs_file, fieldnames)
try:
records = list(songs_reader)
except csv.Error, e:
self.logError(unicode(translate('SongsPlugin.ZionWorxImport',
'Error reading CSV file.')),
unicode(translate('SongsPlugin.ZionWorxImport',
'Line %d: %s' % (songs_reader.line_num, e))))
return
num_records = len(records)
log.info(u'%s records found in CSV file' % num_records)
self.importWizard.progressBar.setMaximum(num_records)
for index, record in enumerate(records, 1):
if self.stopImportFlag:
return
self.setDefaults()
try:
self.title = self._decode(record[u'Title1'])
if record[u'Title2']:
self.alternateTitle = self._decode(record[u'Title2'])
self.parseAuthor(self._decode(record[u'Writer']))
self.addCopyright(self._decode(record[u'Copyright']))
lyrics = self._decode(record[u'Lyrics'])
except UnicodeDecodeError, e:
self.logError(unicode(translate(
'SongsPlugin.ZionWorxImport', 'Record %d' % index)),
unicode(translate('SongsPlugin.ZionWorxImport',
'Decoding error: %s' % e)))
continue
except TypeError, e:
self.logError(unicode(translate(
'SongsPlugin.ZionWorxImport', 'File not valid ZionWorx '
'CSV format.')), u'TypeError: %s' % e)
return
verse = u''
for line in lyrics.splitlines():
if line and not line.isspace():
verse += line + u'\n'
elif verse:
self.addVerse(verse)
verse = u''
if verse:
self.addVerse(verse)
title = self.title
if not self.finish():
self.logError(unicode(translate(
'SongsPlugin.ZionWorxImport', 'Record %d' % index))
+ (u': "' + title + u'"' if title else u''))
def _decode(self, str):
"""
Decodes CSV input to unicode, stripping all control characters (except
new lines).
"""
# This encoding choice seems OK. ZionWorx has no option for setting the
# encoding for its songs, so we assume encoding is always the same.
return unicode(str, u'cp1252').translate(self.control_chars_map)

View File

@ -194,7 +194,7 @@ class SongsPlugin(Plugin):
self.manager.save_object(song)
def importSongs(self, format, **kwargs):
class_ = SongFormat.get_class(format)
class_ = SongFormat.get(format, u'class')
importer = class_(self.manager, **kwargs)
importer.register(self.mediaItem.importWizard)
return importer