diff --git a/openlp/core/lib/eventreceiver.py b/openlp/core/lib/eventreceiver.py index f27171db4..91149d62c 100644 --- a/openlp/core/lib/eventreceiver.py +++ b/openlp/core/lib/eventreceiver.py @@ -268,7 +268,7 @@ class Receiver(object): <> )`` """ - 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__ diff --git a/openlp/core/lib/pluginmanager.py b/openlp/core/lib/pluginmanager.py index 80160911d..084d567a0 100644 --- a/openlp/core/lib/pluginmanager.py +++ b/openlp/core/lib/pluginmanager.py @@ -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) diff --git a/openlp/core/lib/renderer.py b/openlp/core/lib/renderer.py index e782585d0..6ce51ab60 100644 --- a/openlp/core/lib/renderer.py +++ b/openlp/core/lib/renderer.py @@ -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):
""" % \ - (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() diff --git a/openlp/core/lib/serviceitem.py b/openlp/core/lib/serviceitem.py index 0cf5626ff..5ee8e6167 100644 --- a/openlp/core/lib/serviceitem.py +++ b/openlp/core/lib/serviceitem.py @@ -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() diff --git a/openlp/core/lib/ui.py b/openlp/core/lib/ui.py index c0472cce8..9707886a9 100644 --- a/openlp/core/lib/ui.py +++ b/openlp/core/lib/ui.py @@ -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:') diff --git a/openlp/core/ui/advancedtab.py b/openlp/core/ui/advancedtab.py index 1947f741d..0f34efe13 100644 --- a/openlp/core/ui/advancedtab.py +++ b/openlp/core/ui/advancedtab.py @@ -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', + 'WARNING: 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() diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index be30fac40..eb6b6c925 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -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 = {} diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 27fdb7c2e..be58e1cd6 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -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') diff --git a/openlp/core/ui/media/mediacontroller.py b/openlp/core/ui/media/mediacontroller.py index 324721568..7e51cd172 100644 --- a/openlp/core/ui/media/mediacontroller.py +++ b/openlp/core/ui/media/mediacontroller.py @@ -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) diff --git a/openlp/core/ui/media/phononplayer.py b/openlp/core/ui/media/phononplayer.py index c366fe339..9bc739f65 100644 --- a/openlp/core/ui/media/phononplayer.py +++ b/openlp/core/ui/media/phononplayer.py @@ -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()) diff --git a/openlp/core/ui/media/vlc.py b/openlp/core/ui/media/vlc.py index 1f6aa0e6a..71552cb31 100644 --- a/openlp/core/ui/media/vlc.py +++ b/openlp/core/ui/media/vlc.py @@ -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""" diff --git a/openlp/core/ui/media/vlcplayer.py b/openlp/core/ui/media/vlcplayer.py index 3879adb29..2d98fb599 100644 --- a/openlp/core/ui/media/vlcplayer.py +++ b/openlp/core/ui/media/vlcplayer.py @@ -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: diff --git a/openlp/core/ui/media/webkitplayer.py b/openlp/core/ui/media/webkitplayer.py index e3713d7ae..7df376187 100644 --- a/openlp/core/ui/media/webkitplayer.py +++ b/openlp/core/ui/media/webkitplayer.py @@ -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) diff --git a/openlp/core/ui/pluginform.py b/openlp/core/ui/pluginform.py index 435181568..f520bb235 100644 --- a/openlp/core/ui/pluginform.py +++ b/openlp/core/ui/pluginform.py @@ -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') diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 512f1f9bc..71d3d4eb4 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -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): diff --git a/openlp/core/ui/settingsform.py b/openlp/core/ui/settingsform.py index 7dd02826c..d183d9d4a 100644 --- a/openlp/core/ui/settingsform.py +++ b/openlp/core/ui/settingsform.py @@ -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 diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index eaf682f92..2aae81888 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -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)) diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index e38321e1e..6f302c752 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -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): """ diff --git a/openlp/core/ui/themestab.py b/openlp/core/ui/themestab.py index 8d5c5fb47..825a71896 100644 --- a/openlp/core/ui/themestab.py +++ b/openlp/core/ui/themestab.py @@ -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() diff --git a/openlp/core/ui/wizard.py b/openlp/core/ui/wizard.py index 0b4e8ec37..d7fbf4aaa 100644 --- a/openlp/core/ui/wizard.py +++ b/openlp/core/ui/wizard.py @@ -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) diff --git a/openlp/core/utils/__init__.py b/openlp/core/utils/__init__.py index cc6fabe7b..4a1399fb7 100644 --- a/openlp/core/utils/__init__.py +++ b/openlp/core/utils/__init__.py @@ -89,7 +89,7 @@ class AppLocation(object): VersionDir = 5 CacheDir = 6 LanguageDir = 7 - + # Base path where data/config/cache dir is located BaseDir = None diff --git a/openlp/plugins/bibles/lib/csvbible.py b/openlp/plugins/bibles/lib/csvbible.py index cd4b921a1..e61ae9164 100644 --- a/openlp/plugins/bibles/lib/csvbible.py +++ b/openlp/plugins/bibles/lib/csvbible.py @@ -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. """ diff --git a/openlp/plugins/custom/lib/mediaitem.py b/openlp/plugins/custom/lib/mediaitem.py index 7b7284b55..d2002deb5 100644 --- a/openlp/plugins/custom/lib/mediaitem.py +++ b/openlp/plugins/custom/lib/mediaitem.py @@ -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): diff --git a/openlp/plugins/songs/forms/editsongform.py b/openlp/plugins/songs/forms/editsongform.py index 61043f805..226d8baa1 100644 --- a/openlp/plugins/songs/forms/editsongform.py +++ b/openlp/plugins/songs/forms/editsongform.py @@ -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)) diff --git a/openlp/plugins/songs/forms/songimportform.py b/openlp/plugins/songs/forms/songimportform.py index 2603bf964..d9dd6b56c 100644 --- a/openlp/plugins/songs/forms/songimportform.py +++ b/openlp/plugins/songs/forms/songimportform.py @@ -37,7 +37,7 @@ from openlp.core.lib import Receiver, SettingsManager, translate from openlp.core.lib.ui import UiStrings, critical_error_message_box from openlp.core.lib.settings import Settings from openlp.core.ui.wizard import OpenLPWizard, WizardStrings -from openlp.plugins.songs.lib.importer import SongFormat +from openlp.plugins.songs.lib.importer import SongFormat, SongFormatSelect log = logging.getLogger(__name__) @@ -66,122 +66,59 @@ class SongImportForm(OpenLPWizard): """ Set up the song wizard UI. """ + self.formatWidgets = dict([(format, {}) for format in + SongFormat.get_format_list()]) OpenLPWizard.setupUi(self, image) - self.formatStack.setCurrentIndex(0) + self.currentFormat = SongFormat.OpenLyrics + self.formatStack.setCurrentIndex(self.currentFormat) QtCore.QObject.connect(self.formatComboBox, QtCore.SIGNAL(u'currentIndexChanged(int)'), self.onCurrentIndexChanged) def onCurrentIndexChanged(self, index): """ - Called when the format combo box's index changed. We have to check if - the import is available and accordingly to disable or enable the next - button. + Called when the format combo box's index changed. """ + self.currentFormat = index self.formatStack.setCurrentIndex(index) - next_button = self.button(QtGui.QWizard.NextButton) - next_button.setEnabled(SongFormat.get_availability(index)) + self.sourcePage.emit(QtCore.SIGNAL(u'completeChanged()')) def customInit(self): """ Song wizard specific initialisation. """ - if not SongFormat.get_availability(SongFormat.OpenLP1): - self.openLP1DisabledWidget.setVisible(True) - self.openLP1ImportWidget.setVisible(False) - if not SongFormat.get_availability(SongFormat.SongsOfFellowship): - self.songsOfFellowshipDisabledWidget.setVisible(True) - self.songsOfFellowshipImportWidget.setVisible(False) - if not SongFormat.get_availability(SongFormat.Generic): - self.genericDisabledWidget.setVisible(True) - self.genericImportWidget.setVisible(False) + for format in SongFormat.get_format_list(): + if not SongFormat.get(format, u'availability'): + self.formatWidgets[format][u'disabledWidget'].setVisible(True) + self.formatWidgets[format][u'importWidget'].setVisible(False) def customSignals(self): """ Song wizard specific signals. """ - QtCore.QObject.connect(self.openLP2BrowseButton, - QtCore.SIGNAL(u'clicked()'), - self.onOpenLP2BrowseButtonClicked) - QtCore.QObject.connect(self.openLP1BrowseButton, - QtCore.SIGNAL(u'clicked()'), - self.onOpenLP1BrowseButtonClicked) - QtCore.QObject.connect(self.powerSongBrowseButton, - QtCore.SIGNAL(u'clicked()'), - self.onPowerSongBrowseButtonClicked) - QtCore.QObject.connect(self.openLyricsAddButton, - QtCore.SIGNAL(u'clicked()'), - self.onOpenLyricsAddButtonClicked) - QtCore.QObject.connect(self.openLyricsRemoveButton, - QtCore.SIGNAL(u'clicked()'), - self.onOpenLyricsRemoveButtonClicked) - QtCore.QObject.connect(self.openSongAddButton, - QtCore.SIGNAL(u'clicked()'), - self.onOpenSongAddButtonClicked) - QtCore.QObject.connect(self.openSongRemoveButton, - QtCore.SIGNAL(u'clicked()'), - self.onOpenSongRemoveButtonClicked) - QtCore.QObject.connect(self.wordsOfWorshipAddButton, - QtCore.SIGNAL(u'clicked()'), - self.onWordsOfWorshipAddButtonClicked) - QtCore.QObject.connect(self.wordsOfWorshipRemoveButton, - QtCore.SIGNAL(u'clicked()'), - self.onWordsOfWorshipRemoveButtonClicked) - QtCore.QObject.connect(self.ccliAddButton, - QtCore.SIGNAL(u'clicked()'), - self.onCCLIAddButtonClicked) - QtCore.QObject.connect(self.ccliRemoveButton, - QtCore.SIGNAL(u'clicked()'), - self.onCCLIRemoveButtonClicked) - QtCore.QObject.connect(self.dreamBeamAddButton, - QtCore.SIGNAL(u'clicked()'), - self.onDreamBeamAddButtonClicked) - QtCore.QObject.connect(self.dreamBeamRemoveButton, - QtCore.SIGNAL(u'clicked()'), - self.onDreamBeamRemoveButtonClicked) - QtCore.QObject.connect(self.songsOfFellowshipAddButton, - QtCore.SIGNAL(u'clicked()'), - self.onSongsOfFellowshipAddButtonClicked) - QtCore.QObject.connect(self.songsOfFellowshipRemoveButton, - QtCore.SIGNAL(u'clicked()'), - self.onSongsOfFellowshipRemoveButtonClicked) - QtCore.QObject.connect(self.genericAddButton, - QtCore.SIGNAL(u'clicked()'), - self.onGenericAddButtonClicked) - QtCore.QObject.connect(self.genericRemoveButton, - QtCore.SIGNAL(u'clicked()'), - self.onGenericRemoveButtonClicked) - QtCore.QObject.connect(self.easySlidesBrowseButton, - QtCore.SIGNAL(u'clicked()'), - self.onEasySlidesBrowseButtonClicked) - QtCore.QObject.connect(self.ewBrowseButton, - QtCore.SIGNAL(u'clicked()'), - self.onEWBrowseButtonClicked) - QtCore.QObject.connect(self.songBeamerAddButton, - QtCore.SIGNAL(u'clicked()'), - self.onSongBeamerAddButtonClicked) - QtCore.QObject.connect(self.songBeamerRemoveButton, - QtCore.SIGNAL(u'clicked()'), - self.onSongBeamerRemoveButtonClicked) - QtCore.QObject.connect(self.songShowPlusAddButton, - QtCore.SIGNAL(u'clicked()'), - self.onSongShowPlusAddButtonClicked) - QtCore.QObject.connect(self.songShowPlusRemoveButton, - QtCore.SIGNAL(u'clicked()'), - self.onSongShowPlusRemoveButtonClicked) - QtCore.QObject.connect(self.foilPresenterAddButton, - QtCore.SIGNAL(u'clicked()'), - self.onFoilPresenterAddButtonClicked) - QtCore.QObject.connect(self.foilPresenterRemoveButton, - QtCore.SIGNAL(u'clicked()'), - self.onFoilPresenterRemoveButtonClicked) + for format in SongFormat.get_format_list(): + select_mode = SongFormat.get(format, u'selectMode') + if select_mode == SongFormatSelect.MultipleFiles: + QtCore.QObject.connect(self.formatWidgets[format][u'addButton'], + QtCore.SIGNAL(u'clicked()'), self.onAddButtonClicked) + QtCore.QObject.connect( + self.formatWidgets[format][u'removeButton'], + QtCore.SIGNAL(u'clicked()'), self.onRemoveButtonClicked) + else: + QtCore.QObject.connect( + self.formatWidgets[format][u'browseButton'], + QtCore.SIGNAL(u'clicked()'), self.onBrowseButtonClicked) + QtCore.QObject.connect( + self.formatWidgets[format][u'filepathEdit'], + QtCore.SIGNAL(u'textChanged (const QString&)'), + self.onFilepathEditTextChanged) def addCustomPages(self): """ Add song wizard specific pages. """ # Source Page - self.sourcePage = QtGui.QWizardPage() + self.sourcePage = SongImportSourcePage() self.sourcePage.setObjectName(u'SourcePage') self.sourceLayout = QtGui.QVBoxLayout(self.sourcePage) self.sourceLayout.setObjectName(u'SourceLayout') @@ -197,42 +134,16 @@ class SongImportForm(OpenLPWizard): self.formatLayout.setItem(1, QtGui.QFormLayout.LabelRole, self.formatSpacer) self.sourceLayout.addLayout(self.formatLayout) + self.formatHSpacing = self.formatLayout.horizontalSpacing() + self.formatVSpacing = self.formatLayout.verticalSpacing() + self.formatLayout.setVerticalSpacing(0) self.stackSpacer = QtGui.QSpacerItem(10, 0, QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Expanding) self.formatStack = QtGui.QStackedLayout() self.formatStack.setObjectName(u'FormatStack') - # OpenLyrics - self.addFileSelectItem(u'openLyrics', u'OpenLyrics', True) - # OpenLP 2.0 - self.addFileSelectItem(u'openLP2', single_select=True) - # openlp.org 1.x - self.addFileSelectItem(u'openLP1', None, True, True) - # Generic Document/Presentation import - self.addFileSelectItem(u'generic', None, True) - # CCLI File import - self.addFileSelectItem(u'ccli') - # DreamBeam - self.addFileSelectItem(u'dreamBeam') - # EasySlides - self.addFileSelectItem(u'easySlides', single_select=True) - # EasyWorship - self.addFileSelectItem(u'ew', single_select=True) - # Foilpresenter - self.addFileSelectItem(u'foilPresenter') - # Open Song - self.addFileSelectItem(u'openSong', u'OpenSong') - # PowerSong - self.addFileSelectItem(u'powerSong', single_select=True) - # SongBeamer - self.addFileSelectItem(u'songBeamer') - # Song Show Plus - self.addFileSelectItem(u'songShowPlus') - # Songs of Fellowship - self.addFileSelectItem(u'songsOfFellowship', None, True) - # Words of Worship - self.addFileSelectItem(u'wordsOfWorship') -# Commented out for future use. -# self.addFileSelectItem(u'csv', u'CSV', single_select=True) + self.disablableFormats = [] + for self.currentFormat in SongFormat.get_format_list(): + self.addFileSelectItem() self.sourceLayout.addLayout(self.formatStack) self.addPage(self.sourcePage) @@ -246,113 +157,38 @@ class SongImportForm(OpenLPWizard): translate('OpenLP.Ui', 'Welcome to the Song Import Wizard')) self.informationLabel.setText( translate('SongsPlugin.ImportWizardForm', - 'This wizard will help you to import songs from a variety of ' - 'formats. Click the next button below to start the process by ' - 'selecting a format to import from.')) + 'This wizard will help you to import songs from a variety of ' + 'formats. Click the next button below to start the process by ' + 'selecting a format to import from.')) self.sourcePage.setTitle(WizardStrings.ImportSelect) self.sourcePage.setSubTitle(WizardStrings.ImportSelectLong) self.formatLabel.setText(WizardStrings.FormatLabel) - self.formatComboBox.setItemText(SongFormat.OpenLyrics, - translate('SongsPlugin.ImportWizardForm', - 'OpenLyrics or OpenLP 2.0 Exported Song')) - self.formatComboBox.setItemText(SongFormat.OpenLP2, UiStrings().OLPV2) - self.formatComboBox.setItemText(SongFormat.OpenLP1, UiStrings().OLPV1) - self.formatComboBox.setItemText(SongFormat.Generic, - translate('SongsPlugin.ImportWizardForm', - 'Generic Document/Presentation')) - self.formatComboBox.setItemText(SongFormat.CCLI, WizardStrings.CCLI) - self.formatComboBox.setItemText( - SongFormat.DreamBeam, WizardStrings.DB) - self.formatComboBox.setItemText( - SongFormat.EasySlides, WizardStrings.ES) - self.formatComboBox.setItemText( - SongFormat.EasyWorship, WizardStrings.EW) - self.formatComboBox.setItemText( - SongFormat.FoilPresenter, WizardStrings.FP) - self.formatComboBox.setItemText(SongFormat.OpenSong, WizardStrings.OS) - self.formatComboBox.setItemText( - SongFormat.PowerSong, WizardStrings.PS) - self.formatComboBox.setItemText( - SongFormat.SongBeamer, WizardStrings.SB) - self.formatComboBox.setItemText( - SongFormat.SongShowPlus, WizardStrings.SSP) - self.formatComboBox.setItemText( - SongFormat.SongsOfFellowship, WizardStrings.SoF) - self.formatComboBox.setItemText( - SongFormat.WordsOfWorship, WizardStrings.WoW) -# self.formatComboBox.setItemText(SongFormat.CSV, WizardStrings.CSV) - self.openLP2FilenameLabel.setText( - translate('SongsPlugin.ImportWizardForm', 'Filename:')) - self.openLP2BrowseButton.setText(UiStrings().Browse) - self.openLP1FilenameLabel.setText( - translate('SongsPlugin.ImportWizardForm', 'Filename:')) - self.openLP1BrowseButton.setText(UiStrings().Browse) - self.openLP1DisabledLabel.setText(WizardStrings.NoSqlite) - self.powerSongFilenameLabel.setText( - translate('SongsPlugin.ImportWizardForm', 'Folder:')) - self.powerSongBrowseButton.setText(UiStrings().Browse) - self.openLyricsAddButton.setText( - translate('SongsPlugin.ImportWizardForm', 'Add Files...')) - self.openLyricsRemoveButton.setText( - translate('SongsPlugin.ImportWizardForm', 'Remove File(s)')) - self.openLyricsDisabledLabel.setText( - translate('SongsPlugin.ImportWizardForm', 'The OpenLyrics ' - 'importer has not yet been developed, but as you can see, we are ' - 'still intending to do so. Hopefully it will be in the next ' - 'release.')) - self.openSongAddButton.setText( - translate('SongsPlugin.ImportWizardForm', 'Add Files...')) - self.openSongRemoveButton.setText( - translate('SongsPlugin.ImportWizardForm', 'Remove File(s)')) - self.wordsOfWorshipAddButton.setText( - translate('SongsPlugin.ImportWizardForm', 'Add Files...')) - self.wordsOfWorshipRemoveButton.setText( - translate('SongsPlugin.ImportWizardForm', 'Remove File(s)')) - self.ccliAddButton.setText( - translate('SongsPlugin.ImportWizardForm', 'Add Files...')) - self.ccliRemoveButton.setText( - translate('SongsPlugin.ImportWizardForm', 'Remove File(s)')) - self.dreamBeamAddButton.setText( - translate('SongsPlugin.ImportWizardForm', 'Add Files...')) - self.dreamBeamRemoveButton.setText( - translate('SongsPlugin.ImportWizardForm', 'Remove File(s)')) - self.songsOfFellowshipAddButton.setText( - translate('SongsPlugin.ImportWizardForm', 'Add Files...')) - self.songsOfFellowshipRemoveButton.setText( - translate('SongsPlugin.ImportWizardForm', 'Remove File(s)')) - self.songsOfFellowshipDisabledLabel.setText( - translate('SongsPlugin.ImportWizardForm', 'The Songs of ' - 'Fellowship importer has been disabled because OpenLP cannot ' - 'access OpenOffice or LibreOffice.')) - self.genericAddButton.setText( - translate('SongsPlugin.ImportWizardForm', 'Add Files...')) - self.genericRemoveButton.setText( - translate('SongsPlugin.ImportWizardForm', 'Remove File(s)')) - self.genericDisabledLabel.setText( - translate('SongsPlugin.ImportWizardForm', 'The generic document/' - 'presentation importer has been disabled because OpenLP cannot ' - 'access OpenOffice or LibreOffice.')) - self.easySlidesFilenameLabel.setText( - translate('SongsPlugin.ImportWizardForm', 'Filename:')) - self.easySlidesBrowseButton.setText(UiStrings().Browse) - self.ewFilenameLabel.setText( - translate('SongsPlugin.ImportWizardForm', 'Filename:')) - self.ewBrowseButton.setText(UiStrings().Browse) - self.songBeamerAddButton.setText( - translate('SongsPlugin.ImportWizardForm', 'Add Files...')) - self.songBeamerRemoveButton.setText( - translate('SongsPlugin.ImportWizardForm', 'Remove File(s)')) - self.songShowPlusAddButton.setText( - translate('SongsPlugin.ImportWizardForm', 'Add Files...')) - self.songShowPlusRemoveButton.setText( - translate('SongsPlugin.ImportWizardForm', 'Remove File(s)')) - self.foilPresenterAddButton.setText( - translate('SongsPlugin.ImportWizardForm', 'Add Files...')) - self.foilPresenterRemoveButton.setText( - translate('SongsPlugin.ImportWizardForm', 'Remove File(s)')) -# self.csvFilenameLabel.setText( -# translate('SongsPlugin.ImportWizardForm', 'Filename:')) -# self.csvBrowseButton.setText(UiStrings().Browse) + for format in SongFormat.get_format_list(): + format_name, custom_combo_text, description_text, select_mode = \ + SongFormat.get(format, u'name', u'comboBoxText', + u'descriptionText', u'selectMode') + combo_box_text = (custom_combo_text if custom_combo_text else + format_name) + self.formatComboBox.setItemText(format, combo_box_text) + if description_text is not None: + self.formatWidgets[format][u'descriptionLabel'].setText( + description_text) + if select_mode == SongFormatSelect.MultipleFiles: + self.formatWidgets[format][u'addButton'].setText( + translate('SongsPlugin.ImportWizardForm', 'Add Files...')) + self.formatWidgets[format][u'removeButton'].setText( + translate('SongsPlugin.ImportWizardForm', 'Remove File(s)')) + else: + self.formatWidgets[format][u'browseButton'].setText( + UiStrings().Browse) + f_label = 'Filename:' + if select_mode == SongFormatSelect.SingleFolder: + f_label = 'Folder:' + self.formatWidgets[format][u'filepathLabel'].setText( + translate('SongsPlugin.ImportWizardForm', f_label)) + for format in self.disablableFormats: + self.formatWidgets[format][u'disabledLabel'].setText( + SongFormat.get(format, u'disabledLabelText')) self.progressPage.setTitle(WizardStrings.Importing) self.progressPage.setSubTitle( translate('SongsPlugin.ImportWizardForm', @@ -364,14 +200,29 @@ class SongImportForm(OpenLPWizard): self.errorSaveToButton.setText(translate('SongsPlugin.ImportWizardForm', 'Save to File')) # Align all QFormLayouts towards each other. - width = max(self.formatLabel.minimumSizeHint().width(), - self.openLP2FilenameLabel.minimumSizeHint().width()) - self.formatSpacer.changeSize(width, 0, QtGui.QSizePolicy.Fixed, - QtGui.QSizePolicy.Fixed) + formats = filter(lambda f: u'filepathLabel' in self.formatWidgets[f], + SongFormat.get_format_list()) + labels = [self.formatWidgets[f][u'filepathLabel'] for f in formats] + # Get max width of all labels + max_label_width = max(self.formatLabel.minimumSizeHint().width(), + max([label.minimumSizeHint().width() for label in labels])) + self.formatSpacer.changeSize(max_label_width, 0, + QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) + spacers = [self.formatWidgets[f][u'filepathSpacer'] for f in formats] + for index, spacer in enumerate(spacers): + spacer.changeSize( + max_label_width - labels[index].minimumSizeHint().width(), 0, + QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) + # Align descriptionLabels with rest of layout + for format in SongFormat.get_format_list(): + if SongFormat.get(format, u'descriptionText') is not None: + self.formatWidgets[format][u'descriptionSpacer'].changeSize( + max_label_width + self.formatHSpacing, 0, + QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) def customPageChanged(self, pageId): """ - Called when changing to a page other than the progress page + Called when changing to a page other than the progress page. """ if self.page(pageId) == self.sourcePage: self.onCurrentIndexChanged(self.formatStack.currentIndex()) @@ -379,108 +230,32 @@ class SongImportForm(OpenLPWizard): def validateCurrentPage(self): """ Validate the current page before moving on to the next page. + Provide each song format class with a chance to validate its input by + overriding isValidSource(). """ if self.currentPage() == self.welcomePage: return True elif self.currentPage() == self.sourcePage: - source_format = self.formatComboBox.currentIndex() + format = self.currentFormat Settings().setValue(u'songs/last import type', - source_format) - import_class = SongFormat.get_class(source_format) - if source_format == SongFormat.OpenLP2: - if self.openLP2FilenameEdit.text().isEmpty(): - critical_error_message_box(UiStrings().NFSs, - WizardStrings.YouSpecifyFile % UiStrings().OLPV2) - self.openLP2BrowseButton.setFocus() - return False - elif source_format == SongFormat.OpenLP1: - if self.openLP1FilenameEdit.text().isEmpty(): - critical_error_message_box(UiStrings().NFSs, - WizardStrings.YouSpecifyFile % UiStrings().OLPV1) - self.openLP1BrowseButton.setFocus() - return False - elif source_format == SongFormat.PowerSong: - if self.powerSongFilenameEdit.text().isEmpty() or \ - not import_class.isValidSource( - folder=self.powerSongFilenameEdit.text()): - critical_error_message_box(UiStrings().NFdSs, - WizardStrings.YouSpecifyFolder % WizardStrings.PS) - self.powerSongBrowseButton.setFocus() - return False - elif source_format == SongFormat.OpenLyrics: - if self.openLyricsFileListWidget.count() == 0: - critical_error_message_box(UiStrings().NFSp, - WizardStrings.YouSpecifyFile % WizardStrings.OL) - self.openLyricsAddButton.setFocus() - return False - elif source_format == SongFormat.OpenSong: - if self.openSongFileListWidget.count() == 0: - critical_error_message_box(UiStrings().NFSp, - WizardStrings.YouSpecifyFile % WizardStrings.OS) - self.openSongAddButton.setFocus() - return False - elif source_format == SongFormat.WordsOfWorship: - if self.wordsOfWorshipFileListWidget.count() == 0: - critical_error_message_box(UiStrings().NFSp, - WizardStrings.YouSpecifyFile % WizardStrings.WoW) - self.wordsOfWorshipAddButton.setFocus() - return False - elif source_format == SongFormat.CCLI: - if self.ccliFileListWidget.count() == 0: - critical_error_message_box(UiStrings().NFSp, - WizardStrings.YouSpecifyFile % WizardStrings.CCLI) - self.ccliAddButton.setFocus() - return False - elif source_format == SongFormat.DreamBeam: - if self.dreamBeamFileListWidget.count() == 0: - critical_error_message_box(UiStrings().NFSp, - WizardStrings.YouSpecifyFile % WizardStrings.DB) - self.dreamBeamAddButton.setFocus() - return False - elif source_format == SongFormat.SongsOfFellowship: - if self.songsOfFellowshipFileListWidget.count() == 0: - critical_error_message_box(UiStrings().NFSp, - WizardStrings.YouSpecifyFile % WizardStrings.SoF) - self.songsOfFellowshipAddButton.setFocus() - return False - elif source_format == SongFormat.Generic: - if self.genericFileListWidget.count() == 0: - critical_error_message_box(UiStrings().NFSp, - translate('SongsPlugin.ImportWizardForm', - 'You need to specify at least one document or ' - 'presentation file to import from.')) - self.genericAddButton.setFocus() - return False - elif source_format == SongFormat.EasySlides: - if self.easySlidesFilenameEdit.text().isEmpty(): - critical_error_message_box(UiStrings().NFSp, - WizardStrings.YouSpecifyFile % WizardStrings.ES) - self.easySlidesBrowseButton.setFocus() - return False - elif source_format == SongFormat.EasyWorship: - if self.ewFilenameEdit.text().isEmpty(): - critical_error_message_box(UiStrings().NFSs, - WizardStrings.YouSpecifyFile % WizardStrings.EW) - self.ewBrowseButton.setFocus() - return False - elif source_format == SongFormat.SongBeamer: - if self.songBeamerFileListWidget.count() == 0: - critical_error_message_box(UiStrings().NFSp, - WizardStrings.YouSpecifyFile % WizardStrings.SB) - self.songBeamerAddButton.setFocus() - return False - elif source_format == SongFormat.SongShowPlus: - if self.songShowPlusFileListWidget.count() == 0: - critical_error_message_box(UiStrings().NFSp, - WizardStrings.YouSpecifyFile % WizardStrings.SSP) - self.songShowPlusAddButton.setFocus() - return False - elif source_format == SongFormat.FoilPresenter: - if self.foilPresenterFileListWidget.count() == 0: - critical_error_message_box(UiStrings().NFSp, - WizardStrings.YouSpecifyFile % WizardStrings.FP) - self.foilPresenterAddButton.setFocus() - return False + format) + select_mode, class_, error_msg = SongFormat.get(format, + u'selectMode', u'class', u'invalidSourceMsg') + if select_mode == SongFormatSelect.MultipleFiles: + import_source = self.getListOfFiles( + self.formatWidgets[format][u'fileListWidget']) + error_title = UiStrings().IFSp + focus_button = self.formatWidgets[format][u'addButton'] + else: + import_source = \ + self.formatWidgets[format][u'filepathEdit'].text() + error_title = (UiStrings().IFSs if select_mode == + SongFormatSelect.SingleFile else UiStrings().IFdSs) + focus_button = self.formatWidgets[format][u'browseButton'] + if not class_.isValidSource(import_source): + critical_error_message_box(error_title, error_msg) + focus_button.setFocus() + return False return True elif self.currentPage() == self.progressPage: return True @@ -526,203 +301,40 @@ class SongImportForm(OpenLPWizard): item = listbox.takeItem(listbox.row(item)) del item - def onOpenLP2BrowseButtonClicked(self): - """ - Get OpenLP v2 song database file - """ - self.getFileName(WizardStrings.OpenTypeFile % UiStrings().OLPV2, - self.openLP2FilenameEdit, u'%s (*.sqlite)' - % (translate('SongsPlugin.ImportWizardForm', - 'OpenLP 2.0 Databases')) - ) + def onBrowseButtonClicked(self): + format = self.currentFormat + select_mode, format_name, filter = SongFormat.get(format, u'selectMode', + u'name', u'filter') + filepathEdit = self.formatWidgets[format][u'filepathEdit'] + if select_mode == SongFormatSelect.SingleFile: + self.getFileName(WizardStrings.OpenTypeFile % format_name, + filepathEdit, filter) + elif select_mode == SongFormatSelect.SingleFolder: + self.getFolder(WizardStrings.OpenTypeFolder % format_name, + filepathEdit) - def onOpenLP1BrowseButtonClicked(self): - """ - Get OpenLP v1 song database file - """ - self.getFileName(WizardStrings.OpenTypeFile % UiStrings().OLPV1, - self.openLP1FilenameEdit, u'%s (*.olp)' - % translate('SongsPlugin.ImportWizardForm', - 'openlp.org v1.x Databases') - ) + def onAddButtonClicked(self): + format = self.currentFormat + select_mode, format_name, filter, custom_title = SongFormat.get(format, + u'selectMode', u'name', u'filter', + u'getFilesTitle') + title = custom_title if custom_title \ + else WizardStrings.OpenTypeFile % format_name + if select_mode == SongFormatSelect.MultipleFiles: + self.getFiles(title, self.formatWidgets[format][u'fileListWidget'], + filter) + self.sourcePage.emit(QtCore.SIGNAL(u'completeChanged()')) - def onPowerSongBrowseButtonClicked(self): - """ - Get PowerSong song database folder - """ - self.getFolder(WizardStrings.OpenTypeFolder % WizardStrings.PS, - self.powerSongFilenameEdit) + def onRemoveButtonClicked(self): + self.removeSelectedItems( + self.formatWidgets[self.currentFormat][u'fileListWidget']) + self.sourcePage.emit(QtCore.SIGNAL(u'completeChanged()')) - def onOpenLyricsAddButtonClicked(self): + def onFilepathEditTextChanged(self): """ - Get OpenLyrics song database files + Called when the content of the Filename/Folder edit box changes. """ - self.getFiles(WizardStrings.OpenTypeFile % WizardStrings.OL, - self.openLyricsFileListWidget, u'%s (*.xml)' % - translate('SongsPlugin.ImportWizardForm', 'OpenLyrics Files')) - - def onOpenLyricsRemoveButtonClicked(self): - """ - Remove selected OpenLyrics files from the import list - """ - self.removeSelectedItems(self.openLyricsFileListWidget) - - def onOpenSongAddButtonClicked(self): - """ - Get OpenSong song database files - """ - self.getFiles(WizardStrings.OpenTypeFile % WizardStrings.OS, - self.openSongFileListWidget) - - def onOpenSongRemoveButtonClicked(self): - """ - Remove selected OpenSong files from the import list - """ - self.removeSelectedItems(self.openSongFileListWidget) - - def onWordsOfWorshipAddButtonClicked(self): - """ - Get Words of Worship song database files - """ - self.getFiles(WizardStrings.OpenTypeFile % WizardStrings.WoW, - self.wordsOfWorshipFileListWidget, u'%s (*.wsg *.wow-song)' - % translate('SongsPlugin.ImportWizardForm', - 'Words Of Worship Song Files') - ) - - def onWordsOfWorshipRemoveButtonClicked(self): - """ - Remove selected Words of Worship files from the import list - """ - self.removeSelectedItems(self.wordsOfWorshipFileListWidget) - - def onCCLIAddButtonClicked(self): - """ - Get CCLI song database files - """ - self.getFiles(WizardStrings.OpenTypeFile % WizardStrings.CCLI, - self.ccliFileListWidget, u'%s (*.usr *.txt)' - % translate('SongsPlugin.ImportWizardForm', - 'CCLI SongSelect Files')) - - def onCCLIRemoveButtonClicked(self): - """ - Remove selected CCLI files from the import list - """ - self.removeSelectedItems(self.ccliFileListWidget) - - def onDreamBeamAddButtonClicked(self): - """ - Get DreamBeam song database files - """ - self.getFiles(WizardStrings.OpenTypeFile % WizardStrings.DB, - self.dreamBeamFileListWidget, u'%s (*.xml)' - % translate('SongsPlugin.ImportWizardForm', - 'DreamBeam Song Files') - ) - - def onDreamBeamRemoveButtonClicked(self): - """ - Remove selected DreamBeam files from the import list - """ - self.removeSelectedItems(self.dreamBeamFileListWidget) - - def onSongsOfFellowshipAddButtonClicked(self): - """ - Get Songs of Fellowship song database files - """ - self.getFiles(WizardStrings.OpenTypeFile % WizardStrings.SoF, - self.songsOfFellowshipFileListWidget, u'%s (*.rtf)' - % translate('SongsPlugin.ImportWizardForm', - 'Songs Of Fellowship Song Files') - ) - - def onSongsOfFellowshipRemoveButtonClicked(self): - """ - Remove selected Songs of Fellowship files from the import list - """ - self.removeSelectedItems(self.songsOfFellowshipFileListWidget) - - def onGenericAddButtonClicked(self): - """ - Get song database files - """ - self.getFiles( - translate('SongsPlugin.ImportWizardForm', - 'Select Document/Presentation Files'), - self.genericFileListWidget - ) - - def onGenericRemoveButtonClicked(self): - """ - Remove selected files from the import list - """ - self.removeSelectedItems(self.genericFileListWidget) - - def onEasySlidesBrowseButtonClicked(self): - """ - Get EasySlides song database file - """ - self.getFileName(WizardStrings.OpenTypeFile % WizardStrings.ES, - self.easySlidesFilenameEdit, u'%s (*.xml)' - % translate('SongsPlugin.ImportWizardForm', - 'EasySlides XML File')) - - def onEWBrowseButtonClicked(self): - """ - Get EasyWorship song database files - """ - self.getFileName(WizardStrings.OpenTypeFile % WizardStrings.EW, - self.ewFilenameEdit, u'%s (*.db)' - % translate('SongsPlugin.ImportWizardForm', - 'EasyWorship Song Database')) - - def onSongBeamerAddButtonClicked(self): - """ - Get SongBeamer song database files - """ - self.getFiles(WizardStrings.OpenTypeFile % WizardStrings.SB, - self.songBeamerFileListWidget, u'%s (*.sng)' % - translate('SongsPlugin.ImportWizardForm', 'SongBeamer Files') - ) - - def onSongBeamerRemoveButtonClicked(self): - """ - Remove selected SongBeamer files from the import list - """ - self.removeSelectedItems(self.songBeamerFileListWidget) - - def onSongShowPlusAddButtonClicked(self): - """ - Get SongShow Plus song database files - """ - self.getFiles(WizardStrings.OpenTypeFile % WizardStrings.SSP, - self.songShowPlusFileListWidget, u'%s (*.sbsong)' - % translate('SongsPlugin.ImportWizardForm', - 'SongShow Plus Song Files') - ) - - def onSongShowPlusRemoveButtonClicked(self): - """ - Remove selected SongShow Plus files from the import list - """ - self.removeSelectedItems(self.songShowPlusFileListWidget) - - def onFoilPresenterAddButtonClicked(self): - """ - Get FoilPresenter song database files - """ - self.getFiles(WizardStrings.OpenTypeFile % WizardStrings.FP, - self.foilPresenterFileListWidget, u'%s (*.foil)' - % translate('SongsPlugin.ImportWizardForm', - 'Foilpresenter Song Files') - ) - - def onFoilPresenterRemoveButtonClicked(self): - """ - Remove selected FoilPresenter files from the import list - """ - self.removeSelectedItems(self.foilPresenterFileListWidget) + self.sourcePage.emit(QtCore.SIGNAL(u'completeChanged()')) def setDefaults(self): """ @@ -737,22 +349,12 @@ class SongImportForm(OpenLPWizard): last_import_type >= self.formatComboBox.count(): last_import_type = 0 self.formatComboBox.setCurrentIndex(last_import_type) - self.openLP2FilenameEdit.setText(u'') - self.openLP1FilenameEdit.setText(u'') - self.powerSongFilenameEdit.setText(u'') - self.openLyricsFileListWidget.clear() - self.openSongFileListWidget.clear() - self.wordsOfWorshipFileListWidget.clear() - self.ccliFileListWidget.clear() - self.dreamBeamFileListWidget.clear() - self.songsOfFellowshipFileListWidget.clear() - self.genericFileListWidget.clear() - self.easySlidesFilenameEdit.setText(u'') - self.ewFilenameEdit.setText(u'') - self.songBeamerFileListWidget.clear() - self.songShowPlusFileListWidget.clear() - self.foilPresenterFileListWidget.clear() - #self.csvFilenameEdit.setText(u'') + for format in SongFormat.get_format_list(): + select_mode = SongFormat.get(format, u'selectMode') + if select_mode == SongFormatSelect.MultipleFiles: + self.formatWidgets[format][u'fileListWidget'].clear() + else: + self.formatWidgets[format][u'filepathEdit'].setText(u'') self.errorReportTextEdit.clear() self.errorReportTextEdit.setHidden(True) self.errorCopyToButton.setHidden(True) @@ -772,87 +374,18 @@ class SongImportForm(OpenLPWizard): class, and then runs the ``doImport`` method of the importer to do the actual importing. """ - source_format = self.formatComboBox.currentIndex() - importer = None - if source_format == SongFormat.OpenLP2: - # Import an OpenLP 2.0 database - importer = self.plugin.importSongs(SongFormat.OpenLP2, - filename=unicode(self.openLP2FilenameEdit.text()) - ) - elif source_format == SongFormat.OpenLP1: - # Import an openlp.org database - importer = self.plugin.importSongs(SongFormat.OpenLP1, - filename=unicode(self.openLP1FilenameEdit.text()), - plugin=self.plugin - ) - elif source_format == SongFormat.PowerSong: - # Import PowerSong folder - importer = self.plugin.importSongs(SongFormat.PowerSong, - folder=unicode(self.powerSongFilenameEdit.text()) - ) - elif source_format == SongFormat.OpenLyrics: - # Import OpenLyrics songs - importer = self.plugin.importSongs(SongFormat.OpenLyrics, - filenames=self.getListOfFiles(self.openLyricsFileListWidget) - ) - elif source_format == SongFormat.OpenSong: - # Import OpenSong songs - importer = self.plugin.importSongs(SongFormat.OpenSong, - filenames=self.getListOfFiles(self.openSongFileListWidget) - ) - elif source_format == SongFormat.WordsOfWorship: - # Import Words Of Worship songs - importer = self.plugin.importSongs(SongFormat.WordsOfWorship, + source_format = self.currentFormat + select_mode = SongFormat.get(source_format, u'selectMode') + if select_mode == SongFormatSelect.SingleFile: + importer = self.plugin.importSongs(source_format, filename=unicode( + self.formatWidgets[source_format][u'filepathEdit'].text())) + elif select_mode == SongFormatSelect.SingleFolder: + importer = self.plugin.importSongs(source_format, folder=unicode( + self.formatWidgets[source_format][u'filepathEdit'].text())) + else: + importer = self.plugin.importSongs(source_format, filenames=self.getListOfFiles( - self.wordsOfWorshipFileListWidget) - ) - elif source_format == SongFormat.CCLI: - # Import Words Of Worship songs - importer = self.plugin.importSongs(SongFormat.CCLI, - filenames=self.getListOfFiles(self.ccliFileListWidget) - ) - elif source_format == SongFormat.DreamBeam: - # Import DreamBeam songs - importer = self.plugin.importSongs(SongFormat.DreamBeam, - filenames=self.getListOfFiles( - self.dreamBeamFileListWidget) - ) - elif source_format == SongFormat.SongsOfFellowship: - # Import a Songs of Fellowship RTF file - importer = self.plugin.importSongs(SongFormat.SongsOfFellowship, - filenames=self.getListOfFiles( - self.songsOfFellowshipFileListWidget) - ) - elif source_format == SongFormat.Generic: - # Import a generic document or presentation - importer = self.plugin.importSongs(SongFormat.Generic, - filenames=self.getListOfFiles(self.genericFileListWidget) - ) - elif source_format == SongFormat.EasySlides: - # Import an EasySlides export file - importer = self.plugin.importSongs(SongFormat.EasySlides, - filename=unicode(self.easySlidesFilenameEdit.text()) - ) - elif source_format == SongFormat.EasyWorship: - # Import an EasyWorship database - importer = self.plugin.importSongs(SongFormat.EasyWorship, - filename=unicode(self.ewFilenameEdit.text()) - ) - elif source_format == SongFormat.SongBeamer: - # Import SongBeamer songs - importer = self.plugin.importSongs(SongFormat.SongBeamer, - filenames=self.getListOfFiles(self.songBeamerFileListWidget) - ) - elif source_format == SongFormat.SongShowPlus: - # Import ShongShow Plus songs - importer = self.plugin.importSongs(SongFormat.SongShowPlus, - filenames=self.getListOfFiles(self.songShowPlusFileListWidget) - ) - elif source_format == SongFormat.FoilPresenter: - # Import Foilpresenter songs - importer = self.plugin.importSongs(SongFormat.FoilPresenter, - filenames=self.getListOfFiles(self.foilPresenterFileListWidget) - ) + self.formatWidgets[source_format][u'fileListWidget'])) importer.doImport() self.progressLabel.setText(WizardStrings.FinishedImport) @@ -874,90 +407,144 @@ class SongImportForm(OpenLPWizard): report_file.write(self.errorReportTextEdit.toPlainText()) report_file.close() - def addFileSelectItem(self, prefix, obj_prefix=None, can_disable=False, - single_select=False): - if not obj_prefix: - obj_prefix = prefix + def addFileSelectItem(self): + format = self.currentFormat + prefix, can_disable, description_text, select_mode = SongFormat.get( + format, u'prefix', u'canDisable', u'descriptionText', u'selectMode') page = QtGui.QWidget() - page.setObjectName(obj_prefix + u'Page') + page.setObjectName(prefix + u'Page') if can_disable: - importWidget = self.disablableWidget(page, prefix, obj_prefix) + importWidget = self.disablableWidget(page, prefix) else: importWidget = page importLayout = QtGui.QVBoxLayout(importWidget) importLayout.setMargin(0) - importLayout.setObjectName(obj_prefix + u'ImportLayout') - if single_select: - fileLayout = QtGui.QHBoxLayout() - fileLayout.setObjectName(obj_prefix + u'FileLayout') - filenameLabel = QtGui.QLabel(importWidget) - filenameLabel.setObjectName(obj_prefix + u'FilenameLabel') - fileLayout.addWidget(filenameLabel) - filenameEdit = QtGui.QLineEdit(importWidget) - filenameEdit.setObjectName(obj_prefix + u'FilenameEdit') - fileLayout.addWidget(filenameEdit) + importLayout.setObjectName(prefix + u'ImportLayout') + if description_text is not None: + descriptionLayout = QtGui.QHBoxLayout() + descriptionLayout.setObjectName(prefix + u'DescriptionLayout') + descriptionSpacer = QtGui.QSpacerItem(0, 0, QtGui.QSizePolicy.Fixed, + QtGui.QSizePolicy.Fixed) + descriptionLayout.addSpacerItem(descriptionSpacer) + descriptionLabel = QtGui.QLabel(importWidget) + descriptionLabel.setWordWrap(True) + descriptionLabel.setOpenExternalLinks(True) + descriptionLabel.setObjectName(prefix + u'DescriptionLabel') + descriptionLayout.addWidget(descriptionLabel) + importLayout.addLayout(descriptionLayout) + self.formatWidgets[format][u'descriptionLabel'] = descriptionLabel + self.formatWidgets[format][u'descriptionSpacer'] = descriptionSpacer + if select_mode == SongFormatSelect.SingleFile or \ + select_mode == SongFormatSelect.SingleFolder: + filepathLayout = QtGui.QHBoxLayout() + filepathLayout.setObjectName(prefix + u'FilepathLayout') + filepathLayout.setContentsMargins(0, self.formatVSpacing, 0, 0) + filepathLabel = QtGui.QLabel(importWidget) + filepathLabel.setObjectName(prefix + u'FilepathLabel') + filepathLayout.addWidget(filepathLabel) + filepathSpacer = QtGui.QSpacerItem(0, 0, QtGui.QSizePolicy.Fixed, + QtGui.QSizePolicy.Fixed) + filepathLayout.addSpacerItem(filepathSpacer) + filepathEdit = QtGui.QLineEdit(importWidget) + filepathEdit.setObjectName(prefix + u'FilepathEdit') + filepathLayout.addWidget(filepathEdit) browseButton = QtGui.QToolButton(importWidget) browseButton.setIcon(self.openIcon) - browseButton.setObjectName(obj_prefix + u'BrowseButton') - fileLayout.addWidget(browseButton) - importLayout.addLayout(fileLayout) + browseButton.setObjectName(prefix + u'BrowseButton') + filepathLayout.addWidget(browseButton) + importLayout.addLayout(filepathLayout) importLayout.addSpacerItem(self.stackSpacer) - else: + self.formatWidgets[format][u'filepathLabel'] = filepathLabel + self.formatWidgets[format][u'filepathSpacer'] = filepathSpacer + self.formatWidgets[format][u'filepathLayout'] = filepathLayout + self.formatWidgets[format][u'filepathEdit'] = filepathEdit + self.formatWidgets[format][u'browseButton'] = browseButton + elif select_mode == SongFormatSelect.MultipleFiles: fileListWidget = QtGui.QListWidget(importWidget) fileListWidget.setSelectionMode( QtGui.QAbstractItemView.ExtendedSelection) - fileListWidget.setObjectName(obj_prefix + u'FileListWidget') + fileListWidget.setObjectName(prefix + u'FileListWidget') importLayout.addWidget(fileListWidget) buttonLayout = QtGui.QHBoxLayout() - buttonLayout.setObjectName(obj_prefix + u'ButtonLayout') + buttonLayout.setObjectName(prefix + u'ButtonLayout') addButton = QtGui.QPushButton(importWidget) addButton.setIcon(self.openIcon) - addButton.setObjectName(obj_prefix + u'AddButton') + addButton.setObjectName(prefix + u'AddButton') buttonLayout.addWidget(addButton) buttonLayout.addStretch() removeButton = QtGui.QPushButton(importWidget) removeButton.setIcon(self.deleteIcon) - removeButton.setObjectName(obj_prefix + u'RemoveButton') + removeButton.setObjectName(prefix + u'RemoveButton') buttonLayout.addWidget(removeButton) importLayout.addLayout(buttonLayout) + self.formatWidgets[format][u'fileListWidget'] = fileListWidget + self.formatWidgets[format][u'buttonLayout'] = buttonLayout + self.formatWidgets[format][u'addButton'] = addButton + self.formatWidgets[format][u'removeButton'] = removeButton self.formatStack.addWidget(page) - setattr(self, prefix + u'Page', page) - if single_select: - setattr(self, prefix + u'FilenameLabel', filenameLabel) - setattr(self, prefix + u'FileLayout', fileLayout) - setattr(self, prefix + u'FilenameEdit', filenameEdit) - setattr(self, prefix + u'BrowseButton', browseButton) - else: - setattr(self, prefix + u'FileListWidget', fileListWidget) - setattr(self, prefix + u'ButtonLayout', buttonLayout) - setattr(self, prefix + u'AddButton', addButton) - setattr(self, prefix + u'RemoveButton', removeButton) - setattr(self, prefix + u'ImportLayout', importLayout) + self.formatWidgets[format][u'page'] = page + self.formatWidgets[format][u'importLayout'] = importLayout self.formatComboBox.addItem(u'') - def disablableWidget(self, page, prefix, obj_prefix): + def disablableWidget(self, page, prefix): + format = self.currentFormat + self.disablableFormats.append(format) layout = QtGui.QVBoxLayout(page) layout.setMargin(0) layout.setSpacing(0) - layout.setObjectName(obj_prefix + u'Layout') + layout.setObjectName(prefix + u'Layout') disabledWidget = QtGui.QWidget(page) disabledWidget.setVisible(False) - disabledWidget.setObjectName(obj_prefix + u'DisabledWidget') + disabledWidget.setObjectName(prefix + u'DisabledWidget') disabledLayout = QtGui.QVBoxLayout(disabledWidget) disabledLayout.setMargin(0) - disabledLayout.setObjectName(obj_prefix + u'DisabledLayout') + disabledLayout.setObjectName(prefix + u'DisabledLayout') disabledLabel = QtGui.QLabel(disabledWidget) disabledLabel.setWordWrap(True) - disabledLabel.setObjectName(obj_prefix + u'DisabledLabel') + disabledLabel.setObjectName(prefix + u'DisabledLabel') disabledLayout.addWidget(disabledLabel) disabledLayout.addSpacerItem(self.stackSpacer) layout.addWidget(disabledWidget) importWidget = QtGui.QWidget(page) - importWidget.setObjectName(obj_prefix + u'ImportWidget') + importWidget.setObjectName(prefix + u'ImportWidget') layout.addWidget(importWidget) - setattr(self, prefix + u'Layout', layout) - setattr(self, prefix + u'DisabledWidget', disabledWidget) - setattr(self, prefix + u'DisabledLayout', disabledLayout) - setattr(self, prefix + u'DisabledLabel', disabledLabel) - setattr(self, prefix + u'ImportWidget', importWidget) + self.formatWidgets[format][u'layout'] = layout + self.formatWidgets[format][u'disabledWidget'] = disabledWidget + self.formatWidgets[format][u'disabledLayout'] = disabledLayout + self.formatWidgets[format][u'disabledLabel'] = disabledLabel + self.formatWidgets[format][u'importWidget'] = importWidget return importWidget + +class SongImportSourcePage(QtGui.QWizardPage): + """ + Subclass of QtGui.QWizardPage to override isComplete() for Source Page. + """ + def isComplete(self): + """ + Return True if: + + * an available format is selected, and + * if MultipleFiles mode, at least one file is selected + * or if SingleFile mode, the specified file exists + * or if SingleFolder mode, the specified folder exists + + When this method returns True, the wizard's Next button is enabled. + """ + wizard = self.wizard() + format = wizard.currentFormat + select_mode, format_available = SongFormat.get(format, u'selectMode', + u'availability') + if format_available: + if select_mode == SongFormatSelect.MultipleFiles: + if wizard.formatWidgets[format][u'fileListWidget'].count() > 0: + return True + else: + filepath = wizard.formatWidgets[format][u'filepathEdit'].text() + if not filepath.isEmpty(): + if select_mode == SongFormatSelect.SingleFile \ + and os.path.isfile(filepath): + return True + elif select_mode == SongFormatSelect.SingleFolder \ + and os.path.isdir(filepath): + return True + return False diff --git a/openlp/plugins/songs/lib/importer.py b/openlp/plugins/songs/lib/importer.py index 16d943a73..19916b97d 100644 --- a/openlp/plugins/songs/lib/importer.py +++ b/openlp/plugins/songs/lib/importer.py @@ -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 User Manual.') +# }, +# 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'] diff --git a/openlp/plugins/songs/lib/powersongimport.py b/openlp/plugins/songs/lib/powersongimport.py index 9946d273d..d3ac766f9 100644 --- a/openlp/plugins/songs/lib/powersongimport.py +++ b/openlp/plugins/songs/lib/powersongimport.py @@ -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: diff --git a/openlp/plugins/songs/lib/songimport.py b/openlp/plugins/songs/lib/songimport.py index ac6818184..dba86f3ab 100644 --- a/openlp/plugins/songs/lib/songimport.py +++ b/openlp/plugins/songs/lib/songimport.py @@ -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): """ diff --git a/openlp/plugins/songs/lib/zionworximport.py b/openlp/plugins/songs/lib/zionworximport.py new file mode 100644 index 000000000..8f83c4509 --- /dev/null +++ b/openlp/plugins/songs/lib/zionworximport.py @@ -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) diff --git a/openlp/plugins/songs/songsplugin.py b/openlp/plugins/songs/songsplugin.py index 4d59186e5..d7b36414a 100644 --- a/openlp/plugins/songs/songsplugin.py +++ b/openlp/plugins/songs/songsplugin.py @@ -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