forked from openlp/openlp
Merge with trunk, resolve conflicts
This commit is contained in:
commit
245c0acd89
@ -268,7 +268,7 @@ class Receiver(object):
|
||||
<<ACTION>>
|
||||
)``
|
||||
"""
|
||||
eventreceiver = EventReceiver()
|
||||
__eventreceiver__ = EventReceiver()
|
||||
|
||||
@staticmethod
|
||||
def send_message(event, msg=None):
|
||||
@ -281,11 +281,11 @@ class Receiver(object):
|
||||
``msg``
|
||||
Defaults to *None*. The message to send with the event.
|
||||
"""
|
||||
Receiver.eventreceiver.send_message(event, msg)
|
||||
Receiver.__eventreceiver__.send_message(event, msg)
|
||||
|
||||
@staticmethod
|
||||
def get_receiver():
|
||||
"""
|
||||
Get the global ``eventreceiver`` instance.
|
||||
Get the global ``__eventreceiver__`` instance.
|
||||
"""
|
||||
return Receiver.eventreceiver
|
||||
return Receiver.__eventreceiver__
|
||||
|
@ -41,13 +41,13 @@ class PluginManager(object):
|
||||
and executes all the hooks, as and when necessary.
|
||||
"""
|
||||
log.info(u'Plugin manager loaded')
|
||||
|
||||
__instance__ = None
|
||||
@staticmethod
|
||||
def get_instance():
|
||||
"""
|
||||
Obtain a single instance of class.
|
||||
"""
|
||||
return PluginManager.instance
|
||||
return PluginManager.__instance__
|
||||
|
||||
def __init__(self, plugin_dir):
|
||||
"""
|
||||
@ -58,7 +58,7 @@ class PluginManager(object):
|
||||
The directory to search for plugins.
|
||||
"""
|
||||
log.info(u'Plugin manager Initialising')
|
||||
PluginManager.instance = self
|
||||
PluginManager.__instance__ = self
|
||||
if not plugin_dir in sys.path:
|
||||
log.debug(u'Inserting %s into sys.path', plugin_dir)
|
||||
sys.path.insert(0, plugin_dir)
|
||||
|
@ -55,29 +55,32 @@ class Renderer(object):
|
||||
"""
|
||||
log.info(u'Renderer Loaded')
|
||||
|
||||
def __init__(self, imageManager, themeManager):
|
||||
def __init__(self, image_manager, theme_manager):
|
||||
"""
|
||||
Initialise the renderer.
|
||||
|
||||
``imageManager``
|
||||
A imageManager instance which takes care of e. g. caching and resizing
|
||||
images.
|
||||
``image_manager``
|
||||
A image_manager instance which takes care of e. g. caching and
|
||||
resizing images.
|
||||
|
||||
``themeManager``
|
||||
The themeManager instance, used to get the current theme details.
|
||||
``theme_manager``
|
||||
The theme_manager instance, used to get the current theme details.
|
||||
"""
|
||||
log.debug(u'Initialisation started')
|
||||
self.themeManager = themeManager
|
||||
self.imageManager = imageManager
|
||||
self.theme_manager = theme_manager
|
||||
self.image_manager = image_manager
|
||||
self.screens = ScreenList()
|
||||
self.service_theme = u''
|
||||
self.theme_level = u''
|
||||
self.override_background = None
|
||||
self.theme_data = None
|
||||
self.bg_frame = None
|
||||
self.theme_level = ThemeLevel.Global
|
||||
self.global_theme_name = u''
|
||||
self.service_theme_name = u''
|
||||
self.item_theme_name = u''
|
||||
self.force_page = False
|
||||
self.display = MainDisplay(None, self.imageManager, False, self)
|
||||
self.display = MainDisplay(None, self.image_manager, False, self)
|
||||
self.display.setup()
|
||||
self._theme_dimensions = {}
|
||||
self._calculate_default()
|
||||
QtCore.QObject.connect(Receiver.get_receiver(),
|
||||
QtCore.SIGNAL(u'theme_update_global'), self.set_global_theme)
|
||||
|
||||
def update_display(self):
|
||||
"""
|
||||
@ -87,100 +90,132 @@ class Renderer(object):
|
||||
self._calculate_default()
|
||||
if self.display:
|
||||
self.display.close()
|
||||
self.display = MainDisplay(None, self.imageManager, False, self)
|
||||
self.display = MainDisplay(None, self.image_manager, False, self)
|
||||
self.display.setup()
|
||||
self.bg_frame = None
|
||||
self.theme_data = None
|
||||
self._theme_dimensions = {}
|
||||
|
||||
def set_global_theme(self, global_theme, theme_level=ThemeLevel.Global):
|
||||
def update_theme(self, theme_name, old_theme_name=None, only_delete=False):
|
||||
"""
|
||||
Set the global-level theme and the theme level.
|
||||
This method updates the theme in ``_theme_dimensions`` when a theme
|
||||
has been edited or renamed.
|
||||
|
||||
``global_theme``
|
||||
The global-level theme to be set.
|
||||
``theme_name``
|
||||
The current theme name.
|
||||
|
||||
``old_theme_name``
|
||||
The old theme name. Has only to be passed, when the theme has been
|
||||
renamed. Defaults to *None*.
|
||||
|
||||
``only_delete``
|
||||
Only remove the given ``theme_name`` from the ``_theme_dimensions``
|
||||
list. This can be used when a theme is permanently deleted.
|
||||
"""
|
||||
if old_theme_name is not None and \
|
||||
old_theme_name in self._theme_dimensions:
|
||||
del self._theme_dimensions[old_theme_name]
|
||||
if theme_name in self._theme_dimensions:
|
||||
del self._theme_dimensions[theme_name]
|
||||
if not only_delete:
|
||||
self._set_theme(theme_name)
|
||||
|
||||
def _set_theme(self, theme_name):
|
||||
"""
|
||||
Helper method to save theme names and theme data.
|
||||
|
||||
``theme_name``
|
||||
The theme name.
|
||||
"""
|
||||
if theme_name not in self._theme_dimensions:
|
||||
theme_data = self.theme_manager.getThemeData(theme_name)
|
||||
main_rect = self.get_main_rectangle(theme_data)
|
||||
footer_rect = self.get_footer_rectangle(theme_data)
|
||||
self._theme_dimensions[theme_name] = \
|
||||
[theme_data, main_rect, footer_rect]
|
||||
else:
|
||||
theme_data, main_rect, footer_rect = \
|
||||
self._theme_dimensions[theme_name]
|
||||
# if No file do not update cache
|
||||
if theme_data.background_filename:
|
||||
self.image_manager.addImage(theme_data.theme_name,
|
||||
theme_data.background_filename, u'theme',
|
||||
QtGui.QColor(theme_data.background_border_color))
|
||||
|
||||
def pre_render(self, override_theme_data=None):
|
||||
"""
|
||||
Set up the theme to be used before rendering an item.
|
||||
|
||||
``override_theme_data``
|
||||
The theme data should be passed, when we want to use our own theme
|
||||
data, regardless of the theme level. This should for example be used
|
||||
in the theme manager. **Note**, this is **not** to be mixed up with
|
||||
the ``set_item_theme`` method.
|
||||
"""
|
||||
# Just assume we use the global theme.
|
||||
theme_to_use = self.global_theme_name
|
||||
# The theme level is either set to Service or Item. Use the service
|
||||
# theme if one is set. We also have to use the service theme, even when
|
||||
# the theme level is set to Item, because the item does not necessarily
|
||||
# have to have a theme.
|
||||
if self.theme_level != ThemeLevel.Global:
|
||||
# When the theme level is at Service and we actually have a service
|
||||
# theme then use it.
|
||||
if self.service_theme_name:
|
||||
theme_to_use = self.service_theme_name
|
||||
# If we have Item level and have an item theme then use it.
|
||||
if self.theme_level == ThemeLevel.Song and self.item_theme_name:
|
||||
theme_to_use = self.item_theme_name
|
||||
if override_theme_data is None:
|
||||
if theme_to_use not in self._theme_dimensions:
|
||||
self._set_theme(theme_to_use)
|
||||
theme_data, main_rect, footer_rect = \
|
||||
self._theme_dimensions[theme_to_use]
|
||||
else:
|
||||
# Ignore everything and use own theme data.
|
||||
theme_data = override_theme_data
|
||||
main_rect = self.get_main_rectangle(override_theme_data)
|
||||
footer_rect = self.get_footer_rectangle(override_theme_data)
|
||||
self._set_text_rectangle(theme_data, main_rect, footer_rect)
|
||||
return theme_data, self._rect, self._rect_footer
|
||||
|
||||
def set_theme_level(self, theme_level):
|
||||
"""
|
||||
Sets the theme level.
|
||||
|
||||
``theme_level``
|
||||
Defaults to ``ThemeLevel.Global``. The theme level, can be
|
||||
``ThemeLevel.Global``, ``ThemeLevel.Service`` or
|
||||
``ThemeLevel.Song``.
|
||||
The theme level to be used.
|
||||
"""
|
||||
self.global_theme = global_theme
|
||||
self.theme_level = theme_level
|
||||
self.global_theme_data = \
|
||||
self.themeManager.getThemeData(self.global_theme)
|
||||
self.theme_data = None
|
||||
self._cache_background_image(self.global_theme_data)
|
||||
|
||||
def set_service_theme(self, service_theme):
|
||||
def set_global_theme(self, global_theme_name):
|
||||
"""
|
||||
Set the global-level theme name.
|
||||
|
||||
``global_theme_name``
|
||||
The global-level theme's name.
|
||||
"""
|
||||
self._set_theme(global_theme_name)
|
||||
self.global_theme_name = global_theme_name
|
||||
|
||||
def set_service_theme(self, service_theme_name):
|
||||
"""
|
||||
Set the service-level theme.
|
||||
|
||||
``service_theme``
|
||||
The service-level theme to be set.
|
||||
``service_theme_name``
|
||||
The service level theme's name.
|
||||
"""
|
||||
self.service_theme = service_theme
|
||||
self.theme_data = None
|
||||
self._cache_background_image(self.themeManager.getThemeData
|
||||
(service_theme))
|
||||
self._set_theme(service_theme_name)
|
||||
self.service_theme_name = service_theme_name
|
||||
|
||||
def _cache_background_image(self, temp_theme):
|
||||
def set_item_theme(self, item_theme_name):
|
||||
"""
|
||||
Adds a background image to the image cache if necessary.
|
||||
Set the item-level theme. **Note**, this has to be done for each item we
|
||||
are rendering.
|
||||
|
||||
``temp_theme``
|
||||
The theme object containing the theme data.
|
||||
``item_theme_name``
|
||||
The item theme's name.
|
||||
"""
|
||||
# if No file do not update cache
|
||||
if temp_theme.background_filename:
|
||||
self.imageManager.addImage(temp_theme.theme_name,
|
||||
temp_theme.background_filename, u'theme',
|
||||
QtGui.QColor(temp_theme.background_border_color))
|
||||
|
||||
def set_override_theme(self, override_theme, override_levels=False):
|
||||
"""
|
||||
Set the appropriate theme depending on the theme level.
|
||||
Called by the service item when building a display frame
|
||||
|
||||
``override_theme``
|
||||
The name of the song-level theme. None means the service
|
||||
item wants to use the given value.
|
||||
|
||||
``override_levels``
|
||||
Used to force the theme data passed in to be used.
|
||||
"""
|
||||
log.debug(u'set override theme to %s', override_theme)
|
||||
theme_level = self.theme_level
|
||||
if override_levels:
|
||||
theme_level = ThemeLevel.Song
|
||||
if theme_level == ThemeLevel.Global:
|
||||
theme = self.global_theme
|
||||
elif theme_level == ThemeLevel.Service:
|
||||
if self.service_theme == u'':
|
||||
theme = self.global_theme
|
||||
else:
|
||||
theme = self.service_theme
|
||||
else:
|
||||
# Images have a theme of -1
|
||||
if override_theme and override_theme != -1:
|
||||
theme = override_theme
|
||||
elif theme_level == ThemeLevel.Song or \
|
||||
theme_level == ThemeLevel.Service:
|
||||
if self.service_theme == u'':
|
||||
theme = self.global_theme
|
||||
else:
|
||||
theme = self.service_theme
|
||||
else:
|
||||
theme = self.global_theme
|
||||
log.debug(u'theme is now %s', theme)
|
||||
# Force the theme to be the one passed in.
|
||||
if override_levels:
|
||||
self.theme_data = override_theme
|
||||
else:
|
||||
self.theme_data = self.themeManager.getThemeData(theme)
|
||||
self._calculate_default()
|
||||
self._build_text_rectangle(self.theme_data)
|
||||
self._cache_background_image(self.theme_data)
|
||||
return self._rect, self._rect_footer
|
||||
self._set_theme(item_theme_name)
|
||||
self.item_theme_name = item_theme_name
|
||||
|
||||
def generate_preview(self, theme_data, force_page=False):
|
||||
"""
|
||||
@ -195,27 +230,31 @@ class Renderer(object):
|
||||
log.debug(u'generate preview')
|
||||
# save value for use in format_slide
|
||||
self.force_page = force_page
|
||||
# set the default image size for previews
|
||||
self._calculate_default()
|
||||
# build a service item to generate preview
|
||||
serviceItem = ServiceItem()
|
||||
serviceItem.theme = theme_data
|
||||
if self.force_page:
|
||||
# make big page for theme edit dialog to get line count
|
||||
serviceItem.add_from_text(u'', VERSE_FOR_LINE_COUNT)
|
||||
else:
|
||||
self.imageManager.deleteImage(theme_data.theme_name)
|
||||
self.image_manager.deleteImage(theme_data.theme_name)
|
||||
serviceItem.add_from_text(u'', VERSE)
|
||||
serviceItem.renderer = self
|
||||
serviceItem.raw_footer = FOOTER
|
||||
# if No file do not update cache
|
||||
if theme_data.background_filename:
|
||||
self.image_manager.addImage(theme_data.theme_name,
|
||||
theme_data.background_filename, u'theme',
|
||||
QtGui.QColor(theme_data.background_border_color))
|
||||
theme_data, main, footer = self.pre_render(theme_data)
|
||||
serviceItem.themedata = theme_data
|
||||
serviceItem.main = main
|
||||
serviceItem.footer = footer
|
||||
serviceItem.render(True)
|
||||
if not self.force_page:
|
||||
self.display.buildHtml(serviceItem)
|
||||
raw_html = serviceItem.get_rendered_frame(0)
|
||||
self.display.text(raw_html)
|
||||
preview = self.display.preview()
|
||||
# Reset the real screen size for subsequent render requests
|
||||
self._calculate_default()
|
||||
return preview
|
||||
self.force_page = False
|
||||
|
||||
@ -264,7 +303,7 @@ class Renderer(object):
|
||||
try:
|
||||
text_to_render, text = \
|
||||
text.split(u'\n[---]\n', 1)
|
||||
except:
|
||||
except ValueError:
|
||||
text_to_render = text.split(u'\n[---]\n')[0]
|
||||
text = u''
|
||||
text_to_render, raw_tags, html_tags = \
|
||||
@ -315,52 +354,41 @@ class Renderer(object):
|
||||
# 90% is start of footer
|
||||
self.footer_start = int(self.height * 0.90)
|
||||
|
||||
def _build_text_rectangle(self, theme):
|
||||
"""
|
||||
Builds a text block using the settings in ``theme``
|
||||
and the size of the display screen.height.
|
||||
Note the system has a 10 pixel border round the screen
|
||||
|
||||
``theme``
|
||||
The theme to build a text block for.
|
||||
"""
|
||||
log.debug(u'_build_text_rectangle')
|
||||
main_rect = self.get_main_rectangle(theme)
|
||||
footer_rect = self.get_footer_rectangle(theme)
|
||||
self._set_text_rectangle(main_rect, footer_rect)
|
||||
|
||||
def get_main_rectangle(self, theme):
|
||||
def get_main_rectangle(self, theme_data):
|
||||
"""
|
||||
Calculates the placement and size of the main rectangle.
|
||||
|
||||
``theme``
|
||||
``theme_data``
|
||||
The theme information
|
||||
"""
|
||||
if not theme.font_main_override:
|
||||
return QtCore.QRect(10, 0, self.width - 20, self.footer_start)
|
||||
if not theme_data.font_main_override:
|
||||
return QtCore.QRect(10, 0, self.width, self.footer_start)
|
||||
else:
|
||||
return QtCore.QRect(theme.font_main_x, theme.font_main_y,
|
||||
theme.font_main_width - 1, theme.font_main_height - 1)
|
||||
return QtCore.QRect(theme_data.font_main_x, theme_data.font_main_y,
|
||||
theme_data.font_main_width - 1, theme_data.font_main_height - 1)
|
||||
|
||||
def get_footer_rectangle(self, theme):
|
||||
def get_footer_rectangle(self, theme_data):
|
||||
"""
|
||||
Calculates the placement and size of the footer rectangle.
|
||||
|
||||
``theme``
|
||||
The theme information
|
||||
``theme_data``
|
||||
The theme data.
|
||||
"""
|
||||
if not theme.font_footer_override:
|
||||
if not theme_data.font_footer_override:
|
||||
return QtCore.QRect(10, self.footer_start, self.width - 20,
|
||||
self.height - self.footer_start)
|
||||
else:
|
||||
return QtCore.QRect(theme.font_footer_x,
|
||||
theme.font_footer_y, theme.font_footer_width - 1,
|
||||
theme.font_footer_height - 1)
|
||||
return QtCore.QRect(theme_data.font_footer_x,
|
||||
theme_data.font_footer_y, theme_data.font_footer_width - 1,
|
||||
theme_data.font_footer_height - 1)
|
||||
|
||||
def _set_text_rectangle(self, rect_main, rect_footer):
|
||||
def _set_text_rectangle(self, theme_data, rect_main, rect_footer):
|
||||
"""
|
||||
Sets the rectangle within which text should be rendered.
|
||||
|
||||
``theme_data``
|
||||
The theme data.
|
||||
|
||||
``rect_main``
|
||||
The main text block.
|
||||
|
||||
@ -372,9 +400,9 @@ class Renderer(object):
|
||||
self._rect_footer = rect_footer
|
||||
self.page_width = self._rect.width()
|
||||
self.page_height = self._rect.height()
|
||||
if self.theme_data.font_main_shadow:
|
||||
self.page_width -= int(self.theme_data.font_main_shadow_size)
|
||||
self.page_height -= int(self.theme_data.font_main_shadow_size)
|
||||
if theme_data.font_main_shadow:
|
||||
self.page_width -= int(theme_data.font_main_shadow_size)
|
||||
self.page_height -= int(theme_data.font_main_shadow_size)
|
||||
self.web = QtWebKit.QWebView()
|
||||
self.web.setVisible(False)
|
||||
self.web.resize(self.page_width, self.page_height)
|
||||
@ -392,8 +420,8 @@ class Renderer(object):
|
||||
</script><style>*{margin: 0; padding: 0; border: 0;}
|
||||
#main {position: absolute; top: 0px; %s %s}</style></head><body>
|
||||
<div id="main"></div></body></html>""" % \
|
||||
(build_lyrics_format_css(self.theme_data, self.page_width,
|
||||
self.page_height), build_lyrics_outline_css(self.theme_data))
|
||||
(build_lyrics_format_css(theme_data, self.page_width,
|
||||
self.page_height), build_lyrics_outline_css(theme_data))
|
||||
self.web.setHtml(html)
|
||||
self.empty_height = self.web_frame.contentsSize().height()
|
||||
|
||||
|
@ -158,19 +158,24 @@ class ServiceItem(object):
|
||||
self.icon = icon
|
||||
self.iconic_representation = build_icon(icon)
|
||||
|
||||
def render(self, use_override=False):
|
||||
def render(self, provides_own_theme_data=False):
|
||||
"""
|
||||
The render method is what generates the frames for the screen and
|
||||
obtains the display information from the renderer. At this point all
|
||||
slides are built for the given display size.
|
||||
|
||||
``provides_own_theme_data``
|
||||
This switch disables the usage of the item's theme. However, this is
|
||||
disabled by default. If this is used, it has to be taken care, that
|
||||
the renderer knows the correct theme data. However, this is needed
|
||||
for the theme manager.
|
||||
"""
|
||||
log.debug(u'Render called')
|
||||
self._display_frames = []
|
||||
self.bg_image_bytes = None
|
||||
theme = self.theme if self.theme else None
|
||||
self.main, self.footer = \
|
||||
self.renderer.set_override_theme(theme, use_override)
|
||||
self.themedata = self.renderer.theme_data
|
||||
if not provides_own_theme_data:
|
||||
self.renderer.set_item_theme(self.theme)
|
||||
self.themedata, self.main, self.footer = self.renderer.pre_render()
|
||||
if self.service_item_type == ServiceItemType.Text:
|
||||
log.debug(u'Formatting slides')
|
||||
for slide in self._raw_frames:
|
||||
@ -211,7 +216,7 @@ class ServiceItem(object):
|
||||
self.image_border = background
|
||||
self.service_item_type = ServiceItemType.Image
|
||||
self._raw_frames.append({u'title': title, u'path': path})
|
||||
self.renderer.imageManager.addImage(title, path, u'image',
|
||||
self.renderer.image_manager.addImage(title, path, u'image',
|
||||
self.image_border)
|
||||
self._new_item()
|
||||
|
||||
|
@ -80,6 +80,10 @@ class UiStrings(object):
|
||||
self.Help = translate('OpenLP.Ui', 'Help')
|
||||
self.Hours = translate('OpenLP.Ui', 'h',
|
||||
'The abbreviated unit for hours')
|
||||
self.IFdSs = translate('OpenLP.Ui', 'Invalid Folder Selected',
|
||||
'Singular')
|
||||
self.IFSs = translate('OpenLP.Ui', 'Invalid File Selected', 'Singular')
|
||||
self.IFSp = translate('OpenLP.Ui', 'Invalid Files Selected', 'Plural')
|
||||
self.Image = translate('OpenLP.Ui', 'Image')
|
||||
self.Import = translate('OpenLP.Ui', 'Import')
|
||||
self.LayoutStyle = translate('OpenLP.Ui', 'Layout style:')
|
||||
|
@ -31,11 +31,16 @@ from datetime import datetime, timedelta
|
||||
|
||||
from PyQt4 import QtCore, QtGui
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from openlp.core.lib import SettingsTab, translate, build_icon, Receiver
|
||||
from openlp.core.lib.settings import Settings
|
||||
from openlp.core.lib.ui import UiStrings
|
||||
from openlp.core.utils import get_images_filter, AppLocation
|
||||
from openlp.core.lib import SlideLimits
|
||||
from openlp.core.utils import get_images_filter
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
class AdvancedTab(SettingsTab):
|
||||
"""
|
||||
@ -60,6 +65,7 @@ class AdvancedTab(SettingsTab):
|
||||
'#strftime-strptime-behavior for more information.'))
|
||||
self.defaultImage = u':/graphics/openlp-splash-screen.png'
|
||||
self.defaultColor = u'#ffffff'
|
||||
self.dataExists = False
|
||||
self.iconPath = u':/system/system_settings.png'
|
||||
advanced_translated = translate('OpenLP.AdvancedTab', 'Advanced')
|
||||
SettingsTab.__init__(self, parent, u'Advanced', advanced_translated)
|
||||
@ -152,6 +158,71 @@ class AdvancedTab(SettingsTab):
|
||||
self.serviceNameLayout.addRow(self.serviceNameExampleLabel,
|
||||
self.serviceNameExample)
|
||||
self.leftLayout.addWidget(self.serviceNameGroupBox)
|
||||
# Data Directory
|
||||
self.dataDirectoryGroupBox = QtGui.QGroupBox(self.leftColumn)
|
||||
self.dataDirectoryGroupBox.setObjectName(u'dataDirectoryGroupBox')
|
||||
self.dataDirectoryLayout = QtGui.QFormLayout(self.dataDirectoryGroupBox)
|
||||
self.dataDirectoryLayout.setObjectName(u'dataDirectoryLayout')
|
||||
self.dataDirectoryCurrentLabel = QtGui.QLabel(self.dataDirectoryGroupBox)
|
||||
self.dataDirectoryCurrentLabel.setObjectName(
|
||||
u'dataDirectoryCurrentLabel')
|
||||
self.dataDirectoryLabel = QtGui.QLabel(self.dataDirectoryGroupBox)
|
||||
self.dataDirectoryLabel.setObjectName(u'dataDirectoryLabel')
|
||||
self.dataDirectoryNewLabel = QtGui.QLabel(self.dataDirectoryGroupBox)
|
||||
self.dataDirectoryNewLabel.setObjectName(u'dataDirectoryCurrentLabel')
|
||||
self.newDataDirectoryEdit = QtGui.QLineEdit(self.dataDirectoryGroupBox)
|
||||
self.newDataDirectoryEdit.setObjectName(u'newDataDirectoryEdit')
|
||||
self.newDataDirectoryEdit.setReadOnly(True)
|
||||
self.newDataDirectoryHasFilesLabel = QtGui.QLabel(
|
||||
self.dataDirectoryGroupBox)
|
||||
self.newDataDirectoryHasFilesLabel.setObjectName(
|
||||
u'newDataDirectoryHasFilesLabel')
|
||||
self.newDataDirectoryHasFilesLabel.setWordWrap(True)
|
||||
self.dataDirectoryBrowseButton = QtGui.QToolButton(
|
||||
self.dataDirectoryGroupBox)
|
||||
self.dataDirectoryBrowseButton.setObjectName(
|
||||
u'dataDirectoryBrowseButton')
|
||||
self.dataDirectoryBrowseButton.setIcon(
|
||||
build_icon(u':/general/general_open.png'))
|
||||
self.dataDirectoryDefaultButton = QtGui.QToolButton(
|
||||
self.dataDirectoryGroupBox)
|
||||
self.dataDirectoryDefaultButton.setObjectName(
|
||||
u'dataDirectoryDefaultButton')
|
||||
self.dataDirectoryDefaultButton.setIcon(
|
||||
build_icon(u':/general/general_revert.png'))
|
||||
self.dataDirectoryCancelButton = QtGui.QToolButton(
|
||||
self.dataDirectoryGroupBox)
|
||||
self.dataDirectoryCancelButton.setObjectName(
|
||||
u'dataDirectoryCancelButton')
|
||||
self.dataDirectoryCancelButton.setIcon(
|
||||
build_icon(u':/general/general_delete.png'))
|
||||
self.newDataDirectoryLabelHBox = QtGui.QHBoxLayout()
|
||||
self.newDataDirectoryLabelHBox.setObjectName(
|
||||
u'newDataDirectoryLabelHBox')
|
||||
self.newDataDirectoryLabelHBox.addWidget(self.newDataDirectoryEdit)
|
||||
self.newDataDirectoryLabelHBox.addWidget(
|
||||
self.dataDirectoryBrowseButton)
|
||||
self.newDataDirectoryLabelHBox.addWidget(
|
||||
self.dataDirectoryDefaultButton)
|
||||
self.dataDirectoryCopyCheckHBox = QtGui.QHBoxLayout()
|
||||
self.dataDirectoryCopyCheckHBox.setObjectName(
|
||||
u'dataDirectoryCopyCheckHBox')
|
||||
self.dataDirectoryCopyCheckBox = QtGui.QCheckBox(
|
||||
self.dataDirectoryGroupBox)
|
||||
self.dataDirectoryCopyCheckBox.setObjectName(
|
||||
u'dataDirectoryCopyCheckBox')
|
||||
self.dataDirectoryCopyCheckHBox.addWidget(
|
||||
self.dataDirectoryCopyCheckBox)
|
||||
self.dataDirectoryCopyCheckHBox.addStretch()
|
||||
self.dataDirectoryCopyCheckHBox.addWidget(
|
||||
self.dataDirectoryCancelButton)
|
||||
self.dataDirectoryLayout.addRow(self.dataDirectoryCurrentLabel,
|
||||
self.dataDirectoryLabel)
|
||||
self.dataDirectoryLayout.addRow(self.dataDirectoryNewLabel,
|
||||
self.newDataDirectoryLabelHBox)
|
||||
self.dataDirectoryLayout.addRow(self.dataDirectoryCopyCheckHBox)
|
||||
self.dataDirectoryLayout.addRow(self.newDataDirectoryHasFilesLabel)
|
||||
self.leftLayout.addWidget(self.dataDirectoryGroupBox)
|
||||
self.leftLayout.addStretch()
|
||||
# Default Image
|
||||
self.defaultImageGroupBox = QtGui.QGroupBox(self.rightColumn)
|
||||
@ -220,7 +291,6 @@ class AdvancedTab(SettingsTab):
|
||||
self.x11Layout.addWidget(self.x11BypassCheckBox)
|
||||
self.rightLayout.addWidget(self.x11GroupBox)
|
||||
self.rightLayout.addStretch()
|
||||
|
||||
self.shouldUpdateServiceNameExample = False
|
||||
QtCore.QObject.connect(self.serviceNameCheckBox,
|
||||
QtCore.SIGNAL(u'toggled(bool)'), self.serviceNameCheckBoxToggled)
|
||||
@ -244,6 +314,18 @@ class AdvancedTab(SettingsTab):
|
||||
QtCore.SIGNAL(u'clicked()'), self.onDefaultRevertButtonClicked)
|
||||
QtCore.QObject.connect(self.x11BypassCheckBox,
|
||||
QtCore.SIGNAL(u'toggled(bool)'), self.onX11BypassCheckBoxToggled)
|
||||
QtCore.QObject.connect(self.dataDirectoryBrowseButton,
|
||||
QtCore.SIGNAL(u'clicked()'),
|
||||
self.onDataDirectoryBrowseButtonClicked)
|
||||
QtCore.QObject.connect(self.dataDirectoryDefaultButton,
|
||||
QtCore.SIGNAL(u'clicked()'),
|
||||
self.onDataDirectoryDefaultButtonClicked)
|
||||
QtCore.QObject.connect(self.dataDirectoryCancelButton,
|
||||
QtCore.SIGNAL(u'clicked()'),
|
||||
self.onDataDirectoryCancelButtonClicked)
|
||||
QtCore.QObject.connect(self.dataDirectoryCopyCheckBox,
|
||||
QtCore.SIGNAL(u'toggled(bool)'),
|
||||
self.onDataDirectoryCopyCheckBoxToggled)
|
||||
QtCore.QObject.connect(self.endSlideRadioButton,
|
||||
QtCore.SIGNAL(u'clicked()'), self.onEndSlideButtonClicked)
|
||||
QtCore.QObject.connect(self.wrapSlideRadioButton,
|
||||
@ -258,6 +340,8 @@ class AdvancedTab(SettingsTab):
|
||||
self.tabTitleVisible = UiStrings().Advanced
|
||||
self.uiGroupBox.setTitle(
|
||||
translate('OpenLP.AdvancedTab', 'UI Settings'))
|
||||
self.dataDirectoryGroupBox.setTitle(
|
||||
translate('OpenLP.AdvancedTab', 'Data Location'))
|
||||
self.recentLabel.setText(
|
||||
translate('OpenLP.AdvancedTab',
|
||||
'Number of recent files to display:'))
|
||||
@ -321,6 +405,32 @@ class AdvancedTab(SettingsTab):
|
||||
'Browse for an image file to display.'))
|
||||
self.defaultRevertButton.setToolTip(translate('OpenLP.AdvancedTab',
|
||||
'Revert to the default OpenLP logo.'))
|
||||
self.dataDirectoryCurrentLabel.setText(translate('OpenLP.AdvancedTab',
|
||||
'Current path:'))
|
||||
self.dataDirectoryNewLabel.setText(translate('OpenLP.AdvancedTab',
|
||||
'Custom path:'))
|
||||
self.dataDirectoryBrowseButton.setToolTip(
|
||||
translate('OpenLP.AdvancedTab',
|
||||
'Browse for new data file location.'))
|
||||
self.dataDirectoryDefaultButton.setToolTip(
|
||||
translate('OpenLP.AdvancedTab',
|
||||
'Set the data location to the default.'))
|
||||
self.dataDirectoryCancelButton.setText(
|
||||
translate('OpenLP.AdvancedTab',
|
||||
'Cancel'))
|
||||
self.dataDirectoryCancelButton.setToolTip(
|
||||
translate('OpenLP.AdvancedTab',
|
||||
'Cancel OpenLP data directory location change.'))
|
||||
self.dataDirectoryCopyCheckBox.setText(
|
||||
translate('OpenLP.AdvancedTab',
|
||||
'Copy data to new location.'))
|
||||
self.dataDirectoryCopyCheckBox.setToolTip(
|
||||
translate('OpenLP.AdvancedTab',
|
||||
'Copy the OpenLP data files to the new location.'))
|
||||
self.newDataDirectoryHasFilesLabel.setText(
|
||||
translate('OpenLP.AdvancedTab',
|
||||
'<strong>WARNING:</strong> New data directory location contains '
|
||||
'OpenLP data files. These files WILL be replaced during a copy.'))
|
||||
self.x11GroupBox.setTitle(translate('OpenLP.AdvancedTab',
|
||||
'X11'))
|
||||
self.x11BypassCheckBox.setText(translate('OpenLP.AdvancedTab',
|
||||
@ -398,6 +508,40 @@ class AdvancedTab(SettingsTab):
|
||||
else:
|
||||
self.nextItemRadioButton.setChecked(True)
|
||||
settings.endGroup()
|
||||
self.dataDirectoryCopyCheckBox.hide()
|
||||
self.newDataDirectoryHasFilesLabel.hide()
|
||||
self.dataDirectoryCancelButton.hide()
|
||||
# Since data location can be changed, make sure the path is present.
|
||||
self.currentDataPath = AppLocation.get_data_path()
|
||||
if not os.path.exists(self.currentDataPath):
|
||||
log.error(u'Data path not found %s' % self.currentDataPath)
|
||||
answer = QtGui.QMessageBox.critical(self,
|
||||
translate('OpenLP.AdvancedTab',
|
||||
'Data Directory Error'),
|
||||
translate('OpenLP.AdvancedTab',
|
||||
'OpenLP data directory was not found\n\n%s\n\n'
|
||||
'This data directory was previously changed from the OpenLP '
|
||||
'default location. If the new location was on removable '
|
||||
'media, that media needs to be made available.\n\n'
|
||||
'Click "No" to stop loading OpenLP. allowing you to fix '
|
||||
'the the problem.\n\n'
|
||||
'Click "Yes" to reset the data directory to the default '
|
||||
'location.' % self.currentDataPath),
|
||||
QtGui.QMessageBox.StandardButtons(
|
||||
QtGui.QMessageBox.Yes |
|
||||
QtGui.QMessageBox.No),
|
||||
QtGui.QMessageBox.No)
|
||||
if answer == QtGui.QMessageBox.No:
|
||||
log.info(u'User requested termination')
|
||||
Receiver.send_message(u'cleanup')
|
||||
sys.exit()
|
||||
# Set data location to default.
|
||||
settings.remove(u'advanced/data path')
|
||||
self.currentDataPath = AppLocation.get_data_path()
|
||||
log.warning(u'User requested data path set to default %s'
|
||||
% self.currentDataPath)
|
||||
self.dataDirectoryLabel.setText(os.path.abspath(
|
||||
self.currentDataPath))
|
||||
self.defaultColorButton.setStyleSheet(
|
||||
u'background-color: %s' % self.defaultColor)
|
||||
|
||||
@ -447,6 +591,11 @@ class AdvancedTab(SettingsTab):
|
||||
self.displayChanged = False
|
||||
Receiver.send_message(u'slidecontroller_update_slide_limits')
|
||||
|
||||
def cancel(self):
|
||||
# Dialogue was cancelled, remove any pending data path change.
|
||||
self.onDataDirectoryCancelButtonClicked()
|
||||
SettingsTab.cancel(self)
|
||||
|
||||
def serviceNameCheckBoxToggled(self, default_service_enabled):
|
||||
self.serviceNameDay.setEnabled(default_service_enabled)
|
||||
time_enabled = default_service_enabled and \
|
||||
@ -508,6 +657,122 @@ class AdvancedTab(SettingsTab):
|
||||
self.defaultFileEdit.setText(filename)
|
||||
self.defaultFileEdit.setFocus()
|
||||
|
||||
def onDataDirectoryBrowseButtonClicked(self):
|
||||
"""
|
||||
Browse for a new data directory location.
|
||||
"""
|
||||
old_root_path = unicode(self.dataDirectoryLabel.text())
|
||||
# Get the new directory location.
|
||||
new_data_path = unicode(QtGui.QFileDialog.getExistingDirectory(self,
|
||||
translate('OpenLP.AdvancedTab',
|
||||
'Select Data Directory Location'), old_root_path,
|
||||
options = QtGui.QFileDialog.ShowDirsOnly))
|
||||
# Set the new data path.
|
||||
if new_data_path:
|
||||
if self.currentDataPath.lower() == new_data_path.lower():
|
||||
self.onDataDirectoryCancelButtonClicked()
|
||||
return
|
||||
else:
|
||||
return
|
||||
# Make sure they want to change the data.
|
||||
answer = QtGui.QMessageBox.question(self,
|
||||
translate('OpenLP.AdvancedTab', 'Confirm Data Directory Change'),
|
||||
translate('OpenLP.AdvancedTab',
|
||||
'Are you sure you want to change the location of the OpenLP '
|
||||
'data directory to:\n\n%s\n\n'
|
||||
'The data directory will be changed when OpenLP is closed.'
|
||||
% new_data_path),
|
||||
QtGui.QMessageBox.StandardButtons(
|
||||
QtGui.QMessageBox.Yes |
|
||||
QtGui.QMessageBox.No),
|
||||
QtGui.QMessageBox.No)
|
||||
if answer != QtGui.QMessageBox.Yes:
|
||||
return
|
||||
# Check if data already exists here.
|
||||
self.checkDataOverwrite(new_data_path)
|
||||
# Save the new location.
|
||||
Receiver.send_message(u'set_new_data_path', new_data_path)
|
||||
self.newDataDirectoryEdit.setText(new_data_path)
|
||||
self.dataDirectoryCancelButton.show()
|
||||
|
||||
def onDataDirectoryDefaultButtonClicked(self):
|
||||
"""
|
||||
Re-set the data directory location to the 'default' location.
|
||||
"""
|
||||
new_data_path = AppLocation.get_directory(AppLocation.DataDir)
|
||||
if self.currentDataPath.lower() != new_data_path.lower():
|
||||
# Make sure they want to change the data location back to the default.
|
||||
answer = QtGui.QMessageBox.question(self,
|
||||
translate('OpenLP.AdvancedTab', 'Reset Data Directory'),
|
||||
translate('OpenLP.AdvancedTab',
|
||||
'Are you sure you want to change the location of the OpenLP '
|
||||
'data directory to the default location?\n\n'
|
||||
'This location will be used after OpenLP is closed.'),
|
||||
QtGui.QMessageBox.StandardButtons(
|
||||
QtGui.QMessageBox.Yes |
|
||||
QtGui.QMessageBox.No),
|
||||
QtGui.QMessageBox.No)
|
||||
if answer != QtGui.QMessageBox.Yes:
|
||||
return
|
||||
self.checkDataOverwrite(new_data_path)
|
||||
# Save the new location.
|
||||
Receiver.send_message(u'set_new_data_path', new_data_path)
|
||||
self.newDataDirectoryEdit.setText(os.path.abspath(new_data_path))
|
||||
self.dataDirectoryCancelButton.show()
|
||||
else:
|
||||
# We cancel the change in case user changed their mind.
|
||||
self.onDataDirectoryCancelButtonClicked()
|
||||
|
||||
def onDataDirectoryCopyCheckBoxToggled(self):
|
||||
Receiver.send_message(u'set_copy_data',
|
||||
self.dataDirectoryCopyCheckBox.isChecked())
|
||||
if self.dataExists:
|
||||
if self.dataDirectoryCopyCheckBox.isChecked():
|
||||
self.newDataDirectoryHasFilesLabel.show()
|
||||
else:
|
||||
self.newDataDirectoryHasFilesLabel.hide()
|
||||
|
||||
def checkDataOverwrite(self, data_path ):
|
||||
test_path = os.path.join(data_path, u'songs')
|
||||
self.dataDirectoryCopyCheckBox.show()
|
||||
if os.path.exists(test_path):
|
||||
self.dataExists = True
|
||||
# Check is they want to replace existing data.
|
||||
answer = QtGui.QMessageBox.warning(self,
|
||||
translate('OpenLP.AdvancedTab', 'Overwrite Existing Data'),
|
||||
translate('OpenLP.AdvancedTab',
|
||||
'WARNING: \n\n'
|
||||
'The location you have selected \n\n%s\n\n'
|
||||
'appears to contain OpenLP data files. Do you wish to replace '
|
||||
'these files with the current data files?'
|
||||
% os.path.abspath(data_path,)),
|
||||
QtGui.QMessageBox.StandardButtons(
|
||||
QtGui.QMessageBox.Yes |
|
||||
QtGui.QMessageBox.No),
|
||||
QtGui.QMessageBox.No)
|
||||
if answer == QtGui.QMessageBox.Yes:
|
||||
self.dataDirectoryCopyCheckBox.setChecked(True)
|
||||
self.newDataDirectoryHasFilesLabel.show()
|
||||
else:
|
||||
self.dataDirectoryCopyCheckBox.setChecked(False)
|
||||
self.newDataDirectoryHasFilesLabel.hide()
|
||||
else:
|
||||
self.dataExists = False
|
||||
self.dataDirectoryCopyCheckBox.setChecked(True)
|
||||
self.newDataDirectoryHasFilesLabel.hide()
|
||||
|
||||
def onDataDirectoryCancelButtonClicked(self):
|
||||
"""
|
||||
Cancel the data directory location change
|
||||
"""
|
||||
self.newDataDirectoryEdit.clear()
|
||||
self.dataDirectoryCopyCheckBox.setChecked(False)
|
||||
Receiver.send_message(u'set_new_data_path', u'')
|
||||
Receiver.send_message(u'set_copy_data', False)
|
||||
self.dataDirectoryCopyCheckBox.hide()
|
||||
self.dataDirectoryCancelButton.hide()
|
||||
self.newDataDirectoryHasFilesLabel.hide()
|
||||
|
||||
def onDefaultRevertButtonClicked(self):
|
||||
self.defaultFileEdit.setText(u':/graphics/openlp-splash-screen.png')
|
||||
self.defaultFileEdit.setFocus()
|
||||
|
@ -101,9 +101,8 @@ class Display(QtGui.QGraphicsView):
|
||||
self.frame.setScrollBarPolicy(QtCore.Qt.Horizontal,
|
||||
QtCore.Qt.ScrollBarAlwaysOff)
|
||||
|
||||
def resizeEvent(self, ev):
|
||||
self.webView.setGeometry(0, 0,
|
||||
self.width(), self.height())
|
||||
def resizeEvent(self, event):
|
||||
self.webView.setGeometry(0, 0, self.width(), self.height())
|
||||
|
||||
def isWebLoaded(self):
|
||||
"""
|
||||
@ -121,7 +120,6 @@ class MainDisplay(Display):
|
||||
Display.__init__(self, parent, live, controller)
|
||||
self.imageManager = imageManager
|
||||
self.screens = ScreenList()
|
||||
self.plugins = PluginManager.get_instance().plugins
|
||||
self.rebuildCSS = False
|
||||
self.hideMode = None
|
||||
self.override = {}
|
||||
|
@ -29,6 +29,8 @@ import logging
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
from distutils import dir_util
|
||||
from distutils.errors import DistutilsFileError
|
||||
from tempfile import gettempdir
|
||||
import time
|
||||
from datetime import datetime
|
||||
@ -583,6 +585,8 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
|
||||
# Once settings are loaded update the menu with the recent files.
|
||||
self.updateRecentFilesMenu()
|
||||
self.pluginForm = PluginForm(self)
|
||||
self.newDataPath = u''
|
||||
self.copyData = False
|
||||
# Set up signals and slots
|
||||
QtCore.QObject.connect(self.importThemeItem,
|
||||
QtCore.SIGNAL(u'triggered()'),
|
||||
@ -635,6 +639,8 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
|
||||
QtCore.SIGNAL(u'config_screen_changed'), self.screenChanged)
|
||||
QtCore.QObject.connect(Receiver.get_receiver(),
|
||||
QtCore.SIGNAL(u'mainwindow_status_text'), self.showStatusMessage)
|
||||
QtCore.QObject.connect(Receiver.get_receiver(),
|
||||
QtCore.SIGNAL(u'cleanup'), self.cleanUp)
|
||||
# Media Manager
|
||||
QtCore.QObject.connect(self.mediaToolBox,
|
||||
QtCore.SIGNAL(u'currentChanged(int)'), self.onMediaToolBoxChanged)
|
||||
@ -647,6 +653,10 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
|
||||
QtCore.QObject.connect(Receiver.get_receiver(),
|
||||
QtCore.SIGNAL(u'openlp_information_message'),
|
||||
self.onInformationMessage)
|
||||
QtCore.QObject.connect(Receiver.get_receiver(),
|
||||
QtCore.SIGNAL(u'set_new_data_path'), self.setNewDataPath)
|
||||
QtCore.QObject.connect(Receiver.get_receiver(),
|
||||
QtCore.SIGNAL(u'set_copy_data'), self.setCopyData)
|
||||
# warning cyclic dependency
|
||||
# renderer needs to call ThemeManager and
|
||||
# ThemeManager needs to call Renderer
|
||||
@ -1199,6 +1209,9 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
|
||||
if save_settings:
|
||||
# Save settings
|
||||
self.saveSettings()
|
||||
# Check if we need to change the data directory
|
||||
if self.newDataPath:
|
||||
self.changeDataDirectory()
|
||||
# Close down the display
|
||||
if self.liveController.display:
|
||||
self.liveController.display.close()
|
||||
@ -1475,3 +1488,45 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
|
||||
self.timer_id = 0
|
||||
self.loadProgressBar.hide()
|
||||
Receiver.send_message(u'openlp_process_events')
|
||||
|
||||
def setNewDataPath(self, new_data_path):
|
||||
self.newDataPath = new_data_path
|
||||
|
||||
def setCopyData(self, copy_data):
|
||||
self.copyData = copy_data
|
||||
|
||||
def changeDataDirectory(self):
|
||||
log.info(u'Changing data path to %s' % self.newDataPath )
|
||||
old_data_path = unicode(AppLocation.get_data_path())
|
||||
# Copy OpenLP data to new location if requested.
|
||||
if self.copyData:
|
||||
log.info(u'Copying data to new path')
|
||||
try:
|
||||
Receiver.send_message(u'openlp_process_events')
|
||||
Receiver.send_message(u'cursor_busy')
|
||||
self.showStatusMessage(
|
||||
translate('OpenLP.MainWindow',
|
||||
'Copying OpenLP data to new data directory location - %s '
|
||||
'- Please wait for copy to finish'
|
||||
% self.newDataPath))
|
||||
dir_util.copy_tree(old_data_path, self.newDataPath)
|
||||
log.info(u'Copy sucessful')
|
||||
except (IOError, os.error, DistutilsFileError), why:
|
||||
Receiver.send_message(u'cursor_normal')
|
||||
log.exception(u'Data copy failed %s' % unicode(why))
|
||||
QtGui.QMessageBox.critical(self,
|
||||
translate('OpenLP.MainWindow', 'New Data Directory Error'),
|
||||
translate('OpenLP.MainWindow',
|
||||
'OpenLP Data directory copy failed\n\n%s'
|
||||
% unicode(why)),
|
||||
QtGui.QMessageBox.StandardButtons(
|
||||
QtGui.QMessageBox.Ok))
|
||||
return False
|
||||
else:
|
||||
log.info(u'No data copy requested')
|
||||
# Change the location of data directory in config file.
|
||||
settings = QtCore.QSettings()
|
||||
settings.setValue(u'advanced/data path', self.newDataPath)
|
||||
# Check if the new data path is our default.
|
||||
if self.newDataPath == AppLocation.get_directory(AppLocation.DataDir):
|
||||
settings.remove(u'advanced/data path')
|
||||
|
@ -336,8 +336,7 @@ class MediaController(object):
|
||||
if controller.isLive and \
|
||||
(Settings().value(u'general/auto unblank',
|
||||
QtCore.QVariant(False)).toBool() or \
|
||||
controller.media_info.is_background == True) or \
|
||||
controller.isLive == False:
|
||||
controller.media_info.is_background) or not controller.isLive:
|
||||
if not self.video_play([controller]):
|
||||
critical_error_message_box(
|
||||
translate('MediaPlugin.MediaItem', 'Unsupported File'),
|
||||
@ -496,7 +495,7 @@ class MediaController(object):
|
||||
return
|
||||
controller = self.parent.liveController
|
||||
for display in self.curDisplayMediaPlayer.keys():
|
||||
if display.controller != controller or \
|
||||
if display.controller != controller or \
|
||||
self.curDisplayMediaPlayer[display].state != MediaState.Playing:
|
||||
continue
|
||||
self.curDisplayMediaPlayer[display].pause(display)
|
||||
|
@ -38,21 +38,21 @@ from openlp.core.ui.media import MediaState
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
ADDITIONAL_EXT = {
|
||||
u'audio/ac3': [u'.ac3'],
|
||||
u'audio/flac': [u'.flac'],
|
||||
u'audio/x-m4a': [u'.m4a'],
|
||||
u'audio/midi': [u'.mid', u'.midi'],
|
||||
u'audio/x-mp3': [u'.mp3'],
|
||||
u'audio/mpeg': [u'.mp3', u'.mp2', u'.mpga', u'.mpega', u'.m4a'],
|
||||
u'audio/qcelp': [u'.qcp'],
|
||||
u'audio/x-wma': [u'.wma'],
|
||||
u'audio/x-ms-wma': [u'.wma'],
|
||||
u'video/x-flv': [u'.flv'],
|
||||
u'video/x-matroska': [u'.mpv', u'.mkv'],
|
||||
u'video/x-wmv': [u'.wmv'],
|
||||
u'video/x-mpg': [u'.mpg'],
|
||||
u'video/mpeg' : [u'.mp4', u'.mts'],
|
||||
u'video/x-ms-wmv': [u'.wmv']}
|
||||
u'audio/ac3': [u'.ac3'],
|
||||
u'audio/flac': [u'.flac'],
|
||||
u'audio/x-m4a': [u'.m4a'],
|
||||
u'audio/midi': [u'.mid', u'.midi'],
|
||||
u'audio/x-mp3': [u'.mp3'],
|
||||
u'audio/mpeg': [u'.mp3', u'.mp2', u'.mpga', u'.mpega', u'.m4a'],
|
||||
u'audio/qcelp': [u'.qcp'],
|
||||
u'audio/x-wma': [u'.wma'],
|
||||
u'audio/x-ms-wma': [u'.wma'],
|
||||
u'video/x-flv': [u'.flv'],
|
||||
u'video/x-matroska': [u'.mpv', u'.mkv'],
|
||||
u'video/x-wmv': [u'.wmv'],
|
||||
u'video/x-mpg': [u'.mpg'],
|
||||
u'video/mpeg' : [u'.mp4', u'.mts'],
|
||||
u'video/x-ms-wmv': [u'.wmv']}
|
||||
|
||||
|
||||
class PhononPlayer(MediaPlayer):
|
||||
@ -101,7 +101,7 @@ class PhononPlayer(MediaPlayer):
|
||||
display.mediaObject = Phonon.MediaObject(display)
|
||||
Phonon.createPath(display.mediaObject, display.phononWidget)
|
||||
if display.hasAudio:
|
||||
display.audio = Phonon.AudioOutput( \
|
||||
display.audio = Phonon.AudioOutput(
|
||||
Phonon.VideoCategory, display.mediaObject)
|
||||
Phonon.createPath(display.mediaObject, display.audio)
|
||||
display.phononWidget.raise_()
|
||||
@ -148,18 +148,17 @@ class PhononPlayer(MediaPlayer):
|
||||
controller.media_info.start_time > 0:
|
||||
start_time = controller.media_info.start_time
|
||||
display.mediaObject.play()
|
||||
if self.media_state_wait(display, Phonon.PlayingState):
|
||||
if start_time > 0:
|
||||
self.seek(display, controller.media_info.start_time*1000)
|
||||
self.volume(display, controller.media_info.volume)
|
||||
controller.media_info.length = \
|
||||
int(display.mediaObject.totalTime()/1000)
|
||||
controller.seekSlider.setMaximum(controller.media_info.length*1000)
|
||||
self.state = MediaState.Playing
|
||||
display.phononWidget.raise_()
|
||||
return True
|
||||
else:
|
||||
if not self.media_state_wait(display, Phonon.PlayingState):
|
||||
return False
|
||||
if start_time > 0:
|
||||
self.seek(display, controller.media_info.start_time * 1000)
|
||||
self.volume(display, controller.media_info.volume)
|
||||
controller.media_info.length = \
|
||||
int(display.mediaObject.totalTime() / 1000)
|
||||
controller.seekSlider.setMaximum(controller.media_info.length * 1000)
|
||||
self.state = MediaState.Playing
|
||||
display.phononWidget.raise_()
|
||||
return True
|
||||
|
||||
def pause(self, display):
|
||||
display.mediaObject.pause()
|
||||
@ -198,9 +197,9 @@ class PhononPlayer(MediaPlayer):
|
||||
controller = display.controller
|
||||
if controller.media_info.end_time > 0:
|
||||
if display.mediaObject.currentTime() > \
|
||||
controller.media_info.end_time*1000:
|
||||
controller.media_info.end_time * 1000:
|
||||
self.stop(display)
|
||||
self.set_visible(display, False)
|
||||
if not controller.seekSlider.isSliderDown():
|
||||
controller.seekSlider.setSliderPosition( \
|
||||
controller.seekSlider.setSliderPosition(
|
||||
display.mediaObject.currentTime())
|
||||
|
@ -48,7 +48,7 @@ import sys
|
||||
from inspect import getargspec
|
||||
|
||||
__version__ = "N/A"
|
||||
build_date = "Fri Apr 27 16:47:21 2012"
|
||||
build_date = "Fri Jun 8 09:31:07 2012"
|
||||
|
||||
# Internal guard to prevent internal classes to be directly
|
||||
# instanciated.
|
||||
@ -905,10 +905,10 @@ class Instance(_Ctype):
|
||||
def media_new(self, mrl, *options):
|
||||
"""Create a new Media instance.
|
||||
|
||||
If mrl contains a colon (:), it will be treated as a
|
||||
URL. Else, it will be considered as a local path. If you need
|
||||
more control, directly use media_new_location/media_new_path
|
||||
methods.
|
||||
If mrl contains a colon (:) preceded by more than 1 letter, it
|
||||
will be treated as a URL. Else, it will be considered as a
|
||||
local path. If you need more control, directly use
|
||||
media_new_location/media_new_path methods.
|
||||
|
||||
Options can be specified as supplementary string parameters, e.g.
|
||||
|
||||
@ -920,7 +920,7 @@ class Instance(_Ctype):
|
||||
|
||||
@param options: optional media option=value strings
|
||||
"""
|
||||
if ':' in mrl:
|
||||
if ':' in mrl and mrl.index(':') > 1:
|
||||
# Assume it is a URL
|
||||
m = libvlc_media_new_location(self, mrl)
|
||||
else:
|
||||
@ -1031,6 +1031,13 @@ class Instance(_Ctype):
|
||||
'''
|
||||
return libvlc_log_open(self)
|
||||
|
||||
def media_discoverer_new_from_name(self, psz_name):
|
||||
'''Discover media service by name.
|
||||
@param psz_name: service name.
|
||||
@return: media discover object or NULL in case of error.
|
||||
'''
|
||||
return libvlc_media_discoverer_new_from_name(self, psz_name)
|
||||
|
||||
def media_new_location(self, psz_mrl):
|
||||
'''Create a media with a certain given media resource location,
|
||||
for instance a valid URL.
|
||||
@ -1080,13 +1087,6 @@ class Instance(_Ctype):
|
||||
'''
|
||||
return libvlc_media_new_as_node(self, psz_name)
|
||||
|
||||
def media_discoverer_new_from_name(self, psz_name):
|
||||
'''Discover media service by name.
|
||||
@param psz_name: service name.
|
||||
@return: media discover object or NULL in case of error.
|
||||
'''
|
||||
return libvlc_media_discoverer_new_from_name(self, psz_name)
|
||||
|
||||
def media_library_new(self):
|
||||
'''Create an new Media Library object.
|
||||
@return: a new object or NULL on error.
|
||||
@ -1522,7 +1522,7 @@ class Media(_Ctype):
|
||||
|
||||
def save_meta(self):
|
||||
'''Save the meta previously set.
|
||||
@return: true if the write operation was successful.
|
||||
@return: true if the write operation was successfull.
|
||||
'''
|
||||
return libvlc_media_save_meta(self)
|
||||
|
||||
@ -3088,6 +3088,67 @@ def libvlc_clock():
|
||||
ctypes.c_int64)
|
||||
return f()
|
||||
|
||||
def libvlc_media_discoverer_new_from_name(p_inst, psz_name):
|
||||
'''Discover media service by name.
|
||||
@param p_inst: libvlc instance.
|
||||
@param psz_name: service name.
|
||||
@return: media discover object or NULL in case of error.
|
||||
'''
|
||||
f = _Cfunctions.get('libvlc_media_discoverer_new_from_name', None) or \
|
||||
_Cfunction('libvlc_media_discoverer_new_from_name', ((1,), (1,),), class_result(MediaDiscoverer),
|
||||
ctypes.c_void_p, Instance, ctypes.c_char_p)
|
||||
return f(p_inst, psz_name)
|
||||
|
||||
def libvlc_media_discoverer_release(p_mdis):
|
||||
'''Release media discover object. If the reference count reaches 0, then
|
||||
the object will be released.
|
||||
@param p_mdis: media service discover object.
|
||||
'''
|
||||
f = _Cfunctions.get('libvlc_media_discoverer_release', None) or \
|
||||
_Cfunction('libvlc_media_discoverer_release', ((1,),), None,
|
||||
None, MediaDiscoverer)
|
||||
return f(p_mdis)
|
||||
|
||||
def libvlc_media_discoverer_localized_name(p_mdis):
|
||||
'''Get media service discover object its localized name.
|
||||
@param p_mdis: media discover object.
|
||||
@return: localized name.
|
||||
'''
|
||||
f = _Cfunctions.get('libvlc_media_discoverer_localized_name', None) or \
|
||||
_Cfunction('libvlc_media_discoverer_localized_name', ((1,),), string_result,
|
||||
ctypes.c_void_p, MediaDiscoverer)
|
||||
return f(p_mdis)
|
||||
|
||||
def libvlc_media_discoverer_media_list(p_mdis):
|
||||
'''Get media service discover media list.
|
||||
@param p_mdis: media service discover object.
|
||||
@return: list of media items.
|
||||
'''
|
||||
f = _Cfunctions.get('libvlc_media_discoverer_media_list', None) or \
|
||||
_Cfunction('libvlc_media_discoverer_media_list', ((1,),), class_result(MediaList),
|
||||
ctypes.c_void_p, MediaDiscoverer)
|
||||
return f(p_mdis)
|
||||
|
||||
def libvlc_media_discoverer_event_manager(p_mdis):
|
||||
'''Get event manager from media service discover object.
|
||||
@param p_mdis: media service discover object.
|
||||
@return: event manager object.
|
||||
'''
|
||||
f = _Cfunctions.get('libvlc_media_discoverer_event_manager', None) or \
|
||||
_Cfunction('libvlc_media_discoverer_event_manager', ((1,),), class_result(EventManager),
|
||||
ctypes.c_void_p, MediaDiscoverer)
|
||||
return f(p_mdis)
|
||||
|
||||
def libvlc_media_discoverer_is_running(p_mdis):
|
||||
'''Query if media service discover object is running.
|
||||
@param p_mdis: media service discover object.
|
||||
@return: true if running, false if not \libvlc_return_bool.
|
||||
'''
|
||||
f = _Cfunctions.get('libvlc_media_discoverer_is_running', None) or \
|
||||
_Cfunction('libvlc_media_discoverer_is_running', ((1,),), None,
|
||||
ctypes.c_int, MediaDiscoverer)
|
||||
return f(p_mdis)
|
||||
|
||||
def libvlc_media_new_location(p_instance, psz_mrl):
|
||||
'''Create a media with a certain given media resource location,
|
||||
for instance a valid URL.
|
||||
@ -3258,7 +3319,7 @@ def libvlc_media_set_meta(p_md, e_meta, psz_value):
|
||||
def libvlc_media_save_meta(p_md):
|
||||
'''Save the meta previously set.
|
||||
@param p_md: the media desriptor.
|
||||
@return: true if the write operation was successful.
|
||||
@return: true if the write operation was successfull.
|
||||
'''
|
||||
f = _Cfunctions.get('libvlc_media_save_meta', None) or \
|
||||
_Cfunction('libvlc_media_save_meta', ((1,),), None,
|
||||
@ -3392,67 +3453,6 @@ def libvlc_media_get_tracks_info(p_md):
|
||||
ctypes.c_int, Media, ctypes.POINTER(ctypes.c_void_p))
|
||||
return f(p_md)
|
||||
|
||||
def libvlc_media_discoverer_new_from_name(p_inst, psz_name):
|
||||
'''Discover media service by name.
|
||||
@param p_inst: libvlc instance.
|
||||
@param psz_name: service name.
|
||||
@return: media discover object or NULL in case of error.
|
||||
'''
|
||||
f = _Cfunctions.get('libvlc_media_discoverer_new_from_name', None) or \
|
||||
_Cfunction('libvlc_media_discoverer_new_from_name', ((1,), (1,),), class_result(MediaDiscoverer),
|
||||
ctypes.c_void_p, Instance, ctypes.c_char_p)
|
||||
return f(p_inst, psz_name)
|
||||
|
||||
def libvlc_media_discoverer_release(p_mdis):
|
||||
'''Release media discover object. If the reference count reaches 0, then
|
||||
the object will be released.
|
||||
@param p_mdis: media service discover object.
|
||||
'''
|
||||
f = _Cfunctions.get('libvlc_media_discoverer_release', None) or \
|
||||
_Cfunction('libvlc_media_discoverer_release', ((1,),), None,
|
||||
None, MediaDiscoverer)
|
||||
return f(p_mdis)
|
||||
|
||||
def libvlc_media_discoverer_localized_name(p_mdis):
|
||||
'''Get media service discover object its localized name.
|
||||
@param p_mdis: media discover object.
|
||||
@return: localized name.
|
||||
'''
|
||||
f = _Cfunctions.get('libvlc_media_discoverer_localized_name', None) or \
|
||||
_Cfunction('libvlc_media_discoverer_localized_name', ((1,),), string_result,
|
||||
ctypes.c_void_p, MediaDiscoverer)
|
||||
return f(p_mdis)
|
||||
|
||||
def libvlc_media_discoverer_media_list(p_mdis):
|
||||
'''Get media service discover media list.
|
||||
@param p_mdis: media service discover object.
|
||||
@return: list of media items.
|
||||
'''
|
||||
f = _Cfunctions.get('libvlc_media_discoverer_media_list', None) or \
|
||||
_Cfunction('libvlc_media_discoverer_media_list', ((1,),), class_result(MediaList),
|
||||
ctypes.c_void_p, MediaDiscoverer)
|
||||
return f(p_mdis)
|
||||
|
||||
def libvlc_media_discoverer_event_manager(p_mdis):
|
||||
'''Get event manager from media service discover object.
|
||||
@param p_mdis: media service discover object.
|
||||
@return: event manager object.
|
||||
'''
|
||||
f = _Cfunctions.get('libvlc_media_discoverer_event_manager', None) or \
|
||||
_Cfunction('libvlc_media_discoverer_event_manager', ((1,),), class_result(EventManager),
|
||||
ctypes.c_void_p, MediaDiscoverer)
|
||||
return f(p_mdis)
|
||||
|
||||
def libvlc_media_discoverer_is_running(p_mdis):
|
||||
'''Query if media service discover object is running.
|
||||
@param p_mdis: media service discover object.
|
||||
@return: true if running, false if not \libvlc_return_bool.
|
||||
'''
|
||||
f = _Cfunctions.get('libvlc_media_discoverer_is_running', None) or \
|
||||
_Cfunction('libvlc_media_discoverer_is_running', ((1,),), None,
|
||||
ctypes.c_int, MediaDiscoverer)
|
||||
return f(p_mdis)
|
||||
|
||||
def libvlc_media_library_new(p_instance):
|
||||
'''Create an new Media Library object.
|
||||
@param p_instance: the libvlc instance.
|
||||
@ -5595,7 +5595,7 @@ if __name__ == '__main__':
|
||||
print('Aspect ratio: %s' % player.video_get_aspect_ratio())
|
||||
#print('Window:' % player.get_hwnd()
|
||||
except Exception:
|
||||
print('Error: %s', sys.exc_info()[1])
|
||||
print('Error: %s' % sys.exc_info()[1])
|
||||
|
||||
def sec_forward():
|
||||
"""Go forward one sec"""
|
||||
|
@ -63,7 +63,7 @@ if VLC_AVAILABLE:
|
||||
log.debug(u'VLC could not be loaded: %s' % version)
|
||||
|
||||
AUDIO_EXT = [
|
||||
u'*.mp3'
|
||||
u'*.mp3'
|
||||
, u'*.wav'
|
||||
, u'*.ogg'
|
||||
]
|
||||
@ -131,9 +131,9 @@ class VlcPlayer(MediaPlayer):
|
||||
# this is platform specific!
|
||||
# you have to give the id of the QFrame (or similar object) to
|
||||
# vlc, different platforms have different functions for this
|
||||
if sys.platform == "win32": # for Windows
|
||||
if sys.platform == "win32":
|
||||
display.vlcMediaPlayer.set_hwnd(int(display.vlcWidget.winId()))
|
||||
elif sys.platform == "darwin": # for MacOS
|
||||
elif sys.platform == "darwin":
|
||||
display.vlcMediaPlayer.set_agl(int(display.vlcWidget.winId()))
|
||||
else:
|
||||
# for Linux using the X Server
|
||||
@ -182,17 +182,16 @@ class VlcPlayer(MediaPlayer):
|
||||
if controller.media_info.start_time > 0:
|
||||
start_time = controller.media_info.start_time
|
||||
display.vlcMediaPlayer.play()
|
||||
if self.media_state_wait(display, vlc.State.Playing):
|
||||
if start_time > 0:
|
||||
self.seek(display, controller.media_info.start_time * 1000)
|
||||
controller.media_info.length = \
|
||||
int(display.vlcMediaPlayer.get_media().get_duration() / 1000)
|
||||
controller.seekSlider.setMaximum(controller.media_info.length * 1000)
|
||||
self.state = MediaState.Playing
|
||||
display.vlcWidget.raise_()
|
||||
return True
|
||||
else:
|
||||
if not self.media_state_wait(display, vlc.State.Playing):
|
||||
return False
|
||||
if start_time > 0:
|
||||
self.seek(display, controller.media_info.start_time * 1000)
|
||||
controller.media_info.length = \
|
||||
int(display.vlcMediaPlayer.get_media().get_duration() / 1000)
|
||||
controller.seekSlider.setMaximum(controller.media_info.length * 1000)
|
||||
self.state = MediaState.Playing
|
||||
display.vlcWidget.raise_()
|
||||
return True
|
||||
|
||||
def pause(self, display):
|
||||
if display.vlcMedia.get_state() != vlc.State.Playing:
|
||||
|
@ -227,33 +227,33 @@ FLASH_HTML = u"""
|
||||
"""
|
||||
|
||||
VIDEO_EXT = [
|
||||
u'*.3gp'
|
||||
, u'*.3gpp'
|
||||
, u'*.3g2'
|
||||
, u'*.3gpp2'
|
||||
, u'*.aac'
|
||||
, u'*.flv'
|
||||
, u'*.f4a'
|
||||
, u'*.f4b'
|
||||
, u'*.f4p'
|
||||
, u'*.f4v'
|
||||
, u'*.mov'
|
||||
, u'*.m4a'
|
||||
, u'*.m4b'
|
||||
, u'*.m4p'
|
||||
, u'*.m4v'
|
||||
, u'*.mkv'
|
||||
, u'*.mp4'
|
||||
, u'*.ogv'
|
||||
, u'*.webm'
|
||||
, u'*.mpg', u'*.wmv', u'*.mpeg', u'*.avi'
|
||||
, u'*.swf'
|
||||
]
|
||||
u'*.3gp'
|
||||
, u'*.3gpp'
|
||||
, u'*.3g2'
|
||||
, u'*.3gpp2'
|
||||
, u'*.aac'
|
||||
, u'*.flv'
|
||||
, u'*.f4a'
|
||||
, u'*.f4b'
|
||||
, u'*.f4p'
|
||||
, u'*.f4v'
|
||||
, u'*.mov'
|
||||
, u'*.m4a'
|
||||
, u'*.m4b'
|
||||
, u'*.m4p'
|
||||
, u'*.m4v'
|
||||
, u'*.mkv'
|
||||
, u'*.mp4'
|
||||
, u'*.ogv'
|
||||
, u'*.webm'
|
||||
, u'*.mpg', u'*.wmv', u'*.mpeg', u'*.avi'
|
||||
, u'*.swf'
|
||||
]
|
||||
|
||||
AUDIO_EXT = [
|
||||
u'*.mp3'
|
||||
, u'*.ogg'
|
||||
]
|
||||
u'*.mp3'
|
||||
, u'*.ogg'
|
||||
]
|
||||
|
||||
|
||||
class WebkitPlayer(MediaPlayer):
|
||||
@ -339,7 +339,7 @@ class WebkitPlayer(MediaPlayer):
|
||||
else:
|
||||
display.frame.evaluateJavaScript(u'show_video("play");')
|
||||
if start_time > 0:
|
||||
self.seek(display, controller.media_info.start_time*1000)
|
||||
self.seek(display, controller.media_info.start_time * 1000)
|
||||
# TODO add playing check and get the correct media length
|
||||
controller.media_info.length = length
|
||||
self.state = MediaState.Playing
|
||||
@ -375,11 +375,11 @@ class WebkitPlayer(MediaPlayer):
|
||||
controller = display.controller
|
||||
if controller.media_info.is_flash:
|
||||
seek = seekVal
|
||||
display.frame.evaluateJavaScript( \
|
||||
display.frame.evaluateJavaScript(
|
||||
u'show_flash("seek", null, null, "%s");' % (seek))
|
||||
else:
|
||||
seek = float(seekVal)/1000
|
||||
display.frame.evaluateJavaScript( \
|
||||
seek = float(seekVal) / 1000
|
||||
display.frame.evaluateJavaScript(
|
||||
u'show_video("seek", null, null, null, "%f");' % (seek))
|
||||
|
||||
def reset(self, display):
|
||||
@ -406,24 +406,24 @@ class WebkitPlayer(MediaPlayer):
|
||||
def update_ui(self, display):
|
||||
controller = display.controller
|
||||
if controller.media_info.is_flash:
|
||||
currentTime = display.frame.evaluateJavaScript( \
|
||||
currentTime = display.frame.evaluateJavaScript(
|
||||
u'show_flash("currentTime");').toInt()[0]
|
||||
length = display.frame.evaluateJavaScript( \
|
||||
length = display.frame.evaluateJavaScript(
|
||||
u'show_flash("length");').toInt()[0]
|
||||
else:
|
||||
if display.frame.evaluateJavaScript( \
|
||||
if display.frame.evaluateJavaScript(
|
||||
u'show_video("isEnded");').toString() == 'true':
|
||||
self.stop(display)
|
||||
(currentTime, ok) = display.frame.evaluateJavaScript( \
|
||||
(currentTime, ok) = display.frame.evaluateJavaScript(
|
||||
u'show_video("currentTime");').toFloat()
|
||||
# check if conversion was ok and value is not 'NaN'
|
||||
if ok and currentTime != float('inf'):
|
||||
currentTime = int(currentTime*1000)
|
||||
(length, ok) = display.frame.evaluateJavaScript( \
|
||||
currentTime = int(currentTime * 1000)
|
||||
(length, ok) = display.frame.evaluateJavaScript(
|
||||
u'show_video("length");').toFloat()
|
||||
# check if conversion was ok and value is not 'NaN'
|
||||
if ok and length != float('inf'):
|
||||
length = int(length*1000)
|
||||
length = int(length * 1000)
|
||||
if currentTime > 0:
|
||||
controller.media_info.length = length
|
||||
controller.seekSlider.setMaximum(length)
|
||||
|
@ -102,9 +102,9 @@ class PluginForm(QtGui.QDialog, Ui_PluginViewDialog):
|
||||
self.versionNumberLabel.setText(self.activePlugin.version)
|
||||
self.aboutTextBrowser.setHtml(self.activePlugin.about())
|
||||
self.programaticChange = True
|
||||
status = 1
|
||||
status = PluginStatus.Active
|
||||
if self.activePlugin.status == PluginStatus.Active:
|
||||
status = 0
|
||||
status = PluginStatus.Inactive
|
||||
self.statusComboBox.setCurrentIndex(status)
|
||||
self.statusComboBox.setEnabled(True)
|
||||
self.programaticChange = False
|
||||
@ -129,7 +129,7 @@ class PluginForm(QtGui.QDialog, Ui_PluginViewDialog):
|
||||
def onStatusComboBoxChanged(self, status):
|
||||
if self.programaticChange or status == PluginStatus.Disabled:
|
||||
return
|
||||
if status == 0:
|
||||
if status == PluginStatus.Inactive:
|
||||
Receiver.send_message(u'cursor_busy')
|
||||
self.activePlugin.toggleStatus(PluginStatus.Active)
|
||||
Receiver.send_message(u'cursor_normal')
|
||||
|
@ -581,10 +581,7 @@ class ServiceManager(QtGui.QWidget):
|
||||
return self.saveFileAs()
|
||||
self.mainwindow.addRecentFile(path_file_name)
|
||||
self.setModified(False)
|
||||
try:
|
||||
delete_file(temp_file_name)
|
||||
except:
|
||||
pass
|
||||
delete_file(temp_file_name)
|
||||
return success
|
||||
|
||||
def saveFileAs(self):
|
||||
|
@ -29,7 +29,7 @@ The :mod:`settingsform` provides a user interface for the OpenLP settings
|
||||
"""
|
||||
import logging
|
||||
|
||||
from PyQt4 import QtGui
|
||||
from PyQt4 import QtCore, QtGui
|
||||
|
||||
from openlp.core.lib import Receiver, build_icon, PluginStatus
|
||||
from openlp.core.ui import AdvancedTab, GeneralTab, ThemesTab
|
||||
|
@ -864,7 +864,7 @@ class SlideController(Controller):
|
||||
image = self.imageManager.getImage(frame[u'title'])
|
||||
label.setPixmap(QtGui.QPixmap.fromImage(image))
|
||||
self.previewListWidget.setCellWidget(framenumber, 0, label)
|
||||
slideHeight = width * self.parent().renderer.screen_ratio
|
||||
slideHeight = width * (1 / self.ratio)
|
||||
row += 1
|
||||
self.slideList[unicode(row)] = row - 1
|
||||
text.append(unicode(row))
|
||||
|
@ -248,8 +248,7 @@ class ThemeManager(QtGui.QWidget):
|
||||
Settings().setValue(
|
||||
self.settingsSection + u'/global theme',
|
||||
QtCore.QVariant(self.global_theme))
|
||||
Receiver.send_message(u'theme_update_global',
|
||||
self.global_theme)
|
||||
Receiver.send_message(u'theme_update_global', self.global_theme)
|
||||
self._pushThemes()
|
||||
|
||||
def onAddTheme(self):
|
||||
@ -286,6 +285,8 @@ class ThemeManager(QtGui.QWidget):
|
||||
if plugin.usesTheme(old_theme_name):
|
||||
plugin.renameTheme(old_theme_name, new_theme_name)
|
||||
self.loadThemes()
|
||||
self.mainwindow.renderer.update_theme(
|
||||
new_theme_name, old_theme_name)
|
||||
|
||||
def onCopyTheme(self):
|
||||
"""
|
||||
@ -322,9 +323,8 @@ class ThemeManager(QtGui.QWidget):
|
||||
Loads the settings for the theme that is to be edited and launches the
|
||||
theme editing form so the user can make their changes.
|
||||
"""
|
||||
if check_item_selected(self.themeListWidget,
|
||||
translate('OpenLP.ThemeManager',
|
||||
'You must select a theme to edit.')):
|
||||
if check_item_selected(self.themeListWidget, translate(
|
||||
'OpenLP.ThemeManager', 'You must select a theme to edit.')):
|
||||
item = self.themeListWidget.currentItem()
|
||||
theme = self.getThemeData(
|
||||
unicode(item.data(QtCore.Qt.UserRole).toString()))
|
||||
@ -333,6 +333,7 @@ class ThemeManager(QtGui.QWidget):
|
||||
self.themeForm.theme = theme
|
||||
self.themeForm.exec_(True)
|
||||
self.old_background_image = None
|
||||
self.mainwindow.renderer.update_theme(theme.theme_name)
|
||||
|
||||
def onDeleteTheme(self):
|
||||
"""
|
||||
@ -350,6 +351,7 @@ class ThemeManager(QtGui.QWidget):
|
||||
# As we do not reload the themes, push out the change. Reload the
|
||||
# list as the internal lists and events need to be triggered.
|
||||
self._pushThemes()
|
||||
self.mainwindow.renderer.update_theme(theme, only_delete=True)
|
||||
|
||||
def deleteTheme(self, theme):
|
||||
"""
|
||||
|
@ -152,8 +152,8 @@ class ThemesTab(SettingsTab):
|
||||
settings.setValue(u'theme level', QtCore.QVariant(self.theme_level))
|
||||
settings.setValue(u'global theme', QtCore.QVariant(self.global_theme))
|
||||
settings.endGroup()
|
||||
self.mainwindow.renderer.set_global_theme(
|
||||
self.global_theme, self.theme_level)
|
||||
self.mainwindow.renderer.set_global_theme(self.global_theme)
|
||||
self.mainwindow.renderer.set_theme_level(self.theme_level)
|
||||
Receiver.send_message(u'theme_update_global', self.global_theme)
|
||||
|
||||
def postSetUp(self):
|
||||
@ -170,8 +170,8 @@ class ThemesTab(SettingsTab):
|
||||
|
||||
def onDefaultComboBoxChanged(self, value):
|
||||
self.global_theme = unicode(self.DefaultComboBox.currentText())
|
||||
self.mainwindow.renderer.set_global_theme(
|
||||
self.global_theme, self.theme_level)
|
||||
self.mainwindow.renderer.set_global_theme(self.global_theme)
|
||||
self.mainwindow.renderer.set_theme_level(self.theme_level)
|
||||
self.__previewGlobalTheme()
|
||||
|
||||
def updateThemeList(self, theme_list):
|
||||
@ -190,8 +190,8 @@ class ThemesTab(SettingsTab):
|
||||
self.DefaultComboBox.clear()
|
||||
self.DefaultComboBox.addItems(theme_list)
|
||||
find_and_set_in_combo_box(self.DefaultComboBox, self.global_theme)
|
||||
self.mainwindow.renderer.set_global_theme(
|
||||
self.global_theme, self.theme_level)
|
||||
self.mainwindow.renderer.set_global_theme(self.global_theme)
|
||||
self.mainwindow.renderer.set_theme_level(self.theme_level)
|
||||
if self.global_theme is not u'':
|
||||
self.__previewGlobalTheme()
|
||||
|
||||
|
@ -44,20 +44,9 @@ class WizardStrings(object):
|
||||
# Applications/Formats we import from or export to. These get used in
|
||||
# multiple places but do not need translating unless you find evidence of
|
||||
# the writers translating their own product name.
|
||||
CCLI = u'CCLI/SongSelect'
|
||||
CSV = u'CSV'
|
||||
DB = u'DreamBeam'
|
||||
EW = u'EasyWorship'
|
||||
ES = u'EasySlides'
|
||||
FP = u'Foilpresenter'
|
||||
OL = u'OpenLyrics'
|
||||
OS = u'OpenSong'
|
||||
OSIS = u'OSIS'
|
||||
PS = u'PowerSong 1.0'
|
||||
SB = u'SongBeamer'
|
||||
SoF = u'Songs of Fellowship'
|
||||
SSP = u'SongShow Plus'
|
||||
WoW = u'Words of Worship'
|
||||
# These strings should need a good reason to be retranslated elsewhere.
|
||||
FinishedImport = translate('OpenLP.Ui', 'Finished import.')
|
||||
FormatLabel = translate('OpenLP.Ui', 'Format:')
|
||||
@ -76,10 +65,12 @@ class WizardStrings(object):
|
||||
PercentSymbolFormat = unicode(translate('OpenLP.Ui', '%p%'))
|
||||
Ready = translate('OpenLP.Ui', 'Ready.')
|
||||
StartingImport = translate('OpenLP.Ui', 'Starting import...')
|
||||
YouSpecifyFile = unicode(translate('OpenLP.Ui', 'You need to specify at '
|
||||
YouSpecifyFile = unicode(translate('OpenLP.Ui', 'You need to specify one '
|
||||
'%s file to import from.', 'A file type e.g. OpenSong'))
|
||||
YouSpecifyFiles = unicode(translate('OpenLP.Ui', 'You need to specify at '
|
||||
'least one %s file to import from.', 'A file type e.g. OpenSong'))
|
||||
YouSpecifyFolder = unicode(translate('OpenLP.Ui', 'You need to specify a '
|
||||
'%s folder to import from.', 'A file type e.g. OpenSong'))
|
||||
YouSpecifyFolder = unicode(translate('OpenLP.Ui', 'You need to specify one '
|
||||
'%s folder to import from.', 'A song format e.g. PowerSong'))
|
||||
|
||||
|
||||
class OpenLPWizard(QtGui.QWizard):
|
||||
@ -108,7 +99,7 @@ class OpenLPWizard(QtGui.QWizard):
|
||||
|
||||
def setupUi(self, image):
|
||||
"""
|
||||
Set up the wizard UI
|
||||
Set up the wizard UI.
|
||||
"""
|
||||
self.setModal(True)
|
||||
self.setWizardStyle(QtGui.QWizard.ModernStyle)
|
||||
|
@ -89,7 +89,7 @@ class AppLocation(object):
|
||||
VersionDir = 5
|
||||
CacheDir = 6
|
||||
LanguageDir = 7
|
||||
|
||||
|
||||
# Base path where data/config/cache dir is located
|
||||
BaseDir = None
|
||||
|
||||
|
@ -73,7 +73,7 @@ class CSVBible(BibleDB):
|
||||
|
||||
def __init__(self, parent, **kwargs):
|
||||
"""
|
||||
Loads a Bible from a set of CVS files.
|
||||
Loads a Bible from a set of CSV files.
|
||||
This class assumes the files contain all the information and
|
||||
a clean bible is being loaded.
|
||||
"""
|
||||
|
@ -198,9 +198,6 @@ class CustomMediaItem(MediaManagerItem):
|
||||
|
||||
def generateSlideData(self, service_item, item=None, xmlVersion=False,
|
||||
remote=False):
|
||||
raw_footer = []
|
||||
slide = None
|
||||
theme = None
|
||||
item_id = self._getIdOfItemToGenerate(item, self.remoteCustom)
|
||||
service_item.add_capability(ItemCapabilities.CanEdit)
|
||||
service_item.add_capability(ItemCapabilities.CanPreview)
|
||||
@ -221,10 +218,9 @@ class CustomMediaItem(MediaManagerItem):
|
||||
service_item.add_from_text(slide[:30], slide)
|
||||
if Settings().value(self.settingsSection + u'/display footer',
|
||||
QtCore.QVariant(True)).toBool() or credit:
|
||||
raw_footer.append(title + u' ' + credit)
|
||||
service_item.raw_footer.append(u' '.join([title, credit]))
|
||||
else:
|
||||
raw_footer.append(u'')
|
||||
service_item.raw_footer = raw_footer
|
||||
service_item.raw_footer.append(u'')
|
||||
return True
|
||||
|
||||
def onSearchTextButtonClicked(self):
|
||||
|
@ -97,7 +97,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||
self.onVerseOrderTextChanged)
|
||||
QtCore.QObject.connect(self.themeAddButton,
|
||||
QtCore.SIGNAL(u'clicked()'),
|
||||
self.mediaitem.plugin.renderer.themeManager.onAddTheme)
|
||||
self.mediaitem.plugin.renderer.theme_manager.onAddTheme)
|
||||
QtCore.QObject.connect(self.maintenanceButton,
|
||||
QtCore.SIGNAL(u'clicked()'), self.onMaintenanceButtonClicked)
|
||||
QtCore.QObject.connect(self.audioAddFromFileButton,
|
||||
@ -172,17 +172,14 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||
def loadThemes(self, theme_list):
|
||||
self.themeComboBox.clear()
|
||||
self.themeComboBox.addItem(u'')
|
||||
self.themes = []
|
||||
for theme in theme_list:
|
||||
self.themeComboBox.addItem(theme)
|
||||
self.themes.append(theme)
|
||||
self.themes = theme_list
|
||||
self.themeComboBox.addItems(theme_list)
|
||||
set_case_insensitive_completer(self.themes, self.themeComboBox)
|
||||
|
||||
def loadMediaFiles(self):
|
||||
self.audioAddFromMediaButton.setVisible(False)
|
||||
for plugin in self.parent().pluginManager.plugins:
|
||||
if plugin.name == u'media' and \
|
||||
plugin.status == PluginStatus.Active:
|
||||
if plugin.name == u'media' and plugin.status == PluginStatus.Active:
|
||||
self.audioAddFromMediaButton.setVisible(True)
|
||||
self.mediaForm.populateFiles(
|
||||
plugin.mediaItem.getList(MediaType.Audio))
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -29,6 +29,9 @@ The :mod:`importer` modules provides the general song import functionality.
|
||||
"""
|
||||
import logging
|
||||
|
||||
from openlp.core.lib import translate
|
||||
from openlp.core.lib.ui import UiStrings
|
||||
from openlp.core.ui.wizard import WizardStrings
|
||||
from opensongimport import OpenSongImport
|
||||
from easyslidesimport import EasySlidesImport
|
||||
from olpimport import OpenLPSongImport
|
||||
@ -41,6 +44,7 @@ from ewimport import EasyWorshipSongImport
|
||||
from songbeamerimport import SongBeamerImport
|
||||
from songshowplusimport import SongShowPlusImport
|
||||
from foilpresenterimport import FoilPresenterImport
|
||||
from zionworximport import ZionWorxImport
|
||||
# Imports that might fail
|
||||
log = logging.getLogger(__name__)
|
||||
try:
|
||||
@ -62,13 +66,58 @@ except ImportError:
|
||||
log.exception('Error importing %s', 'OooImport')
|
||||
HAS_OOO = False
|
||||
|
||||
class SongFormatSelect(object):
|
||||
"""
|
||||
This is a special enumeration class listing available file selection modes.
|
||||
"""
|
||||
SingleFile = 0
|
||||
MultipleFiles = 1
|
||||
SingleFolder = 2
|
||||
|
||||
class SongFormat(object):
|
||||
"""
|
||||
This is a special enumeration class that holds the various types of songs,
|
||||
plus a few helper functions to facilitate generic handling of song types
|
||||
for importing.
|
||||
This is a special static class that holds an enumeration of the various
|
||||
song formats handled by the importer, the attributes of each song format,
|
||||
and a few helper functions.
|
||||
|
||||
Required attributes for each song format:
|
||||
|
||||
``u'class'``
|
||||
Import class, e.g. ``OpenLyricsImport``
|
||||
``u'name'``
|
||||
Name of the format, e.g. ``u'OpenLyrics'``
|
||||
``u'prefix'``
|
||||
Prefix for Qt objects. Use mixedCase, e.g. ``u'openLyrics'``
|
||||
See ``SongImportForm.addFileSelectItem()``
|
||||
|
||||
Optional attributes for each song format:
|
||||
|
||||
``u'canDisable'``
|
||||
Whether song format importer is disablable.
|
||||
``u'availability'``
|
||||
Whether song format importer is available.
|
||||
``u'selectMode'``
|
||||
Whether format accepts single file, multiple files, or single folder
|
||||
(as per ``SongFormatSelect`` options).
|
||||
``u'filter'``
|
||||
File extension filter for ``QFileDialog``.
|
||||
|
||||
Optional/custom text Strings for ``SongImportForm`` widgets:
|
||||
|
||||
``u'comboBoxText'``
|
||||
Combo box selector (default value is the format's ``u'name'``).
|
||||
``u'disabledLabelText'``
|
||||
Required for disablable song formats.
|
||||
``u'getFilesTitle'``
|
||||
Title for ``QFileDialog`` (default includes the format's ``u'name'``).
|
||||
``u'invalidSourceMsg'``
|
||||
Message displayed if ``isValidSource()`` returns ``False``.
|
||||
``u'descriptionText'``
|
||||
Short description (1-2 lines) about the song format.
|
||||
"""
|
||||
_format_availability = {}
|
||||
# Song formats (ordered alphabetically after Generic)
|
||||
# * Numerical order of song formats is significant as it determines the
|
||||
# order used by formatComboBox.
|
||||
Unknown = -1
|
||||
OpenLyrics = 0
|
||||
OpenLP2 = 1
|
||||
@ -85,50 +134,164 @@ class SongFormat(object):
|
||||
SongShowPlus = 12
|
||||
SongsOfFellowship = 13
|
||||
WordsOfWorship = 14
|
||||
#CSV = 15
|
||||
ZionWorx = 15
|
||||
#CSV = 16
|
||||
|
||||
# Set optional attribute defaults
|
||||
__defaults__ = {
|
||||
u'canDisable': False,
|
||||
u'availability': True,
|
||||
u'selectMode': SongFormatSelect.MultipleFiles,
|
||||
u'filter': u'',
|
||||
u'comboBoxText': None,
|
||||
u'disabledLabelText': u'',
|
||||
u'getFilesTitle': None,
|
||||
u'invalidSourceMsg': None,
|
||||
u'descriptionText': None
|
||||
}
|
||||
|
||||
# Set attribute values for each Song Format
|
||||
__attributes__ = {
|
||||
OpenLyrics: {
|
||||
u'class': OpenLyricsImport,
|
||||
u'name': u'OpenLyrics',
|
||||
u'prefix': u'openLyrics',
|
||||
u'filter': u'%s (*.xml)' % translate('SongsPlugin.ImportWizardForm',
|
||||
'OpenLyrics Files'),
|
||||
u'comboBoxText': translate('SongsPlugin.ImportWizardForm',
|
||||
'OpenLyrics or OpenLP 2.0 Exported Song')
|
||||
},
|
||||
OpenLP2: {
|
||||
u'class': OpenLPSongImport,
|
||||
u'name': UiStrings().OLPV2,
|
||||
u'prefix': u'openLP2',
|
||||
u'selectMode': SongFormatSelect.SingleFile,
|
||||
u'filter': u'%s (*.sqlite)' % (translate(
|
||||
'SongsPlugin.ImportWizardForm', 'OpenLP 2.0 Databases'))
|
||||
},
|
||||
OpenLP1: {
|
||||
u'name': UiStrings().OLPV1,
|
||||
u'prefix': u'openLP1',
|
||||
u'canDisable': True,
|
||||
u'selectMode': SongFormatSelect.SingleFile,
|
||||
u'filter': u'%s (*.olp)' % translate('SongsPlugin.ImportWizardForm',
|
||||
'openlp.org v1.x Databases'),
|
||||
u'disabledLabelText': WizardStrings.NoSqlite
|
||||
},
|
||||
Generic: {
|
||||
u'name': translate('SongsPlugin.ImportWizardForm',
|
||||
'Generic Document/Presentation'),
|
||||
u'prefix': u'generic',
|
||||
u'canDisable': True,
|
||||
u'disabledLabelText': translate('SongsPlugin.ImportWizardForm',
|
||||
'The generic document/presentation importer has been disabled '
|
||||
'because OpenLP cannot access OpenOffice or LibreOffice.'),
|
||||
u'getFilesTitle': translate('SongsPlugin.ImportWizardForm',
|
||||
'Select Document/Presentation Files')
|
||||
},
|
||||
CCLI: {
|
||||
u'class': CCLIFileImport,
|
||||
u'name': u'CCLI/SongSelect',
|
||||
u'prefix': u'ccli',
|
||||
u'filter': u'%s (*.usr *.txt)' % translate(
|
||||
'SongsPlugin.ImportWizardForm', 'CCLI SongSelect Files')
|
||||
},
|
||||
DreamBeam: {
|
||||
u'class': DreamBeamImport,
|
||||
u'name': u'DreamBeam',
|
||||
u'prefix': u'dreamBeam',
|
||||
u'filter': u'%s (*.xml)' % translate('SongsPlugin.ImportWizardForm',
|
||||
'DreamBeam Song Files')
|
||||
},
|
||||
EasySlides: {
|
||||
u'class': EasySlidesImport,
|
||||
u'name': u'EasySlides',
|
||||
u'prefix': u'easySlides',
|
||||
u'selectMode': SongFormatSelect.SingleFile,
|
||||
u'filter': u'%s (*.xml)' % translate('SongsPlugin.ImportWizardForm',
|
||||
'EasySlides XML File')
|
||||
},
|
||||
EasyWorship: {
|
||||
u'class': EasyWorshipSongImport,
|
||||
u'name': u'EasyWorship',
|
||||
u'prefix': u'ew',
|
||||
u'selectMode': SongFormatSelect.SingleFile,
|
||||
u'filter': u'%s (*.db)' % translate('SongsPlugin.ImportWizardForm',
|
||||
'EasyWorship Song Database')
|
||||
},
|
||||
FoilPresenter: {
|
||||
u'class': FoilPresenterImport,
|
||||
u'name': u'Foilpresenter',
|
||||
u'prefix': u'foilPresenter',
|
||||
u'filter': u'%s (*.foil)' % translate(
|
||||
'SongsPlugin.ImportWizardForm', 'Foilpresenter Song Files')
|
||||
},
|
||||
OpenSong: {
|
||||
u'class': OpenSongImport,
|
||||
u'name': WizardStrings.OS,
|
||||
u'prefix': u'openSong'
|
||||
},
|
||||
PowerSong: {
|
||||
u'class': PowerSongImport,
|
||||
u'name': u'PowerSong 1.0',
|
||||
u'prefix': u'powerSong',
|
||||
u'selectMode': SongFormatSelect.SingleFolder,
|
||||
u'invalidSourceMsg': translate('SongsPlugin.ImportWizardForm',
|
||||
'You need to specify a valid PowerSong 1.0 database folder.')
|
||||
},
|
||||
SongBeamer: {
|
||||
u'class': SongBeamerImport,
|
||||
u'name': u'SongBeamer',
|
||||
u'prefix': u'songBeamer',
|
||||
u'filter': u'%s (*.sng)' % translate('SongsPlugin.ImportWizardForm',
|
||||
'SongBeamer Files')
|
||||
},
|
||||
SongShowPlus: {
|
||||
u'class': SongShowPlusImport,
|
||||
u'name': u'SongShow Plus',
|
||||
u'prefix': u'songShowPlus',
|
||||
u'filter': u'%s (*.sbsong)' % translate(
|
||||
'SongsPlugin.ImportWizardForm', 'SongShow Plus Song Files')
|
||||
},
|
||||
SongsOfFellowship: {
|
||||
u'name': u'Songs of Fellowship',
|
||||
u'prefix': u'songsOfFellowship',
|
||||
u'canDisable': True,
|
||||
u'filter': u'%s (*.rtf)' % translate('SongsPlugin.ImportWizardForm',
|
||||
'Songs Of Fellowship Song Files'),
|
||||
u'disabledLabelText': translate('SongsPlugin.ImportWizardForm',
|
||||
'The Songs of Fellowship importer has been disabled because '
|
||||
'OpenLP cannot access OpenOffice or LibreOffice.')
|
||||
},
|
||||
WordsOfWorship: {
|
||||
u'class': WowImport,
|
||||
u'name': u'Words of Worship',
|
||||
u'prefix': u'wordsOfWorship',
|
||||
u'filter': u'%s (*.wsg *.wow-song)' % translate(
|
||||
'SongsPlugin.ImportWizardForm', 'Words Of Worship Song Files')
|
||||
},
|
||||
ZionWorx: {
|
||||
u'class': ZionWorxImport,
|
||||
u'name': u'ZionWorx',
|
||||
u'prefix': u'zionWorx',
|
||||
u'selectMode': SongFormatSelect.SingleFile,
|
||||
u'comboBoxText': translate('SongsPlugin.ImportWizardForm',
|
||||
'ZionWorx (CSV)'),
|
||||
u'descriptionText': translate('SongsPlugin.ImportWizardForm',
|
||||
'First convert your ZionWorx database to a CSV text file, as '
|
||||
'explained in the <a href="http://manual.openlp.org/songs.html'
|
||||
'#importing-from-zionworx">User Manual</a>.')
|
||||
# },
|
||||
# CSV: {
|
||||
# u'class': CSVImport,
|
||||
# u'name': WizardStrings.CSV,
|
||||
# u'prefix': u'csv',
|
||||
# u'selectMode': SongFormatSelect.SingleFile
|
||||
}
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def get_class(format):
|
||||
"""
|
||||
Return the appropriate implementation class.
|
||||
|
||||
``format``
|
||||
The song format.
|
||||
"""
|
||||
if format == SongFormat.OpenLP2:
|
||||
return OpenLPSongImport
|
||||
elif format == SongFormat.OpenLP1:
|
||||
return OpenLP1SongImport
|
||||
elif format == SongFormat.OpenLyrics:
|
||||
return OpenLyricsImport
|
||||
elif format == SongFormat.OpenSong:
|
||||
return OpenSongImport
|
||||
elif format == SongFormat.SongsOfFellowship:
|
||||
return SofImport
|
||||
elif format == SongFormat.WordsOfWorship:
|
||||
return WowImport
|
||||
elif format == SongFormat.Generic:
|
||||
return OooImport
|
||||
elif format == SongFormat.CCLI:
|
||||
return CCLIFileImport
|
||||
elif format == SongFormat.DreamBeam:
|
||||
return DreamBeamImport
|
||||
elif format == SongFormat.PowerSong:
|
||||
return PowerSongImport
|
||||
elif format == SongFormat.EasySlides:
|
||||
return EasySlidesImport
|
||||
elif format == SongFormat.EasyWorship:
|
||||
return EasyWorshipSongImport
|
||||
elif format == SongFormat.SongBeamer:
|
||||
return SongBeamerImport
|
||||
elif format == SongFormat.SongShowPlus:
|
||||
return SongShowPlusImport
|
||||
elif format == SongFormat.FoilPresenter:
|
||||
return FoilPresenterImport
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def get_formats_list():
|
||||
def get_format_list():
|
||||
"""
|
||||
Return a list of the supported song formats.
|
||||
"""
|
||||
@ -138,7 +301,7 @@ class SongFormat(object):
|
||||
SongFormat.OpenLP1,
|
||||
SongFormat.Generic,
|
||||
SongFormat.CCLI,
|
||||
SongFormat.DreamBeam,
|
||||
SongFormat.DreamBeam,
|
||||
SongFormat.EasySlides,
|
||||
SongFormat.EasyWorship,
|
||||
SongFormat.FoilPresenter,
|
||||
@ -147,26 +310,55 @@ class SongFormat(object):
|
||||
SongFormat.SongBeamer,
|
||||
SongFormat.SongShowPlus,
|
||||
SongFormat.SongsOfFellowship,
|
||||
SongFormat.WordsOfWorship
|
||||
SongFormat.WordsOfWorship,
|
||||
SongFormat.ZionWorx
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def get(format, *attributes):
|
||||
"""
|
||||
Return requested song format attribute(s).
|
||||
|
||||
``format``
|
||||
A song format from SongFormat.
|
||||
|
||||
``*attributes``
|
||||
Zero or more song format attributes from SongFormat.
|
||||
|
||||
Return type depends on number of supplied attributes:
|
||||
:0: Return dict containing all defined attributes for the format.
|
||||
:1: Return the attribute value.
|
||||
:>1: Return tuple of requested attribute values.
|
||||
"""
|
||||
if not attributes:
|
||||
return SongFormat.__attributes__.get(format)
|
||||
elif len(attributes) == 1:
|
||||
default = SongFormat.__defaults__.get(attributes[0])
|
||||
return SongFormat.__attributes__[format].get(attributes[0],
|
||||
default)
|
||||
else:
|
||||
values = []
|
||||
for attr in attributes:
|
||||
default = SongFormat.__defaults__.get(attr)
|
||||
values.append(SongFormat.__attributes__[format].get(attr,
|
||||
default))
|
||||
return tuple(values)
|
||||
|
||||
@staticmethod
|
||||
def set_availability(format, available):
|
||||
def set(format, attribute, value):
|
||||
"""
|
||||
Set the availability for a given song format.
|
||||
Set specified song format attribute to the supplied value.
|
||||
"""
|
||||
SongFormat._format_availability[format] = available
|
||||
SongFormat.__attributes__[format][attribute] = value
|
||||
|
||||
@staticmethod
|
||||
def get_availability(format):
|
||||
"""
|
||||
Return the availability of a given song format.
|
||||
"""
|
||||
return SongFormat._format_availability.get(format, True)
|
||||
|
||||
SongFormat.set_availability(SongFormat.OpenLP1, HAS_OPENLP1)
|
||||
SongFormat.set_availability(SongFormat.SongsOfFellowship, HAS_SOF)
|
||||
SongFormat.set_availability(SongFormat.Generic, HAS_OOO)
|
||||
|
||||
__all__ = [u'SongFormat']
|
||||
SongFormat.set(SongFormat.OpenLP1, u'availability', HAS_OPENLP1)
|
||||
if HAS_OPENLP1:
|
||||
SongFormat.set(SongFormat.OpenLP1, u'class', OpenLP1SongImport)
|
||||
SongFormat.set(SongFormat.SongsOfFellowship, u'availability', HAS_SOF)
|
||||
if HAS_SOF:
|
||||
SongFormat.set(SongFormat.SongsOfFellowship, u'class', SofImport)
|
||||
SongFormat.set(SongFormat.Generic, u'availability', HAS_OOO)
|
||||
if HAS_OOO:
|
||||
SongFormat.set(SongFormat.Generic, u'class', OooImport)
|
||||
|
||||
__all__ = [u'SongFormat', u'SongFormatSelect']
|
||||
|
@ -33,7 +33,6 @@ import fnmatch
|
||||
import os
|
||||
|
||||
from openlp.core.lib import translate
|
||||
from openlp.core.ui.wizard import WizardStrings
|
||||
from openlp.plugins.songs.lib.songimport import SongImport
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@ -71,26 +70,25 @@ class PowerSongImport(SongImport):
|
||||
|
||||
* .song
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def isValidSource(**kwargs):
|
||||
def isValidSource(import_source):
|
||||
"""
|
||||
Checks if source is a PowerSong 1.0 folder:
|
||||
* is a directory
|
||||
* contains at least one *.song file
|
||||
"""
|
||||
if u'folder' in kwargs:
|
||||
dir = kwargs[u'folder']
|
||||
if os.path.isdir(dir):
|
||||
for file in os.listdir(dir):
|
||||
if fnmatch.fnmatch(file, u'*.song'):
|
||||
return True
|
||||
if os.path.isdir(import_source):
|
||||
for file in os.listdir(import_source):
|
||||
if fnmatch.fnmatch(file, u'*.song'):
|
||||
return True
|
||||
return False
|
||||
|
||||
def doImport(self):
|
||||
"""
|
||||
Receive either a list of files or a folder (unicode) to import.
|
||||
"""
|
||||
from importer import SongFormat
|
||||
PS_string = SongFormat.get(SongFormat.PowerSong, u'name')
|
||||
if isinstance(self.importSource, unicode):
|
||||
if os.path.isdir(self.importSource):
|
||||
dir = self.importSource
|
||||
@ -104,7 +102,7 @@ class PowerSongImport(SongImport):
|
||||
self.logError(unicode(translate('SongsPlugin.PowerSongImport',
|
||||
'No songs to import.')),
|
||||
unicode(translate('SongsPlugin.PowerSongImport',
|
||||
'No %s files found.' % WizardStrings.PS)))
|
||||
'No %s files found.' % PS_string)))
|
||||
return
|
||||
self.importWizard.progressBar.setMaximum(len(self.importSource))
|
||||
for file in self.importSource:
|
||||
@ -124,7 +122,7 @@ class PowerSongImport(SongImport):
|
||||
self.logError(os.path.basename(file), unicode(
|
||||
translate('SongsPlugin.PowerSongImport',
|
||||
'Invalid %s file. Unexpected byte value.'
|
||||
% WizardStrings.PS)))
|
||||
% PS_string)))
|
||||
break
|
||||
else:
|
||||
if label == u'TITLE':
|
||||
@ -142,15 +140,14 @@ class PowerSongImport(SongImport):
|
||||
if not self.title:
|
||||
self.logError(os.path.basename(file), unicode(
|
||||
translate('SongsPlugin.PowerSongImport',
|
||||
'Invalid %s file. Missing "TITLE" header.'
|
||||
% WizardStrings.PS)))
|
||||
'Invalid %s file. Missing "TITLE" header.' % PS_string)))
|
||||
continue
|
||||
# Check that file had COPYRIGHTLINE label
|
||||
if not found_copyright:
|
||||
self.logError(self.title, unicode(
|
||||
translate('SongsPlugin.PowerSongImport',
|
||||
'Invalid %s file. Missing "COPYRIGHTLINE" '
|
||||
'header.' % WizardStrings.PS)))
|
||||
'header.' % PS_string)))
|
||||
continue
|
||||
# Check that file had at least one verse
|
||||
if not self.verses:
|
||||
|
@ -51,11 +51,11 @@ class SongImport(QtCore.QObject):
|
||||
as necessary
|
||||
"""
|
||||
@staticmethod
|
||||
def isValidSource(**kwargs):
|
||||
def isValidSource(import_source):
|
||||
"""
|
||||
Override this method to validate the source prior to import.
|
||||
"""
|
||||
pass
|
||||
return True
|
||||
|
||||
def __init__(self, manager, **kwargs):
|
||||
"""
|
||||
|
142
openlp/plugins/songs/lib/zionworximport.py
Normal file
142
openlp/plugins/songs/lib/zionworximport.py
Normal 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)
|
@ -194,7 +194,7 @@ class SongsPlugin(Plugin):
|
||||
self.manager.save_object(song)
|
||||
|
||||
def importSongs(self, format, **kwargs):
|
||||
class_ = SongFormat.get_class(format)
|
||||
class_ = SongFormat.get(format, u'class')
|
||||
importer = class_(self.manager, **kwargs)
|
||||
importer.register(self.mediaItem.importWizard)
|
||||
return importer
|
||||
|
Loading…
Reference in New Issue
Block a user