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>> <<ACTION>>
)`` )``
""" """
eventreceiver = EventReceiver() __eventreceiver__ = EventReceiver()
@staticmethod @staticmethod
def send_message(event, msg=None): def send_message(event, msg=None):
@ -281,11 +281,11 @@ class Receiver(object):
``msg`` ``msg``
Defaults to *None*. The message to send with the event. Defaults to *None*. The message to send with the event.
""" """
Receiver.eventreceiver.send_message(event, msg) Receiver.__eventreceiver__.send_message(event, msg)
@staticmethod @staticmethod
def get_receiver(): 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. and executes all the hooks, as and when necessary.
""" """
log.info(u'Plugin manager loaded') log.info(u'Plugin manager loaded')
__instance__ = None
@staticmethod @staticmethod
def get_instance(): def get_instance():
""" """
Obtain a single instance of class. Obtain a single instance of class.
""" """
return PluginManager.instance return PluginManager.__instance__
def __init__(self, plugin_dir): def __init__(self, plugin_dir):
""" """
@ -58,7 +58,7 @@ class PluginManager(object):
The directory to search for plugins. The directory to search for plugins.
""" """
log.info(u'Plugin manager Initialising') log.info(u'Plugin manager Initialising')
PluginManager.instance = self PluginManager.__instance__ = self
if not plugin_dir in sys.path: if not plugin_dir in sys.path:
log.debug(u'Inserting %s into sys.path', plugin_dir) log.debug(u'Inserting %s into sys.path', plugin_dir)
sys.path.insert(0, plugin_dir) sys.path.insert(0, plugin_dir)

View File

@ -55,29 +55,32 @@ class Renderer(object):
""" """
log.info(u'Renderer Loaded') log.info(u'Renderer Loaded')
def __init__(self, imageManager, themeManager): def __init__(self, image_manager, theme_manager):
""" """
Initialise the renderer. Initialise the renderer.
``imageManager`` ``image_manager``
A imageManager instance which takes care of e. g. caching and resizing A image_manager instance which takes care of e. g. caching and
images. resizing images.
``themeManager`` ``theme_manager``
The themeManager instance, used to get the current theme details. The theme_manager instance, used to get the current theme details.
""" """
log.debug(u'Initialisation started') log.debug(u'Initialisation started')
self.themeManager = themeManager self.theme_manager = theme_manager
self.imageManager = imageManager self.image_manager = image_manager
self.screens = ScreenList() self.screens = ScreenList()
self.service_theme = u'' self.theme_level = ThemeLevel.Global
self.theme_level = u'' self.global_theme_name = u''
self.override_background = None self.service_theme_name = u''
self.theme_data = None self.item_theme_name = u''
self.bg_frame = None
self.force_page = False 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.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): def update_display(self):
""" """
@ -87,100 +90,132 @@ class Renderer(object):
self._calculate_default() self._calculate_default()
if self.display: if self.display:
self.display.close() self.display.close()
self.display = MainDisplay(None, self.imageManager, False, self) self.display = MainDisplay(None, self.image_manager, False, self)
self.display.setup() self.display.setup()
self.bg_frame = None self._theme_dimensions = {}
self.theme_data = None
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`` ``theme_name``
The global-level theme to be set. 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`` ``theme_level``
Defaults to ``ThemeLevel.Global``. The theme level, can be The theme level to be used.
``ThemeLevel.Global``, ``ThemeLevel.Service`` or
``ThemeLevel.Song``.
""" """
self.global_theme = global_theme
self.theme_level = theme_level 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. Set the service-level theme.
``service_theme`` ``service_theme_name``
The service-level theme to be set. The service level theme's name.
""" """
self.service_theme = service_theme self._set_theme(service_theme_name)
self.theme_data = None self.service_theme_name = service_theme_name
self._cache_background_image(self.themeManager.getThemeData
(service_theme))
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`` ``item_theme_name``
The theme object containing the theme data. The item theme's name.
""" """
# if No file do not update cache self._set_theme(item_theme_name)
if temp_theme.background_filename: self.item_theme_name = item_theme_name
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
def generate_preview(self, theme_data, force_page=False): def generate_preview(self, theme_data, force_page=False):
""" """
@ -195,27 +230,31 @@ class Renderer(object):
log.debug(u'generate preview') log.debug(u'generate preview')
# save value for use in format_slide # save value for use in format_slide
self.force_page = force_page self.force_page = force_page
# set the default image size for previews
self._calculate_default()
# build a service item to generate preview # build a service item to generate preview
serviceItem = ServiceItem() serviceItem = ServiceItem()
serviceItem.theme = theme_data
if self.force_page: if self.force_page:
# make big page for theme edit dialog to get line count # make big page for theme edit dialog to get line count
serviceItem.add_from_text(u'', VERSE_FOR_LINE_COUNT) serviceItem.add_from_text(u'', VERSE_FOR_LINE_COUNT)
else: else:
self.imageManager.deleteImage(theme_data.theme_name) self.image_manager.deleteImage(theme_data.theme_name)
serviceItem.add_from_text(u'', VERSE) serviceItem.add_from_text(u'', VERSE)
serviceItem.renderer = self serviceItem.renderer = self
serviceItem.raw_footer = FOOTER 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) serviceItem.render(True)
if not self.force_page: if not self.force_page:
self.display.buildHtml(serviceItem) self.display.buildHtml(serviceItem)
raw_html = serviceItem.get_rendered_frame(0) raw_html = serviceItem.get_rendered_frame(0)
self.display.text(raw_html) self.display.text(raw_html)
preview = self.display.preview() preview = self.display.preview()
# Reset the real screen size for subsequent render requests
self._calculate_default()
return preview return preview
self.force_page = False self.force_page = False
@ -264,7 +303,7 @@ class Renderer(object):
try: try:
text_to_render, text = \ text_to_render, text = \
text.split(u'\n[---]\n', 1) text.split(u'\n[---]\n', 1)
except: except ValueError:
text_to_render = text.split(u'\n[---]\n')[0] text_to_render = text.split(u'\n[---]\n')[0]
text = u'' text = u''
text_to_render, raw_tags, html_tags = \ text_to_render, raw_tags, html_tags = \
@ -315,52 +354,41 @@ class Renderer(object):
# 90% is start of footer # 90% is start of footer
self.footer_start = int(self.height * 0.90) self.footer_start = int(self.height * 0.90)
def _build_text_rectangle(self, theme): def get_main_rectangle(self, theme_data):
"""
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):
""" """
Calculates the placement and size of the main rectangle. Calculates the placement and size of the main rectangle.
``theme`` ``theme_data``
The theme information The theme information
""" """
if not theme.font_main_override: if not theme_data.font_main_override:
return QtCore.QRect(10, 0, self.width - 20, self.footer_start) return QtCore.QRect(10, 0, self.width, self.footer_start)
else: else:
return QtCore.QRect(theme.font_main_x, theme.font_main_y, return QtCore.QRect(theme_data.font_main_x, theme_data.font_main_y,
theme.font_main_width - 1, theme.font_main_height - 1) 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. Calculates the placement and size of the footer rectangle.
``theme`` ``theme_data``
The theme information 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, return QtCore.QRect(10, self.footer_start, self.width - 20,
self.height - self.footer_start) self.height - self.footer_start)
else: else:
return QtCore.QRect(theme.font_footer_x, return QtCore.QRect(theme_data.font_footer_x,
theme.font_footer_y, theme.font_footer_width - 1, theme_data.font_footer_y, theme_data.font_footer_width - 1,
theme.font_footer_height - 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. Sets the rectangle within which text should be rendered.
``theme_data``
The theme data.
``rect_main`` ``rect_main``
The main text block. The main text block.
@ -372,9 +400,9 @@ class Renderer(object):
self._rect_footer = rect_footer self._rect_footer = rect_footer
self.page_width = self._rect.width() self.page_width = self._rect.width()
self.page_height = self._rect.height() self.page_height = self._rect.height()
if self.theme_data.font_main_shadow: if theme_data.font_main_shadow:
self.page_width -= int(self.theme_data.font_main_shadow_size) self.page_width -= int(theme_data.font_main_shadow_size)
self.page_height -= int(self.theme_data.font_main_shadow_size) self.page_height -= int(theme_data.font_main_shadow_size)
self.web = QtWebKit.QWebView() self.web = QtWebKit.QWebView()
self.web.setVisible(False) self.web.setVisible(False)
self.web.resize(self.page_width, self.page_height) self.web.resize(self.page_width, self.page_height)
@ -392,8 +420,8 @@ class Renderer(object):
</script><style>*{margin: 0; padding: 0; border: 0;} </script><style>*{margin: 0; padding: 0; border: 0;}
#main {position: absolute; top: 0px; %s %s}</style></head><body> #main {position: absolute; top: 0px; %s %s}</style></head><body>
<div id="main"></div></body></html>""" % \ <div id="main"></div></body></html>""" % \
(build_lyrics_format_css(self.theme_data, self.page_width, (build_lyrics_format_css(theme_data, self.page_width,
self.page_height), build_lyrics_outline_css(self.theme_data)) self.page_height), build_lyrics_outline_css(theme_data))
self.web.setHtml(html) self.web.setHtml(html)
self.empty_height = self.web_frame.contentsSize().height() self.empty_height = self.web_frame.contentsSize().height()

View File

@ -158,19 +158,24 @@ class ServiceItem(object):
self.icon = icon self.icon = icon
self.iconic_representation = build_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 The render method is what generates the frames for the screen and
obtains the display information from the renderer. At this point all obtains the display information from the renderer. At this point all
slides are built for the given display size. 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') log.debug(u'Render called')
self._display_frames = [] self._display_frames = []
self.bg_image_bytes = None self.bg_image_bytes = None
theme = self.theme if self.theme else None if not provides_own_theme_data:
self.main, self.footer = \ self.renderer.set_item_theme(self.theme)
self.renderer.set_override_theme(theme, use_override) self.themedata, self.main, self.footer = self.renderer.pre_render()
self.themedata = self.renderer.theme_data
if self.service_item_type == ServiceItemType.Text: if self.service_item_type == ServiceItemType.Text:
log.debug(u'Formatting slides') log.debug(u'Formatting slides')
for slide in self._raw_frames: for slide in self._raw_frames:
@ -211,7 +216,7 @@ class ServiceItem(object):
self.image_border = background self.image_border = background
self.service_item_type = ServiceItemType.Image self.service_item_type = ServiceItemType.Image
self._raw_frames.append({u'title': title, u'path': path}) 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.image_border)
self._new_item() self._new_item()

View File

@ -80,6 +80,10 @@ class UiStrings(object):
self.Help = translate('OpenLP.Ui', 'Help') self.Help = translate('OpenLP.Ui', 'Help')
self.Hours = translate('OpenLP.Ui', 'h', self.Hours = translate('OpenLP.Ui', 'h',
'The abbreviated unit for hours') '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.Image = translate('OpenLP.Ui', 'Image')
self.Import = translate('OpenLP.Ui', 'Import') self.Import = translate('OpenLP.Ui', 'Import')
self.LayoutStyle = translate('OpenLP.Ui', 'Layout style:') self.LayoutStyle = translate('OpenLP.Ui', 'Layout style:')

View File

@ -31,11 +31,16 @@ from datetime import datetime, timedelta
from PyQt4 import QtCore, QtGui 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 import SettingsTab, translate, build_icon, Receiver
from openlp.core.lib.settings import Settings from openlp.core.lib.settings import Settings
from openlp.core.lib.ui import UiStrings 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.lib import SlideLimits
from openlp.core.utils import get_images_filter
log = logging.getLogger(__name__)
class AdvancedTab(SettingsTab): class AdvancedTab(SettingsTab):
""" """
@ -60,6 +65,7 @@ class AdvancedTab(SettingsTab):
'#strftime-strptime-behavior for more information.')) '#strftime-strptime-behavior for more information.'))
self.defaultImage = u':/graphics/openlp-splash-screen.png' self.defaultImage = u':/graphics/openlp-splash-screen.png'
self.defaultColor = u'#ffffff' self.defaultColor = u'#ffffff'
self.dataExists = False
self.iconPath = u':/system/system_settings.png' self.iconPath = u':/system/system_settings.png'
advanced_translated = translate('OpenLP.AdvancedTab', 'Advanced') advanced_translated = translate('OpenLP.AdvancedTab', 'Advanced')
SettingsTab.__init__(self, parent, u'Advanced', advanced_translated) SettingsTab.__init__(self, parent, u'Advanced', advanced_translated)
@ -152,6 +158,71 @@ class AdvancedTab(SettingsTab):
self.serviceNameLayout.addRow(self.serviceNameExampleLabel, self.serviceNameLayout.addRow(self.serviceNameExampleLabel,
self.serviceNameExample) self.serviceNameExample)
self.leftLayout.addWidget(self.serviceNameGroupBox) 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() self.leftLayout.addStretch()
# Default Image # Default Image
self.defaultImageGroupBox = QtGui.QGroupBox(self.rightColumn) self.defaultImageGroupBox = QtGui.QGroupBox(self.rightColumn)
@ -220,7 +291,6 @@ class AdvancedTab(SettingsTab):
self.x11Layout.addWidget(self.x11BypassCheckBox) self.x11Layout.addWidget(self.x11BypassCheckBox)
self.rightLayout.addWidget(self.x11GroupBox) self.rightLayout.addWidget(self.x11GroupBox)
self.rightLayout.addStretch() self.rightLayout.addStretch()
self.shouldUpdateServiceNameExample = False self.shouldUpdateServiceNameExample = False
QtCore.QObject.connect(self.serviceNameCheckBox, QtCore.QObject.connect(self.serviceNameCheckBox,
QtCore.SIGNAL(u'toggled(bool)'), self.serviceNameCheckBoxToggled) QtCore.SIGNAL(u'toggled(bool)'), self.serviceNameCheckBoxToggled)
@ -244,6 +314,18 @@ class AdvancedTab(SettingsTab):
QtCore.SIGNAL(u'clicked()'), self.onDefaultRevertButtonClicked) QtCore.SIGNAL(u'clicked()'), self.onDefaultRevertButtonClicked)
QtCore.QObject.connect(self.x11BypassCheckBox, QtCore.QObject.connect(self.x11BypassCheckBox,
QtCore.SIGNAL(u'toggled(bool)'), self.onX11BypassCheckBoxToggled) 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.QObject.connect(self.endSlideRadioButton,
QtCore.SIGNAL(u'clicked()'), self.onEndSlideButtonClicked) QtCore.SIGNAL(u'clicked()'), self.onEndSlideButtonClicked)
QtCore.QObject.connect(self.wrapSlideRadioButton, QtCore.QObject.connect(self.wrapSlideRadioButton,
@ -258,6 +340,8 @@ class AdvancedTab(SettingsTab):
self.tabTitleVisible = UiStrings().Advanced self.tabTitleVisible = UiStrings().Advanced
self.uiGroupBox.setTitle( self.uiGroupBox.setTitle(
translate('OpenLP.AdvancedTab', 'UI Settings')) translate('OpenLP.AdvancedTab', 'UI Settings'))
self.dataDirectoryGroupBox.setTitle(
translate('OpenLP.AdvancedTab', 'Data Location'))
self.recentLabel.setText( self.recentLabel.setText(
translate('OpenLP.AdvancedTab', translate('OpenLP.AdvancedTab',
'Number of recent files to display:')) 'Number of recent files to display:'))
@ -321,6 +405,32 @@ class AdvancedTab(SettingsTab):
'Browse for an image file to display.')) 'Browse for an image file to display.'))
self.defaultRevertButton.setToolTip(translate('OpenLP.AdvancedTab', self.defaultRevertButton.setToolTip(translate('OpenLP.AdvancedTab',
'Revert to the default OpenLP logo.')) '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', self.x11GroupBox.setTitle(translate('OpenLP.AdvancedTab',
'X11')) 'X11'))
self.x11BypassCheckBox.setText(translate('OpenLP.AdvancedTab', self.x11BypassCheckBox.setText(translate('OpenLP.AdvancedTab',
@ -398,6 +508,40 @@ class AdvancedTab(SettingsTab):
else: else:
self.nextItemRadioButton.setChecked(True) self.nextItemRadioButton.setChecked(True)
settings.endGroup() 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( self.defaultColorButton.setStyleSheet(
u'background-color: %s' % self.defaultColor) u'background-color: %s' % self.defaultColor)
@ -447,6 +591,11 @@ class AdvancedTab(SettingsTab):
self.displayChanged = False self.displayChanged = False
Receiver.send_message(u'slidecontroller_update_slide_limits') 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): def serviceNameCheckBoxToggled(self, default_service_enabled):
self.serviceNameDay.setEnabled(default_service_enabled) self.serviceNameDay.setEnabled(default_service_enabled)
time_enabled = default_service_enabled and \ time_enabled = default_service_enabled and \
@ -508,6 +657,122 @@ class AdvancedTab(SettingsTab):
self.defaultFileEdit.setText(filename) self.defaultFileEdit.setText(filename)
self.defaultFileEdit.setFocus() 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): def onDefaultRevertButtonClicked(self):
self.defaultFileEdit.setText(u':/graphics/openlp-splash-screen.png') self.defaultFileEdit.setText(u':/graphics/openlp-splash-screen.png')
self.defaultFileEdit.setFocus() self.defaultFileEdit.setFocus()

View File

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

View File

@ -29,6 +29,8 @@ import logging
import os import os
import sys import sys
import shutil import shutil
from distutils import dir_util
from distutils.errors import DistutilsFileError
from tempfile import gettempdir from tempfile import gettempdir
import time import time
from datetime import datetime 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. # Once settings are loaded update the menu with the recent files.
self.updateRecentFilesMenu() self.updateRecentFilesMenu()
self.pluginForm = PluginForm(self) self.pluginForm = PluginForm(self)
self.newDataPath = u''
self.copyData = False
# Set up signals and slots # Set up signals and slots
QtCore.QObject.connect(self.importThemeItem, QtCore.QObject.connect(self.importThemeItem,
QtCore.SIGNAL(u'triggered()'), QtCore.SIGNAL(u'triggered()'),
@ -635,6 +639,8 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
QtCore.SIGNAL(u'config_screen_changed'), self.screenChanged) QtCore.SIGNAL(u'config_screen_changed'), self.screenChanged)
QtCore.QObject.connect(Receiver.get_receiver(), QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'mainwindow_status_text'), self.showStatusMessage) QtCore.SIGNAL(u'mainwindow_status_text'), self.showStatusMessage)
QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'cleanup'), self.cleanUp)
# Media Manager # Media Manager
QtCore.QObject.connect(self.mediaToolBox, QtCore.QObject.connect(self.mediaToolBox,
QtCore.SIGNAL(u'currentChanged(int)'), self.onMediaToolBoxChanged) QtCore.SIGNAL(u'currentChanged(int)'), self.onMediaToolBoxChanged)
@ -647,6 +653,10 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
QtCore.QObject.connect(Receiver.get_receiver(), QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'openlp_information_message'), QtCore.SIGNAL(u'openlp_information_message'),
self.onInformationMessage) 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 # warning cyclic dependency
# renderer needs to call ThemeManager and # renderer needs to call ThemeManager and
# ThemeManager needs to call Renderer # ThemeManager needs to call Renderer
@ -1199,6 +1209,9 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
if save_settings: if save_settings:
# Save settings # Save settings
self.saveSettings() self.saveSettings()
# Check if we need to change the data directory
if self.newDataPath:
self.changeDataDirectory()
# Close down the display # Close down the display
if self.liveController.display: if self.liveController.display:
self.liveController.display.close() self.liveController.display.close()
@ -1475,3 +1488,45 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
self.timer_id = 0 self.timer_id = 0
self.loadProgressBar.hide() self.loadProgressBar.hide()
Receiver.send_message(u'openlp_process_events') 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 \ if controller.isLive and \
(Settings().value(u'general/auto unblank', (Settings().value(u'general/auto unblank',
QtCore.QVariant(False)).toBool() or \ QtCore.QVariant(False)).toBool() or \
controller.media_info.is_background == True) or \ controller.media_info.is_background) or not controller.isLive:
controller.isLive == False:
if not self.video_play([controller]): if not self.video_play([controller]):
critical_error_message_box( critical_error_message_box(
translate('MediaPlugin.MediaItem', 'Unsupported File'), translate('MediaPlugin.MediaItem', 'Unsupported File'),

View File

@ -101,7 +101,7 @@ class PhononPlayer(MediaPlayer):
display.mediaObject = Phonon.MediaObject(display) display.mediaObject = Phonon.MediaObject(display)
Phonon.createPath(display.mediaObject, display.phononWidget) Phonon.createPath(display.mediaObject, display.phononWidget)
if display.hasAudio: if display.hasAudio:
display.audio = Phonon.AudioOutput( \ display.audio = Phonon.AudioOutput(
Phonon.VideoCategory, display.mediaObject) Phonon.VideoCategory, display.mediaObject)
Phonon.createPath(display.mediaObject, display.audio) Phonon.createPath(display.mediaObject, display.audio)
display.phononWidget.raise_() display.phononWidget.raise_()
@ -148,18 +148,17 @@ class PhononPlayer(MediaPlayer):
controller.media_info.start_time > 0: controller.media_info.start_time > 0:
start_time = controller.media_info.start_time start_time = controller.media_info.start_time
display.mediaObject.play() display.mediaObject.play()
if self.media_state_wait(display, Phonon.PlayingState): if not self.media_state_wait(display, Phonon.PlayingState):
return False
if start_time > 0: if start_time > 0:
self.seek(display, controller.media_info.start_time*1000) self.seek(display, controller.media_info.start_time * 1000)
self.volume(display, controller.media_info.volume) self.volume(display, controller.media_info.volume)
controller.media_info.length = \ controller.media_info.length = \
int(display.mediaObject.totalTime()/1000) int(display.mediaObject.totalTime() / 1000)
controller.seekSlider.setMaximum(controller.media_info.length*1000) controller.seekSlider.setMaximum(controller.media_info.length * 1000)
self.state = MediaState.Playing self.state = MediaState.Playing
display.phononWidget.raise_() display.phononWidget.raise_()
return True return True
else:
return False
def pause(self, display): def pause(self, display):
display.mediaObject.pause() display.mediaObject.pause()
@ -198,9 +197,9 @@ class PhononPlayer(MediaPlayer):
controller = display.controller controller = display.controller
if controller.media_info.end_time > 0: if controller.media_info.end_time > 0:
if display.mediaObject.currentTime() > \ if display.mediaObject.currentTime() > \
controller.media_info.end_time*1000: controller.media_info.end_time * 1000:
self.stop(display) self.stop(display)
self.set_visible(display, False) self.set_visible(display, False)
if not controller.seekSlider.isSliderDown(): if not controller.seekSlider.isSliderDown():
controller.seekSlider.setSliderPosition( \ controller.seekSlider.setSliderPosition(
display.mediaObject.currentTime()) display.mediaObject.currentTime())

View File

@ -48,7 +48,7 @@ import sys
from inspect import getargspec from inspect import getargspec
__version__ = "N/A" __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 # Internal guard to prevent internal classes to be directly
# instanciated. # instanciated.
@ -905,10 +905,10 @@ class Instance(_Ctype):
def media_new(self, mrl, *options): def media_new(self, mrl, *options):
"""Create a new Media instance. """Create a new Media instance.
If mrl contains a colon (:), it will be treated as a If mrl contains a colon (:) preceded by more than 1 letter, it
URL. Else, it will be considered as a local path. If you need will be treated as a URL. Else, it will be considered as a
more control, directly use media_new_location/media_new_path local path. If you need more control, directly use
methods. media_new_location/media_new_path methods.
Options can be specified as supplementary string parameters, e.g. Options can be specified as supplementary string parameters, e.g.
@ -920,7 +920,7 @@ class Instance(_Ctype):
@param options: optional media option=value strings @param options: optional media option=value strings
""" """
if ':' in mrl: if ':' in mrl and mrl.index(':') > 1:
# Assume it is a URL # Assume it is a URL
m = libvlc_media_new_location(self, mrl) m = libvlc_media_new_location(self, mrl)
else: else:
@ -1031,6 +1031,13 @@ class Instance(_Ctype):
''' '''
return libvlc_log_open(self) 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): def media_new_location(self, psz_mrl):
'''Create a media with a certain given media resource location, '''Create a media with a certain given media resource location,
for instance a valid URL. for instance a valid URL.
@ -1080,13 +1087,6 @@ class Instance(_Ctype):
''' '''
return libvlc_media_new_as_node(self, psz_name) 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): def media_library_new(self):
'''Create an new Media Library object. '''Create an new Media Library object.
@return: a new object or NULL on error. @return: a new object or NULL on error.
@ -1522,7 +1522,7 @@ class Media(_Ctype):
def save_meta(self): def save_meta(self):
'''Save the meta previously set. '''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) return libvlc_media_save_meta(self)
@ -3088,6 +3088,67 @@ def libvlc_clock():
ctypes.c_int64) ctypes.c_int64)
return f() 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): def libvlc_media_new_location(p_instance, psz_mrl):
'''Create a media with a certain given media resource location, '''Create a media with a certain given media resource location,
for instance a valid URL. 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): def libvlc_media_save_meta(p_md):
'''Save the meta previously set. '''Save the meta previously set.
@param p_md: the media desriptor. @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 \ f = _Cfunctions.get('libvlc_media_save_meta', None) or \
_Cfunction('libvlc_media_save_meta', ((1,),), None, _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)) ctypes.c_int, Media, ctypes.POINTER(ctypes.c_void_p))
return f(p_md) 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): def libvlc_media_library_new(p_instance):
'''Create an new Media Library object. '''Create an new Media Library object.
@param p_instance: the libvlc instance. @param p_instance: the libvlc instance.
@ -5595,7 +5595,7 @@ if __name__ == '__main__':
print('Aspect ratio: %s' % player.video_get_aspect_ratio()) print('Aspect ratio: %s' % player.video_get_aspect_ratio())
#print('Window:' % player.get_hwnd() #print('Window:' % player.get_hwnd()
except Exception: except Exception:
print('Error: %s', sys.exc_info()[1]) print('Error: %s' % sys.exc_info()[1])
def sec_forward(): def sec_forward():
"""Go forward one sec""" """Go forward one sec"""

View File

@ -131,9 +131,9 @@ class VlcPlayer(MediaPlayer):
# this is platform specific! # this is platform specific!
# you have to give the id of the QFrame (or similar object) to # you have to give the id of the QFrame (or similar object) to
# vlc, different platforms have different functions for this # 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())) 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())) display.vlcMediaPlayer.set_agl(int(display.vlcWidget.winId()))
else: else:
# for Linux using the X Server # for Linux using the X Server
@ -182,7 +182,8 @@ class VlcPlayer(MediaPlayer):
if controller.media_info.start_time > 0: if controller.media_info.start_time > 0:
start_time = controller.media_info.start_time start_time = controller.media_info.start_time
display.vlcMediaPlayer.play() display.vlcMediaPlayer.play()
if self.media_state_wait(display, vlc.State.Playing): if not self.media_state_wait(display, vlc.State.Playing):
return False
if start_time > 0: if start_time > 0:
self.seek(display, controller.media_info.start_time * 1000) self.seek(display, controller.media_info.start_time * 1000)
controller.media_info.length = \ controller.media_info.length = \
@ -191,8 +192,6 @@ class VlcPlayer(MediaPlayer):
self.state = MediaState.Playing self.state = MediaState.Playing
display.vlcWidget.raise_() display.vlcWidget.raise_()
return True return True
else:
return False
def pause(self, display): def pause(self, display):
if display.vlcMedia.get_state() != vlc.State.Playing: if display.vlcMedia.get_state() != vlc.State.Playing:

View File

@ -339,7 +339,7 @@ class WebkitPlayer(MediaPlayer):
else: else:
display.frame.evaluateJavaScript(u'show_video("play");') display.frame.evaluateJavaScript(u'show_video("play");')
if start_time > 0: 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 # TODO add playing check and get the correct media length
controller.media_info.length = length controller.media_info.length = length
self.state = MediaState.Playing self.state = MediaState.Playing
@ -375,11 +375,11 @@ class WebkitPlayer(MediaPlayer):
controller = display.controller controller = display.controller
if controller.media_info.is_flash: if controller.media_info.is_flash:
seek = seekVal seek = seekVal
display.frame.evaluateJavaScript( \ display.frame.evaluateJavaScript(
u'show_flash("seek", null, null, "%s");' % (seek)) u'show_flash("seek", null, null, "%s");' % (seek))
else: else:
seek = float(seekVal)/1000 seek = float(seekVal) / 1000
display.frame.evaluateJavaScript( \ display.frame.evaluateJavaScript(
u'show_video("seek", null, null, null, "%f");' % (seek)) u'show_video("seek", null, null, null, "%f");' % (seek))
def reset(self, display): def reset(self, display):
@ -406,24 +406,24 @@ class WebkitPlayer(MediaPlayer):
def update_ui(self, display): def update_ui(self, display):
controller = display.controller controller = display.controller
if controller.media_info.is_flash: if controller.media_info.is_flash:
currentTime = display.frame.evaluateJavaScript( \ currentTime = display.frame.evaluateJavaScript(
u'show_flash("currentTime");').toInt()[0] u'show_flash("currentTime");').toInt()[0]
length = display.frame.evaluateJavaScript( \ length = display.frame.evaluateJavaScript(
u'show_flash("length");').toInt()[0] u'show_flash("length");').toInt()[0]
else: else:
if display.frame.evaluateJavaScript( \ if display.frame.evaluateJavaScript(
u'show_video("isEnded");').toString() == 'true': u'show_video("isEnded");').toString() == 'true':
self.stop(display) self.stop(display)
(currentTime, ok) = display.frame.evaluateJavaScript( \ (currentTime, ok) = display.frame.evaluateJavaScript(
u'show_video("currentTime");').toFloat() u'show_video("currentTime");').toFloat()
# check if conversion was ok and value is not 'NaN' # check if conversion was ok and value is not 'NaN'
if ok and currentTime != float('inf'): if ok and currentTime != float('inf'):
currentTime = int(currentTime*1000) currentTime = int(currentTime * 1000)
(length, ok) = display.frame.evaluateJavaScript( \ (length, ok) = display.frame.evaluateJavaScript(
u'show_video("length");').toFloat() u'show_video("length");').toFloat()
# check if conversion was ok and value is not 'NaN' # check if conversion was ok and value is not 'NaN'
if ok and length != float('inf'): if ok and length != float('inf'):
length = int(length*1000) length = int(length * 1000)
if currentTime > 0: if currentTime > 0:
controller.media_info.length = length controller.media_info.length = length
controller.seekSlider.setMaximum(length) controller.seekSlider.setMaximum(length)

View File

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

View File

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

View File

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

View File

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

View File

@ -248,8 +248,7 @@ class ThemeManager(QtGui.QWidget):
Settings().setValue( Settings().setValue(
self.settingsSection + u'/global theme', self.settingsSection + u'/global theme',
QtCore.QVariant(self.global_theme)) QtCore.QVariant(self.global_theme))
Receiver.send_message(u'theme_update_global', Receiver.send_message(u'theme_update_global', self.global_theme)
self.global_theme)
self._pushThemes() self._pushThemes()
def onAddTheme(self): def onAddTheme(self):
@ -286,6 +285,8 @@ class ThemeManager(QtGui.QWidget):
if plugin.usesTheme(old_theme_name): if plugin.usesTheme(old_theme_name):
plugin.renameTheme(old_theme_name, new_theme_name) plugin.renameTheme(old_theme_name, new_theme_name)
self.loadThemes() self.loadThemes()
self.mainwindow.renderer.update_theme(
new_theme_name, old_theme_name)
def onCopyTheme(self): 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 Loads the settings for the theme that is to be edited and launches the
theme editing form so the user can make their changes. theme editing form so the user can make their changes.
""" """
if check_item_selected(self.themeListWidget, if check_item_selected(self.themeListWidget, translate(
translate('OpenLP.ThemeManager', 'OpenLP.ThemeManager', 'You must select a theme to edit.')):
'You must select a theme to edit.')):
item = self.themeListWidget.currentItem() item = self.themeListWidget.currentItem()
theme = self.getThemeData( theme = self.getThemeData(
unicode(item.data(QtCore.Qt.UserRole).toString())) unicode(item.data(QtCore.Qt.UserRole).toString()))
@ -333,6 +333,7 @@ class ThemeManager(QtGui.QWidget):
self.themeForm.theme = theme self.themeForm.theme = theme
self.themeForm.exec_(True) self.themeForm.exec_(True)
self.old_background_image = None self.old_background_image = None
self.mainwindow.renderer.update_theme(theme.theme_name)
def onDeleteTheme(self): def onDeleteTheme(self):
""" """
@ -350,6 +351,7 @@ class ThemeManager(QtGui.QWidget):
# As we do not reload the themes, push out the change. Reload the # As we do not reload the themes, push out the change. Reload the
# list as the internal lists and events need to be triggered. # list as the internal lists and events need to be triggered.
self._pushThemes() self._pushThemes()
self.mainwindow.renderer.update_theme(theme, only_delete=True)
def deleteTheme(self, theme): 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'theme level', QtCore.QVariant(self.theme_level))
settings.setValue(u'global theme', QtCore.QVariant(self.global_theme)) settings.setValue(u'global theme', QtCore.QVariant(self.global_theme))
settings.endGroup() settings.endGroup()
self.mainwindow.renderer.set_global_theme( self.mainwindow.renderer.set_global_theme(self.global_theme)
self.global_theme, self.theme_level) self.mainwindow.renderer.set_theme_level(self.theme_level)
Receiver.send_message(u'theme_update_global', self.global_theme) Receiver.send_message(u'theme_update_global', self.global_theme)
def postSetUp(self): def postSetUp(self):
@ -170,8 +170,8 @@ class ThemesTab(SettingsTab):
def onDefaultComboBoxChanged(self, value): def onDefaultComboBoxChanged(self, value):
self.global_theme = unicode(self.DefaultComboBox.currentText()) self.global_theme = unicode(self.DefaultComboBox.currentText())
self.mainwindow.renderer.set_global_theme( self.mainwindow.renderer.set_global_theme(self.global_theme)
self.global_theme, self.theme_level) self.mainwindow.renderer.set_theme_level(self.theme_level)
self.__previewGlobalTheme() self.__previewGlobalTheme()
def updateThemeList(self, theme_list): def updateThemeList(self, theme_list):
@ -190,8 +190,8 @@ class ThemesTab(SettingsTab):
self.DefaultComboBox.clear() self.DefaultComboBox.clear()
self.DefaultComboBox.addItems(theme_list) self.DefaultComboBox.addItems(theme_list)
find_and_set_in_combo_box(self.DefaultComboBox, self.global_theme) find_and_set_in_combo_box(self.DefaultComboBox, self.global_theme)
self.mainwindow.renderer.set_global_theme( self.mainwindow.renderer.set_global_theme(self.global_theme)
self.global_theme, self.theme_level) self.mainwindow.renderer.set_theme_level(self.theme_level)
if self.global_theme is not u'': if self.global_theme is not u'':
self.__previewGlobalTheme() self.__previewGlobalTheme()

View File

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

View File

@ -73,7 +73,7 @@ class CSVBible(BibleDB):
def __init__(self, parent, **kwargs): 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 This class assumes the files contain all the information and
a clean bible is being loaded. a clean bible is being loaded.
""" """

View File

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

View File

@ -97,7 +97,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.onVerseOrderTextChanged) self.onVerseOrderTextChanged)
QtCore.QObject.connect(self.themeAddButton, QtCore.QObject.connect(self.themeAddButton,
QtCore.SIGNAL(u'clicked()'), QtCore.SIGNAL(u'clicked()'),
self.mediaitem.plugin.renderer.themeManager.onAddTheme) self.mediaitem.plugin.renderer.theme_manager.onAddTheme)
QtCore.QObject.connect(self.maintenanceButton, QtCore.QObject.connect(self.maintenanceButton,
QtCore.SIGNAL(u'clicked()'), self.onMaintenanceButtonClicked) QtCore.SIGNAL(u'clicked()'), self.onMaintenanceButtonClicked)
QtCore.QObject.connect(self.audioAddFromFileButton, QtCore.QObject.connect(self.audioAddFromFileButton,
@ -172,17 +172,14 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
def loadThemes(self, theme_list): def loadThemes(self, theme_list):
self.themeComboBox.clear() self.themeComboBox.clear()
self.themeComboBox.addItem(u'') self.themeComboBox.addItem(u'')
self.themes = [] self.themes = theme_list
for theme in theme_list: self.themeComboBox.addItems(theme_list)
self.themeComboBox.addItem(theme)
self.themes.append(theme)
set_case_insensitive_completer(self.themes, self.themeComboBox) set_case_insensitive_completer(self.themes, self.themeComboBox)
def loadMediaFiles(self): def loadMediaFiles(self):
self.audioAddFromMediaButton.setVisible(False) self.audioAddFromMediaButton.setVisible(False)
for plugin in self.parent().pluginManager.plugins: for plugin in self.parent().pluginManager.plugins:
if plugin.name == u'media' and \ if plugin.name == u'media' and plugin.status == PluginStatus.Active:
plugin.status == PluginStatus.Active:
self.audioAddFromMediaButton.setVisible(True) self.audioAddFromMediaButton.setVisible(True)
self.mediaForm.populateFiles( self.mediaForm.populateFiles(
plugin.mediaItem.getList(MediaType.Audio)) 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 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 opensongimport import OpenSongImport
from easyslidesimport import EasySlidesImport from easyslidesimport import EasySlidesImport
from olpimport import OpenLPSongImport from olpimport import OpenLPSongImport
@ -41,6 +44,7 @@ from ewimport import EasyWorshipSongImport
from songbeamerimport import SongBeamerImport from songbeamerimport import SongBeamerImport
from songshowplusimport import SongShowPlusImport from songshowplusimport import SongShowPlusImport
from foilpresenterimport import FoilPresenterImport from foilpresenterimport import FoilPresenterImport
from zionworximport import ZionWorxImport
# Imports that might fail # Imports that might fail
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
try: try:
@ -62,13 +66,58 @@ except ImportError:
log.exception('Error importing %s', 'OooImport') log.exception('Error importing %s', 'OooImport')
HAS_OOO = False 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): class SongFormat(object):
""" """
This is a special enumeration class that holds the various types of songs, This is a special static class that holds an enumeration of the various
plus a few helper functions to facilitate generic handling of song types song formats handled by the importer, the attributes of each song format,
for importing. 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 Unknown = -1
OpenLyrics = 0 OpenLyrics = 0
OpenLP2 = 1 OpenLP2 = 1
@ -85,50 +134,164 @@ class SongFormat(object):
SongShowPlus = 12 SongShowPlus = 12
SongsOfFellowship = 13 SongsOfFellowship = 13
WordsOfWorship = 14 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 @staticmethod
def get_class(format): def get_format_list():
"""
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():
""" """
Return a list of the supported song formats. Return a list of the supported song formats.
""" """
@ -147,26 +310,55 @@ class SongFormat(object):
SongFormat.SongBeamer, SongFormat.SongBeamer,
SongFormat.SongShowPlus, SongFormat.SongShowPlus,
SongFormat.SongsOfFellowship, SongFormat.SongsOfFellowship,
SongFormat.WordsOfWorship SongFormat.WordsOfWorship,
SongFormat.ZionWorx
] ]
@staticmethod @staticmethod
def set_availability(format, available): def get(format, *attributes):
""" """
Set the availability for a given song format. 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.
""" """
SongFormat._format_availability[format] = available 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 @staticmethod
def get_availability(format): def set(format, attribute, value):
""" """
Return the availability of a given song format. Set specified song format attribute to the supplied value.
""" """
return SongFormat._format_availability.get(format, True) SongFormat.__attributes__[format][attribute] = value
SongFormat.set_availability(SongFormat.OpenLP1, HAS_OPENLP1) SongFormat.set(SongFormat.OpenLP1, u'availability', HAS_OPENLP1)
SongFormat.set_availability(SongFormat.SongsOfFellowship, HAS_SOF) if HAS_OPENLP1:
SongFormat.set_availability(SongFormat.Generic, HAS_OOO) SongFormat.set(SongFormat.OpenLP1, u'class', OpenLP1SongImport)
SongFormat.set(SongFormat.SongsOfFellowship, u'availability', HAS_SOF)
__all__ = [u'SongFormat'] 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 import os
from openlp.core.lib import translate from openlp.core.lib import translate
from openlp.core.ui.wizard import WizardStrings
from openlp.plugins.songs.lib.songimport import SongImport from openlp.plugins.songs.lib.songimport import SongImport
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -71,18 +70,15 @@ class PowerSongImport(SongImport):
* .song * .song
""" """
@staticmethod @staticmethod
def isValidSource(**kwargs): def isValidSource(import_source):
""" """
Checks if source is a PowerSong 1.0 folder: Checks if source is a PowerSong 1.0 folder:
* is a directory * is a directory
* contains at least one *.song file * contains at least one *.song file
""" """
if u'folder' in kwargs: if os.path.isdir(import_source):
dir = kwargs[u'folder'] for file in os.listdir(import_source):
if os.path.isdir(dir):
for file in os.listdir(dir):
if fnmatch.fnmatch(file, u'*.song'): if fnmatch.fnmatch(file, u'*.song'):
return True return True
return False return False
@ -91,6 +87,8 @@ class PowerSongImport(SongImport):
""" """
Receive either a list of files or a folder (unicode) to import. 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 isinstance(self.importSource, unicode):
if os.path.isdir(self.importSource): if os.path.isdir(self.importSource):
dir = self.importSource dir = self.importSource
@ -104,7 +102,7 @@ class PowerSongImport(SongImport):
self.logError(unicode(translate('SongsPlugin.PowerSongImport', self.logError(unicode(translate('SongsPlugin.PowerSongImport',
'No songs to import.')), 'No songs to import.')),
unicode(translate('SongsPlugin.PowerSongImport', unicode(translate('SongsPlugin.PowerSongImport',
'No %s files found.' % WizardStrings.PS))) 'No %s files found.' % PS_string)))
return return
self.importWizard.progressBar.setMaximum(len(self.importSource)) self.importWizard.progressBar.setMaximum(len(self.importSource))
for file in self.importSource: for file in self.importSource:
@ -124,7 +122,7 @@ class PowerSongImport(SongImport):
self.logError(os.path.basename(file), unicode( self.logError(os.path.basename(file), unicode(
translate('SongsPlugin.PowerSongImport', translate('SongsPlugin.PowerSongImport',
'Invalid %s file. Unexpected byte value.' 'Invalid %s file. Unexpected byte value.'
% WizardStrings.PS))) % PS_string)))
break break
else: else:
if label == u'TITLE': if label == u'TITLE':
@ -142,15 +140,14 @@ class PowerSongImport(SongImport):
if not self.title: if not self.title:
self.logError(os.path.basename(file), unicode( self.logError(os.path.basename(file), unicode(
translate('SongsPlugin.PowerSongImport', translate('SongsPlugin.PowerSongImport',
'Invalid %s file. Missing "TITLE" header.' 'Invalid %s file. Missing "TITLE" header.' % PS_string)))
% WizardStrings.PS)))
continue continue
# Check that file had COPYRIGHTLINE label # Check that file had COPYRIGHTLINE label
if not found_copyright: if not found_copyright:
self.logError(self.title, unicode( self.logError(self.title, unicode(
translate('SongsPlugin.PowerSongImport', translate('SongsPlugin.PowerSongImport',
'Invalid %s file. Missing "COPYRIGHTLINE" ' 'Invalid %s file. Missing "COPYRIGHTLINE" '
'header.' % WizardStrings.PS))) 'header.' % PS_string)))
continue continue
# Check that file had at least one verse # Check that file had at least one verse
if not self.verses: if not self.verses:

View File

@ -51,11 +51,11 @@ class SongImport(QtCore.QObject):
as necessary as necessary
""" """
@staticmethod @staticmethod
def isValidSource(**kwargs): def isValidSource(import_source):
""" """
Override this method to validate the source prior to import. Override this method to validate the source prior to import.
""" """
pass return True
def __init__(self, manager, **kwargs): 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) self.manager.save_object(song)
def importSongs(self, format, **kwargs): def importSongs(self, format, **kwargs):
class_ = SongFormat.get_class(format) class_ = SongFormat.get(format, u'class')
importer = class_(self.manager, **kwargs) importer = class_(self.manager, **kwargs)
importer.register(self.mediaItem.importWizard) importer.register(self.mediaItem.importWizard)
return importer return importer