diff --git a/openlp/core/lib/db.py b/openlp/core/lib/db.py index fb8a38b1f..2e5d011cf 100644 --- a/openlp/core/lib/db.py +++ b/openlp/core/lib/db.py @@ -73,7 +73,17 @@ def upgrade_db(url, upgrade): The python module that contains the upgrade instructions. """ session, metadata = init_db(url) - tables = upgrade.upgrade_setup(metadata) + + class Metadata(BaseModel): + """ + Provides a class for the metadata table. + """ + pass + load_changes = True + try: + tables = upgrade.upgrade_setup(metadata) + except SQLAlchemyError, DBAPIError: + load_changes = False metadata_table = Table(u'metadata', metadata, Column(u'key', types.Unicode(64), primary_key=True), Column(u'value', types.UnicodeText(), default=None) @@ -89,20 +99,26 @@ def upgrade_db(url, upgrade): if version > upgrade.__version__: return version, upgrade.__version__ version += 1 - while hasattr(upgrade, u'upgrade_%d' % version): - log.debug(u'Running upgrade_%d', version) - try: - getattr(upgrade, u'upgrade_%d' % version)(session, metadata, tables) - version_meta.value = unicode(version) - except SQLAlchemyError, DBAPIError: - log.exception(u'Could not run database upgrade script "upgrade_%s"'\ - ', upgrade process has been halted.', version) - break - version += 1 + if load_changes: + while hasattr(upgrade, u'upgrade_%d' % version): + log.debug(u'Running upgrade_%d', version) + try: + getattr(upgrade, u'upgrade_%d' % version) \ + (session, metadata, tables) + version_meta.value = unicode(version) + except SQLAlchemyError, DBAPIError: + log.exception(u'Could not run database upgrade script ' + '"upgrade_%s", upgrade process has been halted.', version) + break + version += 1 + else: + version_meta = Metadata.populate(key=u'version', + value=int(upgrade.__version__)) session.add(version_meta) session.commit() return int(version_meta.value), upgrade.__version__ + def delete_database(plugin_name, db_file_name=None): """ Remove a database file from the system. @@ -138,14 +154,6 @@ class BaseModel(object): instance.__setattr__(key, value) return instance - -class Metadata(BaseModel): - """ - Provides a class for the metadata table. - """ - pass - - class Manager(object): """ Provide generic object persistence management diff --git a/openlp/core/lib/renderer.py b/openlp/core/lib/renderer.py index 9ea9c8094..8c63facb8 100644 --- a/openlp/core/lib/renderer.py +++ b/openlp/core/lib/renderer.py @@ -228,15 +228,56 @@ class Renderer(object): # Clean up line endings. lines = self._lines_split(text) pages = self._paginate_slide(lines, line_end) - if len(pages) > 1: - # Songs and Custom - if item.is_capable(ItemCapabilities.AllowsVirtualSplit): - # Do not forget the line breaks! - slides = text.split(u'[---]') - pages = [] - for slide in slides: - lines = slide.strip(u'\n').split(u'\n') + # Songs and Custom + if item.is_capable(ItemCapabilities.AllowsVirtualSplit) and \ + len(pages) > 1 and u'[---]' in text: + pages = [] + while True: + # Check if the first two potential virtual slides will fit + # (as a whole) on one slide. + html_text = expand_tags( + u'\n'.join(text.split(u'\n[---]\n', 2)[:-1])) + html_text = html_text.replace(u'\n', u'
') + if self._text_fits_on_slide(html_text): + # The first two virtual slides fit (as a whole) on one + # slide. Replace the occurrences of [---]. + text = text.replace(u'\n[---]', u'', 2) + else: + # The first two virtual slides did not fit as a whole. + # Check if the first virtual slide will fit. + html_text = expand_tags(text.split(u'\n[---]\n', 1)[1]) + html_text = html_text.replace(u'\n', u'
') + if self._text_fits_on_slide(html_text): + # The first virtual slide fits, so remove it. + text = text.replace(u'\n[---]', u'', 1) + else: + # The first virtual slide does not fit, which means + # we have to render the first virtual slide. + text_contains_break = u'[---]' in text + if text_contains_break: + html_text, text = text.split(u'\n[---]\n', 1) + else: + html_text = text + text = u'' + lines = expand_tags(html_text) + lines = lines.strip(u'\n').split(u'\n') + slides = self._paginate_slide(lines, line_end) + if len(slides) > 1 and text: + # Add all slides apart from the last one the + # list. + pages.extend(slides[:-1]) + if text_contains_break: + text = slides[-1] + u'\n[---]\n' + text + else: + text = slides[-1] + u'\n'+ text + text = text.replace(u'
', u'\n') + else: + pages.extend(slides) + if u'[---]' not in text: + lines = expand_tags(text) + lines = lines.strip(u'\n').split(u'\n') pages.extend(self._paginate_slide(lines, line_end)) + break new_pages = [] for page in pages: while page.endswith(u'
'): @@ -342,7 +383,7 @@ class Renderer(object): separator = u'
' html_lines = map(expand_tags, lines) # Text too long so go to next page. - if self._text_fits_on_slide(separator.join(html_lines)): + if not self._text_fits_on_slide(separator.join(html_lines)): html_text, previous_raw = self._binary_chop(formatted, previous_html, previous_raw, html_lines, lines, separator, u'') else: @@ -375,18 +416,18 @@ class Renderer(object): line = line.strip() html_line = expand_tags(line) # Text too long so go to next page. - if self._text_fits_on_slide(previous_html + html_line): + if not self._text_fits_on_slide(previous_html + html_line): # Check if there was a verse before the current one and append # it, when it fits on the page. if previous_html: - if not self._text_fits_on_slide(previous_html): + if self._text_fits_on_slide(previous_html): formatted.append(previous_raw) previous_html = u'' previous_raw = u'' # Now check if the current verse will fit, if it does # not we have to start to process the verse word by # word. - if not self._text_fits_on_slide(html_line): + if self._text_fits_on_slide(html_line): previous_html = html_line + line_end previous_raw = line + line_end continue @@ -443,7 +484,7 @@ class Renderer(object): highest_index = len(html_list) - 1 index = int(highest_index / 2) while True: - if self._text_fits_on_slide( + if not self._text_fits_on_slide( previous_html + separator.join(html_list[:index + 1]).strip()): # We know that it does not fit, so change/calculate the # new index and highest_index accordingly. @@ -466,8 +507,8 @@ class Renderer(object): else: continue # Check if the remaining elements fit on the slide. - if not self._text_fits_on_slide( - separator.join(html_list[index + 1:]).strip()): + if self._text_fits_on_slide( + separator.join(html_list[index + 1:]).strip()): previous_html = separator.join( html_list[index + 1:]).strip() + line_end previous_raw = separator.join( @@ -489,11 +530,11 @@ class Renderer(object): returned, otherwise ``False``. ``text`` - The text to check. It can contain HTML tags. + The text to check. It may contain HTML tags. """ self.web_frame.evaluateJavaScript(u'show_text("%s")' % text.replace(u'\\', u'\\\\').replace(u'\"', u'\\\"')) - return self.web_frame.contentsSize().height() > self.page_height + return self.web_frame.contentsSize().height() <= self.page_height def _words_split(self, line): """ diff --git a/openlp/core/ui/aboutdialog.py b/openlp/core/ui/aboutdialog.py index 3e941c051..f4a732fb6 100644 --- a/openlp/core/ui/aboutdialog.py +++ b/openlp/core/ui/aboutdialog.py @@ -116,7 +116,7 @@ class Ui_AboutDialog(object): u'Scott "sguerrieri" Guerrieri', u'Matthias "matthub" Hub', u'Meinert "m2j" Jordan', u'Armin "orangeshirt" K\xf6hler', u'Joshua "milleja46" Miller', - u'Stevan "StevanP" Pettit', u'Mattias "mahfiaz" P\xf5ldaru', + u'Stevan "ElderP" Pettit', u'Mattias "mahfiaz" P\xf5ldaru', u'Christian "crichter" Richter', u'Philip "Phill" Ridout', u'Simon "samscudder" Scudder', u'Jeffrey "whydoubt" Smith', u'Maikel Stuivenberg', u'Frode "frodus" Woldsund'] @@ -125,7 +125,7 @@ class Ui_AboutDialog(object): packagers = ['Thomas "tabthorpe" Abthorpe (FreeBSD)', u'Tim "TRB143" Bentley (Fedora)', u'Matthias "matthub" Hub (Mac OS X)', - u'Stevan "StevanP" Pettit (Windows)', + u'Stevan "ElderP" Pettit (Windows)', u'Raoul "superfly" Snyman (Ubuntu)'] translators = { u'af': [u'Johan "nuvolari" Mynhardt'], diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 4ecf792bc..3fe4a777d 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -30,11 +30,13 @@ import os import sys import shutil from tempfile import gettempdir +from datetime import datetime from PyQt4 import QtCore, QtGui from openlp.core.lib import Renderer, build_icon, OpenLPDockWidget, \ - PluginManager, Receiver, translate, ImageManager, PluginStatus + PluginManager, Receiver, translate, ImageManager, PluginStatus, \ + SettingsManager from openlp.core.lib.ui import UiStrings, base_action, checkable_action, \ icon_action, shortcut_action from openlp.core.ui import AboutForm, SettingsForm, ServiceManager, \ @@ -214,7 +216,7 @@ class Ui_MainWindow(object): self.mediaManagerDock.isVisible(), UiStrings().View) self.viewThemeManagerItem = shortcut_action(mainWindow, u'viewThemeManagerItem', [QtGui.QKeySequence(u'F10')], - self.toggleThemeManager, u':/system/system_thememanager.png', + self.toggleThemeManager, u':/system/system_thememanager.png', self.themeManagerDock.isVisible(), UiStrings().View) self.viewServiceManagerItem = shortcut_action(mainWindow, u'viewServiceManagerItem', [QtGui.QKeySequence(u'F9')], @@ -284,6 +286,10 @@ class Ui_MainWindow(object): self.settingsConfigureItem = icon_action(mainWindow, u'settingsConfigureItem', u':/system/system_settings.png', category=UiStrings().Settings) + self.settingsImportItem = base_action(mainWindow, + u'settingsImportItem', category=UiStrings().Settings) + self.settingsExportItem = base_action(mainWindow, + u'settingsExportItem', category=UiStrings().Settings) action_list.add_category(UiStrings().Help, CategoryOrder.standardMenu) self.aboutItem = shortcut_action(mainWindow, u'aboutItem', [QtGui.QKeySequence(u'Ctrl+F1')], self.onAboutItemClicked, @@ -301,10 +307,10 @@ class Ui_MainWindow(object): u':/system/system_online_help.png', category=UiStrings().Help) self.webSiteItem = base_action( mainWindow, u'webSiteItem', category=UiStrings().Help) - add_actions(self.fileImportMenu, - (self.importThemeItem, self.importLanguageItem)) - add_actions(self.fileExportMenu, - (self.exportThemeItem, self.exportLanguageItem)) + add_actions(self.fileImportMenu, (self.settingsImportItem, None, + self.importThemeItem, self.importLanguageItem)) + add_actions(self.fileExportMenu, (self.settingsExportItem, None, + self.exportThemeItem, self.exportLanguageItem)) add_actions(self.fileMenu, (self.fileNewItem, self.fileOpenItem, self.fileSaveItem, self.fileSaveAsItem, self.recentFilesMenu.menuAction(), None, @@ -357,6 +363,7 @@ class Ui_MainWindow(object): self.importLanguageItem.setVisible(False) self.exportLanguageItem.setVisible(False) self.setLockPanel(panelLocked) + self.settingsImported = False def retranslateUi(self, mainWindow): """ @@ -420,6 +427,15 @@ class Ui_MainWindow(object): translate('OpenLP.MainWindow', 'Configure &Formatting Tags...')) self.settingsConfigureItem.setText( translate('OpenLP.MainWindow', '&Configure OpenLP...')) + self.settingsExportItem.setStatusTip(translate('OpenLP.MainWindow', + 'Export OpenLP settings to a specified Ini file')) + self.settingsExportItem.setText( + translate('OpenLP.MainWindow', 'Settings')) + self.settingsImportItem.setStatusTip(translate('OpenLP.MainWindow', + 'Import OpenLP settings from a specified Ini file previously ' + 'exported on this or another machine')) + self.settingsImportItem.setText( + translate('OpenLP.MainWindow', 'Settings')) self.viewMediaManagerItem.setText( translate('OpenLP.MainWindow', '&Media Manager')) self.viewMediaManagerItem.setToolTip( @@ -523,8 +539,12 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): # (not for use by plugins) self.uiSettingsSection = u'user interface' self.generalSettingsSection = u'general' - self.serviceSettingsSection = u'servicemanager' + self.advancedlSettingsSection = u'advanced' + self.servicemanagerSettingsSection = u'servicemanager' self.songsSettingsSection = u'songs' + self.themesSettingsSection = u'themes' + self.displayTagsSection = u'displayTags' + self.headerSection = u'SettingsImport' self.serviceNotSaved = False self.aboutForm = AboutForm(self) self.settingsForm = SettingsForm(self, self) @@ -573,6 +593,10 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): QtCore.SIGNAL(u'triggered()'), self.onSettingsConfigureItemClicked) QtCore.QObject.connect(self.settingsShortcutsItem, QtCore.SIGNAL(u'triggered()'), self.onSettingsShortcutsItemClicked) + QtCore.QObject.connect(self.settingsImportItem, + QtCore.SIGNAL(u'triggered()'), self.onSettingsImportItemClicked) + QtCore.QObject.connect(self.settingsExportItem, + QtCore.SIGNAL(u'triggered()'), self.onSettingsExportItemClicked) # i18n set signals for languages self.languageGroup.triggered.connect(LanguageManager.set_language) QtCore.QObject.connect(self.modeDefaultItem, @@ -868,6 +892,172 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): if self.shortcutForm.exec_(): self.shortcutForm.save() + def onSettingsImportItemClicked(self): + """ + Import settings from an export INI file + """ + answer = QtGui.QMessageBox.critical(self, + translate('OpenLP.MainWindow', 'Import settings?'), + translate('OpenLP.MainWindow', + 'Are you sure you want to import settings?\n\n' + 'Importing settings will make permanent changes to your current ' + 'OpenLP configuration.\n\n' + 'Importing incorrect settings may cause erratic behaviour or ' + 'OpenLP to terminate abnormally.'), + QtGui.QMessageBox.StandardButtons( + QtGui.QMessageBox.Yes | + QtGui.QMessageBox.No), + QtGui.QMessageBox.No) + if answer == QtGui.QMessageBox.No: + return + importFileName = unicode(QtGui.QFileDialog.getOpenFileName(self, + translate('OpenLP.MainWindow', 'Open File'), + '', + translate('OpenLP.MainWindow', + 'OpenLP Export Settings Files (*.conf)'))) + if not importFileName: + return + settingSections = [] + # Add main sections. + settingSections.extend([self.generalSettingsSection]) + settingSections.extend([self.advancedlSettingsSection]) + settingSections.extend([self.uiSettingsSection]) + settingSections.extend([self.servicemanagerSettingsSection]) + settingSections.extend([self.themesSettingsSection]) + settingSections.extend([self.displayTagsSection]) + settingSections.extend([self.headerSection]) + # Add plugin sections. + for plugin in self.pluginManager.plugins: + settingSections.extend([plugin.name]) + settings = QtCore.QSettings() + importSettings = QtCore.QSettings(importFileName, + QtCore.QSettings.IniFormat) + importKeys = importSettings.allKeys() + for sectionKey in importKeys: + # We need to handle the really bad files. + try: + section, key = sectionKey.split(u'/') + except ValueError: + section = u'unknown' + key = u'' + # Switch General back to lowercase. + if section == u'General': + section = u'general' + sectionKey = section + "/" + key + # Make sure it's a valid section for us. + if not section in settingSections: + QtGui.QMessageBox.critical(self, + translate('OpenLP.MainWindow', 'Import settings'), + translate('OpenLP.MainWindow', + 'The file you selected does appear to be a valid OpenLP ' + 'settings file.\n\n' + 'Section [%s] is not valid \n\n' + 'Processing has terminated and no changed have been made.' + % section), + QtGui.QMessageBox.StandardButtons( + QtGui.QMessageBox.Ok)) + return + # We have a good file, import it. + for sectionKey in importKeys: + value = importSettings.value(sectionKey) + settings.setValue(u'%s' % (sectionKey) , + QtCore.QVariant(value)) + now = datetime.now() + settings.beginGroup(self.headerSection) + settings.setValue( u'file_imported' , QtCore.QVariant(importFileName)) + settings.setValue(u'file_date_imported', + now.strftime("%Y-%m-%d %H:%M")) + settings.endGroup() + settings.sync() + # We must do an immediate restart or current configuration will + # overwrite what was just imported when application terminates + # normally. We need to exit without saving configuration. + QtGui.QMessageBox.information(self, + translate('OpenLP.MainWindow', 'Import settings'), + translate('OpenLP.MainWindow', + 'OpenLP will now close. Imported settings will ' + 'be applied the next time you start OpenLP.'), + QtGui.QMessageBox.StandardButtons( + QtGui.QMessageBox.Ok)) + self.settingsImported = True + self.cleanUp() + QtCore.QCoreApplication.exit() + + def onSettingsExportItemClicked(self, exportFileName=None): + """ + Export settings to an INI file + """ + if not exportFileName: + exportFileName = unicode(QtGui.QFileDialog.getSaveFileName(self, + translate('OpenLP.MainWindow', 'Export Settings File'), '', + translate('OpenLP.MainWindow', + 'OpenLP Export Settings File (*.conf)'))) + if not exportFileName: + return + # Make sure it's an .ini file. + if not exportFileName.endswith(u'conf'): + exportFileName = exportFileName + u'.conf' + temp_file = os.path.join(unicode(gettempdir()), + u'openlp', u'exportIni.tmp') + self.saveSettings() + settingSections = [] + # Add main sections. + settingSections.extend([self.generalSettingsSection]) + settingSections.extend([self.advancedlSettingsSection]) + settingSections.extend([self.uiSettingsSection]) + settingSections.extend([self.servicemanagerSettingsSection]) + settingSections.extend([self.themesSettingsSection]) + settingSections.extend([self.displayTagsSection]) + # Add plugin sections. + for plugin in self.pluginManager.plugins: + settingSections.extend([plugin.name]) + # Delete old files if found. + if os.path.exists(temp_file): + os.remove(temp_file) + if os.path.exists(exportFileName): + os.remove(exportFileName) + settings = QtCore.QSettings() + settings.remove(self.headerSection) + # Get the settings. + keys = settings.allKeys() + exportSettings = QtCore.QSettings(temp_file, + QtCore.QSettings.IniFormat) + # Add a header section. + # This is to insure it's our ini file for import. + now = datetime.now() + applicationVersion = get_application_version() + # Write INI format using Qsettings. + # Write our header. + exportSettings.beginGroup(self.headerSection) + exportSettings.setValue(u'Make_Changes', u'At_Own_RISK') + exportSettings.setValue(u'type', u'OpenLP_settings_export') + exportSettings.setValue(u'file_date_created', + now.strftime("%Y-%m-%d %H:%M")) + exportSettings.setValue(u'version', applicationVersion[u'full']) + exportSettings.endGroup() + # Write all the sections and keys. + for sectionKey in keys: + section, key = sectionKey.split(u'/') + keyValue = settings.value(sectionKey) + sectionKey = section + u"/" + key + # Change the service section to servicemanager. + if section == u'service': + sectionKey = u'servicemanager/' + key + exportSettings.setValue(sectionKey, keyValue) + exportSettings.sync() + # Temp INI file has been written. Blanks in keys are now '%20'. + # Read the temp file and output the user's INI file with blanks to + # make it more readable. + tempIni = open(temp_file, u'r') + exportIni = open(exportFileName, u'w') + for fileRecord in tempIni: + fileRecord = fileRecord.replace(u'%20', u' ') + exportIni.write(fileRecord) + tempIni.close() + exportIni.close() + os.remove(temp_file) + return + def onModeDefaultItemClicked(self): """ Put OpenLP into "Default" view mode. @@ -920,6 +1110,9 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): """ Hook to close the main window and display windows on exit """ + # If we just did a settings import, close without saving changes. + if self.settingsImported: + event.accept() if self.serviceManagerContents.isModified(): ret = self.serviceManagerContents.saveModifiedService() if ret == QtGui.QMessageBox.Save: @@ -1117,6 +1310,9 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): """ Save the main window settings. """ + # Exit if we just did a settings import. + if self.settingsImported: + return log.debug(u'Saving QSettings') settings = QtCore.QSettings() settings.beginGroup(self.generalSettingsSection) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 3ab2e9239..ad1161e0d 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -290,7 +290,7 @@ class ServiceManager(QtGui.QWidget): QtCore.SIGNAL(u'service_item_update'), self.serviceItemUpdate) # Last little bits of setting up self.service_theme = unicode(QtCore.QSettings().value( - self.mainwindow.serviceSettingsSection + u'/service theme', + self.mainwindow.servicemanagerSettingsSection + u'/service theme', QtCore.QVariant(u'')).toString()) self.servicePath = AppLocation.get_section_data_path(u'servicemanager') # build the drag and drop context menu @@ -371,7 +371,7 @@ class ServiceManager(QtGui.QWidget): self.mainwindow.setServiceModified(self.isModified(), self.shortFileName()) QtCore.QSettings(). \ - setValue(u'service/last file',QtCore.QVariant(fileName)) + setValue(u'servicemanager/last file',QtCore.QVariant(fileName)) def fileName(self): """ @@ -429,14 +429,15 @@ class ServiceManager(QtGui.QWidget): self.mainwindow, translate('OpenLP.ServiceManager', 'Open File'), SettingsManager.get_last_dir( - self.mainwindow.serviceSettingsSection), + self.mainwindow.servicemanagerSettingsSection), translate('OpenLP.ServiceManager', 'OpenLP Service Files (*.osz)'))) if not fileName: return False else: fileName = loadFile - SettingsManager.set_last_dir(self.mainwindow.serviceSettingsSection, + SettingsManager.set_last_dir( + self.mainwindow.servicemanagerSettingsSection, split_filename(fileName)[0]) self.loadFile(fileName) @@ -461,7 +462,7 @@ class ServiceManager(QtGui.QWidget): self.setFileName(u'') self.setModified(False) QtCore.QSettings(). \ - setValue(u'service/last file',QtCore.QVariant(u'')) + setValue(u'servicemanager/last file',QtCore.QVariant(u'')) def saveFile(self): """ @@ -474,7 +475,8 @@ class ServiceManager(QtGui.QWidget): (basename, extension) = os.path.splitext(file_name) service_file_name = basename + '.osd' log.debug(u'ServiceManager.saveFile - %s' % path_file_name) - SettingsManager.set_last_dir(self.mainwindow.serviceSettingsSection, + SettingsManager.set_last_dir( + self.mainwindow.servicemanagerSettingsSection, path) service = [] write_list = [] @@ -562,7 +564,7 @@ class ServiceManager(QtGui.QWidget): fileName = unicode(QtGui.QFileDialog.getSaveFileName(self.mainwindow, UiStrings().SaveService, SettingsManager.get_last_dir( - self.mainwindow.serviceSettingsSection), + self.mainwindow.servicemanagerSettingsSection), translate('OpenLP.ServiceManager', 'OpenLP Service Files (*.osz)'))) if not fileName: return False @@ -624,7 +626,7 @@ class ServiceManager(QtGui.QWidget): self.mainwindow.addRecentFile(fileName) self.setModified(False) QtCore.QSettings().setValue( - 'service/last file', QtCore.QVariant(fileName)) + 'servicemanager/last file', QtCore.QVariant(fileName)) else: critical_error_message_box( message=translate('OpenLP.ServiceManager', @@ -666,7 +668,7 @@ class ServiceManager(QtGui.QWidget): present. """ fileName = QtCore.QSettings(). \ - value(u'service/last file',QtCore.QVariant(u'')).toString() + value(u'servicemanager/last file',QtCore.QVariant(u'')).toString() if fileName: self.loadFile(fileName) @@ -1008,7 +1010,8 @@ class ServiceManager(QtGui.QWidget): self.service_theme = unicode(self.themeComboBox.currentText()) self.mainwindow.renderer.set_service_theme(self.service_theme) QtCore.QSettings().setValue( - self.mainwindow.serviceSettingsSection + u'/service theme', + self.mainwindow.servicemanagerSettingsSection + + u'/service theme', QtCore.QVariant(self.service_theme)) self.regenerateServiceItems() diff --git a/openlp/plugins/songs/lib/db.py b/openlp/plugins/songs/lib/db.py index bee64b3ba..5bfa0c830 100644 --- a/openlp/plugins/songs/lib/db.py +++ b/openlp/plugins/songs/lib/db.py @@ -31,6 +31,7 @@ the Songs plugin from sqlalchemy import Column, ForeignKey, Table, types from sqlalchemy.orm import mapper, relation +from sqlalchemy.sql.expression import func from openlp.core.lib.db import BaseModel, init_db @@ -195,7 +196,10 @@ def init_schema(url): Column(u'song_number', types.Unicode(64)), Column(u'theme_name', types.Unicode(128)), Column(u'search_title', types.Unicode(255), index=True, nullable=False), - Column(u'search_lyrics', types.UnicodeText, nullable=False) + Column(u'search_lyrics', types.UnicodeText, nullable=False), + Column(u'create_date', types.DateTime(), default=func.now()), + Column(u'last_modified', types.DateTime(), default=func.now(), + onupdate=func.now()) ) # Definition of the "topics" table diff --git a/openlp/plugins/songs/lib/upgrade.py b/openlp/plugins/songs/lib/upgrade.py index e0f2668d9..fae3400c2 100644 --- a/openlp/plugins/songs/lib/upgrade.py +++ b/openlp/plugins/songs/lib/upgrade.py @@ -25,15 +25,16 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ -The :mod:`upgrade` module provides a way for the database and schema that is the backend for -the Songs plugin +The :mod:`upgrade` module provides a way for the database and schema that is the +backend for the Songs plugin """ from sqlalchemy import Column, ForeignKey, Table, types +from sqlalchemy.sql.expression import func from migrate import changeset from migrate.changeset.constraint import ForeignKeyConstraint -__version__ = 1 +__version__ = 2 def upgrade_setup(metadata): """ @@ -57,7 +58,7 @@ def upgrade_1(session, metadata, tables): """ Version 1 upgrade. - This upgrade removes the many-to-many relationship between songs and + This upgrade removes the many-to-many relationship between songs and media_files and replaces it with a one-to-many, which is far more representative of the real relationship between the two entities. @@ -75,3 +76,13 @@ def upgrade_1(session, metadata, tables): ForeignKeyConstraint([u'song_id'], [u'songs.id'], table=tables[u'media_files']).create() +def upgrade_2(session, metadata, tables): + """ + Version 2 upgrade. + + This upgrade adds a create_date and last_modified date to the songs table + """ + Column(u'create_date', types.DateTime(), default=func.now())\ + .create(table=tables[u'songs'], populate_default=True) + Column(u'last_modified', types.DateTime(), default=func.now())\ + .create(table=tables[u'songs'], populate_default=True) diff --git a/openlp/plugins/songusage/forms/songusagedetailform.py b/openlp/plugins/songusage/forms/songusagedetailform.py index 303789d20..f7b04a656 100644 --- a/openlp/plugins/songusage/forms/songusagedetailform.py +++ b/openlp/plugins/songusage/forms/songusagedetailform.py @@ -117,9 +117,11 @@ class SongUsageDetailForm(QtGui.QDialog, Ui_SongUsageDetailDialog): try: fileHandle = open(outname, u'w') for instance in usage: - record = u'\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"\n' % ( - instance.usagedate, instance.usagetime, instance.title, - instance.copyright, instance.ccl_number, instance.authors) + record = u'\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",' \ + u'\"%s\",\"%s\"\n' % ( instance.usagedate, + instance.usagetime, instance.title, instance.copyright, + instance.ccl_number, instance.authors, + instance.plugin_name, instance.source) fileHandle.write(record.encode(u'utf-8')) Receiver.send_message(u'openlp_information_message', { u'title': translate('SongUsagePlugin.SongUsageDetailForm', diff --git a/openlp/plugins/songusage/lib/db.py b/openlp/plugins/songusage/lib/db.py index 9a11ef16b..bbd645634 100644 --- a/openlp/plugins/songusage/lib/db.py +++ b/openlp/plugins/songusage/lib/db.py @@ -56,7 +56,9 @@ def init_schema(url): Column(u'title', types.Unicode(255), nullable=False), Column(u'authors', types.Unicode(255), nullable=False), Column(u'copyright', types.Unicode(255)), - Column(u'ccl_number', types.Unicode(65)) + Column(u'ccl_number', types.Unicode(65)), + Column(u'plugin_name', types.Unicode(20)), + Column(u'source', types.Unicode(10)) ) mapper(SongUsageItem, songusage_table) diff --git a/openlp/plugins/songusage/lib/upgrade.py b/openlp/plugins/songusage/lib/upgrade.py new file mode 100644 index 000000000..50ca32fcd --- /dev/null +++ b/openlp/plugins/songusage/lib/upgrade.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2011 Raoul Snyman # +# Portions copyright (c) 2008-2011 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:`upgrade` module provides a way for the database and schema that is the +backend for the SongsUsage plugin +""" + +from sqlalchemy import Column, Table, types +from migrate import changeset + +__version__ = 1 + +def upgrade_setup(metadata): + """ + Set up the latest revision all tables, with reflection, needed for the + upgrade process. If you want to drop a table, you need to remove it from + here, and add it to your upgrade function. + """ + tables = { + u'songusage_data': Table(u'songusage_data', metadata, autoload=True) + } + return tables + + +def upgrade_1(session, metadata, tables): + """ + Version 1 upgrade. + + This upgrade adds two new fields to the songusage database + """ + Column(u'plugin_name', types.Unicode(20), default=u'') \ + .create(table=tables[u'songusage_data'], populate_default=True) + Column(u'source', types.Unicode(10), default=u'') \ + .create(table=tables[u'songusage_data'], populate_default=True) diff --git a/openlp/plugins/songusage/songusageplugin.py b/openlp/plugins/songusage/songusageplugin.py index b39fe1a33..495d3103d 100644 --- a/openlp/plugins/songusage/songusageplugin.py +++ b/openlp/plugins/songusage/songusageplugin.py @@ -37,6 +37,7 @@ from openlp.core.lib.ui import base_action, shortcut_action from openlp.core.utils.actions import ActionList from openlp.plugins.songusage.forms import SongUsageDetailForm, \ SongUsageDeleteForm +from openlp.plugins.songusage.lib import upgrade from openlp.plugins.songusage.lib.db import init_schema, SongUsageItem log = logging.getLogger(__name__) @@ -46,11 +47,11 @@ class SongUsagePlugin(Plugin): def __init__(self, plugin_helpers): Plugin.__init__(self, u'songusage', plugin_helpers) + self.manager = Manager(u'songusage', init_schema, upgrade_mod=upgrade) self.weight = -4 self.icon = build_icon(u':/plugins/plugin_songusage.png') self.activeIcon = build_icon(u':/songusage/song_usage_active.png') self.inactiveIcon = build_icon(u':/songusage/song_usage_inactive.png') - self.manager = None self.songUsageActive = False def addToolsMenuItem(self, tools_menu): @@ -121,10 +122,10 @@ class SongUsagePlugin(Plugin): Plugin.initialise(self) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'slidecontroller_live_started'), - self.onReceiveSongUsage) + self.displaySongUsage) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'print_service_started'), - self.onReceiveSongUsage) + self.printSongUsage) self.songUsageActive = QtCore.QSettings().value( self.settingsSection + u'/active', QtCore.QVariant(False)).toBool() @@ -137,8 +138,6 @@ class SongUsagePlugin(Plugin): translate('SongUsagePlugin', 'Song Usage')) action_list.add_action(self.songUsageReport, translate('SongUsagePlugin', 'Song Usage')) - if self.manager is None: - self.manager = Manager(u'songusage', init_schema) self.songUsageDeleteForm = SongUsageDeleteForm(self.manager, self.formparent) self.songUsageDetailForm = SongUsageDetailForm(self, self.formparent) @@ -197,10 +196,21 @@ class SongUsagePlugin(Plugin): self.songUsageStatus.blockSignals(False) - def onReceiveSongUsage(self, item): + def displaySongUsage(self, item): """ - Song Usage for live song from SlideController + Song Usage for which has been displayed """ + self._add_song_usage(unicode(translate('SongUsagePlugin', + 'display')), item) + + def printSongUsage(self, item): + """ + Song Usage for which has been printed + """ + self._add_song_usage(unicode(translate('SongUsagePlugin', + 'printed')), item) + + def _add_song_usage(self, source, item): audit = item[0].audit if self.songUsageActive and audit: song_usage_item = SongUsageItem() @@ -210,6 +220,8 @@ class SongUsagePlugin(Plugin): song_usage_item.copyright = audit[2] song_usage_item.ccl_number = audit[3] song_usage_item.authors = u' '.join(audit[1]) + song_usage_item.plugin_name = item[0].name + song_usage_item.source = source self.manager.save_object(song_usage_item) def onSongUsageDelete(self):