diff --git a/openlp/core/__init__.py b/openlp/core/__init__.py index 96520195d..b4da78e83 100644 --- a/openlp/core/__init__.py +++ b/openlp/core/__init__.py @@ -41,6 +41,7 @@ from traceback import format_exception from PyQt4 import QtCore, QtGui from openlp.core.lib import Receiver, check_directory_exists +from openlp.core.lib.settings import Settings from openlp.core.lib.ui import UiStrings from openlp.core.resources import qInitResources from openlp.core.ui.mainwindow import MainWindow @@ -113,15 +114,15 @@ class OpenLP(QtGui.QApplication): # Decide how many screens we have and their size screens = ScreenList.create(self.desktop()) # First time checks in settings - has_run_wizard = QtCore.QSettings().value( + has_run_wizard = Settings().value( u'general/has run wizard', QtCore.QVariant(False)).toBool() if not has_run_wizard: if FirstTimeForm(screens).exec_() == QtGui.QDialog.Accepted: - QtCore.QSettings().setValue(u'general/has run wizard', + Settings().setValue(u'general/has run wizard', QtCore.QVariant(True)) if os.name == u'nt': self.setStyleSheet(application_stylesheet) - show_splash = QtCore.QSettings().value( + show_splash = Settings().value( u'general/show splash', QtCore.QVariant(True)).toBool() if show_splash: self.splash = SplashScreen() @@ -141,7 +142,7 @@ class OpenLP(QtGui.QApplication): self.processEvents() if not has_run_wizard: self.mainWindow.firstTime() - update_check = QtCore.QSettings().value( + update_check = Settings().value( u'general/update check', QtCore.QVariant(True)).toBool() if update_check: VersionThread(self.mainWindow).start() @@ -258,7 +259,28 @@ def main(args=None): app = OpenLP(qt_args) app.setOrganizationName(u'OpenLP') app.setOrganizationDomain(u'openlp.org') - app.setApplicationName(u'OpenLP') + if options.portable: + log.info(u'Running portable') + app.setApplicationName(u'OpenLPPortable') + Settings.setDefaultFormat(Settings.IniFormat) + # Get location OpenLPPortable.ini + app_path = AppLocation.get_directory(AppLocation.AppDir) + portable_settings_file = os.path.abspath(os.path.join(app_path, u'..', + u'..', u'Data', u'OpenLP.ini')) + # Make this our settings file + log.info(u'INI file: %s', portable_settings_file) + Settings.setFilename(portable_settings_file) + portable_settings = Settings() + # Set our data path + data_path = os.path.abspath(os.path.join(app_path, + u'..', u'..', u'Data',)) + log.info(u'Data path: %s', data_path) + # Point to our data path + portable_settings.setValue(u'advanced/data path', data_path) + portable_settings.setValue(u'advanced/is portable', True) + portable_settings.sync() + else: + app.setApplicationName(u'OpenLP') app.setApplicationVersion(get_application_version()[u'version']) # Instance check if not options.testing: @@ -266,7 +288,7 @@ def main(args=None): if app.isAlreadyRunning(): sys.exit() # First time checks in settings - if not QtCore.QSettings().value(u'general/has run wizard', + if not Settings().value(u'general/has run wizard', QtCore.QVariant(False)).toBool(): if not FirstTimeLanguageForm().exec_(): # if cancel then stop processing diff --git a/openlp/core/lib/db.py b/openlp/core/lib/db.py index e097983eb..28d6f8b92 100644 --- a/openlp/core/lib/db.py +++ b/openlp/core/lib/db.py @@ -41,6 +41,7 @@ from sqlalchemy.pool import NullPool from openlp.core.lib import translate from openlp.core.lib.ui import critical_error_message_box from openlp.core.utils import AppLocation, delete_file +from openlp.core.lib.settings import Settings log = logging.getLogger(__name__) @@ -179,7 +180,7 @@ class Manager(object): The file name to use for this database. Defaults to None resulting in the plugin_name being used. """ - settings = QtCore.QSettings() + settings = Settings() settings.beginGroup(plugin_name) self.db_url = u'' self.is_dirty = False @@ -238,27 +239,30 @@ class Manager(object): ``commit`` Commit the session with this object """ - try: - self.session.add(object_instance) - if commit: - self.session.commit() - self.is_dirty = True - return True - except OperationalError: - # This exception clause is for users running MySQL which likes - # to terminate connections on its own without telling anyone. - # See bug #927473 - log.exception(u'Probably a MySQL issue - "MySQL has gone away"') - self.session.rollback() - self.session.add(object_instance) - if commit: - self.session.commit() - self.is_dirty = True - return True - except InvalidRequestError: - self.session.rollback() - log.exception(u'Object save failed') - return False + for try_count in range(3): + try: + self.session.add(object_instance) + if commit: + self.session.commit() + self.is_dirty = True + return True + except OperationalError: + # This exception clause is for users running MySQL which likes + # to terminate connections on its own without telling anyone. + # See bug #927473 + # However, other dbms can raise it, usually in a non-recoverable + # way. So we only retry 3 times. + log.exception(u'Probably a MySQL issue - "MySQL has gone away"') + self.session.rollback() + if try_count >= 2: + raise + except InvalidRequestError: + self.session.rollback() + log.exception(u'Object list save failed') + return False + except: + self.session.rollback() + raise def save_objects(self, object_list, commit=True): """ @@ -270,27 +274,30 @@ class Manager(object): ``commit`` Commit the session with this object """ - try: - self.session.add_all(object_list) - if commit: - self.session.commit() - self.is_dirty = True - return True - except OperationalError: - # This exception clause is for users running MySQL which likes - # to terminate connections on its own without telling anyone. - # See bug #927473 - log.exception(u'Probably a MySQL issue, "MySQL has gone away"') - self.session.rollback() - self.session.add_all(object_list) - if commit: - self.session.commit() - self.is_dirty = True - return True - except InvalidRequestError: - self.session.rollback() - log.exception(u'Object list save failed') - return False + for try_count in range(3): + try: + self.session.add_all(object_list) + if commit: + self.session.commit() + self.is_dirty = True + return True + except OperationalError: + # This exception clause is for users running MySQL which likes + # to terminate connections on its own without telling anyone. + # See bug #927473 + # However, other dbms can raise it, usually in a non-recoverable + # way. So we only retry 3 times. + log.exception(u'Probably a MySQL issue, "MySQL has gone away"') + self.session.rollback() + if try_count >= 2: + raise + except InvalidRequestError: + self.session.rollback() + log.exception(u'Object list save failed') + return False + except: + self.session.rollback() + raise def get_object(self, object_class, key=None): """ @@ -305,15 +312,18 @@ class Manager(object): if not key: return object_class() else: - try: - return self.session.query(object_class).get(key) - except OperationalError: - # This exception clause is for users running MySQL which likes - # to terminate connections on its own without telling anyone. - # See bug #927473 - log.exception(u'Probably a MySQL issue, "MySQL has gone away"') - self.session.rollback() - return self.session.query(object_class).get(key) + for try_count in range(3): + try: + return self.session.query(object_class).get(key) + except OperationalError: + # This exception clause is for users running MySQL which likes + # to terminate connections on its own without telling anyone. + # See bug #927473 + # However, other dbms can raise it, usually in a non-recoverable + # way. So we only retry 3 times. + log.exception(u'Probably a MySQL issue, "MySQL has gone away"') + if try_count >= 2: + raise def get_object_filtered(self, object_class, filter_clause): """ @@ -325,15 +335,18 @@ class Manager(object): ``filter_clause`` The criteria to select the object by """ - try: - return self.session.query(object_class).filter(filter_clause).first() - except OperationalError: - # This exception clause is for users running MySQL which likes - # to terminate connections on its own without telling anyone. - # See bug #927473 - log.exception(u'Probably a MySQL issue, "MySQL has gone away"') - self.session.rollback() - return self.session.query(object_class).filter(filter_clause).first() + for try_count in range(3): + try: + return self.session.query(object_class).filter(filter_clause).first() + except OperationalError: + # This exception clause is for users running MySQL which likes + # to terminate connections on its own without telling anyone. + # See bug #927473 + # However, other dbms can raise it, usually in a non-recoverable + # way. So we only retry 3 times. + log.exception(u'Probably a MySQL issue, "MySQL has gone away"') + if try_count >= 2: + raise def get_all_objects(self, object_class, filter_clause=None, order_by_ref=None): @@ -357,15 +370,18 @@ class Manager(object): query = query.order_by(*order_by_ref) elif order_by_ref is not None: query = query.order_by(order_by_ref) - try: - return query.all() - except OperationalError: - # This exception clause is for users running MySQL which likes - # to terminate connections on its own without telling anyone. - # See bug #927473 - log.exception(u'Probably a MySQL issue, "MySQL has gone away"') - self.session.rollback() - return query.all() + for try_count in range(3): + try: + return query.all() + except OperationalError: + # This exception clause is for users running MySQL which likes + # to terminate connections on its own without telling anyone. + # See bug #927473 + # However, other dbms can raise it, usually in a non-recoverable + # way. So we only retry 3 times. + log.exception(u'Probably a MySQL issue, "MySQL has gone away"') + if try_count >= 2: + raise def get_object_count(self, object_class, filter_clause=None): """ @@ -381,15 +397,18 @@ class Manager(object): query = self.session.query(object_class) if filter_clause is not None: query = query.filter(filter_clause) - try: - return query.count() - except OperationalError: - # This exception clause is for users running MySQL which likes - # to terminate connections on its own without telling anyone. - # See bug #927473 - log.exception(u'Probably a MySQL issue, "MySQL has gone away"') - self.session.rollback() - return query.count() + for try_count in range(3): + try: + return query.count() + except OperationalError: + # This exception clause is for users running MySQL which likes + # to terminate connections on its own without telling anyone. + # See bug #927473 + # However, other dbms can raise it, usually in a non-recoverable + # way. So we only retry 3 times. + log.exception(u'Probably a MySQL issue, "MySQL has gone away"') + if try_count >= 2: + raise def delete_object(self, object_class, key): """ @@ -403,25 +422,29 @@ class Manager(object): """ if key != 0: object_instance = self.get_object(object_class, key) - try: - self.session.delete(object_instance) - self.session.commit() - self.is_dirty = True - return True - except OperationalError: - # This exception clause is for users running MySQL which likes - # to terminate connections on its own without telling anyone. - # See bug #927473 - log.exception(u'Probably a MySQL issue, "MySQL has gone away"') - self.session.rollback() - self.session.delete(object_instance) - self.session.commit() - self.is_dirty = True - return True - except InvalidRequestError: - self.session.rollback() - log.exception(u'Failed to delete object') - return False + for try_count in range(3): + try: + self.session.delete(object_instance) + self.session.commit() + self.is_dirty = True + return True + except OperationalError: + # This exception clause is for users running MySQL which likes + # to terminate connections on its own without telling anyone. + # See bug #927473 + # However, other dbms can raise it, usually in a non-recoverable + # way. So we only retry 3 times. + log.exception(u'Probably a MySQL issue, "MySQL has gone away"') + self.session.rollback() + if try_count >= 2: + raise + except InvalidRequestError: + self.session.rollback() + log.exception(u'Failed to delete object') + return False + except: + self.session.rollback() + raise else: return True @@ -439,31 +462,32 @@ class Manager(object): The filter governing selection of objects to return. Defaults to None. """ - try: - query = self.session.query(object_class) - if filter_clause is not None: - query = query.filter(filter_clause) - query.delete(synchronize_session=False) - self.session.commit() - self.is_dirty = True - return True - except OperationalError: - # This exception clause is for users running MySQL which likes - # to terminate connections on its own without telling anyone. - # See bug #927473 - log.exception(u'Probably a MySQL issue, "MySQL has gone away"') - self.session.rollback() - query = self.session.query(object_class) - if filter_clause is not None: - query = query.filter(filter_clause) - query.delete(synchronize_session=False) - self.session.commit() - self.is_dirty = True - return True - except InvalidRequestError: - self.session.rollback() - log.exception(u'Failed to delete %s records', object_class.__name__) - return False + for try_count in range(3): + try: + query = self.session.query(object_class) + if filter_clause is not None: + query = query.filter(filter_clause) + query.delete(synchronize_session=False) + self.session.commit() + self.is_dirty = True + return True + except OperationalError: + # This exception clause is for users running MySQL which likes + # to terminate connections on its own without telling anyone. + # See bug #927473 + # However, other dbms can raise it, usually in a non-recoverable + # way. So we only retry 3 times. + log.exception(u'Probably a MySQL issue, "MySQL has gone away"') + self.session.rollback() + if try_count >= 2: + raise + except InvalidRequestError: + self.session.rollback() + log.exception(u'Failed to delete %s records', object_class.__name__) + return False + except: + self.session.rollback() + raise def finalise(self): """ 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/formattingtags.py b/openlp/core/lib/formattingtags.py index 11fd898c3..4ac1509fc 100644 --- a/openlp/core/lib/formattingtags.py +++ b/openlp/core/lib/formattingtags.py @@ -32,6 +32,7 @@ import cPickle from PyQt4 import QtCore from openlp.core.lib import translate +from openlp.core.lib.settings import Settings class FormattingTags(object): @@ -68,7 +69,7 @@ class FormattingTags(object): if isinstance(tag[element], unicode): tag[element] = tag[element].encode('utf8') # Formatting Tags were also known as display tags. - QtCore.QSettings().setValue(u'displayTags/html_tags', + Settings().setValue(u'displayTags/html_tags', QtCore.QVariant(cPickle.dumps(tags) if tags else u'')) @staticmethod @@ -164,7 +165,7 @@ class FormattingTags(object): FormattingTags.add_html_tags(temporary_tags) # Formatting Tags were also known as display tags. - user_expands = QtCore.QSettings().value(u'displayTags/html_tags', + user_expands = Settings().value(u'displayTags/html_tags', QtCore.QVariant(u'')).toString() # cPickle only accepts str not unicode strings user_expands_string = str(user_expands) diff --git a/openlp/core/lib/mediamanageritem.py b/openlp/core/lib/mediamanageritem.py index 9bd6fc2a7..703b6e9e5 100644 --- a/openlp/core/lib/mediamanageritem.py +++ b/openlp/core/lib/mediamanageritem.py @@ -38,6 +38,7 @@ from openlp.core.lib import SettingsManager, OpenLPToolbar, ServiceItem, \ from openlp.core.lib.searchedit import SearchEdit from openlp.core.lib.ui import UiStrings, create_widget_action, \ critical_error_message_box +from openlp.core.lib.settings import Settings log = logging.getLogger(__name__) @@ -462,7 +463,7 @@ class MediaManagerItem(QtGui.QWidget): """ Allows the list click action to be determined dynamically """ - if QtCore.QSettings().value(u'advanced/double click live', + if Settings().value(u'advanced/double click live', QtCore.QVariant(False)).toBool(): self.onLiveClick() else: @@ -472,7 +473,7 @@ class MediaManagerItem(QtGui.QWidget): """ Allows the change of current item in the list to be actioned """ - if QtCore.QSettings().value(u'advanced/single click preview', + if Settings().value(u'advanced/single click preview', QtCore.QVariant(False)).toBool() and self.quickPreviewAllowed \ and self.listView.selectedIndexes() \ and self.autoSelectId == -1: diff --git a/openlp/core/lib/plugin.py b/openlp/core/lib/plugin.py index a27e496cf..7a34626ad 100644 --- a/openlp/core/lib/plugin.py +++ b/openlp/core/lib/plugin.py @@ -32,6 +32,7 @@ import logging from PyQt4 import QtCore from openlp.core.lib import Receiver +from openlp.core.lib.settings import Settings from openlp.core.lib.ui import UiStrings from openlp.core.utils import get_application_version @@ -190,7 +191,7 @@ class Plugin(QtCore.QObject): """ Sets the status of the plugin """ - self.status = QtCore.QSettings().value( + self.status = Settings().value( self.settingsSection + u'/status', QtCore.QVariant(PluginStatus.Inactive)).toInt()[0] @@ -199,7 +200,7 @@ class Plugin(QtCore.QObject): Changes the status of the plugin and remembers it """ self.status = new_status - QtCore.QSettings().setValue( + Settings().setValue( self.settingsSection + u'/status', QtCore.QVariant(self.status)) if new_status == PluginStatus.Active: self.initialise() 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..e35c78559 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) + serviceItem.add_from_text(VERSE_FOR_LINE_COUNT) else: - self.imageManager.deleteImage(theme_data.theme_name) - serviceItem.add_from_text(u'', VERSE) + self.image_manager.deleteImage(theme_data.theme_name) + serviceItem.add_from_text(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..1e9555eb9 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,24 +216,21 @@ 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() - def add_from_text(self, title, raw_slide, verse_tag=None): + def add_from_text(self, raw_slide, verse_tag=None): """ Add a text slide to the service item. - ``frame_title`` - The title of the slide in the service item. - ``raw_slide`` The raw text of the slide. """ if verse_tag: verse_tag = verse_tag.upper() self.service_item_type = ServiceItemType.Text - title = title.split(u'\n')[0] + title = raw_slide[:30].split(u'\n')[0] self._raw_frames.append( {u'title': title, u'raw_slide': raw_slide, u'verseTag': verse_tag}) self._new_item() diff --git a/openlp/core/lib/settings.py b/openlp/core/lib/settings.py new file mode 100644 index 000000000..5f012c0d1 --- /dev/null +++ b/openlp/core/lib/settings.py @@ -0,0 +1,66 @@ +# -*- 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:``settings`` module provides a thin wrapper for QSettings, which OpenLP +uses to manage settings persistence. +""" + +import logging + +from PyQt4 import QtCore + +log = logging.getLogger() + +class Settings(QtCore.QSettings): + """ + Class to wrap QSettings. + + * Exposes all the methods of QSettings. + * Adds functionality for OpenLP Portable. If the ``defaultFormat`` is set to + ``IniFormat``, and the path to the Ini file is set using ``setFilename``, + then the Settings constructor (without any arguments) will create a Settings + object for accessing settings stored in that Ini file. + """ + + __filePath = u'' + + @staticmethod + def setFilename(iniFile): + """ + Sets the complete path to an Ini file to be used by Settings objects. + + Does not affect existing Settings objects. + """ + Settings.__filePath = iniFile + + def __init__(self, *args): + if not args and Settings.__filePath and (Settings.defaultFormat() == + Settings.IniFormat): + QtCore.QSettings.__init__(self, Settings.__filePath, + Settings.IniFormat) + else: + QtCore.QSettings.__init__(self, *args) diff --git a/openlp/core/lib/settingsmanager.py b/openlp/core/lib/settingsmanager.py index 0fc4cac96..245b98ef8 100644 --- a/openlp/core/lib/settingsmanager.py +++ b/openlp/core/lib/settingsmanager.py @@ -34,6 +34,7 @@ import os from PyQt4 import QtCore +from openlp.core.lib.settings import Settings from openlp.core.utils import AppLocation class SettingsManager(object): @@ -58,7 +59,7 @@ class SettingsManager(object): name = u'last directory %d' % num else: name = u'last directory' - last_dir = unicode(QtCore.QSettings().value( + last_dir = unicode(Settings().value( section + u'/' + name, QtCore.QVariant(u'')).toString()) return last_dir @@ -81,7 +82,7 @@ class SettingsManager(object): name = u'last directory %d' % num else: name = u'last directory' - QtCore.QSettings().setValue( + Settings().setValue( section + u'/' + name, QtCore.QVariant(directory)) @staticmethod @@ -98,7 +99,7 @@ class SettingsManager(object): ``list`` The list of values to save. """ - settings = QtCore.QSettings() + settings = Settings() settings.beginGroup(section) old_count = settings.value( u'%s count' % name, QtCore.QVariant(0)).toInt()[0] @@ -124,7 +125,7 @@ class SettingsManager(object): ``name`` The name of the list. """ - settings = QtCore.QSettings() + settings = Settings() settings.beginGroup(section) list_count = settings.value( u'%s count' % name, QtCore.QVariant(0)).toInt()[0] 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/aboutdialog.py b/openlp/core/ui/aboutdialog.py index f0908b729..211bdfb0a 100644 --- a/openlp/core/ui/aboutdialog.py +++ b/openlp/core/ui/aboutdialog.py @@ -25,7 +25,7 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### -from PyQt4 import QtCore, QtGui +from PyQt4 import QtGui from openlp.core.lib import build_icon, translate from openlp.core.lib.ui import UiStrings, create_button, create_button_box diff --git a/openlp/core/ui/advancedtab.py b/openlp/core/ui/advancedtab.py index 8b60a55ee..0f34efe13 100644 --- a/openlp/core/ui/advancedtab.py +++ b/openlp/core/ui/advancedtab.py @@ -31,10 +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): """ @@ -59,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) @@ -151,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) @@ -219,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) @@ -243,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, @@ -257,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:')) @@ -320,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', @@ -340,12 +451,12 @@ class AdvancedTab(SettingsTab): """ Load settings from disk. """ - settings = QtCore.QSettings() + settings = Settings() settings.beginGroup(self.settingsSection) # The max recent files value does not have an interface and so never # gets actually stored in the settings therefore the default value of # 20 will always be used. - self.recentSpinBox.setMaximum(QtCore.QSettings().value( + self.recentSpinBox.setMaximum(Settings().value( u'max recent files', QtCore.QVariant(20)).toInt()[0]) self.recentSpinBox.setValue(settings.value(u'recent file count', QtCore.QVariant(4)).toInt()[0]) @@ -397,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) @@ -404,7 +549,7 @@ class AdvancedTab(SettingsTab): """ Save settings to disk. """ - settings = QtCore.QSettings() + settings = Settings() settings.beginGroup(self.settingsSection) settings.setValue(u'default service enabled', self.serviceNameCheckBox.isChecked()) @@ -446,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 \ @@ -507,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/firsttimeform.py b/openlp/core/ui/firsttimeform.py index c675312ef..1866b88b5 100644 --- a/openlp/core/ui/firsttimeform.py +++ b/openlp/core/ui/firsttimeform.py @@ -39,6 +39,7 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import translate, PluginStatus, Receiver, build_icon, \ check_directory_exists +from openlp.core.lib.settings import Settings from openlp.core.utils import get_web_page, AppLocation from firsttimewizard import Ui_FirstTimeWizard, FirstTimePage @@ -116,7 +117,7 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard): check_directory_exists(os.path.join(gettempdir(), u'openlp')) self.noInternetFinishButton.setVisible(False) # Check if this is a re-run of the wizard. - self.hasRunWizard = QtCore.QSettings().value( + self.hasRunWizard = Settings().value( u'general/has run wizard', QtCore.QVariant(False)).toBool() # Sort out internet access for downloads if self.webAccess: @@ -209,7 +210,7 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard): index = self.themeComboBox.findText(theme) if index == -1: self.themeComboBox.addItem(theme) - default_theme = unicode(QtCore.QSettings().value( + default_theme = unicode(Settings().value( u'themes/global theme', QtCore.QVariant(u'')).toString()) # Pre-select the current default theme. @@ -261,7 +262,7 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard): self._performWizard() Receiver.send_message(u'openlp_process_events') Receiver.send_message(u'cursor_normal') - QtCore.QSettings().setValue(u'general/has run wizard', + Settings().setValue(u'general/has run wizard', QtCore.QVariant(True)) self.close() @@ -460,16 +461,16 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard): os.path.join(themes_destination, theme)) # Set Default Display if self.displayComboBox.currentIndex() != -1: - QtCore.QSettings().setValue(u'General/monitor', + Settings().setValue(u'General/monitor', QtCore.QVariant(self.displayComboBox.currentIndex())) self.screens.set_current_display( self.displayComboBox.currentIndex()) # Set Global Theme if self.themeComboBox.currentIndex() != -1: - QtCore.QSettings().setValue(u'themes/global theme', + Settings().setValue(u'themes/global theme', QtCore.QVariant(self.themeComboBox.currentText())) def _setPluginStatus(self, field, tag): status = PluginStatus.Active if field.checkState() \ == QtCore.Qt.Checked else PluginStatus.Inactive - QtCore.QSettings().setValue(tag, QtCore.QVariant(status)) + Settings().setValue(tag, QtCore.QVariant(status)) diff --git a/openlp/core/ui/firsttimelanguagedialog.py b/openlp/core/ui/firsttimelanguagedialog.py index f07d7bd15..51b077039 100644 --- a/openlp/core/ui/firsttimelanguagedialog.py +++ b/openlp/core/ui/firsttimelanguagedialog.py @@ -25,7 +25,7 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### -from PyQt4 import QtCore, QtGui +from PyQt4 import QtGui from openlp.core.lib import translate from openlp.core.lib.ui import create_button_box diff --git a/openlp/core/ui/generaltab.py b/openlp/core/ui/generaltab.py index d0647d829..78358f287 100644 --- a/openlp/core/ui/generaltab.py +++ b/openlp/core/ui/generaltab.py @@ -30,6 +30,7 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import SettingsTab, Receiver, translate from openlp.core.lib.ui import UiStrings +from openlp.core.lib.settings import Settings from openlp.core.ui import ScreenList log = logging.getLogger(__name__) @@ -265,7 +266,7 @@ class GeneralTab(SettingsTab): """ Load the settings to populate the form """ - settings = QtCore.QSettings() + settings = Settings() settings.beginGroup(self.settingsSection) self.monitorComboBox.clear() self.monitorComboBox.addItems(self.screens.get_screen_list()) @@ -327,7 +328,7 @@ class GeneralTab(SettingsTab): """ Save the settings from the form """ - settings = QtCore.QSettings() + settings = Settings() settings.beginGroup(self.settingsSection) settings.setValue(u'monitor', QtCore.QVariant(self.monitorComboBox.currentIndex())) diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index b642240a3..0a56946c1 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -38,6 +38,7 @@ from PyQt4.phonon import Phonon from openlp.core.lib import Receiver, build_html, ServiceItem, image_to_byte, \ translate, PluginManager, expand_tags from openlp.core.lib.theme import BackgroundType +from openlp.core.lib.settings import Settings from openlp.core.ui import HideMode, ScreenList, AlertLocation @@ -100,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): """ @@ -120,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 = {} @@ -131,10 +130,11 @@ class MainDisplay(Display): else: self.audioPlayer = None self.firstTime = True + self.webLoaded = True self.setStyleSheet(u'border: 0px; margin: 0px; padding: 0px;') windowFlags = QtCore.Qt.FramelessWindowHint | QtCore.Qt.Tool | \ - QtCore.Qt.WindowStaysOnTopHint - if QtCore.QSettings().value(u'advanced/x11 bypass wm', + QtCore.Qt.WindowStaysOnTopHint + if Settings().value(u'advanced/x11 bypass wm', QtCore.QVariant(True)).toBool(): windowFlags |= QtCore.Qt.X11BypassWindowManagerHint # FIXME: QtCore.Qt.SplashScreen is workaround to make display screen @@ -195,15 +195,15 @@ class MainDisplay(Display): Display.setup(self) if self.isLive: # Build the initial frame. - image_file = QtCore.QSettings().value(u'advanced/default image', - QtCore.QVariant(u':/graphics/openlp-splash-screen.png'))\ - .toString() background_color = QtGui.QColor() - background_color.setNamedColor(QtCore.QSettings().value( + background_color.setNamedColor(Settings().value( u'advanced/default color', QtCore.QVariant(u'#ffffff')).toString()) if not background_color.isValid(): background_color = QtCore.Qt.white + image_file = Settings().value(u'advanced/default image', + QtCore.QVariant(u':/graphics/openlp-splash-screen.png'))\ + .toString() splash_image = QtGui.QImage(image_file) self.initialFrame = QtGui.QImage( self.screen[u'size'].width(), @@ -290,10 +290,10 @@ class MainDisplay(Display): def image(self, name): """ - Add an image as the background. The image has already been added - to the cache. + Add an image as the background. The image has already been added to the + cache. - ``Image`` + ``name`` The name of the image to be displayed. """ log.debug(u'image to display') @@ -352,7 +352,7 @@ class MainDisplay(Display): # Single screen active if self.screens.display_count == 1: # Only make visible if setting enabled. - if QtCore.QSettings().value(u'general/display on monitor', + if Settings().value(u'general/display on monitor', QtCore.QVariant(True)).toBool(): self.setVisible(True) else: @@ -401,7 +401,7 @@ class MainDisplay(Display): self.footer(serviceItem.foot_text) # if was hidden keep it hidden if self.hideMode and self.isLive and not serviceItem.is_media(): - if QtCore.QSettings().value(u'general/auto unblank', + if Settings().value(u'general/auto unblank', QtCore.QVariant(False)).toBool(): Receiver.send_message(u'slidecontroller_live_unblank') else: @@ -425,7 +425,7 @@ class MainDisplay(Display): log.debug(u'hideDisplay mode = %d', mode) if self.screens.display_count == 1: # Only make visible if setting enabled. - if not QtCore.QSettings().value(u'general/display on monitor', + if not Settings().value(u'general/display on monitor', QtCore.QVariant(True)).toBool(): return if mode == HideMode.Screen: @@ -450,7 +450,7 @@ class MainDisplay(Display): log.debug(u'showDisplay') if self.screens.display_count == 1: # Only make visible if setting enabled. - if not QtCore.QSettings().value(u'general/display on monitor', + if not Settings().value(u'general/display on monitor', QtCore.QVariant(True)).toBool(): return self.frame.evaluateJavaScript('show_blank("show");') @@ -465,7 +465,7 @@ class MainDisplay(Display): """ Hide mouse cursor when moved over display. """ - if QtCore.QSettings().value(u'advanced/hide mouse', + if Settings().value(u'advanced/hide mouse', QtCore.QVariant(False)).toBool(): self.setCursor(QtCore.Qt.BlankCursor) self.frame.evaluateJavaScript('document.body.style.cursor = "none"') diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 3923cad26..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 @@ -38,6 +40,7 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import Renderer, build_icon, OpenLPDockWidget, \ PluginManager, Receiver, translate, ImageManager, PluginStatus from openlp.core.lib.ui import UiStrings, create_action +from openlp.core.lib.settings import Settings from openlp.core.lib import SlideLimits from openlp.core.ui import AboutForm, SettingsForm, ServiceManager, \ ThemeManager, SlideController, PluginForm, MediaDockManager, \ @@ -100,12 +103,12 @@ class Ui_MainWindow(object): # Create slide controllers self.previewController = SlideController(self) self.liveController = SlideController(self, True) - previewVisible = QtCore.QSettings().value( + previewVisible = Settings().value( u'user interface/preview panel', QtCore.QVariant(True)).toBool() self.previewController.panel.setVisible(previewVisible) - liveVisible = QtCore.QSettings().value(u'user interface/live panel', + liveVisible = Settings().value(u'user interface/live panel', QtCore.QVariant(True)).toBool() - panelLocked = QtCore.QSettings().value(u'user interface/lock panel', + panelLocked = Settings().value(u'user interface/lock panel', QtCore.QVariant(False)).toBool() self.liveController.panel.setVisible(liveVisible) # Create menu @@ -582,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()'), @@ -634,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) @@ -646,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 @@ -686,9 +697,9 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): self.previewController.screenSizeChanged() self.liveController.screenSizeChanged() log.info(u'Load data from Settings') - if QtCore.QSettings().value(u'advanced/save current plugin', + if Settings().value(u'advanced/save current plugin', QtCore.QVariant(False)).toBool(): - savedPlugin = QtCore.QSettings().value( + savedPlugin = Settings().value( u'advanced/current media plugin', QtCore.QVariant()).toInt()[0] if savedPlugin != -1: self.mediaToolBox.setCurrentIndex(savedPlugin) @@ -740,11 +751,11 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): if not isinstance(filename, unicode): filename = unicode(filename, sys.getfilesystemencoding()) self.serviceManagerContents.loadFile(filename) - elif QtCore.QSettings().value( + elif Settings().value( self.generalSettingsSection + u'/auto open', QtCore.QVariant(False)).toBool(): self.serviceManagerContents.loadLastFile() - view_mode = QtCore.QSettings().value(u'%s/view mode' % \ + view_mode = Settings().value(u'%s/view mode' % \ self.generalSettingsSection, u'default').toString() if view_mode == u'default': self.modeDefaultItem.setChecked(True) @@ -822,7 +833,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): """ Check and display message if screen blank on setup. """ - settings = QtCore.QSettings() + settings = Settings() self.liveController.mainDisplaySetBackground() if settings.value(u'%s/screen blank' % self.generalSettingsSection, QtCore.QVariant(False)).toBool(): @@ -956,9 +967,9 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): # Add plugin sections. for plugin in self.pluginManager.plugins: setting_sections.extend([plugin.name]) - settings = QtCore.QSettings() - import_settings = QtCore.QSettings(import_file_name, - QtCore.QSettings.IniFormat) + settings = Settings() + import_settings = Settings(import_file_name, + Settings.IniFormat) import_keys = import_settings.allKeys() for section_key in import_keys: # We need to handle the really bad files. @@ -1020,7 +1031,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): 'OpenLP Export Settings File (*.conf)'))) if not export_file_name: return - # Make sure it's a .conf file. + # Make sure it's a .conf file. if not export_file_name.endswith(u'conf'): export_file_name = export_file_name + u'.conf' temp_file = os.path.join(unicode(gettempdir()), @@ -1043,12 +1054,12 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): os.remove(temp_file) if os.path.exists(export_file_name): os.remove(export_file_name) - settings = QtCore.QSettings() + settings = Settings() settings.remove(self.headerSection) # Get the settings. keys = settings.allKeys() - export_settings = QtCore.QSettings(temp_file, - QtCore.QSettings.IniFormat) + export_settings = Settings(temp_file, + Settings.IniFormat) # Add a header section. # This is to insure it's our conf file for import. now = datetime.now() @@ -1106,7 +1117,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): Set OpenLP to a different view mode. """ if mode: - settings = QtCore.QSettings() + settings = Settings() settings.setValue(u'%s/view mode' % self.generalSettingsSection, mode) self.mediaManagerDock.setVisible(media) @@ -1157,7 +1168,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): else: event.ignore() else: - if QtCore.QSettings().value(u'advanced/enable exit confirmation', + if Settings().value(u'advanced/enable exit confirmation', QtCore.QVariant(True)).toBool(): ret = QtGui.QMessageBox.question(self, translate('OpenLP.MainWindow', 'Close OpenLP'), @@ -1188,9 +1199,9 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): # Clean temporary files used by services self.serviceManagerContents.cleanUp() if save_settings: - if QtCore.QSettings().value(u'advanced/save current plugin', + if Settings().value(u'advanced/save current plugin', QtCore.QVariant(False)).toBool(): - QtCore.QSettings().setValue(u'advanced/current media plugin', + Settings().setValue(u'advanced/current media plugin', QtCore.QVariant(self.mediaToolBox.currentIndex())) # Call the cleanup method to shutdown plugins. log.info(u'cleanup plugins') @@ -1198,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() @@ -1271,7 +1285,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): False - Hidden """ self.previewController.panel.setVisible(visible) - QtCore.QSettings().setValue(u'user interface/preview panel', + Settings().setValue(u'user interface/preview panel', QtCore.QVariant(visible)) self.viewPreviewPanel.setChecked(visible) @@ -1303,7 +1317,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): self.viewThemeManagerItem.setEnabled(True) self.viewPreviewPanel.setEnabled(True) self.viewLivePanel.setEnabled(True) - QtCore.QSettings().setValue(u'user interface/lock panel', + Settings().setValue(u'user interface/lock panel', QtCore.QVariant(lock)) def setLivePanelVisibility(self, visible): @@ -1317,7 +1331,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): False - Hidden """ self.liveController.panel.setVisible(visible) - QtCore.QSettings().setValue(u'user interface/live panel', + Settings().setValue(u'user interface/live panel', QtCore.QVariant(visible)) self.viewLivePanel.setChecked(visible) @@ -1327,19 +1341,19 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): """ log.debug(u'Loading QSettings') # Migrate Wrap Settings to Slide Limits Settings - if QtCore.QSettings().contains(self.generalSettingsSection + + if Settings().contains(self.generalSettingsSection + u'/enable slide loop'): - if QtCore.QSettings().value(self.generalSettingsSection + + if Settings().value(self.generalSettingsSection + u'/enable slide loop', QtCore.QVariant(True)).toBool(): - QtCore.QSettings().setValue(self.advancedSettingsSection + + Settings().setValue(self.advancedSettingsSection + u'/slide limits', QtCore.QVariant(SlideLimits.Wrap)) else: - QtCore.QSettings().setValue(self.advancedSettingsSection + + Settings().setValue(self.advancedSettingsSection + u'/slide limits', QtCore.QVariant(SlideLimits.End)) - QtCore.QSettings().remove(self.generalSettingsSection + + Settings().remove(self.generalSettingsSection + u'/enable slide loop') Receiver.send_message(u'slidecontroller_update_slide_limits') - settings = QtCore.QSettings() + settings = Settings() # Remove obsolete entries. settings.remove(u'custom slide') settings.remove(u'service') @@ -1368,7 +1382,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): if self.settingsImported: return log.debug(u'Saving QSettings') - settings = QtCore.QSettings() + settings = Settings() settings.beginGroup(self.generalSettingsSection) recentFiles = QtCore.QVariant(self.recentFiles) \ if self.recentFiles else QtCore.QVariant() @@ -1394,7 +1408,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): Updates the recent file menu with the latest list of service files accessed. """ - recentFileCount = QtCore.QSettings().value( + recentFileCount = Settings().value( u'advanced/recent file count', QtCore.QVariant(4)).toInt()[0] existingRecentFiles = [recentFile for recentFile in self.recentFiles if os.path.isfile(unicode(recentFile))] @@ -1427,7 +1441,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): # The maxRecentFiles value does not have an interface and so never gets # actually stored in the settings therefore the default value of 20 will # always be used. - maxRecentFiles = QtCore.QSettings().value(u'advanced/max recent files', + maxRecentFiles = Settings().value(u'advanced/max recent files', QtCore.QVariant(20)).toInt()[0] if filename: # Add some cleanup to reduce duplication in the recent file list @@ -1474,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/__init__.py b/openlp/core/ui/media/__init__.py index 5c4428a61..10583ffe0 100644 --- a/openlp/core/ui/media/__init__.py +++ b/openlp/core/ui/media/__init__.py @@ -26,6 +26,8 @@ ############################################################################### import logging +from openlp.core.lib.settings import Settings + from PyQt4 import QtCore log = logging.getLogger(__name__) @@ -78,11 +80,11 @@ def get_media_players(): Here an special media player is chosen for all media actions. """ log.debug(u'get_media_players') - players = unicode(QtCore.QSettings().value(u'media/players').toString()) + players = unicode(Settings().value(u'media/players').toString()) if not players: players = u'webkit' reg_ex = QtCore.QRegExp(".*\[(.*)\].*") - if QtCore.QSettings().value(u'media/override player', + if Settings().value(u'media/override player', QtCore.QVariant(QtCore.Qt.Unchecked)).toInt()[0] == QtCore.Qt.Checked: if reg_ex.exactMatch(players): overridden_player = u'%s' % reg_ex.cap(1) @@ -107,10 +109,10 @@ def set_media_players(players_list, overridden_player=u'auto'): """ log.debug(u'set_media_players') players = u','.join(players_list) - if QtCore.QSettings().value(u'media/override player', + if Settings().value(u'media/override player', QtCore.QVariant(QtCore.Qt.Unchecked)).toInt()[0] == \ QtCore.Qt.Checked and overridden_player != u'auto': players = players.replace(overridden_player, u'[%s]' % overridden_player) - QtCore.QSettings().setValue(u'media/players', QtCore.QVariant(players)) + Settings().setValue(u'media/players', QtCore.QVariant(players)) from mediacontroller import MediaController diff --git a/openlp/core/ui/media/mediacontroller.py b/openlp/core/ui/media/mediacontroller.py index b7356fcf5..7e51cd172 100644 --- a/openlp/core/ui/media/mediacontroller.py +++ b/openlp/core/ui/media/mediacontroller.py @@ -30,6 +30,7 @@ import os from PyQt4 import QtCore, QtGui from openlp.core.lib import OpenLPToolbar, Receiver, translate +from openlp.core.lib.settings import Settings from openlp.core.lib.mediaplayer import MediaPlayer from openlp.core.lib.ui import critical_error_message_box from openlp.core.ui.media import MediaState, MediaInfo, MediaType, \ @@ -333,10 +334,9 @@ class MediaController(object): "setBackBoard", null, null, null,"visible");') # now start playing if controller.isLive and \ - (QtCore.QSettings().value(u'general/auto unblank', + (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'), @@ -495,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..733e41177 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 = "Thu Jun 14 15:22:46 2012" # Internal guard to prevent internal classes to be directly # instanciated. @@ -235,6 +235,23 @@ def class_result(classname): return classname(result) return wrap_errcheck +# FILE* ctypes wrapper, copied from +# http://svn.python.org/projects/ctypes/trunk/ctypeslib/ctypeslib/contrib/pythonhdr.py +class FILE(ctypes.Structure): + pass +FILE_ptr = ctypes.POINTER(FILE) + +PyFile_FromFile = ctypes.pythonapi.PyFile_FromFile +PyFile_FromFile.restype = ctypes.py_object +PyFile_FromFile.argtypes = [FILE_ptr, + ctypes.c_char_p, + ctypes.c_char_p, + ctypes.CFUNCTYPE(ctypes.c_int, FILE_ptr)] + +PyFile_AsFile = ctypes.pythonapi.PyFile_AsFile +PyFile_AsFile.restype = FILE_ptr +PyFile_AsFile.argtypes = [ctypes.py_object] + # Generated enum types # class _Enum(ctypes.c_uint): @@ -256,6 +273,21 @@ class _Enum(ctypes.c_uint): def __ne__(self, other): return not self.__eq__(other) +class LogLevel(_Enum): + '''Logging messages level. +\note future libvlc versions may define new levels. + ''' + _enum_names_ = { + 0: 'DEBUG', + 2: 'NOTICE', + 3: 'WARNING', + 4: 'ERROR', + } +LogLevel.DEBUG = LogLevel(0) +LogLevel.ERROR = LogLevel(4) +LogLevel.NOTICE = LogLevel(2) +LogLevel.WARNING = LogLevel(3) + class EventType(_Enum): '''Event types. ''' @@ -577,6 +609,247 @@ AudioOutputChannel.RStereo = AudioOutputChannel(2) AudioOutputChannel.Right = AudioOutputChannel(4) AudioOutputChannel.Stereo = AudioOutputChannel(1) +class Callback(ctypes.c_void_p): + """Callback function notification +\param p_event the event triggering the callback + """ + pass +class LogCb(ctypes.c_void_p): + """Callback prototype for LibVLC log message handler. +\param data data pointer as given to L{libvlc_log_subscribe}() +\param level message level (@ref enum libvlc_log_level) +\param fmt printf() format string (as defined by ISO C11) +\param args variable argument list for the format +\note Log message handlers must be thread-safe. + """ + pass +class VideoUnlockCb(ctypes.c_void_p): + """Callback prototype to unlock a picture buffer. +When the video frame decoding is complete, the unlock callback is invoked. +This callback might not be needed at all. It is only an indication that the +application can now read the pixel values if it needs to. +\warning A picture buffer is unlocked after the picture is decoded, +but before the picture is displayed. +\param opaque private pointer as passed to libvlc_video_set_callbacks() [IN] +\param picture private pointer returned from the @ref libvlc_video_lock_cb + callback [IN] +\param planes pixel planes as defined by the @ref libvlc_video_lock_cb + callback (this parameter is only for convenience) [IN] + """ + pass +class VideoDisplayCb(ctypes.c_void_p): + """Callback prototype to display a picture. +When the video frame needs to be shown, as determined by the media playback +clock, the display callback is invoked. +\param opaque private pointer as passed to libvlc_video_set_callbacks() [IN] +\param picture private pointer returned from the @ref libvlc_video_lock_cb + callback [IN] + """ + pass +class VideoFormatCb(ctypes.c_void_p): + """Callback prototype to configure picture buffers format. +This callback gets the format of the video as output by the video decoder +and the chain of video filters (if any). It can opt to change any parameter +as it needs. In that case, LibVLC will attempt to convert the video format +(rescaling and chroma conversion) but these operations can be CPU intensive. +\param opaque pointer to the private pointer passed to + libvlc_video_set_callbacks() [IN/OUT] +\param chroma pointer to the 4 bytes video format identifier [IN/OUT] +\param width pointer to the pixel width [IN/OUT] +\param height pointer to the pixel height [IN/OUT] +\param pitches table of scanline pitches in bytes for each pixel plane + (the table is allocated by LibVLC) [OUT] +\param lines table of scanlines count for each plane [OUT] +\return the number of picture buffers allocated, 0 indicates failure +\note +For each pixels plane, the scanline pitch must be bigger than or equal to +the number of bytes per pixel multiplied by the pixel width. +Similarly, the number of scanlines must be bigger than of equal to +the pixel height. +Furthermore, we recommend that pitches and lines be multiple of 32 +to not break assumption that might be made by various optimizations +in the video decoders, video filters and/or video converters. + """ + pass +class VideoCleanupCb(ctypes.c_void_p): + """Callback prototype to configure picture buffers format. +\param opaque private pointer as passed to libvlc_video_set_callbacks() + (and possibly modified by @ref libvlc_video_format_cb) [IN] + """ + pass +class AudioPlayCb(ctypes.c_void_p): + """Callback prototype for audio playback. +\param data data pointer as passed to L{libvlc_audio_set_callbacks}() [IN] +\param samples pointer to the first audio sample to play back [IN] +\param count number of audio samples to play back +\param pts expected play time stamp (see libvlc_delay()) + """ + pass +class AudioPauseCb(ctypes.c_void_p): + """Callback prototype for audio pause. +\note The pause callback is never called if the audio is already paused. +\param data data pointer as passed to L{libvlc_audio_set_callbacks}() [IN] +\param pts time stamp of the pause request (should be elapsed already) + """ + pass +class AudioResumeCb(ctypes.c_void_p): + """Callback prototype for audio resumption (i.e. restart from pause). +\note The resume callback is never called if the audio is not paused. +\param data data pointer as passed to L{libvlc_audio_set_callbacks}() [IN] +\param pts time stamp of the resumption request (should be elapsed already) + """ + pass +class AudioFlushCb(ctypes.c_void_p): + """Callback prototype for audio buffer flush +(i.e. discard all pending buffers and stop playback as soon as possible). +\param data data pointer as passed to L{libvlc_audio_set_callbacks}() [IN] + """ + pass +class AudioDrainCb(ctypes.c_void_p): + """Callback prototype for audio buffer drain +(i.e. wait for pending buffers to be played). +\param data data pointer as passed to L{libvlc_audio_set_callbacks}() [IN] + """ + pass +class AudioSetVolumeCb(ctypes.c_void_p): + """Callback prototype for audio volume change. +\param data data pointer as passed to L{libvlc_audio_set_callbacks}() [IN] +\param volume software volume (1. = nominal, 0. = mute) +\param mute muted flag + """ + pass +class AudioSetupCb(ctypes.c_void_p): + """Callback prototype to setup the audio playback. +This is called when the media player needs to create a new audio output. +\param opaque pointer to the data pointer passed to + L{libvlc_audio_set_callbacks}() [IN/OUT] +\param format 4 bytes sample format [IN/OUT] +\param rate sample rate [IN/OUT] +\param channels channels count [IN/OUT] +\return 0 on success, anything else to skip audio playback + """ + pass +class AudioCleanupCb(ctypes.c_void_p): + """Callback prototype for audio playback cleanup. +This is called when the media player no longer needs an audio output. +\param opaque data pointer as passed to L{libvlc_audio_set_callbacks}() [IN] + """ + pass +class CallbackDecorators(object): + "Class holding various method decorators for callback functions." + Callback = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p) + Callback.__doc__ = '''Callback function notification +\param p_event the event triggering the callback + ''' + LogCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int, ctypes.c_char_p, ctypes.c_void_p) + LogCb.__doc__ = '''Callback prototype for LibVLC log message handler. +\param data data pointer as given to L{libvlc_log_subscribe}() +\param level message level (@ref enum libvlc_log_level) +\param fmt printf() format string (as defined by ISO C11) +\param args variable argument list for the format +\note Log message handlers must be thread-safe. + ''' + VideoUnlockCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ListPOINTER(ctypes.c_void_p)) + VideoUnlockCb.__doc__ = '''Callback prototype to unlock a picture buffer. +When the video frame decoding is complete, the unlock callback is invoked. +This callback might not be needed at all. It is only an indication that the +application can now read the pixel values if it needs to. +\warning A picture buffer is unlocked after the picture is decoded, +but before the picture is displayed. +\param opaque private pointer as passed to libvlc_video_set_callbacks() [IN] +\param picture private pointer returned from the @ref libvlc_video_lock_cb + callback [IN] +\param planes pixel planes as defined by the @ref libvlc_video_lock_cb + callback (this parameter is only for convenience) [IN] + ''' + VideoDisplayCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p) + VideoDisplayCb.__doc__ = '''Callback prototype to display a picture. +When the video frame needs to be shown, as determined by the media playback +clock, the display callback is invoked. +\param opaque private pointer as passed to libvlc_video_set_callbacks() [IN] +\param picture private pointer returned from the @ref libvlc_video_lock_cb + callback [IN] + ''' + VideoFormatCb = ctypes.CFUNCTYPE(ctypes.POINTER(ctypes.c_uint), ListPOINTER(ctypes.c_void_p), ctypes.c_char_p, ctypes.POINTER(ctypes.c_uint), ctypes.POINTER(ctypes.c_uint), ctypes.POINTER(ctypes.c_uint), ctypes.POINTER(ctypes.c_uint)) + VideoFormatCb.__doc__ = '''Callback prototype to configure picture buffers format. +This callback gets the format of the video as output by the video decoder +and the chain of video filters (if any). It can opt to change any parameter +as it needs. In that case, LibVLC will attempt to convert the video format +(rescaling and chroma conversion) but these operations can be CPU intensive. +\param opaque pointer to the private pointer passed to + libvlc_video_set_callbacks() [IN/OUT] +\param chroma pointer to the 4 bytes video format identifier [IN/OUT] +\param width pointer to the pixel width [IN/OUT] +\param height pointer to the pixel height [IN/OUT] +\param pitches table of scanline pitches in bytes for each pixel plane + (the table is allocated by LibVLC) [OUT] +\param lines table of scanlines count for each plane [OUT] +\return the number of picture buffers allocated, 0 indicates failure +\note +For each pixels plane, the scanline pitch must be bigger than or equal to +the number of bytes per pixel multiplied by the pixel width. +Similarly, the number of scanlines must be bigger than of equal to +the pixel height. +Furthermore, we recommend that pitches and lines be multiple of 32 +to not break assumption that might be made by various optimizations +in the video decoders, video filters and/or video converters. + ''' + VideoCleanupCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p) + VideoCleanupCb.__doc__ = '''Callback prototype to configure picture buffers format. +\param opaque private pointer as passed to libvlc_video_set_callbacks() + (and possibly modified by @ref libvlc_video_format_cb) [IN] + ''' + AudioPlayCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint, ctypes.c_int64) + AudioPlayCb.__doc__ = '''Callback prototype for audio playback. +\param data data pointer as passed to L{libvlc_audio_set_callbacks}() [IN] +\param samples pointer to the first audio sample to play back [IN] +\param count number of audio samples to play back +\param pts expected play time stamp (see libvlc_delay()) + ''' + AudioPauseCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int64) + AudioPauseCb.__doc__ = '''Callback prototype for audio pause. +\note The pause callback is never called if the audio is already paused. +\param data data pointer as passed to L{libvlc_audio_set_callbacks}() [IN] +\param pts time stamp of the pause request (should be elapsed already) + ''' + AudioResumeCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int64) + AudioResumeCb.__doc__ = '''Callback prototype for audio resumption (i.e. restart from pause). +\note The resume callback is never called if the audio is not paused. +\param data data pointer as passed to L{libvlc_audio_set_callbacks}() [IN] +\param pts time stamp of the resumption request (should be elapsed already) + ''' + AudioFlushCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int64) + AudioFlushCb.__doc__ = '''Callback prototype for audio buffer flush +(i.e. discard all pending buffers and stop playback as soon as possible). +\param data data pointer as passed to L{libvlc_audio_set_callbacks}() [IN] + ''' + AudioDrainCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p) + AudioDrainCb.__doc__ = '''Callback prototype for audio buffer drain +(i.e. wait for pending buffers to be played). +\param data data pointer as passed to L{libvlc_audio_set_callbacks}() [IN] + ''' + AudioSetVolumeCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ctypes.c_float, ctypes.c_bool) + AudioSetVolumeCb.__doc__ = '''Callback prototype for audio volume change. +\param data data pointer as passed to L{libvlc_audio_set_callbacks}() [IN] +\param volume software volume (1. = nominal, 0. = mute) +\param mute muted flag + ''' + AudioSetupCb = ctypes.CFUNCTYPE(ctypes.POINTER(ctypes.c_int), ListPOINTER(ctypes.c_void_p), ctypes.c_char_p, ctypes.POINTER(ctypes.c_uint), ctypes.POINTER(ctypes.c_uint)) + AudioSetupCb.__doc__ = '''Callback prototype to setup the audio playback. +This is called when the media player needs to create a new audio output. +\param opaque pointer to the data pointer passed to + L{libvlc_audio_set_callbacks}() [IN/OUT] +\param format 4 bytes sample format [IN/OUT] +\param rate sample rate [IN/OUT] +\param channels channels count [IN/OUT] +\return 0 on success, anything else to skip audio playback + ''' + AudioCleanupCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p) + AudioCleanupCb.__doc__ = '''Callback prototype for audio playback cleanup. +This is called when the media player no longer needs an audio output. +\param opaque data pointer as passed to L{libvlc_audio_set_callbacks}() [IN] + ''' +cb = CallbackDecorators # End of generated enum types # # From libvlc_structures.h @@ -905,10 +1178,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 +1193,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: @@ -995,12 +1268,6 @@ class Instance(_Ctype): ''' return libvlc_add_intf(self, name) - def wait(self): - '''Waits until an interface causes the instance to exit. - You should start at least one interface first, using L{add_intf}(). - ''' - return libvlc_wait(self) - def set_user_agent(self, name, http): '''Sets the application name. LibVLC passes this as the user agent string when a protocol requires it. @@ -1455,7 +1722,11 @@ class Media(_Ctype): This option will be used to determine how the media_player will read the media. This allows to use VLC's advanced reading/streaming options on a per-media basis. - The options are detailed in vlc --long-help, for instance "--sout-all". + The options are detailed in vlc --long-help, for instance + "--sout-all". Note that all options are not usable on medias: + specifically, due to architectural issues, video-related options + such as text renderer options cannot be set on a single media. They + must be set on the whole libvlc instance instead. @param ppsz_options: the options (as a string). ''' return libvlc_media_add_option(self, ppsz_options) @@ -1465,7 +1736,11 @@ class Media(_Ctype): This option will be used to determine how the media_player will read the media. This allows to use VLC's advanced reading/streaming options on a per-media basis. - The options are detailed in vlc --long-help, for instance "--sout-all". + The options are detailed in vlc --long-help, for instance + "--sout-all". Note that all options are not usable on medias: + specifically, due to architectural issues, video-related options + such as text renderer options cannot be set on a single media. They + must be set on the whole libvlc instance instead. @param ppsz_options: the options (as a string). @param i_flags: the flags for this option. ''' @@ -1608,7 +1883,7 @@ class Media(_Ctype): Note, you need to call L{parse}() or play the media at least once before calling this function. Not doing this will result in an empty array. - @param tracks: address to store an allocated array of Elementary Streams descriptions (must be freed by the caller). + @param tracks: address to store an allocated array of Elementary Streams descriptions (must be freed by the caller) [OUT]. @return: the number of Elementary Streams. ''' return libvlc_media_get_tracks_info(self) @@ -1718,7 +1993,7 @@ class MediaList(_Ctype): """Add media instance to media list. The L{lock} should be held upon entering this function. - @param p_md: a media instance or a MRL. + @param mrl: a media instance or a MRL. @return: 0 on success, -1 if the media list is read-only. """ if isinstance(mrl, basestring): @@ -2149,16 +2424,25 @@ class MediaPlayer(_Ctype): def video_set_format(self, chroma, width, height, pitch): '''Set decoded video chroma and dimensions. This only works in combination with libvlc_video_set_callbacks(), - and is mutually exclusive with libvlc_video_set_format_callbacks(). + and is mutually exclusive with L{video_set_format_callbacks}(). @param chroma: a four-characters string identifying the chroma (e.g. "RV32" or "YUYV"). @param width: pixel width. @param height: pixel height. @param pitch: line pitch (in bytes). @version: LibVLC 1.1.1 or later. - @bug: All pixel planes are expected to have the same pitch. To use the YCbCr color space with chrominance subsampling, consider using libvlc_video_set_format_callbacks() instead. + @bug: All pixel planes are expected to have the same pitch. To use the YCbCr color space with chrominance subsampling, consider using L{video_set_format_callbacks}() instead. ''' return libvlc_video_set_format(self, chroma, width, height, pitch) + def video_set_format_callbacks(self, setup, cleanup): + '''Set decoded video chroma and dimensions. This only works in combination with + libvlc_video_set_callbacks(). + @param setup: callback to select the video format (cannot be NULL). + @param cleanup: callback to release any allocated resources (or NULL). + @version: LibVLC 2.0.0 or later. + ''' + return libvlc_video_set_format_callbacks(self, setup, cleanup) + def set_nsobject(self, drawable): '''Set the NSView handler where the media player should render its video output. Use the vout called "macosx". @@ -2234,10 +2518,42 @@ class MediaPlayer(_Ctype): ''' return libvlc_media_player_get_hwnd(self) + def audio_set_callbacks(self, play, pause, resume, flush, drain, opaque): + '''Set callbacks and private data for decoded audio. + Use L{audio_set_format}() or L{audio_set_format_callbacks}() + to configure the decoded audio format. + @param play: callback to play audio samples (must not be NULL). + @param pause: callback to pause playback (or NULL to ignore). + @param resume: callback to resume playback (or NULL to ignore). + @param flush: callback to flush audio buffers (or NULL to ignore). + @param drain: callback to drain audio buffers (or NULL to ignore). + @param opaque: private pointer for the audio callbacks (as first parameter). + @version: LibVLC 2.0.0 or later. + ''' + return libvlc_audio_set_callbacks(self, play, pause, resume, flush, drain, opaque) + + def audio_set_volume_callback(self, set_volume): + '''Set callbacks and private data for decoded audio. + Use L{audio_set_format}() or L{audio_set_format_callbacks}() + to configure the decoded audio format. + @param set_volume: callback to apply audio volume, or NULL to apply volume in software. + @version: LibVLC 2.0.0 or later. + ''' + return libvlc_audio_set_volume_callback(self, set_volume) + + def audio_set_format_callbacks(self, setup, cleanup): + '''Set decoded audio format. This only works in combination with + L{audio_set_callbacks}(). + @param setup: callback to select the audio format (cannot be NULL). + @param cleanup: callback to release any allocated resources (or NULL). + @version: LibVLC 2.0.0 or later. + ''' + return libvlc_audio_set_format_callbacks(self, setup, cleanup) + def audio_set_format(self, format, rate, channels): '''Set decoded audio format. - This only works in combination with libvlc_audio_set_callbacks(), - and is mutually exclusive with libvlc_audio_set_format_callbacks(). + This only works in combination with L{audio_set_callbacks}(), + and is mutually exclusive with L{audio_set_format_callbacks}(). @param format: a four-characters string identifying the sample format (e.g. "S16N" or "FL32"). @param rate: sample rate (expressed in Hz). @param channels: channels count. @@ -2797,6 +3113,30 @@ def libvlc_clearerr(): None) return f() +def libvlc_vprinterr(fmt, ap): + '''Sets the LibVLC error status and message for the current thread. + Any previous error is overridden. + @param fmt: the format string. + @param ap: the arguments. + @return: a nul terminated string in any case. + ''' + f = _Cfunctions.get('libvlc_vprinterr', None) or \ + _Cfunction('libvlc_vprinterr', ((1,), (1,),), None, + ctypes.c_char_p, ctypes.c_char_p, ctypes.c_void_p) + return f(fmt, ap) + +def libvlc_printerr(fmt, args): + '''Sets the LibVLC error status and message for the current thread. + Any previous error is overridden. + @param fmt: the format string. + @param args: the arguments. + @return: a nul terminated string in any case. + ''' + f = _Cfunctions.get('libvlc_printerr', None) or \ + _Cfunction('libvlc_printerr', ((1,), (1,),), None, + ctypes.c_char_p, ctypes.c_char_p, ctypes.c_void_p) + return f(fmt, args) + def libvlc_new(argc, argv): '''Create and initialize a libvlc instance. This functions accept a list of "command line" arguments similar to the @@ -2842,16 +3182,6 @@ def libvlc_add_intf(p_instance, name): ctypes.c_int, Instance, ctypes.c_char_p) return f(p_instance, name) -def libvlc_wait(p_instance): - '''Waits until an interface causes the instance to exit. - You should start at least one interface first, using L{libvlc_add_intf}(). - @param p_instance: the instance. - ''' - f = _Cfunctions.get('libvlc_wait', None) or \ - _Cfunction('libvlc_wait', ((1,),), None, - None, Instance) - return f(p_instance) - def libvlc_set_user_agent(p_instance, name, http): '''Sets the application name. LibVLC passes this as the user agent string when a protocol requires it. @@ -2916,7 +3246,7 @@ def libvlc_event_attach(p_event_manager, i_event_type, f_callback, user_data): ''' f = _Cfunctions.get('libvlc_event_attach', None) or \ _Cfunction('libvlc_event_attach', ((1,), (1,), (1,), (1,),), None, - ctypes.c_int, EventManager, ctypes.c_uint, ctypes.c_void_p, ctypes.c_void_p) + ctypes.c_int, EventManager, ctypes.c_uint, Callback, ctypes.c_void_p) return f(p_event_manager, i_event_type, f_callback, user_data) def libvlc_event_detach(p_event_manager, i_event_type, f_callback, p_user_data): @@ -2928,7 +3258,7 @@ def libvlc_event_detach(p_event_manager, i_event_type, f_callback, p_user_data): ''' f = _Cfunctions.get('libvlc_event_detach', None) or \ _Cfunction('libvlc_event_detach', ((1,), (1,), (1,), (1,),), None, - None, EventManager, ctypes.c_uint, ctypes.c_void_p, ctypes.c_void_p) + None, EventManager, ctypes.c_uint, Callback, ctypes.c_void_p) return f(p_event_manager, i_event_type, f_callback, p_user_data) def libvlc_event_type_name(event_type): @@ -2940,6 +3270,45 @@ def libvlc_event_type_name(event_type): ctypes.c_char_p, ctypes.c_uint) return f(event_type) +def libvlc_log_subscribe(sub, cb, data): + '''Registers a logging callback to LibVLC. + This function is thread-safe. + @param sub: uninitialized subscriber structure. + @param cb: callback function pointer. + @param data: opaque data pointer for the callback function @note Some log messages (especially debug) are emitted by LibVLC while initializing, before any LibVLC instance even exists. Thus this function does not require a LibVLC instance parameter. @warning As a consequence of not depending on a LibVLC instance, all logging callbacks are shared by all LibVLC instances within the process / address space. This also enables log messages to be emitted by LibVLC components that are not specific to any given LibVLC instance. @warning Do not call this function from within a logging callback. It would trigger a dead lock. + @version: LibVLC 2.1.0 or later. + ''' + f = _Cfunctions.get('libvlc_log_subscribe', None) or \ + _Cfunction('libvlc_log_subscribe', ((1,), (1,), (1,),), None, + None, ctypes.c_void_p, LogCb, ctypes.c_void_p) + return f(sub, cb, data) + +def libvlc_log_subscribe_file(sub, stream): + '''Registers a logging callback to a file. + @param stream: FILE pointer opened for writing (the FILE pointer must remain valid until L{libvlc_log_unsubscribe}()). + @version: LibVLC 2.1.0 or later. + ''' + f = _Cfunctions.get('libvlc_log_subscribe_file', None) or \ + _Cfunction('libvlc_log_subscribe_file', ((1,), (1,),), None, + None, ctypes.c_void_p, FILE_ptr) + return f(sub, stream) + +def libvlc_log_unsubscribe(sub): + '''Deregisters a logging callback from LibVLC. + This function is thread-safe. + @note: After (and only after) L{libvlc_log_unsubscribe}() has returned, + LibVLC warrants that there are no more pending calls of the subscription + callback function. + @warning: Do not call this function from within a logging callback. + It would trigger a dead lock. + @param sub: initialized subscriber structure. + @version: LibVLC 2.1.0 or later. + ''' + f = _Cfunctions.get('libvlc_log_unsubscribe', None) or \ + _Cfunction('libvlc_log_unsubscribe', ((1,),), None, + None, ctypes.c_void_p) + return f(sub) + def libvlc_get_log_verbosity(p_instance): '''Always returns minus one. This function is only provided for backward compatibility. @@ -3158,7 +3527,11 @@ def libvlc_media_add_option(p_md, ppsz_options): This option will be used to determine how the media_player will read the media. This allows to use VLC's advanced reading/streaming options on a per-media basis. - The options are detailed in vlc --long-help, for instance "--sout-all". + The options are detailed in vlc --long-help, for instance + "--sout-all". Note that all options are not usable on medias: + specifically, due to architectural issues, video-related options + such as text renderer options cannot be set on a single media. They + must be set on the whole libvlc instance instead. @param p_md: the media descriptor. @param ppsz_options: the options (as a string). ''' @@ -3172,7 +3545,11 @@ def libvlc_media_add_option_flag(p_md, ppsz_options, i_flags): This option will be used to determine how the media_player will read the media. This allows to use VLC's advanced reading/streaming options on a per-media basis. - The options are detailed in vlc --long-help, for instance "--sout-all". + The options are detailed in vlc --long-help, for instance + "--sout-all". Note that all options are not usable on medias: + specifically, due to architectural issues, video-related options + such as text renderer options cannot be set on a single media. They + must be set on the whole libvlc instance instead. @param p_md: the media descriptor. @param ppsz_options: the options (as a string). @param i_flags: the flags for this option. @@ -3384,7 +3761,7 @@ def libvlc_media_get_tracks_info(p_md): before calling this function. Not doing this will result in an empty array. @param p_md: media descriptor object. - @param tracks: address to store an allocated array of Elementary Streams descriptions (must be freed by the caller). + @param tracks: address to store an allocated array of Elementary Streams descriptions (must be freed by the caller) [OUT]. @return: the number of Elementary Streams. ''' f = _Cfunctions.get('libvlc_media_get_tracks_info', None) or \ @@ -3958,20 +4335,33 @@ def libvlc_media_player_stop(p_mi): def libvlc_video_set_format(mp, chroma, width, height, pitch): '''Set decoded video chroma and dimensions. This only works in combination with libvlc_video_set_callbacks(), - and is mutually exclusive with libvlc_video_set_format_callbacks(). + and is mutually exclusive with L{libvlc_video_set_format_callbacks}(). @param mp: the media player. @param chroma: a four-characters string identifying the chroma (e.g. "RV32" or "YUYV"). @param width: pixel width. @param height: pixel height. @param pitch: line pitch (in bytes). @version: LibVLC 1.1.1 or later. - @bug: All pixel planes are expected to have the same pitch. To use the YCbCr color space with chrominance subsampling, consider using libvlc_video_set_format_callbacks() instead. + @bug: All pixel planes are expected to have the same pitch. To use the YCbCr color space with chrominance subsampling, consider using L{libvlc_video_set_format_callbacks}() instead. ''' f = _Cfunctions.get('libvlc_video_set_format', None) or \ _Cfunction('libvlc_video_set_format', ((1,), (1,), (1,), (1,), (1,),), None, None, MediaPlayer, ctypes.c_char_p, ctypes.c_uint, ctypes.c_uint, ctypes.c_uint) return f(mp, chroma, width, height, pitch) +def libvlc_video_set_format_callbacks(mp, setup, cleanup): + '''Set decoded video chroma and dimensions. This only works in combination with + libvlc_video_set_callbacks(). + @param mp: the media player. + @param setup: callback to select the video format (cannot be NULL). + @param cleanup: callback to release any allocated resources (or NULL). + @version: LibVLC 2.0.0 or later. + ''' + f = _Cfunctions.get('libvlc_video_set_format_callbacks', None) or \ + _Cfunction('libvlc_video_set_format_callbacks', ((1,), (1,), (1,),), None, + None, MediaPlayer, VideoFormatCb, VideoCleanupCb) + return f(mp, setup, cleanup) + def libvlc_media_player_set_nsobject(p_mi, drawable): '''Set the NSView handler where the media player should render its video output. Use the vout called "macosx". @@ -4087,10 +4477,54 @@ def libvlc_media_player_get_hwnd(p_mi): ctypes.c_void_p, MediaPlayer) return f(p_mi) +def libvlc_audio_set_callbacks(mp, play, pause, resume, flush, drain, opaque): + '''Set callbacks and private data for decoded audio. + Use L{libvlc_audio_set_format}() or L{libvlc_audio_set_format_callbacks}() + to configure the decoded audio format. + @param mp: the media player. + @param play: callback to play audio samples (must not be NULL). + @param pause: callback to pause playback (or NULL to ignore). + @param resume: callback to resume playback (or NULL to ignore). + @param flush: callback to flush audio buffers (or NULL to ignore). + @param drain: callback to drain audio buffers (or NULL to ignore). + @param opaque: private pointer for the audio callbacks (as first parameter). + @version: LibVLC 2.0.0 or later. + ''' + f = _Cfunctions.get('libvlc_audio_set_callbacks', None) or \ + _Cfunction('libvlc_audio_set_callbacks', ((1,), (1,), (1,), (1,), (1,), (1,), (1,),), None, + None, MediaPlayer, AudioPlayCb, AudioPauseCb, AudioResumeCb, AudioFlushCb, AudioDrainCb, ctypes.c_void_p) + return f(mp, play, pause, resume, flush, drain, opaque) + +def libvlc_audio_set_volume_callback(mp, set_volume): + '''Set callbacks and private data for decoded audio. + Use L{libvlc_audio_set_format}() or L{libvlc_audio_set_format_callbacks}() + to configure the decoded audio format. + @param mp: the media player. + @param set_volume: callback to apply audio volume, or NULL to apply volume in software. + @version: LibVLC 2.0.0 or later. + ''' + f = _Cfunctions.get('libvlc_audio_set_volume_callback', None) or \ + _Cfunction('libvlc_audio_set_volume_callback', ((1,), (1,),), None, + None, MediaPlayer, AudioSetVolumeCb) + return f(mp, set_volume) + +def libvlc_audio_set_format_callbacks(mp, setup, cleanup): + '''Set decoded audio format. This only works in combination with + L{libvlc_audio_set_callbacks}(). + @param mp: the media player. + @param setup: callback to select the audio format (cannot be NULL). + @param cleanup: callback to release any allocated resources (or NULL). + @version: LibVLC 2.0.0 or later. + ''' + f = _Cfunctions.get('libvlc_audio_set_format_callbacks', None) or \ + _Cfunction('libvlc_audio_set_format_callbacks', ((1,), (1,), (1,),), None, + None, MediaPlayer, AudioSetupCb, AudioCleanupCb) + return f(mp, setup, cleanup) + def libvlc_audio_set_format(mp, format, rate, channels): '''Set decoded audio format. - This only works in combination with libvlc_audio_set_callbacks(), - and is mutually exclusive with libvlc_audio_set_format_callbacks(). + This only works in combination with L{libvlc_audio_set_callbacks}(), + and is mutually exclusive with L{libvlc_audio_set_format_callbacks}(). @param mp: the media player. @param format: a four-characters string identifying the sample format (e.g. "S16N" or "FL32"). @param rate: sample rate (expressed in Hz). @@ -5394,17 +5828,11 @@ def libvlc_vlm_get_event_manager(p_instance): return f(p_instance) -# 8 function(s) blacklisted: -# libvlc_audio_set_callbacks -# libvlc_audio_set_format_callbacks -# libvlc_audio_set_volume_callback -# libvlc_printerr +# 2 function(s) blacklisted: # libvlc_set_exit_handler # libvlc_video_set_callbacks -# libvlc_video_set_format_callbacks -# libvlc_vprinterr -# 13 function(s) not wrapped as methods: +# 18 function(s) not wrapped as methods: # libvlc_audio_output_list_release # libvlc_clearerr # libvlc_clock @@ -5414,10 +5842,15 @@ def libvlc_vlm_get_event_manager(p_instance): # libvlc_get_changeset # libvlc_get_compiler # libvlc_get_version +# libvlc_log_subscribe +# libvlc_log_subscribe_file +# libvlc_log_unsubscribe # libvlc_module_description_list_release # libvlc_new +# libvlc_printerr # libvlc_track_description_list_release # libvlc_track_description_release +# libvlc_vprinterr # Start of footer.py # @@ -5595,7 +6028,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 6d26f3b34..2d98fb599 100644 --- a/openlp/core/ui/media/vlcplayer.py +++ b/openlp/core/ui/media/vlcplayer.py @@ -34,6 +34,7 @@ import sys from PyQt4 import QtCore, QtGui from openlp.core.lib import Receiver +from openlp.core.lib.settings import Settings from openlp.core.lib.mediaplayer import MediaPlayer from openlp.core.ui.media import MediaState @@ -62,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' ] @@ -114,7 +115,7 @@ class VlcPlayer(MediaPlayer): command_line_options = u'--no-video-title-show' if not display.hasAudio: command_line_options += u' --no-audio --no-video-title-show' - if QtCore.QSettings().value(u'advanced/hide mouse', + if Settings().value(u'advanced/hide mouse', QtCore.QVariant(False)).toBool() and \ display.controller.isLive: command_line_options += u' --mouse-hide-timeout=0' @@ -130,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 @@ -181,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/printserviceform.py b/openlp/core/ui/printserviceform.py index 943f8daff..65ee03b0a 100644 --- a/openlp/core/ui/printserviceform.py +++ b/openlp/core/ui/printserviceform.py @@ -33,6 +33,7 @@ from lxml import html from openlp.core.lib import translate, get_text_file_string, Receiver from openlp.core.lib.ui import UiStrings +from openlp.core.lib.settings import Settings from openlp.core.ui.printservicedialog import Ui_PrintServiceDialog, ZoomSize from openlp.core.utils import AppLocation @@ -120,7 +121,7 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog): self.zoom = 0 self.setupUi(self) # Load the settings for the dialog. - settings = QtCore.QSettings() + settings = Settings() settings.beginGroup(u'advanced') self.slideTextCheckBox.setChecked(settings.value( u'print slide text', QtCore.QVariant(False)).toBool()) @@ -318,7 +319,7 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog): elif display == ZoomSize.TwentyFive: self.previewWidget.fitToWidth() self.previewWidget.zoomIn(0.25) - settings = QtCore.QSettings() + settings = Settings() settings.beginGroup(u'advanced') settings.setValue(u'display size', QtCore.QVariant(display)) settings.endGroup() @@ -389,7 +390,7 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog): Save the settings and close the dialog. """ # Save the settings for this dialog. - settings = QtCore.QSettings() + settings = Settings() settings.beginGroup(u'advanced') settings.setValue(u'print slide text', QtCore.QVariant(self.slideTextCheckBox.isChecked())) diff --git a/openlp/core/ui/screen.py b/openlp/core/ui/screen.py index 72c88908e..1e81bae50 100644 --- a/openlp/core/ui/screen.py +++ b/openlp/core/ui/screen.py @@ -34,6 +34,7 @@ import copy from PyQt4 import QtCore from openlp.core.lib import Receiver, translate +from openlp.core.lib.settings import Settings log = logging.getLogger(__name__) @@ -245,7 +246,7 @@ class ScreenList(object): """ Loads the screen size and the monitor number from the settings. """ - settings = QtCore.QSettings() + settings = Settings() settings.beginGroup(u'general') self.set_current_display(settings.value(u'monitor', QtCore.QVariant(self.display_count - 1)).toInt()[0]) diff --git a/openlp/core/ui/serviceitemeditdialog.py b/openlp/core/ui/serviceitemeditdialog.py index 8410f4491..7a3bdf448 100644 --- a/openlp/core/ui/serviceitemeditdialog.py +++ b/openlp/core/ui/serviceitemeditdialog.py @@ -25,7 +25,7 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### -from PyQt4 import QtCore, QtGui +from PyQt4 import QtGui from openlp.core.lib import translate from openlp.core.lib.ui import create_button_box, create_button diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 30567f2d2..71d3d4eb4 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -40,6 +40,7 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import OpenLPToolbar, ServiceItem, Receiver, build_icon, \ ItemCapabilities, SettingsManager, translate, str_to_bool from openlp.core.lib.theme import ThemeLevel +from openlp.core.lib.settings import Settings from openlp.core.lib.ui import UiStrings, critical_error_message_box, \ create_widget_action, find_and_set_in_combo_box from openlp.core.ui import ServiceNoteForm, ServiceItemEditForm, StartTimeForm @@ -274,7 +275,7 @@ class ServiceManager(QtGui.QWidget): QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'service_item_update'), self.serviceItemUpdate) # Last little bits of setting up - self.service_theme = unicode(QtCore.QSettings().value( + self.service_theme = unicode(Settings().value( self.mainwindow.serviceManagerSettingsSection + u'/service theme', QtCore.QVariant(u'')).toString()) self.servicePath = AppLocation.get_section_data_path(u'servicemanager') @@ -352,7 +353,7 @@ class ServiceManager(QtGui.QWidget): self._fileName = unicode(fileName) self.mainwindow.setServiceModified(self.isModified(), self.shortFileName()) - QtCore.QSettings(). \ + Settings(). \ setValue(u'servicemanager/last file',QtCore.QVariant(fileName)) def fileName(self): @@ -371,7 +372,7 @@ class ServiceManager(QtGui.QWidget): """ Triggered when Config dialog is updated. """ - self.expandTabs = QtCore.QSettings().value( + self.expandTabs = Settings().value( u'advanced/expand service item', QtCore.QVariant(u'False')).toBool() @@ -444,7 +445,7 @@ class ServiceManager(QtGui.QWidget): self.setFileName(u'') self.serviceId += 1 self.setModified(False) - QtCore.QSettings(). \ + Settings(). \ setValue(u'servicemanager/last file',QtCore.QVariant(u'')) Receiver.send_message(u'servicemanager_new_service') @@ -580,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): @@ -591,17 +589,17 @@ class ServiceManager(QtGui.QWidget): Get a file name and then call :func:`ServiceManager.saveFile` to save the file. """ - default_service_enabled = QtCore.QSettings().value( + default_service_enabled = Settings().value( u'advanced/default service enabled', QtCore.QVariant(True)).toBool() if default_service_enabled: - service_day = QtCore.QSettings().value( + service_day = Settings().value( u'advanced/default service day', 7).toInt()[0] if service_day == 7: time = datetime.now() else: - service_hour = QtCore.QSettings().value( + service_hour = Settings().value( u'advanced/default service hour', 11).toInt()[0] - service_minute = QtCore.QSettings().value( + service_minute = Settings().value( u'advanced/default service minute', 0).toInt()[0] now = datetime.now() day_delta = service_day - now.weekday() @@ -609,7 +607,7 @@ class ServiceManager(QtGui.QWidget): day_delta += 7 time = now + timedelta(days=day_delta) time = time.replace(hour=service_hour, minute=service_minute) - default_pattern = unicode(QtCore.QSettings().value( + default_pattern = unicode(Settings().value( u'advanced/default service name', translate('OpenLP.AdvancedTab', 'Service %Y-%m-%d %H-%M', 'This may not contain any of the following characters: ' @@ -690,7 +688,7 @@ class ServiceManager(QtGui.QWidget): self.setFileName(fileName) self.mainwindow.addRecentFile(fileName) self.setModified(False) - QtCore.QSettings().setValue( + Settings().setValue( 'servicemanager/last file', QtCore.QVariant(fileName)) else: critical_error_message_box( @@ -732,7 +730,7 @@ class ServiceManager(QtGui.QWidget): service was last closed. Can be blank if there was no service present. """ - fileName = QtCore.QSettings(). \ + fileName = Settings(). \ value(u'servicemanager/last file',QtCore.QVariant(u'')).toString() if fileName: self.loadFile(fileName) @@ -1104,7 +1102,7 @@ class ServiceManager(QtGui.QWidget): log.debug(u'onThemeComboBoxSelected') self.service_theme = unicode(self.themeComboBox.currentText()) self.mainwindow.renderer.set_service_theme(self.service_theme) - QtCore.QSettings().setValue( + Settings().setValue( self.mainwindow.serviceManagerSettingsSection + u'/service theme', QtCore.QVariant(self.service_theme)) @@ -1285,7 +1283,7 @@ class ServiceManager(QtGui.QWidget): if self.serviceItems[item][u'service_item'].is_valid: self.mainwindow.liveController.addServiceManagerItem( self.serviceItems[item][u'service_item'], child) - if QtCore.QSettings().value( + if Settings().value( self.mainwindow.generalSettingsSection + u'/auto preview', QtCore.QVariant(False)).toBool(): item += 1 diff --git a/openlp/core/ui/servicenoteform.py b/openlp/core/ui/servicenoteform.py index fb173ee43..b115cfd40 100644 --- a/openlp/core/ui/servicenoteform.py +++ b/openlp/core/ui/servicenoteform.py @@ -25,7 +25,7 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### -from PyQt4 import QtCore, QtGui +from PyQt4 import QtGui from openlp.core.lib import translate, SpellTextEdit from openlp.core.lib.ui import create_button_box 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/shortcutlistform.py b/openlp/core/ui/shortcutlistform.py index 457525dc0..fb0e8585c 100644 --- a/openlp/core/ui/shortcutlistform.py +++ b/openlp/core/ui/shortcutlistform.py @@ -31,6 +31,7 @@ import re from PyQt4 import QtCore, QtGui from openlp.core.lib import Receiver +from openlp.core.lib.settings import Settings from openlp.core.utils import translate from openlp.core.utils.actions import ActionList from shortcutlistdialog import Ui_ShortcutListDialog @@ -337,7 +338,7 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog): Save the shortcuts. **Note**, that we do not have to load the shortcuts, as they are loaded in :class:`~openlp.core.utils.ActionList`. """ - settings = QtCore.QSettings() + settings = Settings() settings.beginGroup(u'shortcuts') for category in self.action_list.categories: # Check if the category is for internal use only. diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index f9e5656c5..2aae81888 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -35,6 +35,7 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import OpenLPToolbar, Receiver, ItemCapabilities, \ translate, build_icon, build_html, PluginManager, ServiceItem from openlp.core.lib.ui import UiStrings, create_action +from openlp.core.lib.settings import Settings from openlp.core.lib import SlideLimits, ServiceItemAction from openlp.core.ui import HideMode, MainDisplay, Display, ScreenList from openlp.core.utils.actions import ActionList, CategoryOrder @@ -237,7 +238,7 @@ class SlideController(Controller): text=UiStrings().PlaySlidesToEnd, icon=u':/media/media_time.png', checked=False, shortcuts=[], category=self.category, triggers=self.onPlaySlidesOnce) - if QtCore.QSettings().value(self.parent().generalSettingsSection + + if Settings().value(self.parent().generalSettingsSection + u'/enable slide loop', QtCore.QVariant(True)).toBool(): self.playSlidesMenu.setDefaultAction(self.playSlidesLoop) else: @@ -662,7 +663,7 @@ class SlideController(Controller): """ Updates the Slide Limits variable from the settings. """ - self.slide_limits = QtCore.QSettings().value( + self.slide_limits = Settings().value( self.parent().advancedSettingsSection + u'/slide limits', QtCore.QVariant(SlideLimits.End)).toInt()[0] @@ -692,7 +693,7 @@ class SlideController(Controller): self.playSlidesLoop.setChecked(False) self.playSlidesLoop.setIcon(build_icon(u':/media/media_time.png')) if item.is_text(): - if QtCore.QSettings().value( + if Settings().value( self.parent().songsSettingsSection + u'/display songbar', QtCore.QVariant(True)).toBool() and self.slideList: self.songMenu.show() @@ -813,11 +814,11 @@ class SlideController(Controller): QtCore.QObject.connect(action, QtCore.SIGNAL(u'triggered(bool)'), self.onTrackTriggered) - self.display.audioPlayer.repeat = QtCore.QSettings().value( + self.display.audioPlayer.repeat = Settings().value( self.parent().generalSettingsSection + \ u'/audio repeat list', QtCore.QVariant(False)).toBool() - if QtCore.QSettings().value( + if Settings().value( self.parent().generalSettingsSection + \ u'/audio start paused', QtCore.QVariant(True)).toBool(): @@ -863,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)) @@ -930,7 +931,7 @@ class SlideController(Controller): Allow the main display to blank the main display at startup time """ log.debug(u'mainDisplaySetBackground live = %s' % self.isLive) - display_type = QtCore.QSettings().value( + display_type = Settings().value( self.parent().generalSettingsSection + u'/screen blank', QtCore.QVariant(u'')).toString() if self.screens.which_screen(self.window()) != \ @@ -971,11 +972,11 @@ class SlideController(Controller): self.themeScreen.setChecked(False) self.desktopScreen.setChecked(False) if checked: - QtCore.QSettings().setValue( + Settings().setValue( self.parent().generalSettingsSection + u'/screen blank', QtCore.QVariant(u'blanked')) else: - QtCore.QSettings().remove( + Settings().remove( self.parent().generalSettingsSection + u'/screen blank') self.blankPlugin() self.updatePreview() @@ -992,11 +993,11 @@ class SlideController(Controller): self.themeScreen.setChecked(checked) self.desktopScreen.setChecked(False) if checked: - QtCore.QSettings().setValue( + Settings().setValue( self.parent().generalSettingsSection + u'/screen blank', QtCore.QVariant(u'themed')) else: - QtCore.QSettings().remove( + Settings().remove( self.parent().generalSettingsSection + u'/screen blank') self.blankPlugin() self.updatePreview() @@ -1013,11 +1014,11 @@ class SlideController(Controller): self.themeScreen.setChecked(False) self.desktopScreen.setChecked(checked) if checked: - QtCore.QSettings().setValue( + Settings().setValue( self.parent().generalSettingsSection + u'/screen blank', QtCore.QVariant(u'hidden')) else: - QtCore.QSettings().remove( + Settings().remove( self.parent().generalSettingsSection + u'/screen blank') self.hidePlugin(checked) self.updatePreview() @@ -1311,7 +1312,7 @@ class SlideController(Controller): """ triggered by clicking the Preview slide items """ - if QtCore.QSettings().value(u'advanced/double click live', + if Settings().value(u'advanced/double click live', QtCore.QVariant(False)).toBool(): # Live and Preview have issues if we have video or presentations # playing in both at the same time. diff --git a/openlp/core/ui/themelayoutdialog.py b/openlp/core/ui/themelayoutdialog.py index cab9d13fd..3aa9b0ede 100644 --- a/openlp/core/ui/themelayoutdialog.py +++ b/openlp/core/ui/themelayoutdialog.py @@ -25,7 +25,7 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### -from PyQt4 import QtCore, QtGui +from PyQt4 import QtGui from openlp.core.lib import translate from openlp.core.lib.ui import create_button_box diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index 09b54fc07..7ca56ce08 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -40,6 +40,7 @@ from openlp.core.lib import OpenLPToolbar, get_text_file_string, build_icon, \ check_directory_exists, create_thumb, validate_thumb from openlp.core.lib.theme import ThemeXML, BackgroundType, VerticalType, \ BackgroundGradientType +from openlp.core.lib.settings import Settings from openlp.core.lib.ui import UiStrings, critical_error_message_box, \ create_widget_action from openlp.core.theme import Theme @@ -164,7 +165,7 @@ class ThemeManager(QtGui.QWidget): """ Triggered when Config dialog is updated. """ - self.global_theme = unicode(QtCore.QSettings().value( + self.global_theme = unicode(Settings().value( self.settingsSection + u'/global theme', QtCore.QVariant(u'')).toString()) @@ -244,11 +245,10 @@ class ThemeManager(QtGui.QWidget): name = unicode(translate('OpenLP.ThemeManager', '%s (default)')) % self.global_theme self.themeListWidget.item(count).setText(name) - QtCore.QSettings().setValue( + 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): @@ -285,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): """ @@ -321,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())) @@ -332,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): """ @@ -349,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): """ @@ -449,7 +452,7 @@ class ThemeManager(QtGui.QWidget): theme = ThemeXML() theme.theme_name = UiStrings().Default self._writeTheme(theme, None, None) - QtCore.QSettings().setValue( + Settings().setValue( self.settingsSection + u'/global theme', QtCore.QVariant(theme.theme_name)) self.configUpdated() @@ -532,6 +535,7 @@ class ThemeManager(QtGui.QWidget): zip = None out_file = None file_xml = None + abort_import = True try: zip = zipfile.ZipFile(file_name) xml_file = filter(lambda name: @@ -768,7 +772,7 @@ class ThemeManager(QtGui.QWidget): Check to see if theme has been selected and the destructive action is allowed. """ - self.global_theme = unicode(QtCore.QSettings().value( + self.global_theme = unicode(Settings().value( self.settingsSection + u'/global theme', QtCore.QVariant(u'')).toString()) if check_item_selected(self.themeListWidget, select_text): diff --git a/openlp/core/ui/themestab.py b/openlp/core/ui/themestab.py index f763a9e74..825a71896 100644 --- a/openlp/core/ui/themestab.py +++ b/openlp/core/ui/themestab.py @@ -30,6 +30,7 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import SettingsTab, Receiver, translate from openlp.core.lib.theme import ThemeLevel from openlp.core.lib.ui import UiStrings, find_and_set_in_combo_box +from openlp.core.lib.settings import Settings class ThemesTab(SettingsTab): """ @@ -132,7 +133,7 @@ class ThemesTab(SettingsTab): 'any themes associated with either the service or the songs.')) def load(self): - settings = QtCore.QSettings() + settings = Settings() settings.beginGroup(self.settingsSection) self.theme_level = settings.value( u'theme level', ThemeLevel.Song).toInt()[0] @@ -146,13 +147,13 @@ class ThemesTab(SettingsTab): self.SongLevelRadioButton.setChecked(True) def save(self): - settings = QtCore.QSettings() + settings = Settings() settings.beginGroup(self.settingsSection) 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): @@ -169,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): @@ -183,14 +184,14 @@ class ThemesTab(SettingsTab): [u'Bible Theme', u'Song Theme'] """ # Reload as may have been triggered by the ThemeManager. - self.global_theme = unicode(QtCore.QSettings().value( + self.global_theme = unicode(Settings().value( self.settingsSection + u'/global theme', QtCore.QVariant(u'')).toString()) 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 f73237c06..4a1399fb7 100644 --- a/openlp/core/utils/__init__.py +++ b/openlp/core/utils/__init__.py @@ -36,6 +36,8 @@ from subprocess import Popen, PIPE import sys import urllib2 +from openlp.core.lib.settings import Settings + from PyQt4 import QtGui, QtCore if sys.platform != u'win32' and sys.platform != u'darwin': @@ -87,7 +89,7 @@ class AppLocation(object): VersionDir = 5 CacheDir = 6 LanguageDir = 7 - + # Base path where data/config/cache dir is located BaseDir = None @@ -126,8 +128,13 @@ class AppLocation(object): """ Return the path OpenLP stores all its data under. """ - path = AppLocation.get_directory(AppLocation.DataDir) - check_directory_exists(path) + # Check if we have a different data location. + if Settings().contains(u'advanced/data path'): + path = unicode(Settings().value( + u'advanced/data path').toString()) + else: + path = AppLocation.get_directory(AppLocation.DataDir) + check_directory_exists(path) return path @staticmethod @@ -280,7 +287,7 @@ def check_latest_version(current_version): """ version_string = current_version[u'full'] # set to prod in the distribution config file. - settings = QtCore.QSettings() + settings = Settings() settings.beginGroup(u'general') last_test = unicode(settings.value(u'last version test', QtCore.QVariant(datetime.now().date())).toString()) diff --git a/openlp/core/utils/actions.py b/openlp/core/utils/actions.py index 271a7c884..087da222c 100644 --- a/openlp/core/utils/actions.py +++ b/openlp/core/utils/actions.py @@ -30,6 +30,8 @@ by the shortcuts system. """ from PyQt4 import QtCore, QtGui +from openlp.core.lib.settings import Settings + class ActionCategory(object): """ The :class:`~openlp.core.utils.ActionCategory` class encapsulates a @@ -226,7 +228,7 @@ class ActionList(object): else: self.categories[category].actions.add(action, weight) # Load the shortcut from the config. - settings = QtCore.QSettings() + settings = Settings() settings.beginGroup(u'shortcuts') shortcuts = settings.value(action.objectName(), QtCore.QVariant(action.shortcuts())).toStringList() diff --git a/openlp/core/utils/languagemanager.py b/openlp/core/utils/languagemanager.py index 929105ad2..24899fd41 100644 --- a/openlp/core/utils/languagemanager.py +++ b/openlp/core/utils/languagemanager.py @@ -35,6 +35,7 @@ from PyQt4 import QtCore, QtGui from openlp.core.utils import AppLocation from openlp.core.lib import translate +from openlp.core.lib.settings import Settings log = logging.getLogger(__name__) @@ -101,7 +102,7 @@ class LanguageManager(object): """ Retrieve a saved language to use from settings """ - settings = QtCore.QSettings() + settings = Settings() language = unicode(settings.value( u'general/language', QtCore.QVariant(u'[en]')).toString()) log.info(u'Language file: \'%s\' Loaded from conf file' % language) @@ -133,7 +134,7 @@ class LanguageManager(object): language = unicode(qm_list[action_name]) if LanguageManager.auto_language: language = u'[%s]' % language - QtCore.QSettings().setValue( + Settings().setValue( u'general/language', QtCore.QVariant(language)) log.info(u'Language file: \'%s\' written to conf file' % language) if message: diff --git a/openlp/plugins/alerts/alertsplugin.py b/openlp/plugins/alerts/alertsplugin.py index ebaabb214..1a7fe565a 100644 --- a/openlp/plugins/alerts/alertsplugin.py +++ b/openlp/plugins/alerts/alertsplugin.py @@ -32,6 +32,7 @@ from PyQt4 import QtCore from openlp.core.lib import Plugin, StringContent, build_icon, translate from openlp.core.lib.db import Manager from openlp.core.lib.ui import create_action, UiStrings +from openlp.core.lib.settings import Settings from openlp.core.lib.theme import VerticalType from openlp.core.utils.actions import ActionList from openlp.plugins.alerts.lib import AlertsManager, AlertsTab @@ -160,7 +161,7 @@ class AlertsPlugin(Plugin): def toggleAlertsState(self): self.alertsActive = not self.alertsActive - QtCore.QSettings().setValue(self.settingsSection + u'/active', + Settings().setValue(self.settingsSection + u'/active', QtCore.QVariant(self.alertsActive)) def onAlertsTrigger(self): diff --git a/openlp/plugins/alerts/forms/alertdialog.py b/openlp/plugins/alerts/forms/alertdialog.py index b211a3aa4..f4144602f 100644 --- a/openlp/plugins/alerts/forms/alertdialog.py +++ b/openlp/plugins/alerts/forms/alertdialog.py @@ -25,7 +25,7 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### -from PyQt4 import QtCore, QtGui +from PyQt4 import QtGui from openlp.core.lib import build_icon, translate from openlp.core.lib.ui import create_button, create_button_box diff --git a/openlp/plugins/alerts/lib/alertstab.py b/openlp/plugins/alerts/lib/alertstab.py index 22346b0aa..6a323eb2a 100644 --- a/openlp/plugins/alerts/lib/alertstab.py +++ b/openlp/plugins/alerts/lib/alertstab.py @@ -30,6 +30,7 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import SettingsTab, translate, Receiver from openlp.core.ui import AlertLocation from openlp.core.lib.ui import UiStrings, create_valign_selection_widgets +from openlp.core.lib.settings import Settings class AlertsTab(SettingsTab): """ @@ -152,7 +153,7 @@ class AlertsTab(SettingsTab): self.updateDisplay() def load(self): - settings = QtCore.QSettings() + settings = Settings() settings.beginGroup(self.settingsSection) self.timeout = settings.value(u'timeout', QtCore.QVariant(5)).toInt()[0] self.font_color = unicode(settings.value( @@ -180,7 +181,7 @@ class AlertsTab(SettingsTab): self.changed = False def save(self): - settings = QtCore.QSettings() + settings = Settings() settings.beginGroup(self.settingsSection) # Check value has changed as no event handles this field if settings.value(u'location', QtCore.QVariant(1)).toInt()[0] != \ diff --git a/openlp/plugins/bibles/bibleplugin.py b/openlp/plugins/bibles/bibleplugin.py index 0dddab214..422e2deff 100644 --- a/openlp/plugins/bibles/bibleplugin.py +++ b/openlp/plugins/bibles/bibleplugin.py @@ -31,6 +31,7 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import Plugin, StringContent, build_icon, translate from openlp.core.lib.ui import create_action, UiStrings +from openlp.core.lib.settings import Settings from openlp.core.utils.actions import ActionList from openlp.plugins.bibles.lib import BibleManager, BiblesTab, BibleMediaItem from openlp.plugins.bibles.forms import BibleUpgradeForm @@ -62,8 +63,7 @@ class BiblePlugin(Plugin): # unicode(UiStrings().Export)) # Set to invisible until we can export bibles self.exportBibleItem.setVisible(False) - if self.manager.old_bible_databases: - self.toolsUpgradeItem.setVisible(True) + self.toolsUpgradeItem.setVisible(bool(self.manager.old_bible_databases)) def finalise(self): """ @@ -91,7 +91,7 @@ class BiblePlugin(Plugin): QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No)) == QtGui.QMessageBox.Yes: self.onToolsUpgradeItemTriggered() - settings = QtCore.QSettings() + settings = Settings() settings.beginGroup(self.settingsSection) if settings.contains(u'bookname language'): settings.setValue(u'book name language', settings.value( diff --git a/openlp/plugins/bibles/forms/bibleimportform.py b/openlp/plugins/bibles/forms/bibleimportform.py index cc50fdf9b..92911c22a 100644 --- a/openlp/plugins/bibles/forms/bibleimportform.py +++ b/openlp/plugins/bibles/forms/bibleimportform.py @@ -36,6 +36,7 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import Receiver, translate from openlp.core.lib.db import delete_database 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.core.utils import AppLocation from openlp.plugins.bibles.lib.manager import BibleFormat @@ -590,7 +591,7 @@ class BibleImportForm(OpenLPWizard): """ Set default values for the wizard pages. """ - settings = QtCore.QSettings() + settings = Settings() settings.beginGroup(self.plugin.settingsSection) self.restart() self.finishButton.setVisible(False) diff --git a/openlp/plugins/bibles/forms/bibleupgradeform.py b/openlp/plugins/bibles/forms/bibleupgradeform.py index 88bbd8c63..4013284e4 100644 --- a/openlp/plugins/bibles/forms/bibleupgradeform.py +++ b/openlp/plugins/bibles/forms/bibleupgradeform.py @@ -36,6 +36,7 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import Receiver, SettingsManager, translate, \ check_directory_exists 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.core.utils import AppLocation, delete_file from openlp.plugins.bibles.lib.db import BibleDB, BibleMeta, OldBibleDB, \ @@ -341,7 +342,7 @@ class BibleUpgradeForm(OpenLPWizard): Set default values for the wizard pages. """ log.debug(u'BibleUpgrade setDefaults') - settings = QtCore.QSettings() + settings = Settings() settings.beginGroup(self.plugin.settingsSection) self.stop_import_flag = False self.success.clear() diff --git a/openlp/plugins/bibles/forms/languagedialog.py b/openlp/plugins/bibles/forms/languagedialog.py index f3c831410..b4a5b4d6c 100644 --- a/openlp/plugins/bibles/forms/languagedialog.py +++ b/openlp/plugins/bibles/forms/languagedialog.py @@ -24,7 +24,7 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### -from PyQt4 import QtCore, QtGui +from PyQt4 import QtGui from openlp.core.lib import translate from openlp.core.lib.ui import create_button_box diff --git a/openlp/plugins/bibles/lib/__init__.py b/openlp/plugins/bibles/lib/__init__.py index 6cd8b8d8e..d81c0f770 100644 --- a/openlp/plugins/bibles/lib/__init__.py +++ b/openlp/plugins/bibles/lib/__init__.py @@ -31,9 +31,8 @@ plugin. import logging import re -from PyQt4 import QtCore - from openlp.core.lib import translate +from openlp.core.lib.settings import Settings from openlp.plugins.bibles.lib.db import BiblesResourcesDB log = logging.getLogger(__name__) @@ -185,7 +184,7 @@ def update_reference_separators(): ':|v|V|verse|verses;;-|to;;,|and;;end', 'Double-semicolon delimited separators for parsing references. ' 'Consult the developers for further information.')).split(u';;') - settings = QtCore.QSettings() + settings = Settings() settings.beginGroup(u'bibles') custom_separators = [ unicode(settings.value(u'verse separator').toString()), diff --git a/openlp/plugins/bibles/lib/biblestab.py b/openlp/plugins/bibles/lib/biblestab.py index c352a5faf..0b4de68d9 100644 --- a/openlp/plugins/bibles/lib/biblestab.py +++ b/openlp/plugins/bibles/lib/biblestab.py @@ -31,6 +31,7 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import Receiver, SettingsTab, translate from openlp.core.lib.ui import UiStrings, find_and_set_in_combo_box +from openlp.core.lib.settings import Settings from openlp.plugins.bibles.lib import LayoutStyle, DisplayStyle, \ update_reference_separators, get_reference_separator, LanguageSelection @@ -414,7 +415,7 @@ class BiblesTab(SettingsTab): self.getGreyTextPalette(True)) def load(self): - settings = QtCore.QSettings() + settings = Settings() settings.beginGroup(self.settingsSection) self.show_new_chapters = settings.value( u'display new chapter', QtCore.QVariant(False)).toBool() @@ -488,7 +489,7 @@ class BiblesTab(SettingsTab): settings.endGroup() def save(self): - settings = QtCore.QSettings() + settings = Settings() settings.beginGroup(self.settingsSection) settings.setValue(u'display new chapter', QtCore.QVariant(self.show_new_chapters)) 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/bibles/lib/db.py b/openlp/plugins/bibles/lib/db.py index 84a9f81e2..11edd4228 100644 --- a/openlp/plugins/bibles/lib/db.py +++ b/openlp/plugins/bibles/lib/db.py @@ -63,6 +63,7 @@ class Verse(BaseModel): """ pass + def init_schema(url): """ Setup a bible database connection and initialise the database schema. diff --git a/openlp/plugins/bibles/lib/http.py b/openlp/plugins/bibles/lib/http.py index fb79b26e1..3bf3e5397 100644 --- a/openlp/plugins/bibles/lib/http.py +++ b/openlp/plugins/bibles/lib/http.py @@ -43,6 +43,15 @@ from openlp.plugins.bibles.lib import SearchResults from openlp.plugins.bibles.lib.db import BibleDB, BiblesResourcesDB, \ Book +UGLY_CHARS = { + u'\u2014': u' - ', + u'\u2018': u'\'', + u'\u2019': u'\'', + u'\u201c': u'"', + u'\u201d': u'"', + u' ': u' ' +} + log = logging.getLogger(__name__) class BGExtract(object): @@ -54,6 +63,103 @@ class BGExtract(object): self.proxy_url = proxy_url socket.setdefaulttimeout(30) + def _remove_elements(self, parent, tag, class_=None): + """ + Remove a particular element from the BeautifulSoup tree. + + ``parent`` + The element from which items need to be removed. + + ``tag`` + A string of the tab type, e.g. "div" + + ``class_`` + An HTML class attribute for further qualification. + """ + if class_: + all_tags = parent.findAll(tag, class_) + else: + all_tags = parent.findAll(tag) + for element in all_tags: + element.extract() + + def _extract_verse(self, tag): + """ + Extract a verse (or part of a verse) from a tag. + + ``tag`` + The BeautifulSoup Tag element with the stuff we want. + """ + if isinstance(tag, NavigableString): + return None, unicode(tag) + elif tag.get('class') == 'versenum': + verse = unicode(tag.string)\ + .replace('[', '').replace(']', '').strip() + return verse, None + elif tag.get('class') == 'chapternum': + verse = '1' + return verse, None + else: + verse, text = None, '' + for child in tag.contents: + c_verse, c_text = self._extract_verse(child) + if c_verse: + verse = c_verse + if text and c_text: + text += c_text + elif c_text is not None: + text = c_text + return verse, text + + def _clean_soup(self, tag): + """ + Remove all the rubbish from the HTML page. + + ``tag`` + The base tag within which we want to remove stuff. + """ + self._remove_elements(tag, 'sup', 'crossreference') + self._remove_elements(tag, 'sup', 'footnote') + self._remove_elements(tag, 'div', 'footnotes') + self._remove_elements(tag, 'div', 'crossrefs') + self._remove_elements(tag, 'h3') + + def _extract_verses(self, tags): + """ + Extract all the verses from a pre-prepared list of HTML tags. + + ``tags`` + A list of BeautifulSoup Tag elements. + """ + verses = [] + tags = tags[::-1] + current_text = '' + for tag in tags: + verse, text = None, '' + for child in tag.contents: + c_verse, c_text = self._extract_verse(child) + if c_verse: + verse = c_verse + if text and c_text: + text += c_text + elif c_text is not None: + text = c_text + if not verse: + current_text = text + ' ' + current_text + else: + text += ' ' + current_text + current_text = '' + if text: + for old, new in UGLY_CHARS.iteritems(): + text = text.replace(old, new) + text = u' '.join(text.split()) + if verse and text: + verses.append((int(verse.strip()), text)) + verse_list = {} + for verse, text in verses[::-1]: + verse_list[verse] = text + return verse_list + def get_bible_chapter(self, version, book_name, chapter): """ Access and decode Bibles via the BibleGateway website. @@ -80,60 +186,9 @@ class BGExtract(object): if not soup: return None Receiver.send_message(u'openlp_process_events') - footnotes = soup.findAll(u'sup', u'footnote') - if footnotes: - for footnote in footnotes: - footnote.extract() - crossrefs = soup.findAll(u'sup', u'xref') - if crossrefs: - for crossref in crossrefs: - crossref.extract() - headings = soup.findAll(u'h5') - if headings: - for heading in headings: - heading.extract() - chapter_notes = soup.findAll('div', 'footnotes') - if chapter_notes: - log.debug('Found chapter notes') - for note in chapter_notes: - note.extract() - note_comments = soup.findAll(text=u'end of footnotes') - if note_comments: - for comment in note_comments: - comment.extract() - cleanup = [(re.compile('\s+'), lambda match: ' ')] - verses = BeautifulSoup(str(soup), markupMassage=cleanup) - verse_list = {} - # Cater for inconsistent mark up in the first verse of a chapter. - first_verse = verses.find(u'versenum') - if first_verse and first_verse.contents: - verse_list[1] = unicode(first_verse.contents[0]) - for verse in verses(u'sup', u'versenum'): - raw_verse_num = verse.next - clean_verse_num = 0 - # Not all verses exist in all translations and may or may not be - # represented by a verse number. If they are not fine, if they are - # it will probably be in a format that breaks int(). We will then - # have no idea what garbage may be sucked in to the verse text so - # if we do not get a clean int() then ignore the verse completely. - try: - clean_verse_num = int(str(raw_verse_num)) - except ValueError: - log.warn(u'Illegal verse number in %s %s %s:%s', - version, book_name, chapter, unicode(raw_verse_num)) - if clean_verse_num: - verse_text = raw_verse_num.next - part = raw_verse_num.next.next - while not (isinstance(part, Tag) and part.attrMap and - part.attrMap[u'class'] == u'versenum'): - # While we are still in the same verse grab all the text. - if isinstance(part, NavigableString): - verse_text = verse_text + part - if isinstance(part.next, Tag) and part.next.name == u'div': - # Run out of verses so stop. - break - part = part.next - verse_list[clean_verse_num] = unicode(verse_text) + div = soup.find('div', 'result-text-style-normal') + self._clean_soup(div) + verse_list = self._extract_verses(div.findAll('span', 'text')) if not verse_list: log.debug(u'No content found in the BibleGateway response.') send_error_message(u'parse') diff --git a/openlp/plugins/bibles/lib/manager.py b/openlp/plugins/bibles/lib/manager.py index bfba082af..6661fad47 100644 --- a/openlp/plugins/bibles/lib/manager.py +++ b/openlp/plugins/bibles/lib/manager.py @@ -32,6 +32,7 @@ from PyQt4 import QtCore from openlp.core.lib import Receiver, SettingsManager, translate from openlp.core.utils import AppLocation, delete_file +from openlp.core.lib.settings import Settings from openlp.plugins.bibles.lib import parse_reference, \ get_reference_separator, LanguageSelection from openlp.plugins.bibles.lib.db import BibleDB, BibleMeta @@ -126,7 +127,7 @@ class BibleManager(object): self.db_cache = None self.path = AppLocation.get_section_data_path(self.settingsSection) self.proxy_name = unicode( - QtCore.QSettings().value(self.settingsSection + u'/proxy name', + Settings().value(self.settingsSection + u'/proxy name', QtCore.QVariant(u'')).toString()) self.suffix = u'.sqlite' self.import_wizard = None @@ -378,7 +379,7 @@ class BibleManager(object): except (ValueError, TypeError): language_selection = LanguageSelection.Application if language_selection is None or language_selection == -1: - language_selection = QtCore.QSettings().value( + language_selection = Settings().value( self.settingsSection + u'/bookname language', QtCore.QVariant(0)).toInt()[0] return language_selection diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index 288e0e2de..dcabe360e 100644 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -33,6 +33,7 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import MediaManagerItem, Receiver, ItemCapabilities, \ translate, create_separated_list from openlp.core.lib.searchedit import SearchEdit +from openlp.core.lib.settings import Settings from openlp.core.lib.ui import UiStrings, set_case_insensitive_completer, \ create_horizontal_adjusting_combo_box, critical_error_message_box, \ find_and_set_in_combo_box, build_icon @@ -294,7 +295,7 @@ class BibleMediaItem(MediaManagerItem): def configUpdated(self): log.debug(u'configUpdated') - if QtCore.QSettings().value(self.settingsSection + u'/second bibles', + if Settings().value(self.settingsSection + u'/second bibles', QtCore.QVariant(True)).toBool(): self.advancedSecondLabel.setVisible(True) self.advancedSecondComboBox.setVisible(True) @@ -362,7 +363,7 @@ class BibleMediaItem(MediaManagerItem): translate('BiblesPlugin.MediaItem', 'Text Search'), translate('BiblesPlugin.MediaItem', 'Search Text...')) ]) - self.quickSearchEdit.setCurrentSearchType(QtCore.QSettings().value( + self.quickSearchEdit.setCurrentSearchType(Settings().value( u'%s/last search type' % self.settingsSection, QtCore.QVariant(BibleSearch.Reference)).toInt()[0]) self.configUpdated() @@ -386,7 +387,7 @@ class BibleMediaItem(MediaManagerItem): self.advancedVersionComboBox.addItems(bibles) self.advancedSecondComboBox.addItems(bibles) # set the default value - bible = QtCore.QSettings().value( + bible = Settings().value( self.settingsSection + u'/advanced bible', QtCore.QVariant(u'')).toString() if bible in bibles: @@ -394,7 +395,7 @@ class BibleMediaItem(MediaManagerItem): self.initialiseAdvancedBible(unicode(bible)) elif bibles: self.initialiseAdvancedBible(bibles[0]) - bible = QtCore.QSettings().value( + bible = Settings().value( self.settingsSection + u'/quick bible', QtCore.QVariant( self.quickVersionComboBox.currentText())).toString() find_and_set_in_combo_box(self.quickVersionComboBox, bible) @@ -417,7 +418,7 @@ class BibleMediaItem(MediaManagerItem): ``bible`` The bible to initialise (unicode). - + ``last_book_id`` The "book reference id" of the book which is choosen at the moment. (int) @@ -497,11 +498,11 @@ class BibleMediaItem(MediaManagerItem): """ log.debug(u'updateAutoCompleter') # Save the current search type to the configuration. - QtCore.QSettings().setValue(u'%s/last search type' % + Settings().setValue(u'%s/last search type' % self.settingsSection, QtCore.QVariant(self.quickSearchEdit.currentSearchType())) # Save the current bible to the configuration. - QtCore.QSettings().setValue(self.settingsSection + u'/quick bible', + Settings().setValue(self.settingsSection + u'/quick bible', QtCore.QVariant(self.quickVersionComboBox.currentText())) books = [] # We have to do a 'Reference Search'. @@ -596,7 +597,7 @@ class BibleMediaItem(MediaManagerItem): self.advancedStyleComboBox.setCurrentIndex(self.settings.layout_style) self.settings.layoutStyleComboBox.setCurrentIndex( self.settings.layout_style) - QtCore.QSettings().setValue( + Settings().setValue( self.settingsSection + u'/verse layout style', QtCore.QVariant(self.settings.layout_style)) @@ -605,12 +606,12 @@ class BibleMediaItem(MediaManagerItem): self.quickStyleComboBox.setCurrentIndex(self.settings.layout_style) self.settings.layoutStyleComboBox.setCurrentIndex( self.settings.layout_style) - QtCore.QSettings().setValue( + Settings().setValue( self.settingsSection + u'/verse layout style', QtCore.QVariant(self.settings.layout_style)) def onAdvancedVersionComboBox(self): - QtCore.QSettings().setValue(self.settingsSection + u'/advanced bible', + Settings().setValue(self.settingsSection + u'/advanced bible', QtCore.QVariant(self.advancedVersionComboBox.currentText())) self.initialiseAdvancedBible( unicode(self.advancedVersionComboBox.currentText()), @@ -975,7 +976,7 @@ class BibleMediaItem(MediaManagerItem): else: service_item.theme = self.settings.bible_theme for slide in raw_slides: - service_item.add_from_text(slide[:30], slide) + service_item.add_from_text(slide) return True def formatTitle(self, start_bitem, old_bitem): diff --git a/openlp/plugins/custom/forms/editcustomdialog.py b/openlp/plugins/custom/forms/editcustomdialog.py index bd4fb78fa..28edce0d8 100644 --- a/openlp/plugins/custom/forms/editcustomdialog.py +++ b/openlp/plugins/custom/forms/editcustomdialog.py @@ -25,7 +25,7 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### -from PyQt4 import QtCore, QtGui +from PyQt4 import QtGui from openlp.core.lib import build_icon, translate from openlp.core.lib.ui import UiStrings, create_button_box, create_button diff --git a/openlp/plugins/custom/forms/editcustomslidedialog.py b/openlp/plugins/custom/forms/editcustomslidedialog.py index 381917e8c..7bc00d4ca 100644 --- a/openlp/plugins/custom/forms/editcustomslidedialog.py +++ b/openlp/plugins/custom/forms/editcustomslidedialog.py @@ -25,7 +25,7 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### -from PyQt4 import QtCore, QtGui +from PyQt4 import QtGui from openlp.core.lib import translate, SpellTextEdit, build_icon from openlp.core.lib.ui import UiStrings, create_button, create_button_box diff --git a/openlp/plugins/custom/lib/customtab.py b/openlp/plugins/custom/lib/customtab.py index 0466cf696..bd6b854e4 100644 --- a/openlp/plugins/custom/lib/customtab.py +++ b/openlp/plugins/custom/lib/customtab.py @@ -28,6 +28,7 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import SettingsTab, translate +from openlp.core.lib.settings import Settings class CustomTab(SettingsTab): """ @@ -66,11 +67,11 @@ class CustomTab(SettingsTab): self.displayFooter = True def load(self): - self.displayFooter = QtCore.QSettings().value( + self.displayFooter = Settings().value( self.settingsSection + u'/display footer', QtCore.QVariant(True)).toBool() self.displayFooterCheckBox.setChecked(self.displayFooter) def save(self): - QtCore.QSettings().setValue(self.settingsSection + u'/display footer', + Settings().setValue(self.settingsSection + u'/display footer', QtCore.QVariant(self.displayFooter)) diff --git a/openlp/plugins/custom/lib/mediaitem.py b/openlp/plugins/custom/lib/mediaitem.py index af2f261ca..3e25fb6c5 100644 --- a/openlp/plugins/custom/lib/mediaitem.py +++ b/openlp/plugins/custom/lib/mediaitem.py @@ -34,6 +34,7 @@ from sqlalchemy.sql import or_, func from openlp.core.lib import MediaManagerItem, Receiver, ItemCapabilities, \ check_item_selected, translate from openlp.core.lib.ui import UiStrings +from openlp.core.lib.settings import Settings from openlp.plugins.custom.forms import EditCustomForm from openlp.plugins.custom.lib import CustomXMLParser from openlp.plugins.custom.lib.db import CustomSlide @@ -99,7 +100,7 @@ class CustomMediaItem(MediaManagerItem): ]) self.loadList(self.manager.get_all_objects( CustomSlide, order_by_ref=CustomSlide.title)) - self.searchTextEdit.setCurrentSearchType(QtCore.QSettings().value( + self.searchTextEdit.setCurrentSearchType(Settings().value( u'%s/last search type' % self.settingsSection, QtCore.QVariant(CustomSearch.Titles)).toInt()[0]) @@ -197,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) @@ -212,23 +210,22 @@ class CustomMediaItem(MediaManagerItem): theme = customSlide.theme_name if theme: service_item.theme = theme - customXML = CustomXMLParser(customSlide.text) - verseList = customXML.get_verses() - raw_slides = [verse[1] for verse in verseList] + custom_xml = CustomXMLParser(customSlide.text) + verse_list = custom_xml.get_verses() + raw_slides = [verse[1] for verse in verse_list] service_item.title = title for slide in raw_slides: - service_item.add_from_text(slide[:30], slide) - if QtCore.QSettings().value(self.settingsSection + u'/display footer', + service_item.add_from_text(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): # Save the current search type to the configuration. - QtCore.QSettings().setValue(u'%s/last search type' % + Settings().setValue(u'%s/last search type' % self.settingsSection, QtCore.QVariant(self.searchTextEdit.currentSearchType())) # Reload the list considering the new search type. diff --git a/openlp/plugins/images/imageplugin.py b/openlp/plugins/images/imageplugin.py index a72eab1af..e148f5625 100644 --- a/openlp/plugins/images/imageplugin.py +++ b/openlp/plugins/images/imageplugin.py @@ -31,6 +31,7 @@ import logging from openlp.core.lib import Plugin, StringContent, build_icon, translate, \ Receiver +from openlp.core.lib.settings import Settings from openlp.plugins.images.lib import ImageMediaItem, ImageTab log = logging.getLogger(__name__) @@ -94,6 +95,6 @@ class ImagePlugin(Plugin): image manager to require updates. Actual update is triggered by the last part of saving the config. """ - background = QtGui.QColor(QtCore.QSettings().value(self.settingsSection + background = QtGui.QColor(Settings().value(self.settingsSection + u'/background color', QtCore.QVariant(u'#000000'))) self.liveController.imageManager.updateImages(u'image', background) diff --git a/openlp/plugins/images/lib/imagetab.py b/openlp/plugins/images/lib/imagetab.py index a8188ace6..d80240005 100644 --- a/openlp/plugins/images/lib/imagetab.py +++ b/openlp/plugins/images/lib/imagetab.py @@ -28,6 +28,7 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import SettingsTab, translate, Receiver +from openlp.core.lib.settings import Settings class ImageTab(SettingsTab): """ @@ -82,7 +83,7 @@ class ImageTab(SettingsTab): u'background-color: %s' % self.bg_color) def load(self): - settings = QtCore.QSettings() + settings = Settings() settings.beginGroup(self.settingsSection) self.bg_color = unicode(settings.value( u'background color', QtCore.QVariant(u'#000000')).toString()) @@ -92,7 +93,7 @@ class ImageTab(SettingsTab): u'background-color: %s' % self.bg_color) def save(self): - settings = QtCore.QSettings() + settings = Settings() settings.beginGroup(self.settingsSection) settings.setValue(u'background color', QtCore.QVariant(self.bg_color)) settings.endGroup() diff --git a/openlp/plugins/images/lib/mediaitem.py b/openlp/plugins/images/lib/mediaitem.py index ffa67b95e..7e66ad9c0 100644 --- a/openlp/plugins/images/lib/mediaitem.py +++ b/openlp/plugins/images/lib/mediaitem.py @@ -35,6 +35,7 @@ from openlp.core.lib import MediaManagerItem, build_icon, ItemCapabilities, \ SettingsManager, translate, check_item_selected, check_directory_exists, \ Receiver, create_thumb, validate_thumb from openlp.core.lib.ui import UiStrings, critical_error_message_box +from openlp.core.lib.settings import Settings from openlp.core.utils import AppLocation, delete_file, get_images_filter log = logging.getLogger(__name__) @@ -151,7 +152,7 @@ class ImageMediaItem(MediaManagerItem): def generateSlideData(self, service_item, item=None, xmlVersion=False, remote=False): - background = QtGui.QColor(QtCore.QSettings().value(self.settingsSection + background = QtGui.QColor(Settings().value(self.settingsSection + u'/background color', QtCore.QVariant(u'#000000'))) if item: items = [item] @@ -220,7 +221,7 @@ class ImageMediaItem(MediaManagerItem): if check_item_selected(self.listView, translate('ImagePlugin.MediaItem', 'You must select an image to replace the background with.')): - background = QtGui.QColor(QtCore.QSettings().value( + background = QtGui.QColor(Settings().value( self.settingsSection + u'/background color', QtCore.QVariant(u'#000000'))) item = self.listView.selectedIndexes()[0] diff --git a/openlp/plugins/media/lib/mediatab.py b/openlp/plugins/media/lib/mediatab.py index c7ec1a61b..5a9543642 100644 --- a/openlp/plugins/media/lib/mediatab.py +++ b/openlp/plugins/media/lib/mediatab.py @@ -29,6 +29,7 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import SettingsTab, translate, Receiver from openlp.core.lib.ui import UiStrings, create_button +from openlp.core.lib.settings import Settings from openlp.core.ui.media import get_media_players, set_media_players class MediaQCheckBox(QtGui.QCheckBox): """ @@ -186,7 +187,7 @@ class MediaTab(SettingsTab): else: checkbox.setChecked(False) self.updatePlayerList() - self.overridePlayerCheckBox.setChecked(QtCore.QSettings().value( + self.overridePlayerCheckBox.setChecked(Settings().value( self.settingsSection + u'/override player', QtCore.QVariant(QtCore.Qt.Unchecked)).toInt()[0]) @@ -200,9 +201,9 @@ class MediaTab(SettingsTab): player_string_changed = True override_changed = True setting_key = self.settingsSection + u'/override player' - if QtCore.QSettings().value(setting_key).toInt()[0] != \ + if Settings().value(setting_key).toInt()[0] != \ self.overridePlayerCheckBox.checkState(): - QtCore.QSettings().setValue(setting_key, + Settings().setValue(setting_key, QtCore.QVariant(self.overridePlayerCheckBox.checkState())) override_changed = True if override_changed: diff --git a/openlp/plugins/media/mediaplugin.py b/openlp/plugins/media/mediaplugin.py index 13c636e83..08d7f1983 100644 --- a/openlp/plugins/media/mediaplugin.py +++ b/openlp/plugins/media/mediaplugin.py @@ -30,6 +30,7 @@ import logging from PyQt4 import QtCore from openlp.core.lib import Plugin, StringContent, build_icon, translate +from openlp.core.lib.settings import Settings from openlp.plugins.media.lib import MediaMediaItem, MediaTab log = logging.getLogger(__name__) @@ -126,7 +127,7 @@ class MediaPlugin(Plugin): we want to check if we have the old "Use Phonon" setting, and convert it to "enable Phonon" and "make it the first one in the list". """ - settings = QtCore.QSettings() + settings = Settings() settings.beginGroup(self.settingsSection) if settings.contains(u'use phonon'): log.info(u'Found old Phonon setting') diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index 68eea809b..b98c2d093 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -36,6 +36,7 @@ from openlp.core.lib import MediaManagerItem, build_icon, SettingsManager, \ validate_thumb from openlp.core.lib.ui import UiStrings, critical_error_message_box, \ create_horizontal_adjusting_combo_box +from openlp.core.lib.settings import Settings from openlp.plugins.presentations.lib import MessageListener log = logging.getLogger(__name__) @@ -149,7 +150,7 @@ class PresentationMediaItem(MediaManagerItem): if self.displayTypeComboBox.count() > 1: self.displayTypeComboBox.insertItem(0, self.Automatic) self.displayTypeComboBox.setCurrentIndex(0) - if QtCore.QSettings().value(self.settingsSection + u'/override app', + if Settings().value(self.settingsSection + u'/override app', QtCore.QVariant(QtCore.Qt.Unchecked)) == QtCore.Qt.Checked: self.presentationWidget.show() else: diff --git a/openlp/plugins/presentations/lib/presentationcontroller.py b/openlp/plugins/presentations/lib/presentationcontroller.py index 7702f19cd..3bda3e069 100644 --- a/openlp/plugins/presentations/lib/presentationcontroller.py +++ b/openlp/plugins/presentations/lib/presentationcontroller.py @@ -33,6 +33,7 @@ from PyQt4 import QtCore from openlp.core.lib import Receiver, check_directory_exists, create_thumb, \ validate_thumb +from openlp.core.lib.settings import Settings from openlp.core.utils import AppLocation log = logging.getLogger(__name__) @@ -392,7 +393,7 @@ class PresentationController(object): """ Return whether the controller is currently enabled """ - if QtCore.QSettings().value( + if Settings().value( self.settings_section + u'/' + self.name, QtCore.QVariant(QtCore.Qt.Checked)).toInt()[0] == \ QtCore.Qt.Checked: diff --git a/openlp/plugins/presentations/lib/presentationtab.py b/openlp/plugins/presentations/lib/presentationtab.py index 1b9fa04d9..312f2b55a 100644 --- a/openlp/plugins/presentations/lib/presentationtab.py +++ b/openlp/plugins/presentations/lib/presentationtab.py @@ -29,6 +29,7 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import Receiver, SettingsTab, translate from openlp.core.lib.ui import UiStrings +from openlp.core.lib.settings import Settings class PresentationTab(SettingsTab): """ @@ -102,10 +103,10 @@ class PresentationTab(SettingsTab): for key in self.controllers: controller = self.controllers[key] checkbox = self.PresenterCheckboxes[controller.name] - checkbox.setChecked(QtCore.QSettings().value( + checkbox.setChecked(Settings().value( self.settingsSection + u'/' + controller.name, QtCore.QVariant(QtCore.Qt.Checked)).toInt()[0]) - self.OverrideAppCheckBox.setChecked(QtCore.QSettings().value( + self.OverrideAppCheckBox.setChecked(Settings().value( self.settingsSection + u'/override app', QtCore.QVariant(QtCore.Qt.Unchecked)).toInt()[0]) @@ -123,19 +124,19 @@ class PresentationTab(SettingsTab): if controller.is_available(): checkbox = self.PresenterCheckboxes[controller.name] setting_key = self.settingsSection + u'/' + controller.name - if QtCore.QSettings().value(setting_key) != \ + if Settings().value(setting_key) != \ checkbox.checkState(): changed = True - QtCore.QSettings().setValue(setting_key, + Settings().setValue(setting_key, QtCore.QVariant(checkbox.checkState())) if checkbox.isChecked(): controller.start_process() else: controller.kill() setting_key = self.settingsSection + u'/override app' - if QtCore.QSettings().value(setting_key) != \ + if Settings().value(setting_key) != \ self.OverrideAppCheckBox.checkState(): - QtCore.QSettings().setValue(setting_key, + Settings().setValue(setting_key, QtCore.QVariant(self.OverrideAppCheckBox.checkState())) changed = True if changed: diff --git a/openlp/plugins/remotes/html/openlp.js b/openlp/plugins/remotes/html/openlp.js index e1a425860..470185a44 100644 --- a/openlp/plugins/remotes/html/openlp.js +++ b/openlp/plugins/remotes/html/openlp.js @@ -245,6 +245,9 @@ window.OpenLP = { } else { $.each(data.results.items, function (idx, value) { + if (typeof value[0] !== "number"){ + value[0] = OpenLP.escapeString(value[0]) + } ul.append($("
  • ").append($("").attr("href", "#options") .attr("data-rel", "dialog").attr("value", value[0]) .click(OpenLP.showOptions).text(value[1]))); diff --git a/openlp/plugins/remotes/lib/httpserver.py b/openlp/plugins/remotes/lib/httpserver.py index 43afebb41..4bae0a901 100644 --- a/openlp/plugins/remotes/lib/httpserver.py +++ b/openlp/plugins/remotes/lib/httpserver.py @@ -122,6 +122,7 @@ from PyQt4 import QtCore, QtNetwork from mako.template import Template from openlp.core.lib import Receiver, PluginStatus, StringContent +from openlp.core.lib.settings import Settings from openlp.core.utils import AppLocation, translate log = logging.getLogger(__name__) @@ -169,10 +170,10 @@ class HttpServer(object): clients. Listen out for socket connections. """ log.debug(u'Start TCP server') - port = QtCore.QSettings().value( + port = Settings().value( self.plugin.settingsSection + u'/port', QtCore.QVariant(4316)).toInt()[0] - address = QtCore.QSettings().value( + address = Settings().value( self.plugin.settingsSection + u'/ip address', QtCore.QVariant(u'0.0.0.0')).toString() self.server = QtNetwork.QTcpServer() @@ -404,7 +405,7 @@ class HttpConnection(object): u'slide': self.parent.current_slide or 0, u'item': self.parent.current_item._uuid \ if self.parent.current_item else u'', - u'twelve':QtCore.QSettings().value( + u'twelve':Settings().value( u'remotes/twelve hour', QtCore.QVariant(True)).toBool(), u'blank': self.parent.plugin.liveController.blankScreen.\ isChecked(), diff --git a/openlp/plugins/remotes/lib/remotetab.py b/openlp/plugins/remotes/lib/remotetab.py index bae2a7090..5130ee65e 100644 --- a/openlp/plugins/remotes/lib/remotetab.py +++ b/openlp/plugins/remotes/lib/remotetab.py @@ -28,6 +28,7 @@ from PyQt4 import QtCore, QtGui, QtNetwork from openlp.core.lib import SettingsTab, translate, Receiver +from openlp.core.lib.settings import Settings ZERO_URL = u'0.0.0.0' @@ -149,12 +150,12 @@ class RemoteTab(SettingsTab): def load(self): self.portSpinBox.setValue( - QtCore.QSettings().value(self.settingsSection + u'/port', + Settings().value(self.settingsSection + u'/port', QtCore.QVariant(4316)).toInt()[0]) self.addressEdit.setText( - QtCore.QSettings().value(self.settingsSection + u'/ip address', + Settings().value(self.settingsSection + u'/ip address', QtCore.QVariant(ZERO_URL)).toString()) - self.twelveHour = QtCore.QSettings().value( + self.twelveHour = Settings().value( self.settingsSection + u'/twelve hour', QtCore.QVariant(True)).toBool() self.twelveHourCheckBox.setChecked(self.twelveHour) @@ -162,16 +163,16 @@ class RemoteTab(SettingsTab): def save(self): changed = False - if QtCore.QSettings().value(self.settingsSection + u'/ip address', + if Settings().value(self.settingsSection + u'/ip address', QtCore.QVariant(ZERO_URL).toString() != self.addressEdit.text() or - QtCore.QSettings().value(self.settingsSection + u'/port', + Settings().value(self.settingsSection + u'/port', QtCore.QVariant(4316).toInt()[0]) != self.portSpinBox.value()): changed = True - QtCore.QSettings().setValue(self.settingsSection + u'/port', + Settings().setValue(self.settingsSection + u'/port', QtCore.QVariant(self.portSpinBox.value())) - QtCore.QSettings().setValue(self.settingsSection + u'/ip address', + Settings().setValue(self.settingsSection + u'/ip address', QtCore.QVariant(self.addressEdit.text())) - QtCore.QSettings().setValue(self.settingsSection + u'/twelve hour', + Settings().setValue(self.settingsSection + u'/twelve hour', QtCore.QVariant(self.twelveHour)) if changed: Receiver.send_message(u'remotes_config_updated') diff --git a/openlp/plugins/songs/forms/authorsdialog.py b/openlp/plugins/songs/forms/authorsdialog.py index f5cc69aa5..a7e798bf8 100644 --- a/openlp/plugins/songs/forms/authorsdialog.py +++ b/openlp/plugins/songs/forms/authorsdialog.py @@ -25,7 +25,7 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### -from PyQt4 import QtCore, QtGui +from PyQt4 import QtGui from openlp.core.lib import translate from openlp.core.lib.ui import create_button_box diff --git a/openlp/plugins/songs/forms/authorsform.py b/openlp/plugins/songs/forms/authorsform.py index 758f76ca8..de82b4dd7 100644 --- a/openlp/plugins/songs/forms/authorsform.py +++ b/openlp/plugins/songs/forms/authorsform.py @@ -57,19 +57,17 @@ class AuthorsForm(QtGui.QDialog, Ui_AuthorsDialog): self.firstNameEdit.setFocus() return QtGui.QDialog.exec_(self) - def onFirstNameEditTextEdited(self, text): + def onFirstNameEditTextEdited(self, display_name): if not self._autoDisplayName: return - display_name = text - if self.lastNameEdit.text() != u'': + if not self.lastNameEdit.text(): display_name = display_name + u' ' + self.lastNameEdit.text() self.displayEdit.setText(display_name) - def onLastNameEditTextEdited(self, text): + def onLastNameEditTextEdited(self, display_name): if not self._autoDisplayName: return - display_name = text - if self.firstNameEdit.text() != u'': + if not self.firstNameEdit.text(): display_name = self.firstNameEdit.text() + u' ' + display_name self.displayEdit.setText(display_name) diff --git a/openlp/plugins/songs/forms/editsongform.py b/openlp/plugins/songs/forms/editsongform.py index 195dda729..81f9006ea 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)) @@ -259,8 +256,8 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): verse_tags_translated = False if self.song.lyrics.startswith(u' u'1' and verse_tag not in multiple: diff --git a/openlp/plugins/songs/forms/songbookdialog.py b/openlp/plugins/songs/forms/songbookdialog.py index 5c62514ad..073151d1d 100644 --- a/openlp/plugins/songs/forms/songbookdialog.py +++ b/openlp/plugins/songs/forms/songbookdialog.py @@ -25,7 +25,7 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### -from PyQt4 import QtCore, QtGui +from PyQt4 import QtGui from openlp.core.lib import translate from openlp.core.lib.ui import create_button_box diff --git a/openlp/plugins/songs/forms/songimportform.py b/openlp/plugins/songs/forms/songimportform.py index 4bdabd1a2..e2d17137b 100644 --- a/openlp/plugins/songs/forms/songimportform.py +++ b/openlp/plugins/songs/forms/songimportform.py @@ -35,8 +35,9 @@ from PyQt4 import QtCore, QtGui 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__) @@ -65,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') @@ -196,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) @@ -245,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', @@ -363,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()) @@ -378,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() - QtCore.QSettings().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 = self.currentFormat + Settings().setValue(u'songs/last import type', + 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 @@ -525,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): """ @@ -730,28 +343,18 @@ class SongImportForm(OpenLPWizard): self.restart() self.finishButton.setVisible(False) self.cancelButton.setVisible(True) - last_import_type = QtCore.QSettings().value( + last_import_type = Settings().value( u'songs/last import type').toInt()[0] if last_import_type < 0 or \ 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) @@ -771,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) @@ -873,90 +407,145 @@ 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/forms/songmaintenanceform.py b/openlp/plugins/songs/forms/songmaintenanceform.py index 7ba49a102..22210f552 100644 --- a/openlp/plugins/songs/forms/songmaintenanceform.py +++ b/openlp/plugins/songs/forms/songmaintenanceform.py @@ -87,7 +87,15 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): QtCore.SIGNAL(u'currentRowChanged(int)'), self.onBooksListRowChanged) - def exec_(self): + def exec_(self, fromSongEdit=False): + """ + Show the dialog. + + ``fromSongEdit`` + Indicates if the maintenance dialog has been opened from song edit + or from the media manager. Defaults to **False**. + """ + self.fromSongEdit = fromSongEdit self.typeListWidget.setCurrentRow(0) self.resetAuthors() self.resetTopics() @@ -103,20 +111,20 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): else: return -1 - def _deleteItem(self, item_class, list_widget, reset_func, dlg_title, + def _deleteItem(self, itemClass, listWidget, resetFunc, dlgTitle, del_text, err_text): - item_id = self._getCurrentItemId(list_widget) + item_id = self._getCurrentItemId(listWidget) if item_id != -1: - item = self.manager.get_object(item_class, item_id) + item = self.manager.get_object(itemClass, item_id) if item and not item.songs: - if critical_error_message_box(dlg_title, del_text, self, + if critical_error_message_box(dlgTitle, del_text, self, True) == QtGui.QMessageBox.Yes: - self.manager.delete_object(item_class, item.id) - reset_func() + self.manager.delete_object(itemClass, item.id) + resetFunc() else: - critical_error_message_box(dlg_title, err_text) + critical_error_message_box(dlgTitle, err_text) else: - critical_error_message_box(dlg_title, UiStrings().NISs) + critical_error_message_box(dlgTitle, UiStrings().NISs) def resetAuthors(self): """ @@ -157,34 +165,34 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): book_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(book.id)) self.booksListWidget.addItem(book_name) - def checkAuthor(self, new_author, edit=False): + def checkAuthor(self, newAuthor, edit=False): """ Returns *False* if the given Author already exists, otherwise *True*. """ authors = self.manager.get_all_objects(Author, - and_(Author.first_name == new_author.first_name, - Author.last_name == new_author.last_name, - Author.display_name == new_author.display_name)) - return self.__checkObject(authors, new_author, edit) + and_(Author.first_name == newAuthor.first_name, + Author.last_name == newAuthor.last_name, + Author.display_name == newAuthor.display_name)) + return self.__checkObject(authors, newAuthor, edit) - def checkTopic(self, new_topic, edit=False): + def checkTopic(self, newTopic, edit=False): """ Returns *False* if the given Topic already exists, otherwise *True*. """ topics = self.manager.get_all_objects(Topic, - Topic.name == new_topic.name) - return self.__checkObject(topics, new_topic, edit) + Topic.name == newTopic.name) + return self.__checkObject(topics, newTopic, edit) - def checkBook(self, new_book, edit=False): + def checkBook(self, newBook, edit=False): """ Returns *False* if the given Topic already exists, otherwise *True*. """ books = self.manager.get_all_objects(Book, - and_(Book.name == new_book.name, - Book.publisher == new_book.publisher)) - return self.__checkObject(books, new_book, edit) + and_(Book.name == newBook.name, + Book.publisher == newBook.publisher)) + return self.__checkObject(books, newBook, edit) - def __checkObject(self, objects, new_object, edit): + def __checkObject(self, objects, newObject, edit): """ Utility method to check for an existing object. @@ -196,7 +204,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): # not return False when nothing has changed. if edit: for object in objects: - if object.id != new_object.id: + if object.id != newObject.id: return False return True else: @@ -275,7 +283,8 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): if self.checkAuthor(author, True): if self.manager.save_object(author): self.resetAuthors() - Receiver.send_message(u'songs_load_list') + if not self.fromSongEdit: + Receiver.send_message(u'songs_load_list') else: critical_error_message_box( message=translate('SongsPlugin.SongMaintenanceForm', @@ -373,75 +382,76 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): Receiver.send_message(u'cursor_busy') merge(dbObject) reset() - Receiver.send_message(u'songs_load_list') + if not self.fromSongEdit: + Receiver.send_message(u'songs_load_list') Receiver.send_message(u'cursor_normal') - def mergeAuthors(self, old_author): + def mergeAuthors(self, oldAuthor): """ Merges two authors into one author. - ``old_author`` + ``oldAuthor`` The object, which was edited, that will be deleted """ # Find the duplicate. existing_author = self.manager.get_object_filtered(Author, - and_(Author.first_name == old_author.first_name, - Author.last_name == old_author.last_name, - Author.display_name == old_author.display_name, - Author.id != old_author.id)) - # Find the songs, which have the old_author as author. + and_(Author.first_name == oldAuthor.first_name, + Author.last_name == oldAuthor.last_name, + Author.display_name == oldAuthor.display_name, + Author.id != oldAuthor.id)) + # Find the songs, which have the oldAuthor as author. songs = self.manager.get_all_objects(Song, - Song.authors.contains(old_author)) + Song.authors.contains(oldAuthor)) for song in songs: # We check if the song has already existing_author as author. If # that is not the case we add it. if existing_author not in song.authors: song.authors.append(existing_author) - song.authors.remove(old_author) + song.authors.remove(oldAuthor) self.manager.save_object(song) - self.manager.delete_object(Author, old_author.id) + self.manager.delete_object(Author, oldAuthor.id) - def mergeTopics(self, old_topic): + def mergeTopics(self, oldTopic): """ Merges two topics into one topic. - ``old_topic`` + ``oldTopic`` The object, which was edited, that will be deleted """ # Find the duplicate. existing_topic = self.manager.get_object_filtered(Topic, - and_(Topic.name == old_topic.name, Topic.id != old_topic.id)) - # Find the songs, which have the old_topic as topic. + and_(Topic.name == oldTopic.name, Topic.id != oldTopic.id)) + # Find the songs, which have the oldTopic as topic. songs = self.manager.get_all_objects(Song, - Song.topics.contains(old_topic)) + Song.topics.contains(oldTopic)) for song in songs: # We check if the song has already existing_topic as topic. If that # is not the case we add it. if existing_topic not in song.topics: song.topics.append(existing_topic) - song.topics.remove(old_topic) + song.topics.remove(oldTopic) self.manager.save_object(song) - self.manager.delete_object(Topic, old_topic.id) + self.manager.delete_object(Topic, oldTopic.id) - def mergeBooks(self, old_book): + def mergeBooks(self, oldBook): """ Merges two books into one book. - ``old_book`` + ``oldBook`` The object, which was edited, that will be deleted """ # Find the duplicate. existing_book = self.manager.get_object_filtered(Book, - and_(Book.name == old_book.name, - Book.publisher == old_book.publisher, - Book.id != old_book.id)) - # Find the songs, which have the old_book as book. + and_(Book.name == oldBook.name, + Book.publisher == oldBook.publisher, + Book.id != oldBook.id)) + # Find the songs, which have the oldBook as book. songs = self.manager.get_all_objects(Song, - Song.song_book_id == old_book.id) + Song.song_book_id == oldBook.id) for song in songs: song.song_book_id = existing_book.id self.manager.save_object(song) - self.manager.delete_object(Book, old_book.id) + self.manager.delete_object(Book, oldBook.id) def onAuthorDeleteButtonClicked(self): """ diff --git a/openlp/plugins/songs/forms/topicsdialog.py b/openlp/plugins/songs/forms/topicsdialog.py index edd8df9f9..a609478fc 100644 --- a/openlp/plugins/songs/forms/topicsdialog.py +++ b/openlp/plugins/songs/forms/topicsdialog.py @@ -25,7 +25,7 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### -from PyQt4 import QtCore, QtGui +from PyQt4 import QtGui from openlp.core.lib import translate from openlp.core.lib.ui import create_button_box diff --git a/openlp/plugins/songs/lib/__init__.py b/openlp/plugins/songs/lib/__init__.py index 44cf8e113..87540ce54 100644 --- a/openlp/plugins/songs/lib/__init__.py +++ b/openlp/plugins/songs/lib/__init__.py @@ -193,6 +193,7 @@ class VerseType(object): return default return verse_index + def retrieve_windows_encoding(recommendation=None): """ Determines which encoding to use on an information source. The process uses @@ -252,12 +253,14 @@ def retrieve_windows_encoding(recommendation=None): return None return filter(lambda item: item[1] == choice[0], encodings)[0][0] + def clean_string(string): """ Strips punctuation from the passed string to assist searching """ return WHITESPACE.sub(u' ', APOSTROPHE.sub(u'', string)).lower() + def clean_title(title): """ Cleans the song title by removing Unicode control chars groups C0 & C1, @@ -265,6 +268,7 @@ def clean_title(title): """ return CONTROL_CHARS.sub(u'', title).rstrip() + def clean_song(manager, song): """ Cleans the search title, rebuilds the search lyrics, adds a default author diff --git a/openlp/plugins/songs/lib/importer.py b/openlp/plugins/songs/lib/importer.py index 16d943a73..d60130f62 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,69 @@ 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 +145,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 +312,7 @@ class SongFormat(object): SongFormat.OpenLP1, SongFormat.Generic, SongFormat.CCLI, - SongFormat.DreamBeam, + SongFormat.DreamBeam, SongFormat.EasySlides, SongFormat.EasyWorship, SongFormat.FoilPresenter, @@ -147,26 +321,56 @@ class SongFormat(object): SongFormat.SongBeamer, SongFormat.SongShowPlus, SongFormat.SongsOfFellowship, - SongFormat.WordsOfWorship + SongFormat.WordsOfWorship, + SongFormat.ZionWorx ] @staticmethod - def set_availability(format, available): + def get(format, *attributes): """ - Set the availability for a given song format. + Return requested song format attribute(s). + + ``format`` + A song format from SongFormat. + + ``*attributes`` + Zero or more song format attributes from SongFormat. + + Return type depends on number of supplied attributes: + + :0: Return dict containing all defined attributes for the format. + :1: Return the attribute value. + :>1: Return tuple of requested attribute values. """ - SongFormat._format_availability[format] = available + if not attributes: + return SongFormat.__attributes__.get(format) + elif len(attributes) == 1: + default = SongFormat.__defaults__.get(attributes[0]) + return SongFormat.__attributes__[format].get(attributes[0], + default) + else: + values = [] + for attr in attributes: + default = SongFormat.__defaults__.get(attr) + values.append(SongFormat.__attributes__[format].get(attr, + default)) + return tuple(values) @staticmethod - def get_availability(format): + def set(format, attribute, value): """ - Return the availability of a given song format. + Set specified song format attribute to the supplied value. """ - return SongFormat._format_availability.get(format, True) + SongFormat.__attributes__[format][attribute] = value -SongFormat.set_availability(SongFormat.OpenLP1, HAS_OPENLP1) -SongFormat.set_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/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py index 51158a954..0999ee763 100644 --- a/openlp/plugins/songs/lib/mediaitem.py +++ b/openlp/plugins/songs/lib/mediaitem.py @@ -37,6 +37,7 @@ from sqlalchemy.sql import or_ from openlp.core.lib import MediaManagerItem, Receiver, ItemCapabilities, \ translate, check_item_selected, PluginStatus, create_separated_list from openlp.core.lib.ui import UiStrings, create_widget_action +from openlp.core.lib.settings import Settings from openlp.core.utils import AppLocation from openlp.plugins.songs.forms import EditSongForm, SongMaintenanceForm, \ SongImportForm, SongExportForm @@ -131,13 +132,13 @@ class SongMediaItem(MediaManagerItem): self.searchTextEdit.setFocus() def configUpdated(self): - self.searchAsYouType = QtCore.QSettings().value( + self.searchAsYouType = Settings().value( self.settingsSection + u'/search as type', QtCore.QVariant(u'False')).toBool() - self.updateServiceOnEdit = QtCore.QSettings().value( + self.updateServiceOnEdit = Settings().value( self.settingsSection + u'/update service on edit', QtCore.QVariant(u'False')).toBool() - self.addSongFromService = QtCore.QSettings().value( + self.addSongFromService = Settings().value( self.settingsSection + u'/add song from service', QtCore.QVariant(u'True')).toBool() @@ -168,14 +169,14 @@ class SongMediaItem(MediaManagerItem): (SongSearch.Themes, u':/slides/slide_theme.png', UiStrings().Themes, UiStrings().SearchThemes) ]) - self.searchTextEdit.setCurrentSearchType(QtCore.QSettings().value( + self.searchTextEdit.setCurrentSearchType(Settings().value( u'%s/last search type' % self.settingsSection, QtCore.QVariant(SongSearch.Entire)).toInt()[0]) self.configUpdated() def onSearchTextButtonClicked(self): # Save the current search type to the configuration. - QtCore.QSettings().setValue(u'%s/last search type' % + Settings().setValue(u'%s/last search type' % self.settingsSection, QtCore.QVariant(self.searchTextEdit.currentSearchType())) # Reload the list considering the new search type. @@ -464,14 +465,14 @@ class SongMediaItem(MediaManagerItem): service_item.theme = song.theme_name service_item.edit_id = item_id if song.lyrics.startswith(u'