From 34ab7ea901a50279dfd44bb224ef8bd32c9ca1d8 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Mon, 28 Dec 2009 01:54:00 +0200 Subject: [PATCH 001/164] Started moving Bible formats to a generic system. --- openlp/plugins/bibles/lib/common.py | 4 +- .../bibles/lib/{bibleCSVimpl.py => csv.py} | 21 ++--- .../bibles/lib/{bibleDBimpl.py => db.py} | 29 +++++-- .../bibles/lib/{bibleHTTPimpl.py => http.py} | 2 +- openlp/plugins/bibles/lib/manager.py | 78 ++++++++++++------- .../lib/{bibleOpenSongimpl.py => opensong.py} | 6 +- .../bibles/lib/{bibleOSISimpl.py => osis.py} | 2 +- 7 files changed, 92 insertions(+), 50 deletions(-) rename openlp/plugins/bibles/lib/{bibleCSVimpl.py => csv.py} (92%) rename openlp/plugins/bibles/lib/{bibleDBimpl.py => db.py} (89%) rename openlp/plugins/bibles/lib/{bibleHTTPimpl.py => http.py} (99%) rename openlp/plugins/bibles/lib/{bibleOpenSongimpl.py => opensong.py} (97%) rename openlp/plugins/bibles/lib/{bibleOSISimpl.py => osis.py} (99%) diff --git a/openlp/plugins/bibles/lib/common.py b/openlp/plugins/bibles/lib/common.py index fe8d18ff4..027cf6314 100644 --- a/openlp/plugins/bibles/lib/common.py +++ b/openlp/plugins/bibles/lib/common.py @@ -85,7 +85,9 @@ class BibleCommon(object): """ An empty constructor... not sure why I'm here. """ - pass + self.loadbible = True + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'openlpstopimport'), self.stop_import) def _get_web_text(self, urlstring, proxyurl): """ diff --git a/openlp/plugins/bibles/lib/bibleCSVimpl.py b/openlp/plugins/bibles/lib/csv.py similarity index 92% rename from openlp/plugins/bibles/lib/bibleCSVimpl.py rename to openlp/plugins/bibles/lib/csv.py index 0f168bd10..e829f99a0 100644 --- a/openlp/plugins/bibles/lib/bibleCSVimpl.py +++ b/openlp/plugins/bibles/lib/csv.py @@ -26,23 +26,24 @@ import logging import chardet -from openlp.plugins.bibles.lib.common import BibleCommon +from openlp.plugins.bibles.lib.db import BibleDB from openlp.core.lib import Receiver -class BibleCSVImpl(BibleCommon): - global log - log = logging.getLogger(u'BibleCSVImpl') - log.info(u'BibleCVSImpl loaded') - def __init__(self, bibledb): +log = logging.getLogger(__name__) + +class CSVBible(BibleDB): + """ + This class provides a specialisation for importing of CSV Bibles. + """ + + def __init__(self, **kwargs): """ Loads a Bible from a pair of CVS files passed in This class assumes the files contain all the information and a clean bible is being loaded. """ - self.bibledb = bibledb - self.loadbible = True - QtCore.QObject.connect(Receiver.get_receiver(), - QtCore.SIGNAL(u'openlpstopimport'), self.stop_import) + log.info(__name__) + BibleDB.__init__(self, **kwargs) def stop_import(self): self.loadbible = False diff --git a/openlp/plugins/bibles/lib/bibleDBimpl.py b/openlp/plugins/bibles/lib/db.py similarity index 89% rename from openlp/plugins/bibles/lib/bibleDBimpl.py rename to openlp/plugins/bibles/lib/db.py index ffc2f5c9b..260b3bda4 100644 --- a/openlp/plugins/bibles/lib/bibleDBimpl.py +++ b/openlp/plugins/bibles/lib/db.py @@ -27,17 +27,30 @@ import os import logging from common import BibleCommon -from openlp.plugins.bibles.lib.models import * +from models import * -class BibleDBImpl(BibleCommon): - global log - log = logging.getLogger(u'BibleDBImpl') - log.info(u'BibleDBimpl loaded') +log = logging.getLogger(__name__) - def __init__(self, biblepath, biblename, config): +class BibleDB(BibleCommon): + """ + This class represents a database-bound Bible. It is used as a base class + for all the custom importers, so that the can implement their own import + methods, but benefit from the database methods in here via inheritance, + rather than depending on yet another object. + """ + + def __init__(self, **kwargs): + log.info(u'BibleDBimpl loaded') + if u'biblepath' not in kwargs: + raise KeyError(u'Missing keyword argument "path".') + if u'biblename' not in kwargs: + raise KeyError(u'Missing keyword argument "name".') + if u'config' not in kwargs: + raise KeyError(u'Missing keyword argument "config".') # Connect to database - self.config = config - self.biblefile = os.path.join(biblepath, biblename + u'.sqlite') + self.config = kwargs[u'config'] + self.db_file = os.path.join(kwargs[u'path'], + u'%s.sqlite' % kwargs[u'biblename']) log.debug(u'Load bible %s on path %s', biblename, self.biblefile) db_type = self.config.get_config(u'db type', u'sqlite') db_url = u'' diff --git a/openlp/plugins/bibles/lib/bibleHTTPimpl.py b/openlp/plugins/bibles/lib/http.py similarity index 99% rename from openlp/plugins/bibles/lib/bibleHTTPimpl.py rename to openlp/plugins/bibles/lib/http.py index 6d57698c4..e20a2ca64 100644 --- a/openlp/plugins/bibles/lib/bibleHTTPimpl.py +++ b/openlp/plugins/bibles/lib/http.py @@ -171,7 +171,7 @@ class CWExtract(BibleCommon): bible[verse] = self._clean_text(verseText) return SearchResults(book_title, book_chapter, bible) -class BibleHTTPImpl(): +class HTTPBible(object): global log log = logging.getLogger(u'BibleHTTPMgr') log.info(u'BibleHTTP manager loaded') diff --git a/openlp/plugins/bibles/lib/manager.py b/openlp/plugins/bibles/lib/manager.py index 9d0da8374..4d77bc32d 100644 --- a/openlp/plugins/bibles/lib/manager.py +++ b/openlp/plugins/bibles/lib/manager.py @@ -26,37 +26,59 @@ import logging import os -from bibleOpenSongimpl import BibleOpenSongImpl -from bibleOSISimpl import BibleOSISImpl -from bibleCSVimpl import BibleCSVImpl +from opensong import OpenSongBible +from osis import OSISBible +from csv import CSVBible from bibleDBimpl import BibleDBImpl from bibleHTTPimpl import BibleHTTPImpl class BibleMode(object): + """ + This is basically an enumeration class which specifies the mode of a Bible. + Mode refers to whether or not a Bible in OpenLP is a full Bible or needs to + be downloaded from the Internet on an as-needed basis. + """ Full = 1 Partial = 2 class BibleFormat(object): + """ + This is a special enumeration class that holds the various types of Bibles, + plus a few helper functions to facilitate generic handling of Bible types + for importing. + """ Unknown = -1 OSIS = 0 CSV = 1 OpenSong = 2 WebDownload = 3 - @classmethod - def get_handler(class_, id): - if id == class_.OSIS: + @staticmethod + def get_class(id): + """ + Return the appropriate imeplementation class. + """ + if id == BibleFormat.OSIS: return BibleOSISImpl - elif id == class_.CSV: + elif id == BibleFormat.CSV: return BibleCSVImpl - elif id == class_.OpenSong: + elif id == BibleFormat.OpenSong: return BibleOpenSongImpl - elif id == class_.WebDownload: + elif id == BibleFormat.WebDownload: return BibleHTTPImpl else: return None + @staticmethod + def list(): + return [ + BibleFormat.OSIS, + BibleFormat.CSV, + BibleFormat.OpenSong, + BibleFormat.WebDownload + ] + class BibleManager(object): """ @@ -84,11 +106,11 @@ class BibleManager(object): self.bible_db_cache = None # dict of bible http readers self.bible_http_cache = None - self.biblePath = self.config.get_data_path() + self.bible_path = self.config.get_data_path() #get proxy name for screen - self.proxyname = self.config.get_config(u'proxy name') - self.bibleSuffix = u'sqlite' - self.dialogobject = None + self.proxy_name = self.config.get_config(u'proxy name') + self.bible_suffix = u'sqlite' + self.import_wizard = None self.reload_bibles() self.media = None @@ -108,7 +130,7 @@ class BibleManager(object): for f in files: nme = f.split(u'.') bname = nme[0] - self.bible_db_cache[bname] = BibleDBImpl(self.biblePath, + self.bible_db_cache[bname] = BibleDBImpl(self.bible_path, bname, self.config) # look to see if lazy load bible exists and get create getter. biblesource = self.bible_db_cache[bname].get_meta(u'WEB') @@ -139,7 +161,7 @@ class BibleManager(object): self.book_abbreviations = {} filepath = os.path.split(os.path.abspath(__file__))[0] filepath = os.path.abspath(os.path.join( - filepath, u'..', u'resources',u'httpbooks.csv')) + filepath, u'..', u'resources', u'httpbooks.csv')) fbibles = None try: fbibles = open(filepath, u'r') @@ -155,14 +177,14 @@ class BibleManager(object): fbibles.close() log.debug(u'Bible Initialised') - def set_process_dialog(self, dialogobject): + def set_process_dialog(self, wizard): """ Sets the reference to the dialog with the progress bar on it. - ``dialogobject`` - The reference to the dialog. + ``dialog`` + The reference to the import wizard. """ - self.dialogobject = dialogobject + self.import_wizard = wizard def import_bible(self, type, **kwargs): """ @@ -171,7 +193,11 @@ class BibleManager(object): ``type`` What type of Bible, """ - pass + impl_class = BibleFormat.get_handler(type) + bible_impl = impl_class(**kwargs) + bible_name = bible_impl.register() + BibleDBImpl(self.biblePath, bible_name, self.config) + bible_impl.do_import() def register_http_bible(self, biblename, biblesource, bibleid, proxyurl=None, proxyid=None, proxypass=None): @@ -202,7 +228,7 @@ class BibleManager(object): biblename, biblesource, bibleid, proxyurl, proxyid, proxypass) if self._is_new_bible(biblename): # Create new Bible - nbible = BibleDBImpl(self.biblePath, biblename, self.config) + nbible = BibleDBImpl(self.bible_path, biblename, self.config) # Create Database nbible.create_tables() self.bible_db_cache[biblename] = nbible @@ -239,7 +265,7 @@ class BibleManager(object): biblename, booksfile, versefile) if self._is_new_bible(biblename): # Create new Bible - nbible = BibleDBImpl(self.biblePath, biblename, self.config) + nbible = BibleDBImpl(self.bible_path, biblename, self.config) # Create database nbible.create_tables() # Cache the database for use later @@ -261,13 +287,13 @@ class BibleManager(object): log.debug(u'register_OSIS_file_bible %s, %s', biblename, osisfile) if self._is_new_bible(biblename): # Create new Bible - nbible = BibleDBImpl(self.biblePath, biblename, self.config) + nbible = BibleDBImpl(self.bible_path, biblename, self.config) # Create Database nbible.create_tables() # Cache the database for use later self.bible_db_cache[biblename] = nbible # Create the loader and pass in the database - bosis = BibleOSISImpl(self.biblePath, nbible) + bosis = BibleOSISImpl(self.bible_path, nbible) return bosis.load_data(osisfile, self.dialogobject) else: log.debug( @@ -283,13 +309,13 @@ class BibleManager(object): log.debug(u'register_opensong_file_bible %s, %s', biblename, opensongfile) if self._is_new_bible(biblename): # Create new Bible - nbible = BibleDBImpl(self.biblePath, biblename, self.config) + nbible = BibleDBImpl(self.bible_path, biblename, self.config) # Create Database nbible.create_tables() # Cache the database for use later self.bible_db_cache[biblename] = nbible # Create the loader and pass in the database - bcsv = BibleOpenSongImpl(self.biblePath, nbible) + bcsv = BibleOpenSongImpl(self.bible_path, nbible) bcsv.load_data(opensongfile, self.dialogobject) return True else: diff --git a/openlp/plugins/bibles/lib/bibleOpenSongimpl.py b/openlp/plugins/bibles/lib/opensong.py similarity index 97% rename from openlp/plugins/bibles/lib/bibleOpenSongimpl.py rename to openlp/plugins/bibles/lib/opensong.py index 58b9ffcd0..7ede58249 100644 --- a/openlp/plugins/bibles/lib/bibleOpenSongimpl.py +++ b/openlp/plugins/bibles/lib/opensong.py @@ -34,7 +34,7 @@ from PyQt4 import QtCore from openlp.core.lib import Receiver -class BibleOpenSongImpl(): +class OpenSongBible(object): """ OSIS Bible format importer class. """ @@ -81,10 +81,10 @@ class BibleOpenSongImpl(): detect_file = None try: detect_file = open(bible_file, u'r') - details = chardet.detect(detect_file.read(2048)) + details = chardet.detect(detect_file.read(3000)) except: log.exception(u'Failed to detect OpenSong file encoding') - return + return False finally: if detect_file: detect_file.close() diff --git a/openlp/plugins/bibles/lib/bibleOSISimpl.py b/openlp/plugins/bibles/lib/osis.py similarity index 99% rename from openlp/plugins/bibles/lib/bibleOSISimpl.py rename to openlp/plugins/bibles/lib/osis.py index 15b16c072..62beb6e15 100644 --- a/openlp/plugins/bibles/lib/bibleOSISimpl.py +++ b/openlp/plugins/bibles/lib/osis.py @@ -34,7 +34,7 @@ from PyQt4 import QtCore from openlp.core.lib import Receiver -class BibleOSISImpl(): +class OSISBible(object): """ OSIS Bible format importer class. """ From 0374233db6137b2ea33c86a865ca8af6b3443e8e Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Wed, 30 Dec 2009 19:29:08 +0200 Subject: [PATCH 002/164] Lots of changes here, I think. - Pluggable import system - The enumeration class provides the correct importer class - Reworked Bible verse parsing, using regular expressions - Reworked verse query method to accept multiple verse ranges - Renamed lots of methods, which is probably gonna break lots of stuff --- .../plugins/bibles/forms/importwizardform.py | 44 ++- openlp/plugins/bibles/lib/common.py | 65 +++- openlp/plugins/bibles/lib/csv.py | 52 ++- openlp/plugins/bibles/lib/db.py | 323 +++++++++++------- openlp/plugins/bibles/lib/http.py | 81 +++-- openlp/plugins/bibles/lib/manager.py | 111 +++--- openlp/plugins/bibles/lib/mediaitem.py | 9 +- openlp/plugins/bibles/lib/models.py | 4 +- openlp/plugins/bibles/lib/opensong.py | 84 ++--- openlp/plugins/bibles/lib/osis.py | 67 ++-- 10 files changed, 474 insertions(+), 366 deletions(-) diff --git a/openlp/plugins/bibles/forms/importwizardform.py b/openlp/plugins/bibles/forms/importwizardform.py index 22fbea989..9bf6b51c1 100644 --- a/openlp/plugins/bibles/forms/importwizardform.py +++ b/openlp/plugins/bibles/forms/importwizardform.py @@ -169,6 +169,15 @@ class ImportWizardForm(QtGui.QWizard, Ui_BibleImportWizard): QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok)) self.CopyrightEdit.setFocus() return False + elif self.biblemanager.exists( + self.field(u'license_version').toString()): + QtGui.QMessageBox.critical(self, + self.trUtf8('Bible Exists'), + self.trUtf8('This Bible already exists! Please import ' + 'a different Bible or first delete the existing one.'), + QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok)) + self.VersionNameEdit.setFocus() + return False return True if self.currentId() == 3: # Progress page @@ -317,22 +326,22 @@ class ImportWizardForm(QtGui.QWizard, Ui_BibleImportWizard): success = False if bible_type == BibleFormat.OSIS: # Import an OSIS bible - success = self.biblemanager.register_osis_file_bible( - unicode(self.field(u'license_version').toString()), - unicode(self.field(u'osis_location').toString()) + success = self.biblemanager.import_bible(BibleFormat.OSIS, + name=unicode(self.field(u'license_version').toString()), + filename=unicode(self.field(u'osis_location').toString()) ) elif bible_type == BibleFormat.CSV: # Import a CSV bible - success = self.biblemanager.register_csv_file_bible( - unicode(self.field(u'license_version').toString()), - self.field(u'csv_booksfile').toString(), - self.field(u'csv_versefile').toString() + success = self.biblemanager.import_bible(BibleFormat.CSV, + name=unicode(self.field(u'license_version').toString()), + booksfile=self.field(u'csv_booksfile').toString(), + versefile=self.field(u'csv_versefile').toString() ) elif bible_type == BibleFormat.OpenSong: # Import an OpenSong bible - success = self.biblemanager.register_opensong_bible( - unicode(self.field(u'license_version').toString()), - self.field(u'opensong_file').toString() + success = self.biblemanager.import_bible(BibleFormat.OpenSong, + name=unicode(self.field(u'license_version').toString()), + filename=self.field(u'opensong_file').toString() ) elif bible_type == BibleFormat.WebDownload: # Import a bible from the web @@ -344,13 +353,13 @@ class ImportWizardForm(QtGui.QWizard, Ui_BibleImportWizard): elif download_location == DownloadLocation.BibleGateway: bible = self.web_bible_list[DownloadLocation.BibleGateway][ unicode(self.BibleComboBox.currentText())] - success = self.biblemanager.register_http_bible( - unicode(self.field(u'license_version').toString()), - unicode(DownloadLocation.get_name(download_location)), - unicode(bible), - unicode(self.field(u'proxy_server').toString()), - unicode(self.field(u'proxy_username').toString()), - unicode(self.field(u'proxy_password').toString()) + success = self.biblemanager.import_bible(BibleFormat.WebDownload, + name=unicode(self.field(u'license_version').toString()), + download_source=unicode(DownloadLocation.get_name(download_location)), + download_name=unicode(bible), + proxy_server=unicode(self.field(u'proxy_server').toString()), + proxy_username=unicode(self.field(u'proxy_username').toString()), + proxy_password=unicode(self.field(u'proxy_password').toString()) ) if success: self.biblemanager.save_meta_data( @@ -359,6 +368,7 @@ class ImportWizardForm(QtGui.QWizard, Ui_BibleImportWizard): unicode(self.field(u'license_copyright').toString()), unicode(self.field(u'license_permission').toString()) ) + self.biblemanager.reload_bibles() self.ImportProgressLabel.setText(self.trUtf8('Finished import.')) else: self.ImportProgressLabel.setText( diff --git a/openlp/plugins/bibles/lib/common.py b/openlp/plugins/bibles/lib/common.py index 027cf6314..b40c483ba 100644 --- a/openlp/plugins/bibles/lib/common.py +++ b/openlp/plugins/bibles/lib/common.py @@ -26,6 +26,63 @@ import urllib2 import chardet import logging +import re + +only_verses = re.compile(r'([a-zA-Z0-9 ]+)[ ]+([0-9]+)[ ]*[:|v|V][ ]*([0-9]+)' + r'(?:[ ]*-[ ]*([0-9]+|end))?(?:[ ]*,[ ]*([0-9]+)(?:[ ]*-[ ]*([0-9]+|end))?)?', + re.UNICODE) +chapter_range = re.compile(r'([a-zA-Z0-9 ]+)[ ]+([0-9]+)[ ]*[:|v|V][ ]*' + r'([0-9]+)[ ]*-[ ]*([0-9]+)[ ]*[:|v|V][ ]*([0-9]+)', + re.UNICODE) + +def parse_reference(reference): + """ + This is the über-awesome function that takes a person's typed in string + and converts it to a reference list, a list of references to be queried + from the Bible database files. + + The reference list is a list of tuples, with each tuple structured like + this:: + + (book, chapter, start_verse, end_verse) + """ + reference = reference.strip() + reference_list = [] + # We start with the most "complicated" match first, so that they are found + # first, and we don't have any "false positives". + match = chapter_range.match(reference) + if match: + reference_list.extend([ + (match.group(1), match.group(2), match.group(3), -1), + (match.group(1), match.group(4), 1, match.group(5)) + ]) + else: + match = only_verses.match(reference) + if match: + book = match.group(1) + chapter = match.group(2) + verse = match.group(3) + if match.group(4) is None: + reference_list.append((book, chapter, verse, verse)) + elif match.group(5) is None: + end_verse = match.group(4) + if end_verse == u'end': + end_verse = -1 + reference_list.append((book, chapter, verse, end_verse)) + elif match.group(6) is None: + reference_list.extend([ + (book, chapter, verse, match.group(4)), + (book, chapter, match.group(5), match.group(5)) + ]) + else: + end_verse = match.group(6) + if end_verse == u'end': + end_verse = -1 + reference_list.extend([ + (book, chapter, verse, match.group(4)), + (book, chapter, match.group(5), end_verse) + ]) + return reference_list class SearchResults: """ @@ -81,14 +138,6 @@ class BibleCommon(object): log = logging.getLogger(u'BibleCommon') log.info(u'BibleCommon') - def __init__(self): - """ - An empty constructor... not sure why I'm here. - """ - self.loadbible = True - QtCore.QObject.connect(Receiver.get_receiver(), - QtCore.SIGNAL(u'openlpstopimport'), self.stop_import) - def _get_web_text(self, urlstring, proxyurl): """ Get the HTML from the web page. diff --git a/openlp/plugins/bibles/lib/csv.py b/openlp/plugins/bibles/lib/csv.py index e829f99a0..c74452fd3 100644 --- a/openlp/plugins/bibles/lib/csv.py +++ b/openlp/plugins/bibles/lib/csv.py @@ -26,8 +26,8 @@ import logging import chardet -from openlp.plugins.bibles.lib.db import BibleDB from openlp.core.lib import Receiver +from db import BibleDB log = logging.getLogger(__name__) @@ -44,20 +44,22 @@ class CSVBible(BibleDB): """ log.info(__name__) BibleDB.__init__(self, **kwargs) + if u'booksfile' not in kwargs: + raise KeyError(u'You have to supply a file to import books from.') + self.booksfile = kwargs[u'booksfile'] + if u'versesfile' not in kwargs: + raise KeyError(u'You have to supply a file to import verses from.') + self.versesfile = kwargs[u'versesfile'] - def stop_import(self): - self.loadbible = False - - def load_data(self, booksfile, versesfile, dialogobject): + def do_import(self): #Populate the Tables success = True fbooks = None try: - fbooks = open(booksfile, 'r') - count = 0 + fbooks = open(self.booksfile, 'r') for line in fbooks: # cancel pressed - if not self.loadbible: + if self.stop_import: break details = chardet.detect(line) line = unicode(line, details['encoding']) @@ -65,12 +67,8 @@ class CSVBible(BibleDB): p1 = p[1].replace(u'"', u'') p2 = p[2].replace(u'"', u'') p3 = p[3].replace(u'"', u'') - self.bibledb.create_book(p2, p3, int(p1)) - count += 1 - #Flush the screen events - if count % 3 == 0: - Receiver.send_message(u'process_events') - count = 0 + self.create_book(p2, p3, int(p1)) + Receiver.send_message(u'process_events') except: log.exception(u'Loading books from file failed') success = False @@ -82,10 +80,9 @@ class CSVBible(BibleDB): fverse = None try: fverse = open(versesfile, 'r') - count = 0 book_ptr = None for line in fverse: - if not self.loadbible: # cancel pressed + if self.stop_import: # cancel pressed break details = chardet.detect(line) line = unicode(line, details['encoding']) @@ -94,28 +91,23 @@ class CSVBible(BibleDB): p0 = p[0].replace(u'"', u'') p3 = p[3].replace(u'"', u'') if book_ptr is not p0: - book = self.bibledb.get_bible_book(p0) + book = self.get_book(p0) book_ptr = book.name # increament the progress bar - dialogobject.incrementProgressBar(u'Importing %s %s' % \ - book.name) - self.bibledb.add_verse(book.id, p[1], p[2], p3) - count += 1 - #Every x verses repaint the screen - if count % 3 == 0: - Receiver.send_message(u'process_events') - count = 0 - self.bibledb.save_verses() + self.wizard.incrementProgressBar( + u'Importing %s %s' % book.name) + self.commit() + self.add_verse(book.id, p[1], p[2], p3) + Receiver.send_message(u'process_events') + self.commit() except: log.exception(u'Loading verses from file failed') success = False finally: if fverse: fverse.close() - if not self.loadbible: - dialogobject.incrementProgressBar(u'Import canceled!') - dialogobject.ImportProgressBar.setValue( - dialogobject.ImportProgressBar.maximum()) + if self.stop_import: + self.wizard.incrementProgressBar(u'Import canceled!') return False else: return success diff --git a/openlp/plugins/bibles/lib/db.py b/openlp/plugins/bibles/lib/db.py index 260b3bda4..b1e228ee4 100644 --- a/openlp/plugins/bibles/lib/db.py +++ b/openlp/plugins/bibles/lib/db.py @@ -26,12 +26,15 @@ import os import logging -from common import BibleCommon +from sqlalchemy import or_ +from PyQt4 import QtCore + +from openlp.core.lib import Receiver from models import * log = logging.getLogger(__name__) -class BibleDB(BibleCommon): +class BibleDB(): """ This class represents a database-bound Bible. It is used as a base class for all the custom importers, so that the can implement their own import @@ -40,22 +43,39 @@ class BibleDB(BibleCommon): """ def __init__(self, **kwargs): + """ + The constructor loads up the database and creates and initialises the + tables if the database doesn't exist. + + **Required keyword arguments:** + + ``path`` + The path to the bible database file. + + ``name`` + The name of the database. This is also used as the file name for + SQLite databases. + + ``config`` + The configuration object, passed in from the plugin. + """ log.info(u'BibleDBimpl loaded') - if u'biblepath' not in kwargs: + if u'path' not in kwargs: raise KeyError(u'Missing keyword argument "path".') - if u'biblename' not in kwargs: + if u'name' not in kwargs: raise KeyError(u'Missing keyword argument "name".') if u'config' not in kwargs: raise KeyError(u'Missing keyword argument "config".') - # Connect to database + self.stop_import = False + self.name = kwargs[u'name'] self.config = kwargs[u'config'] self.db_file = os.path.join(kwargs[u'path'], - u'%s.sqlite' % kwargs[u'biblename']) - log.debug(u'Load bible %s on path %s', biblename, self.biblefile) + u'%s.sqlite' % kwargs[u'name']) + log.debug(u'Load bible %s on path %s', kwargs[u'name'], self.db_file) db_type = self.config.get_config(u'db type', u'sqlite') db_url = u'' if db_type == u'sqlite': - db_url = u'sqlite:///' + self.biblefile + db_url = u'sqlite:///' + self.db_file else: db_url = u'%s://%s:%s@%s/%s' % \ (db_type, self.config.get_config(u'db username'), @@ -65,134 +85,200 @@ class BibleDB(BibleCommon): self.metadata, self.session = init_models(db_url) self.metadata.create_all(checkfirst=True) + def register(self): + """ + This method basically just initialialises the database. It is called + from the Bible Manager when a Bible is imported. Descendant classes + may want to override this method to supply their own custom + initialisation as well. + """ + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'openlpstopimport'), self.stop_import) + self.create_tables() + return self.name + + def stop_import(self): + """ + Stops the import of the Bible. + """ + log.debug('Stopping import!') + self.stop_import = True + + def commit(self): + log.debug('Committing...') + self.session.commit() + def create_tables(self): log.debug(u'createTables') self.save_meta(u'dbversion', u'2') - self._load_testament(u'Old Testament') - self._load_testament(u'New Testament') - self._load_testament(u'Apocrypha') + self.create_testament(u'Old Testament') + self.create_testament(u'New Testament') + self.create_testament(u'Apocrypha') - def add_verse(self, bookid, chap, vse, text): - verse = Verse() - verse.book_id = bookid - verse.chapter = chap - verse.verse = vse - verse.text = text + def create_testament(self, testament): + log.debug(u'%s: %s', __name__, testament) + self.session.add(Testament.populate(name=testament)) + self.commit() + + def create_book(self, name, abbrev, testament=1): + log.debug(u'create_book %s,%s', bookname, bookabbrev) + book = Book.populate(name=name, abbreviation=abbrev, + testament_id=testament) + self.session.add(book) + self.commit() + return book + + def create_chapter(self, book_id, chapter, textlist): + log.debug(u'create_chapter %s,%s', bookid, chap) + #text list has book and chapter as first two elements of the array + for verse_number, verse_text in textlist.iteritems(): + verse = Verse.populate( + book_id = book_id, + chapter = chap, + verse = verse_number, + text = verse_text + ) + self.session.add(verse) + self.commit() + + def create_verse(self, book_id, chapter, verse, text): + verse = Verse.populate( + book_id=book_id, + chapter=chapter, + verse=verse, + text=text + ) self.session.add(verse) return verse - def save_verses(self): - log.debug('Saving verses...') - self.session.commit() - - def create_chapter(self, bookid, chap, textlist): - log.debug(u'create_chapter %s,%s', bookid, chap) - #text list has book and chapter as first to elements of the array - for verse_number, verse_text in textlist.iteritems(): - verse = Verse() - verse.book_id = bookid - verse.chapter = chap - verse.verse = verse_number - verse.text = verse_text - self.session.add(verse) - self.session.commit() - - def create_book(self, bookname, bookabbrev, testament=1): - log.debug(u'create_book %s,%s', bookname, bookabbrev) - book = Book() - book.testament_id = testament - book.name = bookname - book.abbreviation = bookabbrev - self.session.add(book) - self.session.commit() - return book - - def save_meta(self, key, value): + def create_meta(self, key, value): log.debug(u'save_meta %s/%s', key, value) - bmeta = BibleMeta() - bmeta.key = key - bmeta.value = value - self.session.add(bmeta) - self.session.commit() + self.session.add(BibleMeta.populate(key=key, value=value)) + self.commit() - def get_meta(self, metakey): - log.debug(u'get meta %s', metakey) - return self.session.query(BibleMeta).filter_by(key=metakey).first() + def get_books(self): + log.debug(__name__) + return self.session.query(Book).order_by(Book.id).all() + + def get_book(self, book): + log.debug(u'%s: %s', __name__, book) + db_book = self.session.query(Book)\ + .filter_by(Book.name.like(book + u'%'))\ + .first() + if db_book is None: + db_book = self.session.query(Book)\ + .filter(Book.abbreviation.like(book + u'%'))\ + .first() + return db_book + + def get_chapter(self, id, chapter): + log.debug(u'%s: %s, %s', __name__, id, chapter) + return self.session.query(Verse)\ + .filter_by(chapter=chapter)\ + .filter_by(book_id=id)\ + .first() + + def get_verses(self, reference_list): + """ + This is probably the most used function. It retrieves the list of + verses based on the user's query. + + ``reference_list`` + This is the list of references the media manager item wants. It is + a list of tuples, with the following format:: + + (book, chapter, start_verse, end_verse) + + Therefore, when you are looking for multiple items, simply break + them up into references like this, bundle them into a list. This + function then runs through the list, and returns an amalgamated + list of ``Verse`` objects. For example:: + + [(u'Genesis', 1, 1, 1), (u'Genesis', 2, 2, 3)] + """ + log.debug(u'%s: %s', __name__, reference_list) + verse_list = [] + for book, chapter, start_verse, end_verse in reference_list: + db_book = self.get_book(book) + if end_verse == -1: + end_verse = self.get_chapter_count(book) + if db_book: + book = book.name + log.debug(u'Book name corrected to "%s"' % book) + verses = self.session.query(Verse)\ + .filter_by(book_id=book.id)\ + .filter_by(chapter=chapter)\ + .filter(Verse.verse >= start_verse)\ + .filter(Verse.verse <= end_verse)\ + .order_by(Verse.verse)\ + .all() + verse_list.extend(verses) + return verse_list + + def verse_search(self, text): + """ + Search for verses containing text ``text``. + + ``text`` + The text to search for. If the text contains commas, it will be + split apart and OR'd on the list of values. If the text just + contains spaces, it will split apart and AND'd on the list of + values. + """ + log.debug(u'%s: %s', __name__, text) + verses = self.session.query(Verse) + if text.find(u',') > -1: + or_clause = [] + keywords = [u'%%%s%%' % keyword.strip() for keyword in text.split(u',')] + for keyword in keywords: + or_clause.append(Verse.text.like(keyword)) + verses = verses.filter(or_(*or_clause)) + else: + keywords = [u'%%%s%%' % keyword.strip() for keyword in text.split(u' ')] + for keyword in keywords: + verses = verses.filter(Verse.text.like(keyword)) + verses = verses.all() + return verses + + def get_chapter_count(self, book): + log.debug(u'%s: %s', __name__, book) + count = self.session.query(Verse.chapter).join(Book)\ + .filter(Book.name==book)\ + .distinct().count() + #verse = self.session.query(Verse).join(Book).filter( + # Book.name == bookname).order_by(Verse.chapter.desc()).first() + if not count: + return 0 + else: + return count + + def get_verse_count(self, book, chapter): + log.debug(u'%s: %s, %s', __name__, book, chapter) + count = self.session.query(Verse).join(Book)\ + .filter(Book.name==book)\ + .filter(Verse.chapter==chapter)\ + .count() + #verse = self.session.query(Verse).join(Book).filter( + # Book.name == bookname).filter( + # Verse.chapter == chapter).order_by(Verse.verse.desc()).first() + if not count: + return 0 + else: + return count + + def get_meta(self, key): + log.debug(u'get meta %s', key) + return self.session.query(BibleMeta).get(key) def delete_meta(self, metakey): biblemeta = self.get_meta(metakey) try: self.session.delete(biblemeta) - self.session.commit() + self.commit() return True except: return False - def _load_testament(self, testament): - log.debug(u'load_testaments %s', testament) - test = ONTestament() - test.name = testament - self.session.add(test) - self.session.commit() - - def get_bible_books(self): - log.debug(u'get_bible_books') - return self.session.query(Book).order_by(Book.id).all() - - def get_max_bible_book_verses(self, bookname, chapter): - log.debug(u'get_max_bible_book_verses %s, %s', bookname, chapter) - verse = self.session.query(Verse).join(Book).filter( - Book.name == bookname).filter( - Verse.chapter == chapter).order_by(Verse.verse.desc()).first() - if verse == None: - return 0 - else: - return verse.verse - - def get_max_bible_book_chapter(self, bookname): - log.debug(u'get_max_bible_book_chapter %s', bookname) - verse = self.session.query(Verse).join(Book).filter( - Book.name == bookname).order_by(Verse.chapter.desc()).first() - if verse == None: - return 0 - else: - return verse.chapter - - def get_bible_book(self, bookname): - log.debug(u'get_bible_book %s', bookname) - book = self.session.query(Book).filter( - Book.name.like(bookname + u'%')).first() - if book is None: - book = self.session.query(Book).filter( - Book.abbreviation.like(bookname + u'%')).first() - return book - - def get_bible_chapter(self, id, chapter): - log.debug(u'get_bible_chapter %s, %s', id, chapter) - return self.session.query(Verse).filter_by(chapter=chapter).filter_by( - book_id=id).first() - - def get_bible_text(self, bookname, chapter, sverse, everse): - log.debug(u'get_bible_text %s, %s, %s, %s', bookname, chapter, sverse, - everse) - #Look up book name or abbreviation - book = self.get_bible_book(bookname) - if book: - bookname = book.name - log.debug(u'bookname corrected to %s' % bookname) - verses = self.session.query(Verse).join(Book).filter( - Book.name == bookname).filter(Verse.chapter == chapter).filter( - Verse.verse>=sverse).filter(Verse.verse<=everse).order_by( - Verse.verse).all() - return verses - - def get_verses_from_text(self, versetext): - log.debug(u'get_verses_from_text %s',versetext) - versetext = u'%%%s%%' % versetext - verses = self.session.query(Verse).filter( - Verse.text.like(versetext)).all() - return verses - def dump_bible(self): log.debug( u'.........Dumping Bible Database') log.debug( '...............................Books ') @@ -201,3 +287,4 @@ class BibleDB(BibleCommon): log.debug( u'...............................Verses ') verses = self.session.query(Verse).all() log.debug(verses) + diff --git a/openlp/plugins/bibles/lib/http.py b/openlp/plugins/bibles/lib/http.py index e20a2ca64..57e6e4a16 100644 --- a/openlp/plugins/bibles/lib/http.py +++ b/openlp/plugins/bibles/lib/http.py @@ -26,17 +26,18 @@ import logging from common import BibleCommon, SearchResults +from db import BibleDB class BGExtract(BibleCommon): global log log = logging.getLogger(u'BibleHTTPMgr(BG_extract)') log.info(u'BG_extract loaded') - def __init__(self, proxyurl= None): + def __init__(self, proxyurl=None): log.debug(u'init %s', proxyurl) self.proxyurl = proxyurl - def get_bible_chapter(self, version, bookname, chapter) : + def get_chapter(self, version, bookname, chapter) : """ Access and decode bibles via the BibleGateway website @@ -49,12 +50,10 @@ class BGExtract(BibleCommon): ``chapter`` Chapter number """ - log.debug(u'get_bible_chapter %s,%s,%s', - version, bookname, chapter) - urlstring = \ - u'http://www.biblegateway.com/passage/?search=%s+%d&version=%s' % \ - (bookname, chapter, version) - log.debug(u'BibleGateway urm = %s' % urlstring) + log.debug(u'get_bible_chapter %s, %s, %s', version, bookname, chapter) + urlstring = u'http://www.biblegateway.com/passage/?search=%s+%d' \ + u'&version=%s' % (bookname, chapter, version) + log.debug(u'BibleGateway url = %s' % urlstring) xml_string = self._get_web_text(urlstring, self.proxyurl) verseSearch = u'(.*?)') self.note_regex = re.compile(r'(.*?)') @@ -66,13 +65,11 @@ class OSISBible(object): self.w_regex = re.compile(r'') self.q_regex = re.compile(r'') self.spaces_regex = re.compile(r'([ ]{2,})') - self.bibledb = bibledb self.books = {} filepath = os.path.split(os.path.abspath(__file__))[0] filepath = os.path.abspath(os.path.join( filepath, u'..', u'resources', u'osisbooks.csv')) fbibles = None - self.loadbible = True try: fbibles = open(filepath, u'r') for line in fbibles: @@ -84,31 +81,15 @@ class OSISBible(object): finally: if fbibles: fbibles.close() - QtCore.QObject.connect(Receiver.get_receiver(), - QtCore.SIGNAL(u'openlpstopimport'), self.stop_import) - def stop_import(self): - """ - Stops the import of the Bible. - """ - log.debug('Stopping import!') - self.loadbible = False - - def load_data(self, osisfile_record, dialogobject=None): + def do_import(self): """ Loads a Bible from file. - - ``osisfile_record`` - The file to import from. - - ``dialogobject`` - The Import dialog, so that we can increase the counter on - the progress bar. """ - log.info(u'Load data for %s' % osisfile_record) + log.debug(u'Starting OSIS import from "%s"' % self.filename) detect_file = None try: - detect_file = open(osisfile_record, u'r') + detect_file = open(self.filename, u'r') details = chardet.detect(detect_file.read(3000)) except: log.exception(u'Failed to detect OSIS file encoding') @@ -119,12 +100,12 @@ class OSISBible(object): osis = None success = True try: - osis = codecs.open(osisfile_record, u'r', details['encoding']) + osis = codecs.open(self.filename, u'r', details['encoding']) last_chapter = 0 testament = 1 db_book = None for file_record in osis: - if not self.loadbible: + if self.stop_import: break match = self.verse_regex.search(file_record) if match: @@ -142,13 +123,13 @@ class OSISBible(object): testament) if last_chapter == 0: if book == u'Gen': - dialogobject.ImportProgressBar.setMaximum(1188) + self.wizard.ImportProgressBar.setMaximum(1188) else: - dialogobject.ImportProgressBar.setMaximum(260) + self.wizard.ImportProgressBar.setMaximum(260) if last_chapter != chapter: if last_chapter != 0: self.bibledb.save_verses() - dialogobject.incrementProgressBar( + self.wizard.incrementProgressBar( u'Importing %s %s...' % \ (self.books[match.group(1)][0], chapter)) last_chapter = chapter @@ -170,20 +151,18 @@ class OSISBible(object): .replace(u'', u'').replace(u'', u'')\ .replace(u'', u'') verse_text = self.spaces_regex.sub(u' ', verse_text) - self.bibledb.add_verse(db_book.id, chapter, verse, verse_text) + self.add_verse(db_book.id, chapter, verse, verse_text) Receiver.send_message(u'process_events') - self.bibledb.save_verses() - dialogobject.incrementProgressBar(u'Finishing import...') + self.commit() + self.wizard.incrementProgressBar(u'Finishing import...') except: log.exception(u'Loading bible from OSIS file failed') success = False finally: if osis: osis.close() - if not self.loadbible: - dialogobject.incrementProgressBar(u'Import canceled!') - dialogobject.ImportProgressBar.setValue( - dialogobject.ImportProgressBar.maximum()) + if self.stop_import: + self.wizard.incrementProgressBar(u'Import canceled!') return False else: return success From 6ef3f6aadcc41b40d6a2909289a07fecb9c1430b Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Sat, 9 Jan 2010 09:34:06 +0000 Subject: [PATCH 003/164] Lots of fixes: Alerts now stack up a wait correctly Presentations will run but issue with impress previews Transparent themes now work New image for media previews --- openlp/core/lib/__init__.py | 27 +++++- openlp/core/lib/renderer.py | 93 ++++++++---------- openlp/core/lib/rendermanager.py | 52 +++------- openlp/core/lib/serviceitem.py | 9 +- openlp/core/ui/maindisplay.py | 97 ++++++++++++------- openlp/core/ui/slidecontroller.py | 32 +++--- openlp/plugins/media/lib/mediaitem.py | 9 +- .../presentations/lib/impresscontroller.py | 16 ++- .../presentations/lib/messagelistener.py | 59 ++++++----- resources/images/openlp-2.qrc | 1 + 10 files changed, 210 insertions(+), 185 deletions(-) diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index 22df9a926..846ff2053 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -136,6 +136,30 @@ def contextMenuSeparator(base): action.setSeparator(True) return action +def resize_image(image, width, height): + """ + Resize an image to fit on the current screen. + + ``image`` + The image to resize. + """ + if isinstance(image, QtGui.QImage): + preview = QtGui.QImage(image) + else: + preview = QtGui.QImage(image) + + preview = preview.scaled(width, height, QtCore.Qt.KeepAspectRatio, + QtCore.Qt.SmoothTransformation) + realw = preview.width(); + realh = preview.height() + # and move it to the centre of the preview space + newImage = QtGui.QImage(width, height, QtGui.QImage.Format_ARGB32_Premultiplied) + newImage.fill(QtCore.Qt.black) + painter = QtGui.QPainter(newImage) + painter.drawImage((width - realw) / 2, (height - realh) / 2, preview) + return newImage + + class ThemeLevel(object): Global = 1 Service = 2 @@ -160,6 +184,3 @@ from renderer import Renderer from rendermanager import RenderManager from mediamanageritem import MediaManagerItem from baselistwithdnd import BaseListWithDnD - -#__all__ = [ 'translate', 'get_text_file_string', 'str_to_bool', -# 'contextMenuAction', 'contextMenuSeparator', 'ServiceItem'] \ No newline at end of file diff --git a/openlp/core/lib/renderer.py b/openlp/core/lib/renderer.py index c4d7444c4..e3e275193 100644 --- a/openlp/core/lib/renderer.py +++ b/openlp/core/lib/renderer.py @@ -26,6 +26,7 @@ import logging from PyQt4 import QtGui, QtCore +from openlp.core.lib import resize_image class Renderer(object): """ @@ -90,31 +91,9 @@ class Renderer(object): log.debug(u'set bg image %s', filename) self._bg_image_filename = unicode(filename) if self._frame: - self.scale_bg_image() - - def scale_bg_image(self): - """ - Scale the background image to fit the screen. - """ - assert self._frame - preview = QtGui.QImage(self._bg_image_filename) - width = self._frame.width() - height = self._frame.height() - preview = preview.scaled(width, height, QtCore.Qt.KeepAspectRatio, - QtCore.Qt.SmoothTransformation) - realwidth = preview.width() - realheight = preview.height() - # and move it to the centre of the preview space - self.bg_image = QtGui.QImage(width, height, - QtGui.QImage.Format_ARGB32_Premultiplied) - self.bg_image.fill(QtCore.Qt.black) - painter = QtGui.QPainter() - painter.begin(self.bg_image) - self.background_offsetx = (width - realwidth) / 2 - self.background_offsety = (height - realheight) / 2 - painter.drawImage(self.background_offsetx, - self.background_offsety, preview) - painter.end() + self.bg_image = resize_image(self._bg_image_filename, + self._frame.width(), + self._frame.height()) def set_frame_dest(self, frame_width, frame_height, preview=False): """ @@ -138,7 +117,9 @@ class Renderer(object): self._frameOp = QtGui.QImage(frame_width, frame_height, QtGui.QImage.Format_ARGB32_Premultiplied) if self._bg_image_filename and not self.bg_image: - self.scale_bg_image() + self.bg_image = resize_image(self._bg_image_filename, + self._frame.width(), + self._frame.height()) if self.bg_frame is None: self._generate_background_frame() @@ -276,8 +257,13 @@ class Renderer(object): Results are cached for performance reasons. """ assert(self._theme) - self.bg_frame = QtGui.QImage(self._frame.width(), self._frame.height(), - QtGui.QImage.Format_ARGB32_Premultiplied) + if self._theme.background_mode == u'transparent': + self.bg_frame = \ + QtGui.QPixmap(self._frame.width(), self._frame.height()) + self.bg_frame.fill(QtCore.Qt.transparent) + else: + self.bg_frame = QtGui.QImage(self._frame.width(), self._frame.height(), + QtGui.QImage.Format_ARGB32_Premultiplied) log.debug(u'render background %s start', self._theme.background_type) painter = QtGui.QPainter() painter.begin(self.bg_frame) @@ -422,6 +408,14 @@ class Renderer(object): startx = x starty = y rightextent = None + self.painter = QtGui.QPainter() + self.painter.begin(self._frame) + self.painter.setRenderHint(QtGui.QPainter.Antialiasing); + if self._theme.display_slideTransition: + self.painter2 = QtGui.QPainter() + self.painter2.begin(self._frameOp) + self.painter2.setRenderHint(QtGui.QPainter.Antialiasing); + self.painter2.setOpacity(0.7) # dont allow alignment messing with footers if footer: align = 0 @@ -503,13 +497,14 @@ class Renderer(object): if linenum == 0: self._first_line_right_extent = rightextent # draw a box around the text - debug only + if self._debug: - painter = QtGui.QPainter() - painter.begin(self._frame) - painter.setPen(QtGui.QPen(QtGui.QColor(0,255,0))) - painter.drawRect(startx, starty, rightextent-startx, y-starty) - painter.end() + self.painter.setPen(QtGui.QPen(QtGui.QColor(0,255,0))) + self.painter.drawRect(startx, starty, rightextent-startx, y-starty) brcorner = (rightextent, y) + self.painter.end() + if self._theme.display_slideTransition: + self.painter2.end() return brcorner def _set_theme_font(self): @@ -519,6 +514,7 @@ class Renderer(object): footer_weight = 50 if self._theme.font_footer_weight == u'Bold': footer_weight = 75 + #TODO Add myfont.setPixelSize((screen_height / 100) * font_size) self.footerFont = QtGui.QFont(self._theme.font_footer_name, self._theme.font_footer_proportion, # size footer_weight, # weight @@ -556,45 +552,36 @@ class Renderer(object): Defaults to *None*. The colour to draw with. """ # setup defaults - painter = QtGui.QPainter() - painter.begin(self._frame) - painter.setRenderHint(QtGui.QPainter.Antialiasing); if footer : font = self.footerFont else: font = self.mainFont - painter.setFont(font) + self.painter.setFont(font) if color is None: if footer: - painter.setPen(QtGui.QColor(self._theme.font_footer_color)) + self.painter.setPen(QtGui.QColor(self._theme.font_footer_color)) else: - painter.setPen(QtGui.QColor(self._theme.font_main_color)) + self.painter.setPen(QtGui.QColor(self._theme.font_main_color)) else: - painter.setPen(QtGui.QColor(color)) + self.painter.setPen(QtGui.QColor(color)) x, y = tlcorner metrics = QtGui.QFontMetrics(font) w = metrics.width(line) h = metrics.height() - 2 if draw: - painter.drawText(x, y + metrics.ascent(), line) - painter.end() + self.painter.drawText(x, y + metrics.ascent(), line) if self._theme.display_slideTransition: # Print 2nd image with 70% weight - painter = QtGui.QPainter() - painter.begin(self._frameOp) - painter.setRenderHint(QtGui.QPainter.Antialiasing); - painter.setOpacity(0.7) - painter.setFont(font) + self.painter2.setFont(font) if color is None: if footer: - painter.setPen(QtGui.QColor(self._theme.font_footer_color)) + self.painter2.setPen(QtGui.QColor(self._theme.font_footer_color)) else: - painter.setPen(QtGui.QColor(self._theme.font_main_color)) + self.painter2.setPen(QtGui.QColor(self._theme.font_main_color)) else: - painter.setPen(QtGui.QColor(color)) + self.painter2.setPen(QtGui.QColor(color)) if draw: - painter.drawText(x, y + metrics.ascent(), line) - painter.end() + self.painter2.drawText(x, y + metrics.ascent(), line) return (w, h) def snoop_Image(self, image, image2=None): @@ -609,4 +596,4 @@ class Renderer(object): """ image.save(u'renderer.png', u'png') if image2: - image2.save(u'renderer2.png', u'png') \ No newline at end of file + image2.save(u'renderer2.png', u'png') diff --git a/openlp/core/lib/rendermanager.py b/openlp/core/lib/rendermanager.py index a0e2ce0af..1bd2b1400 100644 --- a/openlp/core/lib/rendermanager.py +++ b/openlp/core/lib/rendermanager.py @@ -28,7 +28,7 @@ import logging from PyQt4 import QtGui, QtCore from renderer import Renderer -from openlp.core.lib import ThemeLevel +from openlp.core.lib import ThemeLevel, resize_image class RenderManager(object): """ @@ -146,13 +146,13 @@ class RenderManager(object): if self.save_bg_frame is None: self.save_bg_frame = self.renderer.bg_frame if self.override_background_changed: - self.renderer.bg_frame = self.resize_image( - self.override_background) + self.renderer.bg_frame = resize_image( + self.override_background, self.width, self.height) self.override_background_changed = False else: if self.override_background_changed: - self.renderer.bg_frame = self.resize_image( - self.override_background) + self.renderer.bg_frame = resize_image( + self.override_background, self.width, self.height) self.override_background_changed = False if self.save_bg_frame: self.renderer.bg_frame = self.save_bg_frame @@ -192,10 +192,13 @@ class RenderManager(object): The theme to generated a preview for. """ log.debug(u'generate preview') + #set the defaukt image size for previews self.calculate_default(QtCore.QSize(1024, 768)) self.renderer.set_theme(themedata) self.build_text_rectangle(themedata) self.renderer.set_frame_dest(self.width, self.height, True) + #Reset the real screen size for subsequent render requests + self.calculate_default(self.screen_list[self.current_display][u'size']) verse = u'Amazing Grace!\n'\ 'How sweet the sound\n'\ 'To save a wretch like me;\n'\ @@ -206,6 +209,7 @@ class RenderManager(object): footer.append(u'Public Domain') footer.append(u'CCLI 123456') formatted = self.renderer.format_slide(verse, False) + #Only Render the first slide page returned return self.renderer.generate_frame_from_lines(formatted[0], footer)[u'main'] def format_slide(self, words): @@ -234,48 +238,18 @@ class RenderManager(object): self.renderer.set_frame_dest(self.width, self.height) return self.renderer.generate_frame_from_lines(main_text, footer_text) - def resize_image(self, image, width=0, height=0): - """ - Resize an image to fit on the current screen. - - ``image`` - The image to resize. - """ - preview = QtGui.QImage(image) - if width == 0: - w = self.width - h = self.height - else: - w = width - h = height - preview = preview.scaled(w, h, QtCore.Qt.KeepAspectRatio, - QtCore.Qt.SmoothTransformation) - realw = preview.width(); - realh = preview.height() - # and move it to the centre of the preview space - newImage = QtGui.QImage(w, h, QtGui.QImage.Format_ARGB32_Premultiplied) - newImage.fill(QtCore.Qt.black) - painter = QtGui.QPainter(newImage) - painter.drawImage((w - realw) / 2, (h - realh) / 2, preview) - return newImage - def calculate_default(self, screen): """ Calculate the default dimentions of the screen. ``screen`` - The QWidget instance of the screen. + The QSize of the screen. """ log.debug(u'calculate default %s', screen) - #size fixed so reflects the preview size. - if self.current_display == 0: - self.width = 1024 - self.height = 768 - else: - self.width = screen.width() - self.height = screen.height() + self.width = screen.width() + self.height = screen.height() self.screen_ratio = float(self.height) / float(self.width) log.debug(u'calculate default %d, %d, %f', self.width, self.height, self.screen_ratio ) # 90% is start of footer - self.footer_start = int(self.height * 0.90) \ No newline at end of file + self.footer_start = int(self.height * 0.90) diff --git a/openlp/core/lib/serviceitem.py b/openlp/core/lib/serviceitem.py index 004973462..19cbe37c2 100644 --- a/openlp/core/lib/serviceitem.py +++ b/openlp/core/lib/serviceitem.py @@ -30,7 +30,7 @@ import uuid from PyQt4 import QtGui -from openlp.core.lib import build_icon, Receiver +from openlp.core.lib import build_icon, Receiver, resize_image class ServiceItemType(object): """ @@ -111,7 +111,8 @@ class ServiceItem(object): elif self.service_item_type == ServiceItemType.Image: for slide in self._raw_frames: slide[u'image'] = \ - self.RenderManager.resize_image(slide[u'image']) + resize_image(slide[u'image'], self.RenderManager.width, + self.RenderManager.height) elif self.service_item_type == ServiceItemType.Command: pass else: @@ -119,7 +120,7 @@ class ServiceItem(object): def render_individual(self, row): """ - Takes an array of text and geneates an Image from the + Takes an array of text and generates an Image from the theme. It assumes the text will fit on the screen as it has generated by the render method above. """ @@ -309,4 +310,4 @@ class ServiceItem(object): def request_audit(self): if self.audit: - Receiver.send_message(u'songusage_live', self.audit) \ No newline at end of file + Receiver.send_message(u'songusage_live', self.audit) diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index 46da99ac2..4ad334f8b 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -90,29 +90,32 @@ class MainDisplay(DisplayWidget): self.parent = parent self.setWindowTitle(u'OpenLP Display') self.screens = screens - self.layout = QtGui.QVBoxLayout(self) - self.layout.setSpacing(0) - self.layout.setMargin(0) - self.layout.setObjectName(u'layout') +# self.layout = QtGui.QVBoxLayout(self) +# self.layout.setSpacing(0) +# self.layout.setMargin(0) +# self.layout.setObjectName(u'layout') self.mediaObject = Phonon.MediaObject(self) self.video = Phonon.VideoWidget() self.video.setVisible(False) self.audio = Phonon.AudioOutput(Phonon.VideoCategory, self.mediaObject) Phonon.createPath(self.mediaObject, self.video) Phonon.createPath(self.mediaObject, self.audio) - self.layout.insertWidget(0, self.video) + #self.layout.insertWidget(0, self.video) self.display = QtGui.QLabel(self) self.display.setScaledContents(True) - self.layout.insertWidget(0, self.display) + self.alertDisplay = QtGui.QLabel(self) + self.alertDisplay.setScaledContents(True) + #self.layout.insertWidget(0, self.display) self.primary = True self.displayBlank = False self.blankFrame = None self.frame = None - self.alertactive = False self.timer_id = 0 self.firstTime = True self.mediaLoaded = False self.hasTransition = False + self.alertList = [] + self.mediaBackground = False QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'alert_text'), self.displayAlert) QtCore.QObject.connect(Receiver.get_receiver(), @@ -130,7 +133,6 @@ class MainDisplay(DisplayWidget): QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'media_stop'), self.onMediaStop) - def setup(self, screenNumber): """ Sets up the screen on a particular screen. @@ -138,44 +140,64 @@ class MainDisplay(DisplayWidget): """ log.debug(u'Setup %s for %s ' %(self.screens, screenNumber)) self.setVisible(False) - screen = self.screens[screenNumber] - if screen[u'number'] != screenNumber: + self.screen = self.screens[screenNumber] + if self.screen[u'number'] != screenNumber: # We will most probably never actually hit this bit, but just in # case the index in the list doesn't match the screen number, we # search for it. for scrn in self.screens: if scrn[u'number'] == screenNumber: - screen = scrn + self.screen = scrn break - self.setGeometry(screen[u'size']) + self.setScreenGeometry() #Build a custom splash screen self.InitialFrame = QtGui.QImage( - screen[u'size'].width(), screen[u'size'].height(), + self.screen[u'size'].width(), + self.screen[u'size'].height(), QtGui.QImage.Format_ARGB32_Premultiplied) splash_image = QtGui.QImage(u':/graphics/openlp-splash-screen.png') painter_image = QtGui.QPainter() painter_image.begin(self.InitialFrame) painter_image.fillRect(self.InitialFrame.rect(), QtCore.Qt.white) painter_image.drawImage( - (screen[u'size'].width() - splash_image.width()) / 2, - (screen[u'size'].height() - splash_image.height()) / 2, + (self.screen[u'size'].width() - splash_image.width()) / 2, + (self.screen[u'size'].height() - splash_image.height()) / 2, splash_image) self.frameView(self.InitialFrame) #Build a Black screen painter = QtGui.QPainter() self.blankFrame = QtGui.QImage( - screen[u'size'].width(), screen[u'size'].height(), + self.screen[u'size'].width(), + self.screen[u'size'].height(), QtGui.QImage.Format_ARGB32_Premultiplied) painter.begin(self.blankFrame) painter.fillRect(self.blankFrame.rect(), QtCore.Qt.black) + #buid a blank transparent image + self.transparent = QtGui.QPixmap(self.screen[u'size'].width(), + self.screen[u'size'].height()) + self.transparent.fill(QtCore.Qt.transparent) # To display or not to display? - if not screen[u'primary']: + if not self.screen[u'primary']: self.showFullScreen() self.primary = False else: self.setVisible(False) self.primary = True + def setScreenGeometry(self): + """ + Define and set up the display sizes. + The alert displays are set to 10% of the screen as the video display + is unable to handle transparent pixmaps. This is a problem with QT. + """ + self.setGeometry(self.screen[u'size']) + self.alertScreenPosition = self.screen[u'size'].height() * 0.9 + self.alertHeight = self.screen[u'size'].height() - self.alertScreenPosition + self.alertDisplay.setGeometry( + QtCore.QRect(0, self.alertScreenPosition, + self.screen[u'size'].width(),self.alertHeight)) + self.video.setGeometry(self.screen[u'size']) + def resetDisplay(self): if self.primary: self.setVisible(False) @@ -235,17 +257,27 @@ class MainDisplay(DisplayWidget): display text """ log.debug(u'display alert called %s' % text) + self.alertList.append(text) + if self.timer_id != 0: + return + self.generateAlert() + + def generateAlert(self): + log.debug(u'Generate Alert called') + if len(self.alertList) == 0: + return + text = self.alertList.pop(0) alertTab = self.parent.settingsForm.AlertsTab - if isinstance(self.frame, QtGui.QImage): - alertframe = QtGui.QPixmap.fromImage(self.frame) - else: - alertframe = QtGui.QPixmap.fromImage(self.frame[u'main']) + alertframe = \ + QtGui.QPixmap(self.screen[u'size'].width(), self.alertHeight) + alertframe.fill(QtCore.Qt.transparent) painter = QtGui.QPainter(alertframe) - top = alertframe.rect().height() * 0.9 + painter.fillRect(alertframe.rect(), QtCore.Qt.transparent) + painter.setRenderHint(QtGui.QPainter.Antialiasing) painter.fillRect( QtCore.QRect( - 0, top, alertframe.rect().width(), - alertframe.rect().height() - top), + 0, 0, alertframe.rect().width(), + alertframe.rect().height()), QtGui.QColor(alertTab.bg_color)) font = QtGui.QFont() font.setFamily(alertTab.font_face) @@ -253,24 +285,23 @@ class MainDisplay(DisplayWidget): font.setPointSize(40) painter.setFont(font) painter.setPen(QtGui.QColor(alertTab.font_color)) - x, y = (0, top) + x, y = (0, 0) metrics = QtGui.QFontMetrics(font) painter.drawText( x, y + metrics.height() - metrics.descent() - 1, text) painter.end() - self.display.setPixmap(alertframe) + self.alertDisplay.setPixmap(alertframe) + self.alertDisplay.setVisible(True) # check to see if we have a timer running if self.timer_id == 0: self.timer_id = self.startTimer(int(alertTab.timeout) * 1000) def timerEvent(self, event): if event.timerId() == self.timer_id: - if isinstance(self.frame, QtGui.QImage): - self.display.setPixmap(QtGui.QPixmap.fromImage(self.frame)) - else: - self.display.setPixmap(QtGui.QPixmap.fromImage(self.frame[u'main'])) - self.killTimer(self.timer_id) - self.timer_id = 0 + self.alertDisplay.setPixmap(self.transparent) + self.killTimer(self.timer_id) + self.timer_id = 0 + self.generateAlert() def onMediaQueue(self, message): log.debug(u'Queue new media message %s' % message) @@ -312,4 +343,4 @@ class MainDisplay(DisplayWidget): self.mediaObject.clearQueue() self.mediaLoaded = False self.video.setVisible(False) - self.display.show() \ No newline at end of file + self.display.show() diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 59dd857ac..c4d52ff57 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -30,7 +30,8 @@ import os from PyQt4 import QtCore, QtGui from PyQt4.phonon import Phonon -from openlp.core.lib import OpenLPToolbar, Receiver, str_to_bool, PluginConfig +from openlp.core.lib import OpenLPToolbar, Receiver, str_to_bool, \ +PluginConfig, resize_image class SlideList(QtGui.QTableWidget): """ @@ -235,6 +236,9 @@ class SlideController(QtGui.QWidget): self.audio = Phonon.AudioOutput(Phonon.VideoCategory, self.mediaObject) Phonon.createPath(self.mediaObject, self.video) Phonon.createPath(self.mediaObject, self.audio) + if not self.isLive: + self.video.setGeometry(QtCore.QRect(0, 0, 300, 225)) + self.video.setVisible(False) self.SlideLayout.insertWidget(0, self.video) # Actual preview screen self.SlidePreview = QtGui.QLabel(self) @@ -246,7 +250,8 @@ class SlideController(QtGui.QWidget): self.SlidePreview.sizePolicy().hasHeightForWidth()) self.SlidePreview.setSizePolicy(sizePolicy) self.SlidePreview.setFixedSize( - QtCore.QSize(self.settingsmanager.slidecontroller_image,self.settingsmanager.slidecontroller_image / 1.3 )) + QtCore.QSize(self.settingsmanager.slidecontroller_image, + self.settingsmanager.slidecontroller_image / 1.3 )) self.SlidePreview.setFrameShape(QtGui.QFrame.Box) self.SlidePreview.setFrameShadow(QtGui.QFrame.Plain) self.SlidePreview.setLineWidth(1) @@ -257,8 +262,6 @@ class SlideController(QtGui.QWidget): # Signals QtCore.QObject.connect(self.PreviewListWidget, QtCore.SIGNAL(u'clicked(QModelIndex)'), self.onSlideSelected) - QtCore.QObject.connect(self.PreviewListWidget, - QtCore.SIGNAL(u'activated(QModelIndex)'), self.onSlideSelected) if isLive: QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'update_spin_delay'), self.receiveSpinDelay) @@ -441,7 +444,9 @@ class SlideController(QtGui.QWidget): else: label = QtGui.QLabel() label.setMargin(4) - pixmap = self.parent.RenderManager.resize_image(frame[u'image']) + pixmap = resize_image(frame[u'image'], + self.parent.RenderManager.width, + self.parent.RenderManager.height) label.setScaledContents(True) label.setPixmap(QtGui.QPixmap.fromImage(pixmap)) self.PreviewListWidget.setCellWidget(framenumber, 0, label) @@ -487,11 +492,12 @@ class SlideController(QtGui.QWidget): """ Blank the screen. """ - if not self.serviceItem and self.serviceItem.is_command(): - if blanked: - Receiver.send_message(u'%s_blank'% self.serviceItem.name.lower()) - else: - Receiver.send_message(u'%s_unblank'% self.serviceItem.name.lower()) + if self.serviceItem is not None: + if self.serviceItem.is_command(): + if blanked: + Receiver.send_message(u'%s_blank'% self.serviceItem.name.lower()) + else: + Receiver.send_message(u'%s_unblank'% self.serviceItem.name.lower()) else: self.parent.mainDisplay.blankDisplay(blanked) @@ -635,7 +641,7 @@ class SlideController(QtGui.QWidget): if self.isLive: Receiver.send_message(u'%s_start' % item.name.lower(), \ [item.title, item.service_item_path, - item.get_frame_title(), slideno, self.isLive]) + item.get_frame_title(), self.isLive]) else: self.mediaObject.stop() self.mediaObject.clearQueue() @@ -663,5 +669,5 @@ class SlideController(QtGui.QWidget): else: self.mediaObject.stop() self.video.hide() - self.SlidePreview.clear() - self.SlidePreview.show() \ No newline at end of file + self.SlidePreview.clear() + self.SlidePreview.show() diff --git a/openlp/plugins/media/lib/mediaitem.py b/openlp/plugins/media/lib/mediaitem.py index f9b9f7929..d946fb819 100644 --- a/openlp/plugins/media/lib/mediaitem.py +++ b/openlp/plugins/media/lib/mediaitem.py @@ -61,8 +61,9 @@ class MediaMediaItem(MediaManagerItem): def retranslateUi(self): self.OnNewPrompt = self.trUtf8('Select Media') - self.OnNewFileMasks = self.trUtf8('Videos (*.avi *.mpeg *.mpg' - '*.mp4);;Audio (*.ogg *.mp3 *.wma);;All files (*)') + self.OnNewFileMasks = self.trUtf8('Videos (*.avi *.mpeg *.mpg *.wmv' + '*.mov *.mp4 *.flv);;Audio (*.ogg *.mp3 *.wma *.wav *.flac)' + ';;All files (*)') def requiredIcons(self): MediaManagerItem.requiredIcons(self) @@ -84,7 +85,7 @@ class MediaMediaItem(MediaManagerItem): for item in items: bitem = self.ListView.item(item.row()) filename = unicode((bitem.data(QtCore.Qt.UserRole)).toString()) - frame = u':/media/media_video.png' + frame = u':/media/image_clapperboard.png' (path, name) = os.path.split(filename) service_item.add_from_command(path, name, frame) return True @@ -110,4 +111,4 @@ class MediaMediaItem(MediaManagerItem): img = self.video_get_preview() item_name.setIcon(build_icon(img)) item_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(file)) - self.ListView.addItem(item_name) \ No newline at end of file + self.ListView.addItem(item_name) diff --git a/openlp/plugins/presentations/lib/impresscontroller.py b/openlp/plugins/presentations/lib/impresscontroller.py index cd8db7e08..9101220e0 100644 --- a/openlp/plugins/presentations/lib/impresscontroller.py +++ b/openlp/plugins/presentations/lib/impresscontroller.py @@ -169,8 +169,12 @@ class ImpressController(PresentationController): for idx in range(pages.getCount()): page = pages.getByIndex(idx) doc.getCurrentController().setCurrentPage(page) - doc.storeToURL(thumbdir + u'/' + self.thumbnailprefix + - unicode(idx+1) + u'.png', props) + path = u'%s/%s%s.png'% (thumbdir, self.thumbnailprefix, + unicode(idx+1)) + try: + doc.storeToURL(path , props) + except: + log.exception(u'%s\nUnable to store preview' % path) def create_property(self, name, value): if os.name == u'nt': @@ -230,7 +234,11 @@ class ImpressController(PresentationController): if self.presentation: self.presentation.end() self.presentation = None - self.document.dispose() + try: + self.document.dispose() + except: + #We tried! + pass self.document = None def is_loaded(self): @@ -305,4 +313,4 @@ class ImpressController(PresentationController): if os.path.isfile(path): return path else: - return None \ No newline at end of file + return None diff --git a/openlp/plugins/presentations/lib/messagelistener.py b/openlp/plugins/presentations/lib/messagelistener.py index 8094b804e..0b2fd6003 100644 --- a/openlp/plugins/presentations/lib/messagelistener.py +++ b/openlp/plugins/presentations/lib/messagelistener.py @@ -47,7 +47,7 @@ class Controller(object): log.debug(u'Live = %s, addHandler %s' % (self.isLive, file)) self.controller = controller if self.controller.is_loaded(): - self.shutdown(None) + self.shutdown() self.controller.load_presentation(file) if self.isLive: self.controller.start_presentation() @@ -73,55 +73,51 @@ class Controller(object): self.controller.goto_slide(int(slide) + 1) self.controller.poll_slidenumber(live) - def first(self, message): + def first(self): """ Based on the handler passed at startup triggers the first slide """ log.debug(u'Live = %s, first' % self.isLive) - print "first ", message if not self.isLive: return self.activate() self.controller.start_presentation() self.controller.poll_slidenumber(self.isLive) - def last(self, message): + def last(self): """ Based on the handler passed at startup triggers the first slide """ log.debug(u'Live = %s, last' % self.isLive) - print "last ", message if not self.isLive: return self.activate() self.controller.goto_slide(self.controller.get_slide_count()) self.controller.poll_slidenumber(self.isLive) - def next(self, message): + def next(self): """ Based on the handler passed at startup triggers the next slide event """ log.debug(u'Live = %s, next' % self.isLive) - print "next ", message if not self.isLive: return self.activate() self.controller.next_step() self.controller.poll_slidenumber(self.isLive) - def previous(self, message): + def previous(self): """ Based on the handler passed at startup triggers the previous slide event """ log.debug(u'Live = %s, previous' % self.isLive) if not self.isLive: return - print "previous ", message self.activate() self.controller.previous_step() self.controller.poll_slidenumber(self.isLive) - def shutdown(self, message): + def shutdown(self): """ Based on the handler passed at startup triggers slide show to shut down """ @@ -159,7 +155,6 @@ class MessageListener(object): self.controllers = controllers self.previewHandler = Controller(False) self.liveHandler = Controller(True) - self.isLive = None # messages are sent from core.ui.slidecontroller QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'presentations_start'), self.startup) @@ -202,36 +197,36 @@ class MessageListener(object): else: self.previewHandler.slide(slide, live) - def first(self, message): - if self.isLive: - self.liveHandler.first(message) + def first(self, isLive): + if isLive: + self.liveHandler.first() else: - self.previewHandler.first(message) + self.previewHandler.first() - def last(self, message): - if self.isLive: - self.liveHandler.last(message) + def last(self, isLive): + if isLive: + self.liveHandler.last() else: - self.previewHandler.last(message) + self.previewHandler.last() - def next(self, message): - if self.isLive: - self.liveHandler.next(message) + def next(self, isLive): + if isLive: + self.liveHandler.next() else: - self.previewHandler.next(message) + self.previewHandler.next() - def previous(self, message): - if self.isLive: - self.liveHandler.previous(message) + def previous(self, isLive): + if isLive: + self.liveHandler.previous() else: - self.previewHandler.previous(message) + self.previewHandler.previous() - def shutdown(self, message): - if self.isLive: - self.liveHandler.shutdown(message) + def shutdown(self, isLive): + if isLive: + self.liveHandler.shutdown() Receiver.send_message(u'live_slide_show') else: - self.previewHandler.shutdown(message) + self.previewHandler.shutdown() def blank(self): if self.isLive: @@ -268,4 +263,4 @@ class MessageListener(object): return message[0], file, message[4] def timeout(self): - self.controller.poll_slidenumber(self.is_live) \ No newline at end of file + self.controller.poll_slidenumber(self.is_live) diff --git a/resources/images/openlp-2.qrc b/resources/images/openlp-2.qrc index 8fa38c42b..4bea9e51b 100644 --- a/resources/images/openlp-2.qrc +++ b/resources/images/openlp-2.qrc @@ -108,6 +108,7 @@ media_video.png media_time.png media_stop.png + image_clapperboard.png messagebox_critical.png From ee8a92b77fb459cc17d9cb906b98b4ba08ed35b8 Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Sun, 10 Jan 2010 11:49:04 +0000 Subject: [PATCH 004/164] fix screen lines calcs so they are correct. Prints added for rendering size diagnostics. --- openlp/core/lib/renderer.py | 20 +++++++++++++------- openlp/core/lib/rendermanager.py | 14 +++++++------- openlp/core/lib/serviceitem.py | 6 ++++-- openlp/core/ui/amendthemeform.py | 10 ++++++++-- openlp/core/ui/maindisplay.py | 7 +++++++ openlp/core/ui/mainwindow.py | 3 ++- openlp/core/ui/slidecontroller.py | 1 + 7 files changed, 42 insertions(+), 19 deletions(-) diff --git a/openlp/core/lib/renderer.py b/openlp/core/lib/renderer.py index e3e275193..30f5c2dee 100644 --- a/openlp/core/lib/renderer.py +++ b/openlp/core/lib/renderer.py @@ -42,7 +42,7 @@ class Renderer(object): Initialise the renderer. """ self._rect = None - self._debug = 0 + self._debug = True self._right_margin = 64 # the amount of right indent self._display_shadow_size_footer = 0 self._display_outline_size_footer = 0 @@ -148,17 +148,22 @@ class Renderer(object): def pre_render_text(self, text): metrics = QtGui.QFontMetrics(self.mainFont) - #take the width work out approx how many characters and add 50% + #work out line width line_width = self._rect.width() - self._right_margin #number of lines on a page - adjust for rounding up. - page_length = int(self._rect.height() / metrics.height() - 2 ) - 1 + line_height = metrics.height() + if self._theme.display_shadow: + line_height += int(self._theme.display_shadow_size) + if self._theme.display_outline: + # pixels top/bottom + line_height += 2 * int(self._theme.display_outline_size) + page_length = int(self._rect.height() / line_height ) #Average number of characters in line ave_line_width = line_width / metrics.averageCharWidth() #Maximum size of a character max_char_width = metrics.maxWidth() - #Min size of a character - min_char_width = metrics.width(u'i') - char_per_line = line_width / min_char_width + #Max characters pre line based on min size of a character + char_per_line = line_width / metrics.width(u'i') log.debug(u'Page Length area height %s , metrics %s , lines %s' % (int(self._rect.height()), metrics.height(), page_length )) split_pages = [] @@ -221,6 +226,7 @@ class Renderer(object): """ self._rect = rect_main self._rect_footer = rect_footer + print "render = ", self._rect def generate_frame_from_lines(self, lines, footer_lines=None): """ @@ -567,7 +573,7 @@ class Renderer(object): x, y = tlcorner metrics = QtGui.QFontMetrics(font) w = metrics.width(line) - h = metrics.height() - 2 + h = metrics.height() if draw: self.painter.drawText(x, y + metrics.ascent(), line) if self._theme.display_slideTransition: diff --git a/openlp/core/lib/rendermanager.py b/openlp/core/lib/rendermanager.py index 1bd2b1400..cbbc6be26 100644 --- a/openlp/core/lib/rendermanager.py +++ b/openlp/core/lib/rendermanager.py @@ -160,8 +160,8 @@ class RenderManager(object): def build_text_rectangle(self, theme): """ - Builds a text block using the settings in ``theme``. - One is needed per slide + Builds a text block using the settings in ``theme`` + and the size of the display screen.height. ``theme`` The theme to build a text block for. @@ -170,14 +170,14 @@ class RenderManager(object): main_rect = None footer_rect = None if not theme.font_main_override: - main_rect = QtCore.QRect(10, 0, self.width - 1, - self.footer_start - 20) + main_rect = QtCore.QRect(10, 0, + self.width - 1, self.footer_start) else: main_rect = QtCore.QRect(theme.font_main_x, theme.font_main_y, theme.font_main_width - 1, theme.font_main_height - 1) if not theme.font_footer_override: - footer_rect = QtCore.QRect(10, self.footer_start, self.width - 1, - self.height-self.footer_start) + footer_rect = QtCore.QRect(10, self.footer_start, + self.width - 1, self.height - self.footer_start) else: footer_rect = QtCore.QRect(theme.font_footer_x, theme.font_footer_y, theme.font_footer_width - 1, @@ -192,7 +192,7 @@ class RenderManager(object): The theme to generated a preview for. """ log.debug(u'generate preview') - #set the defaukt image size for previews + #set the default image size for previews self.calculate_default(QtCore.QSize(1024, 768)) self.renderer.set_theme(themedata) self.build_text_rectangle(themedata) diff --git a/openlp/core/lib/serviceitem.py b/openlp/core/lib/serviceitem.py index 19cbe37c2..603d7cadb 100644 --- a/openlp/core/lib/serviceitem.py +++ b/openlp/core/lib/serviceitem.py @@ -102,11 +102,13 @@ class ServiceItem(object): formated = self.RenderManager.format_slide(slide[u'raw_slide']) for format in formated: lines = u'' + title = u'' for line in format: + if title == u'': + title = line lines += line + u'\n' - title = lines.split(u'\n')[0] self._display_frames.append({u'title': title, \ - u'text': lines, u'verseTag': slide[u'verseTag'] }) + u'text': lines.rstrip(), u'verseTag': slide[u'verseTag'] }) log.log(15, u'Formatting took %4s' % (time.time() - before)) elif self.service_item_type == ServiceItemType.Image: for slide in self._raw_frames: diff --git a/openlp/core/ui/amendthemeform.py b/openlp/core/ui/amendthemeform.py index 24a22a7cf..97eecd1e8 100644 --- a/openlp/core/ui/amendthemeform.py +++ b/openlp/core/ui/amendthemeform.py @@ -694,8 +694,14 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog): if self.allowPreview: #calculate main number of rows metrics = self._getThemeMetrics() + line_height = metrics.height() + if self.theme.display_shadow: + line_height += int(self.theme.display_shadow_size) + if self.theme.display_outline: + # pixels top/bottom + line_height += 2 * int(self.theme.display_outline_size) page_length = \ - (self.FontMainHeightSpinBox.value() / metrics.height() - 2) - 1 + ((self.FontMainHeightSpinBox.value()) / line_height ) log.debug(u'Page Length area height %s, metrics %s, lines %s' % (self.FontMainHeightSpinBox.value(), metrics.height(), page_length)) @@ -719,4 +725,4 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog): if self.theme.font_main_width < metrics.maxWidth() * 2 + 64: self.theme.font_main_width = metrics.maxWidth() * 2 + 64 self.FontMainWidthSpinBox.setValue(self.theme.font_main_width) - return metrics \ No newline at end of file + return metrics diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index 4ad334f8b..019c614d7 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -139,6 +139,7 @@ class MainDisplay(DisplayWidget): @param (integer) screen This is the screen number. """ log.debug(u'Setup %s for %s ' %(self.screens, screenNumber)) + print "all the screen ", self.screens self.setVisible(False) self.screen = self.screens[screenNumber] if self.screen[u'number'] != screenNumber: @@ -190,7 +191,12 @@ class MainDisplay(DisplayWidget): The alert displays are set to 10% of the screen as the video display is unable to handle transparent pixmaps. This is a problem with QT. """ + print "--------- Set screen geom ------------" + print "display ", self.screen[u'size'] + print "main geom before ", self.geometry() self.setGeometry(self.screen[u'size']) + print "main geom after ", self.geometry() + print "display geom", self.display.geometry() self.alertScreenPosition = self.screen[u'size'].height() * 0.9 self.alertHeight = self.screen[u'size'].height() - self.alertScreenPosition self.alertDisplay.setGeometry( @@ -221,6 +227,7 @@ class MainDisplay(DisplayWidget): elif not self.displayBlank: if transition: if self.hasTransition: + print len(self.frame[u'trans']) if self.frame[u'trans'] is not None: self.display.setPixmap(QtGui.QPixmap.fromImage(self.frame[u'trans'])) self.repaint() diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 854811484..a508189de 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -614,6 +614,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): self.settingsForm.exec_() updated_display = self.getMonitorNumber() if updated_display != self.RenderManager.current_display: + print "main display screen changed to ", updated_display self.RenderManager.update_display(updated_display) self.mainDisplay.setup(updated_display) self.activateWindow() @@ -704,4 +705,4 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): def togglePreviewPanel(self): previewBool = self.PreviewController.Panel.isVisible() self.PreviewController.Panel.setVisible(not previewBool) - self.settingsmanager.togglePreviewPanel(not previewBool) \ No newline at end of file + self.settingsmanager.togglePreviewPanel(not previewBool) diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index c4d52ff57..34c66eacd 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -441,6 +441,7 @@ class SlideController(QtGui.QWidget): self.SongMenu.menu().addAction(self.trUtf8(u'%s'%tag), self.onSongBarHandler) item.setText(frame[u'text']) + #print {u'x':frame[u'text']} else: label = QtGui.QLabel() label.setMargin(4) From 85bada91ba69e8addf24476c8bbf6a97774b9953 Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Sun, 10 Jan 2010 19:03:52 +0000 Subject: [PATCH 005/164] Move towards sorting out renderer text sizes --- openlp/core/lib/renderer.py | 2 +- openlp/core/lib/rendermanager.py | 6 ++- openlp/core/ui/maindisplay.py | 73 +++++++++++++++----------------- 3 files changed, 39 insertions(+), 42 deletions(-) diff --git a/openlp/core/lib/renderer.py b/openlp/core/lib/renderer.py index 30f5c2dee..f6da3f6b1 100644 --- a/openlp/core/lib/renderer.py +++ b/openlp/core/lib/renderer.py @@ -226,7 +226,6 @@ class Renderer(object): """ self._rect = rect_main self._rect_footer = rect_footer - print "render = ", self._rect def generate_frame_from_lines(self, lines, footer_lines=None): """ @@ -245,6 +244,7 @@ class Renderer(object): bbox1 = self._render_lines_unaligned(footer_lines, True) # reset the frame. first time do not worry about what you paint on. self._frame = QtGui.QImage(self.bg_frame) + print "generate ", self._frame.size() self._frameOp = QtGui.QImage(self.bg_frame) x, y = self._correctAlignment(self._rect, bbox) bbox = self._render_lines_unaligned(lines, False, (x, y), True) diff --git a/openlp/core/lib/rendermanager.py b/openlp/core/lib/rendermanager.py index cbbc6be26..c4114580b 100644 --- a/openlp/core/lib/rendermanager.py +++ b/openlp/core/lib/rendermanager.py @@ -83,6 +83,7 @@ class RenderManager(object): self.current_display = screen_number self.calculate_default( self.screen_list[self.current_display][u'size']) + self.renderer.bg_frame = None def set_global_theme(self, global_theme, theme_level=ThemeLevel.Global): """ @@ -141,7 +142,7 @@ class RenderManager(object): self.screen_list[self.current_display][u'size']) self.renderer.set_theme(self.themedata) self.build_text_rectangle(self.themedata) - #Replace the backgrount image from renderer with one from image + #Replace the background image from renderer with one from image if self.override_background: if self.save_bg_frame is None: self.save_bg_frame = self.renderer.bg_frame @@ -182,6 +183,7 @@ class RenderManager(object): footer_rect = QtCore.QRect(theme.font_footer_x, theme.font_footer_y, theme.font_footer_width - 1, theme.font_footer_height - 1) + print "build_text_rectangle", main_rect self.renderer.set_text_rectangle(main_rect, footer_rect) def generate_preview(self, themedata): @@ -235,6 +237,7 @@ class RenderManager(object): """ log.debug(u'generate slide') self.build_text_rectangle(self.themedata) + print "set_frame_dest", self.width, self.height self.renderer.set_frame_dest(self.width, self.height) return self.renderer.generate_frame_from_lines(main_text, footer_text) @@ -253,3 +256,4 @@ class RenderManager(object): self.width, self.height, self.screen_ratio ) # 90% is start of footer self.footer_start = int(self.height * 0.90) + print "calculate_default ", self.width, self.height, self.footer_start diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index 019c614d7..3d545c2ec 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -90,22 +90,16 @@ class MainDisplay(DisplayWidget): self.parent = parent self.setWindowTitle(u'OpenLP Display') self.screens = screens -# self.layout = QtGui.QVBoxLayout(self) -# self.layout.setSpacing(0) -# self.layout.setMargin(0) -# self.layout.setObjectName(u'layout') self.mediaObject = Phonon.MediaObject(self) self.video = Phonon.VideoWidget() self.video.setVisible(False) self.audio = Phonon.AudioOutput(Phonon.VideoCategory, self.mediaObject) Phonon.createPath(self.mediaObject, self.video) Phonon.createPath(self.mediaObject, self.audio) - #self.layout.insertWidget(0, self.video) self.display = QtGui.QLabel(self) self.display.setScaledContents(True) self.alertDisplay = QtGui.QLabel(self) self.alertDisplay.setScaledContents(True) - #self.layout.insertWidget(0, self.display) self.primary = True self.displayBlank = False self.blankFrame = None @@ -150,7 +144,21 @@ class MainDisplay(DisplayWidget): if scrn[u'number'] == screenNumber: self.screen = scrn break - self.setScreenGeometry() + #Sort out screen locations and sizes + print "--------- Set screen geom ------------" + print "display ", self.screen[u'size'] + self.setGeometry(self.screen[u'size']) + print "main geom", self.geometry() + print "display geom 1", self.display.geometry() + self.alertScreenPosition = self.screen[u'size'].height() * 0.9 + self.alertHeight = self.screen[u'size'].height() - self.alertScreenPosition + self.alertDisplay.setGeometry( + QtCore.QRect(0, self.alertScreenPosition, + self.screen[u'size'].width(),self.alertHeight)) + self.video.setGeometry(self.screen[u'size']) + self.display.resize(self.screen[u'size'].width(), + self.screen[u'size'].height()) + print "display geom 2", self.display.geometry() #Build a custom splash screen self.InitialFrame = QtGui.QImage( self.screen[u'size'].width(), @@ -172,7 +180,7 @@ class MainDisplay(DisplayWidget): self.screen[u'size'].height(), QtGui.QImage.Format_ARGB32_Premultiplied) painter.begin(self.blankFrame) - painter.fillRect(self.blankFrame.rect(), QtCore.Qt.black) + painter.fillRect(self.blankFrame.rect(), QtCore.Qt.red) #buid a blank transparent image self.transparent = QtGui.QPixmap(self.screen[u'size'].width(), self.screen[u'size'].height()) @@ -185,25 +193,6 @@ class MainDisplay(DisplayWidget): self.setVisible(False) self.primary = True - def setScreenGeometry(self): - """ - Define and set up the display sizes. - The alert displays are set to 10% of the screen as the video display - is unable to handle transparent pixmaps. This is a problem with QT. - """ - print "--------- Set screen geom ------------" - print "display ", self.screen[u'size'] - print "main geom before ", self.geometry() - self.setGeometry(self.screen[u'size']) - print "main geom after ", self.geometry() - print "display geom", self.display.geometry() - self.alertScreenPosition = self.screen[u'size'].height() * 0.9 - self.alertHeight = self.screen[u'size'].height() - self.alertScreenPosition - self.alertDisplay.setGeometry( - QtCore.QRect(0, self.alertScreenPosition, - self.screen[u'size'].width(),self.alertHeight)) - self.video.setGeometry(self.screen[u'size']) - def resetDisplay(self): if self.primary: self.setVisible(False) @@ -222,27 +211,31 @@ class MainDisplay(DisplayWidget): ``frame`` Image frame to be rendered """ - if self.timer_id != 0 : - self.displayAlert() - elif not self.displayBlank: +# if self.timer_id != 0 : +# self.displayAlert() + print "render display start ", self.display.geometry() + if not self.displayBlank: if transition: - if self.hasTransition: - print len(self.frame[u'trans']) - if self.frame[u'trans'] is not None: - self.display.setPixmap(QtGui.QPixmap.fromImage(self.frame[u'trans'])) - self.repaint() - if frame[u'trans'] is not None: - self.display.setPixmap(QtGui.QPixmap.fromImage(frame[u'trans'])) - self.repaint() - self.hasTransition = True + if self.frame is not None: + print "render frame 1 ", self.frame.size() + self.display.setPixmap(QtGui.QPixmap.fromImage(self.frame)) + self.repaint() + self.frame = None + if frame[u'trans'] is not None: + print "render frame 2 ", frame[u'trans'].size() + self.display.setPixmap(QtGui.QPixmap.fromImage(frame[u'trans'])) + self.repaint() + self.frame = frame[u'trans'] + print "render frame 3 ", frame[u'main'].size() self.display.setPixmap(QtGui.QPixmap.fromImage(frame[u'main'])) self.repaint() else: + print "render frame 3 ", frame.size() self.display.setPixmap(QtGui.QPixmap.fromImage(frame)) if not self.isVisible(): self.setVisible(True) self.showFullScreen() - self.frame = frame + print "render display end ", self.display.geometry() def blankDisplay(self, blanked=True): if blanked: From 027d6d3e4e7a3fc91af217a8b53a0fe42c327ece Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Mon, 11 Jan 2010 17:03:16 +0200 Subject: [PATCH 006/164] Moved the stop import out of the base class, since Qt doesn't like it. --- openlp/plugins/bibles/lib/csv.py | 9 +++++++++ openlp/plugins/bibles/lib/db.py | 12 +----------- openlp/plugins/bibles/lib/opensong.py | 9 +++++++++ openlp/plugins/bibles/lib/osis.py | 9 +++++++++ 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/openlp/plugins/bibles/lib/csv.py b/openlp/plugins/bibles/lib/csv.py index c74452fd3..099b3f1fa 100644 --- a/openlp/plugins/bibles/lib/csv.py +++ b/openlp/plugins/bibles/lib/csv.py @@ -50,6 +50,15 @@ class CSVBible(BibleDB): if u'versesfile' not in kwargs: raise KeyError(u'You have to supply a file to import verses from.') self.versesfile = kwargs[u'versesfile'] + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'openlpstopimport'), self.stop_import) + + def stop_import(self): + """ + Stops the import of the Bible. + """ + log.debug('Stopping import!') + self.stop_import = True def do_import(self): #Populate the Tables diff --git a/openlp/plugins/bibles/lib/db.py b/openlp/plugins/bibles/lib/db.py index b1e228ee4..088bd68e8 100644 --- a/openlp/plugins/bibles/lib/db.py +++ b/openlp/plugins/bibles/lib/db.py @@ -29,8 +29,7 @@ import logging from sqlalchemy import or_ from PyQt4 import QtCore -from openlp.core.lib import Receiver -from models import * +from openlp.plugins.bibles.lib.models import * log = logging.getLogger(__name__) @@ -92,18 +91,9 @@ class BibleDB(): may want to override this method to supply their own custom initialisation as well. """ - QtCore.QObject.connect(Receiver.get_receiver(), - QtCore.SIGNAL(u'openlpstopimport'), self.stop_import) self.create_tables() return self.name - def stop_import(self): - """ - Stops the import of the Bible. - """ - log.debug('Stopping import!') - self.stop_import = True - def commit(self): log.debug('Committing...') self.session.commit() diff --git a/openlp/plugins/bibles/lib/opensong.py b/openlp/plugins/bibles/lib/opensong.py index 2442040de..a73d0e3c6 100644 --- a/openlp/plugins/bibles/lib/opensong.py +++ b/openlp/plugins/bibles/lib/opensong.py @@ -52,6 +52,15 @@ class OpenSongBible(BibleDB): if u'filename' not in kwargs: raise KeyError(u'You have to supply a file name to import from.') self.filename = kwargs[u'filename'] + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'openlpstopimport'), self.stop_import) + + def stop_import(self): + """ + Stops the import of the Bible. + """ + log.debug('Stopping import!') + self.stop_import = True def do_import(self): """ diff --git a/openlp/plugins/bibles/lib/osis.py b/openlp/plugins/bibles/lib/osis.py index 17cb34180..f16c1cd02 100644 --- a/openlp/plugins/bibles/lib/osis.py +++ b/openlp/plugins/bibles/lib/osis.py @@ -81,6 +81,15 @@ class OSISBible(BibleDB): finally: if fbibles: fbibles.close() + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'openlpstopimport'), self.stop_import) + + def stop_import(self): + """ + Stops the import of the Bible. + """ + log.debug('Stopping import!') + self.stop_import = True def do_import(self): """ From 9dc38cf289edc6217079cf8e350e08d68c72203b Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Sat, 16 Jan 2010 07:22:50 +0000 Subject: [PATCH 007/164] Refactor the screen handling code to try and set the screen sizes correctly. --- openlp.pyw | 8 +- openlp/core/lib/rendermanager.py | 30 +++---- openlp/core/lib/settingsmanager.py | 4 +- openlp/core/ui/__init__.py | 4 +- openlp/core/ui/generaltab.py | 4 +- openlp/core/ui/maindisplay.py | 10 +-- openlp/core/ui/mainwindow.py | 15 ++-- openlp/core/ui/screen.py | 74 ++++++++++++++++++ openlp/core/ui/slidecontroller.py | 2 +- .../presentations/lib/impresscontroller.py | 2 +- resources/images/image_clapperboard.png | Bin 0 -> 191834 bytes 11 files changed, 104 insertions(+), 49 deletions(-) create mode 100644 openlp/core/ui/screen.py create mode 100644 resources/images/image_clapperboard.png diff --git a/openlp.pyw b/openlp.pyw index 8b847c0ec..2060f09e1 100755 --- a/openlp.pyw +++ b/openlp.pyw @@ -34,7 +34,7 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import Receiver, str_to_bool from openlp.core.resources import qInitResources -from openlp.core.ui import MainWindow, SplashScreen +from openlp.core.ui import MainWindow, SplashScreen, Screen from openlp.core.utils import ConfigHelper log = logging.getLogger() @@ -117,10 +117,10 @@ class OpenLP(QtGui.QApplication): self.splash.show() # make sure Qt really display the splash screen self.processEvents() - screens = [] + screens = Screen() # Decide how many screens we have and their size for screen in xrange(0, self.desktop().numScreens()): - screens.append({u'number': screen, + screens.add_screen({u'number': screen, u'size': self.desktop().availableGeometry(screen), u'primary': (self.desktop().primaryScreen() == screen)}) log.info(u'Screen %d found with resolution %s', @@ -182,4 +182,4 @@ if __name__ == u'__main__': """ Instantiate and run the application. """ - main() \ No newline at end of file + main() diff --git a/openlp/core/lib/rendermanager.py b/openlp/core/lib/rendermanager.py index c4114580b..d8621b345 100644 --- a/openlp/core/lib/rendermanager.py +++ b/openlp/core/lib/rendermanager.py @@ -39,8 +39,8 @@ class RenderManager(object): ``theme_manager`` The ThemeManager instance, used to get the current theme details. - ``screen_list`` - The list of screens available. + ``screens`` + Contains information about the Screens. ``screen_number`` Defaults to *0*. The index of the output/display screen. @@ -49,20 +49,16 @@ class RenderManager(object): log = logging.getLogger(u'RenderManager') log.info(u'RenderManager Loaded') - def __init__(self, theme_manager, screen_list, screen_number=0): + def __init__(self, theme_manager, screens, screen_number=0): """ Initialise the render manager. """ log.debug(u'Initilisation started') - self.screen_list = screen_list + self.screens = screens self.theme_manager = theme_manager - self.displays = len(screen_list) - if (screen_number + 1) > len(screen_list): - self.current_display = 0 - else: - self.current_display = screen_number self.renderer = Renderer() - self.calculate_default(self.screen_list[self.current_display][u'size']) + self.screens.set_current_display(screen_number) + self.calculate_default(self.screens.current[u'size']) self.theme = u'' self.service_theme = u'' self.theme_level = u'' @@ -79,11 +75,8 @@ class RenderManager(object): The updated index of the output/display screen. """ log.debug(u'Update Display') - if self.current_display != screen_number: - self.current_display = screen_number - self.calculate_default( - self.screen_list[self.current_display][u'size']) - self.renderer.bg_frame = None + self.calculate_default(self.screens.current[u'size']) + self.renderer.bg_frame = None def set_global_theme(self, global_theme, theme_level=ThemeLevel.Global): """ @@ -138,8 +131,7 @@ class RenderManager(object): if self.theme != self.renderer.theme_name or self.themedata is None: log.debug(u'theme is now %s', self.theme) self.themedata = self.theme_manager.getThemeData(self.theme) - self.calculate_default( - self.screen_list[self.current_display][u'size']) + self.calculate_default(self.screens.current[u'size']) self.renderer.set_theme(self.themedata) self.build_text_rectangle(self.themedata) #Replace the background image from renderer with one from image @@ -195,12 +187,12 @@ class RenderManager(object): """ log.debug(u'generate preview') #set the default image size for previews - self.calculate_default(QtCore.QSize(1024, 768)) + self.calculate_default(self.screens.preview[u'size']) self.renderer.set_theme(themedata) self.build_text_rectangle(themedata) self.renderer.set_frame_dest(self.width, self.height, True) #Reset the real screen size for subsequent render requests - self.calculate_default(self.screen_list[self.current_display][u'size']) + self.calculate_default(self.screens.current[u'size']) verse = u'Amazing Grace!\n'\ 'How sweet the sound\n'\ 'To save a wretch like me;\n'\ diff --git a/openlp/core/lib/settingsmanager.py b/openlp/core/lib/settingsmanager.py index 580ec9b31..be5c14af1 100644 --- a/openlp/core/lib/settingsmanager.py +++ b/openlp/core/lib/settingsmanager.py @@ -33,7 +33,7 @@ class SettingsManager(object): individual components. """ def __init__(self, screen): - self.screen = screen[0] + self.screen = screen.current self.width = self.screen[u'size'].width() self.height = self.screen[u'size'].height() self.mainwindow_height = self.height * 0.8 @@ -72,4 +72,4 @@ class SettingsManager(object): u'media manager', isVisible) def togglePreviewPanel(self, isVisible): - ConfigHelper.set_config(u'user interface', u'preview panel', isVisible) \ No newline at end of file + ConfigHelper.set_config(u'user interface', u'preview panel', isVisible) diff --git a/openlp/core/ui/__init__.py b/openlp/core/ui/__init__.py index 6b187f5fc..a2252bc41 100644 --- a/openlp/core/ui/__init__.py +++ b/openlp/core/ui/__init__.py @@ -23,7 +23,7 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### -#from slidecontroller import MasterToolbar +from screen import Screen from maindisplay import MainDisplay from amendthemeform import AmendThemeForm from slidecontroller import SlideController @@ -42,4 +42,4 @@ from mainwindow import MainWindow __all__ = ['SplashScreen', 'AboutForm', 'SettingsForm', 'MainWindow', 'MainDisplay', 'SlideController', 'ServiceManager', 'ThemeManager', - 'AmendThemeForm', 'MediaDockManager', 'ThemeLevel'] \ No newline at end of file + 'AmendThemeForm', 'MediaDockManager', 'ThemeLevel'] diff --git a/openlp/core/ui/generaltab.py b/openlp/core/ui/generaltab.py index b5abc2a2e..d821c0f45 100644 --- a/openlp/core/ui/generaltab.py +++ b/openlp/core/ui/generaltab.py @@ -183,7 +183,7 @@ class GeneralTab(SettingsTab): self.Password = self.PasswordEdit.displayText() def load(self): - for screen in self.screen_list: + for screen in self.screen_list.screen_list: screen_name = u'%s %d' % (self.trUtf8('Screen'), screen[u'number'] + 1) if screen[u'primary']: screen_name = u'%s (%s)' % (screen_name, self.trUtf8('primary')) @@ -215,4 +215,4 @@ class GeneralTab(SettingsTab): self.config.set_config(u'save prompt', self.PromptSaveService) self.config.set_config(u'ccli number', self.CCLINumber) self.config.set_config(u'songselect username', self.Username) - self.config.set_config(u'songselect password', self.Password) \ No newline at end of file + self.config.set_config(u'songselect password', self.Password) diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index 3d545c2ec..505e728da 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -135,15 +135,7 @@ class MainDisplay(DisplayWidget): log.debug(u'Setup %s for %s ' %(self.screens, screenNumber)) print "all the screen ", self.screens self.setVisible(False) - self.screen = self.screens[screenNumber] - if self.screen[u'number'] != screenNumber: - # We will most probably never actually hit this bit, but just in - # case the index in the list doesn't match the screen number, we - # search for it. - for scrn in self.screens: - if scrn[u'number'] == screenNumber: - self.screen = scrn - break + self.screen = self.screens.current #Sort out screen locations and sizes print "--------- Set screen geom ------------" print "display ", self.screen[u'size'] diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index a508189de..cbd2e0ad7 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -425,7 +425,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): plugins. """ QtGui.QMainWindow.__init__(self) - self.screenList = screens + self.screens = screens self.applicationVersion = applicationVersion self.serviceNotSaved = False self.settingsmanager = SettingsManager(screens) @@ -433,7 +433,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): self.mainDisplay = MainDisplay(self, screens) self.alertForm = AlertForm(self) self.aboutForm = AboutForm(self, applicationVersion) - self.settingsForm = SettingsForm(self.screenList, self, self) + self.settingsForm = SettingsForm(self.screens, self, self) # Set up the path with plugins pluginpath = os.path.split(os.path.abspath(__file__))[0] pluginpath = os.path.abspath( @@ -500,7 +500,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): #RenderManager needs to call ThemeManager and #ThemeManager needs to call RenderManager self.RenderManager = RenderManager(self.ThemeManagerContents, - self.screenList, self.getMonitorNumber()) + self.screens, self.getMonitorNumber()) #Define the media Dock Manager self.mediaDockManager = MediaDockManager(self.MediaToolBox) log.info(u'Load Plugins') @@ -558,11 +558,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): monitor number does not exist. """ screen_number = int(self.generalConfig.get_config(u'monitor', 0)) - monitor_exists = False - for screen in self.screenList: - if screen[u'number'] == screen_number: - monitor_exists = True - if not monitor_exists: + if not self.screens.screen_exists(screen_number): screen_number = 0 return screen_number @@ -613,8 +609,9 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): """ self.settingsForm.exec_() updated_display = self.getMonitorNumber() - if updated_display != self.RenderManager.current_display: + if updated_display != self.screens.current_display: print "main display screen changed to ", updated_display + self.screens.set_current_display(updated_display) self.RenderManager.update_display(updated_display) self.mainDisplay.setup(updated_display) self.activateWindow() diff --git a/openlp/core/ui/screen.py b/openlp/core/ui/screen.py new file mode 100644 index 000000000..0aa06bfc0 --- /dev/null +++ b/openlp/core/ui/screen.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard # +# --------------------------------------------------------------------------- # +# 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 # +############################################################################### +import logging + +class Screen(object): + """ + Wrapper to handle the parameters of the display screen + """ + global log + log = logging.getLogger(u'Screen') + log.info(u'Screen loaded') + + def __init__(self): + self.preview = None + self.current = None + self.screen_list = [] + self.count = 0 + self.current_display = 0 + + def add_screen(self, screen): + if screen[u'primary'] == True: + self.current = screen + self.screen_list.append(screen) + self.count += 1 + print self.screen_list + + def screen_exists(self, number): + for screen in self.screen_list: + if screen[u'number'] == number: + return True + return False + + def set_current_display(self, number): + if number + 1 > self.count: + self.current = self.screen_list[0] + self.current_display = 0 + else: + self.current = self.screen_list[number] + self.preview = self.current + self.current_display = number + if self.count == 1: + self.preview = self.screen_list[0] + +# if self.screen[u'number'] != screenNumber: +# # We will most probably never actually hit this bit, but just in +# # case the index in the list doesn't match the screen number, we +# # search for it. +# for scrn in self.screens: +# if scrn[u'number'] == screenNumber: +# self.screen = scrn +# break + diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 34c66eacd..da7b98bfc 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -538,7 +538,7 @@ class SlideController(QtGui.QWidget): def updatePreview(self): rm = self.parent.RenderManager - if not rm.screen_list[rm.current_display][u'primary']: + if not rm.screens.current[u'primary']: # Grab now, but try again in a couple of seconds if slide change is slow QtCore.QTimer.singleShot(0.5, self.grabMainDisplay) QtCore.QTimer.singleShot(2.5, self.grabMainDisplay) diff --git a/openlp/plugins/presentations/lib/impresscontroller.py b/openlp/plugins/presentations/lib/impresscontroller.py index 9101220e0..28c6690e3 100644 --- a/openlp/plugins/presentations/lib/impresscontroller.py +++ b/openlp/plugins/presentations/lib/impresscontroller.py @@ -145,7 +145,7 @@ class ImpressController(PresentationController): log.exception(u'Failed to load presentation') return self.presentation = self.document.getPresentation() - self.presentation.Display = self.plugin.render_manager.current_display + 1 + self.presentation.Display = self.plugin.render_manager.screens.current_display + 1 self.controller = None self.create_thumbnails() diff --git a/resources/images/image_clapperboard.png b/resources/images/image_clapperboard.png new file mode 100644 index 0000000000000000000000000000000000000000..e4605691442bbb33be05ae054c32168f7ab2b6ec GIT binary patch literal 191834 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 Date: Sun, 17 Jan 2010 18:48:45 +0200 Subject: [PATCH 008/164] A few fixes after renaming some functions. --- openlp/plugins/bibles/lib/csv.py | 5 +- openlp/plugins/bibles/lib/db.py | 11 +++-- openlp/plugins/bibles/lib/http.py | 3 +- openlp/plugins/bibles/lib/manager.py | 69 ++++++++++++++------------- openlp/plugins/bibles/lib/opensong.py | 25 ++++++---- openlp/plugins/bibles/lib/osis.py | 5 +- 6 files changed, 68 insertions(+), 50 deletions(-) diff --git a/openlp/plugins/bibles/lib/csv.py b/openlp/plugins/bibles/lib/csv.py index ac53f46cc..cd3b7efeb 100644 --- a/openlp/plugins/bibles/lib/csv.py +++ b/openlp/plugins/bibles/lib/csv.py @@ -106,7 +106,7 @@ class CSVBible(BibleDB): self.wizard.incrementProgressBar( u'Importing %s %s' % book.name) self.commit() - self.add_verse(book.id, p[1], p[2], p3) + self.create_verse(book.id, p[1], p[2], p3) Receiver.send_message(u'process_events') self.commit() except: @@ -119,4 +119,5 @@ class CSVBible(BibleDB): self.wizard.incrementProgressBar(u'Import canceled!') return False else: - return success \ No newline at end of file + return success + diff --git a/openlp/plugins/bibles/lib/db.py b/openlp/plugins/bibles/lib/db.py index cf272d6e9..6de5217bb 100644 --- a/openlp/plugins/bibles/lib/db.py +++ b/openlp/plugins/bibles/lib/db.py @@ -25,6 +25,7 @@ import os import logging +import chardet from sqlalchemy import or_ from PyQt4 import QtCore @@ -84,13 +85,14 @@ class BibleDB(): self.metadata, self.session = init_models(db_url) self.metadata.create_all(checkfirst=True) - def register(self): + def register(self, wizard): """ This method basically just initialialises the database. It is called from the Bible Manager when a Bible is imported. Descendant classes may want to override this method to supply their own custom initialisation as well. """ + self.wizard = wizard self.create_tables() return self.name @@ -100,7 +102,7 @@ class BibleDB(): def create_tables(self): log.debug(u'createTables') - self.save_meta(u'dbversion', u'2') + self.create_meta(u'dbversion', u'2') self.create_testament(u'Old Testament') self.create_testament(u'New Testament') self.create_testament(u'Apocrypha') @@ -111,7 +113,7 @@ class BibleDB(): self.commit() def create_book(self, name, abbrev, testament=1): - log.debug(u'create_book %s,%s', bookname, bookabbrev) + log.debug(u'create_book %s,%s', name, abbrev) book = Book.populate(name=name, abbreviation=abbrev, testament_id=testament) self.session.add(book) @@ -132,6 +134,9 @@ class BibleDB(): self.commit() def create_verse(self, book_id, chapter, verse, text): + if not isinstance(text, unicode): + details = chardet.detect(text) + text = unicode(text, details[u'encoding']) verse = Verse.populate( book_id=book_id, chapter=chapter, diff --git a/openlp/plugins/bibles/lib/http.py b/openlp/plugins/bibles/lib/http.py index 7f1c835f8..5c3ac53b0 100644 --- a/openlp/plugins/bibles/lib/http.py +++ b/openlp/plugins/bibles/lib/http.py @@ -232,4 +232,5 @@ class HTTPBible(BibleDB): ev = BGExtract(self.proxyurl) return ev.get_bible_chapter(self.bibleid, book, chapter) except: - log.exception("Failed to get bible chapter") \ No newline at end of file + log.exception("Failed to get bible chapter") + diff --git a/openlp/plugins/bibles/lib/manager.py b/openlp/plugins/bibles/lib/manager.py index 9a667d916..1a9d01d9d 100644 --- a/openlp/plugins/bibles/lib/manager.py +++ b/openlp/plugins/bibles/lib/manager.py @@ -61,13 +61,13 @@ class BibleFormat(object): Return the appropriate imeplementation class. """ if id == BibleFormat.OSIS: - return BibleOSISImpl + return OSISBible elif id == BibleFormat.CSV: - return BibleCSVImpl + return CSVBible elif id == BibleFormat.OpenSong: - return BibleOpenSongImpl + return OpenSongBible elif id == BibleFormat.WebDownload: - return BibleHTTPImpl + return HTTPBible else: return None @@ -161,10 +161,13 @@ class BibleManager(object): try: fbibles = open(filepath, u'r') for line in fbibles: - p = line.split(u',') - self.book_abbreviations[p[0]] = p[1].replace(u'\n', '') - self.book_testaments[p[0]] = p[2].replace(u'\n', '') - self.book_chapters.append({u'book':p[0], u'total':p[3].replace(u'\n', '')}) + parts = line.split(u',') + self.book_abbreviations[parts[0]] = parts[1].replace(u'\n', '') + self.book_testaments[parts[0]] = parts[2].replace(u'\n', '') + self.book_chapters.append({ + u'book': parts[0], + u'total': parts[3].replace(u'\n', '') + }) except: log.exception(u'Failed to load bible') finally: @@ -192,10 +195,10 @@ class BibleManager(object): Keyword arguments to send to the actualy importer class. """ class_ = BibleFormat.get_class(type) - kwargs[u'path'] = self.path - kwargs[u'config'] = self.config + kwargs['path'] = self.path + kwargs['config'] = self.config importer = class_(**kwargs) - name = importer.register() + name = importer.register(self.import_wizard) self.db_cache[name] = importer return importer.do_import() @@ -231,7 +234,7 @@ class BibleManager(object): nbible = BibleDBImpl(self.bible_path, biblename, self.config) # Create Database nbible.create_tables() - self.bible_db_cache[biblename] = nbible + self.db_cache[biblename] = nbible nhttp = BibleHTTPImpl() nhttp.set_bible_source(biblesource) self.bible_http_cache[biblename] = nhttp @@ -366,16 +369,16 @@ class BibleManager(object): log.debug(u'get_book_verse_count %s,%s,%s', bible, book, chapter) web, bible = self.is_bible_web(bible) if web: - count = self.bible_db_cache[bible].get_max_bible_book_verses( + count = self.db_cache[bible].get_max_bible_book_verses( book, chapter) if count == 0: # Make sure the first chapter has been downloaded self.get_verse_text(bible, book, chapter, chapter, 1, 1) - count = self.bible_db_cache[bible].get_max_bible_book_verses( + count = self.db_cache[bible].get_max_bible_book_verses( book, chapter) return count else: - return self.bible_db_cache[bible].get_max_bible_book_verses( + return self.db_cache[bible].get_max_bible_book_verses( book, chapter) def get_verses(self, bible, versetext): @@ -386,7 +389,7 @@ class BibleManager(object): log.debug(u'get_verses_from_text %s,%s', bible, versetext) reflist = parse_reference(versetext) web, bible = self.is_bible_web(bible) - return self.bible_db_cache[bible].get_verses(reflist) + return self.db_cache[bible].get_verses(reflist) def save_meta_data(self, bible, version, copyright, permissions): """ @@ -394,9 +397,9 @@ class BibleManager(object): """ log.debug(u'save_meta data %s,%s, %s,%s', bible, version, copyright, permissions) - self.bible_db_cache[bible].save_meta(u'Version', version) - self.bible_db_cache[bible].save_meta(u'Copyright', copyright) - self.bible_db_cache[bible].save_meta(u'Permissions', permissions) + self.db_cache[bible].create_meta(u'Version', version) + self.db_cache[bible].create_meta(u'Copyright', copyright) + self.db_cache[bible].create_meta(u'Permissions', permissions) def get_meta_data(self, bible, key): """ @@ -404,7 +407,7 @@ class BibleManager(object): """ log.debug(u'get_meta %s,%s', bible, key) web, bible = self.is_bible_web(bible) - return self.bible_db_cache[bible].get_meta(key) + return self.db_cache[bible].get_meta(key) def get_verse_text(self, bible, bookname, schapter, echapter, sverse, everse=0): @@ -425,8 +428,8 @@ class BibleManager(object): # check to see if book/chapter exists fow HTTP bibles and load cache # if necessary web, bible = self.is_bible_web(bible) - if self.bible_http_cache[bible]: - book = self.bible_db_cache[bible].get_bible_book(bookname) + if self.http_cache[bible]: + book = self.db_cache[bible].get_bible_book(bookname) if book is None: log.debug(u'get_verse_text : new book') for chapter in range(schapter, echapter + 1): @@ -434,7 +437,7 @@ class BibleManager(object): unicode(self.media.trUtf8('Downloading %s: %s')) % (bookname, chapter)) search_results = \ - self.bible_http_cache[bible].get_bible_chapter( + self.http_cache[bible].get_bible_chapter( bible, bookname, chapter) if search_results.has_verselist() : ## We have found a book of the bible lets check to see @@ -443,33 +446,33 @@ class BibleManager(object): ## to request ac and get Acts back. bookname = search_results.get_book() # check to see if book/chapter exists - book = self.bible_db_cache[bible].get_bible_book( + book = self.db_cache[bible].get_bible_book( bookname) if book is None: ## Then create book, chapter and text - book = self.bible_db_cache[bible].create_book( + book = self.db_cache[bible].create_book( bookname, self.book_abbreviations[bookname], self.book_testaments[bookname]) log.debug(u'New http book %s, %s, %s', book, book.id, book.name) - self.bible_db_cache[bible].create_chapter( + self.db_cache[bible].create_chapter( book.id, search_results.get_chapter(), search_results.get_verselist()) else: ## Book exists check chapter and texts only. - v = self.bible_db_cache[bible].get_bible_chapter( + v = self.db_cache[bible].get_bible_chapter( book.id, chapter) if v is None: self.media.setQuickMessage( unicode(self.media.trUtf8('%Downloading %s: %s'))\ % (bookname, chapter)) - self.bible_db_cache[bible].create_chapter( + self.db_cache[bible].create_chapter( book.id, chapter, search_results.get_verselist()) else: log.debug(u'get_verse_text : old book') for chapter in range(schapter, echapter + 1): - v = self.bible_db_cache[bible].get_bible_chapter( + v = self.db_cache[bible].get_bible_chapter( book.id, chapter) if v is None: try: @@ -477,17 +480,17 @@ class BibleManager(object): unicode(self.media.trUtf8('Downloading %s: %s')) % (bookname, chapter)) search_results = \ - self.bible_http_cache[bible].get_bible_chapter( + self.http_cache[bible].get_bible_chapter( bible, bookname, chapter) if search_results.has_verselist(): - self.bible_db_cache[bible].create_chapter( + self.db_cache[bible].create_chapter( book.id, search_results.get_chapter(), search_results.get_verselist()) except: log.exception(u'Problem getting scripture online') #Now get verses from database if schapter == echapter: - text = self.bible_db_cache[bible].get_bible_text(bookname, + text = self.db_cache[bible].get_bible_text(bookname, schapter, sverse, everse) else: for i in range (schapter, echapter + 1): @@ -501,7 +504,7 @@ class BibleManager(object): start = 1 end = self.get_book_verse_count(bible, bookname, i) - txt = self.bible_db_cache[bible].get_bible_text( + txt = self.db_cache[bible].get_bible_text( bookname, i, start, end) text.extend(txt) return text diff --git a/openlp/plugins/bibles/lib/opensong.py b/openlp/plugins/bibles/lib/opensong.py index 93820be48..64eeeb0f9 100644 --- a/openlp/plugins/bibles/lib/opensong.py +++ b/openlp/plugins/bibles/lib/opensong.py @@ -49,11 +49,11 @@ class OpenSongBible(BibleDB): """ log.debug(__name__) BibleDB.__init__(self, **kwargs) - if u'filename' not in kwargs: + if 'filename' not in kwargs: raise KeyError(u'You have to supply a file name to import from.') - self.filename = kwargs[u'filename'] - QtCore.QObject.connect(Receiver.get_receiver(), - QtCore.SIGNAL(u'openlpstopimport'), self.stop_import) + self.filename = kwargs['filename'] + #QtCore.QObject.connect(Receiver.get_receiver(), + # QtCore.SIGNAL(u'openlpstopimport'), self.stop_import) def stop_import(self): """ @@ -67,10 +67,12 @@ class OpenSongBible(BibleDB): Loads a Bible from file. """ log.debug(u'Starting OpenSong import from "%s"' % self.filename) + self.filename = unicode(self.filename, u'utf-8') + self.wizard.incrementProgressBar(u'Preparing for import...') detect_file = None try: detect_file = open(self.filename, u'r') - details = chardet.detect(detect_file.read(3000)) + details = chardet.detect(detect_file.read()) except: log.exception(u'Failed to detect OpenSong file encoding') return False @@ -94,11 +96,16 @@ class OpenSongBible(BibleDB): for verse in chapter.v: if self.stop_import: break - self.add_verse(db_book.id, chapter.attrib[u'n'], - verse.attrib[u'n'], verse.text) + self.create_verse( + db_book.id, + int(chapter.attrib[u'n']), + int(verse.attrib[u'n']), + verse.text + ) Receiver.send_message(u'process_events') - self.wizard.incrementProgressBar(u'Importing %s %s' % \ - (dbbook.name, str(chapter.attrib[u'n']))) + self.wizard.incrementProgressBar( + QtCore.QString('Importing %s %s' % \ + (db_book.name, chapter.attrib[u'n']))) self.commit() except: log.exception(u'Loading bible from OpenSong file failed') diff --git a/openlp/plugins/bibles/lib/osis.py b/openlp/plugins/bibles/lib/osis.py index 4f18b27c5..a7e4ba374 100644 --- a/openlp/plugins/bibles/lib/osis.py +++ b/openlp/plugins/bibles/lib/osis.py @@ -160,7 +160,7 @@ class OSISBible(BibleDB): .replace(u'', u'').replace(u'', u'')\ .replace(u'', u'') verse_text = self.spaces_regex.sub(u' ', verse_text) - self.add_verse(db_book.id, chapter, verse, verse_text) + self.create_verse(db_book.id, chapter, verse, verse_text) Receiver.send_message(u'process_events') self.commit() self.wizard.incrementProgressBar(u'Finishing import...') @@ -174,4 +174,5 @@ class OSISBible(BibleDB): self.wizard.incrementProgressBar(u'Import canceled!') return False else: - return success \ No newline at end of file + return success + From a69ba0a151b0e83fcfa1937335167195fb43fffa Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Mon, 18 Jan 2010 19:56:02 +0000 Subject: [PATCH 009/164] Fix themes loading performance by storing small icons. --- openlp/core/ui/thememanager.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index fd0284412..d4ab42432 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -185,6 +185,7 @@ class ThemeManager(QtGui.QWidget): self.ThemeListWidget.takeItem(row) try: os.remove(os.path.join(self.path, th)) + os.remove(os.path.join(self.path, u'thumbs', th)) shutil.rmtree(os.path.join(self.path, theme)) except: #if not present do not worry @@ -259,8 +260,16 @@ class ThemeManager(QtGui.QWidget): self.trUtf8('default')) else: name = textName + thumb = os.path.join(self.path, u'thumbs', u'%s.png' %textName) item_name = QtGui.QListWidgetItem(name) - item_name.setIcon(build_icon(theme)) + if os.path.exists(thumb): + icon = build_icon(thumb) + else: + icon = build_icon(theme) + pixmap = icon.pixmap(QtCore.QSize(88,50)) + ext = os.path.splitext(thumb)[1].lower() + pixmap.save(thumb, ext[1:]) + item_name.setIcon(icon) item_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(textName)) self.ThemeListWidget.addItem(item_name) @@ -427,8 +436,6 @@ class ThemeManager(QtGui.QWidget): if outfile: outfile.close() if image_from and image_from != image_to: - print "if", image_from - print "it", image_to try: shutil.copyfile(image_from, image_to) except: @@ -529,4 +536,4 @@ class ThemeManager(QtGui.QWidget): theme.font_main_y = int(theme.font_main_y.strip()) #theme.theme_mode theme.theme_name = theme.theme_name.strip() - #theme.theme_version \ No newline at end of file + #theme.theme_version From af3f69afcecef4cc7e438f22b22c7a8785703ff8 Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Thu, 21 Jan 2010 19:41:17 +0000 Subject: [PATCH 010/164] Finish off Theme thumbnail performance improvements --- openlp/core/ui/thememanager.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index d4ab42432..4e5460b0a 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -108,6 +108,9 @@ class ThemeManager(QtGui.QWidget): self.themelist = [] self.path = os.path.join(ConfigHelper.get_data_path(), u'themes') self.checkThemesExists(self.path) + self.thumbPath = os.path.join(self.path, u'.thumbnails') + print self.thumbPath + self.checkThemesExists(self.thumbPath) self.amendThemeForm.path = self.path # Last little bits of setting up self.config = PluginConfig(u'themes') @@ -185,7 +188,7 @@ class ThemeManager(QtGui.QWidget): self.ThemeListWidget.takeItem(row) try: os.remove(os.path.join(self.path, th)) - os.remove(os.path.join(self.path, u'thumbs', th)) + os.remove(os.path.join(self.thumbPath, th)) shutil.rmtree(os.path.join(self.path, theme)) except: #if not present do not worry @@ -260,15 +263,14 @@ class ThemeManager(QtGui.QWidget): self.trUtf8('default')) else: name = textName - thumb = os.path.join(self.path, u'thumbs', u'%s.png' %textName) + thumb = os.path.join(self.thumbPath, u'%s.png' % textName) item_name = QtGui.QListWidgetItem(name) if os.path.exists(thumb): icon = build_icon(thumb) else: icon = build_icon(theme) pixmap = icon.pixmap(QtCore.QSize(88,50)) - ext = os.path.splitext(thumb)[1].lower() - pixmap.save(thumb, ext[1:]) + pixmap.save(thumb, u'png') item_name.setIcon(icon) item_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(textName)) @@ -455,6 +457,10 @@ class ThemeManager(QtGui.QWidget): if os.path.exists(samplepathname): os.unlink(samplepathname) frame.save(samplepathname, u'png') + thumb = os.path.join(self.thumbPath, u'%s.png' % name) + icon = build_icon(frame) + pixmap = icon.pixmap(QtCore.QSize(88,50)) + pixmap.save(thumb, u'png') log.debug(u'Theme image written to %s', samplepathname) def generateImage(self, themedata): From 56275fbc211ae89d41d41f9bbc9292d5120a8249 Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Fri, 22 Jan 2010 17:54:08 +0000 Subject: [PATCH 011/164] Remove prints for merge --- openlp/core/lib/renderer.py | 3 +-- openlp/core/lib/rendermanager.py | 3 --- openlp/core/ui/maindisplay.py | 14 -------------- openlp/core/ui/mainwindow.py | 1 - openlp/core/ui/screen.py | 1 - openlp/core/ui/thememanager.py | 1 - 6 files changed, 1 insertion(+), 22 deletions(-) diff --git a/openlp/core/lib/renderer.py b/openlp/core/lib/renderer.py index f6da3f6b1..cc976dae5 100644 --- a/openlp/core/lib/renderer.py +++ b/openlp/core/lib/renderer.py @@ -42,7 +42,7 @@ class Renderer(object): Initialise the renderer. """ self._rect = None - self._debug = True + self._debug = False self._right_margin = 64 # the amount of right indent self._display_shadow_size_footer = 0 self._display_outline_size_footer = 0 @@ -244,7 +244,6 @@ class Renderer(object): bbox1 = self._render_lines_unaligned(footer_lines, True) # reset the frame. first time do not worry about what you paint on. self._frame = QtGui.QImage(self.bg_frame) - print "generate ", self._frame.size() self._frameOp = QtGui.QImage(self.bg_frame) x, y = self._correctAlignment(self._rect, bbox) bbox = self._render_lines_unaligned(lines, False, (x, y), True) diff --git a/openlp/core/lib/rendermanager.py b/openlp/core/lib/rendermanager.py index d8621b345..78e99506a 100644 --- a/openlp/core/lib/rendermanager.py +++ b/openlp/core/lib/rendermanager.py @@ -175,7 +175,6 @@ class RenderManager(object): footer_rect = QtCore.QRect(theme.font_footer_x, theme.font_footer_y, theme.font_footer_width - 1, theme.font_footer_height - 1) - print "build_text_rectangle", main_rect self.renderer.set_text_rectangle(main_rect, footer_rect) def generate_preview(self, themedata): @@ -229,7 +228,6 @@ class RenderManager(object): """ log.debug(u'generate slide') self.build_text_rectangle(self.themedata) - print "set_frame_dest", self.width, self.height self.renderer.set_frame_dest(self.width, self.height) return self.renderer.generate_frame_from_lines(main_text, footer_text) @@ -248,4 +246,3 @@ class RenderManager(object): self.width, self.height, self.screen_ratio ) # 90% is start of footer self.footer_start = int(self.height * 0.90) - print "calculate_default ", self.width, self.height, self.footer_start diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index 505e728da..62457df6f 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -133,15 +133,10 @@ class MainDisplay(DisplayWidget): @param (integer) screen This is the screen number. """ log.debug(u'Setup %s for %s ' %(self.screens, screenNumber)) - print "all the screen ", self.screens self.setVisible(False) self.screen = self.screens.current #Sort out screen locations and sizes - print "--------- Set screen geom ------------" - print "display ", self.screen[u'size'] self.setGeometry(self.screen[u'size']) - print "main geom", self.geometry() - print "display geom 1", self.display.geometry() self.alertScreenPosition = self.screen[u'size'].height() * 0.9 self.alertHeight = self.screen[u'size'].height() - self.alertScreenPosition self.alertDisplay.setGeometry( @@ -150,7 +145,6 @@ class MainDisplay(DisplayWidget): self.video.setGeometry(self.screen[u'size']) self.display.resize(self.screen[u'size'].width(), self.screen[u'size'].height()) - print "display geom 2", self.display.geometry() #Build a custom splash screen self.InitialFrame = QtGui.QImage( self.screen[u'size'].width(), @@ -203,31 +197,23 @@ class MainDisplay(DisplayWidget): ``frame`` Image frame to be rendered """ -# if self.timer_id != 0 : -# self.displayAlert() - print "render display start ", self.display.geometry() if not self.displayBlank: if transition: if self.frame is not None: - print "render frame 1 ", self.frame.size() self.display.setPixmap(QtGui.QPixmap.fromImage(self.frame)) self.repaint() self.frame = None if frame[u'trans'] is not None: - print "render frame 2 ", frame[u'trans'].size() self.display.setPixmap(QtGui.QPixmap.fromImage(frame[u'trans'])) self.repaint() self.frame = frame[u'trans'] - print "render frame 3 ", frame[u'main'].size() self.display.setPixmap(QtGui.QPixmap.fromImage(frame[u'main'])) self.repaint() else: - print "render frame 3 ", frame.size() self.display.setPixmap(QtGui.QPixmap.fromImage(frame)) if not self.isVisible(): self.setVisible(True) self.showFullScreen() - print "render display end ", self.display.geometry() def blankDisplay(self, blanked=True): if blanked: diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index cbd2e0ad7..63744f62c 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -610,7 +610,6 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): self.settingsForm.exec_() updated_display = self.getMonitorNumber() if updated_display != self.screens.current_display: - print "main display screen changed to ", updated_display self.screens.set_current_display(updated_display) self.RenderManager.update_display(updated_display) self.mainDisplay.setup(updated_display) diff --git a/openlp/core/ui/screen.py b/openlp/core/ui/screen.py index 0aa06bfc0..809945ed8 100644 --- a/openlp/core/ui/screen.py +++ b/openlp/core/ui/screen.py @@ -44,7 +44,6 @@ class Screen(object): self.current = screen self.screen_list.append(screen) self.count += 1 - print self.screen_list def screen_exists(self, number): for screen in self.screen_list: diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index 4e5460b0a..b2288f75a 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -109,7 +109,6 @@ class ThemeManager(QtGui.QWidget): self.path = os.path.join(ConfigHelper.get_data_path(), u'themes') self.checkThemesExists(self.path) self.thumbPath = os.path.join(self.path, u'.thumbnails') - print self.thumbPath self.checkThemesExists(self.thumbPath) self.amendThemeForm.path = self.path # Last little bits of setting up From 2d7c9dd402c120da6f04fc1d82a0118cd9673794 Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Fri, 22 Jan 2010 18:59:36 +0000 Subject: [PATCH 012/164] Fixes from the merge --- openlp.pyw | 4 ++-- openlp/core/lib/__init__.py | 6 +----- openlp/core/ui/__init__.py | 2 +- openlp/core/ui/screen.py | 2 +- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/openlp.pyw b/openlp.pyw index 2060f09e1..968ee92d2 100755 --- a/openlp.pyw +++ b/openlp.pyw @@ -34,7 +34,7 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import Receiver, str_to_bool from openlp.core.resources import qInitResources -from openlp.core.ui import MainWindow, SplashScreen, Screen +from openlp.core.ui import MainWindow, SplashScreen, ScreenList from openlp.core.utils import ConfigHelper log = logging.getLogger() @@ -117,7 +117,7 @@ class OpenLP(QtGui.QApplication): self.splash.show() # make sure Qt really display the splash screen self.processEvents() - screens = Screen() + screens = ScreenList() # Decide how many screens we have and their size for screen in xrange(0, self.desktop().numScreens()): screens.add_screen({u'number': screen, diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index 846ff2053..93512f946 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -143,11 +143,7 @@ def resize_image(image, width, height): ``image`` The image to resize. """ - if isinstance(image, QtGui.QImage): - preview = QtGui.QImage(image) - else: - preview = QtGui.QImage(image) - + preview = QtGui.QImage(image) preview = preview.scaled(width, height, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) realw = preview.width(); diff --git a/openlp/core/ui/__init__.py b/openlp/core/ui/__init__.py index a2252bc41..42f232638 100644 --- a/openlp/core/ui/__init__.py +++ b/openlp/core/ui/__init__.py @@ -23,7 +23,7 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### -from screen import Screen +from screen import ScreenList from maindisplay import MainDisplay from amendthemeform import AmendThemeForm from slidecontroller import SlideController diff --git a/openlp/core/ui/screen.py b/openlp/core/ui/screen.py index 809945ed8..2321c3020 100644 --- a/openlp/core/ui/screen.py +++ b/openlp/core/ui/screen.py @@ -24,7 +24,7 @@ ############################################################################### import logging -class Screen(object): +class ScreenList(object): """ Wrapper to handle the parameters of the display screen """ From c35ff6b8d10f87560e0af1cd377ad60752174958 Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Sat, 23 Jan 2010 09:49:01 +0000 Subject: [PATCH 013/164] Presentation display fixes --- openlp/core/ui/maindisplay.py | 3 ++- openlp/core/ui/slidecontroller.py | 5 +++-- .../plugins/presentations/lib/impresscontroller.py | 14 +++++++------- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index 62457df6f..35828e85f 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -236,7 +236,7 @@ class MainDisplay(DisplayWidget): """ log.debug(u'display alert called %s' % text) self.alertList.append(text) - if self.timer_id != 0: + if self.timer_id != 0 or self.mediaLoaded: return self.generateAlert() @@ -299,6 +299,7 @@ class MainDisplay(DisplayWidget): self.firstTime = True self.mediaLoaded = True self.display.hide() + self.alertDisplay.hide() self.video.setFullScreen(True) self.video.setVisible(True) self.mediaObject.play() diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index da7b98bfc..6e3d6ad1d 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -493,6 +493,7 @@ class SlideController(QtGui.QWidget): """ Blank the screen. """ + print "onbl", blanked if self.serviceItem is not None: if self.serviceItem.is_command(): if blanked: @@ -550,7 +551,7 @@ class SlideController(QtGui.QWidget): def grabMainDisplay(self): rm = self.parent.RenderManager winid = QtGui.QApplication.desktop().winId() - rect = rm.screen_list[rm.current_display][u'size'] + rect = rm.screens.current[u'size'] winimg = QtGui.QPixmap.grabWindow(winid, rect.x(), rect.y(), rect.width(), rect.height()) self.SlidePreview.setPixmap(winimg) @@ -666,7 +667,7 @@ class SlideController(QtGui.QWidget): def onMediaStop(self): if self.isLive: - Receiver.send_message(u'%s_stop'% self.serviceItem.name.lower()) + Receiver.send_message(u'%s_stop'% self.serviceItem.name.lower(), self.isLive) else: self.mediaObject.stop() self.video.hide() diff --git a/openlp/plugins/presentations/lib/impresscontroller.py b/openlp/plugins/presentations/lib/impresscontroller.py index 28c6690e3..dc0a3bf82 100644 --- a/openlp/plugins/presentations/lib/impresscontroller.py +++ b/openlp/plugins/presentations/lib/impresscontroller.py @@ -232,13 +232,13 @@ class ImpressController(PresentationController): """ if self.document: if self.presentation: - self.presentation.end() - self.presentation = None - try: - self.document.dispose() - except: - #We tried! - pass + try: + self.presentation.end() + self.presentation = None + self.document.dispose() + except: + #We tried! + pass self.document = None def is_loaded(self): From 4a9a8149c425e9ac5d7fdc61d406a38019f6ee72 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Sun, 24 Jan 2010 09:08:14 +0200 Subject: [PATCH 014/164] Fixed up web Bibles. Renamed "csv.py" to "csvbible.py" to resolve a namespace conflict with the Python csv module. --- .../bibles/lib/{csv.py => csvbible.py} | 56 ++-- openlp/plugins/bibles/lib/db.py | 12 +- openlp/plugins/bibles/lib/http.py | 172 ++++++---- openlp/plugins/bibles/lib/manager.py | 306 +++++------------- openlp/plugins/bibles/lib/mediaitem.py | 198 ++++++------ 5 files changed, 328 insertions(+), 416 deletions(-) rename openlp/plugins/bibles/lib/{csv.py => csvbible.py} (76%) diff --git a/openlp/plugins/bibles/lib/csv.py b/openlp/plugins/bibles/lib/csvbible.py similarity index 76% rename from openlp/plugins/bibles/lib/csv.py rename to openlp/plugins/bibles/lib/csvbible.py index cd3b7efeb..f98ef0c86 100644 --- a/openlp/plugins/bibles/lib/csv.py +++ b/openlp/plugins/bibles/lib/csvbible.py @@ -25,6 +25,7 @@ import logging import chardet +import csv from openlp.core.lib import Receiver from db import BibleDB @@ -42,8 +43,8 @@ class CSVBible(BibleDB): This class assumes the files contain all the information and a clean bible is being loaded. """ - log.info(__name__) BibleDB.__init__(self, **kwargs) + log.info(self.__class__.__name__) if u'booksfile' not in kwargs: raise KeyError(u'You have to supply a file to import books from.') self.booksfile = kwargs[u'booksfile'] @@ -63,58 +64,55 @@ class CSVBible(BibleDB): def do_import(self): #Populate the Tables success = True - fbooks = None + books_file = None try: - fbooks = open(self.booksfile, 'r') - for line in fbooks: + books_file = open(self.booksfile, 'r') + dialect = csv.Sniffer().sniff(books_file.read(1024)) + books_file.seek(0) + books_reader = csv.reader(books_file, dialect) + for line in books_reader: # cancel pressed if self.stop_import: break - details = chardet.detect(line) - line = unicode(line, details['encoding']) - p = line.split(u',') - p1 = p[1].replace(u'"', u'') - p2 = p[2].replace(u'"', u'') - p3 = p[3].replace(u'"', u'') - self.create_book(p2, p3, int(p1)) + details = chardet.detect(line[1]) + self.create_book(unicode(line[1], details['encoding']), + line[2], int(line[0])) Receiver.send_message(u'process_events') except: log.exception(u'Loading books from file failed') success = False finally: - if fbooks: - fbooks.close() + if books_file: + books_file.close() if not success: return False - fverse = None + verse_file = None try: - fverse = open(versesfile, 'r') book_ptr = None - for line in fverse: + verse_file = open(versesfile, 'r') + dialect = csv.Sniffer().sniff(verse_file.read(1024)) + verse_file.seek(0) + verse_reader = csv.reader(verse_file, dialect) + for line in verse_reader: if self.stop_import: # cancel pressed break - details = chardet.detect(line) - line = unicode(line, details['encoding']) - # split into 3 units and leave the rest as a single field - p = line.split(u',', 3) - p0 = p[0].replace(u'"', u'') - p3 = p[3].replace(u'"', u'') - if book_ptr is not p0: - book = self.get_book(p0) + details = chardet.detect(line[3]) + if book_ptr != line[0]: + book = self.get_book(line[0]) book_ptr = book.name - # increament the progress bar self.wizard.incrementProgressBar( - u'Importing %s %s' % book.name) + u'Importing %s %s' % (book.name, line[1])) self.commit() - self.create_verse(book.id, p[1], p[2], p3) + self.create_verse(book.id, line[1], line[2], + unicode(line[3], details['encoding'])) Receiver.send_message(u'process_events') self.commit() except: log.exception(u'Loading verses from file failed') success = False finally: - if fverse: - fverse.close() + if verse_file: + verse_file.close() if self.stop_import: self.wizard.incrementProgressBar(u'Import canceled!') return False diff --git a/openlp/plugins/bibles/lib/db.py b/openlp/plugins/bibles/lib/db.py index 6de5217bb..2131737fd 100644 --- a/openlp/plugins/bibles/lib/db.py +++ b/openlp/plugins/bibles/lib/db.py @@ -34,7 +34,7 @@ from openlp.plugins.bibles.lib.models import * log = logging.getLogger(__name__) -class BibleDB(): +class BibleDB(QtCore.QObject): """ This class represents a database-bound Bible. It is used as a base class for all the custom importers, so that the can implement their own import @@ -121,12 +121,12 @@ class BibleDB(): return book def create_chapter(self, book_id, chapter, textlist): - log.debug(u'create_chapter %s,%s', bookid, chap) + log.debug(u'create_chapter %s,%s', book_id, chapter) #text list has book and chapter as first two elements of the array for verse_number, verse_text in textlist.iteritems(): verse = Verse.populate( book_id = book_id, - chapter = chap, + chapter = chapter, verse = verse_number, text = verse_text ) @@ -158,7 +158,7 @@ class BibleDB(): def get_book(self, book): log.debug(u'%s: %s', __name__, book) db_book = self.session.query(Book)\ - .filter_by(Book.name.like(book + u'%'))\ + .filter(Book.name.like(book + u'%'))\ .first() if db_book is None: db_book = self.session.query(Book)\ @@ -198,10 +198,10 @@ class BibleDB(): if end_verse == -1: end_verse = self.get_chapter_count(book) if db_book: - book = book.name + book = db_book.name log.debug(u'Book name corrected to "%s"' % book) verses = self.session.query(Verse)\ - .filter_by(book_id=book.id)\ + .filter_by(book_id=db_book.id)\ .filter_by(chapter=chapter)\ .filter(Verse.verse >= start_verse)\ .filter(Verse.verse <= end_verse)\ diff --git a/openlp/plugins/bibles/lib/http.py b/openlp/plugins/bibles/lib/http.py index 5c3ac53b0..ff3045107 100644 --- a/openlp/plugins/bibles/lib/http.py +++ b/openlp/plugins/bibles/lib/http.py @@ -24,9 +24,13 @@ ############################################################################### import logging +import urllib2 + +from BeautifulSoup import BeautifulSoup from common import BibleCommon, SearchResults from db import BibleDB +from openlp.plugins.bibles.lib.models import Book class BGExtract(BibleCommon): global log @@ -51,7 +55,7 @@ class BGExtract(BibleCommon): Chapter number """ log.debug(u'get_bible_chapter %s, %s, %s', version, bookname, chapter) - urlstring = u'http://www.biblegateway.com/passage/?search=%s+%d' \ + urlstring = u'http://www.biblegateway.com/passage/?search=%s+%s' \ u'&version=%s' % (bookname, chapter, version) log.debug(u'BibleGateway url = %s' % urlstring) xml_string = self._get_web_text(urlstring, self.proxyurl) @@ -94,17 +98,14 @@ class BGExtract(BibleCommon): return SearchResults(bookname, chapter, bible) class CWExtract(BibleCommon): - global log - log = logging.getLogger(u'BibleHTTPMgr(CWExtract)') - log.info(u'CWExtract loaded') + log.info(u'%s loaded', __name__) def __init__(self, proxyurl=None): log.debug(u'init %s', proxyurl) self.proxyurl = proxyurl - def get_bible_chapter(self, version, bookname, chapter) : - log.debug(u'getBibleChapter %s,%s,%s', - version,bookname, chapter) + def get_bible_chapter(self, version, bookname, chapter): + log.debug(u'%s %s, %s, %s', __name__, version, bookname, chapter) """ Access and decode bibles via the Crosswalk website @@ -120,60 +121,24 @@ class CWExtract(BibleCommon): log.debug(u'get_bible_chapter %s,%s,%s', version, bookname, chapter) bookname = bookname.replace(u' ', u'') - urlstring = u'http://bible.crosswalk.com/OnlineStudyBible/bible.cgi?word=%s+%d&version=%s'\ - % (bookname, chapter, version) - xml_string = self._get_web_text(urlstring, self.proxyurl) - ## Strip Book Title from Heading to return it to system - ## - i = xml_string.find(u'') - j = xml_string.find(u'-', i) - book_title = xml_string[i + 7:j] - book_title = book_title.rstrip() - log.debug(u'Book Title %s', book_title) - i = book_title.rfind(u' ') - book_chapter = book_title[i+1:len(book_title)].rstrip() - book_title = book_title[:i].rstrip() - log.debug(u'Book Title %s', book_title) - log.debug(u'Book Chapter %s', book_chapter) - # Strip Verse Data from Page and build an array + page = urllib2.urlopen(u'http://www.biblestudytools.com/%s/%s/%s.html' % \ + (version, bookname.lower(), chapter)) + soup = BeautifulSoup(page) + htmlverses = soup.findAll(u'span', u'versetext') + verses = {} + for verse in htmlverses: + versenumber = int(verse.contents[0].contents[0]) + versetext = u'' + for part in verse.contents: + if str(part)[0] != u'<': + versetext = versetext + part + versetext = versetext.strip(u'\n\r\t ') + verses[versenumber] = versetext + return SearchResults(bookname, chapter, verses) - i = xml_string.find(u'NavCurrentChapter') - xml_string = xml_string[i:len(xml_string)] - i = xml_string.find(u'<TABLE') - xml_string = xml_string[i:len(xml_string)] - i = xml_string.find(u'<B>') - #remove the <B> at the front - xml_string = xml_string[i + 3 :len(xml_string)] - # Remove the heading for the book - i = xml_string.find(u'<B>') - #remove the <B> at the front - xml_string = xml_string[i + 3 :len(xml_string)] - versePos = xml_string.find(u'<BLOCKQUOTE>') - bible = {} - while versePos > 0: - verseText = u'' - versePos = xml_string.find(u'<B><I>', versePos) + 6 - i = xml_string.find(u'</I></B>', versePos) - # Got the Chapter - verse = xml_string[versePos:i] - # move the starting position to begining of the text - versePos = i + 8 - # find the start of the next verse - i = xml_string.find(u'<B><I>', versePos) - if i == -1: - i = xml_string.find(u'</BLOCKQUOTE>',versePos) - verseText = xml_string[versePos: i] - versePos = 0 - else: - verseText = xml_string[versePos: i] - versePos = i - bible[verse] = self._clean_text(verseText) - return SearchResults(book_title, book_chapter, bible) class HTTPBible(BibleDB): - global log - log = logging.getLogger(u'BibleHTTPMgr') - log.info(u'BibleHTTP manager loaded') + log.info(u'%s loaded', __name__) def __init__(self, **kwargs): """ @@ -185,6 +150,7 @@ class HTTPBible(BibleDB): Init confirms the bible exists and stores the database path. """ + BibleDB.__init__(self, **kwargs) if u'download_source' not in kwargs: raise KeyError(u'Missing keyword argument "download_source"') if u'download_name' not in kwargs: @@ -204,8 +170,9 @@ class HTTPBible(BibleDB): else: self.proxy_password = None - def register(self): - name = BibleDB.register(self) + def do_import(self): + self.wizard.ImportProgressBar.setMaximum(2) + self.wizard.incrementProgressBar('Registering bible...') self.create_meta(u'download source', self.download_source) self.create_meta(u'download name', self.download_name) if self.proxy_server: @@ -216,21 +183,88 @@ class HTTPBible(BibleDB): if self.proxy_password: # store the proxy password self.create_meta(u'proxy password', self.proxy_password) - return name + self.wizard.incrementProgressBar('Registered.') + return True - def get_bible_chapter(self, version, book, chapter): + def get_verses(self, reference_list): + """ + A reimplementation of the ``BibleDB.get_verses`` method, this one is + specifically for web Bibles. It first checks to see if the particular + chapter exists in the DB, and if not it pulls it from the web. If the + chapter DOES exist, it simply pulls the verses from the DB using the + ancestor method. + + ``reference_list`` + This is the list of references the media manager item wants. It is + a list of tuples, with the following format:: + + (book, chapter, start_verse, end_verse) + + Therefore, when you are looking for multiple items, simply break + them up into references like this, bundle them into a list. This + function then runs through the list, and returns an amalgamated + list of ``Verse`` objects. For example:: + + [(u'Genesis', 1, 1, 1), (u'Genesis', 2, 2, 3)] + """ + for reference in reference_list: + log.debug('Reference: %s', reference) + book = reference[0] + db_book = self.get_book(book) + if not db_book: + book_details = self.lookup_book(book) + db_book = self.create_book(book_details[u'name'], + book_details[u'abbr'], book_details[u'test']) + book = db_book.name + if self.get_verse_count(book, reference[1]) == 0: + search_results = self.get_chapter(self.name, book, reference[1]) + if search_results and search_results.has_verselist(): + ## We have found a book of the bible lets check to see + ## if it was there. By reusing the returned book name + ## we get a correct book. For example it is possible + ## to request ac and get Acts back. + bookname = search_results.get_book() + # check to see if book/chapter exists + db_book = self.get_book(bookname) + self.create_chapter(db_book.id, search_results.get_chapter(), + search_results.get_verselist()) + return BibleDB.get_verses(self, reference_list) + + def get_chapter(self, version, book, chapter): """ Receive the request and call the relevant handler methods """ - log.debug(u'get_bible_chapter %s,%s,%s', - version, book, chapter) - log.debug(u'biblesource = %s', self.biblesource) + log.debug(u'get_chapter %s, %s, %s', version, book, chapter) + log.debug(u'source = %s', self.download_source) try: - if self.biblesource.lower() == u'crosswalk': - ev = CWExtract(self.proxyurl) + if self.download_source.lower() == u'crosswalk': + ev = CWExtract(self.proxy_server) else: - ev = BGExtract(self.proxyurl) - return ev.get_bible_chapter(self.bibleid, book, chapter) + ev = BGExtract(self.proxy_server) + return ev.get_bible_chapter(self.download_name, book, chapter) except: log.exception("Failed to get bible chapter") + return None + + def get_books(self): + return [Book.populate(name=book[u'name']) for book in self.books] + + def get_chapter_count(self, book): + return self.books[book][u'chap'] + + def set_proxy_server(self, server): + self.proxy_server = server + + def set_books(self, books): + self.books = books + + def lookup_book(self, book): + log.debug('Looking up "%s" in %s', (book, self.books)) + if book in self.books: + return self.books[book] + else: + for details in self.books: + if details[u'abbr'] == book: + return details + return None diff --git a/openlp/plugins/bibles/lib/manager.py b/openlp/plugins/bibles/lib/manager.py index 1a9d01d9d..f6e7fc4ea 100644 --- a/openlp/plugins/bibles/lib/manager.py +++ b/openlp/plugins/bibles/lib/manager.py @@ -25,11 +25,12 @@ import logging import os +import csv from common import parse_reference from opensong import OpenSongBible from osis import OSISBible -from csv import CSVBible +from csvbible import CSVBible from db import BibleDB from http import HTTPBible @@ -103,77 +104,66 @@ class BibleManager(object): self.config = config log.debug(u'Bible Initialising') self.web = u'Web' - # dict of bible database objects self.db_cache = None - # dict of bible http readers self.http_cache = None + self.http_books = {} self.path = self.config.get_data_path() - #get proxy name for screen self.proxy_name = self.config.get_config(u'proxy name') self.suffix = u'sqlite' self.import_wizard = None self.reload_bibles() self.media = None + def load_http_books(self): + filepath = os.path.split(os.path.abspath(__file__))[0] + filepath = os.path.abspath(os.path.join( + filepath, u'..', u'resources', u'httpbooks.csv')) + books_file = None + try: + self.http_books = {} + books_file = open(filepath, u'r') + dialect = csv.Sniffer().sniff(books_file.read(1024)) + books_file.seek(0) + books_reader = csv.reader(books_file, dialect) + for line in books_reader: + self.http_books[line[0]] = { + u'name': line[0], + u'abbr': line[1], + u'test': line[2], + u'chap': line[3] + } + except: + log.exception(u'Failed to load http books.') + finally: + if books_file: + books_file.close() + def reload_bibles(self): log.debug(u'Reload bibles') files = self.config.get_files(self.suffix) log.debug(u'Bible Files %s', files) self.db_cache = {} self.http_cache = {} - # books of the bible with testaments - self.book_testaments = {} - # books of the bible with chapter count - self.book_chapters = [] - # books of the bible with abbreviation - self.book_abbreviations = {} + self.load_http_books() self.web_bibles_present = False for filename in files: name, extension = os.path.splitext(filename) self.db_cache[name] = BibleDB(path=self.path, name=name, config=self.config) # look to see if lazy load bible exists and get create getter. - source = self.db_cache[name].get_meta(u'web') + source = self.db_cache[name].get_meta(u'download source') if source: self.web_bibles_present = True - web_bible = HTTPBible() - # tell The Server where to get the verses from. - web_bible.set_source(source.value) - self.http_cache[name] = web_bible - # look to see if lazy load bible exists and get create getter. + download_name = self.db_cache[name].get_meta(u'download name').value + web_bible = HTTPBible(path=self.path, name=name, + config=self.config, download_source=source.value, + download_name=download_name) meta_proxy = self.db_cache[name].get_meta(u'proxy url') - proxy_url = None if meta_proxy: - proxy_url = meta_proxy.value - # tell The Server where to get the verses from. - web_bible.set_proxy_url(proxy_url) - # look to see if lazy load bible exists and get create getter. - bible_id = self.db_cache[name].get_meta(u'bible id').value - # tell The Server where to get the verses from. - web_bible.set_bible_id(bible_id) - #else: - # # makes the Full / partial code easier. - # self.http_cache[name] = None - if self.web_bibles_present: - filepath = os.path.split(os.path.abspath(__file__))[0] - filepath = os.path.abspath(os.path.join( - filepath, u'..', u'resources', u'httpbooks.csv')) - fbibles = None - try: - fbibles = open(filepath, u'r') - for line in fbibles: - parts = line.split(u',') - self.book_abbreviations[parts[0]] = parts[1].replace(u'\n', '') - self.book_testaments[parts[0]] = parts[2].replace(u'\n', '') - self.book_chapters.append({ - u'book': parts[0], - u'total': parts[3].replace(u'\n', '') - }) - except: - log.exception(u'Failed to load bible') - finally: - if fbibles: - fbibles.close() - log.debug(u'Bible Initialised') + web_bible.set_proxy_server(meta_proxy.value) + web_bible.set_books(self.http_books) + del self.db_cache[name] + self.db_cache[name] = web_bible + log.debug(u'Bibles reloaded') def set_process_dialog(self, wizard): """ @@ -202,130 +192,6 @@ class BibleManager(object): self.db_cache[name] = importer return importer.do_import() - def register_http_bible(self, biblename, biblesource, bibleid, - proxyurl=None, proxyid=None, proxypass=None): - """ - Return a list of bibles from a given URL. The selected Bible - can then be registered and LazyLoaded into a database. - - ``biblename`` - The name of the bible to register. - - ``biblesource`` - Where this Bible stores it's verses. - - ``bibleid`` - The identifier for a Bible. - - ``proxyurl`` - Defaults to *None*. An optional URL to a proxy server. - - ``proxyid`` - Defaults to *None*. A username for logging into the proxy - server. - - ``proxypass`` - Defaults to *None*. The password to accompany the username. - """ - log.debug(u'register_HTTP_bible %s, %s, %s, %s, %s, %s', - biblename, biblesource, bibleid, proxyurl, proxyid, proxypass) - if self._is_new_bible(biblename): - # Create new Bible - nbible = BibleDBImpl(self.bible_path, biblename, self.config) - # Create Database - nbible.create_tables() - self.db_cache[biblename] = nbible - nhttp = BibleHTTPImpl() - nhttp.set_bible_source(biblesource) - self.bible_http_cache[biblename] = nhttp - # register a lazy loading interest - nbible.create_meta(u'WEB', biblesource) - # store the web id of the bible - nbible.create_meta(u'bibleid', bibleid) - if proxyurl: - # store the proxy URL - nbible.save_meta(u'proxy', proxyurl) - nhttp.set_proxy(proxyurl) - if proxyid: - # store the proxy userid - nbible.save_meta(u'proxyid', proxyid) - if proxypass: - # store the proxy password - nbible.save_meta(u'proxypass', proxypass) - return True - else: - log.debug(u'register_http_file_bible %s not created already exists', - biblename) - return False - - def register_csv_file_bible(self, biblename, booksfile, versefile): - """ - Method to load a bible from a set of files into a database. - If the database exists it is deleted and the database is reloaded - from scratch. - """ - log.debug(u'register_CSV_file_bible %s,%s,%s', - biblename, booksfile, versefile) - if self._is_new_bible(biblename): - # Create new Bible - nbible = BibleDBImpl(self.bible_path, biblename, self.config) - # Create database - nbible.create_tables() - # Cache the database for use later - self.bible_db_cache[biblename] = nbible - # Create the loader and pass in the database - bcsv = BibleCSVImpl(nbible) - return bcsv.load_data(booksfile, versefile, self.dialogobject) - else: - log.debug(u'register_csv_file_bible %s not created already exists', - biblename) - return False - - def register_osis_file_bible(self, biblename, osisfile): - """ - Method to load a bible from a osis xml file extracted from Sword bible - viewer. If the database exists it is deleted and the database is - reloaded from scratch. - """ - log.debug(u'register_OSIS_file_bible %s, %s', biblename, osisfile) - if self._is_new_bible(biblename): - # Create new Bible - nbible = BibleDBImpl(self.bible_path, biblename, self.config) - # Create Database - nbible.create_tables() - # Cache the database for use later - self.bible_db_cache[biblename] = nbible - # Create the loader and pass in the database - bosis = BibleOSISImpl(self.bible_path, nbible) - return bosis.load_data(osisfile, self.dialogobject) - else: - log.debug( - u'register_OSIS_file_bible %s, %s not created already exists', - biblename, osisfile) - return False - - def register_opensong_bible(self, biblename, opensongfile): - """ - Method to load a bible from an OpenSong xml file. If the database - exists it is deleted and the database is reloaded from scratch. - """ - log.debug(u'register_opensong_file_bible %s, %s', biblename, opensongfile) - if self._is_new_bible(biblename): - # Create new Bible - nbible = BibleDBImpl(self.bible_path, biblename, self.config) - # Create Database - nbible.create_tables() - # Cache the database for use later - self.bible_db_cache[biblename] = nbible - # Create the loader and pass in the database - bcsv = BibleOpenSongImpl(self.bible_path, nbible) - bcsv.load_data(opensongfile, self.dialogobject) - return True - else: - log.debug(u'register_opensong_file_bible %s, %s not created ' - u'already exists', biblename, opensongfile) - return False - def get_bibles(self, mode=BibleMode.Full): """ Returns a list of Books of the bible. When ``mode`` is set to @@ -336,7 +202,7 @@ class BibleManager(object): log.debug(u'get_bibles') bible_list = [] for bible_name, bible_object in self.db_cache.iteritems(): - if bible_name in self.http_cache and self.http_cache[bible_name]: + if getattr(bible_object, 'download_source', None): bible_name = u'%s (%s)' % (bible_name, self.web) bible_list.append(bible_name) return bible_list @@ -347,12 +213,12 @@ class BibleManager(object): return True, bible[:pos_end] return False, bible - def get_bible_books(self): + def get_bible_books(self, bible): """ Returns a list of the books of the bible """ log.debug(u'get_bible_books') - return self.book_chapters + return [{'name': book.name, 'total': self.db_cache[bible].get_chapter_count(book.name)} for book in self.db_cache[bible].get_books()] def get_book_chapter_count(self, book): """ @@ -369,17 +235,14 @@ class BibleManager(object): log.debug(u'get_book_verse_count %s,%s,%s', bible, book, chapter) web, bible = self.is_bible_web(bible) if web: - count = self.db_cache[bible].get_max_bible_book_verses( - book, chapter) + count = self.db_cache[bible].get_verse_count(book, chapter) if count == 0: # Make sure the first chapter has been downloaded self.get_verse_text(bible, book, chapter, chapter, 1, 1) - count = self.db_cache[bible].get_max_bible_book_verses( - book, chapter) + count = self.db_cache[bible].get_verse_count(book, chapter) return count else: - return self.db_cache[bible].get_max_bible_book_verses( - book, chapter) + return self.db_cache[bible].get_verse_count(book, chapter) def get_verses(self, bible, versetext): """ @@ -388,7 +251,14 @@ class BibleManager(object): """ log.debug(u'get_verses_from_text %s,%s', bible, versetext) reflist = parse_reference(versetext) - web, bible = self.is_bible_web(bible) + web_index = bible.find('(%s)' % self.web) + if web_index >= 0: + bible = bible[:web_index - 1] + log.debug('Updated bible name: %s', bible) + #web, bible = self.is_bible_web(bible) + #if web: + # return self.http_cache[bible].get_verses(reflist) + #else: return self.db_cache[bible].get_verses(reflist) def save_meta_data(self, bible, version, copyright, permissions): @@ -409,8 +279,7 @@ class BibleManager(object): web, bible = self.is_bible_web(bible) return self.db_cache[bible].get_meta(key) - def get_verse_text(self, bible, bookname, schapter, echapter, sverse, - everse=0): + def get_verse_text(self, bible, book, schapter, echapter, sverse, everse=0): """ Returns a list of verses for a given Book, Chapter and ranges of verses. If the end verse(everse) is less then the start verse(sverse) @@ -424,89 +293,88 @@ class BibleManager(object): text = [] self.media.setQuickMessage(u'') log.debug(u'get_verse_text %s,%s,%s,%s,%s,%s', - bible, bookname, schapter, echapter, sverse, everse) + bible, book, schapter, echapter, sverse, everse) # check to see if book/chapter exists fow HTTP bibles and load cache # if necessary web, bible = self.is_bible_web(bible) + web_bible = False + log.debug('Web Bibles: %s', self.http_cache) if self.http_cache[bible]: - book = self.db_cache[bible].get_bible_book(bookname) - if book is None: - log.debug(u'get_verse_text : new book') + web_bible = True + db_book = self.db_cache[bible].get_book(book) + if db_book is None: + log.debug(u'get_verse_text: new book') for chapter in range(schapter, echapter + 1): self.media.setQuickMessage( unicode(self.media.trUtf8('Downloading %s: %s')) % - (bookname, chapter)) + (book, chapter)) search_results = \ - self.http_cache[bible].get_bible_chapter( - bible, bookname, chapter) - if search_results.has_verselist() : + self.http_cache[bible].get_chapter(bible, book, chapter) + if search_results and search_results.has_verselist(): ## We have found a book of the bible lets check to see ## if it was there. By reusing the returned book name ## we get a correct book. For example it is possible ## to request ac and get Acts back. bookname = search_results.get_book() # check to see if book/chapter exists - book = self.db_cache[bible].get_bible_book( - bookname) - if book is None: + db_book = self.db_cache[bible].get_book(bookname) + if db_book is None: ## Then create book, chapter and text - book = self.db_cache[bible].create_book( + db_book = self.db_cache[bible].create_book( bookname, self.book_abbreviations[bookname], self.book_testaments[bookname]) log.debug(u'New http book %s, %s, %s', - book, book.id, book.name) + db_book, db_book.id, db_book.name) self.db_cache[bible].create_chapter( - book.id, search_results.get_chapter(), + db_book.id, search_results.get_chapter(), search_results.get_verselist()) else: ## Book exists check chapter and texts only. - v = self.db_cache[bible].get_bible_chapter( - book.id, chapter) + v = self.db_cache[bible].get_chapter( + db_book.id, chapter) if v is None: self.media.setQuickMessage( unicode(self.media.trUtf8('%Downloading %s: %s'))\ - % (bookname, chapter)) + % (book, chapter)) self.db_cache[bible].create_chapter( - book.id, chapter, + db_book.id, chapter, search_results.get_verselist()) else: log.debug(u'get_verse_text : old book') for chapter in range(schapter, echapter + 1): - v = self.db_cache[bible].get_bible_chapter( - book.id, chapter) + v = self.db_cache[bible].get_chapter(db_book.id, chapter) if v is None: try: self.media.setQuickMessage(\ unicode(self.media.trUtf8('Downloading %s: %s')) - % (bookname, chapter)) + % (book, chapter)) search_results = \ - self.http_cache[bible].get_bible_chapter( - bible, bookname, chapter) + self.http_cache[bible].get_chapter( + bible, bookn, chapter) if search_results.has_verselist(): self.db_cache[bible].create_chapter( - book.id, search_results.get_chapter(), + db_book.id, search_results.get_chapter(), search_results.get_verselist()) except: log.exception(u'Problem getting scripture online') #Now get verses from database if schapter == echapter: - text = self.db_cache[bible].get_bible_text(bookname, - schapter, sverse, everse) + text = self.db_cache[bible].get_verses( + [(book, schapter, sverse, everse)]) else: - for i in range (schapter, echapter + 1): - if i == schapter: + verse_list = [] + for chapter in range(schapter, echapter + 1): + if chapter == schapter: start = sverse - end = self.get_book_verse_count(bible, bookname, i) - elif i == echapter: + end = self.get_verse_count(bible, book, chapter) + elif chapter == echapter: start = 1 end = everse else: start = 1 - end = self.get_book_verse_count(bible, bookname, i) - - txt = self.db_cache[bible].get_bible_text( - bookname, i, start, end) - text.extend(txt) + end = self.get_verse_count(bible, book, chapter) + verse_list.append((bible, chapter, start, end)) + text = self.db_cache[bible].get_verses(verse_list) return text def exists(self, name): diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index 64a4e2462..cc2cb90e8 100644 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -308,7 +308,7 @@ class BibleMediaItem(MediaManagerItem): self.QuickVersionComboBox.addItem(bible) self.QuickSecondBibleComboBox.addItem(bible) # Without HTTP - bibles = self.parent.biblemanager.get_bibles(BibleMode.Partial) + #bibles = self.parent.biblemanager.get_bibles(BibleMode.Partial) first = True # load bibles into the combo boxes for bible in bibles: @@ -362,8 +362,9 @@ class BibleMediaItem(MediaManagerItem): chapter_to = int(self.AdvancedToChapter.currentText()) verse_from = int(self.AdvancedFromVerse.currentText()) verse_to = int(self.AdvancedToVerse.currentText()) - self.search_results = self.parent.biblemanager.get_verse_text( - bible, book, chapter_from, chapter_to, verse_from, verse_to) + versetext = u'%s %s:%s-%s:%s' % (book, chapter_from, verse_from, \ + chapter_to, verse_to) + self.search_results = self.parent.manager.get_verses(bible, versetext) if self.ClearAdvancedSearchComboBox.currentIndex() == 0: self.ListView.clear() self.displayResults(bible) @@ -384,11 +385,12 @@ class BibleMediaItem(MediaManagerItem): text = unicode(self.QuickSearchEdit.displayText()) if self.ClearQuickSearchComboBox.currentIndex() == 0: self.ListView.clear() - if self.QuickSearchComboBox.currentIndex() == 1: - self.search_results = \ - self.parent.biblemanager.get_verses(bible, text) - else: - self.searchByReference(bible, text) + #if self.QuickSearchComboBox.currentIndex() == 1: + # self.search_results = \ + # self.parent.biblemanager.get_verses(bible, text) + #else: + # self.searchByReference(bible, text) + self.search_results = self.parent.biblemanager.get_verses(bible, text) if self.search_results: self.displayResults(bible) @@ -484,18 +486,18 @@ class BibleMediaItem(MediaManagerItem): def initialiseBible(self, bible): log.debug(u'initialiseBible %s', bible) - book_data = self.parent.biblemanager.get_bible_books() + book_data = self.parent.biblemanager.get_bible_books(bible) self.AdvancedBookComboBox.clear() first = True for book in book_data: row = self.AdvancedBookComboBox.count() - self.AdvancedBookComboBox.addItem(book[u'book']) + self.AdvancedBookComboBox.addItem(book[u'name']) self.AdvancedBookComboBox.setItemData( row, QtCore.QVariant(book[u'total'])) if first: first = False self.initialiseChapterVerse( - bible, book[u'book'], book[u'total']) + bible, book[u'name'], book[u'total']) def initialiseChapterVerse(self, bible, book, chapters): log.debug(u'initialiseChapterVerse %s, %s', bible, book) @@ -533,85 +535,95 @@ class BibleMediaItem(MediaManagerItem): def searchByReference(self, bible, search): log.debug(u'searchByReference %s, %s', bible, search) - book = u'' - start_chapter = u'' - end_chapter = u'' - start_verse = u'' - end_verse = u'' - search = search.replace(u' ', u' ').strip() - #original = search - message = None - # Remove book beware 0 index arrays - for i in range (len(search)-1, 0, - 1): - if search[i] == u' ': - book = search[:i] - # remove book from string - search = search[i:] - break - # allow V or v for verse instead of : - search = search.replace(u'v', ':') - search = search.replace(u'V', ':') - search = search.strip() - colon = search.find(u':') - if colon == -1: - # number : found - i = search.rfind(u' ') - if i == -1: - chapter = u'' - else: - chapter = search[i:len(search)] - hyphen = chapter.find(u'-') - if hyphen != -1: - start_chapter= chapter[:hyphen] - end_chapter= chapter[hyphen + 1:len(chapter)] - else: - start_chapter = chapter - else: - # more complex - sp = search.split(u'-') #find first - sp1 = sp[0].split(u':') - if len(sp1) == 1: - start_chapter = sp1[0] - start_verse = 1 - else: - start_chapter = sp1[0] - start_verse = sp1[1] - if len(sp)== 1: - end_chapter = start_chapter - end_verse = start_verse - else: - sp1 = sp[1].split(u':') - if len(sp1) == 1: - end_chapter = start_chapter - end_verse = sp1[0] - else: - end_chapter = sp1[0] - end_verse = sp1[1] - if end_chapter == u'': - end_chapter = start_chapter.rstrip() - if start_verse == u'': - if end_verse == u'': - start_verse = 1 - else: - start_verse = end_verse - if end_verse == u'': - end_verse = 99 - if start_chapter == u'': - message = self.trUtf8('No chapter found for search criteria') - log.debug(u'results = %s @ %s : %s @ %s : %s'% \ - (unicode(book), unicode(start_chapter), unicode(end_chapter), - unicode(start_verse), unicode(end_verse))) - if message is None: - self.search_results = None - self.search_results = self.parent.biblemanager.get_verse_text( - bible, book, int(start_chapter), int(end_chapter), - int(start_verse), int(end_verse)) - self.copyright = unicode(self.parent.biblemanager.get_meta_data( - bible, u'Copyright').value) - self.permissions = unicode(self.parent.biblemanager.get_meta_data( - bible, u'Permissions').value) - self.version = unicode(self.parent.biblemanager.get_meta_data( - bible, u'Version').value) - else: - QtGui.QMessageBox.information( - self, self.trUtf8('Information'), message) \ No newline at end of file + self.search_results = self.parent.biblemanager.get_verses(bible, search) + self.copyright = unicode(self.parent.biblemanager.get_meta_data( + bible, u'Copyright').value) + self.permissions = unicode(self.parent.biblemanager.get_meta_data( + bible, u'Permissions').value) + self.version = unicode(self.parent.biblemanager.get_meta_data( + bible, u'Version').value) + +# def searchByReference(self, bible, search): +# log.debug(u'searchByReference %s, %s', bible, search) +# book = u'' +# start_chapter = u'' +# end_chapter = u'' +# start_verse = u'' +# end_verse = u'' +# search = search.replace(u' ', u' ').strip() +# #original = search +# message = None +# # Remove book beware 0 index arrays +# for i in range (len(search)-1, 0, - 1): +# if search[i] == u' ': +# book = search[:i] +# # remove book from string +# search = search[i:] +# break +# # allow V or v for verse instead of : +# search = search.replace(u'v', ':') +# search = search.replace(u'V', ':') +# search = search.strip() +# colon = search.find(u':') +# if colon == -1: +# # number : found +# i = search.rfind(u' ') +# if i == -1: +# chapter = u'' +# else: +# chapter = search[i:len(search)] +# hyphen = chapter.find(u'-') +# if hyphen != -1: +# start_chapter= chapter[:hyphen] +# end_chapter= chapter[hyphen + 1:len(chapter)] +# else: +# start_chapter = chapter +# else: +# # more complex +# sp = search.split(u'-') #find first +# sp1 = sp[0].split(u':') +# if len(sp1) == 1: +# start_chapter = sp1[0] +# start_verse = 1 +# else: +# start_chapter = sp1[0] +# start_verse = sp1[1] +# if len(sp)== 1: +# end_chapter = start_chapter +# end_verse = start_verse +# else: +# sp1 = sp[1].split(u':') +# if len(sp1) == 1: +# end_chapter = start_chapter +# end_verse = sp1[0] +# else: +# end_chapter = sp1[0] +# end_verse = sp1[1] +# if end_chapter == u'': +# end_chapter = start_chapter.rstrip() +# if start_verse == u'': +# if end_verse == u'': +# start_verse = 1 +# else: +# start_verse = end_verse +# if end_verse == u'': +# end_verse = 99 +# if start_chapter == u'': +# message = self.trUtf8('No chapter found for search criteria') +# log.debug(u'results = %s @ %s : %s @ %s : %s'% \ +# (unicode(book), unicode(start_chapter), unicode(end_chapter), +# unicode(start_verse), unicode(end_verse))) +# if message is None: +# self.search_results = None +# self.search_results = self.parent.biblemanager.get_verse_text( +# bible, book, int(start_chapter), int(end_chapter), +# int(start_verse), int(end_verse)) +# self.copyright = unicode(self.parent.biblemanager.get_meta_data( +# bible, u'Copyright').value) +# self.permissions = unicode(self.parent.biblemanager.get_meta_data( +# bible, u'Permissions').value) +# self.version = unicode(self.parent.biblemanager.get_meta_data( +# bible, u'Version').value) +# else: +# QtGui.QMessageBox.information( +# self, self.trUtf8('Information'), message) From ad40020169981149f3b76f354f17d9a86c709904 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sun, 24 Jan 2010 13:58:39 +0000 Subject: [PATCH 015/164] Fix logging to single file Fix QMessage to displays on correct screen. --- openlp.pyw | 4 ++-- openlp/core/ui/maindisplay.py | 13 ++++++++----- openlp/core/ui/mainwindow.py | 6 +++--- openlp/core/ui/servicemanager.py | 4 ++-- openlp/core/ui/slidecontroller.py | 2 +- 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/openlp.pyw b/openlp.pyw index 968ee92d2..3b97a33d9 100755 --- a/openlp.pyw +++ b/openlp.pyw @@ -28,7 +28,7 @@ import os import sys import logging -from logging.handlers import RotatingFileHandler +from logging import FileHandler from optparse import OptionParser from PyQt4 import QtCore, QtGui @@ -154,7 +154,7 @@ def main(): help="Set the Qt4 style (passed directly to Qt4).") # Set up logging filename = u'openlp.log' - logfile = RotatingFileHandler(filename, maxBytes=200000, backupCount=5) + logfile = FileHandler(filename) logfile.setFormatter(logging.Formatter( u'%(asctime)s %(name)-15s %(levelname)-8s %(message)s')) log.addHandler(logfile) diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index 35828e85f..7b73ab2ac 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -208,9 +208,11 @@ class MainDisplay(DisplayWidget): self.repaint() self.frame = frame[u'trans'] self.display.setPixmap(QtGui.QPixmap.fromImage(frame[u'main'])) + self.display_frame = frame[u'main'] self.repaint() else: self.display.setPixmap(QtGui.QPixmap.fromImage(frame)) + self.display_frame = frame if not self.isVisible(): self.setVisible(True) self.showFullScreen() @@ -221,11 +223,11 @@ class MainDisplay(DisplayWidget): self.display.setPixmap(QtGui.QPixmap.fromImage(self.blankFrame)) else: self.displayBlank = False - if self.frame: - self.frameView(self.frame) - if blanked != self.parent.LiveController.blankButton.isChecked(): - self.parent.LiveController.blankButton.setChecked(self.displayBlank) - self.parent.generalConfig.set_config(u'screen blank', self.displayBlank) + if self.display_frame: + self.frameView(self.display_frame) +# if blanked != self.parent.LiveController.blankButton.isChecked(): +# self.parent.LiveController.blankButton.setChecked(self.displayBlank) +# self.parent.generalConfig.set_config(u'screen blank', self.displayBlank) def displayAlert(self, text=u''): """ @@ -313,6 +315,7 @@ class MainDisplay(DisplayWidget): def onMediaStop(self): log.debug(u'Media stopped by user') self.mediaObject.stop() + self.onMediaFinish() def onMediaFinish(self): log.debug(u'Reached end of media playlist') diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 63744f62c..4090f9895 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -545,7 +545,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): if app_version != version: version_text = unicode(self.trUtf8('OpenLP version %s has been updated ' 'to version %s\n\nYou can obtain the latest version from http://openlp.org')) - QtGui.QMessageBox.question(None, + QtGui.QMessageBox.question(self, self.trUtf8('OpenLP Version Updated'), version_text % (app_version, version), QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok), @@ -576,7 +576,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): self.ServiceManagerContents.onLoadService(True) if str_to_bool(self.generalConfig.get_config(u'screen blank', False)) \ and str_to_bool(self.generalConfig.get_config(u'blank warning', False)): - QtGui.QMessageBox.question(None, + QtGui.QMessageBox.question(self, self.trUtf8('OpenLP Main Display Blanked'), self.trUtf8('The Main Display has been blanked out'), QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok), @@ -620,7 +620,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): Hook to close the main window and display windows on exit """ if self.serviceNotSaved: - ret = QtGui.QMessageBox.question(None, + ret = QtGui.QMessageBox.question(self, self.trUtf8('Save Changes to Service?'), self.trUtf8('Your service has changed, do you want to save those changes?'), QtGui.QMessageBox.StandardButtons( diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 7e9fca933..44170e616 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -348,7 +348,7 @@ class ServiceManager(QtGui.QWidget): if self.parent.serviceNotSaved and \ str_to_bool(PluginConfig(u'General'). get_config(u'save prompt', u'False')): - ret = QtGui.QMessageBox.question(None, + ret = QtGui.QMessageBox.question(self, self.trUtf8('Save Changes to Service?'), self.trUtf8('Your service is unsaved, do you want to save those ' 'changes before creating a new one ?'), @@ -697,4 +697,4 @@ class ServiceManager(QtGui.QWidget): theme = unicode(self.sender().text()) item, count = self.findServiceItem() self.serviceItems[item][u'service_item'].theme = theme - self.regenerateServiceItems() \ No newline at end of file + self.regenerateServiceItems() diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 6e3d6ad1d..e42dbc7f5 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -493,7 +493,7 @@ class SlideController(QtGui.QWidget): """ Blank the screen. """ - print "onbl", blanked + print "Button Pressed", blanked if self.serviceItem is not None: if self.serviceItem.is_command(): if blanked: From e6e6eeb7c025682d1bfb0d32fba72c068b94bf91 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sun, 24 Jan 2010 15:18:26 +0000 Subject: [PATCH 016/164] Media and Blank screen now play together nicely. --- openlp/core/ui/maindisplay.py | 2 ++ openlp/core/ui/slidecontroller.py | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index 7b73ab2ac..a4ff53567 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -298,6 +298,7 @@ class MainDisplay(DisplayWidget): log.debug(u'Play the new media, Live ') if not self.mediaLoaded and not self.displayBlank: self.blankDisplay() + self.display_frame = self.blankFrame self.firstTime = True self.mediaLoaded = True self.display.hide() @@ -326,3 +327,4 @@ class MainDisplay(DisplayWidget): self.mediaLoaded = False self.video.setVisible(False) self.display.show() + self.blankDisplay(False) diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index e42dbc7f5..97bd406d3 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -441,7 +441,6 @@ class SlideController(QtGui.QWidget): self.SongMenu.menu().addAction(self.trUtf8(u'%s'%tag), self.onSongBarHandler) item.setText(frame[u'text']) - #print {u'x':frame[u'text']} else: label = QtGui.QLabel() label.setMargin(4) @@ -493,7 +492,6 @@ class SlideController(QtGui.QWidget): """ Blank the screen. """ - print "Button Pressed", blanked if self.serviceItem is not None: if self.serviceItem.is_command(): if blanked: From d6912adbf8058c801b075a4e59cb812bfb888bdc Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sun, 24 Jan 2010 16:28:18 +0000 Subject: [PATCH 017/164] Make display blanking work correctly --- openlp/core/ui/mainwindow.py | 2 +- openlp/core/ui/slidecontroller.py | 21 ++++++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 4090f9895..8e93fbf12 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -576,12 +576,12 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): self.ServiceManagerContents.onLoadService(True) if str_to_bool(self.generalConfig.get_config(u'screen blank', False)) \ and str_to_bool(self.generalConfig.get_config(u'blank warning', False)): + self.LiveController.onBlankDisplay(True) QtGui.QMessageBox.question(self, self.trUtf8('OpenLP Main Display Blanked'), self.trUtf8('The Main Display has been blanked out'), QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok), QtGui.QMessageBox.Ok) - self.LiveController.blankButton.setChecked(True) def onHelpAboutItemClicked(self): """ diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 97bd406d3..3131c2bfa 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -164,9 +164,9 @@ class SlideController(QtGui.QWidget): self.Toolbar.addToolbarSeparator(u'Close Separator') self.blankButton = self.Toolbar.addToolbarButton( u'Blank Screen', u':/slides/slide_close.png', - self.trUtf8('Blank Screen'), self.onBlankScreen, True) + self.trUtf8('Blank Screen'), self.onBlankDisplay, True) QtCore.QObject.connect(Receiver.get_receiver(), - QtCore.SIGNAL(u'live_slide_blank'), self.onBlankDisplay) + QtCore.SIGNAL(u'live_slide_blank'), self.blankScreen) if not self.isLive: self.Toolbar.addToolbarSeparator(u'Close Separator') self.Toolbar.addToolbarButton( @@ -485,12 +485,19 @@ class SlideController(QtGui.QWidget): self.PreviewListWidget.selectRow(0) self.onSlideSelected() - def onBlankDisplay(self): - self.blankButton.setChecked(self.parent.mainDisplay.displayBlank) - - def onBlankScreen(self, blanked): + def onBlankDisplay(self, force=False): """ - Blank the screen. + Handle the blank screen button + """ + if force: + self.blankButton.setChecked(True) + self.blankScreen(self.blankButton.isChecked()) + self.parent.generalConfig.set_config(u'screen blank', + self.blankButton.isChecked()) + + def blankScreen(self, blanked=False): + """ + Blank the display screen. """ if self.serviceItem is not None: if self.serviceItem.is_command(): From dd18a4da769d174c164f34f8d3aa7f765f668be8 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sun, 24 Jan 2010 16:30:48 +0000 Subject: [PATCH 018/164] Fix spacing of media items --- openlp/plugins/media/lib/mediaitem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openlp/plugins/media/lib/mediaitem.py b/openlp/plugins/media/lib/mediaitem.py index d946fb819..c2dbc5d93 100644 --- a/openlp/plugins/media/lib/mediaitem.py +++ b/openlp/plugins/media/lib/mediaitem.py @@ -61,7 +61,7 @@ class MediaMediaItem(MediaManagerItem): def retranslateUi(self): self.OnNewPrompt = self.trUtf8('Select Media') - self.OnNewFileMasks = self.trUtf8('Videos (*.avi *.mpeg *.mpg *.wmv' + self.OnNewFileMasks = self.trUtf8('Videos (*.avi *.mpeg *.mpg *.wmv ' '*.mov *.mp4 *.flv);;Audio (*.ogg *.mp3 *.wma *.wav *.flac)' ';;All files (*)') From 9239c0cdf3bdc52137701516c79d01fdbb5f5782 Mon Sep 17 00:00:00 2001 From: Jon Tibble <meths@btinternet.com> Date: Sun, 24 Jan 2010 23:16:15 +0000 Subject: [PATCH 019/164] Cleanups --- openlp/core/lib/rendermanager.py | 2 +- openlp/core/ui/__init__.py | 2 +- openlp/core/ui/aboutform.py | 1 - openlp/core/ui/maindisplay.py | 1 - openlp/core/ui/servicemanager.py | 4 ++-- openlp/core/ui/thememanager.py | 4 ++-- openlp/plugins/bibles/forms/importwizardform.py | 1 - openlp/plugins/bibles/lib/bibleOpenSongimpl.py | 4 +--- openlpcnv.pyw | 2 +- 9 files changed, 8 insertions(+), 13 deletions(-) diff --git a/openlp/core/lib/rendermanager.py b/openlp/core/lib/rendermanager.py index 78e99506a..b47eaa313 100644 --- a/openlp/core/lib/rendermanager.py +++ b/openlp/core/lib/rendermanager.py @@ -25,7 +25,7 @@ import logging -from PyQt4 import QtGui, QtCore +from PyQt4 import QtCore from renderer import Renderer from openlp.core.lib import ThemeLevel, resize_image diff --git a/openlp/core/ui/__init__.py b/openlp/core/ui/__init__.py index 42f232638..5d4c798d8 100644 --- a/openlp/core/ui/__init__.py +++ b/openlp/core/ui/__init__.py @@ -42,4 +42,4 @@ from mainwindow import MainWindow __all__ = ['SplashScreen', 'AboutForm', 'SettingsForm', 'MainWindow', 'MainDisplay', 'SlideController', 'ServiceManager', 'ThemeManager', - 'AmendThemeForm', 'MediaDockManager', 'ThemeLevel'] + 'AmendThemeForm', 'MediaDockManager'] diff --git a/openlp/core/ui/aboutform.py b/openlp/core/ui/aboutform.py index d6a97e2c9..c3eb7bdcb 100644 --- a/openlp/core/ui/aboutform.py +++ b/openlp/core/ui/aboutform.py @@ -25,7 +25,6 @@ from PyQt4 import QtCore, QtGui -from openlp.core.lib import build_icon from aboutdialog import Ui_AboutDialog class AboutForm(QtGui.QDialog, Ui_AboutDialog): diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index a4ff53567..11b89743d 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -25,7 +25,6 @@ import logging import os -import time from PyQt4 import QtCore, QtGui from PyQt4.phonon import Phonon diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 44170e616..514e39077 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -30,8 +30,8 @@ import zipfile from PyQt4 import QtCore, QtGui from openlp.core.lib import PluginConfig, OpenLPToolbar, ServiceItem, \ - ServiceItemType, contextMenuAction, contextMenuSeparator, contextMenu, \ - Receiver, contextMenu, str_to_bool + contextMenuAction, contextMenuSeparator, contextMenu, Receiver, \ + contextMenu, str_to_bool class ServiceManagerList(QtGui.QTreeWidget): diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index b2288f75a..298f5ab23 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -34,8 +34,8 @@ from PyQt4 import QtCore, QtGui from openlp.core.ui import AmendThemeForm from openlp.core.theme import Theme from openlp.core.lib import PluginConfig, OpenLPToolbar, contextMenuAction, \ - ThemeXML, ThemeLevel, str_to_bool, get_text_file_string, build_icon, \ - Receiver, contextMenuSeparator + ThemeXML, str_to_bool, get_text_file_string, build_icon, Receiver, \ + contextMenuSeparator from openlp.core.utils import ConfigHelper class ThemeManager(QtGui.QWidget): diff --git a/openlp/plugins/bibles/forms/importwizardform.py b/openlp/plugins/bibles/forms/importwizardform.py index 7e82b6b7f..da95d968f 100644 --- a/openlp/plugins/bibles/forms/importwizardform.py +++ b/openlp/plugins/bibles/forms/importwizardform.py @@ -26,7 +26,6 @@ import logging import os import os.path -from time import sleep from PyQt4 import QtCore, QtGui diff --git a/openlp/plugins/bibles/lib/bibleOpenSongimpl.py b/openlp/plugins/bibles/lib/bibleOpenSongimpl.py index 4d171d57c..575d1bf0b 100644 --- a/openlp/plugins/bibles/lib/bibleOpenSongimpl.py +++ b/openlp/plugins/bibles/lib/bibleOpenSongimpl.py @@ -23,13 +23,11 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### -import os -import os.path import logging import chardet import codecs -from lxml import objectify +from lxml import objectify from PyQt4 import QtCore from openlp.core.lib import Receiver diff --git a/openlpcnv.pyw b/openlpcnv.pyw index 877e74744..8c3a8bcf5 100755 --- a/openlpcnv.pyw +++ b/openlpcnv.pyw @@ -147,4 +147,4 @@ if __name__ == u'__main__': newdb = os.path.join(newpath, u'songs.sqlite') mig.convert_sqlite2_to_3(olddb, newdb) mig.process() - #mig.move_log_file() \ No newline at end of file + #mig.move_log_file() From 9beb6c7db54674ff2724a472ac9daa862254cda0 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Mon, 25 Jan 2010 20:34:47 +0000 Subject: [PATCH 020/164] Add Image layer and rename text layer --- openlp/core/ui/maindisplay.py | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index a4ff53567..933aa93d7 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -96,8 +96,10 @@ class MainDisplay(DisplayWidget): self.audio = Phonon.AudioOutput(Phonon.VideoCategory, self.mediaObject) Phonon.createPath(self.mediaObject, self.video) Phonon.createPath(self.mediaObject, self.audio) - self.display = QtGui.QLabel(self) - self.display.setScaledContents(True) + self.display_image = QtGui.QLabel(self) + self.display_image.setScaledContents(True) + self.display_text = QtGui.QLabel(self) + self.display_text.setScaledContents(True) self.alertDisplay = QtGui.QLabel(self) self.alertDisplay.setScaledContents(True) self.primary = True @@ -123,7 +125,7 @@ class MainDisplay(DisplayWidget): QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'media_play'), self.onMediaPlay) QtCore.QObject.connect(Receiver.get_receiver(), - QtCore.SIGNAL(u'media_pause'), self.onMediaPaws) + QtCore.SIGNAL(u'media_pause'), self.onMediaPause) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'media_stop'), self.onMediaStop) @@ -143,7 +145,9 @@ class MainDisplay(DisplayWidget): QtCore.QRect(0, self.alertScreenPosition, self.screen[u'size'].width(),self.alertHeight)) self.video.setGeometry(self.screen[u'size']) - self.display.resize(self.screen[u'size'].width(), + self.display_image.resize(self.screen[u'size'].width(), + self.screen[u'size'].height()) + self.display_text.resize(self.screen[u'size'].width(), self.screen[u'size'].height()) #Build a custom splash screen self.InitialFrame = QtGui.QImage( @@ -200,18 +204,18 @@ class MainDisplay(DisplayWidget): if not self.displayBlank: if transition: if self.frame is not None: - self.display.setPixmap(QtGui.QPixmap.fromImage(self.frame)) + self.display_text.setPixmap(QtGui.QPixmap.fromImage(self.frame)) self.repaint() self.frame = None if frame[u'trans'] is not None: - self.display.setPixmap(QtGui.QPixmap.fromImage(frame[u'trans'])) + self.display_text.setPixmap(QtGui.QPixmap.fromImage(frame[u'trans'])) self.repaint() self.frame = frame[u'trans'] - self.display.setPixmap(QtGui.QPixmap.fromImage(frame[u'main'])) + self.display_text.setPixmap(QtGui.QPixmap.fromImage(frame[u'main'])) self.display_frame = frame[u'main'] self.repaint() else: - self.display.setPixmap(QtGui.QPixmap.fromImage(frame)) + self.display_text.setPixmap(QtGui.QPixmap.fromImage(frame)) self.display_frame = frame if not self.isVisible(): self.setVisible(True) @@ -220,10 +224,10 @@ class MainDisplay(DisplayWidget): def blankDisplay(self, blanked=True): if blanked: self.displayBlank = True - self.display.setPixmap(QtGui.QPixmap.fromImage(self.blankFrame)) + self.display_text.setPixmap(QtGui.QPixmap.fromImage(self.blankFrame)) else: self.displayBlank = False - if self.display_frame: + if self.display_text_frame: self.frameView(self.display_frame) # if blanked != self.parent.LiveController.blankButton.isChecked(): # self.parent.LiveController.blankButton.setChecked(self.displayBlank) @@ -285,7 +289,7 @@ class MainDisplay(DisplayWidget): def onMediaQueue(self, message): log.debug(u'Queue new media message %s' % message) - self.display.close() + self.display_text.close() file = os.path.join(message[1], message[2]) if self.firstTime: self.mediaObject.setCurrentSource(Phonon.MediaSource(file)) @@ -301,7 +305,8 @@ class MainDisplay(DisplayWidget): self.display_frame = self.blankFrame self.firstTime = True self.mediaLoaded = True - self.display.hide() + self.display_image.hide() + self.display_text.hide() self.alertDisplay.hide() self.video.setFullScreen(True) self.video.setVisible(True) @@ -309,7 +314,7 @@ class MainDisplay(DisplayWidget): if self.primary: self.setVisible(True) - def onMediaPaws(self): + def onMediaPause(self): log.debug(u'Media paused by user') self.mediaObject.pause() @@ -326,5 +331,5 @@ class MainDisplay(DisplayWidget): self.mediaObject.clearQueue() self.mediaLoaded = False self.video.setVisible(False) - self.display.show() + self.display_text.show() self.blankDisplay(False) From 04c68a075eec20c5ac6a755534f30ba4372be3a0 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Tue, 26 Jan 2010 19:16:47 +0000 Subject: [PATCH 021/164] More Image Layer changes --- openlp/core/lib/rendermanager.py | 18 ------------------ openlp/core/ui/maindisplay.py | 22 ++++++++++++++++++---- openlp/plugins/images/lib/mediaitem.py | 5 ++--- 3 files changed, 20 insertions(+), 25 deletions(-) diff --git a/openlp/core/lib/rendermanager.py b/openlp/core/lib/rendermanager.py index 78e99506a..600a029c0 100644 --- a/openlp/core/lib/rendermanager.py +++ b/openlp/core/lib/rendermanager.py @@ -64,8 +64,6 @@ class RenderManager(object): self.theme_level = u'' self.override_background = None self.themedata = None - self.save_bg_frame = None - self.override_background_changed = False def update_display(self, screen_number): """ @@ -134,22 +132,6 @@ class RenderManager(object): self.calculate_default(self.screens.current[u'size']) self.renderer.set_theme(self.themedata) self.build_text_rectangle(self.themedata) - #Replace the background image from renderer with one from image - if self.override_background: - if self.save_bg_frame is None: - self.save_bg_frame = self.renderer.bg_frame - if self.override_background_changed: - self.renderer.bg_frame = resize_image( - self.override_background, self.width, self.height) - self.override_background_changed = False - else: - if self.override_background_changed: - self.renderer.bg_frame = resize_image( - self.override_background, self.width, self.height) - self.override_background_changed = False - if self.save_bg_frame: - self.renderer.bg_frame = self.save_bg_frame - self.save_bg_frame = None def build_text_rectangle(self, theme): """ diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index 933aa93d7..62d5b2f21 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -30,7 +30,7 @@ import time from PyQt4 import QtCore, QtGui from PyQt4.phonon import Phonon -from openlp.core.lib import Receiver +from openlp.core.lib import Receiver, resize_image class DisplayWidget(QtGui.QWidget): """ @@ -162,7 +162,7 @@ class MainDisplay(DisplayWidget): (self.screen[u'size'].width() - splash_image.width()) / 2, (self.screen[u'size'].height() - splash_image.height()) / 2, splash_image) - self.frameView(self.InitialFrame) + self.display_image.setPixmap(QtGui.QPixmap.fromImage(self.InitialFrame)) #Build a Black screen painter = QtGui.QPainter() self.blankFrame = QtGui.QImage( @@ -171,10 +171,11 @@ class MainDisplay(DisplayWidget): QtGui.QImage.Format_ARGB32_Premultiplied) painter.begin(self.blankFrame) painter.fillRect(self.blankFrame.rect(), QtCore.Qt.red) - #buid a blank transparent image + #build a blank transparent image self.transparent = QtGui.QPixmap(self.screen[u'size'].width(), self.screen[u'size'].height()) self.transparent.fill(QtCore.Qt.transparent) + self.frameView(self.transparent) # To display or not to display? if not self.screen[u'primary']: self.showFullScreen() @@ -194,6 +195,16 @@ class MainDisplay(DisplayWidget): if not self.primary: self.setVisible(True) + def addImageWithText(self, frame): + frame = resize_image(frame, + self.screen[u'size'].width(), + self.screen[u'size'].height() ) + self.display_image.setPixmap(QtGui.QPixmap.fromImage(frame)) +# self.display_image.show() +# if not self.isVisible(): +# self.setVisible(True) +# self.showFullScreen() + def frameView(self, frame, transition=False): """ Called from a slide controller to display a frame @@ -215,7 +226,10 @@ class MainDisplay(DisplayWidget): self.display_frame = frame[u'main'] self.repaint() else: - self.display_text.setPixmap(QtGui.QPixmap.fromImage(frame)) + if isinstance(frame, QtGui.QPixmap): + self.display_text.setPixmap(frame) + else: + self.display_text.setPixmap(QtGui.QPixmap.fromImage(frame)) self.display_frame = frame if not self.isVisible(): self.setVisible(True) diff --git a/openlp/plugins/images/lib/mediaitem.py b/openlp/plugins/images/lib/mediaitem.py index 9ee3a0c6f..8ea1df64b 100644 --- a/openlp/plugins/images/lib/mediaitem.py +++ b/openlp/plugins/images/lib/mediaitem.py @@ -172,7 +172,6 @@ class ImageMediaItem(MediaManagerItem): filename = unicode((bitem.data(QtCore.Qt.UserRole)).toString()) self.OverrideLabel.setText(bitem.text()) frame = QtGui.QImage(unicode(filename)) - self.parent.render_manager.override_background = frame - self.parent.render_manager.override_background_changed = True + self.parent.live_controller.parent.mainDisplay.addImageWithText(frame) else: - MediaManagerItem.onPreviewClick(self) \ No newline at end of file + MediaManagerItem.onPreviewClick(self) From 262ff559534b82ffff463142ddde6f3b22b35d4a Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Wed, 27 Jan 2010 14:35:42 +0200 Subject: [PATCH 022/164] Added bible conversion script. --- scripts/bible-1to2-converter.py | 306 ++++++++++++++++++++++++++++++++ 1 file changed, 306 insertions(+) create mode 100755 scripts/bible-1to2-converter.py diff --git a/scripts/bible-1to2-converter.py b/scripts/bible-1to2-converter.py new file mode 100755 index 000000000..226c1ec2e --- /dev/null +++ b/scripts/bible-1to2-converter.py @@ -0,0 +1,306 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard # +# --------------------------------------------------------------------------- # +# 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 # +############################################################################### + +import sys +import os +import sqlite +import sqlite3 +import re +from optparse import OptionParser +from traceback import format_tb as get_traceback + +# Some global options to be used throughout the import process +verbose = False +debug = False +old_cursor = None +new_cursor = None + +# SQL create statments +create_statements = [ + (u'table "book"', u"""CREATE TABLE book ( + id INTEGER NOT NULL, + testament_id INTEGER, + name VARCHAR(30), + abbreviation VARCHAR(5), + PRIMARY KEY (id), + FOREIGN KEY(testament_id) REFERENCES testament (id) +)"""), + (u'table "metadata"', u"""CREATE TABLE metadata ( + "key" VARCHAR(255) NOT NULL, + value VARCHAR(255), + PRIMARY KEY ("key") +)"""), + (u'table "testament"', u"""CREATE TABLE testament ( + id INTEGER NOT NULL, + name VARCHAR(30), + PRIMARY KEY (id) +)"""), + (u'table "verse"', u"""CREATE TABLE verse ( + id INTEGER NOT NULL, + book_id INTEGER, + chapter INTEGER, + verse INTEGER, + text TEXT, + PRIMARY KEY (id), + FOREIGN KEY(book_id) REFERENCES book (id) +)"""), + (u'index "idx_abbrev"', + u"""CREATE INDEX idx_abbrev ON book (abbreviation, id)"""), + (u'index "idx_chapter_verse_book', + u"""CREATE INDEX idx_chapter_verse_book ON verse (chapter, verse, book_id, id)"""), + (u'index "idx_chapter_verse_text"', + u"""CREATE INDEX idx_chapter_verse_text ON verse (text, verse, book_id, id)"""), + (u'index "idx_name"', + u"""CREATE INDEX idx_name ON book (name, id)""") +] + +def display_sql(sql, params): + prepared_params = [] + for param in params: + if isinstance(param, basestring): + prepared_params.append(u'"%s"' % param) + elif isinstance(param, (int, long)): + prepared_params.append(u'%d' % param) + elif isinstance(param, (float, complex)): + prepared_params.append(u'%f' % param) + else: + prepared_params.append(u'"%s"' % str(param)) + for prepared_param in prepared_params: + sql = sql.replace(u'?', prepared_param, 1) + return sql + +def create_database(): + global new_cursor, create_statements + if debug or verbose: + print 'Creating new database:' + else: + print 'Creating new database...', + for statement_type, sql_create in create_statements: + if debug: + print '... ', sql_create.replace('\n', ' ').replace(' ', ' ') + elif verbose: + print '... creating %s...' % statement_type, + new_cursor.execute(sql_create) + if verbose and not debug: + print 'done.' + if not verbose and not debug: + print 'done.' + +def import_bible(): + global old_cursor, new_cursor, debug, verbose + if debug or verbose: + print 'Importing metadata:' + else: + print 'Importing metadata...', + if debug: + print '... SELECT "key", "value" FROM metadata' + elif verbose: + print '... fetching metadata from old database...', + old_cursor.execute(u'SELECT "key", "value" FROM metadata') + rows = old_cursor.fetchall() + if not debug and verbose: + print 'done.' + for row in rows: + key = unicode(row[0], u'cp1252') + value = unicode(row[1], u'cp1252') + sql_insert = u'INSERT INTO metadata '\ + '("key", "value") '\ + 'VALUES (?, ?)' + sql_params = (key, value) + if debug: + print '...', display_sql(sql_insert, sql_params) + elif verbose: + print '... importing "%s"' % key + new_cursor.execute(sql_insert, sql_params) + if not verbose and not debug: + print 'done.' + if debug or verbose: + print 'Importing testaments:' + else: + print 'Importing testaments...', + if debug: + print '... SELECT id, name FROM testament' + elif verbose: + print '... fetching testaments from old database...', + old_cursor.execute(u'SELECT id, name FROM testament') + rows = old_cursor.fetchall() + if not debug and verbose: + print 'done.' + for row in rows: + id = int(row[0]) + name = unicode(row[1], u'cp1252') + sql_insert = u'INSERT INTO testament '\ + '(id, name) '\ + 'VALUES (?, ?)' + sql_params = (id, name) + if debug: + print '...', display_sql(sql_insert, sql_params) + elif verbose: + print '... importing "%s"' % name + new_cursor.execute(sql_insert, sql_params) + if not verbose and not debug: + print 'done.' + if debug or verbose: + print 'Importing books:' + else: + print 'Importing books...', + if debug: + print '... SELECT id, testament_id, name, abbreviation FROM book' + elif verbose: + print '... fetching books from old database...', + old_cursor.execute(u'SELECT id, testament_id, name, abbreviation FROM book') + rows = old_cursor.fetchall() + if not debug and verbose: + print 'done.' + book_map = {} + for row in rows: + testament_id = int(row[1]) + name = unicode(row[2], u'cp1252') + abbreviation = unicode(row[3], u'cp1252') + sql_insert = u'INSERT INTO book '\ + '(id, testament_id, name, abbreviation) '\ + 'VALUES (NULL, ?, ?, ?)' + sql_params = (testament_id, name, abbreviation) + if debug: + print '...', display_sql(sql_insert, sql_params) + elif verbose: + print '... importing "%s"' % name + new_cursor.execute(sql_insert, sql_params) + book_map[row[0]] = new_cursor.lastrowid + if debug: + print ' >>> (old) books.id =', row[0], ' (new) books.id =', book_map[row[0]] + if not verbose and not debug: + print 'done.' + if debug or verbose: + print 'Importing verses:' + else: + print 'Importing verses...', + if debug: + print '... SELECT id, book_id, chapter, verse, text || \'\' AS text FROM verse...', + elif verbose: + print '... fetching verses from old database...', + old_cursor.execute(u'SELECT id, book_id, chapter, verse, text || \'\' AS text FROM verse') + rows = old_cursor.fetchall() + if debug or verbose: + print 'done.' + song_map = {} + for row in rows: + book_id = int(row[1]) + chapter = int(row[2]) + verse = int(row[3]) + text = unicode(row[4], u'cp1252') + sql_insert = u'INSERT INTO verse '\ + '(id, book_id, chapter, verse, text) '\ + 'VALUES (NULL, ?, ?, ?, ?)' + sql_params = (book_map[book_id], chapter, verse, text) + if debug: + print '...', display_sql(sql_insert, sql_params) + elif verbose: + print '... importing "%s..."' % text[:17] + new_cursor.execute(sql_insert, sql_params) + if not verbose and not debug: + print 'done.' + +def main(old_db, new_db): + global old_cursor, new_cursor, debug + old_connection = None + new_connection = None + try: + old_connection = sqlite.connect(old_db) + except: + if debug: + errormsg = '\n' + ''.join(get_traceback(sys.exc_info()[2]))\ + + str(sys.exc_info()[1]) + else: + errormsg = sys.exc_info()[1] + print 'There was a problem connecting to the old database:', errormsg + return 1 + try: + new_connection = sqlite3.connect(new_db) + except: + if debug: + errormsg = '\n' + ''.join(get_traceback(sys.exc_info()[2]))\ + + str(sys.exc_info()[1]) + else: + errormsg = sys.exc_info()[1] + print 'There was a problem creating the new database:', errormsg + return 1 + old_cursor = old_connection.cursor() + new_cursor = new_connection.cursor() + try: + create_database() + except: + if debug: + errormsg = '\n' + ''.join(get_traceback(sys.exc_info()[2]))\ + + str(sys.exc_info()[1]) + else: + errormsg = sys.exc_info()[1] + print 'There was a problem creating the database:', errormsg + return 1 + try: + import_bible() + new_connection.commit() + except: + new_connection.rollback() + if debug: + errormsg = '\n' + ''.join(get_traceback(sys.exc_info()[2]))\ + + str(sys.exc_info()[1]) + else: + errormsg = sys.exc_info()[1] + print 'There was a problem importing songs:', errormsg + return 1 + print 'Import complete.' + +if __name__ == u'__main__': + option_parser = OptionParser(usage='Usage: %prog [options] OLDDATABASE NEWDATABASE') + option_parser.add_option('-o', '--overwrite', dest='overwrite', default=False, + action=u'store_true', help='Overwrite database file if it already exists.') + option_parser.add_option('-v', '--verbose', dest='verbose', default=False, + action=u'store_true', help='Outputs additional progress data.') + option_parser.add_option('-d', '--debug', dest='debug', default=False, + action=u'store_true', help='Outputs raw SQL statements (overrides verbose).') + options, arguments = option_parser.parse_args() + if len(arguments) < 2: + if len(arguments) == 0: + option_parser.error('Please specify an old database and a new database.') + else: + option_parser.error('Please specify a new database.') + old_db = os.path.abspath(arguments[0]) + new_db = os.path.abspath(arguments[1]) + if not os.path.isfile(old_db): + option_parser.error('Old database file ("%s") is not a file.' % old_db) + if not os.path.exists(old_db): + option_parser.error('Old database file ("%s") does not exist.' % old_db) + if os.path.exists(new_db): + if not options.overwrite: + option_parser.error('New database file ("%s") exists. If you want to overwrite it, specify the --overwrite option.' % new_db) + else: + if not os.path.isfile(new_db): + option_parser.error('New database file ("%s") is not a file.' % new_db) + os.unlink(new_db) + verbose = options.verbose + debug = options.debug + main(old_db, new_db) From af5bfe30661de83e9bd915d558c6a35ae334043d Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Thu, 28 Jan 2010 07:15:23 +0000 Subject: [PATCH 023/164] Clean up SongUsage so it now works again --- openlp/core/ui/maindisplay.py | 5 +- openlp/core/ui/thememanager.py | 53 ++++++++++--------- .../songusage/forms/songusagedeleteform.py | 8 +-- .../songusage/forms/songusagedetailform.py | 19 ++++--- openlp/plugins/songusage/songusageplugin.py | 4 +- 5 files changed, 45 insertions(+), 44 deletions(-) diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index 62d5b2f21..83e245322 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -241,11 +241,8 @@ class MainDisplay(DisplayWidget): self.display_text.setPixmap(QtGui.QPixmap.fromImage(self.blankFrame)) else: self.displayBlank = False - if self.display_text_frame: + if self.display_frame: self.frameView(self.display_frame) -# if blanked != self.parent.LiveController.blankButton.isChecked(): -# self.parent.LiveController.blankButton.setChecked(self.displayBlank) -# self.parent.generalConfig.set_config(u'screen blank', self.displayBlank) def displayAlert(self, text=u''): """ diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index b2288f75a..0bf005891 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -249,32 +249,33 @@ class ThemeManager(QtGui.QWidget): log.debug(u'Load themes from dir') self.themelist = [] self.ThemeListWidget.clear() - for root, dirs, files in os.walk(self.path): - for name in files: - if name.endswith(u'.png'): - #check to see file is in theme root directory - theme = os.path.join(self.path, name) - if os.path.exists(theme): - (path, filename) = os.path.split(unicode(file)) - textName = os.path.splitext(name)[0] - if textName == self.global_theme: - name = u'%s (%s)' % (textName, - self.trUtf8('default')) - else: - name = textName - thumb = os.path.join(self.thumbPath, u'%s.png' % textName) - item_name = QtGui.QListWidgetItem(name) - if os.path.exists(thumb): - icon = build_icon(thumb) - else: - icon = build_icon(theme) - pixmap = icon.pixmap(QtCore.QSize(88,50)) - pixmap.save(thumb, u'png') - item_name.setIcon(icon) - item_name.setData(QtCore.Qt.UserRole, - QtCore.QVariant(textName)) - self.ThemeListWidget.addItem(item_name) - self.themelist.append(textName) + #root, dirs, files = os.walk(self.path) + dirList = os.listdir(self.path) + for name in dirList: + if name.endswith(u'.png'): + #check to see file is in theme root directory + theme = os.path.join(self.path, name) + if os.path.exists(theme): + (path, filename) = os.path.split(unicode(file)) + textName = os.path.splitext(name)[0] + if textName == self.global_theme: + name = u'%s (%s)' % (textName, + self.trUtf8('default')) + else: + name = textName + thumb = os.path.join(self.thumbPath, u'%s.png' % textName) + item_name = QtGui.QListWidgetItem(name) + if os.path.exists(thumb): + icon = build_icon(thumb) + else: + icon = build_icon(theme) + pixmap = icon.pixmap(QtCore.QSize(88,50)) + pixmap.save(thumb, u'png') + item_name.setIcon(icon) + item_name.setData(QtCore.Qt.UserRole, + QtCore.QVariant(textName)) + self.ThemeListWidget.addItem(item_name) + self.themelist.append(textName) self.pushThemes() def pushThemes(self): diff --git a/openlp/plugins/songusage/forms/songusagedeleteform.py b/openlp/plugins/songusage/forms/songusagedeleteform.py index b20f13c6b..98faf26ad 100644 --- a/openlp/plugins/songusage/forms/songusagedeleteform.py +++ b/openlp/plugins/songusage/forms/songusagedeleteform.py @@ -33,11 +33,11 @@ class SongUsageDeleteForm(QtGui.QDialog, Ui_SongUsageDeleteDialog): """ Class documentation goes here. """ - def __init__(self, auditmanager, parent=None): + def __init__(self, songusagemanager, parent=None): """ Constructor """ - self.auditmanager = auditmanager + self.songusagemanager = songusagemanager QtGui.QDialog.__init__(self, parent) self.setupUi(self) @@ -52,5 +52,5 @@ class SongUsageDeleteForm(QtGui.QDialog, Ui_SongUsageDeleteDialog): if ret == QtGui.QMessageBox.Ok: qDeleteDate = self.DeleteCalendar.selectedDate() deleteDate = date(qDeleteDate.year(), qDeleteDate.month(), qDeleteDate.day()) - self.auditmanager.delete_to_date(deleteDate) - self.close() \ No newline at end of file + self.songusagemanager.delete_to_date(deleteDate) + self.close() diff --git a/openlp/plugins/songusage/forms/songusagedetailform.py b/openlp/plugins/songusage/forms/songusagedetailform.py index 93b6d2e98..ead6b5166 100644 --- a/openlp/plugins/songusage/forms/songusagedetailform.py +++ b/openlp/plugins/songusage/forms/songusagedetailform.py @@ -25,10 +25,14 @@ import os from PyQt4 import QtCore, QtGui +import logging from songusagedetaildialog import Ui_SongUsageDetailDialog class SongUsageDetailForm(QtGui.QDialog, Ui_SongUsageDetailDialog): + global log + log = logging.getLogger(u'SongUsageDetailForm') + log.info(u'SongUsage Detail Form loaded') """ Class documentation goes here. """ @@ -106,19 +110,19 @@ class SongUsageDetailForm(QtGui.QDialog, Ui_SongUsageDetailDialog): self.close() def detailedReport(self): - print "detailed" - filename = u'audit_det_%s_%s.txt' % \ + log.debug(u'Detailed report generated') + filename = u'usage_detail_%s_%s.txt' % \ (self.FromDateEdit.date().toString(u'ddMMyyyy'), self.ToDateEdit.date().toString(u'ddMMyyyy')) - audits = self.parent.auditmanager.get_all_audits() + usage = self.parent.songusagemanager.get_all_songusage() outname = os.path.join(unicode(self.FileLineEdit.text()), filename) file = None try: file = open(outname, u'w') - for audit in audits: + for instance in usage: record = u'\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"\n' % \ - (audit.auditdate,audit.audittime, audit.title, - audit.copyright, audit.ccl_number , audit.authors) + (instance.usagedate,instance.usagetime, instance.title, + instance.copyright, instance.ccl_number , instance.authors) file.write(record) except: log.exception(u'Failed to write out audit records') @@ -127,8 +131,7 @@ class SongUsageDetailForm(QtGui.QDialog, Ui_SongUsageDetailDialog): file.close() def summaryReport(self): - print "summary" + log.debug(u'Summary report generated') filename = u'audit_sum_%s_%s.txt' % \ (self.FromDateEdit.date().toString(u'ddMMyyyy'), self.ToDateEdit.date().toString(u'ddMMyyyy')) - print filename \ No newline at end of file diff --git a/openlp/plugins/songusage/songusageplugin.py b/openlp/plugins/songusage/songusageplugin.py index effc06657..802f73d3d 100644 --- a/openlp/plugins/songusage/songusageplugin.py +++ b/openlp/plugins/songusage/songusageplugin.py @@ -141,7 +141,7 @@ class SongUsagePlugin(Plugin): SongUsageitem.authors = u'' for author in SongUsageData[1]: SongUsageitem.authors += author + u' ' - self.songusagemanager.insert_SongUsage(SongUsageitem) + self.songusagemanager.insert_songusage(SongUsageitem) def onSongUsageDelete(self): self.SongUsagedeleteform.exec_() @@ -154,4 +154,4 @@ class SongUsagePlugin(Plugin): about_text = self.trUtf8('<b>SongUsage Plugin</b><br>This plugin ' 'records the use of songs and when they have been used during ' 'a live service') - return about_text \ No newline at end of file + return about_text From 3baf2f2f7baed56c890aae19b96b4ac7e4250aae Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Thu, 28 Jan 2010 07:39:34 +0000 Subject: [PATCH 024/164] Import the startup showing the update screen --- openlp.pyw | 2 +- openlp/core/ui/mainwindow.py | 3 ++- version.txt | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/openlp.pyw b/openlp.pyw index 3b97a33d9..f1a710f59 100755 --- a/openlp.pyw +++ b/openlp.pyw @@ -131,7 +131,7 @@ class OpenLP(QtGui.QApplication): if show_splash: # now kill the splashscreen self.splash.finish(self.mainWindow) - self.mainWindow.versionCheck() + #self.mainWindow.versionCheck() return self.exec_() def main(): diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 8e93fbf12..b41b6f1b4 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -50,7 +50,6 @@ media_manager_style = """ border-color: palette(light); } """ - class Ui_MainWindow(object): def setupUi(self, MainWindow): """ @@ -582,6 +581,8 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): self.trUtf8('The Main Display has been blanked out'), QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok), QtGui.QMessageBox.Ok) + self.repaint() + self.versionCheck() def onHelpAboutItemClicked(self): """ diff --git a/version.txt b/version.txt index a41b1ed7a..fcd5e288e 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.9.0-697 +1.9.0-696 From dc7148b76ed587002eaef391eef3abc43a85f2e7 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Thu, 28 Jan 2010 11:46:25 +0000 Subject: [PATCH 025/164] Fix screen display setup when returning from presentations --- openlp.pyw | 3 ++- openlp/core/ui/maindisplay.py | 8 ++++++++ openlp/core/ui/mainwindow.py | 2 -- version.txt | 2 +- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/openlp.pyw b/openlp.pyw index f1a710f59..03496e70a 100755 --- a/openlp.pyw +++ b/openlp.pyw @@ -131,7 +131,8 @@ class OpenLP(QtGui.QApplication): if show_splash: # now kill the splashscreen self.splash.finish(self.mainWindow) - #self.mainWindow.versionCheck() + self.mainWindow.repaint() + self.mainWindow.versionCheck() return self.exec_() def main(): diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index 83e245322..7728fc4c4 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -183,10 +183,13 @@ class MainDisplay(DisplayWidget): else: self.setVisible(False) self.primary = True + self.repaint() def resetDisplay(self): if self.primary: self.setVisible(False) + else: + self.showFullScreen() def hideDisplay(self): self.setVisible(False) @@ -194,6 +197,7 @@ class MainDisplay(DisplayWidget): def showDisplay(self): if not self.primary: self.setVisible(True) + self.showFullScreen() def addImageWithText(self, frame): frame = resize_image(frame, @@ -252,8 +256,11 @@ class MainDisplay(DisplayWidget): display text """ log.debug(u'display alert called %s' % text) + self.parent.StatusBar.showMessage(self.trUtf8(u'')) self.alertList.append(text) if self.timer_id != 0 or self.mediaLoaded: + self.parent.StatusBar.showMessage(\ + self.trUtf8(u'Alert message created and delayed')) return self.generateAlert() @@ -343,4 +350,5 @@ class MainDisplay(DisplayWidget): self.mediaLoaded = False self.video.setVisible(False) self.display_text.show() + self.display_image.show() self.blankDisplay(False) diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index b41b6f1b4..226c629e8 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -581,8 +581,6 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): self.trUtf8('The Main Display has been blanked out'), QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok), QtGui.QMessageBox.Ok) - self.repaint() - self.versionCheck() def onHelpAboutItemClicked(self): """ diff --git a/version.txt b/version.txt index fcd5e288e..a41b1ed7a 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.9.0-696 +1.9.0-697 From dc6be31d1f1c5ed3bdc84dd1246e8708329ee330 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Thu, 28 Jan 2010 17:36:13 +0000 Subject: [PATCH 026/164] Move changes to get displays working correctly and sort out Impress --- openlp.pyw | 4 +++ openlp/core/ui/maindisplay.py | 27 ++++++++++--------- .../presentations/lib/impresscontroller.py | 2 +- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/openlp.pyw b/openlp.pyw index 03496e70a..58925fdb9 100755 --- a/openlp.pyw +++ b/openlp.pyw @@ -68,6 +68,10 @@ class OpenLP(QtGui.QApplication): global log log.info(u'OpenLP Application Loaded') + def notify(self, obj, evt): + #TODO needed for presentation exceptions + return QtGui.QApplication.notify(self, obj, evt) + def run(self): """ Run the OpenLP application. diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index 7728fc4c4..cda5774d6 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -100,8 +100,8 @@ class MainDisplay(DisplayWidget): self.display_image.setScaledContents(True) self.display_text = QtGui.QLabel(self) self.display_text.setScaledContents(True) - self.alertDisplay = QtGui.QLabel(self) - self.alertDisplay.setScaledContents(True) + self.display_alert = QtGui.QLabel(self) + self.display_alert.setScaledContents(True) self.primary = True self.displayBlank = False self.blankFrame = None @@ -141,7 +141,7 @@ class MainDisplay(DisplayWidget): self.setGeometry(self.screen[u'size']) self.alertScreenPosition = self.screen[u'size'].height() * 0.9 self.alertHeight = self.screen[u'size'].height() - self.alertScreenPosition - self.alertDisplay.setGeometry( + self.display_alert.setGeometry( QtCore.QRect(0, self.alertScreenPosition, self.screen[u'size'].width(),self.alertHeight)) self.video.setGeometry(self.screen[u'size']) @@ -163,6 +163,7 @@ class MainDisplay(DisplayWidget): (self.screen[u'size'].height() - splash_image.height()) / 2, splash_image) self.display_image.setPixmap(QtGui.QPixmap.fromImage(self.InitialFrame)) + self.repaint() #Build a Black screen painter = QtGui.QPainter() self.blankFrame = QtGui.QImage( @@ -170,11 +171,13 @@ class MainDisplay(DisplayWidget): self.screen[u'size'].height(), QtGui.QImage.Format_ARGB32_Premultiplied) painter.begin(self.blankFrame) + #TODO make black when testing finished painter.fillRect(self.blankFrame.rect(), QtCore.Qt.red) #build a blank transparent image self.transparent = QtGui.QPixmap(self.screen[u'size'].width(), self.screen[u'size'].height()) self.transparent.fill(QtCore.Qt.transparent) + self.display_alert.setPixmap(self.transparent) self.frameView(self.transparent) # To display or not to display? if not self.screen[u'primary']: @@ -183,7 +186,6 @@ class MainDisplay(DisplayWidget): else: self.setVisible(False) self.primary = True - self.repaint() def resetDisplay(self): if self.primary: @@ -248,6 +250,7 @@ class MainDisplay(DisplayWidget): if self.display_frame: self.frameView(self.display_frame) + def displayAlert(self, text=u''): """ Called from the Alert Tab to display an alert @@ -292,22 +295,24 @@ class MainDisplay(DisplayWidget): painter.drawText( x, y + metrics.height() - metrics.descent() - 1, text) painter.end() - self.alertDisplay.setPixmap(alertframe) - self.alertDisplay.setVisible(True) + self.display_alert.setPixmap(alertframe) + self.display_alert.setVisible(True) # check to see if we have a timer running if self.timer_id == 0: self.timer_id = self.startTimer(int(alertTab.timeout) * 1000) def timerEvent(self, event): if event.timerId() == self.timer_id: - self.alertDisplay.setPixmap(self.transparent) + self.display_alert.setPixmap(self.transparent) self.killTimer(self.timer_id) self.timer_id = 0 self.generateAlert() def onMediaQueue(self, message): log.debug(u'Queue new media message %s' % message) + self.display_image.close() self.display_text.close() + self.display_alert.close() file = os.path.join(message[1], message[2]) if self.firstTime: self.mediaObject.setCurrentSource(Phonon.MediaSource(file)) @@ -325,12 +330,12 @@ class MainDisplay(DisplayWidget): self.mediaLoaded = True self.display_image.hide() self.display_text.hide() - self.alertDisplay.hide() + self.display_alert.hide() self.video.setFullScreen(True) self.video.setVisible(True) self.mediaObject.play() - if self.primary: - self.setVisible(True) + self.setVisible(True) + self.hide() def onMediaPause(self): log.debug(u'Media paused by user') @@ -343,8 +348,6 @@ class MainDisplay(DisplayWidget): def onMediaFinish(self): log.debug(u'Reached end of media playlist') - if self.primary: - self.setVisible(False) self.mediaObject.stop() self.mediaObject.clearQueue() self.mediaLoaded = False diff --git a/openlp/plugins/presentations/lib/impresscontroller.py b/openlp/plugins/presentations/lib/impresscontroller.py index dc0a3bf82..d2a31e202 100644 --- a/openlp/plugins/presentations/lib/impresscontroller.py +++ b/openlp/plugins/presentations/lib/impresscontroller.py @@ -256,7 +256,7 @@ class ImpressController(PresentationController): return False if self.controller is None: return False - return self.controller.isRunning() and self.controller.isActive() + return True def unblank_screen(self): return self.controller.resume() From 04d7a9227ef4bf060f3274fe108349fada7b6358 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Fri, 29 Jan 2010 11:59:13 +0000 Subject: [PATCH 027/164] Automate Presentation filters --- openlp/plugins/presentations/lib/impresscontroller.py | 1 + openlp/plugins/presentations/lib/mediaitem.py | 10 ++++++++-- .../plugins/presentations/lib/powerpointcontroller.py | 3 ++- openlp/plugins/presentations/lib/pptviewcontroller.py | 1 + .../presentations/lib/presentationcontroller.py | 3 ++- 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/openlp/plugins/presentations/lib/impresscontroller.py b/openlp/plugins/presentations/lib/impresscontroller.py index d2a31e202..ee15d27be 100644 --- a/openlp/plugins/presentations/lib/impresscontroller.py +++ b/openlp/plugins/presentations/lib/impresscontroller.py @@ -62,6 +62,7 @@ class ImpressController(PresentationController): """ log.debug(u'Initialising') PresentationController.__init__(self, plugin, u'Impress') + self.supports= [u'.odp', u'.ppt', u'.pps'] self.process = None self.document = None self.presentation = None diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index c1394c547..0b14f797d 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -63,7 +63,13 @@ class PresentationMediaItem(MediaManagerItem): def retranslateUi(self): self.OnNewPrompt = self.trUtf8('Select Presentation(s)') - self.OnNewFileMasks = self.trUtf8('Presentations (*.ppt *.pps *.odp)') + fileType = u'' + for controller in self.controllers: + if self.controllers[controller].enabled: + for type in self.controllers[controller].supports: + if fileType.find(type) == -1: + fileType += type + u' ' + self.OnNewFileMasks = self.trUtf8('Presentations (%s)' % fileType) def requiredIcons(self): MediaManagerItem.requiredIcons(self) @@ -151,4 +157,4 @@ class PresentationMediaItem(MediaManagerItem): service_item.add_from_command(path, name, img) i = i + 1 img = controller.get_slide_preview_file(i) - return True \ No newline at end of file + return True diff --git a/openlp/plugins/presentations/lib/powerpointcontroller.py b/openlp/plugins/presentations/lib/powerpointcontroller.py index d64314c76..18b644112 100644 --- a/openlp/plugins/presentations/lib/powerpointcontroller.py +++ b/openlp/plugins/presentations/lib/powerpointcontroller.py @@ -52,6 +52,7 @@ class PowerpointController(PresentationController): """ log.debug(u'Initialising') PresentationController.__init__(self, plugin, u'Powerpoint') + self.supports= [u'.ppt', u'.pps'] self.process = None self.presentation = None @@ -255,4 +256,4 @@ class PowerpointController(PresentationController): if os.path.isfile(path): return path else: - return None \ No newline at end of file + return None diff --git a/openlp/plugins/presentations/lib/pptviewcontroller.py b/openlp/plugins/presentations/lib/pptviewcontroller.py index 02b280540..0cf2405f1 100644 --- a/openlp/plugins/presentations/lib/pptviewcontroller.py +++ b/openlp/plugins/presentations/lib/pptviewcontroller.py @@ -49,6 +49,7 @@ class PptviewController(PresentationController): log.debug(u'Initialising') self.process = None PresentationController.__init__(self, plugin, u'Powerpoint Viewer') + self.supports= [u'.ppt', u'.pps'] self.pptid = None def check_available(self): diff --git a/openlp/plugins/presentations/lib/presentationcontroller.py b/openlp/plugins/presentations/lib/presentationcontroller.py index 82c014b64..ac9581d28 100644 --- a/openlp/plugins/presentations/lib/presentationcontroller.py +++ b/openlp/plugins/presentations/lib/presentationcontroller.py @@ -136,6 +136,7 @@ class PresentationController(object): ``name`` Name of the application, to appear in the application """ + self.supports = [] self.plugin = plugin self.name = name self.available = self.check_available() @@ -313,4 +314,4 @@ class PresentationController(object): else: prefix = u'preview' Receiver.send_message(u'%s_slidecontroller_change' % prefix, - self.slidenumber - 1) \ No newline at end of file + self.slidenumber - 1) From 1fd98217abcb51edae32a585afe1e6b8c13fba84 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Fri, 29 Jan 2010 13:06:47 +0000 Subject: [PATCH 028/164] Pass active presentation types to other controllers --- openlp/core/lib/eventreceiver.py | 5 ++++- openlp/core/ui/servicemanager.py | 7 ++++++- openlp/plugins/presentations/presentationplugin.py | 10 ++++++++-- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/openlp/core/lib/eventreceiver.py b/openlp/core/lib/eventreceiver.py index f3b43d2b7..e72314652 100644 --- a/openlp/core/lib/eventreceiver.py +++ b/openlp/core/lib/eventreceiver.py @@ -104,6 +104,9 @@ class EventReceiver(QtCore.QObject): ``remote_edit_clear`` Informs all components that remote edit has been aborted. + ``presentation types`` + Informs all components of the presentation types supported. + """ global log log = logging.getLogger(u'EventReceiver') @@ -161,4 +164,4 @@ class Receiver(): """ Get the global ``eventreceiver`` instance. """ - return Receiver.eventreceiver \ No newline at end of file + return Receiver.eventreceiver diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 44170e616..6bb09a92e 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -225,12 +225,17 @@ class ServiceManager(QtGui.QWidget): QtCore.SIGNAL(u'update_themes'), self.updateThemeList) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'remote_edit_clear'), self.onRemoteEditClear) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'presentation types'), self.onPresentationTypes) # Last little bits of setting up self.config = PluginConfig(u'ServiceManager') self.servicePath = self.config.get_data_path() self.service_theme = unicode( self.config.get_config(u'service theme', u'')) + def onPresentationTypes(self, presentation_types): + self.presentation_types = presentation_types + def onMoveSelectionUp(self): """ Moves the selection up the window @@ -617,7 +622,7 @@ class ServiceManager(QtGui.QWidget): else: pos = parentitem.data(0, QtCore.Qt.UserRole).toInt()[0] count = item.data(0, QtCore.Qt.UserRole).toInt()[0] - #adjuest for zero based arrays + #adjust for zero based arrays pos = pos - 1 return pos, count diff --git a/openlp/plugins/presentations/presentationplugin.py b/openlp/plugins/presentations/presentationplugin.py index a380e9bb0..502557508 100644 --- a/openlp/plugins/presentations/presentationplugin.py +++ b/openlp/plugins/presentations/presentationplugin.py @@ -26,7 +26,7 @@ import os import logging -from openlp.core.lib import Plugin, build_icon +from openlp.core.lib import Plugin, build_icon, Receiver from openlp.plugins.presentations.lib import * class PresentationPlugin(Plugin): @@ -51,6 +51,12 @@ class PresentationPlugin(Plugin): log.info(u'Presentations Initialising') Plugin.initialise(self) self.insert_toolbox_item() + presentation_types = [] + for controller in self.controllers: + if self.controllers[controller].enabled: + presentation_types.append({u'%s' % controller : self.controllers[controller].supports}) + Receiver.send_message( + u'presentation types', presentation_types) def finalise(self): log.info(u'Plugin Finalise') @@ -106,4 +112,4 @@ class PresentationPlugin(Plugin): 'the ability to show presentations using a number of different ' 'programs. The choice of available presentation programs is ' 'available to the user in a drop down box.') - return about_text \ No newline at end of file + return about_text From 3610b232bda1e1269ce862c19c872f3fd85c6019 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Fri, 29 Jan 2010 16:38:28 +0000 Subject: [PATCH 029/164] Fix up saving and loading service items --- openlp.pyw | 2 +- openlp/core/ui/servicemanager.py | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/openlp.pyw b/openlp.pyw index 58925fdb9..4741059d2 100755 --- a/openlp.pyw +++ b/openlp.pyw @@ -159,7 +159,7 @@ def main(): help="Set the Qt4 style (passed directly to Qt4).") # Set up logging filename = u'openlp.log' - logfile = FileHandler(filename) + logfile = FileHandler(filename, u'w') logfile.setFormatter(logging.Formatter( u'%(asctime)s %(name)-15s %(levelname)-8s %(message)s')) log.addHandler(logfile) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 6bb09a92e..5f384a955 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -438,10 +438,10 @@ class ServiceManager(QtGui.QWidget): for item in self.serviceItems: service.append({u'serviceitem':item[u'service_item'].get_service_repr()}) if item[u'service_item'].uses_file(): - for frame in item[u'service_item'].get_frames: + for frame in item[u'service_item'].get_frames(): path_from = unicode(os.path.join( item[u'service_item'].service_item_path, - frame.get_frame_title())) + frame[u'title'])) zip.write(path_from) file = open(servicefile, u'wb') cPickle.dump(service, file) @@ -504,7 +504,8 @@ class ServiceManager(QtGui.QWidget): serviceitem = ServiceItem() serviceitem.RenderManager = self.parent.RenderManager serviceitem.set_from_service(item, self.servicePath) - self.addServiceItem(serviceitem) + if self.validateItem(serviceitem): + self.addServiceItem(serviceitem) try: if os.path.isfile(p_file): os.remove(p_file) @@ -521,6 +522,14 @@ class ServiceManager(QtGui.QWidget): self.serviceName = name[len(name) - 1] self.parent.serviceChanged(True, self.serviceName) + def validateItem(self, serviceItem): + print "---" + print serviceItem.name + print serviceItem.title + print serviceItem.service_item_path + print serviceItem.service_item_type + return True + def cleanUp(self): """ Empties the servicePath of temporary files From 0e29d0b4afd0863cb41e3bd4f7e53e2a2ae5f7ac Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Fri, 29 Jan 2010 22:20:30 +0200 Subject: [PATCH 030/164] Show a small progress bar rather than a "downloading" message. --- openlp/core/lib/baselistwithdnd.py | 4 +++- openlp/core/lib/eventreceiver.py | 3 ++- openlp/core/lib/mediamanageritem.py | 4 ++-- openlp/plugins/bibles/lib/http.py | 2 +- openlp/plugins/bibles/lib/manager.py | 4 ++-- openlp/plugins/bibles/lib/mediaitem.py | 30 ++++++++++++++++++++++++++ 6 files changed, 40 insertions(+), 7 deletions(-) diff --git a/openlp/core/lib/baselistwithdnd.py b/openlp/core/lib/baselistwithdnd.py index d2537e0e4..f7095550a 100644 --- a/openlp/core/lib/baselistwithdnd.py +++ b/openlp/core/lib/baselistwithdnd.py @@ -32,6 +32,7 @@ class BaseListWithDnD(QtGui.QListWidget): def __init__(self, parent=None): QtGui.QListWidget.__init__(self, parent) + self.parent = parent # this must be set by the class which is inheriting assert(self.PluginName) @@ -47,4 +48,5 @@ class BaseListWithDnD(QtGui.QListWidget): mimeData = QtCore.QMimeData() drag.setMimeData(mimeData) mimeData.setText(self.PluginName) - dropAction = drag.start(QtCore.Qt.CopyAction) \ No newline at end of file + dropAction = drag.start(QtCore.Qt.CopyAction) + diff --git a/openlp/core/lib/eventreceiver.py b/openlp/core/lib/eventreceiver.py index f3b43d2b7..41a18287e 100644 --- a/openlp/core/lib/eventreceiver.py +++ b/openlp/core/lib/eventreceiver.py @@ -161,4 +161,5 @@ class Receiver(): """ Get the global ``eventreceiver`` instance. """ - return Receiver.eventreceiver \ No newline at end of file + return Receiver.eventreceiver + diff --git a/openlp/core/lib/mediamanageritem.py b/openlp/core/lib/mediamanageritem.py index 6f6f82818..5f490eed1 100644 --- a/openlp/core/lib/mediamanageritem.py +++ b/openlp/core/lib/mediamanageritem.py @@ -253,7 +253,7 @@ class MediaManagerItem(QtGui.QWidget): def addListViewToToolBar(self): #Add the List widget - self.ListView = self.ListViewWithDnD_class() + self.ListView = self.ListViewWithDnD_class(self) self.ListView.uniformItemSizes = True self.ListView.setGeometry(QtCore.QRect(10, 100, 256, 591)) self.ListView.setSpacing(1) @@ -400,4 +400,4 @@ class MediaManagerItem(QtGui.QWidget): if self.generateSlideData(service_item): return service_item else: - return None \ No newline at end of file + return None diff --git a/openlp/plugins/bibles/lib/http.py b/openlp/plugins/bibles/lib/http.py index ff3045107..bd8833105 100644 --- a/openlp/plugins/bibles/lib/http.py +++ b/openlp/plugins/bibles/lib/http.py @@ -247,7 +247,7 @@ class HTTPBible(BibleDB): return None def get_books(self): - return [Book.populate(name=book[u'name']) for book in self.books] + return [Book.populate(name=self.books[book]['name']) for book in self.books] def get_chapter_count(self, book): return self.books[book][u'chap'] diff --git a/openlp/plugins/bibles/lib/manager.py b/openlp/plugins/bibles/lib/manager.py index f6e7fc4ea..88982a8bf 100644 --- a/openlp/plugins/bibles/lib/manager.py +++ b/openlp/plugins/bibles/lib/manager.py @@ -202,8 +202,8 @@ class BibleManager(object): log.debug(u'get_bibles') bible_list = [] for bible_name, bible_object in self.db_cache.iteritems(): - if getattr(bible_object, 'download_source', None): - bible_name = u'%s (%s)' % (bible_name, self.web) + #if getattr(bible_object, 'download_source', None): + # bible_name = u'%s (%s)' % (bible_name, self.web) bible_list.append(bible_name) return bible_list diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index cc2cb90e8..49cede58a 100644 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -42,6 +42,9 @@ class BibleListView(BaseListWithDnD): self.PluginName = u'Bibles' BaseListWithDnD.__init__(self, parent) + def resizeEvent(self, event): + self.parent.onListViewResize(event.size().width(), event.size().width()) + class BibleMediaItem(MediaManagerItem): """ @@ -242,6 +245,20 @@ class BibleMediaItem(MediaManagerItem): QtCore.SIGNAL(u'pressed()'), self.onQuickSearchButton) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'config_updated'), self.configUpdated) + # Other stuff + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'')) + + def addListViewToToolBar(self): + MediaManagerItem.addListViewToToolBar(self) + # Progress Bar + self.SearchProgress = QtGui.QProgressBar(self) + self.SearchProgress.setFormat('%p%') + self.SearchProgress.setMaximum(3) + self.SearchProgress.setGeometry(self.ListView.geometry().left(), + self.ListView.geometry().top(), 81, 23) + self.SearchProgress.setVisible(False) + self.SearchProgress.setObjectName(u'SearchProgress') def configUpdated(self): if str_to_bool( @@ -319,6 +336,19 @@ class BibleMediaItem(MediaManagerItem): # use the first bible as the trigger self.initialiseBible(bible) + def onListViewResize(self, width, height): + self.SearchProgress.setGeometry(self.ListView.geometry().x(), + (self.ListView.geometry().y() + self.ListView.geometry().height())\ + - 23, 81, 23) + + def onSearchProgressShow(self, value): + self.SearchProgress.setVisible(True) + self.SearchProgress.setValue(value) + + def onSearchProgressHide(self, value): + self.SearchProgress.setVisible(True) + self.SearchProgress.setValue(value) + def onAdvancedVersionComboBox(self): self.initialiseBible( unicode(self.AdvancedVersionComboBox.currentText())) From 0cbb079aa3d73df174df61474b7406bd7142222d Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Fri, 29 Jan 2010 23:26:24 +0200 Subject: [PATCH 031/164] Added the little progress bar back in. --- openlp/plugins/bibles/lib/http.py | 10 ++++++++-- openlp/plugins/bibles/lib/mediaitem.py | 10 ++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/openlp/plugins/bibles/lib/http.py b/openlp/plugins/bibles/lib/http.py index 5c9610bf6..a4caeb0f3 100644 --- a/openlp/plugins/bibles/lib/http.py +++ b/openlp/plugins/bibles/lib/http.py @@ -28,6 +28,7 @@ import urllib2 from BeautifulSoup import BeautifulSoup +from openlp.core.lib import Receiver from common import BibleCommon, SearchResults from db import BibleDB from openlp.plugins.bibles.lib.models import Book @@ -208,12 +209,17 @@ class HTTPBible(BibleDB): [(u'Genesis', 1, 1, 1), (u'Genesis', 2, 2, 3)] """ Receiver.send_message(u'bible_showprogress') + Receiver.send_message(u'process_events') for reference in reference_list: log.debug('Reference: %s', reference) book = reference[0] db_book = self.get_book(book) if not db_book: book_details = self.lookup_book(book) + if not book_details: + Receiver.send_message(u'bible_hideprogress') + Receiver.send_message(u'bible_nobook') + return [] db_book = self.create_book(book_details[u'name'], book_details[u'abbr'], book_details[u'test']) book = db_book.name @@ -266,7 +272,7 @@ class HTTPBible(BibleDB): return self.books[book] else: for details in self.books: - if details[u'abbr'] == book: - return details + if self.books[details][u'abbr'] == book: + return self.books[details] return None diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index 9f9840fef..9390cf642 100644 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -250,6 +250,8 @@ class BibleMediaItem(MediaManagerItem): QtCore.SIGNAL(u'bible_showprogress'), self.onSearchProgressShow) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'bible_hideprogress'), self.onSearchProgressHide) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'bible_nobook'), self.onNoBookFound) def addListViewToToolBar(self): MediaManagerItem.addListViewToToolBar(self) @@ -351,6 +353,14 @@ class BibleMediaItem(MediaManagerItem): def onSearchProgressHide(self): self.SearchProgress.setVisible(False) + def onNoBookFound(self): + QtGui.QMessageBox.critical(self, + self.trUtf8('No Book Found'), + self.trUtf8('No matching Book could be found in this Bible.'), + QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok), + QtGui.QMessageBox.Ok + ) + def onAdvancedVersionComboBox(self): self.initialiseBible( unicode(self.AdvancedVersionComboBox.currentText())) From baabcba2905ac7f034c5a57f8e0beefbe81fce10 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Fri, 29 Jan 2010 23:35:02 +0200 Subject: [PATCH 032/164] Changed the value of the progress bar to look a little more "active" --- openlp/plugins/bibles/lib/http.py | 2 ++ openlp/plugins/bibles/lib/mediaitem.py | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/openlp/plugins/bibles/lib/http.py b/openlp/plugins/bibles/lib/http.py index a4caeb0f3..a1041d98f 100644 --- a/openlp/plugins/bibles/lib/http.py +++ b/openlp/plugins/bibles/lib/http.py @@ -128,6 +128,7 @@ class CWExtract(BibleCommon): htmlverses = soup.findAll(u'span', u'versetext') verses = {} for verse in htmlverses: + Receiver.send_message(u'process_events') versenumber = int(verse.contents[0].contents[0]) versetext = u'' for part in verse.contents: @@ -235,6 +236,7 @@ class HTTPBible(BibleDB): db_book = self.get_book(bookname) self.create_chapter(db_book.id, search_results.get_chapter(), search_results.get_verselist()) + Receiver.send_message(u'process_events') Receiver.send_message(u'bible_hideprogress') return BibleDB.get_verses(self, reference_list) diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index 9390cf642..341d36c6f 100644 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -348,7 +348,8 @@ class BibleMediaItem(MediaManagerItem): def onSearchProgressShow(self): self.SearchProgress.setVisible(True) self.SearchProgress.setMinimum(0) - self.SearchProgress.setMaximum(0) + self.SearchProgress.setMaximum(2) + self.SearchProgress.setValue(1) def onSearchProgressHide(self): self.SearchProgress.setVisible(False) From d45a9733af5dcac5f1882339d8ab142b5de96d63 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Sat, 30 Jan 2010 00:26:30 +0200 Subject: [PATCH 033/164] Fixed a bug in Windows and other versions of PyQt4 where "setProperty" was not available. Changed it to "setValue" which is what it's meant to be anyways. --- openlp/plugins/bibles/forms/bibleimportwizard.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openlp/plugins/bibles/forms/bibleimportwizard.py b/openlp/plugins/bibles/forms/bibleimportwizard.py index 1f655a52d..ec190182a 100644 --- a/openlp/plugins/bibles/forms/bibleimportwizard.py +++ b/openlp/plugins/bibles/forms/bibleimportwizard.py @@ -302,8 +302,7 @@ class Ui_BibleImportWizard(object): self.ImportProgressLabel.setObjectName(u'ImportProgressLabel') self.ImportLayout.addWidget(self.ImportProgressLabel) self.ImportProgressBar = QtGui.QProgressBar(self.ImportPage) - self.ImportProgressBar.setProperty(u'value', 0) - self.ImportProgressBar.setInvertedAppearance(False) + self.ImportProgressBar.setValue(0) self.ImportProgressBar.setObjectName(u'ImportProgressBar') self.ImportLayout.addWidget(self.ImportProgressBar) BibleImportWizard.addPage(self.ImportPage) From ab7125c98ec6b2d19b661a831eb44d080e61efc5 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Sat, 30 Jan 2010 10:42:03 +0200 Subject: [PATCH 034/164] Make the progress bar only pop up when we *actually* fetch a verse from the Internet --- openlp/plugins/bibles/lib/http.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/openlp/plugins/bibles/lib/http.py b/openlp/plugins/bibles/lib/http.py index a1041d98f..5b75407be 100644 --- a/openlp/plugins/bibles/lib/http.py +++ b/openlp/plugins/bibles/lib/http.py @@ -209,8 +209,6 @@ class HTTPBible(BibleDB): [(u'Genesis', 1, 1, 1), (u'Genesis', 2, 2, 3)] """ - Receiver.send_message(u'bible_showprogress') - Receiver.send_message(u'process_events') for reference in reference_list: log.debug('Reference: %s', reference) book = reference[0] @@ -218,13 +216,14 @@ class HTTPBible(BibleDB): if not db_book: book_details = self.lookup_book(book) if not book_details: - Receiver.send_message(u'bible_hideprogress') Receiver.send_message(u'bible_nobook') return [] db_book = self.create_book(book_details[u'name'], book_details[u'abbr'], book_details[u'test']) book = db_book.name if self.get_verse_count(book, reference[1]) == 0: + Receiver.send_message(u'bible_showprogress') + Receiver.send_message(u'process_events') search_results = self.get_chapter(self.name, book, reference[1]) if search_results and search_results.has_verselist(): ## We have found a book of the bible lets check to see @@ -236,8 +235,8 @@ class HTTPBible(BibleDB): db_book = self.get_book(bookname) self.create_chapter(db_book.id, search_results.get_chapter(), search_results.get_verselist()) + Receiver.send_message(u'bible_hideprogress') Receiver.send_message(u'process_events') - Receiver.send_message(u'bible_hideprogress') return BibleDB.get_verses(self, reference_list) def get_chapter(self, version, book, chapter): From 2b5ddb13a430779d7a093ba5164d4b4d6d9eaf91 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Sun, 31 Jan 2010 21:49:01 +0200 Subject: [PATCH 035/164] An attempt to fix the problems some folks are having. --- openlp/plugins/bibles/bibleplugin.py | 4 +- .../plugins/bibles/forms/bibleimportwizard.py | 13 +----- .../plugins/bibles/forms/importwizardform.py | 44 ++++++++----------- openlp/plugins/bibles/lib/csvbible.py | 4 +- openlp/plugins/bibles/lib/db.py | 3 +- openlp/plugins/bibles/lib/http.py | 19 ++++---- openlp/plugins/bibles/lib/manager.py | 28 +++++++----- openlp/plugins/bibles/lib/mediaitem.py | 2 +- openlp/plugins/bibles/lib/opensong.py | 33 ++++++-------- openlp/plugins/bibles/lib/osis.py | 4 +- openlp/plugins/custom/forms/editcustomform.py | 10 ++--- openlp/plugins/songs/lib/mediaitem.py | 11 +++-- 12 files changed, 80 insertions(+), 95 deletions(-) diff --git a/openlp/plugins/bibles/bibleplugin.py b/openlp/plugins/bibles/bibleplugin.py index 912a02212..5fafac65b 100644 --- a/openlp/plugins/bibles/bibleplugin.py +++ b/openlp/plugins/bibles/bibleplugin.py @@ -45,7 +45,7 @@ class BiblePlugin(Plugin): def initialise(self): log.info(u'bibles Initialising') if self.biblemanager is None: - self.biblemanager = BibleManager(self.config) + self.biblemanager = BibleManager(self, self.config) Plugin.initialise(self) self.insert_toolbox_item() self.ImportBibleItem.setVisible(True) @@ -90,4 +90,4 @@ class BiblePlugin(Plugin): about_text = self.trUtf8('<strong>Bible Plugin</strong><br />This ' 'plugin allows bible verses from different sources to be ' 'displayed on the screen during the service.') - return about_text \ No newline at end of file + return about_text diff --git a/openlp/plugins/bibles/forms/bibleimportwizard.py b/openlp/plugins/bibles/forms/bibleimportwizard.py index ec190182a..59e38e39a 100644 --- a/openlp/plugins/bibles/forms/bibleimportwizard.py +++ b/openlp/plugins/bibles/forms/bibleimportwizard.py @@ -91,15 +91,6 @@ class Ui_BibleImportWizard(object): self.OsisLayout.setMargin(0) self.OsisLayout.setSpacing(8) self.OsisLayout.setObjectName(u'OsisLayout') - self.OsisBibleNameLabel = QtGui.QLabel(self.OsisPage) - self.OsisBibleNameLabel.setIndent(0) - self.OsisBibleNameLabel.setObjectName(u'OsisBibleNameLabel') - self.OsisLayout.setWidget(0, QtGui.QFormLayout.LabelRole, - self.OsisBibleNameLabel) - self.OsisBibleNameEdit = QtGui.QLineEdit(self.OsisPage) - self.OsisBibleNameEdit.setObjectName(u'OsisBibleNameEdit') - self.OsisLayout.setWidget(0, QtGui.QFormLayout.FieldRole, - self.OsisBibleNameEdit) self.OsisLocationLabel = QtGui.QLabel(self.OsisPage) self.OsisLocationLabel.setObjectName(u'OsisLocationLabel') self.OsisLayout.setWidget(1, QtGui.QFormLayout.LabelRole, @@ -307,7 +298,6 @@ class Ui_BibleImportWizard(object): self.ImportLayout.addWidget(self.ImportProgressBar) BibleImportWizard.addPage(self.ImportPage) - self.retranslateUi(BibleImportWizard) self.FormatWidget.setCurrentIndex(0) self.WebDownloadTabWidget.setCurrentIndex(0) @@ -333,7 +323,6 @@ class Ui_BibleImportWizard(object): self.FormatComboBox.setItemText(1, self.trUtf8('CSV')) self.FormatComboBox.setItemText(2, self.trUtf8('OpenSong')) self.FormatComboBox.setItemText(3, self.trUtf8('Web Download')) - self.OsisBibleNameLabel.setText(self.trUtf8('Bible Name:')) self.OsisLocationLabel.setText(self.trUtf8('File Location:')) self.BooksLocationLabel.setText(self.trUtf8('Books Location:')) self.VerseLocationLabel.setText(self.trUtf8('Verse Location:')) @@ -361,4 +350,4 @@ class Ui_BibleImportWizard(object): self.ImportPage.setSubTitle( self.trUtf8('Please wait while your Bible is imported.')) self.ImportProgressLabel.setText(self.trUtf8('Ready.')) - #self.ImportProgressBar.setFormat(u'%p') + self.ImportProgressBar.setFormat(u'%p%') diff --git a/openlp/plugins/bibles/forms/importwizardform.py b/openlp/plugins/bibles/forms/importwizardform.py index 4fcb18ad4..8be453db0 100644 --- a/openlp/plugins/bibles/forms/importwizardform.py +++ b/openlp/plugins/bibles/forms/importwizardform.py @@ -27,6 +27,7 @@ import logging import os import os.path from time import sleep +import csv from PyQt4 import QtCore, QtGui @@ -107,14 +108,6 @@ class ImportWizardForm(QtGui.QWizard, Ui_BibleImportWizard): elif self.currentId() == 1: # Select page if self.field(u'source_format').toInt()[0] == BibleFormat.OSIS: - if self.field(u'osis_biblename').toString() == u'': - QtGui.QMessageBox.critical(self, - self.trUtf8('Invalid Bible Name'), - self.trUtf8('You need to specify a name for your ' - 'Bible!'), - QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok)) - self.OsisBibleNameEdit.setFocus() - return False if self.field(u'osis_location').toString() == u'': QtGui.QMessageBox.critical(self, self.trUtf8('Invalid Bible Location'), @@ -218,8 +211,6 @@ class ImportWizardForm(QtGui.QWizard, Ui_BibleImportWizard): def registerFields(self): self.SelectPage.registerField( u'source_format', self.FormatComboBox) - self.SelectPage.registerField( - u'osis_biblename', self.OsisBibleNameEdit) self.SelectPage.registerField( u'osis_location', self.OSISLocationEdit) self.SelectPage.registerField( @@ -247,7 +238,6 @@ class ImportWizardForm(QtGui.QWizard, Ui_BibleImportWizard): def setDefaults(self): self.setField(u'source_format', 0) - self.setField(u'osis_biblename', u'') self.setField(u'osis_location', u'') self.setField(u'csv_booksfile', u'') self.setField(u'csv_versefile', u'') @@ -276,29 +266,33 @@ class ImportWizardForm(QtGui.QWizard, Ui_BibleImportWizard): fbibles = None try: self.web_bible_list[DownloadLocation.Crosswalk] = {} - fbibles = open(os.path.join(filepath, u'crosswalkbooks.csv'), 'r') - for line in fbibles: - parts = line.split(u',') - self.web_bible_list[DownloadLocation.Crosswalk][parts[0]] = \ - parts[1].rstrip() + books_file = open(os.path.join(filepath, u'crosswalkbooks.csv'), 'r') + dialect = csv.Sniffer().sniff(books_file.read(1024)) + books_file.seek(0) + books_reader = csv.reader(books_file, dialect) + for line in books_reader: + self.web_bible_list[DownloadLocation.Crosswalk][line[0]] = \ + unicode(line[1], u'utf-8').strip() except: log.exception(u'Crosswalk resources missing') finally: - if fbibles: - fbibles.close() + if books_file: + books_file.close() #Load and store BibleGateway Bibles try: self.web_bible_list[DownloadLocation.BibleGateway] = {} - fbibles = open(os.path.join(filepath, u'biblegateway.csv'), 'r') - for line in fbibles: - parts = line.split(u',') - self.web_bible_list[DownloadLocation.BibleGateway][parts[0]] = \ - parts[1].rstrip() + books_file = open(os.path.join(filepath, u'biblegateway.csv'), 'r') + dialect = csv.Sniffer().sniff(books_file.read(1024)) + books_file.seek(0) + books_reader = csv.reader(books_file, dialect) + for line in books_reader: + self.web_bible_list[DownloadLocation.BibleGateway][line[0]] = \ + unicode(line[1], u'utf-8').strip() except: log.exception(u'Biblegateway resources missing') finally: - if fbibles: - fbibles.close() + if books_file: + books_file.close() def getFileName(self, title, editbox): filename = QtGui.QFileDialog.getOpenFileName(self, title, diff --git a/openlp/plugins/bibles/lib/csvbible.py b/openlp/plugins/bibles/lib/csvbible.py index f98ef0c86..d0db5159d 100644 --- a/openlp/plugins/bibles/lib/csvbible.py +++ b/openlp/plugins/bibles/lib/csvbible.py @@ -37,13 +37,13 @@ class CSVBible(BibleDB): This class provides a specialisation for importing of CSV Bibles. """ - def __init__(self, **kwargs): + def __init__(self, parent, **kwargs): """ Loads a Bible from a pair of CVS files passed in This class assumes the files contain all the information and a clean bible is being loaded. """ - BibleDB.__init__(self, **kwargs) + BibleDB.__init__(self, parent, **kwargs) log.info(self.__class__.__name__) if u'booksfile' not in kwargs: raise KeyError(u'You have to supply a file to import books from.') diff --git a/openlp/plugins/bibles/lib/db.py b/openlp/plugins/bibles/lib/db.py index 2131737fd..5c7229f4a 100644 --- a/openlp/plugins/bibles/lib/db.py +++ b/openlp/plugins/bibles/lib/db.py @@ -42,7 +42,7 @@ class BibleDB(QtCore.QObject): rather than depending on yet another object. """ - def __init__(self, **kwargs): + def __init__(self, parent, **kwargs): """ The constructor loads up the database and creates and initialises the tables if the database doesn't exist. @@ -60,6 +60,7 @@ class BibleDB(QtCore.QObject): The configuration object, passed in from the plugin. """ log.info(u'BibleDBimpl loaded') + QtCore.QObject.__init__(self) if u'path' not in kwargs: raise KeyError(u'Missing keyword argument "path".') if u'name' not in kwargs: diff --git a/openlp/plugins/bibles/lib/http.py b/openlp/plugins/bibles/lib/http.py index 5b75407be..4472000d5 100644 --- a/openlp/plugins/bibles/lib/http.py +++ b/openlp/plugins/bibles/lib/http.py @@ -142,7 +142,7 @@ class CWExtract(BibleCommon): class HTTPBible(BibleDB): log.info(u'%s loaded', __name__) - def __init__(self, **kwargs): + def __init__(self, parent, **kwargs): """ Finds all the bibles defined for the system Creates an Interface Object for each bible containing connection @@ -152,7 +152,7 @@ class HTTPBible(BibleDB): Init confirms the bible exists and stores the database path. """ - BibleDB.__init__(self, **kwargs) + BibleDB.__init__(self, parent, **kwargs) if u'download_source' not in kwargs: raise KeyError(u'Missing keyword argument "download_source"') if u'download_name' not in kwargs: @@ -267,13 +267,10 @@ class HTTPBible(BibleDB): def set_books(self, books): self.books = books - def lookup_book(self, book): - log.debug('Looking up "%s" in %s', (book, self.books)) - if book in self.books: - return self.books[book] - else: - for details in self.books: - if self.books[details][u'abbr'] == book: - return self.books[details] - return None + def lookup_book(self, name): + log.debug('Looking up "%s" in %s', (name, self.books)) + for book in self.books: + if book[u'name'] == name or book[u'abbr'] == name: + return book + return None diff --git a/openlp/plugins/bibles/lib/manager.py b/openlp/plugins/bibles/lib/manager.py index 88982a8bf..3ca76bbab 100644 --- a/openlp/plugins/bibles/lib/manager.py +++ b/openlp/plugins/bibles/lib/manager.py @@ -90,7 +90,7 @@ class BibleManager(object): log = logging.getLogger(u'BibleManager') log.info(u'Bible manager loaded') - def __init__(self, config): + def __init__(self, parent, config): """ Finds all the bibles defined for the system and creates an interface object for each bible containing connection information. Throws @@ -101,8 +101,9 @@ class BibleManager(object): ``config`` The plugin's configuration object. """ - self.config = config log.debug(u'Bible Initialising') + self.config = config + self.parent = parent self.web = u'Web' self.db_cache = None self.http_cache = None @@ -120,18 +121,19 @@ class BibleManager(object): filepath, u'..', u'resources', u'httpbooks.csv')) books_file = None try: - self.http_books = {} + self.http_books = [] books_file = open(filepath, u'r') dialect = csv.Sniffer().sniff(books_file.read(1024)) books_file.seek(0) books_reader = csv.reader(books_file, dialect) for line in books_reader: - self.http_books[line[0]] = { - u'name': line[0], - u'abbr': line[1], + self.http_books.append({ + u'name': unicode(line[0]), + u'abbr': unicode(line[1]), u'test': line[2], - u'chap': line[3] - } + u'chap': line[3], + u'ordr': order + }) except: log.exception(u'Failed to load http books.') finally: @@ -148,13 +150,13 @@ class BibleManager(object): self.web_bibles_present = False for filename in files: name, extension = os.path.splitext(filename) - self.db_cache[name] = BibleDB(path=self.path, name=name, config=self.config) + self.db_cache[name] = BibleDB(self.parent, path=self.path, name=name, config=self.config) # look to see if lazy load bible exists and get create getter. source = self.db_cache[name].get_meta(u'download source') if source: self.web_bibles_present = True download_name = self.db_cache[name].get_meta(u'download name').value - web_bible = HTTPBible(path=self.path, name=name, + web_bible = HTTPBible(self.parent, path=self.path, name=name, config=self.config, download_source=source.value, download_name=download_name) meta_proxy = self.db_cache[name].get_meta(u'proxy url') @@ -187,7 +189,7 @@ class BibleManager(object): class_ = BibleFormat.get_class(type) kwargs['path'] = self.path kwargs['config'] = self.config - importer = class_(**kwargs) + importer = class_(self.parent, **kwargs) name = importer.register(self.import_wizard) self.db_cache[name] = importer return importer.do_import() @@ -381,8 +383,12 @@ class BibleManager(object): """ Check cache to see if new bible """ + if not isinstance(name, unicode): + name = unicode(name, u'utf8') for bible, db_object in self.db_cache.iteritems(): log.debug(u'Bible from cache in is_new_bible %s', bible) + if not isinstance(bible, unicode): + bible = unicode(bible, u'utf8') if bible == name: return True return False diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index 341d36c6f..213c79c54 100644 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -357,7 +357,7 @@ class BibleMediaItem(MediaManagerItem): def onNoBookFound(self): QtGui.QMessageBox.critical(self, self.trUtf8('No Book Found'), - self.trUtf8('No matching Book could be found in this Bible.'), + self.trUtf8('No matching book could be found in this Bible.'), QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok), QtGui.QMessageBox.Ok ) diff --git a/openlp/plugins/bibles/lib/opensong.py b/openlp/plugins/bibles/lib/opensong.py index 64eeeb0f9..9e0c77b13 100644 --- a/openlp/plugins/bibles/lib/opensong.py +++ b/openlp/plugins/bibles/lib/opensong.py @@ -42,18 +42,18 @@ class OpenSongBible(BibleDB): OpenSong Bible format importer class. """ - def __init__(self, **kwargs): + def __init__(self, parent, **kwargs): """ Constructor to create and set up an instance of the OpenSongBible class. This class is used to import Bibles from OpenSong's XML format. """ log.debug(__name__) - BibleDB.__init__(self, **kwargs) + BibleDB.__init__(self, parent, **kwargs) if 'filename' not in kwargs: raise KeyError(u'You have to supply a file name to import from.') self.filename = kwargs['filename'] - #QtCore.QObject.connect(Receiver.get_receiver(), - # QtCore.SIGNAL(u'openlpstopimport'), self.stop_import) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'openlpstopimport'), self.stop_import) def stop_import(self): """ @@ -69,27 +69,20 @@ class OpenSongBible(BibleDB): log.debug(u'Starting OpenSong import from "%s"' % self.filename) self.filename = unicode(self.filename, u'utf-8') self.wizard.incrementProgressBar(u'Preparing for import...') - detect_file = None - try: - detect_file = open(self.filename, u'r') - details = chardet.detect(detect_file.read()) - except: - log.exception(u'Failed to detect OpenSong file encoding') - return False - finally: - if detect_file: - detect_file.close() file = None success = True try: - file = codecs.open(self.filename, u'r', details['encoding']) + # NOTE: We don't need to do any of the normal encoding detection + # here, because lxml does it's own encoding detection, and the two + # mechanisms together interfere with each other. + file = open(self.filename, u'r') opensong = objectify.parse(file) bible = opensong.getroot() for book in bible.b: if self.stop_import: break - db_book = self.create_book(book.attrib[u'n'], - book.attrib[u'n'][:4]) + db_book = self.create_book(unicode(book.attrib[u'n']), + unicode(book.attrib[u'n'][:4])) for chapter in book.c: if self.stop_import: break @@ -100,12 +93,12 @@ class OpenSongBible(BibleDB): db_book.id, int(chapter.attrib[u'n']), int(verse.attrib[u'n']), - verse.text + unicode(verse.text) ) Receiver.send_message(u'process_events') self.wizard.incrementProgressBar( - QtCore.QString('Importing %s %s' % \ - (db_book.name, chapter.attrib[u'n']))) + QtCore.QString('%s %s %s' % (self.trUtf8('Importing'),\ + db_book.name, chapter.attrib[u'n']))) self.commit() except: log.exception(u'Loading bible from OpenSong file failed') diff --git a/openlp/plugins/bibles/lib/osis.py b/openlp/plugins/bibles/lib/osis.py index a7e4ba374..b68bd5ff5 100644 --- a/openlp/plugins/bibles/lib/osis.py +++ b/openlp/plugins/bibles/lib/osis.py @@ -43,13 +43,13 @@ class OSISBible(BibleDB): log = logging.getLogger(u'BibleOSISImpl') log.info(u'BibleOSISImpl loaded') - def __init__(self, **kwargs): + def __init__(self, parent, **kwargs): """ Constructor to create and set up an instance of the OpenSongBible class. This class is used to import Bibles from OpenSong's XML format. """ log.debug(__name__) - BibleDB.__init__(self, **kwargs) + BibleDB.__init__(self, parent, **kwargs) if u'filename' not in kwargs: raise KeyError(u'You have to supply a file name to import from.') self.filename = kwargs[u'filename'] diff --git a/openlp/plugins/custom/forms/editcustomform.py b/openlp/plugins/custom/forms/editcustomform.py index 98eb5598a..e701c0938 100644 --- a/openlp/plugins/custom/forms/editcustomform.py +++ b/openlp/plugins/custom/forms/editcustomform.py @@ -153,10 +153,10 @@ class EditCustomForm(QtGui.QDialog, Ui_customEditDialog): sxml.add_verse_to_lyrics(u'custom', unicode(count), unicode(self.VerseListView.item(i).text())) count += 1 - self.customSlide.title = unicode(self.TitleEdit.displayText()) - self.customSlide.text = unicode(sxml.extract_xml()) - self.customSlide.credits = unicode(self.CreditEdit.displayText()) - self.customSlide.theme_name = unicode(self.ThemeComboBox.currentText()) + self.customSlide.title = unicode(self.TitleEdit.displayText(), u'utf-8') + self.customSlide.text = unicode(sxml.extract_xml(), u'utf-8') + self.customSlide.credits = unicode(self.CreditEdit.displayText(), u'utf-8') + self.customSlide.theme_name = unicode(self.ThemeComboBox.currentText(), u'utf-8') self.custommanager.save_slide(self.customSlide) return True @@ -257,4 +257,4 @@ class EditCustomForm(QtGui.QDialog, Ui_customEditDialog): if len(self.VerseTextEdit.toPlainText()) > 0: self.VerseTextEdit.setFocus() return False, self.trUtf8('You have unsaved data') - return True, u'' \ No newline at end of file + return True, u'' diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py index 9aef321e5..f5fe0686b 100644 --- a/openlp/plugins/songs/lib/mediaitem.py +++ b/openlp/plugins/songs/lib/mediaitem.py @@ -185,8 +185,13 @@ class SongMediaItem(MediaManagerItem): if author_list != u'': author_list = author_list + u', ' author_list = author_list + author.display_name - song_detail = unicode(self.trUtf8('%s (%s)' % \ - (unicode(song.title), unicode(author_list)))) + if not isinstance(author_list, unicode): + author_list = unicode(author_list, u'utf8') + if isinstance(song.title, unicode): + song_title = song.title + else: + song_title = unicode(song.title, u'utf8') + song_detail = u'%s (%s)' % (song_title, author_list) song_name = QtGui.QListWidgetItem(song_detail) song_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(song.id)) self.ListView.addItem(song_name) @@ -339,4 +344,4 @@ class SongMediaItem(MediaManagerItem): service_item.audit = [ song.title, author_audit, song.copyright, song.ccli_number ] - return True \ No newline at end of file + return True From db534dc2e84a61234a5f51b30c2a8e3d91bd74ef Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Sun, 31 Jan 2010 21:51:23 +0200 Subject: [PATCH 036/164] Removed signals while they're still not working. --- openlp/plugins/bibles/lib/csvbible.py | 4 ++-- openlp/plugins/bibles/lib/opensong.py | 4 ++-- openlp/plugins/bibles/lib/osis.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openlp/plugins/bibles/lib/csvbible.py b/openlp/plugins/bibles/lib/csvbible.py index d0db5159d..94ebf3ce7 100644 --- a/openlp/plugins/bibles/lib/csvbible.py +++ b/openlp/plugins/bibles/lib/csvbible.py @@ -51,8 +51,8 @@ class CSVBible(BibleDB): if u'versesfile' not in kwargs: raise KeyError(u'You have to supply a file to import verses from.') self.versesfile = kwargs[u'versesfile'] - QtCore.QObject.connect(Receiver.get_receiver(), - QtCore.SIGNAL(u'openlpstopimport'), self.stop_import) + #QtCore.QObject.connect(Receiver.get_receiver(), + # QtCore.SIGNAL(u'openlpstopimport'), self.stop_import) def stop_import(self): """ diff --git a/openlp/plugins/bibles/lib/opensong.py b/openlp/plugins/bibles/lib/opensong.py index 9e0c77b13..f9f445973 100644 --- a/openlp/plugins/bibles/lib/opensong.py +++ b/openlp/plugins/bibles/lib/opensong.py @@ -52,8 +52,8 @@ class OpenSongBible(BibleDB): if 'filename' not in kwargs: raise KeyError(u'You have to supply a file name to import from.') self.filename = kwargs['filename'] - QtCore.QObject.connect(Receiver.get_receiver(), - QtCore.SIGNAL(u'openlpstopimport'), self.stop_import) + #QtCore.QObject.connect(Receiver.get_receiver(), + # QtCore.SIGNAL(u'openlpstopimport'), self.stop_import) def stop_import(self): """ diff --git a/openlp/plugins/bibles/lib/osis.py b/openlp/plugins/bibles/lib/osis.py index b68bd5ff5..dbcb1d57f 100644 --- a/openlp/plugins/bibles/lib/osis.py +++ b/openlp/plugins/bibles/lib/osis.py @@ -81,8 +81,8 @@ class OSISBible(BibleDB): finally: if fbibles: fbibles.close() - QtCore.QObject.connect(Receiver.get_receiver(), - QtCore.SIGNAL(u'openlpstopimport'), self.stop_import) + #QtCore.QObject.connect(Receiver.get_receiver(), + # QtCore.SIGNAL(u'openlpstopimport'), self.stop_import) def stop_import(self): """ From 01ec0f407fc6bffd4c836e7183e5a0fed00cbe49 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Sun, 31 Jan 2010 22:36:54 +0200 Subject: [PATCH 037/164] Fixed up some of the Bible loading --- .../plugins/bibles/forms/importwizardform.py | 22 +++++++++---------- openlp/plugins/bibles/lib/db.py | 16 +++++++------- openlp/plugins/bibles/lib/http.py | 8 +++---- openlp/plugins/bibles/lib/manager.py | 3 +-- 4 files changed, 24 insertions(+), 25 deletions(-) diff --git a/openlp/plugins/bibles/forms/importwizardform.py b/openlp/plugins/bibles/forms/importwizardform.py index 8be453db0..c490e960e 100644 --- a/openlp/plugins/bibles/forms/importwizardform.py +++ b/openlp/plugins/bibles/forms/importwizardform.py @@ -97,9 +97,9 @@ class ImportWizardForm(QtGui.QWizard, Ui_BibleImportWizard): QtCore.SIGNAL(u'currentIdChanged(int)'), self.onCurrentIdChanged) - def show(self): + def exec_(self): self.setDefaults() - return QtGui.QWizard.show() + return QtGui.QWizard.exec_(self) def validateCurrentPage(self): if self.currentId() == 0: @@ -238,22 +238,22 @@ class ImportWizardForm(QtGui.QWizard, Ui_BibleImportWizard): def setDefaults(self): self.setField(u'source_format', 0) - self.setField(u'osis_location', u'') - self.setField(u'csv_booksfile', u'') - self.setField(u'csv_versefile', u'') - self.setField(u'opensong_file', u'') - self.setField(u'web_location', 0) + self.setField(u'osis_location', '') + self.setField(u'csv_booksfile', '') + self.setField(u'csv_versefile', '') + self.setField(u'opensong_file', '') + self.setField(u'web_location', DownloadLocation.Crosswalk) self.setField(u'web_biblename', self.BibleComboBox) self.setField(u'proxy_server', - self.config.get_config(u'proxy address', u'')) + self.config.get_config(u'proxy address', '')) self.setField(u'proxy_username', - self.config.get_config(u'proxy username',u'')) + self.config.get_config(u'proxy username','')) self.setField(u'proxy_password', - self.config.get_config(u'proxy password',u'')) + self.config.get_config(u'proxy password','')) self.setField(u'license_version', self.VersionNameEdit) self.setField(u'license_copyright', self.CopyrightEdit) self.setField(u'license_permission', self.PermissionEdit) - self.onLocationComboBoxChanged(0) + self.onLocationComboBoxChanged(DownloadLocation.Crosswalk) def loadWebBibles(self): """ diff --git a/openlp/plugins/bibles/lib/db.py b/openlp/plugins/bibles/lib/db.py index 5c7229f4a..bd29001e2 100644 --- a/openlp/plugins/bibles/lib/db.py +++ b/openlp/plugins/bibles/lib/db.py @@ -109,7 +109,7 @@ class BibleDB(QtCore.QObject): self.create_testament(u'Apocrypha') def create_testament(self, testament): - log.debug(u'%s: %s', __name__, testament) + log.debug(u'BibleDB.create_testament("%s")', testament) self.session.add(Testament.populate(name=testament)) self.commit() @@ -153,11 +153,11 @@ class BibleDB(QtCore.QObject): self.commit() def get_books(self): - log.debug(__name__) + log.debug(u'BibleDB.get_books()') return self.session.query(Book).order_by(Book.id).all() def get_book(self, book): - log.debug(u'%s: %s', __name__, book) + log.debug(u'BibleDb.get_book("%s")', __name__, book) db_book = self.session.query(Book)\ .filter(Book.name.like(book + u'%'))\ .first() @@ -168,7 +168,7 @@ class BibleDB(QtCore.QObject): return db_book def get_chapter(self, id, chapter): - log.debug(u'%s: %s, %s', __name__, id, chapter) + log.debug(u'BibleDB.get_chapter("%s", %s)', id, chapter) return self.session.query(Verse)\ .filter_by(chapter=chapter)\ .filter_by(book_id=id)\ @@ -192,7 +192,7 @@ class BibleDB(QtCore.QObject): [(u'Genesis', 1, 1, 1), (u'Genesis', 2, 2, 3)] """ - log.debug(u'%s: %s', __name__, reference_list) + log.debug(u'BibleDB.get_verses: %s', reference_list) verse_list = [] for book, chapter, start_verse, end_verse in reference_list: db_book = self.get_book(book) @@ -221,7 +221,7 @@ class BibleDB(QtCore.QObject): contains spaces, it will split apart and AND'd on the list of values. """ - log.debug(u'%s: %s', __name__, text) + log.debug(u'BibleDB.verse_search("%s")', text) verses = self.session.query(Verse) if text.find(u',') > -1: or_clause = [] @@ -237,7 +237,7 @@ class BibleDB(QtCore.QObject): return verses def get_chapter_count(self, book): - log.debug(u'%s: %s', __name__, book) + log.debug(u'BibleDB.get_chapter_count("%s")', book) count = self.session.query(Verse.chapter).join(Book)\ .filter(Book.name==book)\ .distinct().count() @@ -249,7 +249,7 @@ class BibleDB(QtCore.QObject): return count def get_verse_count(self, book, chapter): - log.debug(u'%s: %s, %s', __name__, book, chapter) + log.debug(u'BibleDB.get_verse_count("%s", %s)', book, chapter) count = self.session.query(Verse).join(Book)\ .filter(Book.name==book)\ .filter(Verse.chapter==chapter)\ diff --git a/openlp/plugins/bibles/lib/http.py b/openlp/plugins/bibles/lib/http.py index 4472000d5..607c94f0d 100644 --- a/openlp/plugins/bibles/lib/http.py +++ b/openlp/plugins/bibles/lib/http.py @@ -42,7 +42,7 @@ class BGExtract(BibleCommon): log.debug(u'init %s', proxyurl) self.proxyurl = proxyurl - def get_chapter(self, version, bookname, chapter) : + def get_bible_chapter(self, version, bookname, chapter) : """ Access and decode bibles via the BibleGateway website @@ -256,10 +256,10 @@ class HTTPBible(BibleDB): return None def get_books(self): - return [Book.populate(name=self.books[book]['name']) for book in self.books] + return [Book.populate(name=book['name']) for book in self.books] def get_chapter_count(self, book): - return self.books[book][u'chap'] + return self.lookup_book(book)[u'chap'] def set_proxy_server(self, server): self.proxy_server = server @@ -268,7 +268,7 @@ class HTTPBible(BibleDB): self.books = books def lookup_book(self, name): - log.debug('Looking up "%s" in %s', (name, self.books)) + log.debug('Looking up "%s" in %s', name, self.books) for book in self.books: if book[u'name'] == name or book[u'abbr'] == name: return book diff --git a/openlp/plugins/bibles/lib/manager.py b/openlp/plugins/bibles/lib/manager.py index 3ca76bbab..4c67dc289 100644 --- a/openlp/plugins/bibles/lib/manager.py +++ b/openlp/plugins/bibles/lib/manager.py @@ -131,8 +131,7 @@ class BibleManager(object): u'name': unicode(line[0]), u'abbr': unicode(line[1]), u'test': line[2], - u'chap': line[3], - u'ordr': order + u'chap': line[3] }) except: log.exception(u'Failed to load http books.') From 17a08e384747d3355f05cc00706794947e470d3b Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Mon, 1 Feb 2010 19:42:52 +0000 Subject: [PATCH 038/164] latest hacking --- .../presentations/lib/impresscontroller.py | 36 ++- .../presentations/lib/messagelistener.py | 291 +++++++----------- .../lib/presentationcontroller.py | 2 +- 3 files changed, 146 insertions(+), 183 deletions(-) diff --git a/openlp/plugins/presentations/lib/impresscontroller.py b/openlp/plugins/presentations/lib/impresscontroller.py index ee15d27be..b160b3b4b 100644 --- a/openlp/plugins/presentations/lib/impresscontroller.py +++ b/openlp/plugins/presentations/lib/impresscontroller.py @@ -62,11 +62,12 @@ class ImpressController(PresentationController): """ log.debug(u'Initialising') PresentationController.__init__(self, plugin, u'Impress') - self.supports= [u'.odp', u'.ppt', u'.pps'] + self.supports= [u'.odp', u'.ppt', u'.pps', u'.pptx', u'.ppsx'] self.process = None self.document = None self.presentation = None self.controller = None + self.desktop = None def check_available(self): """ @@ -86,7 +87,7 @@ class ImpressController(PresentationController): It is not displayed to the user but is available to the UNO interface when required. """ - log.debug(u'start Openoffice') + log.debug(u'start process Openoffice') if os.name == u'nt': self.manager = self.get_com_servicemanager() self.manager._FlagAsMethod(u'Bridge_GetStruct') @@ -102,7 +103,7 @@ class ImpressController(PresentationController): """ Called at system exit to clean up any running presentations """ - log.debug(u'Kill') + log.debug(u'Kill OpenOffice') self.close_presentation() if os.name != u'nt': desktop = self.get_uno_desktop() @@ -122,8 +123,9 @@ class ImpressController(PresentationController): ``presentation`` The file name of the presentatios to the run. """ - log.debug(u'LoadPresentation') + log.debug(u'Load Presentation OpenOffice') self.store_filename(presentation) + print "s.dsk1 ", self.desktop if os.name == u'nt': desktop = self.get_com_desktop() if desktop is None: @@ -136,6 +138,7 @@ class ImpressController(PresentationController): if desktop is None: return self.desktop = desktop + print "s.dsk2 ", self.desktop properties = [] properties.append(self.create_property(u'Minimized', True)) properties = tuple(properties) @@ -154,9 +157,9 @@ class ImpressController(PresentationController): """ Create thumbnail images for presentation """ + log.debug(u'create thumbnails OpenOffice') if self.check_thumbnails(): return - if os.name == u'nt': thumbdir = u'file:///' + self.thumbnailpath.replace( u'\\', u'/').replace(u':', u'|').replace(u' ', u'%20') @@ -171,13 +174,14 @@ class ImpressController(PresentationController): page = pages.getByIndex(idx) doc.getCurrentController().setCurrentPage(page) path = u'%s/%s%s.png'% (thumbdir, self.thumbnailprefix, - unicode(idx+1)) + unicode(idx + 1)) try: doc.storeToURL(path , props) except: log.exception(u'%s\nUnable to store preview' % path) def create_property(self, name, value): + log.debug(u'create property OpenOffice') if os.name == u'nt': prop = self.manager.Bridge_GetStruct(u'com.sun.star.beans.PropertyValue') else: @@ -187,7 +191,7 @@ class ImpressController(PresentationController): return prop def get_uno_desktop(self): - log.debug(u'getUNODesktop') + log.debug(u'get UNO Desktop Openoffice') ctx = None loop = 0 context = uno.getComponentContext() @@ -197,6 +201,7 @@ class ImpressController(PresentationController): try: ctx = resolver.resolve(u'uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext') except: + log.exception(u'Unable to fine running instance ') self.start_process() loop += 1 try: @@ -209,7 +214,7 @@ class ImpressController(PresentationController): return None def get_com_desktop(self): - log.debug(u'getCOMDesktop') + log.debug(u'get COM Desktop OpenOffice') try: desktop = self.manager.createInstance(u'com.sun.star.frame.Desktop') return desktop @@ -218,7 +223,7 @@ class ImpressController(PresentationController): return None def get_com_servicemanager(self): - log.debug(u'get_com_servicemanager') + log.debug(u'get_com_servicemanager openoffice') try: return Dispatch(u'com.sun.star.ServiceManager') except: @@ -231,6 +236,7 @@ class ImpressController(PresentationController): Triggerent by new object being added to SlideController orOpenLP being shut down """ + log.debug(u'close Presentation OpenOffice') if self.document: if self.presentation: try: @@ -243,32 +249,44 @@ class ImpressController(PresentationController): self.document = None def is_loaded(self): + log.debug(u'is loaded OpenOffice') + print "is_loaded " if self.presentation is None or self.document is None: + print "no present or document" return False try: if self.document.getPresentation() is None: + print "no getPresentation" return False except: return False return True def is_active(self): + log.debug(u'is active OpenOffice') + print "is_active " if not self.is_loaded(): + print "False " return False + print "self.con ", self.controller if self.controller is None: return False return True def unblank_screen(self): + log.debug(u'unblank screen OpenOffice') return self.controller.resume() def blank_screen(self): + log.debug(u'blank screen OpenOffice') self.controller.blankScreen(0) def stop_presentation(self): + log.debug(u'stop presentation OpenOffice') self.controller.deactivate() def start_presentation(self): + log.debug(u'start presentation OpenOffice') if self.controller is None or not self.controller.isRunning(): self.presentation.start() # start() returns before the getCurrentComponent is ready. Try for 5 seconds diff --git a/openlp/plugins/presentations/lib/messagelistener.py b/openlp/plugins/presentations/lib/messagelistener.py index 0b2fd6003..fafaa2d95 100644 --- a/openlp/plugins/presentations/lib/messagelistener.py +++ b/openlp/plugins/presentations/lib/messagelistener.py @@ -4,8 +4,8 @@ ############################################################################### # OpenLP - Open Source Lyrics Projection # # --------------------------------------------------------------------------- # -# Copyright (c) 2008-2010 Raoul Snyman # -# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Copyright (c) 2008-2009 Raoul Snyman # +# Portions copyright (c) 2008-2009 Tim Bentley, Jonathan Corwin, Michael # # Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # # Carsten Tinggaard # # --------------------------------------------------------------------------- # @@ -30,118 +30,6 @@ from PyQt4 import QtCore from openlp.core.lib import Receiver -class Controller(object): - """ - This is the Presentation listener who acts on events from the slide - controller and passes the messages on the the correct presentation handlers - """ - global log - log = logging.getLogger(u'Controller') - log.info(u'Controller loaded') - - def __init__(self, live): - self.isLive = live - log.info(u'%s controller loaded' % live) - - def addHandler(self, controller, file): - log.debug(u'Live = %s, addHandler %s' % (self.isLive, file)) - self.controller = controller - if self.controller.is_loaded(): - self.shutdown() - self.controller.load_presentation(file) - if self.isLive: - self.controller.start_presentation() - Receiver.send_message(u'live_slide_hide') - self.controller.slidenumber = 0 - - def activate(self): - log.debug(u'Live = %s, activate' % self.isLive) - if self.controller.is_active(): - return - if not self.controller.is_loaded(): - self.controller.load_presentation(self.controller.filepath) - if self.isLive: - self.controller.start_presentation() - if self.controller.slidenumber > 1: - self.controller.goto_slide(self.controller.slidenumber) - - def slide(self, slide, live): - log.debug(u'Live = %s, slide' % live) -# if not isLive: -# return - self.activate() - self.controller.goto_slide(int(slide) + 1) - self.controller.poll_slidenumber(live) - - def first(self): - """ - Based on the handler passed at startup triggers the first slide - """ - log.debug(u'Live = %s, first' % self.isLive) - if not self.isLive: - return - self.activate() - self.controller.start_presentation() - self.controller.poll_slidenumber(self.isLive) - - def last(self): - """ - Based on the handler passed at startup triggers the first slide - """ - log.debug(u'Live = %s, last' % self.isLive) - if not self.isLive: - return - self.activate() - self.controller.goto_slide(self.controller.get_slide_count()) - self.controller.poll_slidenumber(self.isLive) - - def next(self): - """ - Based on the handler passed at startup triggers the next slide event - """ - log.debug(u'Live = %s, next' % self.isLive) - if not self.isLive: - return - self.activate() - self.controller.next_step() - self.controller.poll_slidenumber(self.isLive) - - def previous(self): - """ - Based on the handler passed at startup triggers the previous slide event - """ - log.debug(u'Live = %s, previous' % self.isLive) - if not self.isLive: - return - self.activate() - self.controller.previous_step() - self.controller.poll_slidenumber(self.isLive) - - def shutdown(self): - """ - Based on the handler passed at startup triggers slide show to shut down - """ - log.debug(u'Live = %s, shutdown' % self.isLive) - self.controller.close_presentation() - self.controller.slidenumber = 0 - #self.timer.stop() - - def blank(self): - if not self.isLive: - return - if not self.controller.is_loaded(): - return - if not self.controller.is_active(): - return - self.controller.blank_screen() - - def unblank(self): - if not self.is_live: - return - self.activate() - self.controller.unblank_screen() - - class MessageListener(object): """ This is the Presentation listener who acts on events from the slide @@ -153,8 +41,8 @@ class MessageListener(object): def __init__(self, controllers): self.controllers = controllers - self.previewHandler = Controller(False) - self.liveHandler = Controller(True) + self.handler = None + self.is_live = None # messages are sent from core.ui.slidecontroller QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'presentations_start'), self.startup) @@ -183,62 +71,133 @@ class MessageListener(object): Start of new presentation Save the handler as any new presentations start here """ - log.debug(u'Startup called with message %s' % message) - self.handler, file, isLive = self.decodeMessage(message) - if isLive: - self.liveHandler.addHandler(self.controllers[self.handler], file) - else: - self.previewHandler.addHandler(self.controllers[self.handler], file) + log.debug(u'startup %s ' % message) + self.handler, file, self.is_live = self.decodeMessage(message) + self.controller = self.controllers[self.handler] + if self.controller.is_loaded(): + self.shutdown(None) + print "aaa ", self.is_live + self.controller.load_presentation(file) + if self.is_live: + self.controller.start_presentation() + Receiver.send_message(u'live_slide_hide') + self.controller.slidenumber = 0 + self.timer.start() + + def activate(self): + log.debug(u'activate') + if self.controller.is_active(): + return + if not self.controller.is_loaded(): + self.controller.load_presentation(self.controller.filepath) + log.debug(u'activate 2') + print "activate 2" + self.controller.start_presentation() + log.debug(u'activate 3') + print "activate 3" + if self.controller.slidenumber > 1: + self.controller.goto_slide(self.controller.slidenumber) def slide(self, message): + log.debug(u'slide %s ' % message) + #Not wanted for preview frame + if not self.is_live: + return slide, live = self.splitMessage(message) - if live: - self.liveHandler.slide(slide, live) - else: - self.previewHandler.slide(slide, live) + self.activate() + print ">>> ", message + if message: + print message[0], self.is_live + self.controller.goto_slide(int(slide) + 1) + self.controller.poll_slidenumber(self.is_live) - def first(self, isLive): - if isLive: - self.liveHandler.first() - else: - self.previewHandler.first() + def first(self, message): + """ + Based on the handler passed at startup triggers the first slide + """ + log.debug(u'first %s ' % message) + #Not wanted for preview frame + if not self.is_live: + return + self.activate() + self.controller.start_presentation() + self.controller.poll_slidenumber(self.is_live) - def last(self, isLive): - if isLive: - self.liveHandler.last() - else: - self.previewHandler.last() + def last(self, message): + """ + Based on the handler passed at startup triggers the first slide + """ + log.debug(u'last %s ' % message) + #Not wanted for preview frame + if not self.is_live: + return + self.activate() + self.controller.goto_slide(self.controller.get_slide_count()) + self.controller.poll_slidenumber(self.is_live) - def next(self, isLive): - if isLive: - self.liveHandler.next() - else: - self.previewHandler.next() + def next(self, message): + """ + Based on the handler passed at startup triggers the next slide event + """ + log.debug(u'next ', message) + #Not wanted for preview frame + if not self.is_live: + return + self.activate() + self.controller.next_step() + self.controller.poll_slidenumber(self.is_live) - def previous(self, isLive): - if isLive: - self.liveHandler.previous() - else: - self.previewHandler.previous() + def previous(self, message): + """ + Based on the handler passed at startup triggers the previous slide event + """ + log.debug(u'previous %s' % message) + if not self.is_live: + return + self.activate() + self.controller.previous_step() + self.controller.poll_slidenumber(self.is_live) - def shutdown(self, isLive): - if isLive: - self.liveHandler.shutdown() + def shutdown(self, message): + """ + Based on the handler passed at startup triggers slide show to shut down + """ + log.debug(u'shutdown %s ' % message) + if self.is_live: Receiver.send_message(u'live_slide_show') - else: - self.previewHandler.shutdown() + self.controller.close_presentation() + self.controller.slidenumber = 0 + self.timer.stop() def blank(self): - if self.isLive: - self.liveHandler.blank() - else: - self.previewHandler.blank() + log.debug(u'blank') + if not self.is_live: + return + if not self.controller.is_loaded(): + return + if not self.controller.is_active(): + return + self.controller.blank_screen() def unblank(self): - if self.isLive: - self.liveHandler.unblank() - else: - self.previewHandler.unblank() + log.debug(u'unblank') + if not self.is_live: + return + self.activate() + self.controller.unblank_screen() + + def decodeMessage(self, message): + """ + Splits the message from the SlideController into it's component parts + + ``message`` + Message containing Presentaion handler name and file to be presented. + """ + file = os.path.join(message[1], message[2]) + return message[0], file, message[4] + + def timeout(self): + self.controller.poll_slidenumber(self.is_live) def splitMessage(self, message): """ @@ -250,17 +209,3 @@ class MessageListener(object): """ bits = message.split(u':') return bits[0], bits[1] - - def decodeMessage(self, message): - """ - Splits the initial message from the SlideController - into it's component parts - - ``message`` - Message containing Presentaion handler name and file to be presented. - """ - file = os.path.join(message[1], message[2]) - return message[0], file, message[4] - - def timeout(self): - self.controller.poll_slidenumber(self.is_live) diff --git a/openlp/plugins/presentations/lib/presentationcontroller.py b/openlp/plugins/presentations/lib/presentationcontroller.py index ac9581d28..db42a482d 100644 --- a/openlp/plugins/presentations/lib/presentationcontroller.py +++ b/openlp/plugins/presentations/lib/presentationcontroller.py @@ -118,7 +118,7 @@ class PresentationController(object): """ global log log = logging.getLogger(u'PresentationController') - log.info(u'loaded') + log.info(u'PresentationController loaded') def __init__(self, plugin=None, name=u'PresentationController'): """ From 377f3ad6ea96d0859fa870592a31d142ba4f51fd Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Mon, 1 Feb 2010 20:06:50 +0000 Subject: [PATCH 039/164] Put the messagelistener back --- .../presentations/lib/messagelistener.py | 291 +++++++++++------- 1 file changed, 173 insertions(+), 118 deletions(-) diff --git a/openlp/plugins/presentations/lib/messagelistener.py b/openlp/plugins/presentations/lib/messagelistener.py index fafaa2d95..0b2fd6003 100644 --- a/openlp/plugins/presentations/lib/messagelistener.py +++ b/openlp/plugins/presentations/lib/messagelistener.py @@ -4,8 +4,8 @@ ############################################################################### # OpenLP - Open Source Lyrics Projection # # --------------------------------------------------------------------------- # -# Copyright (c) 2008-2009 Raoul Snyman # -# Portions copyright (c) 2008-2009 Tim Bentley, Jonathan Corwin, Michael # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # # Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # # Carsten Tinggaard # # --------------------------------------------------------------------------- # @@ -30,6 +30,118 @@ from PyQt4 import QtCore from openlp.core.lib import Receiver +class Controller(object): + """ + This is the Presentation listener who acts on events from the slide + controller and passes the messages on the the correct presentation handlers + """ + global log + log = logging.getLogger(u'Controller') + log.info(u'Controller loaded') + + def __init__(self, live): + self.isLive = live + log.info(u'%s controller loaded' % live) + + def addHandler(self, controller, file): + log.debug(u'Live = %s, addHandler %s' % (self.isLive, file)) + self.controller = controller + if self.controller.is_loaded(): + self.shutdown() + self.controller.load_presentation(file) + if self.isLive: + self.controller.start_presentation() + Receiver.send_message(u'live_slide_hide') + self.controller.slidenumber = 0 + + def activate(self): + log.debug(u'Live = %s, activate' % self.isLive) + if self.controller.is_active(): + return + if not self.controller.is_loaded(): + self.controller.load_presentation(self.controller.filepath) + if self.isLive: + self.controller.start_presentation() + if self.controller.slidenumber > 1: + self.controller.goto_slide(self.controller.slidenumber) + + def slide(self, slide, live): + log.debug(u'Live = %s, slide' % live) +# if not isLive: +# return + self.activate() + self.controller.goto_slide(int(slide) + 1) + self.controller.poll_slidenumber(live) + + def first(self): + """ + Based on the handler passed at startup triggers the first slide + """ + log.debug(u'Live = %s, first' % self.isLive) + if not self.isLive: + return + self.activate() + self.controller.start_presentation() + self.controller.poll_slidenumber(self.isLive) + + def last(self): + """ + Based on the handler passed at startup triggers the first slide + """ + log.debug(u'Live = %s, last' % self.isLive) + if not self.isLive: + return + self.activate() + self.controller.goto_slide(self.controller.get_slide_count()) + self.controller.poll_slidenumber(self.isLive) + + def next(self): + """ + Based on the handler passed at startup triggers the next slide event + """ + log.debug(u'Live = %s, next' % self.isLive) + if not self.isLive: + return + self.activate() + self.controller.next_step() + self.controller.poll_slidenumber(self.isLive) + + def previous(self): + """ + Based on the handler passed at startup triggers the previous slide event + """ + log.debug(u'Live = %s, previous' % self.isLive) + if not self.isLive: + return + self.activate() + self.controller.previous_step() + self.controller.poll_slidenumber(self.isLive) + + def shutdown(self): + """ + Based on the handler passed at startup triggers slide show to shut down + """ + log.debug(u'Live = %s, shutdown' % self.isLive) + self.controller.close_presentation() + self.controller.slidenumber = 0 + #self.timer.stop() + + def blank(self): + if not self.isLive: + return + if not self.controller.is_loaded(): + return + if not self.controller.is_active(): + return + self.controller.blank_screen() + + def unblank(self): + if not self.is_live: + return + self.activate() + self.controller.unblank_screen() + + class MessageListener(object): """ This is the Presentation listener who acts on events from the slide @@ -41,8 +153,8 @@ class MessageListener(object): def __init__(self, controllers): self.controllers = controllers - self.handler = None - self.is_live = None + self.previewHandler = Controller(False) + self.liveHandler = Controller(True) # messages are sent from core.ui.slidecontroller QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'presentations_start'), self.startup) @@ -71,133 +183,62 @@ class MessageListener(object): Start of new presentation Save the handler as any new presentations start here """ - log.debug(u'startup %s ' % message) - self.handler, file, self.is_live = self.decodeMessage(message) - self.controller = self.controllers[self.handler] - if self.controller.is_loaded(): - self.shutdown(None) - print "aaa ", self.is_live - self.controller.load_presentation(file) - if self.is_live: - self.controller.start_presentation() - Receiver.send_message(u'live_slide_hide') - self.controller.slidenumber = 0 - self.timer.start() - - def activate(self): - log.debug(u'activate') - if self.controller.is_active(): - return - if not self.controller.is_loaded(): - self.controller.load_presentation(self.controller.filepath) - log.debug(u'activate 2') - print "activate 2" - self.controller.start_presentation() - log.debug(u'activate 3') - print "activate 3" - if self.controller.slidenumber > 1: - self.controller.goto_slide(self.controller.slidenumber) + log.debug(u'Startup called with message %s' % message) + self.handler, file, isLive = self.decodeMessage(message) + if isLive: + self.liveHandler.addHandler(self.controllers[self.handler], file) + else: + self.previewHandler.addHandler(self.controllers[self.handler], file) def slide(self, message): - log.debug(u'slide %s ' % message) - #Not wanted for preview frame - if not self.is_live: - return slide, live = self.splitMessage(message) - self.activate() - print ">>> ", message - if message: - print message[0], self.is_live - self.controller.goto_slide(int(slide) + 1) - self.controller.poll_slidenumber(self.is_live) + if live: + self.liveHandler.slide(slide, live) + else: + self.previewHandler.slide(slide, live) - def first(self, message): - """ - Based on the handler passed at startup triggers the first slide - """ - log.debug(u'first %s ' % message) - #Not wanted for preview frame - if not self.is_live: - return - self.activate() - self.controller.start_presentation() - self.controller.poll_slidenumber(self.is_live) + def first(self, isLive): + if isLive: + self.liveHandler.first() + else: + self.previewHandler.first() - def last(self, message): - """ - Based on the handler passed at startup triggers the first slide - """ - log.debug(u'last %s ' % message) - #Not wanted for preview frame - if not self.is_live: - return - self.activate() - self.controller.goto_slide(self.controller.get_slide_count()) - self.controller.poll_slidenumber(self.is_live) + def last(self, isLive): + if isLive: + self.liveHandler.last() + else: + self.previewHandler.last() - def next(self, message): - """ - Based on the handler passed at startup triggers the next slide event - """ - log.debug(u'next ', message) - #Not wanted for preview frame - if not self.is_live: - return - self.activate() - self.controller.next_step() - self.controller.poll_slidenumber(self.is_live) + def next(self, isLive): + if isLive: + self.liveHandler.next() + else: + self.previewHandler.next() - def previous(self, message): - """ - Based on the handler passed at startup triggers the previous slide event - """ - log.debug(u'previous %s' % message) - if not self.is_live: - return - self.activate() - self.controller.previous_step() - self.controller.poll_slidenumber(self.is_live) + def previous(self, isLive): + if isLive: + self.liveHandler.previous() + else: + self.previewHandler.previous() - def shutdown(self, message): - """ - Based on the handler passed at startup triggers slide show to shut down - """ - log.debug(u'shutdown %s ' % message) - if self.is_live: + def shutdown(self, isLive): + if isLive: + self.liveHandler.shutdown() Receiver.send_message(u'live_slide_show') - self.controller.close_presentation() - self.controller.slidenumber = 0 - self.timer.stop() + else: + self.previewHandler.shutdown() def blank(self): - log.debug(u'blank') - if not self.is_live: - return - if not self.controller.is_loaded(): - return - if not self.controller.is_active(): - return - self.controller.blank_screen() + if self.isLive: + self.liveHandler.blank() + else: + self.previewHandler.blank() def unblank(self): - log.debug(u'unblank') - if not self.is_live: - return - self.activate() - self.controller.unblank_screen() - - def decodeMessage(self, message): - """ - Splits the message from the SlideController into it's component parts - - ``message`` - Message containing Presentaion handler name and file to be presented. - """ - file = os.path.join(message[1], message[2]) - return message[0], file, message[4] - - def timeout(self): - self.controller.poll_slidenumber(self.is_live) + if self.isLive: + self.liveHandler.unblank() + else: + self.previewHandler.unblank() def splitMessage(self, message): """ @@ -209,3 +250,17 @@ class MessageListener(object): """ bits = message.split(u':') return bits[0], bits[1] + + def decodeMessage(self, message): + """ + Splits the initial message from the SlideController + into it's component parts + + ``message`` + Message containing Presentaion handler name and file to be presented. + """ + file = os.path.join(message[1], message[2]) + return message[0], file, message[4] + + def timeout(self): + self.controller.poll_slidenumber(self.is_live) From 632fae5a7662207e7bfa16847e1eb7736a95f279 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Tue, 2 Feb 2010 18:16:11 +0000 Subject: [PATCH 040/164] Remove print statements --- openlp/core/ui/servicemanager.py | 10 +++++----- .../presentations/lib/impresscontroller.py | 16 ++++++++-------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 5f384a955..1eef9a81b 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -523,11 +523,11 @@ class ServiceManager(QtGui.QWidget): self.parent.serviceChanged(True, self.serviceName) def validateItem(self, serviceItem): - print "---" - print serviceItem.name - print serviceItem.title - print serviceItem.service_item_path - print serviceItem.service_item_type +# print "---" +# print serviceItem.name +# print serviceItem.title +# print serviceItem.service_item_path +# print serviceItem.service_item_type return True def cleanUp(self): diff --git a/openlp/plugins/presentations/lib/impresscontroller.py b/openlp/plugins/presentations/lib/impresscontroller.py index b160b3b4b..b58a9affc 100644 --- a/openlp/plugins/presentations/lib/impresscontroller.py +++ b/openlp/plugins/presentations/lib/impresscontroller.py @@ -125,7 +125,7 @@ class ImpressController(PresentationController): """ log.debug(u'Load Presentation OpenOffice') self.store_filename(presentation) - print "s.dsk1 ", self.desktop + #print "s.dsk1 ", self.desktop if os.name == u'nt': desktop = self.get_com_desktop() if desktop is None: @@ -138,7 +138,7 @@ class ImpressController(PresentationController): if desktop is None: return self.desktop = desktop - print "s.dsk2 ", self.desktop + #print "s.dsk2 ", self.desktop properties = [] properties.append(self.create_property(u'Minimized', True)) properties = tuple(properties) @@ -250,13 +250,13 @@ class ImpressController(PresentationController): def is_loaded(self): log.debug(u'is loaded OpenOffice') - print "is_loaded " + #print "is_loaded " if self.presentation is None or self.document is None: - print "no present or document" + #print "no present or document" return False try: if self.document.getPresentation() is None: - print "no getPresentation" + #print "no getPresentation" return False except: return False @@ -264,11 +264,11 @@ class ImpressController(PresentationController): def is_active(self): log.debug(u'is active OpenOffice') - print "is_active " + #print "is_active " if not self.is_loaded(): - print "False " + #print "False " return False - print "self.con ", self.controller + #print "self.con ", self.controller if self.controller is None: return False return True From af580436900957047c5c442f3ae787b853046d5c Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Tue, 2 Feb 2010 20:05:21 +0000 Subject: [PATCH 041/164] Make startup smoother with a Thread --- openlp.pyw | 2 +- openlp/core/ui/mainwindow.py | 16 ++++++++++++++++ version.txt | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/openlp.pyw b/openlp.pyw index 4741059d2..78e10e74a 100755 --- a/openlp.pyw +++ b/openlp.pyw @@ -136,7 +136,7 @@ class OpenLP(QtGui.QApplication): # now kill the splashscreen self.splash.finish(self.mainWindow) self.mainWindow.repaint() - self.mainWindow.versionCheck() + self.mainWindow.versionThread() return self.exec_() def main(): diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 226c629e8..a17ee7d5a 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -25,6 +25,7 @@ import os import logging +import time from PyQt4 import QtCore, QtGui @@ -50,6 +51,15 @@ media_manager_style = """ border-color: palette(light); } """ +class versionThread(QtCore.QThread): + def __init__(self, parent): + QtCore.QThread.__init__(self, parent) + self.parent = parent + def run (self): + time.sleep(5) + Receiver.send_message(u'version_check') + + class Ui_MainWindow(object): def setupUi(self, MainWindow): """ @@ -483,6 +493,8 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): QtCore.SIGNAL(u'triggered()'), self.onOptionsSettingsItemClicked) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'update_global_theme'), self.defaultThemeChanged) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'version_check'), self.versionCheck) QtCore.QObject.connect(self.FileNewItem, QtCore.SIGNAL(u'triggered()'), self.ServiceManagerContents.onNewService) @@ -582,6 +594,10 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok), QtGui.QMessageBox.Ok) + def versionThread(self): + vT = versionThread(self) + vT.start() + def onHelpAboutItemClicked(self): """ Show the About form diff --git a/version.txt b/version.txt index d490f5631..a41b1ed7a 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.9.0-698 +1.9.0-697 From b5a4178ca7dbcabd6bbbb60b88c43364b5fc9520 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Wed, 3 Feb 2010 17:23:33 +0000 Subject: [PATCH 042/164] Cleanup loggin levels and amend default --- openlp.pyw | 4 ++-- openlp/core/lib/pluginmanager.py | 6 ++++-- openlp/plugins/bibles/bibleplugin.py | 3 ++- openlp/plugins/custom/customplugin.py | 2 +- openlp/plugins/images/imageplugin.py | 2 +- openlp/plugins/media/mediaplugin.py | 2 +- openlp/plugins/remotes/remoteplugin.py | 2 +- 7 files changed, 12 insertions(+), 9 deletions(-) diff --git a/openlp.pyw b/openlp.pyw index 78e10e74a..f87afdbfd 100755 --- a/openlp.pyw +++ b/openlp.pyw @@ -91,7 +91,7 @@ class OpenLP(QtGui.QApplication): u'version': bits[0], u'build': bits[1] } - log.info(u'Openlp version %s build %s' % ( + log.warn(u'Openlp version %s build %s' % ( app_version[u'version'], app_version[u'build'])) except: app_version = { @@ -148,7 +148,7 @@ def main(): usage = u'Usage: %prog [options] [qt-options]' parser = OptionParser(usage=usage) parser.add_option("-l", "--log-level", dest="loglevel", - default="info", metavar="LEVEL", + default="warning", metavar="LEVEL", help="Set logging to LEVEL level. Valid values are " "\"debug\", \"info\", \"warning\".") parser.add_option("-p", "--portable", dest="portable", diff --git a/openlp/core/lib/pluginmanager.py b/openlp/core/lib/pluginmanager.py index c580555ff..adc79fab6 100644 --- a/openlp/core/lib/pluginmanager.py +++ b/openlp/core/lib/pluginmanager.py @@ -54,7 +54,7 @@ class PluginManager(object): log.debug(u'Base path %s ', self.basepath) self.plugins = [] # this has to happen after the UI is sorted self.find_plugins(dir) - log.info(u'Plugin manager done init') + log.warn(u'Plugin manager Initialised') def find_plugins(self, dir, plugin_helpers): """ @@ -200,6 +200,7 @@ class PluginManager(object): % (plugin.name, plugin.is_active())) if plugin.is_active(): plugin.initialise() + log.warn(u'Initialisation Complete for %s ' % plugin.name) if not plugin.is_active(): plugin.remove_toolbox_item() @@ -211,4 +212,5 @@ class PluginManager(object): log.info(u'finalising plugins') for plugin in self.plugins: if plugin.is_active(): - plugin.finalise() \ No newline at end of file + plugin.finalise() + log.warn(u'Finalisation Complete for %s ' % plugin.name) diff --git a/openlp/plugins/bibles/bibleplugin.py b/openlp/plugins/bibles/bibleplugin.py index 912a02212..a43af02de 100644 --- a/openlp/plugins/bibles/bibleplugin.py +++ b/openlp/plugins/bibles/bibleplugin.py @@ -50,6 +50,7 @@ class BiblePlugin(Plugin): self.insert_toolbox_item() self.ImportBibleItem.setVisible(True) self.ExportBibleItem.setVisible(True) + log.warn(u'Bibles Initialised') def finalise(self): log.info(u'Plugin Finalise') @@ -90,4 +91,4 @@ class BiblePlugin(Plugin): about_text = self.trUtf8('<strong>Bible Plugin</strong><br />This ' 'plugin allows bible verses from different sources to be ' 'displayed on the screen during the service.') - return about_text \ No newline at end of file + return about_text diff --git a/openlp/plugins/custom/customplugin.py b/openlp/plugins/custom/customplugin.py index 8da4fabfd..91c89212a 100644 --- a/openlp/plugins/custom/customplugin.py +++ b/openlp/plugins/custom/customplugin.py @@ -72,4 +72,4 @@ class CustomPlugin(Plugin): 'allows slides to be displayed on the screen in the same way ' 'songs are. This plugin provides greater freedom over the ' 'songs plugin.<br>') - return about_text \ No newline at end of file + return about_text diff --git a/openlp/plugins/images/imageplugin.py b/openlp/plugins/images/imageplugin.py index dc4b7eec2..5d4c9252e 100644 --- a/openlp/plugins/images/imageplugin.py +++ b/openlp/plugins/images/imageplugin.py @@ -62,4 +62,4 @@ class ImagePlugin(Plugin): '<i>Override background</i> is chosen and an image is selected ' 'any somgs which are rendered will use the selected image from ' 'the background instead of the one provied by the theme.<br>') - return about_text \ No newline at end of file + return about_text diff --git a/openlp/plugins/media/mediaplugin.py b/openlp/plugins/media/mediaplugin.py index a338fa021..f1b7fa653 100644 --- a/openlp/plugins/media/mediaplugin.py +++ b/openlp/plugins/media/mediaplugin.py @@ -56,4 +56,4 @@ class MediaPlugin(Plugin): def about(self): about_text = self.trUtf8('<b>Media Plugin</b><br>This plugin ' 'allows the playing of audio and video media') - return about_text \ No newline at end of file + return about_text diff --git a/openlp/plugins/remotes/remoteplugin.py b/openlp/plugins/remotes/remoteplugin.py index 428cdbf90..23c8b9bc7 100644 --- a/openlp/plugins/remotes/remoteplugin.py +++ b/openlp/plugins/remotes/remoteplugin.py @@ -83,4 +83,4 @@ class RemotesPlugin(Plugin): 'provides the ability to send messages to a running version of ' 'openlp on a different computer.<br>The Primary use for this ' 'would be to send alerts from a creche') - return about_text \ No newline at end of file + return about_text From 1164ab9beb2592f610bd4dae7cdc7c41a35dad44 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Wed, 3 Feb 2010 18:29:02 +0000 Subject: [PATCH 043/164] Added Extra key events to SlideController --- openlp/core/ui/servicemanager.py | 23 +++++++++++++++++++-- openlp/core/ui/slidecontroller.py | 34 +++++++++++++++++++++++++++---- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 1eef9a81b..6a2589a43 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -227,6 +227,8 @@ class ServiceManager(QtGui.QWidget): QtCore.SIGNAL(u'remote_edit_clear'), self.onRemoteEditClear) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'presentation types'), self.onPresentationTypes) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'servicemanager_next_item'), self.nextItem) # Last little bits of setting up self.config = PluginConfig(u'ServiceManager') self.servicePath = self.config.get_data_path() @@ -236,12 +238,29 @@ class ServiceManager(QtGui.QWidget): def onPresentationTypes(self, presentation_types): self.presentation_types = presentation_types + def nextItem(self): + """ + Called by the SlideController to select the + next service item + """ + selected = self.ServiceManagerList.selectedItems()[0] + lookFor = 0 + serviceIterator = QtGui.QTreeWidgetItemIterator(self.ServiceManagerList) + while serviceIterator.value(): + if lookFor == 1 and serviceIterator.value().parent() is None: + self.ServiceManagerList.setCurrentItem(serviceIterator.value()) + self.makeLive() + return + if serviceIterator.value() == selected: + lookFor = 1 + serviceIterator += 1 + def onMoveSelectionUp(self): """ Moves the selection up the window Called by the up arrow """ - serviceIterator = QTreeWidgetItemIterator(self.ServiceManagerList) + serviceIterator = QtGui.QTreeWidgetItemIterator(self.ServiceManagerList) tempItem = None setLastItem = False while serviceIterator: @@ -266,7 +285,7 @@ class ServiceManager(QtGui.QWidget): Moves the selection down the window Called by the down arrow """ - serviceIterator = QTreeWidgetItemIterator(self.ServiceManagerList) + serviceIterator = QtGui.QTreeWidgetItemIterator(self.ServiceManagerList) firstItem = serviceIterator setSelected = False while serviceIterator: diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 3131c2bfa..71445b074 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -41,6 +41,11 @@ class SlideList(QtGui.QTableWidget): def __init__(self, parent=None, name=None): QtGui.QTableWidget.__init__(self, parent.Controller) self.parent = parent + self.hotkey_map = {QtCore.Qt.Key_Return: 'servicemanager_next_item', + QtCore.Qt.Key_Space: 'live_slidecontroller_next_noloop', + QtCore.Qt.Key_Enter: 'live_slidecontroller_next_noloop', + QtCore.Qt.Key_0: 'servicemanager_next_item', + QtCore.Qt.Key_Backspace: 'live_slidecontroller_previous_noloop'} def keyPressEvent(self, event): if type(event) == QtGui.QKeyEvent: @@ -57,6 +62,9 @@ class SlideList(QtGui.QTableWidget): elif event.key() == QtCore.Qt.Key_PageDown: self.parent.onSlideSelectedLast() event.accept() + elif event.key() in self.hotkey_map: + Receiver.send_message(self.hotkey_map[event.key()]); + event.accept() event.ignore() else: event.ignore() @@ -277,6 +285,11 @@ class SlideController(QtGui.QWidget): QtCore.SIGNAL(u'%s_next' % prefix), self.onSlideSelectedNext) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'%s_previous' % prefix), self.onSlideSelectedPrevious) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'%s_next_noloop' % prefix), self.onSlideSelectedNextNoloop) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'%s_previous_noloop' % prefix), + self.onSlideSelectedPreviousNoloop) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'%s_last' % prefix), self.onSlideSelectedLast) QtCore.QObject.connect(Receiver.get_receiver(), @@ -561,7 +574,10 @@ class SlideController(QtGui.QWidget): rect.y(), rect.width(), rect.height()) self.SlidePreview.setPixmap(winimg) - def onSlideSelectedNext(self): + def onSlideSelectedNextNoloop(self): + self.onSlideSelectedNext(False) + + def onSlideSelectedNext(self, loop=True): """ Go to the next slide. """ @@ -574,11 +590,18 @@ class SlideController(QtGui.QWidget): else: row = self.PreviewListWidget.currentRow() + 1 if row == self.PreviewListWidget.rowCount(): - row = 0 + if loop: + row = 0 + else: + Receiver.send_message('servicemanager_next_item') + return self.PreviewListWidget.selectRow(row) self.onSlideSelected() - def onSlideSelectedPrevious(self): + def onSlideSelectedPreviousNoloop(self): + self.onSlideSelectedPrevious(False) + + def onSlideSelectedPrevious(self, loop=True): """ Go to the previous slide. """ @@ -591,7 +614,10 @@ class SlideController(QtGui.QWidget): else: row = self.PreviewListWidget.currentRow() - 1 if row == -1: - row = self.PreviewListWidget.rowCount() - 1 + if loop: + row = self.PreviewListWidget.rowCount() - 1 + else: + row = 0 self.PreviewListWidget.selectRow(row) self.onSlideSelected() From 5838495bb4926385cce55feb65f673702c12f5d7 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Wed, 3 Feb 2010 18:53:31 +0000 Subject: [PATCH 044/164] Update remote client --- .../{remoteclient-cli.py => remoteclient.py} | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) rename openlp/plugins/remotes/{remoteclient-cli.py => remoteclient.py} (87%) diff --git a/openlp/plugins/remotes/remoteclient-cli.py b/openlp/plugins/remotes/remoteclient.py similarity index 87% rename from openlp/plugins/remotes/remoteclient-cli.py rename to openlp/plugins/remotes/remoteclient.py index a55aafe50..6d1abe877 100755 --- a/openlp/plugins/remotes/remoteclient-cli.py +++ b/openlp/plugins/remotes/remoteclient.py @@ -28,7 +28,6 @@ import socket import sys from optparse import OptionParser - def sendData(options, message): addr = (options.address, options.port) try: @@ -47,34 +46,23 @@ def main(): parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=True, help="make lots of noise [%default]") - parser.add_option("-p", "--port", - default=4316, + parser.add_option("-p", "--port", default=4316, help="IP Port number %default ") parser.add_option("-a", "--address", help="Recipient address ") - parser.add_option("-e", "--event", - default=u'Alert', - help="Action to be undertaken") parser.add_option("-m", "--message", help="Message to be passed for the action") - parser.add_option("-n", "--slidenext", - help="Trigger the next slide") (options, args) = parser.parse_args() if len(args) > 0: parser.print_help() parser.error("incorrect number of arguments") - elif options.message is None: - parser.print_help() - parser.error("No message passed") elif options.address is None: parser.print_help() parser.error("IP address missing") - elif options.slidenext: - options.event = u'next_slide' - options.message = u'' - text = format_message(options) - sendData(options, text) + elif options.message is None: + parser.print_help() + parser.error("No message passed") else: text = format_message(options) sendData(options, text) From e465264630ee51eb5d04ddf8e9d9d1908a71340e Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Thu, 4 Feb 2010 19:38:21 +0200 Subject: [PATCH 045/164] Standardised manager name to "manager" --- openlp/plugins/bibles/bibleplugin.py | 7 ++-- .../plugins/bibles/forms/importwizardform.py | 22 +++++----- openlp/plugins/bibles/lib/manager.py | 4 +- openlp/plugins/bibles/lib/mediaitem.py | 42 +++++++++---------- 4 files changed, 38 insertions(+), 37 deletions(-) diff --git a/openlp/plugins/bibles/bibleplugin.py b/openlp/plugins/bibles/bibleplugin.py index 5fafac65b..d560b0bce 100644 --- a/openlp/plugins/bibles/bibleplugin.py +++ b/openlp/plugins/bibles/bibleplugin.py @@ -40,12 +40,12 @@ class BiblePlugin(Plugin): self.weight = -9 self.icon = build_icon(u':/media/media_bible.png') #Register the bible Manager - self.biblemanager = None + self.manager = None def initialise(self): log.info(u'bibles Initialising') - if self.biblemanager is None: - self.biblemanager = BibleManager(self, self.config) + if self.manager is None: + self.manager = BibleManager(self, self.config) Plugin.initialise(self) self.insert_toolbox_item() self.ImportBibleItem.setVisible(True) @@ -91,3 +91,4 @@ class BiblePlugin(Plugin): 'plugin allows bible verses from different sources to be ' 'displayed on the screen during the service.') return about_text + diff --git a/openlp/plugins/bibles/forms/importwizardform.py b/openlp/plugins/bibles/forms/importwizardform.py index c490e960e..a684c01a3 100644 --- a/openlp/plugins/bibles/forms/importwizardform.py +++ b/openlp/plugins/bibles/forms/importwizardform.py @@ -60,7 +60,7 @@ class ImportWizardForm(QtGui.QWizard, Ui_BibleImportWizard): log = logging.getLogger(u'BibleImportForm') log.info(u'BibleImportForm loaded') - def __init__(self, parent, config, biblemanager, bibleplugin): + def __init__(self, parent, config, manager, bibleplugin): ''' Constructor ''' @@ -69,10 +69,10 @@ class ImportWizardForm(QtGui.QWizard, Ui_BibleImportWizard): self.registerFields() self.finishButton = self.button(QtGui.QWizard.FinishButton) self.cancelButton = self.button(QtGui.QWizard.CancelButton) - self.biblemanager = biblemanager + self.manager = manager self.config = config self.bibleplugin = bibleplugin - self.biblemanager.set_process_dialog(self) + self.manager.set_process_dialog(self) self.web_bible_list = {} self.loadWebBibles() QtCore.QObject.connect(self.LocationComboBox, @@ -162,7 +162,7 @@ class ImportWizardForm(QtGui.QWizard, Ui_BibleImportWizard): QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok)) self.CopyrightEdit.setFocus() return False - elif self.biblemanager.exists( + elif self.manager.exists( self.field(u'license_version').toString()): QtGui.QMessageBox.critical(self, self.trUtf8('Bible Exists'), @@ -320,20 +320,20 @@ class ImportWizardForm(QtGui.QWizard, Ui_BibleImportWizard): success = False if bible_type == BibleFormat.OSIS: # Import an OSIS bible - success = self.biblemanager.import_bible(BibleFormat.OSIS, + success = self.manager.import_bible(BibleFormat.OSIS, name=unicode(self.field(u'license_version').toString()), filename=unicode(self.field(u'osis_location').toString()) ) elif bible_type == BibleFormat.CSV: # Import a CSV bible - success = self.biblemanager.import_bible(BibleFormat.CSV, + success = self.manager.import_bible(BibleFormat.CSV, name=unicode(self.field(u'license_version').toString()), booksfile=self.field(u'csv_booksfile').toString(), versefile=self.field(u'csv_versefile').toString() ) elif bible_type == BibleFormat.OpenSong: # Import an OpenSong bible - success = self.biblemanager.import_bible(BibleFormat.OpenSong, + success = self.manager.import_bible(BibleFormat.OpenSong, name=unicode(self.field(u'license_version').toString()), filename=self.field(u'opensong_file').toString() ) @@ -347,7 +347,7 @@ class ImportWizardForm(QtGui.QWizard, Ui_BibleImportWizard): elif download_location == DownloadLocation.BibleGateway: bible = self.web_bible_list[DownloadLocation.BibleGateway][ unicode(self.BibleComboBox.currentText())] - success = self.biblemanager.import_bible(BibleFormat.WebDownload, + success = self.manager.import_bible(BibleFormat.WebDownload, name=unicode(self.field(u'license_version').toString()), download_source=unicode(DownloadLocation.get_name(download_location)), download_name=unicode(bible), @@ -356,13 +356,13 @@ class ImportWizardForm(QtGui.QWizard, Ui_BibleImportWizard): proxy_password=unicode(self.field(u'proxy_password').toString()) ) if success: - self.biblemanager.save_meta_data( + self.manager.save_meta_data( unicode(self.field(u'license_version').toString()), unicode(self.field(u'license_version').toString()), unicode(self.field(u'license_copyright').toString()), unicode(self.field(u'license_permission').toString()) ) - self.biblemanager.reload_bibles() + self.manager.reload_bibles() self.ImportProgressLabel.setText(self.trUtf8('Finished import.')) else: self.ImportProgressLabel.setText( @@ -372,4 +372,4 @@ class ImportWizardForm(QtGui.QWizard, Ui_BibleImportWizard): self.ImportProgressBar.setValue(self.ImportProgressBar.maximum()) self.finishButton.setVisible(True) self.cancelButton.setVisible(False) - Receiver.send_message(u'process_events') + Receiver.send_message(u'process_events') \ No newline at end of file diff --git a/openlp/plugins/bibles/lib/manager.py b/openlp/plugins/bibles/lib/manager.py index 4c67dc289..7ac99530c 100644 --- a/openlp/plugins/bibles/lib/manager.py +++ b/openlp/plugins/bibles/lib/manager.py @@ -383,11 +383,11 @@ class BibleManager(object): Check cache to see if new bible """ if not isinstance(name, unicode): - name = unicode(name, u'utf8') + name = unicode(name) for bible, db_object in self.db_cache.iteritems(): log.debug(u'Bible from cache in is_new_bible %s', bible) if not isinstance(bible, unicode): - bible = unicode(bible, u'utf8') + bible = unicode(bible) if bible == name: return True return False diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index 213c79c54..fb6b2eafa 100644 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -303,7 +303,7 @@ class BibleMediaItem(MediaManagerItem): def initialise(self): log.debug(u'bible manager initialise') - self.parent.biblemanager.media = self + self.parent.manager.media = self self.loadBibles() self.configUpdated() log.debug(u'bible manager initialise complete') @@ -323,13 +323,13 @@ class BibleMediaItem(MediaManagerItem): self.AdvancedSecondBibleComboBox.clear() self.QuickSecondBibleComboBox.addItem(u'') self.AdvancedSecondBibleComboBox.addItem(u'') - bibles = self.parent.biblemanager.get_bibles(BibleMode.Full) + bibles = self.parent.manager.get_bibles(BibleMode.Full) # load bibles into the combo boxes for bible in bibles: self.QuickVersionComboBox.addItem(bible) self.QuickSecondBibleComboBox.addItem(bible) # Without HTTP - #bibles = self.parent.biblemanager.get_bibles(BibleMode.Partial) + #bibles = self.parent.manager.get_bibles(BibleMode.Partial) first = True # load bibles into the combo boxes for bible in bibles: @@ -375,10 +375,10 @@ class BibleMediaItem(MediaManagerItem): def onNewClick(self): #self.bibleimportform = BibleImportForm( - # self.parent.config, self.parent.biblemanager, self) + # self.parent.config, self.parent.manager, self) #self.bibleimportform.exec_() self.bibleimportform = ImportWizardForm(self, self.parent.config, - self.parent.biblemanager, self.parent) + self.parent.manager, self.parent) self.bibleimportform.exec_() self.reloadBibles() @@ -393,7 +393,7 @@ class BibleMediaItem(MediaManagerItem): bible = unicode(self.AdvancedVersionComboBox.currentText()) book = unicode(self.AdvancedBookComboBox.currentText()) # get the verse count for new chapter - verses = self.parent.biblemanager.get_book_verse_count( + verses = self.parent.manager.get_book_verse_count( bible, book, int(text2)) self.adjustComboBox(1, verses, self.AdvancedToVerse) @@ -418,7 +418,7 @@ class BibleMediaItem(MediaManagerItem): cf = int(self.AdvancedFromChapter.currentText()) self.adjustComboBox(cf, self.chapters_from, self.AdvancedToChapter) # get the verse count for new chapter - vse = self.parent.biblemanager.get_book_verse_count(bible, book, cf) + vse = self.parent.manager.get_book_verse_count(bible, book, cf) self.adjustComboBox(1, vse, self.AdvancedFromVerse) self.adjustComboBox(1, vse, self.AdvancedToVerse) @@ -430,10 +430,10 @@ class BibleMediaItem(MediaManagerItem): self.ListView.clear() #if self.QuickSearchComboBox.currentIndex() == 1: # self.search_results = \ - # self.parent.biblemanager.get_verses(bible, text) + # self.parent.manager.get_verses(bible, text) #else: # self.searchByReference(bible, text) - self.search_results = self.parent.biblemanager.get_verses(bible, text) + self.search_results = self.parent.manager.get_verses(bible, text) if self.search_results: self.displayResults(bible) @@ -524,12 +524,12 @@ class BibleMediaItem(MediaManagerItem): def reloadBibles(self): log.debug(u'Reloading Bibles') - self.parent.biblemanager.reload_bibles() + self.parent.manager.reload_bibles() self.loadBibles() def initialiseBible(self, bible): log.debug(u'initialiseBible %s', bible) - book_data = self.parent.biblemanager.get_bible_books(bible) + book_data = self.parent.manager.get_bible_books(bible) self.AdvancedBookComboBox.clear() first = True for book in book_data: @@ -545,7 +545,7 @@ class BibleMediaItem(MediaManagerItem): def initialiseChapterVerse(self, bible, book, chapters): log.debug(u'initialiseChapterVerse %s, %s', bible, book) self.chapters_from = chapters - self.verses = self.parent.biblemanager.get_book_verse_count(bible, + self.verses = self.parent.manager.get_book_verse_count(bible, book, 1) if self.verses == 0: self.AdvancedSearchButton.setEnabled(False) @@ -578,12 +578,12 @@ class BibleMediaItem(MediaManagerItem): def searchByReference(self, bible, search): log.debug(u'searchByReference %s, %s', bible, search) - self.search_results = self.parent.biblemanager.get_verses(bible, search) - self.copyright = unicode(self.parent.biblemanager.get_meta_data( + self.search_results = self.parent.manager.get_verses(bible, search) + self.copyright = unicode(self.parent.manager.get_meta_data( bible, u'Copyright').value) - self.permissions = unicode(self.parent.biblemanager.get_meta_data( + self.permissions = unicode(self.parent.manager.get_meta_data( bible, u'Permissions').value) - self.version = unicode(self.parent.biblemanager.get_meta_data( + self.version = unicode(self.parent.manager.get_meta_data( bible, u'Version').value) # def searchByReference(self, bible, search): @@ -658,15 +658,15 @@ class BibleMediaItem(MediaManagerItem): # unicode(start_verse), unicode(end_verse))) # if message is None: # self.search_results = None -# self.search_results = self.parent.biblemanager.get_verse_text( +# self.search_results = self.parent.manager.get_verse_text( # bible, book, int(start_chapter), int(end_chapter), # int(start_verse), int(end_verse)) -# self.copyright = unicode(self.parent.biblemanager.get_meta_data( +# self.copyright = unicode(self.parent.manager.get_meta_data( # bible, u'Copyright').value) -# self.permissions = unicode(self.parent.biblemanager.get_meta_data( +# self.permissions = unicode(self.parent.manager.get_meta_data( # bible, u'Permissions').value) -# self.version = unicode(self.parent.biblemanager.get_meta_data( +# self.version = unicode(self.parent.manager.get_meta_data( # bible, u'Version').value) # else: # QtGui.QMessageBox.information( -# self, self.trUtf8('Information'), message) +# self, self.trUtf8('Information'), message) \ No newline at end of file From 079c9a4679e556cf398da0276da482ea6f955211 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Thu, 4 Feb 2010 19:25:32 +0000 Subject: [PATCH 046/164] Key handling changes --- openlp/core/ui/maindisplay.py | 8 ++++++++ openlp/core/ui/servicemanager.py | 2 ++ openlp/core/ui/slidecontroller.py | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index cda5774d6..65a8843a8 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -44,6 +44,11 @@ class DisplayWidget(QtGui.QWidget): def __init__(self, parent=None, name=None): QtGui.QWidget.__init__(self, parent) self.parent = parent + self.hotkey_map = {QtCore.Qt.Key_Return: 'servicemanager_next_item', + QtCore.Qt.Key_Space: 'live_slidecontroller_next_noloop', + QtCore.Qt.Key_Enter: 'live_slidecontroller_next_noloop', + QtCore.Qt.Key_0: 'servicemanager_next_item', + QtCore.Qt.Key_Backspace: 'live_slidecontroller_previous_noloop'} def keyPressEvent(self, event): if type(event) == QtGui.QKeyEvent: @@ -60,6 +65,9 @@ class DisplayWidget(QtGui.QWidget): elif event.key() == QtCore.Qt.Key_PageDown: Receiver.send_message(u'live_slidecontroller_last') event.accept() + elif event.key() in self.hotkey_map: + Receiver.send_message(self.hotkey_map[event.key()]); + event.accept() elif event.key() == QtCore.Qt.Key_Escape: self.resetDisplay() event.accept() diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 6a2589a43..b96e47f97 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -243,6 +243,8 @@ class ServiceManager(QtGui.QWidget): Called by the SlideController to select the next service item """ + if len(self.ServiceManagerList.selectedItems()) == 0: + return selected = self.ServiceManagerList.selectedItems()[0] lookFor = 0 serviceIterator = QtGui.QTreeWidgetItemIterator(self.ServiceManagerList) diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 71445b074..8a4aebd8c 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -62,7 +62,7 @@ class SlideList(QtGui.QTableWidget): elif event.key() == QtCore.Qt.Key_PageDown: self.parent.onSlideSelectedLast() event.accept() - elif event.key() in self.hotkey_map: + elif event.key() in self.hotkey_map and self.parent.isLive: Receiver.send_message(self.hotkey_map[event.key()]); event.accept() event.ignore() From ce6738a976e7e36f72b0eeabd7b5f423f93a1a4f Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Thu, 4 Feb 2010 21:34:12 +0200 Subject: [PATCH 047/164] Fixed up searching for non-latin characters in Bible book names. Removed an annoying exception to do with debug logging. --- openlp/plugins/bibles/lib/common.py | 13 ++++++++++--- openlp/plugins/bibles/lib/db.py | 10 +++++----- openlp/plugins/bibles/lib/manager.py | 10 +++++----- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/openlp/plugins/bibles/lib/common.py b/openlp/plugins/bibles/lib/common.py index 3190b5d8d..962a1271f 100644 --- a/openlp/plugins/bibles/lib/common.py +++ b/openlp/plugins/bibles/lib/common.py @@ -28,13 +28,15 @@ import chardet import logging import re -only_verses = re.compile(r'([a-zA-Z0-9 ]+)[ ]+([0-9]+)[ ]*[:|v|V][ ]*([0-9]+)' +only_verses = re.compile(r'([\w .]+)[ ]+([0-9]+)[ ]*[:|v|V][ ]*([0-9]+)' r'(?:[ ]*-[ ]*([0-9]+|end))?(?:[ ]*,[ ]*([0-9]+)(?:[ ]*-[ ]*([0-9]+|end))?)?', re.UNICODE) -chapter_range = re.compile(r'([a-zA-Z0-9 ]+)[ ]+([0-9]+)[ ]*[:|v|V][ ]*' +chapter_range = re.compile(r'([\w .]+)[ ]+([0-9]+)[ ]*[:|v|V][ ]*' r'([0-9]+)[ ]*-[ ]*([0-9]+)[ ]*[:|v|V][ ]*([0-9]+)', re.UNICODE) +log = logging.getLogger(__name__) + def parse_reference(reference): """ This is the über-awesome function that takes a person's typed in string @@ -47,11 +49,13 @@ def parse_reference(reference): (book, chapter, start_verse, end_verse) """ reference = reference.strip() + log.debug('parse_reference("%s")', reference) reference_list = [] # We start with the most "complicated" match first, so that they are found # first, and we don't have any "false positives". match = chapter_range.match(reference) if match: + log.debug('Found a chapter range.') reference_list.extend([ (match.group(1), match.group(2), match.group(3), -1), (match.group(1), match.group(4), 1, match.group(5)) @@ -59,6 +63,7 @@ def parse_reference(reference): else: match = only_verses.match(reference) if match: + log.debug('Found a verse range.') book = match.group(1) chapter = match.group(2) verse = match.group(3) @@ -82,6 +87,8 @@ def parse_reference(reference): (book, chapter, verse, match.group(4)), (book, chapter, match.group(5), end_verse) ]) + else: + log.debug('Didn\'t find anything.') return reference_list class SearchResults: @@ -216,4 +223,4 @@ class BibleCommon(object): text = text[:start_tag] + text[end_tag + 1:] start_tag = text.find(u'<') text = text.replace(u'>', u'') - return text.rstrip().lstrip() \ No newline at end of file + return text.rstrip().lstrip() diff --git a/openlp/plugins/bibles/lib/db.py b/openlp/plugins/bibles/lib/db.py index bd29001e2..413f2ca1f 100644 --- a/openlp/plugins/bibles/lib/db.py +++ b/openlp/plugins/bibles/lib/db.py @@ -157,7 +157,7 @@ class BibleDB(QtCore.QObject): return self.session.query(Book).order_by(Book.id).all() def get_book(self, book): - log.debug(u'BibleDb.get_book("%s")', __name__, book) + log.debug(u'BibleDb.get_book("%s")', book) db_book = self.session.query(Book)\ .filter(Book.name.like(book + u'%'))\ .first() @@ -200,7 +200,7 @@ class BibleDB(QtCore.QObject): end_verse = self.get_chapter_count(book) if db_book: book = db_book.name - log.debug(u'Book name corrected to "%s"' % book) + log.debug(u'Book name corrected to "%s"', book) verses = self.session.query(Verse)\ .filter_by(book_id=db_book.id)\ .filter_by(chapter=chapter)\ @@ -276,11 +276,11 @@ class BibleDB(QtCore.QObject): return False def dump_bible(self): - log.debug( u'.........Dumping Bible Database') - log.debug( '...............................Books ') + log.debug(u'.........Dumping Bible Database') + log.debug('...............................Books ') books = self.session.query(Book).all() log.debug(books) - log.debug( u'...............................Verses ') + log.debug(u'...............................Verses ') verses = self.session.query(Verse).all() log.debug(verses) diff --git a/openlp/plugins/bibles/lib/manager.py b/openlp/plugins/bibles/lib/manager.py index 7ac99530c..92e6cda9a 100644 --- a/openlp/plugins/bibles/lib/manager.py +++ b/openlp/plugins/bibles/lib/manager.py @@ -250,12 +250,12 @@ class BibleManager(object): Returns all the number of verses for a given book and chapterMaxBibleBookVerses """ - log.debug(u'get_verses_from_text %s,%s', bible, versetext) + log.debug(u'get_verses_from_text %s, %s', bible, versetext) reflist = parse_reference(versetext) - web_index = bible.find('(%s)' % self.web) - if web_index >= 0: - bible = bible[:web_index - 1] - log.debug('Updated bible name: %s', bible) + #web_index = bible.find('(%s)' % self.web) + #if web_index >= 0: + # bible = bible[:web_index - 1] + # log.debug('Updated bible name: %s', bible) #web, bible = self.is_bible_web(bible) #if web: # return self.http_cache[bible].get_verses(reflist) From 58894e2afa05229a8fa9cd44d5705e913876900e Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Fri, 5 Feb 2010 19:08:47 +0000 Subject: [PATCH 048/164] Update default config file and add version checking option --- openlp/core/utils/__init__.py | 4 +- resources/.config/openlp/openlp.conf | 80 +++++++++++++--------------- 2 files changed, 41 insertions(+), 43 deletions(-) diff --git a/openlp/core/utils/__init__.py b/openlp/core/utils/__init__.py index 962aa69bd..9504c771e 100644 --- a/openlp/core/utils/__init__.py +++ b/openlp/core/utils/__init__.py @@ -37,12 +37,14 @@ log = logging.getLogger(__name__) def check_latest_version(config, current_version): version_string = current_version + #set to prod in the distribution confif file. + environment = config.get_config(u'run environment', u'dev') last_test = config.get_config(u'last version test', datetime.now().date()) this_test = unicode(datetime.now().date()) config.set_config(u'last version test', this_test) if last_test != this_test: version_string = u'' - req = urllib2.Request(u'http://www.openlp.org/files/version.txt') + req = urllib2.Request(u'http://www.openlp.org/files/%s_version.txt' % environment) req.add_header(u'User-Agent', u'OpenLP/%s' % current_version) try: handle = urllib2.urlopen(req, None) diff --git a/resources/.config/openlp/openlp.conf b/resources/.config/openlp/openlp.conf index 876bea5cf..a1501ff73 100644 --- a/resources/.config/openlp/openlp.conf +++ b/resources/.config/openlp/openlp.conf @@ -1,77 +1,73 @@ -[audit] -first service = 2 -db type = sqlite -audit active = False -second service = 2 -audit_status = 0 -data path = audit - [bibles] display new chapter = False display brackets = 0 -verse layout style = 0 -bible theme = 0 -search as type = True -bibles_status = 0 +dual bibles = False +db type = sqlite +bible theme = +verse layout style = 1 +status = 1 data path = bibles [media] -use mode layout = False -media_status = 1 - -[image] -loop delay = 5 +status = 1 [alerts] font color = #ffffff +background color = #660000 font face = Sans Serif timeout = 5 -background color = #660000 -[user interface] -display previewpanel = True -display thememanager = True -display servicemanager = True -display mediamanager = True +[remotes] +remote port = 4316 [presentations] -data path = presentations +status = 1 impress = 0 +data path = presentations +powerpoint = 0 +powerpoint viewer = 0 [custom] +status = 1 +display footer = True data path = custom db type = sqlite -custom_status = 0 [themes] +global theme = data path = themes -theme global theme = -theme global style = Global +theme level = 1 + +[images] +status = 1 +data path = images +loop delay = 5 + +[user interface] +theme manager = True +media manager = True +preview panel = True +service manager = True [servicemanager] data path = servicemanager -theme service theme = - -[remotes] -remotes_status = 1 -remote port = 4316 - -[images] -images_status = 1 [general] monitor = 0 +run environment = dev +ccli number = +blank warning = False show splash = True -application version test = 2009-10-14 -user name = -application version = 1.9.0-600 -warning = False +last version test = 2010-02-05 +songselect username = +save prompt = False +songselect password = auto open = False -password = -ccl number = XXX [songs] -songs_status = 0 +status = 1 +search as type = False +display songbar = True data path = songs db type = sqlite From 00e84d40f3522b9d6dcb157be31fed38261c8a84 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sat, 6 Feb 2010 08:04:01 +0000 Subject: [PATCH 049/164] Cleanups and fix alerts for Presentations --- openlp/core/ui/__init__.py | 2 +- openlp/core/ui/alertform.py | 2 +- openlp/core/ui/maindisplay.py | 7 +- openlp/core/ui/mainwindow.py | 2 +- openlp/plugins/songs/forms/editversedialog.py | 122 ++++++++++-------- 5 files changed, 75 insertions(+), 60 deletions(-) diff --git a/openlp/core/ui/__init__.py b/openlp/core/ui/__init__.py index 42f232638..5d4c798d8 100644 --- a/openlp/core/ui/__init__.py +++ b/openlp/core/ui/__init__.py @@ -42,4 +42,4 @@ from mainwindow import MainWindow __all__ = ['SplashScreen', 'AboutForm', 'SettingsForm', 'MainWindow', 'MainDisplay', 'SlideController', 'ServiceManager', 'ThemeManager', - 'AmendThemeForm', 'MediaDockManager', 'ThemeLevel'] + 'AmendThemeForm', 'MediaDockManager'] diff --git a/openlp/core/ui/alertform.py b/openlp/core/ui/alertform.py index 4b099b954..beae62a53 100644 --- a/openlp/core/ui/alertform.py +++ b/openlp/core/ui/alertform.py @@ -99,4 +99,4 @@ class AlertForm(QtGui.QDialog): self.CancelButton.setText(self.trUtf8('Cancel')) def onDisplayClicked(self): - self.parent.mainDisplay.displayAlert(unicode(self.AlertEntryEditItem.text())) \ No newline at end of file + self.parent.mainDisplay.displayAlert(unicode(self.AlertEntryEditItem.text())) diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index 65a8843a8..bf0e11cb3 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -202,22 +202,21 @@ class MainDisplay(DisplayWidget): self.showFullScreen() def hideDisplay(self): + self.mediaLoaded = True self.setVisible(False) def showDisplay(self): + self.mediaLoaded = False if not self.primary: self.setVisible(True) self.showFullScreen() + self.generateAlert() def addImageWithText(self, frame): frame = resize_image(frame, self.screen[u'size'].width(), self.screen[u'size'].height() ) self.display_image.setPixmap(QtGui.QPixmap.fromImage(frame)) -# self.display_image.show() -# if not self.isVisible(): -# self.setVisible(True) -# self.showFullScreen() def frameView(self, frame, transition=False): """ diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index a17ee7d5a..36f427d13 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -56,7 +56,7 @@ class versionThread(QtCore.QThread): QtCore.QThread.__init__(self, parent) self.parent = parent def run (self): - time.sleep(5) + time.sleep(2) Receiver.send_message(u'version_check') diff --git a/openlp/plugins/songs/forms/editversedialog.py b/openlp/plugins/songs/forms/editversedialog.py index 8f532ac3f..89e704c87 100644 --- a/openlp/plugins/songs/forms/editversedialog.py +++ b/openlp/plugins/songs/forms/editversedialog.py @@ -1,113 +1,129 @@ # -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 -# Form implementation generated from reading ui file 'editversedialog.ui' -# -# Created: Wed Dec 2 08:14:47 2009 -# by: PyQt4 UI code generator 4.6.2 -# -# WARNING! All changes made in this file will be lost! +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard # +# --------------------------------------------------------------------------- # +# 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 # +############################################################################### from PyQt4 import QtCore, QtGui class Ui_EditVerseDialog(object): def setupUi(self, EditVerseDialog): - EditVerseDialog.setObjectName("EditVerseDialog") + EditVerseDialog.setObjectName(u'EditVerseDialog') EditVerseDialog.resize(500, 521) EditVerseDialog.setModal(True) self.layoutWidget = QtGui.QWidget(EditVerseDialog) self.layoutWidget.setGeometry(QtCore.QRect(11, 1, 471, 491)) - self.layoutWidget.setObjectName("layoutWidget") + self.layoutWidget.setObjectName(u'layoutWidget') self.verticalLayout_3 = QtGui.QVBoxLayout(self.layoutWidget) - self.verticalLayout_3.setObjectName("verticalLayout_3") + self.verticalLayout_3.setObjectName(u'verticalLayout_3') self.horizontalLayout = QtGui.QHBoxLayout() - self.horizontalLayout.setObjectName("horizontalLayout") + self.horizontalLayout.setObjectName(u'horizontalLayout') self.verticalLayout = QtGui.QVBoxLayout() - self.verticalLayout.setObjectName("verticalLayout") + self.verticalLayout.setObjectName(u'verticalLayout') self.VerseTypeLabel = QtGui.QLabel(self.layoutWidget) self.VerseTypeLabel.setTextFormat(QtCore.Qt.PlainText) self.VerseTypeLabel.setAlignment(QtCore.Qt.AlignCenter) - self.VerseTypeLabel.setObjectName("VerseTypeLabel") + self.VerseTypeLabel.setObjectName(u'VerseTypeLabel') self.verticalLayout.addWidget(self.VerseTypeLabel) self.VerseListComboBox = QtGui.QComboBox(self.layoutWidget) - self.VerseListComboBox.setObjectName("VerseListComboBox") - self.VerseListComboBox.addItem("") - self.VerseListComboBox.addItem("") - self.VerseListComboBox.addItem("") - self.VerseListComboBox.addItem("") - self.VerseListComboBox.addItem("") - self.VerseListComboBox.addItem("") - self.VerseListComboBox.addItem("") + self.VerseListComboBox.setObjectName(u'VerseListComboBox') + self.VerseListComboBox.addItem(u'') + self.VerseListComboBox.addItem(u'') + self.VerseListComboBox.addItem(u'') + self.VerseListComboBox.addItem(u'') + self.VerseListComboBox.addItem(u'') + self.VerseListComboBox.addItem(u'') + self.VerseListComboBox.addItem(u'') self.verticalLayout.addWidget(self.VerseListComboBox) self.horizontalLayout.addLayout(self.verticalLayout) self.verticalLayout_2 = QtGui.QVBoxLayout() - self.verticalLayout_2.setObjectName("verticalLayout_2") + self.verticalLayout_2.setObjectName(u'verticalLayout_2') self.VerseNumberLabel = QtGui.QLabel(self.layoutWidget) self.VerseNumberLabel.setAlignment(QtCore.Qt.AlignCenter) - self.VerseNumberLabel.setObjectName("VerseNumberLabel") + self.VerseNumberLabel.setObjectName(u'VerseNumberLabel') self.verticalLayout_2.addWidget(self.VerseNumberLabel) self.SubVerseListComboBox = QtGui.QComboBox(self.layoutWidget) - self.SubVerseListComboBox.setObjectName("SubVerseListComboBox") + self.SubVerseListComboBox.setObjectName(u'SubVerseListComboBox') self.verticalLayout_2.addWidget(self.SubVerseListComboBox) self.horizontalLayout.addLayout(self.verticalLayout_2) self.verticalLayout_3.addLayout(self.horizontalLayout) self.VerseTextEdit = QtGui.QTextEdit(self.layoutWidget) self.VerseTextEdit.setAcceptRichText(False) - self.VerseTextEdit.setObjectName("VerseTextEdit") + self.VerseTextEdit.setObjectName(u'VerseTextEdit') self.verticalLayout_3.addWidget(self.VerseTextEdit) self.horizontalLayout_2 = QtGui.QHBoxLayout() - self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.horizontalLayout_2.setObjectName(u'horizontalLayout_2') self.addBridge = QtGui.QPushButton(self.layoutWidget) - self.addBridge.setObjectName("addBridge") + self.addBridge.setObjectName(u'addBridge') self.horizontalLayout_2.addWidget(self.addBridge) self.addVerse = QtGui.QPushButton(self.layoutWidget) - self.addVerse.setObjectName("addVerse") + self.addVerse.setObjectName(u'addVerse') self.horizontalLayout_2.addWidget(self.addVerse) self.addChorus = QtGui.QPushButton(self.layoutWidget) - self.addChorus.setObjectName("addChorus") + self.addChorus.setObjectName(u'addChorus') self.horizontalLayout_2.addWidget(self.addChorus) self.verticalLayout_3.addLayout(self.horizontalLayout_2) self.horizontalLayout_3 = QtGui.QHBoxLayout() - self.horizontalLayout_3.setObjectName("horizontalLayout_3") + self.horizontalLayout_3.setObjectName(u'horizontalLayout_3') self.addPreChorus = QtGui.QPushButton(self.layoutWidget) - self.addPreChorus.setObjectName("addPreChorus") + self.addPreChorus.setObjectName(u'addPreChorus') self.horizontalLayout_3.addWidget(self.addPreChorus) self.addIntro = QtGui.QPushButton(self.layoutWidget) - self.addIntro.setObjectName("addIntro") + self.addIntro.setObjectName(u'addIntro') self.horizontalLayout_3.addWidget(self.addIntro) self.addOther = QtGui.QPushButton(self.layoutWidget) - self.addOther.setObjectName("addOther") + self.addOther.setObjectName(u'addOther') self.horizontalLayout_3.addWidget(self.addOther) self.addEnding = QtGui.QPushButton(self.layoutWidget) - self.addEnding.setObjectName("addEnding") + self.addEnding.setObjectName(u'addEnding') self.horizontalLayout_3.addWidget(self.addEnding) self.verticalLayout_3.addLayout(self.horizontalLayout_3) self.ButtonBox = QtGui.QDialogButtonBox(self.layoutWidget) self.ButtonBox.setOrientation(QtCore.Qt.Horizontal) self.ButtonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Save) - self.ButtonBox.setObjectName("ButtonBox") + self.ButtonBox.setObjectName(u'ButtonBox') self.verticalLayout_3.addWidget(self.ButtonBox) self.retranslateUi(EditVerseDialog) - QtCore.QObject.connect(self.ButtonBox, QtCore.SIGNAL("accepted()"), EditVerseDialog.accept) - QtCore.QObject.connect(self.ButtonBox, QtCore.SIGNAL("rejected()"), EditVerseDialog.reject) + QtCore.QObject.connect(self.ButtonBox, QtCore.SIGNAL(u'accepted()'), EditVerseDialog.accept) + QtCore.QObject.connect(self.ButtonBox, QtCore.SIGNAL(u'rejected()'), EditVerseDialog.reject) QtCore.QMetaObject.connectSlotsByName(EditVerseDialog) def retranslateUi(self, EditVerseDialog): - EditVerseDialog.setWindowTitle(QtGui.QApplication.translate("EditVerseDialog", "Edit Verse", None, QtGui.QApplication.UnicodeUTF8)) - self.VerseTypeLabel.setText(QtGui.QApplication.translate("EditVerseDialog", "Verse Type", None, QtGui.QApplication.UnicodeUTF8)) - self.VerseListComboBox.setItemText(0, QtGui.QApplication.translate("EditVerseDialog", "Intro", None, QtGui.QApplication.UnicodeUTF8)) - self.VerseListComboBox.setItemText(1, QtGui.QApplication.translate("EditVerseDialog", "Verse", None, QtGui.QApplication.UnicodeUTF8)) - self.VerseListComboBox.setItemText(2, QtGui.QApplication.translate("EditVerseDialog", "Pre-Chorus", None, QtGui.QApplication.UnicodeUTF8)) - self.VerseListComboBox.setItemText(3, QtGui.QApplication.translate("EditVerseDialog", "Chorus", None, QtGui.QApplication.UnicodeUTF8)) - self.VerseListComboBox.setItemText(4, QtGui.QApplication.translate("EditVerseDialog", "Bridge", None, QtGui.QApplication.UnicodeUTF8)) - self.VerseListComboBox.setItemText(5, QtGui.QApplication.translate("EditVerseDialog", "Ending", None, QtGui.QApplication.UnicodeUTF8)) - self.VerseListComboBox.setItemText(6, QtGui.QApplication.translate("EditVerseDialog", "Other", None, QtGui.QApplication.UnicodeUTF8)) - self.VerseNumberLabel.setText(QtGui.QApplication.translate("EditVerseDialog", "Number", None, QtGui.QApplication.UnicodeUTF8)) - self.addBridge.setText(QtGui.QApplication.translate("EditVerseDialog", "Bridge", None, QtGui.QApplication.UnicodeUTF8)) - self.addVerse.setText(QtGui.QApplication.translate("EditVerseDialog", "Verse", None, QtGui.QApplication.UnicodeUTF8)) - self.addChorus.setText(QtGui.QApplication.translate("EditVerseDialog", "Chorus", None, QtGui.QApplication.UnicodeUTF8)) - self.addPreChorus.setText(QtGui.QApplication.translate("EditVerseDialog", "Pre-Chorus", None, QtGui.QApplication.UnicodeUTF8)) - self.addIntro.setText(QtGui.QApplication.translate("EditVerseDialog", "Intro", None, QtGui.QApplication.UnicodeUTF8)) - self.addOther.setText(QtGui.QApplication.translate("EditVerseDialog", "Other", None, QtGui.QApplication.UnicodeUTF8)) - self.addEnding.setText(QtGui.QApplication.translate("EditVerseDialog", "Ending", None, QtGui.QApplication.UnicodeUTF8)) + EditVerseDialog.setWindowTitle(self.trUtf8('Edit Verse')) + self.VerseTypeLabel.setText(self.trUtf8('Verse Type')) + self.VerseListComboBox.setItemText(0, self.trUtf8('Intro')) + self.VerseListComboBox.setItemText(1, self.trUtf8('Verse')) + self.VerseListComboBox.setItemText(2, self.trUtf8('Pre-Chorus')) + self.VerseListComboBox.setItemText(3, self.trUtf8('Chorus')) + self.VerseListComboBox.setItemText(4, self.trUtf8('Bridge')) + self.VerseListComboBox.setItemText(5, self.trUtf8('Ending')) + self.VerseListComboBox.setItemText(6, self.trUtf8('Other')) + self.VerseNumberLabel.setText(self.trUtf8('Number')) + self.addBridge.setText(self.trUtf8('Bridge')) + self.addVerse.setText(self.trUtf8('Verse')) + self.addChorus.setText(self.trUtf8('Chorus')) + self.addPreChorus.setText(self.trUtf8('Pre-Chorus')) + self.addIntro.setText(self.trUtf8('Intro')) + self.addOther.setText(self.trUtf8('Other')) + self.addEnding.setText(self.trUtf8('Ending')) From 78c525ab760dfe35755962ae7b68090c45be11b5 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Sat, 6 Feb 2010 12:22:20 +0200 Subject: [PATCH 050/164] Moved data for web bibles to am SQLite database. --- openlp.pyw | 2 +- openlp/plugins/bibles/lib/common.py | 36 +++- openlp/plugins/bibles/lib/db.py | 2 +- openlp/plugins/bibles/lib/http.py | 107 +++++++++-- openlp/plugins/bibles/lib/manager.py | 225 +++++------------------ openlp/plugins/bibles/lib/mediaitem.py | 237 ++++++++----------------- openlp/plugins/bibles/lib/models.py | 2 +- 7 files changed, 249 insertions(+), 362 deletions(-) diff --git a/openlp.pyw b/openlp.pyw index 58925fdb9..914dbce50 100755 --- a/openlp.pyw +++ b/openlp.pyw @@ -148,7 +148,7 @@ def main(): usage = u'Usage: %prog [options] [qt-options]' parser = OptionParser(usage=usage) parser.add_option("-l", "--log-level", dest="loglevel", - default="info", metavar="LEVEL", + default="warning", metavar="LEVEL", help="Set logging to LEVEL level. Valid values are " "\"debug\", \"info\", \"warning\".") parser.add_option("-p", "--portable", dest="portable", diff --git a/openlp/plugins/bibles/lib/common.py b/openlp/plugins/bibles/lib/common.py index 962a1271f..5152ca496 100644 --- a/openlp/plugins/bibles/lib/common.py +++ b/openlp/plugins/bibles/lib/common.py @@ -27,6 +27,7 @@ import urllib2 import chardet import logging import re +import sqlite3 only_verses = re.compile(r'([\w .]+)[ ]+([0-9]+)[ ]*[:|v|V][ ]*([0-9]+)' r'(?:[ ]*-[ ]*([0-9]+|end))?(?:[ ]*,[ ]*([0-9]+)(?:[ ]*-[ ]*([0-9]+|end))?)?', @@ -56,10 +57,33 @@ def parse_reference(reference): match = chapter_range.match(reference) if match: log.debug('Found a chapter range.') - reference_list.extend([ - (match.group(1), match.group(2), match.group(3), -1), - (match.group(1), match.group(4), 1, match.group(5)) - ]) + book = match.group(1) + from_verse = match.group(3) + to_verse = match.group(5) + if int(match.group(2)) == int(match.group(4)): + reference_list.append( + (match.group(1), int(match.group(2)), from_verse, to_verse) + ) + else: + if int(match.group(2)) > int(match.group(4)): + from_chapter = int(match.group(4)) + to_chapter = int(match.group(2)) + else: + from_chapter = int(match.group(2)) + to_chapter = int(match.group(4)) + for chapter in xrange(from_chapter, to_chapter + 1): + if chapter == from_chapter: + reference_list.append( + (match.group(1), chapter, from_verse, -1) + ) + elif chapter == to_chapter: + reference_list.append( + (match.group(1), chapter, 1, to_verse) + ) + else: + reference_list.append( + (match.group(1), chapter, 1, -1) + ) else: match = only_verses.match(reference) if match: @@ -89,9 +113,11 @@ def parse_reference(reference): ]) else: log.debug('Didn\'t find anything.') + log.debug(reference_list) return reference_list -class SearchResults: + +class SearchResults(object): """ Encapsulate a set of search results. This is Bible-type independant. """ diff --git a/openlp/plugins/bibles/lib/db.py b/openlp/plugins/bibles/lib/db.py index 413f2ca1f..deedf9d21 100644 --- a/openlp/plugins/bibles/lib/db.py +++ b/openlp/plugins/bibles/lib/db.py @@ -197,7 +197,7 @@ class BibleDB(QtCore.QObject): for book, chapter, start_verse, end_verse in reference_list: db_book = self.get_book(book) if end_verse == -1: - end_verse = self.get_chapter_count(book) + end_verse = self.get_verse_count(book, chapter) if db_book: book = db_book.name log.debug(u'Book name corrected to "%s"', book) diff --git a/openlp/plugins/bibles/lib/http.py b/openlp/plugins/bibles/lib/http.py index 607c94f0d..eb140b710 100644 --- a/openlp/plugins/bibles/lib/http.py +++ b/openlp/plugins/bibles/lib/http.py @@ -25,6 +25,8 @@ import logging import urllib2 +import os +import sqlite3 from BeautifulSoup import BeautifulSoup @@ -33,6 +35,89 @@ from common import BibleCommon, SearchResults from db import BibleDB from openlp.plugins.bibles.lib.models import Book +class HTTPBooks(object): + cursor = None + + @staticmethod + def get_cursor(): + if HTTPBooks.cursor is None: + filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), + u'..', u'resources', u'httpbooks.sqlite') + conn = sqlite3.connect(filepath) + HTTPBooks.cursor = conn.cursor() + return HTTPBooks.cursor + + @staticmethod + def run_sql(query, parameters=()): + cursor = HTTPBooks.get_cursor() + cursor.execute(query, parameters) + return cursor.fetchall() + + @staticmethod + def get_books(): + books = HTTPBooks.run_sql(u'SELECT id, testament_id, name, ' + u'abbreviation, chapters FROM books ORDER BY id') + book_list = [] + for book in books: + book_list.append({ + u'id': book[0], + u'testament_id': book[1], + u'name': unicode(book[2]), + u'abbreviation': unicode(book[3]), + u'chapters': book[4] + }) + return book_list + + @staticmethod + def get_book(name): + if not isinstance(name, unicode): + name = unicode(name) + books = HTTPBooks.run_sql(u'SELECT id, testament_id, name, ' + u'abbreviation, chapters FROM books WHERE name = ? OR ' + u'abbreviation = ?', (name, name)) + if len(books) > 0: + return { + u'id': books[0][0], + u'testament_id': books[0][1], + u'name': unicode(books[0][2]), + u'abbreviation': unicode(books[0][3]), + u'chapters': books[0][4] + } + else: + return None + + @staticmethod + def get_chapter(name, chapter): + if not isinstance(name, int): + chapter = int(chapter) + book = HTTPBooks.get_book(name) + chapters = HTTPBooks.run_sql(u'SELECT id, book_id, chapter, ' + u'verses FROM chapters WHERE book_id = ?', (book[u'id'],)) + if len(chapters) > 0: + return { + u'id': chapters[0][0], + u'book_id': chapters[0][1], + u'chapter': chapters[0][2], + u'verses': chapters[0][3] + } + else: + return None + + @staticmethod + def get_chapter_count(book): + details = HTTPBooks.get_book(book) + if details: + return details[u'chapters'] + return 0 + + @staticmethod + def get_verse_count(book, chapter): + details = HTTPBooks.get_chapter(book, chapter) + if details: + return details[u'verses'] + return 0 + + class BGExtract(BibleCommon): global log log = logging.getLogger(u'BibleHTTPMgr(BG_extract)') @@ -219,7 +304,7 @@ class HTTPBible(BibleDB): Receiver.send_message(u'bible_nobook') return [] db_book = self.create_book(book_details[u'name'], - book_details[u'abbr'], book_details[u'test']) + book_details[u'abbreviation'], book_details[u'testament_id']) book = db_book.name if self.get_verse_count(book, reference[1]) == 0: Receiver.send_message(u'bible_showprogress') @@ -256,21 +341,17 @@ class HTTPBible(BibleDB): return None def get_books(self): - return [Book.populate(name=book['name']) for book in self.books] + return [Book.populate(name=book['name']) for book in HTTPBooks.get_books()] + + def lookup_book(self, book): + return HTTPBooks.get_book(book) def get_chapter_count(self, book): - return self.lookup_book(book)[u'chap'] + return HTTPBooks.get_chapter_count(book) + + def get_verse_count(self, book, chapter): + return HTTPBooks.get_verse_count(book, chapter) def set_proxy_server(self, server): self.proxy_server = server - def set_books(self, books): - self.books = books - - def lookup_book(self, name): - log.debug('Looking up "%s" in %s', name, self.books) - for book in self.books: - if book[u'name'] == name or book[u'abbr'] == name: - return book - return None - diff --git a/openlp/plugins/bibles/lib/manager.py b/openlp/plugins/bibles/lib/manager.py index 92e6cda9a..db70ac6db 100644 --- a/openlp/plugins/bibles/lib/manager.py +++ b/openlp/plugins/bibles/lib/manager.py @@ -106,8 +106,6 @@ class BibleManager(object): self.parent = parent self.web = u'Web' self.db_cache = None - self.http_cache = None - self.http_books = {} self.path = self.config.get_data_path() self.proxy_name = self.config.get_config(u'proxy name') self.suffix = u'sqlite' @@ -115,54 +113,30 @@ class BibleManager(object): self.reload_bibles() self.media = None - def load_http_books(self): - filepath = os.path.split(os.path.abspath(__file__))[0] - filepath = os.path.abspath(os.path.join( - filepath, u'..', u'resources', u'httpbooks.csv')) - books_file = None - try: - self.http_books = [] - books_file = open(filepath, u'r') - dialect = csv.Sniffer().sniff(books_file.read(1024)) - books_file.seek(0) - books_reader = csv.reader(books_file, dialect) - for line in books_reader: - self.http_books.append({ - u'name': unicode(line[0]), - u'abbr': unicode(line[1]), - u'test': line[2], - u'chap': line[3] - }) - except: - log.exception(u'Failed to load http books.') - finally: - if books_file: - books_file.close() - def reload_bibles(self): + """ + Reloads the Bibles from the available Bible databases on disk. If a web + Bible is encountered, an instance of HTTPBible is loaded instead of the + BibleDB class. + """ log.debug(u'Reload bibles') files = self.config.get_files(self.suffix) log.debug(u'Bible Files %s', files) self.db_cache = {} - self.http_cache = {} - self.load_http_books() - self.web_bibles_present = False for filename in files: name, extension = os.path.splitext(filename) self.db_cache[name] = BibleDB(self.parent, path=self.path, name=name, config=self.config) # look to see if lazy load bible exists and get create getter. source = self.db_cache[name].get_meta(u'download source') if source: - self.web_bibles_present = True download_name = self.db_cache[name].get_meta(u'download name').value + meta_proxy = self.db_cache[name].get_meta(u'proxy url') web_bible = HTTPBible(self.parent, path=self.path, name=name, config=self.config, download_source=source.value, download_name=download_name) - meta_proxy = self.db_cache[name].get_meta(u'proxy url') if meta_proxy: web_bible.set_proxy_server(meta_proxy.value) - web_bible.set_books(self.http_books) - del self.db_cache[name] + #del self.db_cache[name] self.db_cache[name] = web_bible log.debug(u'Bibles reloaded') @@ -180,10 +154,10 @@ class BibleManager(object): Register a bible in the bible cache, and then import the verses. ``type`` - What type of Bible, one of the BibleFormat values. + What type of Bible, one of the ``BibleFormat`` values. ``**kwargs`` - Keyword arguments to send to the actualy importer class. + Keyword arguments to send to the actual importer class. """ class_ = BibleFormat.get_class(type) kwargs['path'] = self.path @@ -193,73 +167,61 @@ class BibleManager(object): self.db_cache[name] = importer return importer.do_import() - def get_bibles(self, mode=BibleMode.Full): + def get_bibles(self): """ - Returns a list of Books of the bible. When ``mode`` is set to - ``BibleMode.Full`` this method returns all the Bibles for the Advanced - Search, and when the mode is ``BibleMode.Partial`` this method returns - all the bibles for the Quick Search. + Returns a list of the names of available Bibles. """ log.debug(u'get_bibles') - bible_list = [] - for bible_name, bible_object in self.db_cache.iteritems(): - #if getattr(bible_object, 'download_source', None): - # bible_name = u'%s (%s)' % (bible_name, self.web) - bible_list.append(bible_name) - return bible_list + return [name for name, bible in self.db_cache.iteritems()] - def is_bible_web(self, bible): - pos_end = bible.find(u' (%s)' % self.web) - if pos_end != -1: - return True, bible[:pos_end] - return False, bible - - def get_bible_books(self, bible): + def get_books(self, bible): """ - Returns a list of the books of the bible - """ - log.debug(u'get_bible_books') - return [{'name': book.name, 'total': self.db_cache[bible].get_chapter_count(book.name)} for book in self.db_cache[bible].get_books()] + Returns a list of Bible books, and the number of chapters in that book. - def get_book_chapter_count(self, book): + ``bible`` + Unicode. The Bible to get the list of books from. + """ + log.debug(u'BibleManager.get_books("%s")', bible) + return [ + { + u'name': book.name, + u'chapters': self.db_cache[bible].get_chapter_count(book.name) + } + for book in self.db_cache[bible].get_books() + ] + + def get_chapter_count(self, bible, book): """ Returns the number of Chapters for a given book """ log.debug(u'get_book_chapter_count %s', book) - return self.book_chapters[book] + return self.db_cache[bible].get_chapter_count(book) - def get_book_verse_count(self, bible, book, chapter): + def get_verse_count(self, bible, book, chapter): """ Returns all the number of verses for a given book and chapterMaxBibleBookVerses """ - log.debug(u'get_book_verse_count %s,%s,%s', bible, book, chapter) - web, bible = self.is_bible_web(bible) - if web: - count = self.db_cache[bible].get_verse_count(book, chapter) - if count == 0: - # Make sure the first chapter has been downloaded - self.get_verse_text(bible, book, chapter, chapter, 1, 1) - count = self.db_cache[bible].get_verse_count(book, chapter) - return count - else: - return self.db_cache[bible].get_verse_count(book, chapter) + log.debug(u'BibleManager.get_verse_count("%s", "%s", %s)', bible, book, chapter) + return self.db_cache[bible].get_verse_count(book, chapter) def get_verses(self, bible, versetext): """ - Returns all the number of verses for a given - book and chapterMaxBibleBookVerses + Parses a scripture reference, fetches the verses from the Bible + specified, and returns a list of ``Verse`` objects. + + ``bible`` + Unicode. The Bible to use. + + ``versetext`` + Unicode. The scripture reference. Valid scripture references are: + + - Genesis 1:1 + - Genesis 1:1-10 + - Genesis 1:1-2:10 """ - log.debug(u'get_verses_from_text %s, %s', bible, versetext) + log.debug(u'BibleManager.get_verses("%s", "%s")', bible, versetext) reflist = parse_reference(versetext) - #web_index = bible.find('(%s)' % self.web) - #if web_index >= 0: - # bible = bible[:web_index - 1] - # log.debug('Updated bible name: %s', bible) - #web, bible = self.is_bible_web(bible) - #if web: - # return self.http_cache[bible].get_verses(reflist) - #else: return self.db_cache[bible].get_verses(reflist) def save_meta_data(self, bible, version, copyright, permissions): @@ -277,107 +239,8 @@ class BibleManager(object): Returns the meta data for a given key """ log.debug(u'get_meta %s,%s', bible, key) - web, bible = self.is_bible_web(bible) return self.db_cache[bible].get_meta(key) - def get_verse_text(self, bible, book, schapter, echapter, sverse, everse=0): - """ - Returns a list of verses for a given Book, Chapter and ranges of verses. - If the end verse(everse) is less then the start verse(sverse) - then only one verse is returned - - ``bible`` - The name of the bible to be used - - Rest can be guessed at ! - """ - text = [] - self.media.setQuickMessage(u'') - log.debug(u'get_verse_text %s,%s,%s,%s,%s,%s', - bible, book, schapter, echapter, sverse, everse) - # check to see if book/chapter exists fow HTTP bibles and load cache - # if necessary - web, bible = self.is_bible_web(bible) - web_bible = False - log.debug('Web Bibles: %s', self.http_cache) - if self.http_cache[bible]: - web_bible = True - db_book = self.db_cache[bible].get_book(book) - if db_book is None: - log.debug(u'get_verse_text: new book') - for chapter in range(schapter, echapter + 1): - self.media.setQuickMessage( - unicode(self.media.trUtf8('Downloading %s: %s')) % - (book, chapter)) - search_results = \ - self.http_cache[bible].get_chapter(bible, book, chapter) - if search_results and search_results.has_verselist(): - ## We have found a book of the bible lets check to see - ## if it was there. By reusing the returned book name - ## we get a correct book. For example it is possible - ## to request ac and get Acts back. - bookname = search_results.get_book() - # check to see if book/chapter exists - db_book = self.db_cache[bible].get_book(bookname) - if db_book is None: - ## Then create book, chapter and text - db_book = self.db_cache[bible].create_book( - bookname, self.book_abbreviations[bookname], - self.book_testaments[bookname]) - log.debug(u'New http book %s, %s, %s', - db_book, db_book.id, db_book.name) - self.db_cache[bible].create_chapter( - db_book.id, search_results.get_chapter(), - search_results.get_verselist()) - else: - ## Book exists check chapter and texts only. - v = self.db_cache[bible].get_chapter( - db_book.id, chapter) - if v is None: - self.media.setQuickMessage( - unicode(self.media.trUtf8('%Downloading %s: %s'))\ - % (book, chapter)) - self.db_cache[bible].create_chapter( - db_book.id, chapter, - search_results.get_verselist()) - else: - log.debug(u'get_verse_text : old book') - for chapter in range(schapter, echapter + 1): - v = self.db_cache[bible].get_chapter(db_book.id, chapter) - if v is None: - try: - self.media.setQuickMessage(\ - unicode(self.media.trUtf8('Downloading %s: %s')) - % (book, chapter)) - search_results = \ - self.http_cache[bible].get_chapter( - bible, bookn, chapter) - if search_results.has_verselist(): - self.db_cache[bible].create_chapter( - db_book.id, search_results.get_chapter(), - search_results.get_verselist()) - except: - log.exception(u'Problem getting scripture online') - #Now get verses from database - if schapter == echapter: - text = self.db_cache[bible].get_verses( - [(book, schapter, sverse, everse)]) - else: - verse_list = [] - for chapter in range(schapter, echapter + 1): - if chapter == schapter: - start = sverse - end = self.get_verse_count(bible, book, chapter) - elif chapter == echapter: - start = 1 - end = everse - else: - start = 1 - end = self.get_verse_count(bible, book, chapter) - verse_list.append((bible, chapter, start, end)) - text = self.db_cache[bible].get_verses(verse_list) - return text - def exists(self, name): """ Check cache to see if new bible diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index fb6b2eafa..45c417130 100644 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -60,6 +60,7 @@ class BibleMediaItem(MediaManagerItem): self.IconPath = u'songs/song' self.ListViewWithDnD_class = BibleListView self.servicePath = None + self.lastReference = u'' MediaManagerItem.__init__(self, parent, icon, title) # place to store the search results self.search_results = {} @@ -323,21 +324,16 @@ class BibleMediaItem(MediaManagerItem): self.AdvancedSecondBibleComboBox.clear() self.QuickSecondBibleComboBox.addItem(u'') self.AdvancedSecondBibleComboBox.addItem(u'') - bibles = self.parent.manager.get_bibles(BibleMode.Full) + bibles = self.parent.manager.get_bibles() # load bibles into the combo boxes + first = True for bible in bibles: self.QuickVersionComboBox.addItem(bible) self.QuickSecondBibleComboBox.addItem(bible) - # Without HTTP - #bibles = self.parent.manager.get_bibles(BibleMode.Partial) - first = True - # load bibles into the combo boxes - for bible in bibles: self.AdvancedVersionComboBox.addItem(bible) self.AdvancedSecondBibleComboBox.addItem(bible) if first: first = False - # use the first bible as the trigger self.initialiseBible(bible) def onListViewResize(self, width, height): @@ -374,9 +370,6 @@ class BibleMediaItem(MediaManagerItem): self.AdvancedBookComboBox.itemData(item).toInt()[0]) def onNewClick(self): - #self.bibleimportform = BibleImportForm( - # self.parent.config, self.parent.manager, self) - #self.bibleimportform.exec_() self.bibleimportform = ImportWizardForm(self, self.parent.config, self.parent.manager, self.parent) self.bibleimportform.exec_() @@ -387,14 +380,13 @@ class BibleMediaItem(MediaManagerItem): self.adjustComboBox(frm, self.verses, self.AdvancedToVerse) def onAdvancedToChapter(self): - text1 = unicode(self.AdvancedFromChapter.currentText()) - text2 = unicode(self.AdvancedToChapter.currentText()) - if text1 != text2: + frm = unicode(self.AdvancedFromChapter.currentText()) + to = unicode(self.AdvancedToChapter.currentText()) + if frm != to: bible = unicode(self.AdvancedVersionComboBox.currentText()) book = unicode(self.AdvancedBookComboBox.currentText()) # get the verse count for new chapter - verses = self.parent.manager.get_book_verse_count( - bible, book, int(text2)) + verses = self.parent.manager.get_verse_count(bible, book, int(to)) self.adjustComboBox(1, verses, self.AdvancedToVerse) def onAdvancedSearchButton(self): @@ -408,6 +400,7 @@ class BibleMediaItem(MediaManagerItem): versetext = u'%s %s:%s-%s:%s' % (book, chapter_from, verse_from, \ chapter_to, verse_to) self.search_results = self.parent.manager.get_verses(bible, versetext) + self.lastReference = versetext if self.ClearAdvancedSearchComboBox.currentIndex() == 0: self.ListView.clear() self.displayResults(bible) @@ -418,7 +411,7 @@ class BibleMediaItem(MediaManagerItem): cf = int(self.AdvancedFromChapter.currentText()) self.adjustComboBox(cf, self.chapters_from, self.AdvancedToChapter) # get the verse count for new chapter - vse = self.parent.manager.get_book_verse_count(bible, book, cf) + vse = self.parent.manager.get_verse_count(bible, book, cf) self.adjustComboBox(1, vse, self.AdvancedFromVerse) self.adjustComboBox(1, vse, self.AdvancedToVerse) @@ -428,12 +421,8 @@ class BibleMediaItem(MediaManagerItem): text = unicode(self.QuickSearchEdit.displayText()) if self.ClearQuickSearchComboBox.currentIndex() == 0: self.ListView.clear() - #if self.QuickSearchComboBox.currentIndex() == 1: - # self.search_results = \ - # self.parent.manager.get_verses(bible, text) - #else: - # self.searchByReference(bible, text) self.search_results = self.parent.manager.get_verses(bible, text) + self.lastReference = text if self.search_results: self.displayResults(bible) @@ -446,60 +435,62 @@ class BibleMediaItem(MediaManagerItem): raw_slides = [] raw_footer = [] bible_text = u'' + #If we want to use a 2nd translation / version + bible2 = u'' + if self.SearchTabWidget.currentIndex() == 0: + bible2 = unicode(self.QuickSecondBibleComboBox.currentText()) + else: + bible2 = unicode(self.AdvancedSecondBibleComboBox.currentText()) + if bible2: + self.searchByReference(bible2, self.lastReference) + bible2_verses = self.search_results + bible2_version = self.parent.manager.get_meta_data(bible2, u'Version') + bible2_copyright = self.parent.manager.get_meta_data(bible2, u'Copyright') + bible2_permission = self.parent.manager.get_meta_data(bible2, u'Permission') + # Let's loop through the main lot, and assemble our verses for item in items: bitem = self.ListView.item(item.row()) - text = unicode((bitem.data(QtCore.Qt.UserRole)).toString()) - search_verse = text[:text.find(u'(')] - bible = text[text.find(u'(') + 1:-1] - self.searchByReference(bible, search_verse) - book = self.search_results[0].book.name - chapter = unicode(self.search_results[0].chapter) - verse = unicode(self.search_results[0].verse) - text = self.search_results[0].text + reference = bitem.data(QtCore.Qt.UserRole).toPyObject() + bible = unicode(reference[QtCore.QString('bible')]) + book = unicode(reference[QtCore.QString('book')]) + chapter = unicode(reference[QtCore.QString('chapter')]) + verse = unicode(reference[QtCore.QString('verse')]) + text = unicode(reference[QtCore.QString('text')]) + version = unicode(reference[QtCore.QString('version')]) + copyright = unicode(reference[QtCore.QString('copyright')]) + permission = unicode(reference[QtCore.QString('permission')]) if self.parent.settings_tab.display_style == 1: - loc = self.formatVerse(old_chapter, chapter, verse, u'(u', u')') + verse_text = self.formatVerse(old_chapter, chapter, verse, u'(u', u')') elif self.parent.settings_tab.display_style == 2: - loc = self.formatVerse(old_chapter, chapter, verse, u'{', u'}') + verse_text = self.formatVerse(old_chapter, chapter, verse, u'{', u'}') elif self.parent.settings_tab.display_style == 3: - loc = self.formatVerse(old_chapter, chapter, verse, u'[', u']') + verse_text = self.formatVerse(old_chapter, chapter, verse, u'[', u']') else: - loc = self.formatVerse(old_chapter, chapter, verse, u'', u'') + verse_text = self.formatVerse(old_chapter, chapter, verse, u'', u'') old_chapter = chapter - footer = u'%s (%s %s)' % (book, self.version, self.copyright) + footer = u'%s (%s %s)' % (book, version, copyright) #If not found throws and error so add.s - try: - raw_footer.index(footer) - except: + if footer not in raw_footer: raw_footer.append(footer) - #If we want to use a 2nd translation / version - bible2 = u'' - if self.SearchTabWidget.currentIndex() == 0: - bible2 = unicode(self.QuickSecondBibleComboBox.currentText()) - else: - bible2 = unicode(self.AdvancedSecondBibleComboBox.currentText()) - if len(bible2) > 0: - self.searchByReference(bible2, search_verse) - footer = u'%s (%s %s)' % (book, self.version, self.copyright) + if bible2: + footer = u'%s (%s %s)' % (book, version, copyright) #If not found throws and error so add.s - try: - raw_footer.index(footer) - except: + if footer not in raw_footer: raw_footer.append(footer) - bible_text = u'%s %s \n\n\n %s %s)' % \ - (loc, text, loc, self.search_results[0].text) + bible_text = u'%s %s \n\n %s %s)' % \ + (verse_text, text, verse_text, bible2_verses[item.row()].text) raw_slides.append(bible_text) bible_text = u'' else: #Paragraph style force new line per verse if self.parent.settings_tab.layout_style == 1: text = text + u'\n\n' - bible_text = u'%s %s %s' % (bible_text, loc, text) + bible_text = u'%s %s %s' % (bible_text, verse_text, text) #if we are verse per slide then create slide if self.parent.settings_tab.layout_style == 0: raw_slides.append(bible_text) bible_text = u'' - service_item.title = u'%s %s' % (book, loc) - + service_item.title = u'%s %s' % (book, verse_text) if len(self.parent.settings_tab.bible_theme) == 0: service_item.theme = None else: @@ -513,14 +504,14 @@ class BibleMediaItem(MediaManagerItem): return True def formatVerse(self, old_chapter, chapter, verse, opening, closing): - loc = opening + verse_text = opening if old_chapter != chapter: - loc += chapter + u':' + verse_text += chapter + u':' elif not self.parent.settings_tab.show_new_chapters: - loc += chapter + u':' - loc += verse - loc += closing - return loc + verse_text += chapter + u':' + verse_text += verse + verse_text += closing + return verse_text def reloadBibles(self): log.debug(u'Reloading Bibles') @@ -529,24 +520,23 @@ class BibleMediaItem(MediaManagerItem): def initialiseBible(self, bible): log.debug(u'initialiseBible %s', bible) - book_data = self.parent.manager.get_bible_books(bible) + book_data = self.parent.manager.get_books(bible) self.AdvancedBookComboBox.clear() first = True for book in book_data: row = self.AdvancedBookComboBox.count() self.AdvancedBookComboBox.addItem(book[u'name']) self.AdvancedBookComboBox.setItemData( - row, QtCore.QVariant(book[u'total'])) + row, QtCore.QVariant(book[u'chapters'])) if first: first = False self.initialiseChapterVerse( - bible, book[u'name'], book[u'total']) + bible, book[u'name'], book[u'chapters']) def initialiseChapterVerse(self, bible, book, chapters): log.debug(u'initialiseChapterVerse %s, %s', bible, book) self.chapters_from = chapters - self.verses = self.parent.manager.get_book_verse_count(bible, - book, 1) + self.verses = self.parent.manager.get_verse_count(bible, book, 1) if self.verses == 0: self.AdvancedSearchButton.setEnabled(False) self.AdvancedMessage.setText(self.trUtf8('Bible not fully loaded')) @@ -565,12 +555,30 @@ class BibleMediaItem(MediaManagerItem): combo.addItem(unicode(i)) def displayResults(self, bible): + version = self.parent.manager.get_meta_data(bible, u'Version') + copyright = self.parent.manager.get_meta_data(bible, u'Copyright') + permission = self.parent.manager.get_meta_data(bible, u'Permission') + if not permission: + permission = u'' + else: + permission = permission.value for count, verse in enumerate(self.search_results): - bible_text = u' %s %d:%d (%s)' % (verse.book.name, - verse.chapter, verse.verse, bible) + bible_text = u' %s %d:%d (%s)' % \ + (verse.book.name, verse.chapter, verse.verse, bible) bible_verse = QtGui.QListWidgetItem(bible_text) - bible_verse.setData(QtCore.Qt.UserRole, - QtCore.QVariant(bible_text)) + #bible_verse.setData(QtCore.Qt.UserRole, + # QtCore.QVariant(bible_text)) + vdict = { + 'bible': QtCore.QVariant(bible), + 'version': QtCore.QVariant(version.value), + 'copyright': QtCore.QVariant(copyright.value), + 'permission': QtCore.QVariant(permission), + 'book': QtCore.QVariant(verse.book.name), + 'chapter': QtCore.QVariant(verse.chapter), + 'verse': QtCore.QVariant(verse.verse), + 'text': QtCore.QVariant(verse.text) + } + bible_verse.setData(QtCore.Qt.UserRole, QtCore.QVariant(vdict)) self.ListView.addItem(bible_verse) row = self.ListView.setCurrentRow(count) if row: @@ -579,94 +587,3 @@ class BibleMediaItem(MediaManagerItem): def searchByReference(self, bible, search): log.debug(u'searchByReference %s, %s', bible, search) self.search_results = self.parent.manager.get_verses(bible, search) - self.copyright = unicode(self.parent.manager.get_meta_data( - bible, u'Copyright').value) - self.permissions = unicode(self.parent.manager.get_meta_data( - bible, u'Permissions').value) - self.version = unicode(self.parent.manager.get_meta_data( - bible, u'Version').value) - -# def searchByReference(self, bible, search): -# log.debug(u'searchByReference %s, %s', bible, search) -# book = u'' -# start_chapter = u'' -# end_chapter = u'' -# start_verse = u'' -# end_verse = u'' -# search = search.replace(u' ', u' ').strip() -# #original = search -# message = None -# # Remove book beware 0 index arrays -# for i in range (len(search)-1, 0, - 1): -# if search[i] == u' ': -# book = search[:i] -# # remove book from string -# search = search[i:] -# break -# # allow V or v for verse instead of : -# search = search.replace(u'v', ':') -# search = search.replace(u'V', ':') -# search = search.strip() -# colon = search.find(u':') -# if colon == -1: -# # number : found -# i = search.rfind(u' ') -# if i == -1: -# chapter = u'' -# else: -# chapter = search[i:len(search)] -# hyphen = chapter.find(u'-') -# if hyphen != -1: -# start_chapter= chapter[:hyphen] -# end_chapter= chapter[hyphen + 1:len(chapter)] -# else: -# start_chapter = chapter -# else: -# # more complex -# sp = search.split(u'-') #find first -# sp1 = sp[0].split(u':') -# if len(sp1) == 1: -# start_chapter = sp1[0] -# start_verse = 1 -# else: -# start_chapter = sp1[0] -# start_verse = sp1[1] -# if len(sp)== 1: -# end_chapter = start_chapter -# end_verse = start_verse -# else: -# sp1 = sp[1].split(u':') -# if len(sp1) == 1: -# end_chapter = start_chapter -# end_verse = sp1[0] -# else: -# end_chapter = sp1[0] -# end_verse = sp1[1] -# if end_chapter == u'': -# end_chapter = start_chapter.rstrip() -# if start_verse == u'': -# if end_verse == u'': -# start_verse = 1 -# else: -# start_verse = end_verse -# if end_verse == u'': -# end_verse = 99 -# if start_chapter == u'': -# message = self.trUtf8('No chapter found for search criteria') -# log.debug(u'results = %s @ %s : %s @ %s : %s'% \ -# (unicode(book), unicode(start_chapter), unicode(end_chapter), -# unicode(start_verse), unicode(end_verse))) -# if message is None: -# self.search_results = None -# self.search_results = self.parent.manager.get_verse_text( -# bible, book, int(start_chapter), int(end_chapter), -# int(start_verse), int(end_verse)) -# self.copyright = unicode(self.parent.manager.get_meta_data( -# bible, u'Copyright').value) -# self.permissions = unicode(self.parent.manager.get_meta_data( -# bible, u'Permissions').value) -# self.version = unicode(self.parent.manager.get_meta_data( -# bible, u'Version').value) -# else: -# QtGui.QMessageBox.information( -# self, self.trUtf8('Information'), message) \ No newline at end of file diff --git a/openlp/plugins/bibles/lib/models.py b/openlp/plugins/bibles/lib/models.py index d9c43d1df..2802cb27f 100644 --- a/openlp/plugins/bibles/lib/models.py +++ b/openlp/plugins/bibles/lib/models.py @@ -105,4 +105,4 @@ mapper(Testament, testament_table, properties={'books': relation(Book, backref='testament')}) mapper(Book, book_table, properties={'verses': relation(Verse, backref='book')}) -mapper(Verse, verse_table) \ No newline at end of file +mapper(Verse, verse_table) From a5dc0bd75d7e6fd75880e935cf5717ffcdd7b96f Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sat, 6 Feb 2010 10:33:33 +0000 Subject: [PATCH 051/164] Set plugins up for alpha release --- openlp/plugins/bibles/bibleplugin.py | 6 +++--- openlp/plugins/custom/customplugin.py | 5 +++-- openlp/plugins/images/imageplugin.py | 5 +++-- openlp/plugins/media/mediaplugin.py | 5 +++-- openlp/plugins/presentations/presentationplugin.py | 5 +++-- openlp/plugins/remotes/remoteplugin.py | 2 +- openlp/plugins/songs/songsplugin.py | 7 ++++--- openlp/plugins/songusage/songusageplugin.py | 2 +- 8 files changed, 21 insertions(+), 16 deletions(-) diff --git a/openlp/plugins/bibles/bibleplugin.py b/openlp/plugins/bibles/bibleplugin.py index a43af02de..cac575987 100644 --- a/openlp/plugins/bibles/bibleplugin.py +++ b/openlp/plugins/bibles/bibleplugin.py @@ -27,7 +27,7 @@ import logging from PyQt4 import QtCore, QtGui -from openlp.core.lib import Plugin, build_icon +from openlp.core.lib import Plugin, build_icon, PluginStatus from openlp.plugins.bibles.lib import BibleManager, BiblesTab, BibleMediaItem class BiblePlugin(Plugin): @@ -36,11 +36,12 @@ class BiblePlugin(Plugin): log.info(u'Bible Plugin loaded') def __init__(self, plugin_helpers): - Plugin.__init__(self, u'Bibles', u'1.9.0', plugin_helpers) + Plugin.__init__(self, u'Bibles', u'1.9.1', plugin_helpers) self.weight = -9 self.icon = build_icon(u':/media/media_bible.png') #Register the bible Manager self.biblemanager = None + self.status = PluginStatus.Active def initialise(self): log.info(u'bibles Initialising') @@ -50,7 +51,6 @@ class BiblePlugin(Plugin): self.insert_toolbox_item() self.ImportBibleItem.setVisible(True) self.ExportBibleItem.setVisible(True) - log.warn(u'Bibles Initialised') def finalise(self): log.info(u'Plugin Finalise') diff --git a/openlp/plugins/custom/customplugin.py b/openlp/plugins/custom/customplugin.py index 91c89212a..fb30bed9f 100644 --- a/openlp/plugins/custom/customplugin.py +++ b/openlp/plugins/custom/customplugin.py @@ -26,7 +26,7 @@ import logging from forms import EditCustomForm -from openlp.core.lib import Plugin, build_icon +from openlp.core.lib import Plugin, build_icon, PluginStatus from openlp.plugins.custom.lib import CustomManager, CustomMediaItem, CustomTab @@ -45,11 +45,12 @@ class CustomPlugin(Plugin): log.info(u'Custom Plugin loaded') def __init__(self, plugin_helpers): - Plugin.__init__(self, u'Custom', u'1.9.0', plugin_helpers) + Plugin.__init__(self, u'Custom', u'1.9.1', plugin_helpers) self.weight = -5 self.custommanager = CustomManager(self.config) self.edit_custom_form = EditCustomForm(self.custommanager) self.icon = build_icon(u':/media/media_custom.png') + self.status = PluginStatus.Active def get_settings_tab(self): return CustomTab(self.name) diff --git a/openlp/plugins/images/imageplugin.py b/openlp/plugins/images/imageplugin.py index 5d4c9252e..e6895c767 100644 --- a/openlp/plugins/images/imageplugin.py +++ b/openlp/plugins/images/imageplugin.py @@ -25,7 +25,7 @@ import logging -from openlp.core.lib import Plugin, build_icon +from openlp.core.lib import Plugin, build_icon, PluginStatus from openlp.plugins.images.lib import ImageMediaItem, ImageTab class ImagePlugin(Plugin): @@ -34,9 +34,10 @@ class ImagePlugin(Plugin): log.info(u'Image Plugin loaded') def __init__(self, plugin_helpers): - Plugin.__init__(self, u'Images', u'1.9.0', plugin_helpers) + Plugin.__init__(self, u'Images', u'1.9.1', plugin_helpers) self.weight = -7 self.icon = build_icon(u':/media/media_image.png') + self.status = PluginStatus.Active def initialise(self): log.info(u'Plugin Initialising') diff --git a/openlp/plugins/media/mediaplugin.py b/openlp/plugins/media/mediaplugin.py index f1b7fa653..640de1cb3 100644 --- a/openlp/plugins/media/mediaplugin.py +++ b/openlp/plugins/media/mediaplugin.py @@ -25,7 +25,7 @@ import logging -from openlp.core.lib import Plugin, build_icon +from openlp.core.lib import Plugin, build_icon, PluginStatus from openlp.plugins.media.lib import MediaMediaItem class MediaPlugin(Plugin): @@ -34,11 +34,12 @@ class MediaPlugin(Plugin): log.info(u'Media Plugin loaded') def __init__(self, plugin_helpers): - Plugin.__init__(self, u'Media', u'1.9.0', plugin_helpers) + Plugin.__init__(self, u'Media', u'1.9.1', plugin_helpers) self.weight = -6 self.icon = build_icon(u':/media/media_video.png') # passed with drag and drop messages self.dnd_id = u'Media' + self.status = PluginStatus.Active def initialise(self): log.info(u'Plugin Initialising') diff --git a/openlp/plugins/presentations/presentationplugin.py b/openlp/plugins/presentations/presentationplugin.py index 502557508..7103a3a2c 100644 --- a/openlp/plugins/presentations/presentationplugin.py +++ b/openlp/plugins/presentations/presentationplugin.py @@ -26,7 +26,7 @@ import os import logging -from openlp.core.lib import Plugin, build_icon, Receiver +from openlp.core.lib import Plugin, build_icon, Receiver, PluginStatus from openlp.plugins.presentations.lib import * class PresentationPlugin(Plugin): @@ -37,9 +37,10 @@ class PresentationPlugin(Plugin): def __init__(self, plugin_helpers): log.debug(u'Initialised') self.controllers = {} - Plugin.__init__(self, u'Presentations', u'1.9.0', plugin_helpers) + Plugin.__init__(self, u'Presentations', u'1.9.1', plugin_helpers) self.weight = -8 self.icon = build_icon(u':/media/media_presentation.png') + self.status = PluginStatus.Active def get_settings_tab(self): """ diff --git a/openlp/plugins/remotes/remoteplugin.py b/openlp/plugins/remotes/remoteplugin.py index 23c8b9bc7..bd8d8974b 100644 --- a/openlp/plugins/remotes/remoteplugin.py +++ b/openlp/plugins/remotes/remoteplugin.py @@ -37,7 +37,7 @@ class RemotesPlugin(Plugin): log.info(u'Remote Plugin loaded') def __init__(self, plugin_helpers): - Plugin.__init__(self, u'Remotes', u'1.9.0', plugin_helpers) + Plugin.__init__(self, u'Remotes', u'1.9.1', plugin_helpers) self.weight = -1 self.server = None diff --git a/openlp/plugins/songs/songsplugin.py b/openlp/plugins/songs/songsplugin.py index d22cf4fd4..088b9a1cf 100644 --- a/openlp/plugins/songs/songsplugin.py +++ b/openlp/plugins/songs/songsplugin.py @@ -27,7 +27,7 @@ import logging from PyQt4 import QtCore, QtGui -from openlp.core.lib import Plugin, build_icon +from openlp.core.lib import Plugin, build_icon, PluginStatus from openlp.plugins.songs.lib import SongManager, SongMediaItem, SongsTab from openlp.plugins.songs.forms import OpenLPImportForm, OpenSongExportForm, \ OpenSongImportForm, OpenLPExportForm @@ -49,7 +49,7 @@ class SongsPlugin(Plugin): """ Create and set up the Songs plugin. """ - Plugin.__init__(self, u'Songs', u'1.9.0', plugin_helpers) + Plugin.__init__(self, u'Songs', u'1.9.1', plugin_helpers) self.weight = -10 self.songmanager = SongManager(self.config) self.openlp_import_form = OpenLPImportForm() @@ -57,6 +57,7 @@ class SongsPlugin(Plugin): self.openlp_export_form = OpenLPExportForm() self.opensong_export_form = OpenSongExportForm() self.icon = build_icon(u':/media/media_song.png') + self.status = PluginStatus.Active def get_settings_tab(self): return SongsTab(self.name) @@ -178,4 +179,4 @@ class SongsPlugin(Plugin): def about(self): about_text = self.trUtf8('<b>Song Plugin</b> <br>This plugin allows ' 'Songs to be managed and displayed.<br>') - return about_text \ No newline at end of file + return about_text diff --git a/openlp/plugins/songusage/songusageplugin.py b/openlp/plugins/songusage/songusageplugin.py index 802f73d3d..d0ebe68ef 100644 --- a/openlp/plugins/songusage/songusageplugin.py +++ b/openlp/plugins/songusage/songusageplugin.py @@ -39,7 +39,7 @@ class SongUsagePlugin(Plugin): log.info(u'SongUsage Plugin loaded') def __init__(self, plugin_helpers): - Plugin.__init__(self, u'SongUsage', u'1.9.0', plugin_helpers) + Plugin.__init__(self, u'SongUsage', u'1.9.1', plugin_helpers) self.weight = -4 self.icon = build_icon(u':/media/media_image.png') self.songusagemanager = None From cf95b81a497f10e5f56f76ce911c8457a94a2c41 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Sat, 6 Feb 2010 12:42:47 +0200 Subject: [PATCH 052/164] Last few fixes --- openlp/plugins/bibles/lib/http.py | 10 +++++++--- .../plugins/bibles/resources/httpbooks.sqlite | Bin 0 -> 45056 bytes 2 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 openlp/plugins/bibles/resources/httpbooks.sqlite diff --git a/openlp/plugins/bibles/lib/http.py b/openlp/plugins/bibles/lib/http.py index eb140b710..1cf92e4d2 100644 --- a/openlp/plugins/bibles/lib/http.py +++ b/openlp/plugins/bibles/lib/http.py @@ -207,8 +207,12 @@ class CWExtract(BibleCommon): log.debug(u'get_bible_chapter %s,%s,%s', version, bookname, chapter) bookname = bookname.replace(u' ', u'') - page = urllib2.urlopen(u'http://www.biblestudytools.com/%s/%s/%s.html' % \ - (version, bookname.lower(), chapter)) + chapter_url = u'http://www.biblestudytools.com/%s/%s/%s.html' % \ + (version, bookname.lower(), chapter) + log.debug(u'URL: %s', chapter_url) + page = urllib2.urlopen(chapter_url) + if not page: + return None soup = BeautifulSoup(page) htmlverses = soup.findAll(u'span', u'versetext') verses = {} @@ -306,7 +310,7 @@ class HTTPBible(BibleDB): db_book = self.create_book(book_details[u'name'], book_details[u'abbreviation'], book_details[u'testament_id']) book = db_book.name - if self.get_verse_count(book, reference[1]) == 0: + if BibleDB.get_verse_count(self, book, reference[1]) == 0: Receiver.send_message(u'bible_showprogress') Receiver.send_message(u'process_events') search_results = self.get_chapter(self.name, book, reference[1]) diff --git a/openlp/plugins/bibles/resources/httpbooks.sqlite b/openlp/plugins/bibles/resources/httpbooks.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..ea0c405309e361c3366188aec8604b97db846861 GIT binary patch literal 45056 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 literal 0 HcmV?d00001 From 92ef1f0d57f87238d738401051403bbf82a7008b Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Sat, 6 Feb 2010 15:05:45 +0200 Subject: [PATCH 053/164] Fixed multi-verse dual bible. --- openlp/plugins/bibles/lib/mediaitem.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index 45c417130..bcd9adbb3 100644 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -60,7 +60,7 @@ class BibleMediaItem(MediaManagerItem): self.IconPath = u'songs/song' self.ListViewWithDnD_class = BibleListView self.servicePath = None - self.lastReference = u'' + self.lastReference = [] MediaManagerItem.__init__(self, parent, icon, title) # place to store the search results self.search_results = {} @@ -400,9 +400,10 @@ class BibleMediaItem(MediaManagerItem): versetext = u'%s %s:%s-%s:%s' % (book, chapter_from, verse_from, \ chapter_to, verse_to) self.search_results = self.parent.manager.get_verses(bible, versetext) - self.lastReference = versetext if self.ClearAdvancedSearchComboBox.currentIndex() == 0: self.ListView.clear() + self.lastReference = [] + self.lastReference.append(versetext) self.displayResults(bible) def onAdvancedFromChapter(self): @@ -421,8 +422,9 @@ class BibleMediaItem(MediaManagerItem): text = unicode(self.QuickSearchEdit.displayText()) if self.ClearQuickSearchComboBox.currentIndex() == 0: self.ListView.clear() + self.lastReference = [] + self.lastReference.append(versetext) self.search_results = self.parent.manager.get_verses(bible, text) - self.lastReference = text if self.search_results: self.displayResults(bible) @@ -442,8 +444,9 @@ class BibleMediaItem(MediaManagerItem): else: bible2 = unicode(self.AdvancedSecondBibleComboBox.currentText()) if bible2: - self.searchByReference(bible2, self.lastReference) - bible2_verses = self.search_results + bible2_verses = [] + for scripture in self.lastReference: + bible2_verses.extend(self.parent.manager.get_verses(bible2, scripture)) bible2_version = self.parent.manager.get_meta_data(bible2, u'Version') bible2_copyright = self.parent.manager.get_meta_data(bible2, u'Copyright') bible2_permission = self.parent.manager.get_meta_data(bible2, u'Permission') @@ -477,7 +480,7 @@ class BibleMediaItem(MediaManagerItem): #If not found throws and error so add.s if footer not in raw_footer: raw_footer.append(footer) - bible_text = u'%s %s \n\n %s %s)' % \ + bible_text = u'%s %s \n\n %s %s' % \ (verse_text, text, verse_text, bible2_verses[item.row()].text) raw_slides.append(bible_text) bible_text = u'' From f81968e62b2c615b88dada7417c6e9b1264f42cc Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Sat, 6 Feb 2010 15:22:26 +0200 Subject: [PATCH 054/164] Mis-named variable in QuickSearch click. --- openlp/plugins/bibles/lib/mediaitem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index bcd9adbb3..648694f45 100644 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -423,7 +423,7 @@ class BibleMediaItem(MediaManagerItem): if self.ClearQuickSearchComboBox.currentIndex() == 0: self.ListView.clear() self.lastReference = [] - self.lastReference.append(versetext) + self.lastReference.append(text) self.search_results = self.parent.manager.get_verses(bible, text) if self.search_results: self.displayResults(bible) From bd4d2daa74ed58b1e3f1a9e6b26089081e4934af Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Sat, 6 Feb 2010 15:23:38 +0200 Subject: [PATCH 055/164] Removed now unnecessary file. --- openlp/plugins/bibles/resources/httpbooks.csv | 66 ------------------- 1 file changed, 66 deletions(-) delete mode 100644 openlp/plugins/bibles/resources/httpbooks.csv diff --git a/openlp/plugins/bibles/resources/httpbooks.csv b/openlp/plugins/bibles/resources/httpbooks.csv deleted file mode 100644 index 2d8afa20e..000000000 --- a/openlp/plugins/bibles/resources/httpbooks.csv +++ /dev/null @@ -1,66 +0,0 @@ -Genesis,Gen,1,50 -Exodus,Exod,1,40 -Leviticus,Lev,1,27 -Numbers,Num,1,36 -Deuteronomy,Deut,1,34 -Joshua,Josh,1,24 -Judges,Judg,1,21 -Ruth,Ruth,1,4 -1 Samual,1Sam,1,31 -2 Samual,2Sam,1,24 -1 Kings,1Kgs,1,22 -2 Kings,2Kgs,1,25 -1 Chronicles,1Chr,1,29 -2 Chronicles,2Chr,1,36 -Ezra,Esra,1,10 -Nehemiah,Neh,1,13 -Esther,Esth,1,10 -Job,Job,1,42 -Psalms,Ps,1,150 -Proverbs,Prov,1,31 -Ecclesiastes,Eccl,1,12 -Song of Songs,Song,1,8 -Isaiah,Isa,1,66 -Jeremiah,Jer,1,5 -Lamentations,Lam,1,5 -Ezekiel,Ezek,1,48 -Daniel,Dan,1,12 -Hosea,Hos,1,14 -Joel,Joel,1,3 -Amos,Amos,1,9 -Obad,Obad,1,1 -Jonah,Jonah,1,4 -Micah,Mic,1,7 -Naham,Nah,1,3 -Habakkuk,Hab,1,3 -Zephaniah,Zeph,1,3 -Haggai,Hag,1,2 -Zechariah,Zech,1,3 -Malachi,Mal,1,4 -Matthew,Matt,2,28 -Mark,Mark,2,16 -Luke,Luke,2,24 -John,John,2,21 -Acts,Acts,2,28 -Romans,Rom,2,16 -1 Corinthans,1Cor,2,16 -2 Corinthans,2Cor,2,13 -Galatians,Gal,2,6 -Ephesians,Eph,2,6 -Philippians,Phil,2,4 -Colossians,Col,2,4 -1 Thessalonians,1Thess,2,5 -2 Thessalonians,2Thess,2,3 -1 Timothy,1Tim,2,6 -2 Timothy,2Tim,2,4 -Titus,Titus,2,3 -Philemon,Phlm,2,1 -Hebrews,Heb,2,13 -James,Jas,2,5 -1 Peter,1Pet,2,5 -2 Peter,2Pet,2,3 -1 John,1John,2,5 -2 John,2John,2,1 -3 John,3John,2,1 -Jude,Jude,2,1 -Revelation,Rev,2,22 From f5229ffaa4bc9aa2ae35c0f612dbb758c65a0cc3 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sat, 6 Feb 2010 15:33:23 +0000 Subject: [PATCH 056/164] Bible fixes --- openlp/plugins/bibles/lib/csvbible.py | 12 ++++++------ openlp/plugins/bibles/lib/db.py | 2 +- openlp/plugins/bibles/lib/opensong.py | 12 ++++++------ openlp/plugins/bibles/lib/osis.py | 16 ++++++++-------- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/openlp/plugins/bibles/lib/csvbible.py b/openlp/plugins/bibles/lib/csvbible.py index 94ebf3ce7..a1a16339c 100644 --- a/openlp/plugins/bibles/lib/csvbible.py +++ b/openlp/plugins/bibles/lib/csvbible.py @@ -51,15 +51,15 @@ class CSVBible(BibleDB): if u'versesfile' not in kwargs: raise KeyError(u'You have to supply a file to import verses from.') self.versesfile = kwargs[u'versesfile'] - #QtCore.QObject.connect(Receiver.get_receiver(), - # QtCore.SIGNAL(u'openlpstopimport'), self.stop_import) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'openlpstopimport'), self.stop_import) def stop_import(self): """ Stops the import of the Bible. """ log.debug('Stopping import!') - self.stop_import = True + self.stop_import_flag = True def do_import(self): #Populate the Tables @@ -72,7 +72,7 @@ class CSVBible(BibleDB): books_reader = csv.reader(books_file, dialect) for line in books_reader: # cancel pressed - if self.stop_import: + if self.stop_import_flag: break details = chardet.detect(line[1]) self.create_book(unicode(line[1], details['encoding']), @@ -94,7 +94,7 @@ class CSVBible(BibleDB): verse_file.seek(0) verse_reader = csv.reader(verse_file, dialect) for line in verse_reader: - if self.stop_import: # cancel pressed + if self.stop_import_flag: # cancel pressed break details = chardet.detect(line[3]) if book_ptr != line[0]: @@ -113,7 +113,7 @@ class CSVBible(BibleDB): finally: if verse_file: verse_file.close() - if self.stop_import: + if self.stop_import_flag: self.wizard.incrementProgressBar(u'Import canceled!') return False else: diff --git a/openlp/plugins/bibles/lib/db.py b/openlp/plugins/bibles/lib/db.py index deedf9d21..67c71ca10 100644 --- a/openlp/plugins/bibles/lib/db.py +++ b/openlp/plugins/bibles/lib/db.py @@ -67,7 +67,7 @@ class BibleDB(QtCore.QObject): raise KeyError(u'Missing keyword argument "name".') if u'config' not in kwargs: raise KeyError(u'Missing keyword argument "config".') - self.stop_import = False + self.stop_import_flag = False self.name = kwargs[u'name'] self.config = kwargs[u'config'] self.db_file = os.path.join(kwargs[u'path'], diff --git a/openlp/plugins/bibles/lib/opensong.py b/openlp/plugins/bibles/lib/opensong.py index f9f445973..830267f2a 100644 --- a/openlp/plugins/bibles/lib/opensong.py +++ b/openlp/plugins/bibles/lib/opensong.py @@ -52,15 +52,15 @@ class OpenSongBible(BibleDB): if 'filename' not in kwargs: raise KeyError(u'You have to supply a file name to import from.') self.filename = kwargs['filename'] - #QtCore.QObject.connect(Receiver.get_receiver(), - # QtCore.SIGNAL(u'openlpstopimport'), self.stop_import) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'openlpstopimport'), self.stop_import) def stop_import(self): """ Stops the import of the Bible. """ log.debug('Stopping import!') - self.stop_import = True + self.stop_import_flag = True def do_import(self): """ @@ -79,15 +79,15 @@ class OpenSongBible(BibleDB): opensong = objectify.parse(file) bible = opensong.getroot() for book in bible.b: - if self.stop_import: + if self.stop_import_flag: break db_book = self.create_book(unicode(book.attrib[u'n']), unicode(book.attrib[u'n'][:4])) for chapter in book.c: - if self.stop_import: + if self.stop_import_flag: break for verse in chapter.v: - if self.stop_import: + if self.stop_import_flag: break self.create_verse( db_book.id, diff --git a/openlp/plugins/bibles/lib/osis.py b/openlp/plugins/bibles/lib/osis.py index dbcb1d57f..f7b25d47b 100644 --- a/openlp/plugins/bibles/lib/osis.py +++ b/openlp/plugins/bibles/lib/osis.py @@ -81,15 +81,15 @@ class OSISBible(BibleDB): finally: if fbibles: fbibles.close() - #QtCore.QObject.connect(Receiver.get_receiver(), - # QtCore.SIGNAL(u'openlpstopimport'), self.stop_import) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'openlpstopimport'), self.stop_import) def stop_import(self): """ Stops the import of the Bible. """ log.debug('Stopping import!') - self.stop_import = True + self.stop_import_flag = True def do_import(self): """ @@ -114,7 +114,7 @@ class OSISBible(BibleDB): testament = 1 db_book = None for file_record in osis: - if self.stop_import: + if self.stop_import_flag: break match = self.verse_regex.search(file_record) if match: @@ -126,7 +126,7 @@ class OSISBible(BibleDB): log.debug('New book: "%s"', self.books[book][0]) if book == u'Matt': testament += 1 - db_book = self.bibledb.create_book( + db_book = self.create_book( unicode(self.books[book][0]), unicode(self.books[book][1]), testament) @@ -137,7 +137,8 @@ class OSISBible(BibleDB): self.wizard.ImportProgressBar.setMaximum(260) if last_chapter != chapter: if last_chapter != 0: - self.bibledb.save_verses() + pass + #self.save_verses() self.wizard.incrementProgressBar( u'Importing %s %s...' % \ (self.books[match.group(1)][0], chapter)) @@ -170,9 +171,8 @@ class OSISBible(BibleDB): finally: if osis: osis.close() - if self.stop_import: + if self.stop_import_flag: self.wizard.incrementProgressBar(u'Import canceled!') return False else: return success - From 8992f410a57ae5727793b833d7589aa1ec851927 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sat, 6 Feb 2010 16:51:18 +0000 Subject: [PATCH 057/164] Bible fixes part 2 --- openlp/plugins/bibles/lib/osis.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openlp/plugins/bibles/lib/osis.py b/openlp/plugins/bibles/lib/osis.py index f7b25d47b..396b3b6ba 100644 --- a/openlp/plugins/bibles/lib/osis.py +++ b/openlp/plugins/bibles/lib/osis.py @@ -137,8 +137,7 @@ class OSISBible(BibleDB): self.wizard.ImportProgressBar.setMaximum(260) if last_chapter != chapter: if last_chapter != 0: - pass - #self.save_verses() + self.commit() self.wizard.incrementProgressBar( u'Importing %s %s...' % \ (self.books[match.group(1)][0], chapter)) From 2ffe5af9a3608ba9820d204a7da67428d04d059e Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sat, 6 Feb 2010 17:12:03 +0000 Subject: [PATCH 058/164] Fix log levels --- openlp.pyw | 2 +- openlp/core/lib/pluginmanager.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openlp.pyw b/openlp.pyw index f87afdbfd..d35591a1c 100755 --- a/openlp.pyw +++ b/openlp.pyw @@ -91,7 +91,7 @@ class OpenLP(QtGui.QApplication): u'version': bits[0], u'build': bits[1] } - log.warn(u'Openlp version %s build %s' % ( + log.info(u'Openlp version %s build %s' % ( app_version[u'version'], app_version[u'build'])) except: app_version = { diff --git a/openlp/core/lib/pluginmanager.py b/openlp/core/lib/pluginmanager.py index adc79fab6..371cf3661 100644 --- a/openlp/core/lib/pluginmanager.py +++ b/openlp/core/lib/pluginmanager.py @@ -200,7 +200,7 @@ class PluginManager(object): % (plugin.name, plugin.is_active())) if plugin.is_active(): plugin.initialise() - log.warn(u'Initialisation Complete for %s ' % plugin.name) + log.info(u'Initialisation Complete for %s ' % plugin.name) if not plugin.is_active(): plugin.remove_toolbox_item() @@ -213,4 +213,4 @@ class PluginManager(object): for plugin in self.plugins: if plugin.is_active(): plugin.finalise() - log.warn(u'Finalisation Complete for %s ' % plugin.name) + log.info(u'Finalisation Complete for %s ' % plugin.name) From d4668e2ba39eea50117e11bb8bcdc3362be6ac9b Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sat, 6 Feb 2010 17:13:55 +0000 Subject: [PATCH 059/164] Fix log levels --- openlp/core/lib/pluginmanager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openlp/core/lib/pluginmanager.py b/openlp/core/lib/pluginmanager.py index 371cf3661..4d4da144a 100644 --- a/openlp/core/lib/pluginmanager.py +++ b/openlp/core/lib/pluginmanager.py @@ -54,7 +54,7 @@ class PluginManager(object): log.debug(u'Base path %s ', self.basepath) self.plugins = [] # this has to happen after the UI is sorted self.find_plugins(dir) - log.warn(u'Plugin manager Initialised') + log.info(u'Plugin manager Initialised') def find_plugins(self, dir, plugin_helpers): """ From 18760262ab5eaec49028f5f3d5a8386ed7b8ec92 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sun, 7 Feb 2010 20:11:37 +0000 Subject: [PATCH 060/164] Edit Cancel updating song by accident --- openlp/plugins/songs/forms/editsongform.py | 54 +++++++++++----------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/openlp/plugins/songs/forms/editsongform.py b/openlp/plugins/songs/forms/editsongform.py index fb7e0eecd..e06551631 100644 --- a/openlp/plugins/songs/forms/editsongform.py +++ b/openlp/plugins/songs/forms/editsongform.py @@ -316,13 +316,13 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): def onVerseAddButtonClicked(self): self.verse_form.setVerse(u'', self.VerseListWidget.count() + 1, True) - self.verse_form.exec_() - afterText, verse, subVerse = self.verse_form.getVerse() - data = u'%s:%s' %(verse, subVerse) - item = QtGui.QListWidgetItem(afterText) - item.setData(QtCore.Qt.UserRole, QtCore.QVariant(data)) - item.setText(afterText) - self.VerseListWidget.addItem(item) + if self.verse_form.exec_(): + afterText, verse, subVerse = self.verse_form.getVerse() + data = u'%s:%s' %(verse, subVerse) + item = QtGui.QListWidgetItem(afterText) + item.setData(QtCore.Qt.UserRole, QtCore.QVariant(data)) + item.setText(afterText) + self.VerseListWidget.addItem(item) def onVerseEditButtonClicked(self): item = self.VerseListWidget.currentItem() @@ -331,25 +331,25 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): verseId = unicode((item.data(QtCore.Qt.UserRole)).toString()) self.verse_form.setVerse(tempText, \ self.VerseListWidget.count(), True, verseId) - self.verse_form.exec_() - afterText, verse, subVerse = self.verse_form.getVerse() - data = u'%s:%s' %(verse, subVerse) - item.setData(QtCore.Qt.UserRole, QtCore.QVariant(data)) - item.setText(afterText) - #number of lines has change so repaint the list moving the data - if len(tempText.split(u'\n')) != len(afterText.split(u'\n')): - tempList = {} - tempId = {} - for row in range(0, self.VerseListWidget.count()): - tempList[row] = self.VerseListWidget.item(row).text() - tempId[row] = self.VerseListWidget.item(row).\ - data(QtCore.Qt.UserRole) - self.VerseListWidget.clear() - for row in range (0, len(tempList)): - item = QtGui.QListWidgetItem(tempList[row]) - item.setData(QtCore.Qt.UserRole, tempId[row]) - self.VerseListWidget.addItem(item) - self.VerseListWidget.repaint() + if self.verse_form.exec_(): + afterText, verse, subVerse = self.verse_form.getVerse() + data = u'%s:%s' %(verse, subVerse) + item.setData(QtCore.Qt.UserRole, QtCore.QVariant(data)) + item.setText(afterText) + #number of lines has change so repaint the list moving the data + if len(tempText.split(u'\n')) != len(afterText.split(u'\n')): + tempList = {} + tempId = {} + for row in range(0, self.VerseListWidget.count()): + tempList[row] = self.VerseListWidget.item(row).text() + tempId[row] = self.VerseListWidget.item(row).\ + data(QtCore.Qt.UserRole) + self.VerseListWidget.clear() + for row in range (0, len(tempList)): + item = QtGui.QListWidgetItem(tempList[row]) + item.setData(QtCore.Qt.UserRole, tempId[row]) + self.VerseListWidget.addItem(item) + self.VerseListWidget.repaint() self.VerseEditButton.setEnabled(False) self.VerseDeleteButton.setEnabled(False) @@ -533,4 +533,4 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): self.song.search_title = self.song.search_title.replace(u'{', u'') self.song.search_title = self.song.search_title.replace(u'}', u'') self.song.search_title = self.song.search_title.replace(u'?', u'') - self.song.search_title = unicode(self.song.search_title) \ No newline at end of file + self.song.search_title = unicode(self.song.search_title) From fc9b8fcdadf2bbcfd91cb3f4196e0d38dac59bc0 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Tue, 9 Feb 2010 08:28:34 +0000 Subject: [PATCH 061/164] fix presentation import values --- openlp/plugins/presentations/lib/mediaitem.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index 0b14f797d..38a4d0343 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -67,6 +67,7 @@ class PresentationMediaItem(MediaManagerItem): for controller in self.controllers: if self.controllers[controller].enabled: for type in self.controllers[controller].supports: + type = u'*%s' % type if fileType.find(type) == -1: fileType += type + u' ' self.OnNewFileMasks = self.trUtf8('Presentations (%s)' % fileType) From be4c40b401af92f684de92d8d18dedc7b71b000f Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Tue, 9 Feb 2010 11:20:19 +0000 Subject: [PATCH 062/164] Cleaner fix --- openlp/plugins/presentations/lib/mediaitem.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index 38a4d0343..590074ead 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -67,9 +67,8 @@ class PresentationMediaItem(MediaManagerItem): for controller in self.controllers: if self.controllers[controller].enabled: for type in self.controllers[controller].supports: - type = u'*%s' % type if fileType.find(type) == -1: - fileType += type + u' ' + fileType += u'*%s ' % type self.OnNewFileMasks = self.trUtf8('Presentations (%s)' % fileType) def requiredIcons(self): From 508f68e175e72db25e52659d9403055d086a8484 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Tue, 9 Feb 2010 12:36:41 +0000 Subject: [PATCH 063/164] Rename forms to correct name. Refactor UI to simplify it --- .../forms/{auditdeletedialog.ui => songusagedeletedialog.ui} | 0 .../forms/{auditdetaildialog.ui => songusagedetaildialog.ui} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename resources/forms/{auditdeletedialog.ui => songusagedeletedialog.ui} (100%) rename resources/forms/{auditdetaildialog.ui => songusagedetaildialog.ui} (100%) diff --git a/resources/forms/auditdeletedialog.ui b/resources/forms/songusagedeletedialog.ui similarity index 100% rename from resources/forms/auditdeletedialog.ui rename to resources/forms/songusagedeletedialog.ui diff --git a/resources/forms/auditdetaildialog.ui b/resources/forms/songusagedetaildialog.ui similarity index 100% rename from resources/forms/auditdetaildialog.ui rename to resources/forms/songusagedetaildialog.ui From 643801ae05531985bdbaab2f27c8e81b51b413d1 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Tue, 9 Feb 2010 14:44:36 +0000 Subject: [PATCH 064/164] New alert tag features and song usage dialog --- openlp/core/ui/alertstab.py | 45 +- .../songusage/forms/songusagedetaildialog.py | 228 +++------- resources/forms/songusagedetaildialog.ui | 392 +++--------------- 3 files changed, 158 insertions(+), 507 deletions(-) diff --git a/openlp/core/ui/alertstab.py b/openlp/core/ui/alertstab.py index d2e38e048..c5dc1976d 100644 --- a/openlp/core/ui/alertstab.py +++ b/openlp/core/ui/alertstab.py @@ -83,6 +83,22 @@ class AlertsTab(SettingsTab): self.BackgroundColorButton.setObjectName(u'BackgroundColorButton') self.ColorLayout.addWidget(self.BackgroundColorButton) self.FontLayout.addWidget(self.ColorWidget) + self.FontSizeWidget = QtGui.QWidget(self.FontGroupBox) + self.FontSizeWidget.setObjectName(u'FontSizeWidget') + self.FontSizeLayout = QtGui.QHBoxLayout(self.FontSizeWidget) + self.FontSizeLayout.setSpacing(8) + self.FontSizeLayout.setMargin(0) + self.FontSizeLayout.setObjectName(u'FontSizeLayout') + self.FontSizeLabel = QtGui.QLabel(self.FontSizeWidget) + self.FontSizeLabel.setObjectName(u'FontSizeLabel') + self.FontSizeLayout.addWidget(self.FontSizeLabel) + self.FontSizeSpinBox = QtGui.QSpinBox(self.FontSizeWidget) + self.FontSizeSpinBox.setObjectName(u'FontSizeSpinBox') + self.FontSizeLayout.addWidget(self.FontSizeSpinBox) + self.FontSizeSpacer = QtGui.QSpacerItem(147, 20, + QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.FontSizeLayout.addItem(self.FontSizeSpacer) + self.FontLayout.addWidget(self.FontSizeWidget) self.TimeoutWidget = QtGui.QWidget(self.FontGroupBox) self.TimeoutWidget.setObjectName(u'TimeoutWidget') self.TimeoutLayout = QtGui.QHBoxLayout(self.TimeoutWidget) @@ -100,6 +116,22 @@ class AlertsTab(SettingsTab): QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) self.TimeoutLayout.addItem(self.TimeoutSpacer) self.FontLayout.addWidget(self.TimeoutWidget) + self.LocationWidget = QtGui.QWidget(self.FontGroupBox) + self.LocationWidget.setObjectName(u'LocationWidget') + self.LocationLayout = QtGui.QHBoxLayout(self.LocationWidget) + self.LocationLayout.setSpacing(8) + self.LocationLayout.setMargin(0) + self.LocationLayout.setObjectName(u'LocationLayout') + self.LocationLabel = QtGui.QLabel(self.LocationWidget) + self.LocationLabel.setObjectName(u'LocationLabel') + self.LocationLayout.addWidget(self.LocationLabel) + self.LocationComboBox = QtGui.QComboBox(self.LocationWidget) + self.LocationComboBox.setObjectName(u'LocationComboBox') + self.LocationLayout.addWidget(self.LocationComboBox) + self.LocationSpacer = QtGui.QSpacerItem(147, 20, + QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.LocationLayout.addItem(self.LocationSpacer) + self.FontLayout.addWidget(self.LocationWidget) self.SlideLeftLayout.addWidget(self.FontGroupBox) self.SlideLeftSpacer = QtGui.QSpacerItem(20, 94, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) @@ -152,10 +184,15 @@ class AlertsTab(SettingsTab): self.FontLabel.setText(self.trUtf8('Font Name:')) self.FontColorLabel.setText(self.trUtf8('Font Color:')) self.BackgroundColorLabel.setText(self.trUtf8('Background Color:')) + self.FontSizeLabel.setText(self.trUtf8('Font Size:')) + self.FontSizeSpinBox.setSuffix(self.trUtf8('pt')) self.TimeoutLabel.setText(self.trUtf8('Alert timeout:')) self.TimeoutSpinBox.setSuffix(self.trUtf8('s')) + self.LocationLabel.setText(self.trUtf8('Location:')) self.PreviewGroupBox.setTitle(self.trUtf8('Preview')) self.FontPreview.setText(self.trUtf8('openlp.org 2.0 rocks!')) + self.LocationComboBox.addItem(self.trUtf8('Top')) + self.LocationComboBox.addItem(self.trUtf8('Bottom')) def onBackgroundColorButtonClicked(self): self.bg_color = QtGui.QColorDialog.getColor( @@ -181,15 +218,19 @@ class AlertsTab(SettingsTab): self.timeout = int(self.config.get_config(u'timeout', 5)) self.font_color = unicode( self.config.get_config(u'font color', u'#ffffff')) + self.font_size = int(self.config.get_config(u'font size', 40)) self.bg_color = unicode( self.config.get_config(u'background color', u'#660000')) self.font_face = unicode( self.config.get_config(u'font face', QtGui.QFont().family())) + self.location = int(self.config.get_config(u'location', 0)) + self.FontSizeSpinBox.setValue(self.font_size) self.TimeoutSpinBox.setValue(self.timeout) self.FontColorButton.setStyleSheet( u'background-color: %s' % self.font_color) self.BackgroundColorButton.setStyleSheet( u'background-color: %s' % self.bg_color) + self.LocationComboBox.setCurrentIndex(self.location) font = QtGui.QFont() font.setFamily(self.font_face) self.FontComboBox.setCurrentFont(font) @@ -199,8 +240,10 @@ class AlertsTab(SettingsTab): self.font_face = self.FontComboBox.currentFont().family() self.config.set_config(u'background color', unicode(self.bg_color)) self.config.set_config(u'font color', unicode(self.font_color)) + self.config.set_config(u'font size', unicode(self.font_size)) self.config.set_config(u'font face', unicode(self.font_face)) self.config.set_config(u'timeout', unicode(self.timeout)) + self.config.set_config(u'location', unicode(self.FontComboBox.currentIndex())) def updateDisplay(self): font = QtGui.QFont() @@ -209,4 +252,4 @@ class AlertsTab(SettingsTab): font.setPointSize(16) self.FontPreview.setFont(font) self.FontPreview.setStyleSheet(u'background-color: %s; color: %s' % \ - (self.bg_color, self.font_color)) \ No newline at end of file + (self.bg_color, self.font_color)) diff --git a/openlp/plugins/songusage/forms/songusagedetaildialog.py b/openlp/plugins/songusage/forms/songusagedetaildialog.py index bfb2efcaf..a191b8f08 100644 --- a/openlp/plugins/songusage/forms/songusagedetaildialog.py +++ b/openlp/plugins/songusage/forms/songusagedetaildialog.py @@ -1,197 +1,71 @@ # -*- coding: utf-8 -*- -# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 -############################################################################### -# OpenLP - Open Source Lyrics Projection # -# --------------------------------------------------------------------------- # -# Copyright (c) 2008-2010 Raoul Snyman # -# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # -# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # -# Carsten Tinggaard # -# --------------------------------------------------------------------------- # -# 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 # -############################################################################### +# Form implementation generated from reading ui file 'songusagedetaildialog.ui' +# +# Created: Tue Feb 9 07:34:05 2010 +# by: PyQt4 UI code generator 4.6.2 +# +# WARNING! All changes made in this file will be lost! from PyQt4 import QtCore, QtGui -class Ui_SongUsageDetailDialog(object): +class Ui_AuditDetailDialog(object): def setupUi(self, AuditDetailDialog): - AuditDetailDialog.setObjectName(u'AuditDetailDialog') - AuditDetailDialog.resize(593, 501) - self.buttonBox = QtGui.QDialogButtonBox(AuditDetailDialog) - self.buttonBox.setGeometry(QtCore.QRect(420, 470, 170, 25)) - self.buttonBox.setStandardButtons( - QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok) - self.buttonBox.setObjectName(u'buttonBox') - self.FileGroupBox = QtGui.QGroupBox(AuditDetailDialog) - self.FileGroupBox.setGeometry(QtCore.QRect(10, 370, 571, 70)) - self.FileGroupBox.setObjectName(u'FileGroupBox') + AuditDetailDialog.setObjectName("AuditDetailDialog") + AuditDetailDialog.resize(609, 413) + self.verticalLayout = QtGui.QVBoxLayout(AuditDetailDialog) + self.verticalLayout.setObjectName("verticalLayout") + self.DateRangeGroupBox = QtGui.QGroupBox(AuditDetailDialog) + self.DateRangeGroupBox.setObjectName("DateRangeGroupBox") + self.verticalLayout_2 = QtGui.QVBoxLayout(self.DateRangeGroupBox) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.DateHorizontalLayout = QtGui.QHBoxLayout() + self.DateHorizontalLayout.setObjectName("DateHorizontalLayout") + self.FromDate = QtGui.QCalendarWidget(self.DateRangeGroupBox) + self.FromDate.setObjectName("FromDate") + self.DateHorizontalLayout.addWidget(self.FromDate) + self.ToLabel = QtGui.QLabel(self.DateRangeGroupBox) + self.ToLabel.setScaledContents(False) + self.ToLabel.setAlignment(QtCore.Qt.AlignCenter) + self.ToLabel.setObjectName("ToLabel") + self.DateHorizontalLayout.addWidget(self.ToLabel) + self.ToDate = QtGui.QCalendarWidget(self.DateRangeGroupBox) + self.ToDate.setObjectName("ToDate") + self.DateHorizontalLayout.addWidget(self.ToDate) + self.verticalLayout_2.addLayout(self.DateHorizontalLayout) + self.FileGroupBox = QtGui.QGroupBox(self.DateRangeGroupBox) + self.FileGroupBox.setObjectName("FileGroupBox") self.verticalLayout_4 = QtGui.QVBoxLayout(self.FileGroupBox) - self.verticalLayout_4.setObjectName(u'verticalLayout_4') + self.verticalLayout_4.setObjectName("verticalLayout_4") self.horizontalLayout = QtGui.QHBoxLayout() - self.horizontalLayout.setObjectName(u'horizontalLayout') + self.horizontalLayout.setObjectName("horizontalLayout") self.FileLineEdit = QtGui.QLineEdit(self.FileGroupBox) - self.FileLineEdit.setObjectName(u'FileLineEdit') + self.FileLineEdit.setObjectName("FileLineEdit") self.horizontalLayout.addWidget(self.FileLineEdit) self.SaveFilePushButton = QtGui.QPushButton(self.FileGroupBox) icon = QtGui.QIcon() - icon.addPixmap(QtGui.QPixmap(u':/exports/export_load.png'), - QtGui.QIcon.Normal, QtGui.QIcon.Off) + icon.addPixmap(QtGui.QPixmap(":/exports/export_load.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.SaveFilePushButton.setIcon(icon) - self.SaveFilePushButton.setObjectName(u'SaveFilePushButton') + self.SaveFilePushButton.setObjectName("SaveFilePushButton") self.horizontalLayout.addWidget(self.SaveFilePushButton) self.verticalLayout_4.addLayout(self.horizontalLayout) - self.layoutWidget = QtGui.QWidget(AuditDetailDialog) - self.layoutWidget.setGeometry(QtCore.QRect(10, 10, 561, 361)) - self.layoutWidget.setObjectName(u'layoutWidget') - self.verticalLayout_3 = QtGui.QVBoxLayout(self.layoutWidget) - self.verticalLayout_3.setObjectName(u'verticalLayout_3') - self.ReportTypeGroup = QtGui.QGroupBox(self.layoutWidget) - self.ReportTypeGroup.setObjectName(u'ReportTypeGroup') - self.layoutWidget1 = QtGui.QWidget(self.ReportTypeGroup) - self.layoutWidget1.setGeometry(QtCore.QRect(50, 40, 481, 23)) - self.layoutWidget1.setObjectName(u'layoutWidget1') - self.ReportHorizontalLayout = QtGui.QHBoxLayout(self.layoutWidget1) - self.ReportHorizontalLayout.setObjectName(u'ReportHorizontalLayout') - self.SummaryReport = QtGui.QRadioButton(self.layoutWidget1) - self.SummaryReport.setObjectName(u'SummaryReport') - self.ReportHorizontalLayout.addWidget(self.SummaryReport) - self.DetailedReport = QtGui.QRadioButton(self.layoutWidget1) - self.DetailedReport.setChecked(True) - self.DetailedReport.setObjectName(u'DetailedReport') - self.ReportHorizontalLayout.addWidget(self.DetailedReport) - self.verticalLayout_3.addWidget(self.ReportTypeGroup) - self.DateRangeGroupBox = QtGui.QGroupBox(self.layoutWidget) - self.DateRangeGroupBox.setObjectName(u'DateRangeGroupBox') - self.verticalLayout_2 = QtGui.QVBoxLayout(self.DateRangeGroupBox) - self.verticalLayout_2.setObjectName(u'verticalLayout_2') - self.DateHorizontalLayout = QtGui.QHBoxLayout() - self.DateHorizontalLayout.setObjectName(u'DateHorizontalLayout') - self.FromDateEdit = QtGui.QDateEdit(self.DateRangeGroupBox) - self.FromDateEdit.setCalendarPopup(True) - self.FromDateEdit.setObjectName(u'FromDateEdit') - self.DateHorizontalLayout.addWidget(self.FromDateEdit) - self.To = QtGui.QLabel(self.DateRangeGroupBox) - self.To.setObjectName(u'To') - self.DateHorizontalLayout.addWidget(self.To) - self.ToDateEdit = QtGui.QDateEdit(self.DateRangeGroupBox) - self.ToDateEdit.setCalendarPopup(True) - self.ToDateEdit.setObjectName(u'ToDateEdit') - self.DateHorizontalLayout.addWidget(self.ToDateEdit) - self.verticalLayout_2.addLayout(self.DateHorizontalLayout) - self.verticalLayout_3.addWidget(self.DateRangeGroupBox) - self.TimePeriodGroupBox = QtGui.QGroupBox(self.layoutWidget) - self.TimePeriodGroupBox.setObjectName(u'TimePeriodGroupBox') - self.verticalLayout = QtGui.QVBoxLayout(self.TimePeriodGroupBox) - self.verticalLayout.setObjectName(u'verticalLayout') - self.FirstHorizontalLayout = QtGui.QHBoxLayout() - self.FirstHorizontalLayout.setObjectName(u'FirstHorizontalLayout') - self.FirstCheckBox = QtGui.QCheckBox(self.TimePeriodGroupBox) - self.FirstCheckBox.setChecked(True) - self.FirstCheckBox.setObjectName(u'FirstCheckBox') - self.FirstHorizontalLayout.addWidget(self.FirstCheckBox) - self.FirstFromTimeEdit = QtGui.QTimeEdit(self.TimePeriodGroupBox) - self.FirstFromTimeEdit.setTime(QtCore.QTime(9, 0, 0)) - self.FirstFromTimeEdit.setObjectName(u'FirstFromTimeEdit') - self.FirstHorizontalLayout.addWidget(self.FirstFromTimeEdit) - self.FirstTo = QtGui.QLabel(self.TimePeriodGroupBox) - self.FirstTo.setObjectName(u'FirstTo') - self.FirstHorizontalLayout.addWidget(self.FirstTo) - self.FirstToTimeEdit = QtGui.QTimeEdit(self.TimePeriodGroupBox) - self.FirstToTimeEdit.setCalendarPopup(True) - self.FirstToTimeEdit.setTime(QtCore.QTime(10, 0, 0)) - self.FirstToTimeEdit.setObjectName(u'FirstToTimeEdit') - self.FirstHorizontalLayout.addWidget(self.FirstToTimeEdit) - self.verticalLayout.addLayout(self.FirstHorizontalLayout) - self.SecondHorizontalLayout = QtGui.QHBoxLayout() - self.SecondHorizontalLayout.setObjectName(u'SecondHorizontalLayout') - self.SecondCheckBox = QtGui.QCheckBox(self.TimePeriodGroupBox) - self.SecondCheckBox.setChecked(True) - self.SecondCheckBox.setObjectName(u'SecondCheckBox') - self.SecondHorizontalLayout.addWidget(self.SecondCheckBox) - self.SecondFromTimeEdit = QtGui.QTimeEdit(self.TimePeriodGroupBox) - self.SecondFromTimeEdit.setTime(QtCore.QTime(10, 45, 0)) - self.SecondFromTimeEdit.setObjectName(u'SecondFromTimeEdit') - self.SecondHorizontalLayout.addWidget(self.SecondFromTimeEdit) - self.SecondTo = QtGui.QLabel(self.TimePeriodGroupBox) - self.SecondTo.setObjectName(u'SecondTo') - self.SecondHorizontalLayout.addWidget(self.SecondTo) - self.SecondToTimeEdit = QtGui.QTimeEdit(self.TimePeriodGroupBox) - self.SecondToTimeEdit.setObjectName(u'SecondToTimeEdit') - self.SecondHorizontalLayout.addWidget(self.SecondToTimeEdit) - self.verticalLayout.addLayout(self.SecondHorizontalLayout) - self.ThirdHorizontalLayout = QtGui.QHBoxLayout() - self.ThirdHorizontalLayout.setObjectName(u'ThirdHorizontalLayout') - self.ThirdCheckBox = QtGui.QCheckBox(self.TimePeriodGroupBox) - self.ThirdCheckBox.setChecked(True) - self.ThirdCheckBox.setObjectName(u'ThirdCheckBox') - self.ThirdHorizontalLayout.addWidget(self.ThirdCheckBox) - self.ThirdFromTimeEdit = QtGui.QTimeEdit(self.TimePeriodGroupBox) - self.ThirdFromTimeEdit.setTime(QtCore.QTime(18, 30, 0)) - self.ThirdFromTimeEdit.setObjectName(u'ThirdFromTimeEdit') - self.ThirdHorizontalLayout.addWidget(self.ThirdFromTimeEdit) - self.ThirdTo = QtGui.QLabel(self.TimePeriodGroupBox) - self.ThirdTo.setObjectName(u'ThirdTo') - self.ThirdHorizontalLayout.addWidget(self.ThirdTo) - self.ThirdToTimeEdit = QtGui.QTimeEdit(self.TimePeriodGroupBox) - self.ThirdToTimeEdit.setTime(QtCore.QTime(19, 30, 0)) - self.ThirdToTimeEdit.setObjectName(u'ThirdToTimeEdit') - self.ThirdHorizontalLayout.addWidget(self.ThirdToTimeEdit) - self.verticalLayout.addLayout(self.ThirdHorizontalLayout) - self.verticalLayout_3.addWidget(self.TimePeriodGroupBox) + self.verticalLayout_2.addWidget(self.FileGroupBox) + self.verticalLayout.addWidget(self.DateRangeGroupBox) + self.buttonBox = QtGui.QDialogButtonBox(AuditDetailDialog) + self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.verticalLayout.addWidget(self.buttonBox) self.retranslateUi(AuditDetailDialog) - QtCore.QObject.connect( - self.buttonBox, QtCore.SIGNAL(u'accepted()'), - AuditDetailDialog.accept) - QtCore.QObject.connect( - self.buttonBox, QtCore.SIGNAL(u'rejected()'), - AuditDetailDialog.close) - QtCore.QObject.connect( - self.FirstCheckBox, QtCore.SIGNAL(u'stateChanged(int)'), - AuditDetailDialog.changeFirstService) - QtCore.QObject.connect( - self.SecondCheckBox, QtCore.SIGNAL(u'stateChanged(int)'), - AuditDetailDialog.changeSecondService) - QtCore.QObject.connect( - self.ThirdCheckBox, QtCore.SIGNAL(u'stateChanged(int)'), - AuditDetailDialog.changeThirdService) - QtCore.QObject.connect( - self.SaveFilePushButton, QtCore.SIGNAL(u'pressed()'), - AuditDetailDialog.defineOutputLocation) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL("accepted()"), AuditDetailDialog.accept) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL("rejected()"), AuditDetailDialog.close) + QtCore.QObject.connect(self.SaveFilePushButton, QtCore.SIGNAL("pressed()"), AuditDetailDialog.defineOutputLocation) QtCore.QMetaObject.connectSlotsByName(AuditDetailDialog) def retranslateUi(self, AuditDetailDialog): - AuditDetailDialog.setWindowTitle(self.trUtf8('Audit Detail Extraction')) - self.FileGroupBox.setTitle(self.trUtf8('Report Location')) - self.ReportTypeGroup.setTitle(self.trUtf8('Report Type')) - self.SummaryReport.setText(self.trUtf8('Summary')) - self.DetailedReport.setText(self.trUtf8('Detailed')) - self.DateRangeGroupBox.setTitle(self.trUtf8('Select Date Range')) - self.FromDateEdit.setDisplayFormat(self.trUtf8('dd/MM/yyyy')) - self.To.setText(self.trUtf8('to')) - self.ToDateEdit.setDisplayFormat(self.trUtf8('dd/MM/yyyy')) - self.TimePeriodGroupBox.setTitle(self.trUtf8('Select Time Periods')) - self.FirstCheckBox.setText(self.trUtf8('First Service')) - self.FirstFromTimeEdit.setDisplayFormat(self.trUtf8('hh:mm AP')) - self.FirstTo.setText(self.trUtf8('to')) - self.FirstToTimeEdit.setDisplayFormat(self.trUtf8('hh:mm AP')) - self.SecondCheckBox.setText(self.trUtf8('Second Service')) - self.SecondFromTimeEdit.setDisplayFormat(self.trUtf8('hh:mm AP')) - self.SecondTo.setText(self.trUtf8('to')) - self.SecondToTimeEdit.setDisplayFormat(self.trUtf8('hh:mm AP')) - self.ThirdCheckBox.setText(self.trUtf8('Third Service')) - self.ThirdFromTimeEdit.setDisplayFormat(self.trUtf8('hh:mm AP')) - self.ThirdTo.setText(self.trUtf8('to')) - self.ThirdToTimeEdit.setDisplayFormat(self.trUtf8('hh:mm AP')) \ No newline at end of file + AuditDetailDialog.setWindowTitle(QtGui.QApplication.translate("AuditDetailDialog", "Audit Detail Extraction", None, QtGui.QApplication.UnicodeUTF8)) + self.DateRangeGroupBox.setTitle(QtGui.QApplication.translate("AuditDetailDialog", "Select Date Range", None, QtGui.QApplication.UnicodeUTF8)) + self.ToLabel.setText(QtGui.QApplication.translate("AuditDetailDialog", "to", None, QtGui.QApplication.UnicodeUTF8)) + self.FileGroupBox.setTitle(QtGui.QApplication.translate("AuditDetailDialog", "Report Location", None, QtGui.QApplication.UnicodeUTF8)) + +import openlp-2_rc diff --git a/resources/forms/songusagedetaildialog.ui b/resources/forms/songusagedetaildialog.ui index bafcfd535..a32b63899 100644 --- a/resources/forms/songusagedetaildialog.ui +++ b/resources/forms/songusagedetaildialog.ui @@ -6,299 +6,81 @@ <rect> <x>0</x> <y>0</y> - <width>593</width> - <height>501</height> + <width>609</width> + <height>413</height> </rect> </property> <property name="windowTitle"> <string>Audit Detail Extraction</string> </property> - <widget class="QDialogButtonBox" name="buttonBox"> - <property name="geometry"> - <rect> - <x>420</x> - <y>470</y> - <width>170</width> - <height>25</height> - </rect> - </property> - <property name="standardButtons"> - <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> - </property> - </widget> - <widget class="QGroupBox" name="FileGroupBox"> - <property name="geometry"> - <rect> - <x>10</x> - <y>370</y> - <width>571</width> - <height>70</height> - </rect> - </property> - <property name="title"> - <string>Report Location</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout_4"> - <item> - <layout class="QHBoxLayout" name="horizontalLayout"> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QGroupBox" name="DateRangeGroupBox"> + <property name="title"> + <string>Select Date Range</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> <item> - <widget class="QLineEdit" name="FileLineEdit"/> + <layout class="QHBoxLayout" name="DateHorizontalLayout"> + <item> + <widget class="QCalendarWidget" name="FromDate"/> + </item> + <item> + <widget class="QLabel" name="ToLabel"> + <property name="text"> + <string>to</string> + </property> + <property name="scaledContents"> + <bool>false</bool> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QCalendarWidget" name="ToDate"/> + </item> + </layout> </item> <item> - <widget class="QPushButton" name="SaveFilePushButton"> - <property name="text"> - <string/> - </property> - <property name="icon"> - <iconset resource="../images/openlp-2.qrc"> - <normaloff>:/exports/export_load.png</normaloff>:/exports/export_load.png</iconset> + <widget class="QGroupBox" name="FileGroupBox"> + <property name="title"> + <string>Report Location</string> </property> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLineEdit" name="FileLineEdit"/> + </item> + <item> + <widget class="QPushButton" name="SaveFilePushButton"> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset resource="../images/openlp-2.qrc"> + <normaloff>:/exports/export_load.png</normaloff>:/exports/export_load.png</iconset> + </property> + </widget> + </item> + </layout> + </item> + </layout> </widget> </item> </layout> - </item> - </layout> - </widget> - <widget class="QWidget" name="layoutWidget"> - <property name="geometry"> - <rect> - <x>10</x> - <y>10</y> - <width>561</width> - <height>361</height> - </rect> - </property> - <layout class="QVBoxLayout" name="verticalLayout_3"> - <item> - <widget class="QGroupBox" name="ReportTypeGroup"> - <property name="title"> - <string>Report Type</string> - </property> - <widget class="QWidget" name="layoutWidget"> - <property name="geometry"> - <rect> - <x>50</x> - <y>40</y> - <width>481</width> - <height>23</height> - </rect> - </property> - <layout class="QHBoxLayout" name="ReportHorizontalLayout"> - <item> - <widget class="QRadioButton" name="SummaryReport"> - <property name="text"> - <string>Summary</string> - </property> - </widget> - </item> - <item> - <widget class="QRadioButton" name="DetailedReport"> - <property name="text"> - <string>Detailed</string> - </property> - <property name="checked"> - <bool>true</bool> - </property> - </widget> - </item> - </layout> - </widget> - </widget> - </item> - <item> - <widget class="QGroupBox" name="DateRangeGroupBox"> - <property name="title"> - <string>Select Date Range</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <item> - <layout class="QHBoxLayout" name="DateHorizontalLayout"> - <item> - <widget class="QDateEdit" name="FromDateEdit"> - <property name="displayFormat"> - <string>dd/MM/yyyy</string> - </property> - <property name="calendarPopup"> - <bool>true</bool> - </property> - </widget> - </item> - <item> - <widget class="QLabel" name="To"> - <property name="text"> - <string>to</string> - </property> - </widget> - </item> - <item> - <widget class="QDateEdit" name="ToDateEdit"> - <property name="displayFormat"> - <string>dd/MM/yyyy</string> - </property> - <property name="calendarPopup"> - <bool>true</bool> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </widget> - </item> - <item> - <widget class="QGroupBox" name="TimePeriodGroupBox"> - <property name="title"> - <string>Select Time Periods</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <layout class="QHBoxLayout" name="FirstHorizontalLayout"> - <item> - <widget class="QCheckBox" name="FirstCheckBox"> - <property name="text"> - <string>First Service</string> - </property> - <property name="checked"> - <bool>true</bool> - </property> - </widget> - </item> - <item> - <widget class="QTimeEdit" name="FirstFromTimeEdit"> - <property name="displayFormat"> - <string>hh:mm AP</string> - </property> - <property name="time"> - <time> - <hour>9</hour> - <minute>0</minute> - <second>0</second> - </time> - </property> - </widget> - </item> - <item> - <widget class="QLabel" name="FirstTo"> - <property name="text"> - <string>to</string> - </property> - </widget> - </item> - <item> - <widget class="QTimeEdit" name="FirstToTimeEdit"> - <property name="displayFormat"> - <string>hh:mm AP</string> - </property> - <property name="calendarPopup"> - <bool>true</bool> - </property> - <property name="time"> - <time> - <hour>10</hour> - <minute>0</minute> - <second>0</second> - </time> - </property> - </widget> - </item> - </layout> - </item> - <item> - <layout class="QHBoxLayout" name="SecondHorizontalLayout"> - <item> - <widget class="QCheckBox" name="SecondCheckBox"> - <property name="text"> - <string>Second Service</string> - </property> - <property name="checked"> - <bool>true</bool> - </property> - </widget> - </item> - <item> - <widget class="QTimeEdit" name="SecondFromTimeEdit"> - <property name="displayFormat"> - <string>hh:mm AP</string> - </property> - <property name="time"> - <time> - <hour>10</hour> - <minute>45</minute> - <second>0</second> - </time> - </property> - </widget> - </item> - <item> - <widget class="QLabel" name="SecondTo"> - <property name="text"> - <string>to</string> - </property> - </widget> - </item> - <item> - <widget class="QTimeEdit" name="SecondToTimeEdit"> - <property name="displayFormat"> - <string>hh:mm AP</string> - </property> - </widget> - </item> - </layout> - </item> - <item> - <layout class="QHBoxLayout" name="ThirdHorizontalLayout"> - <item> - <widget class="QCheckBox" name="ThirdCheckBox"> - <property name="text"> - <string>Third Service</string> - </property> - <property name="checked"> - <bool>true</bool> - </property> - </widget> - </item> - <item> - <widget class="QTimeEdit" name="ThirdFromTimeEdit"> - <property name="displayFormat"> - <string>hh:mm AP</string> - </property> - <property name="time"> - <time> - <hour>18</hour> - <minute>30</minute> - <second>0</second> - </time> - </property> - </widget> - </item> - <item> - <widget class="QLabel" name="ThirdTo"> - <property name="text"> - <string>to</string> - </property> - </widget> - </item> - <item> - <widget class="QTimeEdit" name="ThirdToTimeEdit"> - <property name="displayFormat"> - <string>hh:mm AP</string> - </property> - <property name="time"> - <time> - <hour>19</hour> - <minute>30</minute> - <second>0</second> - </time> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </widget> - </item> - </layout> - </widget> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> </widget> <resources> <include location="../images/openlp-2.qrc"/> @@ -336,54 +118,6 @@ </hint> </hints> </connection> - <connection> - <sender>FirstCheckBox</sender> - <signal>stateChanged(int)</signal> - <receiver>AuditDetailDialog</receiver> - <slot>changeFirstService(int)</slot> - <hints> - <hint type="sourcelabel"> - <x>26</x> - <y>285</y> - </hint> - <hint type="destinationlabel"> - <x>136</x> - <y>483</y> - </hint> - </hints> - </connection> - <connection> - <sender>SecondCheckBox</sender> - <signal>stateChanged(int)</signal> - <receiver>AuditDetailDialog</receiver> - <slot>changeSecondService(int)</slot> - <hints> - <hint type="sourcelabel"> - <x>41</x> - <y>323</y> - </hint> - <hint type="destinationlabel"> - <x>103</x> - <y>494</y> - </hint> - </hints> - </connection> - <connection> - <sender>ThirdCheckBox</sender> - <signal>stateChanged(int)</signal> - <receiver>AuditDetailDialog</receiver> - <slot>changeThirdService(int)</slot> - <hints> - <hint type="sourcelabel"> - <x>38</x> - <y>351</y> - </hint> - <hint type="destinationlabel"> - <x>155</x> - <y>463</y> - </hint> - </hints> - </connection> <connection> <sender>SaveFilePushButton</sender> <signal>pressed()</signal> From 73d6a24792ee02e8d547d5d1cc4c221525e6972f Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Tue, 9 Feb 2010 14:56:31 +0000 Subject: [PATCH 065/164] Add Fontsize and location to alerts settings --- openlp/core/ui/alertstab.py | 18 +++++++++++++----- .../songusage/forms/songusagedetaildialog.py | 2 -- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/openlp/core/ui/alertstab.py b/openlp/core/ui/alertstab.py index c5dc1976d..bf4b9a004 100644 --- a/openlp/core/ui/alertstab.py +++ b/openlp/core/ui/alertstab.py @@ -126,6 +126,8 @@ class AlertsTab(SettingsTab): self.LocationLabel.setObjectName(u'LocationLabel') self.LocationLayout.addWidget(self.LocationLabel) self.LocationComboBox = QtGui.QComboBox(self.LocationWidget) + self.LocationComboBox.addItem(QtCore.QString()) + self.LocationComboBox.addItem(QtCore.QString()) self.LocationComboBox.setObjectName(u'LocationComboBox') self.LocationLayout.addWidget(self.LocationComboBox) self.LocationSpacer = QtGui.QSpacerItem(147, 20, @@ -178,6 +180,8 @@ class AlertsTab(SettingsTab): QtCore.SIGNAL(u'activated(int)'), self.onFontComboBoxClicked) QtCore.QObject.connect(self.TimeoutSpinBox, QtCore.SIGNAL(u'valueChanged(int)'), self.onTimeoutSpinBoxChanged) + QtCore.QObject.connect(self.FontSizeSpinBox, + QtCore.SIGNAL(u'valueChanged(int)'), self.onFontSizeSpinBoxChanged) def retranslateUi(self): self.FontGroupBox.setTitle(self.trUtf8('Font')) @@ -190,9 +194,9 @@ class AlertsTab(SettingsTab): self.TimeoutSpinBox.setSuffix(self.trUtf8('s')) self.LocationLabel.setText(self.trUtf8('Location:')) self.PreviewGroupBox.setTitle(self.trUtf8('Preview')) - self.FontPreview.setText(self.trUtf8('openlp.org 2.0 rocks!')) - self.LocationComboBox.addItem(self.trUtf8('Top')) - self.LocationComboBox.addItem(self.trUtf8('Bottom')) + self.FontPreview.setText(self.trUtf8('openlp.org')) + self.LocationComboBox.setItemText(0, self.trUtf8('Top')) + self.LocationComboBox.setItemText(1, self.trUtf8('Bottom')) def onBackgroundColorButtonClicked(self): self.bg_color = QtGui.QColorDialog.getColor( @@ -214,6 +218,10 @@ class AlertsTab(SettingsTab): def onTimeoutSpinBoxChanged(self): self.timeout = self.TimeoutSpinBox.value() + def onFontSizeSpinBoxChanged(self): + self.font_size = self.FontSizeSpinBox.value() + self.updateDisplay() + def load(self): self.timeout = int(self.config.get_config(u'timeout', 5)) self.font_color = unicode( @@ -243,13 +251,13 @@ class AlertsTab(SettingsTab): self.config.set_config(u'font size', unicode(self.font_size)) self.config.set_config(u'font face', unicode(self.font_face)) self.config.set_config(u'timeout', unicode(self.timeout)) - self.config.set_config(u'location', unicode(self.FontComboBox.currentIndex())) + self.config.set_config(u'location', unicode(self.LocationComboBox.currentIndex())) def updateDisplay(self): font = QtGui.QFont() font.setFamily(self.FontComboBox.currentFont().family()) font.setBold(True) - font.setPointSize(16) + font.setPointSize(self.font_size) self.FontPreview.setFont(font) self.FontPreview.setStyleSheet(u'background-color: %s; color: %s' % \ (self.bg_color, self.font_color)) diff --git a/openlp/plugins/songusage/forms/songusagedetaildialog.py b/openlp/plugins/songusage/forms/songusagedetaildialog.py index a191b8f08..a4bb2e906 100644 --- a/openlp/plugins/songusage/forms/songusagedetaildialog.py +++ b/openlp/plugins/songusage/forms/songusagedetaildialog.py @@ -67,5 +67,3 @@ class Ui_AuditDetailDialog(object): self.DateRangeGroupBox.setTitle(QtGui.QApplication.translate("AuditDetailDialog", "Select Date Range", None, QtGui.QApplication.UnicodeUTF8)) self.ToLabel.setText(QtGui.QApplication.translate("AuditDetailDialog", "to", None, QtGui.QApplication.UnicodeUTF8)) self.FileGroupBox.setTitle(QtGui.QApplication.translate("AuditDetailDialog", "Report Location", None, QtGui.QApplication.UnicodeUTF8)) - -import openlp-2_rc From edc46f2967755d6b5879e116cf86e196d63d0235 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Tue, 9 Feb 2010 16:09:57 +0000 Subject: [PATCH 066/164] Fix songs so tagging can work > 10 verses. Tested with 100! --- openlp/core/ui/slidecontroller.py | 2 +- openlp/plugins/songs/forms/editsongform.py | 2 +- openlp/plugins/songs/lib/mediaitem.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 8a4aebd8c..e88699b71 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -444,7 +444,7 @@ class SlideController(QtGui.QWidget): tag = None #If verse handle verse number else tag only if bits[0] == self.trUtf8('Verse'): - tag = u'%s%s' % (bits[0][0], bits[1][0] ) + tag = u'%s%s' % (bits[0][0], bits[1][0:] ) else: tag = bits[0] try: diff --git a/openlp/plugins/songs/forms/editsongform.py b/openlp/plugins/songs/forms/editsongform.py index e06551631..477910108 100644 --- a/openlp/plugins/songs/forms/editsongform.py +++ b/openlp/plugins/songs/forms/editsongform.py @@ -410,7 +410,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): self.AuthorsListView.setFocus() #split the verse list by space and mark lower case for testing for verse in unicode(self.VerseOrderEdit.text()).lower().split(u' '): - if len(verse) == 2: + if len(verse) > 1: if verse[0:1] == u'v' and verse[1:].isdigit(): pass else: diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py index f5fe0686b..7398fe18d 100644 --- a/openlp/plugins/songs/lib/mediaitem.py +++ b/openlp/plugins/songs/lib/mediaitem.py @@ -310,7 +310,7 @@ class SongMediaItem(MediaManagerItem): for verse in verseList: if verse[1]: if verse[0][u'type'] == "Verse": - if verse[0][u'label'][0] == order[1:]: + if verse[0][u'label'] == order[1:]: verseTag = u'%s:%s' % \ (verse[0][u'type'], verse[0][u'label']) service_item.add_from_text\ From 944f45c8c855cf9fd7d5e7b35d8585dbbd78b533 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Thu, 11 Feb 2010 18:39:28 +0000 Subject: [PATCH 067/164] Fix up song usage extract processing --- openlp/core/ui/__init__.py | 2 - openlp/core/ui/maindisplay.py | 66 ++----------- openlp/core/ui/mainwindow.py | 22 +---- openlp/core/ui/settingsform.py | 7 +- openlp/plugins/alerts/__init__.py | 24 +++++ openlp/plugins/alerts/alertsplugin.py | 98 +++++++++++++++++++ openlp/plugins/alerts/forms/__init__.py | 27 +++++ .../ui => plugins/alerts/forms}/alertform.py | 8 +- .../ui => plugins/alerts/forms}/alertstab.py | 8 +- openlp/plugins/alerts/lib/__init__.py | 25 +++++ openlp/plugins/alerts/lib/alertmanager.py | 75 ++++++++++++++ .../songusage/forms/songusagedeleteform.py | 3 +- .../songusage/forms/songusagedetaildialog.py | 2 +- .../songusage/forms/songusagedetailform.py | 63 ++---------- openlp/plugins/songusage/lib/manager.py | 10 +- 15 files changed, 280 insertions(+), 160 deletions(-) create mode 100644 openlp/plugins/alerts/__init__.py create mode 100644 openlp/plugins/alerts/alertsplugin.py create mode 100644 openlp/plugins/alerts/forms/__init__.py rename openlp/{core/ui => plugins/alerts/forms}/alertform.py (98%) rename openlp/{core/ui => plugins/alerts/forms}/alertstab.py (98%) create mode 100644 openlp/plugins/alerts/lib/__init__.py create mode 100644 openlp/plugins/alerts/lib/alertmanager.py diff --git a/openlp/core/ui/__init__.py b/openlp/core/ui/__init__.py index 5d4c798d8..6e37afc7f 100644 --- a/openlp/core/ui/__init__.py +++ b/openlp/core/ui/__init__.py @@ -28,11 +28,9 @@ from maindisplay import MainDisplay from amendthemeform import AmendThemeForm from slidecontroller import SlideController from splashscreen import SplashScreen -from alertstab import AlertsTab from generaltab import GeneralTab from themestab import ThemesTab from aboutform import AboutForm -from alertform import AlertForm from pluginform import PluginForm from settingsform import SettingsForm from mediadockmanager import MediaDockManager diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index bf0e11cb3..43cb0d848 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -120,8 +120,6 @@ class MainDisplay(DisplayWidget): self.hasTransition = False self.alertList = [] self.mediaBackground = False - QtCore.QObject.connect(Receiver.get_receiver(), - QtCore.SIGNAL(u'alert_text'), self.displayAlert) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'live_slide_hide'), self.hideDisplay) QtCore.QObject.connect(Receiver.get_receiver(), @@ -218,6 +216,12 @@ class MainDisplay(DisplayWidget): self.screen[u'size'].height() ) self.display_image.setPixmap(QtGui.QPixmap.fromImage(frame)) + def addAlertImage(self, frame, blank=False): + if blank: + self.display_alert.setPixmap(self.transparent) + else: + self.display_alert.setPixmap(frame) + def frameView(self, frame, transition=False): """ Called from a slide controller to display a frame @@ -257,64 +261,6 @@ class MainDisplay(DisplayWidget): if self.display_frame: self.frameView(self.display_frame) - - def displayAlert(self, text=u''): - """ - Called from the Alert Tab to display an alert - - ``text`` - display text - """ - log.debug(u'display alert called %s' % text) - self.parent.StatusBar.showMessage(self.trUtf8(u'')) - self.alertList.append(text) - if self.timer_id != 0 or self.mediaLoaded: - self.parent.StatusBar.showMessage(\ - self.trUtf8(u'Alert message created and delayed')) - return - self.generateAlert() - - def generateAlert(self): - log.debug(u'Generate Alert called') - if len(self.alertList) == 0: - return - text = self.alertList.pop(0) - alertTab = self.parent.settingsForm.AlertsTab - alertframe = \ - QtGui.QPixmap(self.screen[u'size'].width(), self.alertHeight) - alertframe.fill(QtCore.Qt.transparent) - painter = QtGui.QPainter(alertframe) - painter.fillRect(alertframe.rect(), QtCore.Qt.transparent) - painter.setRenderHint(QtGui.QPainter.Antialiasing) - painter.fillRect( - QtCore.QRect( - 0, 0, alertframe.rect().width(), - alertframe.rect().height()), - QtGui.QColor(alertTab.bg_color)) - font = QtGui.QFont() - font.setFamily(alertTab.font_face) - font.setBold(True) - font.setPointSize(40) - painter.setFont(font) - painter.setPen(QtGui.QColor(alertTab.font_color)) - x, y = (0, 0) - metrics = QtGui.QFontMetrics(font) - painter.drawText( - x, y + metrics.height() - metrics.descent() - 1, text) - painter.end() - self.display_alert.setPixmap(alertframe) - self.display_alert.setVisible(True) - # check to see if we have a timer running - if self.timer_id == 0: - self.timer_id = self.startTimer(int(alertTab.timeout) * 1000) - - def timerEvent(self, event): - if event.timerId() == self.timer_id: - self.display_alert.setPixmap(self.transparent) - self.killTimer(self.timer_id) - self.timer_id = 0 - self.generateAlert() - def onMediaQueue(self, message): log.debug(u'Queue new media message %s' % message) self.display_image.close() diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 36f427d13..2ec93ce79 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -29,7 +29,7 @@ import time from PyQt4 import QtCore, QtGui -from openlp.core.ui import AboutForm, SettingsForm, AlertForm, \ +from openlp.core.ui import AboutForm, SettingsForm, \ ServiceManager, ThemeManager, MainDisplay, SlideController, \ PluginForm, MediaDockManager from openlp.core.lib import RenderManager, PluginConfig, build_icon, \ @@ -227,13 +227,8 @@ class Ui_MainWindow(object): self.settingsmanager.showServiceManager) self.ViewServiceManagerItem.setIcon(ServiceManagerIcon) self.ViewServiceManagerItem.setObjectName(u'ViewServiceManagerItem') - self.ToolsAlertItem = QtGui.QAction(MainWindow) - AlertIcon = build_icon(u':/tools/tools_alert.png') - self.ToolsAlertItem.setIcon(AlertIcon) - self.ToolsAlertItem.setObjectName(u'ToolsAlertItem') self.PluginItem = QtGui.QAction(MainWindow) - #PluginIcon = build_icon(u':/tools/tools_alert.png') - self.PluginItem.setIcon(AlertIcon) + #self.PluginItem.setIcon(AlertIcon) self.PluginItem.setObjectName(u'PluginItem') self.HelpDocumentationItem = QtGui.QAction(MainWindow) ContentsIcon = build_icon(u':/system/system_help_contents.png') @@ -292,7 +287,6 @@ class Ui_MainWindow(object): self.OptionsMenu.addAction(self.OptionsViewMenu.menuAction()) self.OptionsMenu.addSeparator() self.OptionsMenu.addAction(self.OptionsSettingsItem) - self.ToolsMenu.addAction(self.ToolsAlertItem) self.ToolsMenu.addAction(self.PluginItem) self.ToolsMenu.addSeparator() self.ToolsMenu.addAction(self.ToolsAddToolItem) @@ -394,9 +388,6 @@ class Ui_MainWindow(object): self.action_Preview_Panel.setStatusTip( self.trUtf8('Toggle the visibility of the Preview Panel')) self.action_Preview_Panel.setShortcut(self.trUtf8('F11')) - self.ToolsAlertItem.setText(self.trUtf8('&Alert')) - self.ToolsAlertItem.setStatusTip(self.trUtf8('Show an alert message')) - self.ToolsAlertItem.setShortcut(self.trUtf8('F7')) self.PluginItem.setText(self.trUtf8('&Plugin List')) self.PluginItem.setStatusTip(self.trUtf8('List the Plugins')) self.PluginItem.setShortcut(self.trUtf8('Alt+F7')) @@ -440,7 +431,6 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): self.settingsmanager = SettingsManager(screens) self.generalConfig = PluginConfig(u'General') self.mainDisplay = MainDisplay(self, screens) - self.alertForm = AlertForm(self) self.aboutForm = AboutForm(self, applicationVersion) self.settingsForm = SettingsForm(self.screens, self, self) # Set up the path with plugins @@ -485,8 +475,6 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): self.action_Preview_Panel.setChecked) QtCore.QObject.connect(self.HelpAboutItem, QtCore.SIGNAL(u'triggered()'), self.onHelpAboutItemClicked) - QtCore.QObject.connect(self.ToolsAlertItem, - QtCore.SIGNAL(u'triggered()'), self.onToolsAlertItemClicked) QtCore.QObject.connect(self.PluginItem, QtCore.SIGNAL(u'triggered()'), self.onPluginItemClicked) QtCore.QObject.connect(self.OptionsSettingsItem, @@ -605,12 +593,6 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): self.aboutForm.applicationVersion = self.applicationVersion self.aboutForm.exec_() - def onToolsAlertItemClicked(self): - """ - Show the Alert form - """ - self.alertForm.exec_() - def onPluginItemClicked(self): """ Show the Plugin form diff --git a/openlp/core/ui/settingsform.py b/openlp/core/ui/settingsform.py index ed5bd9d76..c8989625d 100644 --- a/openlp/core/ui/settingsform.py +++ b/openlp/core/ui/settingsform.py @@ -27,7 +27,7 @@ import logging from PyQt4 import QtGui -from openlp.core.ui import GeneralTab, ThemesTab, AlertsTab +from openlp.core.ui import GeneralTab, ThemesTab from openlp.core.lib import Receiver from settingsdialog import Ui_SettingsDialog @@ -44,9 +44,6 @@ class SettingsForm(QtGui.QDialog, Ui_SettingsDialog): # Themes tab self.ThemesTab = ThemesTab(mainWindow) self.addTab(u'Themes', self.ThemesTab) - # Alert tab - self.AlertsTab = AlertsTab() - self.addTab(u'Alerts', self.AlertsTab) def addTab(self, name, tab): log.info(u'Adding %s tab' % tab.tabTitle) @@ -73,4 +70,4 @@ class SettingsForm(QtGui.QDialog, Ui_SettingsDialog): def postSetUp(self): for tab_index in range(0, self.SettingsTabWidget.count()): - self.SettingsTabWidget.widget(tab_index).postSetUp() \ No newline at end of file + self.SettingsTabWidget.widget(tab_index).postSetUp() diff --git a/openlp/plugins/alerts/__init__.py b/openlp/plugins/alerts/__init__.py new file mode 100644 index 000000000..a78b7d0d7 --- /dev/null +++ b/openlp/plugins/alerts/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard # +# --------------------------------------------------------------------------- # +# 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 # +############################################################################### diff --git a/openlp/plugins/alerts/alertsplugin.py b/openlp/plugins/alerts/alertsplugin.py new file mode 100644 index 000000000..f18f1b9b7 --- /dev/null +++ b/openlp/plugins/alerts/alertsplugin.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard # +# --------------------------------------------------------------------------- # +# 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 # +############################################################################### + +from datetime import datetime +import logging + +from PyQt4 import QtCore, QtGui + +from openlp.core.lib import Plugin, Receiver, str_to_bool, build_icon, PluginStatus +#from openlp.plugins.alerts.lib import alertsManager +from openlp.plugins.alerts.forms import AlertsTab, AlertForm +#from openlp.plugins.alerts.lib.models import alertsItem + +class alertsPlugin(Plugin): + global log + log = logging.getLogger(u'alertsPlugin') + log.info(u'alerts Plugin loaded') + + def __init__(self, plugin_helpers): + Plugin.__init__(self, u'alerts', u'1.9.1', plugin_helpers) + self.weight = -3 + self.icon = build_icon(u':/media/media_image.png') + self.alertsmanager = None + self.alertForm = AlertForm(self) + self.status = PluginStatus.Active + + def get_settings_tab(self): + return AlertsTab(self.name) + + def add_tools_menu_item(self, tools_menu): + """ + Give the alerts plugin the opportunity to add items to the + **Tools** menu. + + ``tools_menu`` + The actual **Tools** menu item, so that your actions can + use it as their parent. + """ + log.info(u'add tools menu') + self.toolsAlertItem = QtGui.QAction(tools_menu) + AlertIcon = build_icon(u':/tools/tools_alert.png') + self.toolsAlertItem.setIcon(AlertIcon) + self.toolsAlertItem.setObjectName(u'toolsAlertItem') + self.toolsAlertItem.setText(self.trUtf8('&Alert')) + self.toolsAlertItem.setStatusTip(self.trUtf8('Show an alert message')) + self.toolsAlertItem.setShortcut(self.trUtf8('F7')) + self.service_manager.parent.ToolsMenu.addAction(self.toolsAlertItem) + QtCore.QObject.connect(self.toolsAlertItem, + QtCore.SIGNAL(u'triggered()'), self.onAlertsTrigger) + self.toolsAlertItem.setVisible(False) + + def initialise(self): + log.info(u'alerts Initialising') + Plugin.initialise(self) + self.toolsAlertItem.setVisible(True) + + def finalise(self): + log.info(u'Plugin Finalise') + self.toolsAlertItem.setVisible(False) + #stop any events being processed + + def togglealertsState(self): + self.alertsActive = not self.alertsActive + self.config.set_config(u'active', self.alertsActive) + + def onAlertsTrigger(self): + self.alertForm.exec_() + + def onalertsReport(self): + self.alertsdetailform.initialise() + self.alertsdetailform.exec_() + + def about(self): + about_text = self.trUtf8('<b>Alerts Plugin</b><br>This plugin ' + 'controls the displaying of alerts on the presentations screen') + return about_text diff --git a/openlp/plugins/alerts/forms/__init__.py b/openlp/plugins/alerts/forms/__init__.py new file mode 100644 index 000000000..dba3abc93 --- /dev/null +++ b/openlp/plugins/alerts/forms/__init__.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard # +# --------------------------------------------------------------------------- # +# 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 # +############################################################################### + +from alertstab import AlertsTab +from alertform import AlertForm diff --git a/openlp/core/ui/alertform.py b/openlp/plugins/alerts/forms/alertform.py similarity index 98% rename from openlp/core/ui/alertform.py rename to openlp/plugins/alerts/forms/alertform.py index beae62a53..86724c99c 100644 --- a/openlp/core/ui/alertform.py +++ b/openlp/plugins/alerts/forms/alertform.py @@ -31,11 +31,11 @@ class AlertForm(QtGui.QDialog): global log log = logging.getLogger(u'AlertForm') - def __init__(self, parent=None): - QtGui.QDialog.__init__(self, parent) + def __init__(self, parent): + QtGui.QDialog.__init__(self, None) self.parent = parent self.setupUi(self) - log.debug(u'Defined') + log.debug(u'AlertForm Defined') def setupUi(self, AlertForm): AlertForm.setObjectName(u'AlertForm') @@ -85,9 +85,7 @@ class AlertForm(QtGui.QDialog): self.CancelButton.setObjectName(u'CancelButton') self.horizontalLayout.addWidget(self.CancelButton) self.AlertFormLayout.addWidget(self.ButtonBoxWidget) - self.retranslateUi(AlertForm) - QtCore.QObject.connect(self.CancelButton, QtCore.SIGNAL(u'clicked()'), AlertForm.close) QtCore.QObject.connect(self.DisplayButton, QtCore.SIGNAL(u'clicked()'), self.onDisplayClicked) QtCore.QMetaObject.connectSlotsByName(AlertForm) diff --git a/openlp/core/ui/alertstab.py b/openlp/plugins/alerts/forms/alertstab.py similarity index 98% rename from openlp/core/ui/alertstab.py rename to openlp/plugins/alerts/forms/alertstab.py index bf4b9a004..2bb9beed3 100644 --- a/openlp/core/ui/alertstab.py +++ b/openlp/plugins/alerts/forms/alertstab.py @@ -31,10 +31,8 @@ class AlertsTab(SettingsTab): """ AlertsTab is the alerts settings tab in the settings dialog. """ - def __init__(self): - SettingsTab.__init__(self, u'Alerts') - self.font_color = '#ffffff' - self.bg_color = '#660000' + def __init__(self, title, section=None): + SettingsTab.__init__(self, title, section) def setupUi(self): self.setObjectName(u'AlertsTab') @@ -159,7 +157,7 @@ class AlertsTab(SettingsTab): self.PreviewLayout.setMargin(8) self.PreviewLayout.setObjectName(u'PreviewLayout') self.FontPreview = QtGui.QLineEdit(self.PreviewGroupBox) - self.FontPreview.setMinimumSize(QtCore.QSize(280, 100)) + self.FontPreview.setFixedSize(QtCore.QSize(350, 100)) self.FontPreview.setReadOnly(True) self.FontPreview.setFocusPolicy(QtCore.Qt.NoFocus) self.FontPreview.setAlignment( diff --git a/openlp/plugins/alerts/lib/__init__.py b/openlp/plugins/alerts/lib/__init__.py new file mode 100644 index 000000000..bf7bc5074 --- /dev/null +++ b/openlp/plugins/alerts/lib/__init__.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard # +# --------------------------------------------------------------------------- # +# 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 # +############################################################################### +from alertmanager import AlertManager diff --git a/openlp/plugins/alerts/lib/alertmanager.py b/openlp/plugins/alerts/lib/alertmanager.py new file mode 100644 index 000000000..34dc2622c --- /dev/null +++ b/openlp/plugins/alerts/lib/alertmanager.py @@ -0,0 +1,75 @@ + +import logging + +from PyQt4 import QtCore, QtGui + +from openlp.core.lib import str_to_bool, Receiver +from openlp.core.lib import SettingsTab + +class AlertManager(self): + """ + BiblesTab is the Bibles settings tab in the settings dialog. + """ + global log + log = logging.getLogger(u'AlertManager') + log.info(u'Alert Manager loaded') + + def __init__(self): + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'alert_text'), self.displayAlert) + + def displayAlert(self, text=u''): + """ + Called from the Alert Tab to display an alert + + ``text`` + display text + """ + log.debug(u'display alert called %s' % text) + self.parent.StatusBar.showMessage(self.trUtf8(u'')) + self.alertList.append(text) + if self.timer_id != 0 or self.mediaLoaded: + self.parent.StatusBar.showMessage(\ + self.trUtf8(u'Alert message created and delayed')) + return + self.generateAlert() + + def generateAlert(self): + log.debug(u'Generate Alert called') + if len(self.alertList) == 0: + return + text = self.alertList.pop(0) + alertTab = self.parent.settingsForm.AlertsTab + alertframe = \ + QtGui.QPixmap(self.screen[u'size'].width(), self.alertHeight) + alertframe.fill(QtCore.Qt.transparent) + painter = QtGui.QPainter(alertframe) + painter.fillRect(alertframe.rect(), QtCore.Qt.transparent) + painter.setRenderHint(QtGui.QPainter.Antialiasing) + painter.fillRect( + QtCore.QRect( + 0, 0, alertframe.rect().width(), + alertframe.rect().height()), + QtGui.QColor(alertTab.bg_color)) + font = QtGui.QFont() + font.setFamily(alertTab.font_face) + font.setBold(True) + font.setPointSize(alertTab.font_size) + painter.setFont(font) + painter.setPen(QtGui.QColor(alertTab.font_color)) + x, y = (0, 0) + metrics = QtGui.QFontMetrics(font) + painter.drawText( + x, y + metrics.height() - metrics.descent() - 1, text) + painter.end() + self.display_alert.setPixmap(alertframe) + # check to see if we have a timer running + if self.timer_id == 0: + self.timer_id = self.startTimer(int(alertTab.timeout) * 1000) + + def timerEvent(self, event): + if event.timerId() == self.timer_id: + self.display_alert.setPixmap(self.transparent) + self.killTimer(self.timer_id) + self.timer_id = 0 + self.generateAlert() diff --git a/openlp/plugins/songusage/forms/songusagedeleteform.py b/openlp/plugins/songusage/forms/songusagedeleteform.py index 98faf26ad..26fe2b7e2 100644 --- a/openlp/plugins/songusage/forms/songusagedeleteform.py +++ b/openlp/plugins/songusage/forms/songusagedeleteform.py @@ -50,7 +50,6 @@ class SongUsageDeleteForm(QtGui.QDialog, Ui_SongUsageDeleteDialog): QtGui.QMessageBox.Cancel), QtGui.QMessageBox.Cancel) if ret == QtGui.QMessageBox.Ok: - qDeleteDate = self.DeleteCalendar.selectedDate() - deleteDate = date(qDeleteDate.year(), qDeleteDate.month(), qDeleteDate.day()) + deleteDate = self.DeleteCalendar.selectedDate().toPyDate() self.songusagemanager.delete_to_date(deleteDate) self.close() diff --git a/openlp/plugins/songusage/forms/songusagedetaildialog.py b/openlp/plugins/songusage/forms/songusagedetaildialog.py index a4bb2e906..63866d1fd 100644 --- a/openlp/plugins/songusage/forms/songusagedetaildialog.py +++ b/openlp/plugins/songusage/forms/songusagedetaildialog.py @@ -9,7 +9,7 @@ from PyQt4 import QtCore, QtGui -class Ui_AuditDetailDialog(object): +class Ui_SongUsageDetailDialog(object): def setupUi(self, AuditDetailDialog): AuditDetailDialog.setObjectName("AuditDetailDialog") AuditDetailDialog.resize(609, 413) diff --git a/openlp/plugins/songusage/forms/songusagedetailform.py b/openlp/plugins/songusage/forms/songusagedetailform.py index ead6b5166..df749d8c0 100644 --- a/openlp/plugins/songusage/forms/songusagedetailform.py +++ b/openlp/plugins/songusage/forms/songusagedetailform.py @@ -45,33 +45,14 @@ class SongUsageDetailForm(QtGui.QDialog, Ui_SongUsageDetailDialog): self.setupUi(self) def initialise(self): - self.FirstCheckBox.setCheckState( - int(self.parent.config.get_config(u'first service', QtCore.Qt.Checked))) - self.SecondCheckBox.setCheckState( - int(self.parent.config.get_config(u'second service', QtCore.Qt.Checked))) - self.ThirdCheckBox.setCheckState( - int(self.parent.config.get_config(u'third service', QtCore.Qt.Checked))) year = QtCore.QDate().currentDate().year() if QtCore.QDate().currentDate().month() < 9: year -= 1 toDate = QtCore.QDate(year, 8, 31) fromDate = QtCore.QDate(year - 1, 9, 1) - self.FromDateEdit.setDate(fromDate) - self.ToDateEdit.setDate(toDate) + self.FromDate.setSelectedDate(fromDate) + self.ToDate.setSelectedDate(toDate) self.FileLineEdit.setText(self.parent.config.get_last_dir(1)) - self.resetWindow() - - def changeFirstService(self, value): - self.parent.config.set_config(u'first service', value) - self.resetWindow() - - def changeSecondService(self, value): - self.parent.config.set_config(u'second service', value) - self.resetWindow() - - def changeThirdService(self, value): - self.parent.config.set_config(u'third service', value) - self.resetWindow() def defineOutputLocation(self): path = QtGui.QFileDialog.getExistingDirectory(self, @@ -82,39 +63,14 @@ class SongUsageDetailForm(QtGui.QDialog, Ui_SongUsageDetailDialog): self.parent.config.set_last_dir(path, 1) self.FileLineEdit.setText(path) - def resetWindow(self): - if self.FirstCheckBox.checkState() == QtCore.Qt.Unchecked: - self.FirstFromTimeEdit.setEnabled(False) - self.FirstToTimeEdit.setEnabled(False) - else: - self.FirstFromTimeEdit.setEnabled(True) - self.FirstToTimeEdit.setEnabled(True) - if self.SecondCheckBox.checkState() == QtCore.Qt.Unchecked: - self.SecondFromTimeEdit.setEnabled(False) - self.SecondToTimeEdit.setEnabled(False) - else: - self.SecondFromTimeEdit.setEnabled(True) - self.SecondToTimeEdit.setEnabled(True) - if self.ThirdCheckBox.checkState() == QtCore.Qt.Unchecked: - self.ThirdFromTimeEdit.setEnabled(False) - self.ThirdToTimeEdit.setEnabled(False) - else: - self.ThirdFromTimeEdit.setEnabled(True) - self.ThirdToTimeEdit.setEnabled(True) - def accept(self): - if self.DetailedReport.isChecked(): - self.detailedReport() - else: - self.summaryReport() - self.close() - - def detailedReport(self): log.debug(u'Detailed report generated') filename = u'usage_detail_%s_%s.txt' % \ - (self.FromDateEdit.date().toString(u'ddMMyyyy'), - self.ToDateEdit.date().toString(u'ddMMyyyy')) - usage = self.parent.songusagemanager.get_all_songusage() + (self.FromDate.selectedDate().toString(u'ddMMyyyy'), + self.ToDate.selectedDate().toString(u'ddMMyyyy')) + usage = self.parent.songusagemanager.get_all_songusage(\ + self.FromDate.selectedDate(), \ + self.ToDate.selectedDate()) outname = os.path.join(unicode(self.FileLineEdit.text()), filename) file = None try: @@ -130,8 +86,3 @@ class SongUsageDetailForm(QtGui.QDialog, Ui_SongUsageDetailDialog): if file: file.close() - def summaryReport(self): - log.debug(u'Summary report generated') - filename = u'audit_sum_%s_%s.txt' % \ - (self.FromDateEdit.date().toString(u'ddMMyyyy'), - self.ToDateEdit.date().toString(u'ddMMyyyy')) diff --git a/openlp/plugins/songusage/lib/manager.py b/openlp/plugins/songusage/lib/manager.py index cf286d37f..0b29d6c98 100644 --- a/openlp/plugins/songusage/lib/manager.py +++ b/openlp/plugins/songusage/lib/manager.py @@ -60,12 +60,14 @@ class SongUsageManager(): log.debug(u'SongUsage Initialised') - def get_all_songusage(self): + def get_all_songusage(self, start_date, end_date): """ Returns the details of SongUsage """ - return self.session.query(SongUsageItem).\ - order_by(SongUsageItem.usagedate, SongUsageItem.usagetime ).all() + return self.session.query(SongUsageItem) \ + .filter(SongUsageItem.usagedate >= start_date.toPyDate()) \ + .filter(SongUsageItem.usagedate < end_date.toPyDate()) \ + .order_by(SongUsageItem.usagedate, SongUsageItem.usagetime ).all() def insert_songusage(self, songusageitem): """ @@ -133,4 +135,4 @@ class SongUsageManager(): except: self.session.rollback() log.exception(u'Failed to delete all Song Usage items to %s' % date) - return False \ No newline at end of file + return False From 47a61a65812069f8014fbc0f00a290ef11f6e88a Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Fri, 12 Feb 2010 18:42:09 +0000 Subject: [PATCH 068/164] Finished moving alert code to plugin structure --- openlp/core/lib/plugin.py | 3 ++- openlp/core/lib/pluginmanager.py | 2 +- openlp/core/ui/maindisplay.py | 2 -- openlp/core/ui/mainwindow.py | 1 + openlp/plugins/alerts/alertsplugin.py | 7 ++++--- openlp/plugins/alerts/forms/alertform.py | 3 ++- openlp/plugins/alerts/lib/__init__.py | 2 +- .../lib/{alertmanager.py => alertsmanager.py} | 21 ++++++++++++------- 8 files changed, 24 insertions(+), 17 deletions(-) rename openlp/plugins/alerts/lib/{alertmanager.py => alertsmanager.py} (76%) diff --git a/openlp/core/lib/plugin.py b/openlp/core/lib/plugin.py index 598c594fd..ca291d578 100644 --- a/openlp/core/lib/plugin.py +++ b/openlp/core/lib/plugin.py @@ -127,6 +127,7 @@ class Plugin(QtCore.QObject): self.service_manager = plugin_helpers[u'service'] self.settings = plugin_helpers[u'settings'] self.mediadock = plugin_helpers[u'toolbox'] + self.maindisplay = plugin_helpers[u'maindisplay'] QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'%s_add_service_item' % self.name), self.process_add_service_event) @@ -252,4 +253,4 @@ class Plugin(QtCore.QObject): if self.media_item: self.mediadock.insert_dock(self.media_item, self.icon, self.weight) if self.settings_tab: - self.settings.insertTab(self.settings_tab, self.weight) \ No newline at end of file + self.settings.insertTab(self.settings_tab, self.weight) diff --git a/openlp/core/lib/pluginmanager.py b/openlp/core/lib/pluginmanager.py index 4d4da144a..b06f23953 100644 --- a/openlp/core/lib/pluginmanager.py +++ b/openlp/core/lib/pluginmanager.py @@ -101,7 +101,7 @@ class PluginManager(object): log.debug(u'Loaded plugin %s with helpers', unicode(p)) plugin_objects.append(plugin) except TypeError: - log.error(u'loaded plugin %s has no helpers', unicode(p)) + log.exception(u'loaded plugin %s has no helpers', unicode(p)) plugins_list = sorted(plugin_objects, self.order_by_weight) for plugin in plugins_list: if plugin.check_pre_conditions(): diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index 43cb0d848..7846b58d5 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -114,11 +114,9 @@ class MainDisplay(DisplayWidget): self.displayBlank = False self.blankFrame = None self.frame = None - self.timer_id = 0 self.firstTime = True self.mediaLoaded = False self.hasTransition = False - self.alertList = [] self.mediaBackground = False QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'live_slide_hide'), self.hideDisplay) diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 2ec93ce79..df66d5e97 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -510,6 +510,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): self.plugin_helpers[u'service'] = self.ServiceManagerContents self.plugin_helpers[u'settings'] = self.settingsForm self.plugin_helpers[u'toolbox'] = self.mediaDockManager + self.plugin_helpers[u'maindisplay'] = self.mainDisplay self.plugin_manager.find_plugins(pluginpath, self.plugin_helpers) # hook methods have to happen after find_plugins. Find plugins needs # the controllers hence the hooks have moved from setupUI() to here diff --git a/openlp/plugins/alerts/alertsplugin.py b/openlp/plugins/alerts/alertsplugin.py index f18f1b9b7..1163f70ee 100644 --- a/openlp/plugins/alerts/alertsplugin.py +++ b/openlp/plugins/alerts/alertsplugin.py @@ -29,7 +29,7 @@ import logging from PyQt4 import QtCore, QtGui from openlp.core.lib import Plugin, Receiver, str_to_bool, build_icon, PluginStatus -#from openlp.plugins.alerts.lib import alertsManager +from openlp.plugins.alerts.lib import AlertsManager from openlp.plugins.alerts.forms import AlertsTab, AlertForm #from openlp.plugins.alerts.lib.models import alertsItem @@ -42,12 +42,13 @@ class alertsPlugin(Plugin): Plugin.__init__(self, u'alerts', u'1.9.1', plugin_helpers) self.weight = -3 self.icon = build_icon(u':/media/media_image.png') - self.alertsmanager = None + self.alertsmanager = AlertsManager(self) self.alertForm = AlertForm(self) self.status = PluginStatus.Active def get_settings_tab(self): - return AlertsTab(self.name) + self.alertsTab = AlertsTab(self.name) + return self.alertsTab def add_tools_menu_item(self, tools_menu): """ diff --git a/openlp/plugins/alerts/forms/alertform.py b/openlp/plugins/alerts/forms/alertform.py index 86724c99c..68549eaee 100644 --- a/openlp/plugins/alerts/forms/alertform.py +++ b/openlp/plugins/alerts/forms/alertform.py @@ -26,6 +26,7 @@ import logging from PyQt4 import QtCore, QtGui from openlp.core.lib import build_icon +from openlp.plugins.alerts.lib import alertmanager class AlertForm(QtGui.QDialog): global log @@ -97,4 +98,4 @@ class AlertForm(QtGui.QDialog): self.CancelButton.setText(self.trUtf8('Cancel')) def onDisplayClicked(self): - self.parent.mainDisplay.displayAlert(unicode(self.AlertEntryEditItem.text())) + self.parent.alertsmanager.displayAlert(unicode(self.AlertEntryEditItem.text())) diff --git a/openlp/plugins/alerts/lib/__init__.py b/openlp/plugins/alerts/lib/__init__.py index bf7bc5074..f6fb2c0d7 100644 --- a/openlp/plugins/alerts/lib/__init__.py +++ b/openlp/plugins/alerts/lib/__init__.py @@ -22,4 +22,4 @@ # with this program; if not, write to the Free Software Foundation, Inc., 59 # # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### -from alertmanager import AlertManager +from alertsmanager import AlertsManager diff --git a/openlp/plugins/alerts/lib/alertmanager.py b/openlp/plugins/alerts/lib/alertsmanager.py similarity index 76% rename from openlp/plugins/alerts/lib/alertmanager.py rename to openlp/plugins/alerts/lib/alertsmanager.py index 34dc2622c..977614b86 100644 --- a/openlp/plugins/alerts/lib/alertmanager.py +++ b/openlp/plugins/alerts/lib/alertsmanager.py @@ -6,7 +6,7 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import str_to_bool, Receiver from openlp.core.lib import SettingsTab -class AlertManager(self): +class AlertsManager(QtCore.QObject): """ BiblesTab is the Bibles settings tab in the settings dialog. """ @@ -14,7 +14,11 @@ class AlertManager(self): log = logging.getLogger(u'AlertManager') log.info(u'Alert Manager loaded') - def __init__(self): + def __init__(self, parent): + QtCore.QObject.__init__(self) + self.parent = parent + self.timer_id = 0 + self.alertList = [] QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'alert_text'), self.displayAlert) @@ -26,9 +30,10 @@ class AlertManager(self): display text """ log.debug(u'display alert called %s' % text) - self.parent.StatusBar.showMessage(self.trUtf8(u'')) + self.screen = self.parent.maindisplay.screen + self.parent.maindisplay.parent.StatusBar.showMessage(self.trUtf8(u'')) self.alertList.append(text) - if self.timer_id != 0 or self.mediaLoaded: + if self.timer_id != 0 or self.parent.maindisplay.mediaLoaded: self.parent.StatusBar.showMessage(\ self.trUtf8(u'Alert message created and delayed')) return @@ -39,9 +44,9 @@ class AlertManager(self): if len(self.alertList) == 0: return text = self.alertList.pop(0) - alertTab = self.parent.settingsForm.AlertsTab + alertTab = self.parent.alertsTab alertframe = \ - QtGui.QPixmap(self.screen[u'size'].width(), self.alertHeight) + QtGui.QPixmap(self.screen[u'size'].width(), self.parent.maindisplay.alertHeight) alertframe.fill(QtCore.Qt.transparent) painter = QtGui.QPainter(alertframe) painter.fillRect(alertframe.rect(), QtCore.Qt.transparent) @@ -62,14 +67,14 @@ class AlertManager(self): painter.drawText( x, y + metrics.height() - metrics.descent() - 1, text) painter.end() - self.display_alert.setPixmap(alertframe) + self.parent.maindisplay.display_alert.setPixmap(alertframe) # check to see if we have a timer running if self.timer_id == 0: self.timer_id = self.startTimer(int(alertTab.timeout) * 1000) def timerEvent(self, event): if event.timerId() == self.timer_id: - self.display_alert.setPixmap(self.transparent) + self.parent.maindisplay.display_alert.setPixmap(self.parent.maindisplay.transparent) self.killTimer(self.timer_id) self.timer_id = 0 self.generateAlert() From 973d5e3dd87daabeb6827e02ecd3ffeed5990b30 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sat, 13 Feb 2010 07:30:00 +0000 Subject: [PATCH 069/164] Alerts move to top and bottom --- openlp/core/ui/maindisplay.py | 6 +++ openlp/plugins/alerts/lib/alertsmanager.py | 45 ++++++++++++++-------- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index 7846b58d5..4f46ccbe4 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -190,6 +190,7 @@ class MainDisplay(DisplayWidget): else: self.setVisible(False) self.primary = True + Receiver.send_message(u'screen_changed') def resetDisplay(self): if self.primary: @@ -214,6 +215,11 @@ class MainDisplay(DisplayWidget): self.screen[u'size'].height() ) self.display_image.setPixmap(QtGui.QPixmap.fromImage(frame)) + def setAlertSize(self, top, height): + self.display_alert.setGeometry( + QtCore.QRect(0, top, + self.screen[u'size'].width(), height)) + def addAlertImage(self, frame, blank=False): if blank: self.display_alert.setPixmap(self.transparent) diff --git a/openlp/plugins/alerts/lib/alertsmanager.py b/openlp/plugins/alerts/lib/alertsmanager.py index 977614b86..1f4369588 100644 --- a/openlp/plugins/alerts/lib/alertsmanager.py +++ b/openlp/plugins/alerts/lib/alertsmanager.py @@ -21,6 +21,27 @@ class AlertsManager(QtCore.QObject): self.alertList = [] QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'alert_text'), self.displayAlert) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'screen_changed'), self.screenChanged) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'config_updated'), self.screenChanged) + + def screenChanged(self): + log.debug(u'screen changed') + self.screen = self.parent.maindisplay.screen + self.alertTab = self.parent.alertsTab + self.font = QtGui.QFont() + self.font.setFamily(self.alertTab.font_face) + self.font.setBold(True) + self.font.setPointSize(self.alertTab.font_size) + self.metrics = QtGui.QFontMetrics(self.font) + self.alertHeight = self.metrics.height() + 4 + if self.alertTab.location == 0: + self.alertScreenPosition = 0 + else: + self.alertScreenPosition = self.screen[u'size'].height() - self.alertHeight + self.alertHeight = self.screen[u'size'].height() - self.alertScreenPosition + self.parent.maindisplay.setAlertSize(self.alertScreenPosition, self.alertHeight) def displayAlert(self, text=u''): """ @@ -30,11 +51,10 @@ class AlertsManager(QtCore.QObject): display text """ log.debug(u'display alert called %s' % text) - self.screen = self.parent.maindisplay.screen self.parent.maindisplay.parent.StatusBar.showMessage(self.trUtf8(u'')) self.alertList.append(text) if self.timer_id != 0 or self.parent.maindisplay.mediaLoaded: - self.parent.StatusBar.showMessage(\ + self.parent.maindisplay.parent.StatusBar.showMessage(\ self.trUtf8(u'Alert message created and delayed')) return self.generateAlert() @@ -46,7 +66,7 @@ class AlertsManager(QtCore.QObject): text = self.alertList.pop(0) alertTab = self.parent.alertsTab alertframe = \ - QtGui.QPixmap(self.screen[u'size'].width(), self.parent.maindisplay.alertHeight) + QtGui.QPixmap(self.screen[u'size'].width(), self.alertHeight) alertframe.fill(QtCore.Qt.transparent) painter = QtGui.QPainter(alertframe) painter.fillRect(alertframe.rect(), QtCore.Qt.transparent) @@ -55,26 +75,21 @@ class AlertsManager(QtCore.QObject): QtCore.QRect( 0, 0, alertframe.rect().width(), alertframe.rect().height()), - QtGui.QColor(alertTab.bg_color)) - font = QtGui.QFont() - font.setFamily(alertTab.font_face) - font.setBold(True) - font.setPointSize(alertTab.font_size) - painter.setFont(font) - painter.setPen(QtGui.QColor(alertTab.font_color)) - x, y = (0, 0) - metrics = QtGui.QFontMetrics(font) + QtGui.QColor(self.alertTab.bg_color)) + painter.setFont(self.font) + painter.setPen(QtGui.QColor(self.alertTab.font_color)) + x, y = (0, 2) painter.drawText( - x, y + metrics.height() - metrics.descent() - 1, text) + x, y + self.metrics.height() - self.metrics.descent() - 1, text) painter.end() - self.parent.maindisplay.display_alert.setPixmap(alertframe) + self.parent.maindisplay.addAlertImage(alertframe) # check to see if we have a timer running if self.timer_id == 0: self.timer_id = self.startTimer(int(alertTab.timeout) * 1000) def timerEvent(self, event): if event.timerId() == self.timer_id: - self.parent.maindisplay.display_alert.setPixmap(self.parent.maindisplay.transparent) + self.parent.maindisplay.addAlertImage(None, True) self.killTimer(self.timer_id) self.timer_id = 0 self.generateAlert() From 9c97660b73c79cf3e064e14c9164ffffc16249e5 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sat, 13 Feb 2010 07:37:03 +0000 Subject: [PATCH 070/164] Alerts now move between top and bottom --- openlp/core/ui/maindisplay.py | 6 +----- openlp/plugins/alerts/forms/alertstab.py | 5 +++++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index 4f46ccbe4..1ba31ea62 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -143,11 +143,7 @@ class MainDisplay(DisplayWidget): self.screen = self.screens.current #Sort out screen locations and sizes self.setGeometry(self.screen[u'size']) - self.alertScreenPosition = self.screen[u'size'].height() * 0.9 - self.alertHeight = self.screen[u'size'].height() - self.alertScreenPosition - self.display_alert.setGeometry( - QtCore.QRect(0, self.alertScreenPosition, - self.screen[u'size'].width(),self.alertHeight)) + self.display_alert.setGeometry(self.screen[u'size']) self.video.setGeometry(self.screen[u'size']) self.display_image.resize(self.screen[u'size'].width(), self.screen[u'size'].height()) diff --git a/openlp/plugins/alerts/forms/alertstab.py b/openlp/plugins/alerts/forms/alertstab.py index 2bb9beed3..a6653d152 100644 --- a/openlp/plugins/alerts/forms/alertstab.py +++ b/openlp/plugins/alerts/forms/alertstab.py @@ -176,6 +176,8 @@ class AlertsTab(SettingsTab): QtCore.SIGNAL(u'pressed()'), self.onFontColorButtonClicked) QtCore.QObject.connect(self.FontComboBox, QtCore.SIGNAL(u'activated(int)'), self.onFontComboBoxClicked) + QtCore.QObject.connect(self.LocationComboBox, + QtCore.SIGNAL(u'activated(int)'), self.onLocationComboBoxClicked) QtCore.QObject.connect(self.TimeoutSpinBox, QtCore.SIGNAL(u'valueChanged(int)'), self.onTimeoutSpinBoxChanged) QtCore.QObject.connect(self.FontSizeSpinBox, @@ -206,6 +208,9 @@ class AlertsTab(SettingsTab): def onFontComboBoxClicked(self): self.updateDisplay() + def onLocationComboBoxClicked(self, location): + self.location = location + def onFontColorButtonClicked(self): self.font_color = QtGui.QColorDialog.getColor( QtGui.QColor(self.font_color), self).name() From acc97f3c79ea91b82cde108d983eb862307aaf96 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sun, 14 Feb 2010 13:27:32 +0000 Subject: [PATCH 071/164] Alerts now have history --- openlp/plugins/alerts/alertsplugin.py | 7 +- openlp/plugins/alerts/forms/alertdialog.py | 70 +++++++++ .../plugins/alerts/forms/alerteditdialog.py | 63 ++++++++ openlp/plugins/alerts/forms/alertform.py | 141 +++++++++--------- openlp/plugins/alerts/forms/alertstab.py | 52 ++++++- openlp/plugins/alerts/lib/__init__.py | 1 + openlp/plugins/alerts/lib/classes.py | 46 ++++++ openlp/plugins/alerts/lib/manager.py | 108 ++++++++++++++ openlp/plugins/alerts/lib/meta.py | 38 +++++ openlp/plugins/alerts/lib/models.py | 39 +++++ openlp/plugins/alerts/lib/tables.py | 33 ++++ openlp/plugins/bibles/lib/biblestab.py | 5 +- openlp/plugins/custom/lib/tables.py | 4 +- resources/forms/alertdialog.ui | 116 ++++++++++++++ resources/forms/alerteditdialog.ui | 94 ++++++++++++ resources/forms/alertform.ui | 133 ----------------- 16 files changed, 738 insertions(+), 212 deletions(-) create mode 100644 openlp/plugins/alerts/forms/alertdialog.py create mode 100644 openlp/plugins/alerts/forms/alerteditdialog.py create mode 100644 openlp/plugins/alerts/lib/classes.py create mode 100644 openlp/plugins/alerts/lib/manager.py create mode 100644 openlp/plugins/alerts/lib/meta.py create mode 100644 openlp/plugins/alerts/lib/models.py create mode 100644 openlp/plugins/alerts/lib/tables.py create mode 100644 resources/forms/alertdialog.ui create mode 100644 resources/forms/alerteditdialog.ui delete mode 100644 resources/forms/alertform.ui diff --git a/openlp/plugins/alerts/alertsplugin.py b/openlp/plugins/alerts/alertsplugin.py index 1163f70ee..7214618d7 100644 --- a/openlp/plugins/alerts/alertsplugin.py +++ b/openlp/plugins/alerts/alertsplugin.py @@ -29,9 +29,8 @@ import logging from PyQt4 import QtCore, QtGui from openlp.core.lib import Plugin, Receiver, str_to_bool, build_icon, PluginStatus -from openlp.plugins.alerts.lib import AlertsManager +from openlp.plugins.alerts.lib import AlertsManager, DBManager from openlp.plugins.alerts.forms import AlertsTab, AlertForm -#from openlp.plugins.alerts.lib.models import alertsItem class alertsPlugin(Plugin): global log @@ -43,7 +42,8 @@ class alertsPlugin(Plugin): self.weight = -3 self.icon = build_icon(u':/media/media_image.png') self.alertsmanager = AlertsManager(self) - self.alertForm = AlertForm(self) + self.manager = DBManager(self.config) + self.alertForm = AlertForm(self.manager, self) self.status = PluginStatus.Active def get_settings_tab(self): @@ -87,6 +87,7 @@ class alertsPlugin(Plugin): self.config.set_config(u'active', self.alertsActive) def onAlertsTrigger(self): + self.alertForm.loadList() self.alertForm.exec_() def onalertsReport(self): diff --git a/openlp/plugins/alerts/forms/alertdialog.py b/openlp/plugins/alerts/forms/alertdialog.py new file mode 100644 index 000000000..4756a318f --- /dev/null +++ b/openlp/plugins/alerts/forms/alertdialog.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'alertform.ui' +# +# Created: Sat Feb 13 08:19:51 2010 +# by: PyQt4 UI code generator 4.6.2 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +class Ui_AlertDialog(object): + def setupUi(self, AlertForm): + AlertForm.setObjectName("AlertDialog") + AlertForm.resize(430, 320) + icon = QtGui.QIcon() + icon.addPixmap(QtGui.QPixmap(":/icon/openlp.org-icon-32.bmp"), QtGui.QIcon.Normal, QtGui.QIcon.Off) + AlertForm.setWindowIcon(icon) + self.AlertFormLayout = QtGui.QVBoxLayout(AlertForm) + self.AlertFormLayout.setSpacing(8) + self.AlertFormLayout.setMargin(8) + self.AlertFormLayout.setObjectName("AlertFormLayout") + self.AlertEntryWidget = QtGui.QWidget(AlertForm) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.AlertEntryWidget.sizePolicy().hasHeightForWidth()) + self.AlertEntryWidget.setSizePolicy(sizePolicy) + self.AlertEntryWidget.setObjectName("AlertEntryWidget") + self.verticalLayout_2 = QtGui.QVBoxLayout(self.AlertEntryWidget) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.verticalLayout = QtGui.QVBoxLayout() + self.verticalLayout.setObjectName("verticalLayout") + self.AlertEntryLabel = QtGui.QLabel(self.AlertEntryWidget) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.AlertEntryLabel.sizePolicy().hasHeightForWidth()) + self.AlertEntryLabel.setSizePolicy(sizePolicy) + self.AlertEntryLabel.setObjectName("AlertEntryLabel") + self.verticalLayout.addWidget(self.AlertEntryLabel) + self.AlertEntryEditItem = QtGui.QLineEdit(self.AlertEntryWidget) + self.AlertEntryEditItem.setObjectName("AlertEntryEditItem") + self.verticalLayout.addWidget(self.AlertEntryEditItem) + self.AlertListWidget = QtGui.QListWidget(self.AlertEntryWidget) + self.AlertListWidget.setObjectName("AlertListWidget") + self.verticalLayout.addWidget(self.AlertListWidget) + self.verticalLayout_2.addLayout(self.verticalLayout) + self.horizontalLayout = QtGui.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + spacerItem = QtGui.QSpacerItem(181, 38, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem) + self.DisplayButton = QtGui.QPushButton(self.AlertEntryWidget) + self.DisplayButton.setObjectName("DisplayButton") + self.horizontalLayout.addWidget(self.DisplayButton) + self.CancelButton = QtGui.QPushButton(self.AlertEntryWidget) + self.CancelButton.setObjectName("CancelButton") + self.horizontalLayout.addWidget(self.CancelButton) + self.verticalLayout_2.addLayout(self.horizontalLayout) + self.AlertFormLayout.addWidget(self.AlertEntryWidget) + + self.retranslateUi(AlertForm) + QtCore.QObject.connect(self.CancelButton, QtCore.SIGNAL("clicked()"), AlertForm.close) + QtCore.QMetaObject.connectSlotsByName(AlertForm) + + def retranslateUi(self, AlertForm): + AlertForm.setWindowTitle(QtGui.QApplication.translate("AlertForm", "Alert Message", None, QtGui.QApplication.UnicodeUTF8)) + self.AlertEntryLabel.setText(QtGui.QApplication.translate("AlertForm", "Alert Text:", None, QtGui.QApplication.UnicodeUTF8)) + self.DisplayButton.setText(QtGui.QApplication.translate("AlertForm", "Display", None, QtGui.QApplication.UnicodeUTF8)) + self.CancelButton.setText(QtGui.QApplication.translate("AlertForm", "Cancel", None, QtGui.QApplication.UnicodeUTF8)) diff --git a/openlp/plugins/alerts/forms/alerteditdialog.py b/openlp/plugins/alerts/forms/alerteditdialog.py new file mode 100644 index 000000000..53985d82b --- /dev/null +++ b/openlp/plugins/alerts/forms/alerteditdialog.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'alerteditdialog.ui' +# +# Created: Sat Feb 13 08:20:09 2010 +# by: PyQt4 UI code generator 4.6.2 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +class Ui_AlertList(object): + def setupUi(self, AlertList): + AlertList.setObjectName("AlertList") + AlertList.resize(400, 300) + self.buttonBox = QtGui.QDialogButtonBox(AlertList) + self.buttonBox.setGeometry(QtCore.QRect(220, 270, 173, 27)) + self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel) + self.buttonBox.setObjectName("buttonBox") + self.widget = QtGui.QWidget(AlertList) + self.widget.setGeometry(QtCore.QRect(20, 10, 361, 251)) + self.widget.setObjectName("widget") + self.verticalLayout_2 = QtGui.QVBoxLayout(self.widget) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.horizontalLayout_2 = QtGui.QHBoxLayout() + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.lineEdit = QtGui.QLineEdit(self.widget) + self.lineEdit.setObjectName("lineEdit") + self.horizontalLayout_2.addWidget(self.lineEdit) + self.SaveButton = QtGui.QPushButton(self.widget) + self.SaveButton.setObjectName("SaveButton") + self.horizontalLayout_2.addWidget(self.SaveButton) + self.verticalLayout_2.addLayout(self.horizontalLayout_2) + self.horizontalLayout = QtGui.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + self.listWidget = QtGui.QListWidget(self.widget) + self.listWidget.setAlternatingRowColors(True) + self.listWidget.setObjectName("listWidget") + self.horizontalLayout.addWidget(self.listWidget) + self.verticalLayout = QtGui.QVBoxLayout() + self.verticalLayout.setObjectName("verticalLayout") + self.AddButton = QtGui.QPushButton(self.widget) + self.AddButton.setObjectName("AddButton") + self.verticalLayout.addWidget(self.AddButton) + self.EdirButton = QtGui.QPushButton(self.widget) + self.EdirButton.setObjectName("EdirButton") + self.verticalLayout.addWidget(self.EdirButton) + self.DeleteButton = QtGui.QPushButton(self.widget) + self.DeleteButton.setObjectName("DeleteButton") + self.verticalLayout.addWidget(self.DeleteButton) + self.horizontalLayout.addLayout(self.verticalLayout) + self.verticalLayout_2.addLayout(self.horizontalLayout) + + self.retranslateUi(AlertList) + QtCore.QMetaObject.connectSlotsByName(AlertList) + + def retranslateUi(self, AlertList): + AlertList.setWindowTitle(QtGui.QApplication.translate("AlertList", "Form", None, QtGui.QApplication.UnicodeUTF8)) + self.SaveButton.setText(QtGui.QApplication.translate("AlertList", "Save", None, QtGui.QApplication.UnicodeUTF8)) + self.AddButton.setText(QtGui.QApplication.translate("AlertList", "Add", None, QtGui.QApplication.UnicodeUTF8)) + self.EdirButton.setText(QtGui.QApplication.translate("AlertList", "Edit", None, QtGui.QApplication.UnicodeUTF8)) + self.DeleteButton.setText(QtGui.QApplication.translate("AlertList", "Delete", None, QtGui.QApplication.UnicodeUTF8)) + diff --git a/openlp/plugins/alerts/forms/alertform.py b/openlp/plugins/alerts/forms/alertform.py index 68549eaee..882da7cfa 100644 --- a/openlp/plugins/alerts/forms/alertform.py +++ b/openlp/plugins/alerts/forms/alertform.py @@ -23,79 +23,82 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### -import logging -from PyQt4 import QtCore, QtGui -from openlp.core.lib import build_icon -from openlp.plugins.alerts.lib import alertmanager +from datetime import date -class AlertForm(QtGui.QDialog): - global log - log = logging.getLogger(u'AlertForm') +from PyQt4 import QtGui, QtCore +from openlp.plugins.alerts.lib.models import AlertItem - def __init__(self, parent): - QtGui.QDialog.__init__(self, None) +from alertdialog import Ui_AlertDialog + +class AlertForm(QtGui.QDialog, Ui_AlertDialog): + """ + Class documentation goes here. + """ + def __init__(self, manager, parent): + """ + Constructor + """ + self.manager = manager self.parent = parent + self.history_required = True + QtGui.QDialog.__init__(self, None) self.setupUi(self) - log.debug(u'AlertForm Defined') + QtCore.QObject.connect(self.CancelButton, + QtCore.SIGNAL(u'clicked()'), + AlertForm.close) + QtCore.QObject.connect(self.DisplayButton, + QtCore.SIGNAL(u'clicked()'), + self.onDisplayClicked) + QtCore.QObject.connect(self.AlertEntryEditItem, + QtCore.SIGNAL(u'textChanged(const QString&)'), + self.onTextChanged) + QtCore.QObject.connect(self.AlertListWidget, + QtCore.SIGNAL(u'doubleClicked(QModelIndex)'), + self.onDoubleClick) + QtCore.QObject.connect(self.AlertListWidget, + QtCore.SIGNAL(u'clicked(QModelIndex)'), + self.onSingleClick) - def setupUi(self, AlertForm): - AlertForm.setObjectName(u'AlertForm') - AlertForm.resize(370, 110) - icon = build_icon(u':/icon/openlp-logo-16x16.png') - AlertForm.setWindowIcon(icon) - self.AlertFormLayout = QtGui.QVBoxLayout(AlertForm) - self.AlertFormLayout.setSpacing(8) - self.AlertFormLayout.setMargin(8) - self.AlertFormLayout.setObjectName(u'AlertFormLayout') - self.AlertEntryWidget = QtGui.QWidget(AlertForm) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.AlertEntryWidget.sizePolicy().hasHeightForWidth()) - self.AlertEntryWidget.setSizePolicy(sizePolicy) - self.AlertEntryWidget.setObjectName(u'AlertEntryWidget') - self.AlertEntryLabel = QtGui.QLabel(self.AlertEntryWidget) - self.AlertEntryLabel.setGeometry(QtCore.QRect(0, 0, 353, 16)) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.AlertEntryLabel.sizePolicy().hasHeightForWidth()) - self.AlertEntryLabel.setSizePolicy(sizePolicy) - self.AlertEntryLabel.setObjectName(u'AlertEntryLabel') - self.AlertEntryEditItem = QtGui.QLineEdit(self.AlertEntryWidget) - self.AlertEntryEditItem.setGeometry(QtCore.QRect(0, 20, 353, 26)) - self.AlertEntryEditItem.setObjectName(u'AlertEntryEditItem') - self.AlertFormLayout.addWidget(self.AlertEntryWidget) - self.ButtonBoxWidget = QtGui.QWidget(AlertForm) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.ButtonBoxWidget.sizePolicy().hasHeightForWidth()) - self.ButtonBoxWidget.setSizePolicy(sizePolicy) - self.ButtonBoxWidget.setObjectName(u'ButtonBoxWidget') - self.horizontalLayout = QtGui.QHBoxLayout(self.ButtonBoxWidget) - self.horizontalLayout.setSpacing(8) - self.horizontalLayout.setMargin(0) - self.horizontalLayout.setObjectName(u'horizontalLayout') - spacerItem = QtGui.QSpacerItem(267, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.horizontalLayout.addItem(spacerItem) - self.DisplayButton = QtGui.QPushButton(self.ButtonBoxWidget) - self.DisplayButton.setObjectName(u'DisplayButton') - self.horizontalLayout.addWidget(self.DisplayButton) - self.CancelButton = QtGui.QPushButton(self.ButtonBoxWidget) - self.CancelButton.setObjectName(u'CancelButton') - self.horizontalLayout.addWidget(self.CancelButton) - self.AlertFormLayout.addWidget(self.ButtonBoxWidget) - self.retranslateUi(AlertForm) - QtCore.QObject.connect(self.CancelButton, QtCore.SIGNAL(u'clicked()'), AlertForm.close) - QtCore.QObject.connect(self.DisplayButton, QtCore.SIGNAL(u'clicked()'), self.onDisplayClicked) - QtCore.QMetaObject.connectSlotsByName(AlertForm) - - def retranslateUi(self, AlertForm): - AlertForm.setWindowTitle(self.trUtf8('Alert Message')) - self.AlertEntryLabel.setText(self.trUtf8('Alert Text:')) - self.DisplayButton.setText(self.trUtf8('Display')) - self.CancelButton.setText(self.trUtf8('Cancel')) + def loadList(self): + self.AlertListWidget.clear() + alerts = self.manager.get_all_alerts() + for alert in alerts: + item_name = QtGui.QListWidgetItem(alert.text) + self.AlertListWidget.addItem(item_name) def onDisplayClicked(self): - self.parent.alertsmanager.displayAlert(unicode(self.AlertEntryEditItem.text())) + self.triggerAlert(unicode(self.AlertEntryEditItem.text())) + if self.parent.alertsTab.save_history and self.history_required: + alert = AlertItem() + alert.text = unicode(self.AlertEntryEditItem.text()) + self.manager.save_alert(alert) + self.history_required = False + self.loadList() + + def onTextChanged(self): + #Data has changed by editing it so potential storage + self.history_required = True + + def onDoubleClick(self): + """ + List item has been double clicked to display it + """ + items = self.AlertListWidget.selectedIndexes() + for item in items: + bitem = self.AlertListWidget.item(item.row()) + self.triggerAlert(bitem.text()) + self.history_required = False + + def onSingleClick(self): + """ + List item has been single clicked to add it to + the edit field so it can be changed. + """ + items = self.AlertListWidget.selectedIndexes() + for item in items: + bitem = self.AlertListWidget.item(item.row()) + self.AlertEntryEditItem.setText(bitem.text()) + self.history_required = False + + def triggerAlert(self, text): + self.parent.alertsmanager.displayAlert(text) diff --git a/openlp/plugins/alerts/forms/alertstab.py b/openlp/plugins/alerts/forms/alertstab.py index a6653d152..7c50ccc19 100644 --- a/openlp/plugins/alerts/forms/alertstab.py +++ b/openlp/plugins/alerts/forms/alertstab.py @@ -25,7 +25,7 @@ from PyQt4 import QtCore, QtGui -from openlp.core.lib import SettingsTab +from openlp.core.lib import SettingsTab, str_to_bool class AlertsTab(SettingsTab): """ @@ -132,6 +132,38 @@ class AlertsTab(SettingsTab): QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) self.LocationLayout.addItem(self.LocationSpacer) self.FontLayout.addWidget(self.LocationWidget) + self.HistoryWidget = QtGui.QWidget(self.FontGroupBox) + self.HistoryWidget.setObjectName(u'HistoryWidget') + self.HistoryLayout = QtGui.QHBoxLayout(self.HistoryWidget) + self.HistoryLayout.setSpacing(8) + self.HistoryLayout.setMargin(0) + self.HistoryLayout.setObjectName(u'HistoryLayout') + self.HistoryLabel = QtGui.QLabel(self.HistoryWidget) + self.HistoryLabel.setObjectName(u'HistoryLabel') + self.HistoryLayout.addWidget(self.HistoryLabel) + self.HistoryCheckBox = QtGui.QCheckBox(self.HistoryWidget) + self.HistoryCheckBox.setObjectName(u'HistoryCheckBox') + self.HistoryLayout.addWidget(self.HistoryCheckBox) + self.HistorySpacer = QtGui.QSpacerItem(147, 20, + QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.HistoryLayout.addItem(self.HistorySpacer) + self.FontLayout.addWidget(self.HistoryWidget) + self.HistoryEditWidget = QtGui.QWidget(self.FontGroupBox) + self.HistoryEditWidget.setObjectName(u'HistoryEditWidget') + self.HistoryEditLayout = QtGui.QHBoxLayout(self.HistoryEditWidget) + self.HistoryEditLayout.setSpacing(8) + self.HistoryEditLayout.setMargin(0) + self.HistoryEditLayout.setObjectName(u'HistoryEditLayout') + self.HistoryEditLabel = QtGui.QLabel(self.HistoryEditWidget) + self.HistoryEditLabel.setObjectName(u'HistoryEditLabel') + self.HistoryEditLayout.addWidget(self.HistoryEditLabel) + self.HistoryEditPushButton = QtGui.QPushButton(self.HistoryEditWidget) + self.HistoryEditPushButton.setObjectName(u'HistoryEditPushButton') + self.HistoryEditLayout.addWidget(self.HistoryEditPushButton) + self.HistoryEditSpacer = QtGui.QSpacerItem(147, 20, + QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.HistoryEditLayout.addItem(self.HistoryEditSpacer) + self.FontLayout.addWidget(self.HistoryEditWidget) self.SlideLeftLayout.addWidget(self.FontGroupBox) self.SlideLeftSpacer = QtGui.QSpacerItem(20, 94, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) @@ -170,6 +202,9 @@ class AlertsTab(SettingsTab): self.SlideRightLayout.addItem(self.SlideRightSpacer) self.AlertsLayout.addWidget(self.AlertRightColumn) # Signals and slots + QtCore.QObject.connect(self.HistoryCheckBox, + QtCore.SIGNAL(u'stateChanged(int)'), + self.onHistoryCheckBoxChanged) QtCore.QObject.connect(self.BackgroundColorButton, QtCore.SIGNAL(u'pressed()'), self.onBackgroundColorButtonClicked) QtCore.QObject.connect(self.FontColorButton, @@ -193,6 +228,8 @@ class AlertsTab(SettingsTab): self.TimeoutLabel.setText(self.trUtf8('Alert timeout:')) self.TimeoutSpinBox.setSuffix(self.trUtf8('s')) self.LocationLabel.setText(self.trUtf8('Location:')) + self.HistoryLabel.setText(self.trUtf8('Keep History:')) + self.HistoryEditLabel.setText(self.trUtf8('Edit History:')) self.PreviewGroupBox.setTitle(self.trUtf8('Preview')) self.FontPreview.setText(self.trUtf8('openlp.org')) self.LocationComboBox.setItemText(0, self.trUtf8('Top')) @@ -211,6 +248,12 @@ class AlertsTab(SettingsTab): def onLocationComboBoxClicked(self, location): self.location = location + def onHistoryCheckBoxChanged(self, check_state): + self.save_history = False + # we have a set value convert to True/False + if check_state == QtCore.Qt.Checked: + self.save_history = True + def onFontColorButtonClicked(self): self.font_color = QtGui.QColorDialog.getColor( QtGui.QColor(self.font_color), self).name() @@ -235,6 +278,8 @@ class AlertsTab(SettingsTab): self.font_face = unicode( self.config.get_config(u'font face', QtGui.QFont().family())) self.location = int(self.config.get_config(u'location', 0)) + self.save_history = str_to_bool( + self.config.get_config(u'save history', u'False')) self.FontSizeSpinBox.setValue(self.font_size) self.TimeoutSpinBox.setValue(self.timeout) self.FontColorButton.setStyleSheet( @@ -242,6 +287,7 @@ class AlertsTab(SettingsTab): self.BackgroundColorButton.setStyleSheet( u'background-color: %s' % self.bg_color) self.LocationComboBox.setCurrentIndex(self.location) + self.HistoryCheckBox.setChecked(self.save_history) font = QtGui.QFont() font.setFamily(self.font_face) self.FontComboBox.setCurrentFont(font) @@ -254,7 +300,9 @@ class AlertsTab(SettingsTab): self.config.set_config(u'font size', unicode(self.font_size)) self.config.set_config(u'font face', unicode(self.font_face)) self.config.set_config(u'timeout', unicode(self.timeout)) - self.config.set_config(u'location', unicode(self.LocationComboBox.currentIndex())) + self.config.set_config(u'location', + unicode(self.LocationComboBox.currentIndex())) + self.config.set_config(u'save history', unicode(self.save_history)) def updateDisplay(self): font = QtGui.QFont() diff --git a/openlp/plugins/alerts/lib/__init__.py b/openlp/plugins/alerts/lib/__init__.py index f6fb2c0d7..c39574719 100644 --- a/openlp/plugins/alerts/lib/__init__.py +++ b/openlp/plugins/alerts/lib/__init__.py @@ -23,3 +23,4 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### from alertsmanager import AlertsManager +from manager import DBManager diff --git a/openlp/plugins/alerts/lib/classes.py b/openlp/plugins/alerts/lib/classes.py new file mode 100644 index 000000000..eec21300c --- /dev/null +++ b/openlp/plugins/alerts/lib/classes.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard # +# --------------------------------------------------------------------------- # +# 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 # +############################################################################### + +class BaseModel(object): + """ + BaseModel provides a base object with a set of generic functions + """ + + @classmethod + def populate(cls, **kwargs): + """ + Creates an instance of a class and populates it, returning the instance + """ + me = cls() + keys = kwargs.keys() + for key in keys: + me.__setattr__(key, kwargs[key]) + return me + +class AlertItem(BaseModel): + """ + Custom Slide model + """ + pass diff --git a/openlp/plugins/alerts/lib/manager.py b/openlp/plugins/alerts/lib/manager.py new file mode 100644 index 000000000..79ed928e1 --- /dev/null +++ b/openlp/plugins/alerts/lib/manager.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard # +# --------------------------------------------------------------------------- # +# 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 # +############################################################################### + +import logging + +from openlp.plugins.alerts.lib.models import init_models, metadata, AlertItem + +class DBManager(): + """ + The Song Manager provides a central location for all database code. This + class takes care of connecting to the database and running all the queries. + """ + + global log + log = logging.getLogger(u'AlertsDBManager') + log.info(u'Alerts DB loaded') + + def __init__(self, config): + """ + Creates the connection to the database, and creates the tables if they + don't exist. + """ + self.config = config + log.debug(u'Alerts Initialising') + self.db_url = u'' + db_type = self.config.get_config(u'db type', u'sqlite') + if db_type == u'sqlite': + self.db_url = u'sqlite:///%s/alerts.sqlite' % \ + self.config.get_data_path() + else: + self.db_url = u'%s://%s:%s@%s/%s' % \ + (db_type, self.config.get_config(u'db username'), + self.config.get_config(u'db password'), + self.config.get_config(u'db hostname'), + self.config.get_config(u'db database')) + self.session = init_models(self.db_url) + metadata.create_all(checkfirst=True) + + log.debug(u'Alerts Initialised') + + def get_all_alerts(self): + """ + Returns the details of a Alert Show + """ + return self.session.query(AlertItem).order_by(AlertItem.text).all() + + def save_alert(self, AlertItem): + """ + Saves a Alert show to the database + """ + log.debug(u'Alert added') + try: + self.session.add(AlertItem) + self.session.commit() + log.debug(u'Alert saved') + return True + except: + self.session.rollback() + log.excertion(u'Alert save failed') + return False + + def get_alerts(self, id=None): + """ + Returns the details of a Alert + """ + if id is None: + return AlertItem() + else: + return self.session.query(AlertItem).get(id) + + def delete_alert(self, id): + """ + Delete a Alert show + """ + if id !=0: + Alerts = self.get_Alert(id) + try: + self.session.delete(AlertItem) + self.session.commit() + return True + except: + self.session.rollback() + log.excertion(u'Alert deleton failed') + return False + else: + return True diff --git a/openlp/plugins/alerts/lib/meta.py b/openlp/plugins/alerts/lib/meta.py new file mode 100644 index 000000000..38b0f7206 --- /dev/null +++ b/openlp/plugins/alerts/lib/meta.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard # +# --------------------------------------------------------------------------- # +# 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 # +############################################################################### + +from sqlalchemy import MetaData + +__all__ = ['session', 'metadata', 'engine'] + +# SQLAlchemy database engine. Updated by model.init_model() +engine = None + +# SQLAlchemy session manager. Updated by model.init_model() +session = None + +# Global metadata. If you have multiple databases with overlapping table +# names, you'll need a metadata for each database +metadata = MetaData() \ No newline at end of file diff --git a/openlp/plugins/alerts/lib/models.py b/openlp/plugins/alerts/lib/models.py new file mode 100644 index 000000000..4f556cd23 --- /dev/null +++ b/openlp/plugins/alerts/lib/models.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard # +# --------------------------------------------------------------------------- # +# 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 # +############################################################################### + +from sqlalchemy import create_engine +from sqlalchemy.orm import scoped_session, sessionmaker, mapper + +from openlp.plugins.alerts.lib.meta import metadata +from openlp.plugins.alerts.lib.tables import * +from openlp.plugins.alerts.lib.classes import * + +def init_models(url): + engine = create_engine(url) + metadata.bind = engine + session = scoped_session(sessionmaker(autoflush=True, autocommit=False, + bind=engine)) + mapper(AlertItem, alerts_table) + return session diff --git a/openlp/plugins/alerts/lib/tables.py b/openlp/plugins/alerts/lib/tables.py new file mode 100644 index 000000000..0a731fb13 --- /dev/null +++ b/openlp/plugins/alerts/lib/tables.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard # +# --------------------------------------------------------------------------- # +# 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 # +############################################################################### + +from sqlalchemy import Column, Table, types + +from openlp.plugins.alerts.lib.meta import metadata + +# Definition of the "alerts" table +alerts_table = Table(u'alerts', metadata, + Column(u'id', types.Integer(), primary_key=True), + Column(u'text', types.UnicodeText, nullable=False)) diff --git a/openlp/plugins/bibles/lib/biblestab.py b/openlp/plugins/bibles/lib/biblestab.py index 9c7e6b280..ef8e6a090 100644 --- a/openlp/plugins/bibles/lib/biblestab.py +++ b/openlp/plugins/bibles/lib/biblestab.py @@ -27,8 +27,7 @@ import logging from PyQt4 import QtCore, QtGui -from openlp.core.lib import str_to_bool, Receiver -from openlp.core.lib import SettingsTab +from openlp.core.lib import str_to_bool, Receiver, SettingsTab class BiblesTab(SettingsTab): """ @@ -226,4 +225,4 @@ class BiblesTab(SettingsTab): # Not Found id = 0 self.bible_theme = u'' - self.BibleThemeComboBox.setCurrentIndex(id) \ No newline at end of file + self.BibleThemeComboBox.setCurrentIndex(id) diff --git a/openlp/plugins/custom/lib/tables.py b/openlp/plugins/custom/lib/tables.py index 061c6d5c3..13c9de5b9 100644 --- a/openlp/plugins/custom/lib/tables.py +++ b/openlp/plugins/custom/lib/tables.py @@ -27,11 +27,11 @@ from sqlalchemy import Column, Table, types from openlp.plugins.custom.lib.meta import metadata -# Definition of the "songs" table +# Definition of the "custom slide" table custom_slide_table = Table(u'custom_slide', metadata, Column(u'id', types.Integer(), primary_key=True), Column(u'title', types.Unicode(255), nullable=False), Column(u'text', types.UnicodeText, nullable=False), Column(u'credits', types.UnicodeText), Column(u'theme_name', types.Unicode(128)) -) \ No newline at end of file +) diff --git a/resources/forms/alertdialog.ui b/resources/forms/alertdialog.ui new file mode 100644 index 000000000..c3bfdaf8e --- /dev/null +++ b/resources/forms/alertdialog.ui @@ -0,0 +1,116 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>AlertForm</class> + <widget class="QWidget" name="AlertForm"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>430</width> + <height>320</height> + </rect> + </property> + <property name="windowTitle"> + <string>Alert Message</string> + </property> + <property name="windowIcon"> + <iconset> + <normaloff>:/icon/openlp.org-icon-32.bmp</normaloff>:/icon/openlp.org-icon-32.bmp</iconset> + </property> + <layout class="QVBoxLayout" name="AlertFormLayout"> + <property name="spacing"> + <number>8</number> + </property> + <property name="margin"> + <number>8</number> + </property> + <item> + <widget class="QWidget" name="AlertEntryWidget" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="AlertEntryLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Alert Text:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="AlertEntryEditItem"/> + </item> + <item> + <widget class="QListWidget" name="AlertListWidget"/> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <spacer name="ButtonBoxWidgetSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>181</width> + <height>38</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="DisplayButton"> + <property name="text"> + <string>Display</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="CancelButton"> + <property name="text"> + <string>Cancel</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <resources> + <include location="../images/openlp-2.qrc"/> + </resources> + <connections> + <connection> + <sender>CancelButton</sender> + <signal>clicked()</signal> + <receiver>AlertForm</receiver> + <slot>close()</slot> + <hints> + <hint type="sourcelabel"> + <x>294</x> + <y>66</y> + </hint> + <hint type="destinationlabel"> + <x>257</x> + <y>3</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/resources/forms/alerteditdialog.ui b/resources/forms/alerteditdialog.ui new file mode 100644 index 000000000..a4b63ccb7 --- /dev/null +++ b/resources/forms/alerteditdialog.ui @@ -0,0 +1,94 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>AlertList</class> + <widget class="QWidget" name="AlertList"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>300</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="geometry"> + <rect> + <x>220</x> + <y>270</y> + <width>173</width> + <height>27</height> + </rect> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel</set> + </property> + </widget> + <widget class="QWidget" name=""> + <property name="geometry"> + <rect> + <x>20</x> + <y>10</y> + <width>361</width> + <height>251</height> + </rect> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QLineEdit" name="lineEdit"/> + </item> + <item> + <widget class="QPushButton" name="SaveButton"> + <property name="text"> + <string>Save</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QListWidget" name="listWidget"> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QPushButton" name="AddButton"> + <property name="text"> + <string>Add</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="EdirButton"> + <property name="text"> + <string>Edit</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="DeleteButton"> + <property name="text"> + <string>Delete</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + </layout> + </widget> + </widget> + <resources/> + <connections/> +</ui> diff --git a/resources/forms/alertform.ui b/resources/forms/alertform.ui deleted file mode 100644 index 1caf4a356..000000000 --- a/resources/forms/alertform.ui +++ /dev/null @@ -1,133 +0,0 @@ -<ui version="4.0" > - <class>AlertForm</class> - <widget class="QWidget" name="AlertForm" > - <property name="geometry" > - <rect> - <x>0</x> - <y>0</y> - <width>370</width> - <height>105</height> - </rect> - </property> - <property name="windowTitle" > - <string>Alert Message</string> - </property> - <property name="windowIcon" > - <iconset resource="../images/openlp-2.qrc" > - <normaloff>:/icon/openlp.org-icon-32.bmp</normaloff>:/icon/openlp.org-icon-32.bmp</iconset> - </property> - <layout class="QVBoxLayout" name="AlertFormLayout" > - <property name="spacing" > - <number>8</number> - </property> - <property name="margin" > - <number>8</number> - </property> - <item> - <widget class="QWidget" native="1" name="AlertEntryWidget" > - <property name="sizePolicy" > - <sizepolicy vsizetype="Preferred" hsizetype="Preferred" > - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <widget class="QLabel" name="AlertEntryLabel" > - <property name="geometry" > - <rect> - <x>0</x> - <y>0</y> - <width>353</width> - <height>16</height> - </rect> - </property> - <property name="sizePolicy" > - <sizepolicy vsizetype="Fixed" hsizetype="Preferred" > - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="text" > - <string>Alert Text:</string> - </property> - </widget> - <widget class="QLineEdit" name="AlertEntryEditItem" > - <property name="geometry" > - <rect> - <x>0</x> - <y>20</y> - <width>353</width> - <height>21</height> - </rect> - </property> - </widget> - </widget> - </item> - <item> - <widget class="QWidget" native="1" name="ButtonBoxWidget" > - <property name="sizePolicy" > - <sizepolicy vsizetype="Preferred" hsizetype="Preferred" > - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <layout class="QHBoxLayout" name="horizontalLayout" > - <property name="spacing" > - <number>8</number> - </property> - <property name="margin" > - <number>0</number> - </property> - <item> - <spacer name="ButtonBoxWidgetSpacer" > - <property name="orientation" > - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0" > - <size> - <width>267</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QPushButton" name="DisplayButton" > - <property name="text" > - <string>Display</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="CancelButton" > - <property name="text" > - <string>Cancel</string> - </property> - </widget> - </item> - </layout> - </widget> - </item> - </layout> - </widget> - <resources> - <include location="../images/openlp-2.qrc" /> - </resources> - <connections> - <connection> - <sender>CancelButton</sender> - <signal>clicked()</signal> - <receiver>AlertForm</receiver> - <slot>close()</slot> - <hints> - <hint type="sourcelabel" > - <x>294</x> - <y>66</y> - </hint> - <hint type="destinationlabel" > - <x>257</x> - <y>3</y> - </hint> - </hints> - </connection> - </connections> -</ui> From 2fefcef09d58137300037842d367360d19298d3d Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sun, 14 Feb 2010 20:19:57 +0000 Subject: [PATCH 072/164] Alerts editing now work - history and pre determined --- openlp/plugins/alerts/alertsplugin.py | 11 +- openlp/plugins/alerts/forms/__init__.py | 1 + openlp/plugins/alerts/forms/alertdialog.py | 3 +- .../plugins/alerts/forms/alerteditdialog.py | 70 ++++---- openlp/plugins/alerts/forms/alerteditform.py | 168 ++++++++++++++++++ openlp/plugins/alerts/forms/alertform.py | 3 - openlp/plugins/alerts/forms/alertstab.py | 10 +- openlp/plugins/alerts/lib/manager.py | 10 +- openlp/plugins/custom/lib/manager.py | 8 +- openlp/plugins/songusage/lib/manager.py | 2 +- resources/forms/alertdialog.ui | 6 +- resources/forms/alerteditdialog.ui | 33 ++-- 12 files changed, 257 insertions(+), 68 deletions(-) create mode 100644 openlp/plugins/alerts/forms/alerteditform.py diff --git a/openlp/plugins/alerts/alertsplugin.py b/openlp/plugins/alerts/alertsplugin.py index 7214618d7..169e4b506 100644 --- a/openlp/plugins/alerts/alertsplugin.py +++ b/openlp/plugins/alerts/alertsplugin.py @@ -30,7 +30,7 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import Plugin, Receiver, str_to_bool, build_icon, PluginStatus from openlp.plugins.alerts.lib import AlertsManager, DBManager -from openlp.plugins.alerts.forms import AlertsTab, AlertForm +from openlp.plugins.alerts.forms import AlertsTab, AlertForm, AlertEditForm class alertsPlugin(Plugin): global log @@ -44,10 +44,11 @@ class alertsPlugin(Plugin): self.alertsmanager = AlertsManager(self) self.manager = DBManager(self.config) self.alertForm = AlertForm(self.manager, self) + self.alertEditForm = AlertEditForm(self.manager, self) self.status = PluginStatus.Active def get_settings_tab(self): - self.alertsTab = AlertsTab(self.name) + self.alertsTab = AlertsTab(self) return self.alertsTab def add_tools_menu_item(self, tools_menu): @@ -90,9 +91,9 @@ class alertsPlugin(Plugin): self.alertForm.loadList() self.alertForm.exec_() - def onalertsReport(self): - self.alertsdetailform.initialise() - self.alertsdetailform.exec_() + def onAlertsEdit(self): + self.alertEditForm.loadList() + self.alertEditForm.exec_() def about(self): about_text = self.trUtf8('<b>Alerts Plugin</b><br>This plugin ' diff --git a/openlp/plugins/alerts/forms/__init__.py b/openlp/plugins/alerts/forms/__init__.py index dba3abc93..14c30d73b 100644 --- a/openlp/plugins/alerts/forms/__init__.py +++ b/openlp/plugins/alerts/forms/__init__.py @@ -25,3 +25,4 @@ from alertstab import AlertsTab from alertform import AlertForm +from alerteditform import AlertEditForm diff --git a/openlp/plugins/alerts/forms/alertdialog.py b/openlp/plugins/alerts/forms/alertdialog.py index 4756a318f..3e467dd0e 100644 --- a/openlp/plugins/alerts/forms/alertdialog.py +++ b/openlp/plugins/alerts/forms/alertdialog.py @@ -43,6 +43,7 @@ class Ui_AlertDialog(object): self.AlertEntryEditItem.setObjectName("AlertEntryEditItem") self.verticalLayout.addWidget(self.AlertEntryEditItem) self.AlertListWidget = QtGui.QListWidget(self.AlertEntryWidget) + self.AlertListWidget.setAlternatingRowColors(True) self.AlertListWidget.setObjectName("AlertListWidget") self.verticalLayout.addWidget(self.AlertListWidget) self.verticalLayout_2.addLayout(self.verticalLayout) @@ -60,7 +61,7 @@ class Ui_AlertDialog(object): self.AlertFormLayout.addWidget(self.AlertEntryWidget) self.retranslateUi(AlertForm) - QtCore.QObject.connect(self.CancelButton, QtCore.SIGNAL("clicked()"), AlertForm.close) + QtCore.QObject.connect(self.CancelButton, QtCore.SIGNAL("clicked()"), self.close) QtCore.QMetaObject.connectSlotsByName(AlertForm) def retranslateUi(self, AlertForm): diff --git a/openlp/plugins/alerts/forms/alerteditdialog.py b/openlp/plugins/alerts/forms/alerteditdialog.py index 53985d82b..c382d5352 100644 --- a/openlp/plugins/alerts/forms/alerteditdialog.py +++ b/openlp/plugins/alerts/forms/alerteditdialog.py @@ -2,62 +2,66 @@ # Form implementation generated from reading ui file 'alerteditdialog.ui' # -# Created: Sat Feb 13 08:20:09 2010 +# Created: Sun Feb 14 16:45:10 2010 # by: PyQt4 UI code generator 4.6.2 # # WARNING! All changes made in this file will be lost! from PyQt4 import QtCore, QtGui -class Ui_AlertList(object): - def setupUi(self, AlertList): - AlertList.setObjectName("AlertList") - AlertList.resize(400, 300) - self.buttonBox = QtGui.QDialogButtonBox(AlertList) +class Ui_AlertEditDialog(object): + def setupUi(self, AlertEditDialog): + AlertEditDialog.setObjectName("AlertEditDialog") + AlertEditDialog.resize(400, 300) + self.buttonBox = QtGui.QDialogButtonBox(AlertEditDialog) self.buttonBox.setGeometry(QtCore.QRect(220, 270, 173, 27)) self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel) self.buttonBox.setObjectName("buttonBox") - self.widget = QtGui.QWidget(AlertList) - self.widget.setGeometry(QtCore.QRect(20, 10, 361, 251)) - self.widget.setObjectName("widget") - self.verticalLayout_2 = QtGui.QVBoxLayout(self.widget) + self.layoutWidget = QtGui.QWidget(AlertEditDialog) + self.layoutWidget.setGeometry(QtCore.QRect(20, 10, 361, 251)) + self.layoutWidget.setObjectName("layoutWidget") + self.verticalLayout_2 = QtGui.QVBoxLayout(self.layoutWidget) self.verticalLayout_2.setObjectName("verticalLayout_2") self.horizontalLayout_2 = QtGui.QHBoxLayout() self.horizontalLayout_2.setObjectName("horizontalLayout_2") - self.lineEdit = QtGui.QLineEdit(self.widget) - self.lineEdit.setObjectName("lineEdit") - self.horizontalLayout_2.addWidget(self.lineEdit) - self.SaveButton = QtGui.QPushButton(self.widget) - self.SaveButton.setObjectName("SaveButton") - self.horizontalLayout_2.addWidget(self.SaveButton) + self.AlertLineEdit = QtGui.QLineEdit(self.layoutWidget) + self.AlertLineEdit.setObjectName("AlertLineEdit") + self.horizontalLayout_2.addWidget(self.AlertLineEdit) self.verticalLayout_2.addLayout(self.horizontalLayout_2) self.horizontalLayout = QtGui.QHBoxLayout() self.horizontalLayout.setObjectName("horizontalLayout") - self.listWidget = QtGui.QListWidget(self.widget) - self.listWidget.setAlternatingRowColors(True) - self.listWidget.setObjectName("listWidget") - self.horizontalLayout.addWidget(self.listWidget) + self.AlertListWidget = QtGui.QListWidget(self.layoutWidget) + self.AlertListWidget.setAlternatingRowColors(True) + self.AlertListWidget.setObjectName("AlertListWidget") + self.horizontalLayout.addWidget(self.AlertListWidget) self.verticalLayout = QtGui.QVBoxLayout() self.verticalLayout.setObjectName("verticalLayout") - self.AddButton = QtGui.QPushButton(self.widget) + self.SaveButton = QtGui.QPushButton(self.layoutWidget) + self.SaveButton.setObjectName("SaveButton") + self.verticalLayout.addWidget(self.SaveButton) + self.ClearButton = QtGui.QPushButton(self.layoutWidget) + self.ClearButton.setObjectName("ClearButton") + self.verticalLayout.addWidget(self.ClearButton) + self.AddButton = QtGui.QPushButton(self.layoutWidget) self.AddButton.setObjectName("AddButton") self.verticalLayout.addWidget(self.AddButton) - self.EdirButton = QtGui.QPushButton(self.widget) - self.EdirButton.setObjectName("EdirButton") - self.verticalLayout.addWidget(self.EdirButton) - self.DeleteButton = QtGui.QPushButton(self.widget) + self.EditButton = QtGui.QPushButton(self.layoutWidget) + self.EditButton.setObjectName("EditButton") + self.verticalLayout.addWidget(self.EditButton) + self.DeleteButton = QtGui.QPushButton(self.layoutWidget) self.DeleteButton.setObjectName("DeleteButton") self.verticalLayout.addWidget(self.DeleteButton) self.horizontalLayout.addLayout(self.verticalLayout) self.verticalLayout_2.addLayout(self.horizontalLayout) - self.retranslateUi(AlertList) - QtCore.QMetaObject.connectSlotsByName(AlertList) + self.retranslateUi(AlertEditDialog) + QtCore.QMetaObject.connectSlotsByName(AlertEditDialog) - def retranslateUi(self, AlertList): - AlertList.setWindowTitle(QtGui.QApplication.translate("AlertList", "Form", None, QtGui.QApplication.UnicodeUTF8)) - self.SaveButton.setText(QtGui.QApplication.translate("AlertList", "Save", None, QtGui.QApplication.UnicodeUTF8)) - self.AddButton.setText(QtGui.QApplication.translate("AlertList", "Add", None, QtGui.QApplication.UnicodeUTF8)) - self.EdirButton.setText(QtGui.QApplication.translate("AlertList", "Edit", None, QtGui.QApplication.UnicodeUTF8)) - self.DeleteButton.setText(QtGui.QApplication.translate("AlertList", "Delete", None, QtGui.QApplication.UnicodeUTF8)) + def retranslateUi(self, AlertEditDialog): + AlertEditDialog.setWindowTitle(QtGui.QApplication.translate("AlertEditDialog", "Maintain Alerts", None, QtGui.QApplication.UnicodeUTF8)) + self.SaveButton.setText(QtGui.QApplication.translate("AlertEditDialog", "Save", None, QtGui.QApplication.UnicodeUTF8)) + self.ClearButton.setText(QtGui.QApplication.translate("AlertEditDialog", "Clear", None, QtGui.QApplication.UnicodeUTF8)) + self.AddButton.setText(QtGui.QApplication.translate("AlertEditDialog", "Add", None, QtGui.QApplication.UnicodeUTF8)) + self.EditButton.setText(QtGui.QApplication.translate("AlertEditDialog", "Edit", None, QtGui.QApplication.UnicodeUTF8)) + self.DeleteButton.setText(QtGui.QApplication.translate("AlertEditDialog", "Delete", None, QtGui.QApplication.UnicodeUTF8)) diff --git a/openlp/plugins/alerts/forms/alerteditform.py b/openlp/plugins/alerts/forms/alerteditform.py new file mode 100644 index 000000000..fef8a04e5 --- /dev/null +++ b/openlp/plugins/alerts/forms/alerteditform.py @@ -0,0 +1,168 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard # +# --------------------------------------------------------------------------- # +# 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 # +############################################################################### + +from datetime import date + +from PyQt4 import QtGui, QtCore +from openlp.plugins.alerts.lib.models import AlertItem + +from alerteditdialog import Ui_AlertEditDialog + +class AlertEditForm(QtGui.QDialog, Ui_AlertEditDialog): + """ + Class documentation goes here. + """ + def __init__(self, manager, parent): + """ + Constructor + """ + self.manager = manager + self.parent = parent + QtGui.QDialog.__init__(self, None) + self.setupUi(self) + QtCore.QObject.connect(self.DeleteButton, + QtCore.SIGNAL(u'clicked()'), + self.onDeleteClick) + QtCore.QObject.connect(self.ClearButton, + QtCore.SIGNAL(u'clicked()'), + self.onClearClick) + QtCore.QObject.connect(self.EditButton, + QtCore.SIGNAL(u'clicked()'), + self.onEditClick) + QtCore.QObject.connect(self.AddButton, + QtCore.SIGNAL(u'clicked()'), + self.onAddClick) + QtCore.QObject.connect(self.SaveButton, + QtCore.SIGNAL(u'clicked()'), + self.onSaveClick) + QtCore.QObject.connect(self.buttonBox, + QtCore.SIGNAL(u'rejected()'), self.close) + QtCore.QObject.connect(self.AlertLineEdit, + QtCore.SIGNAL(u'textChanged(const QString&)'), + self.onTextChanged) + QtCore.QObject.connect(self.AlertListWidget, + QtCore.SIGNAL(u'doubleClicked(QModelIndex)'), + self.onItemSelected) + QtCore.QObject.connect(self.AlertListWidget, + QtCore.SIGNAL(u'clicked(QModelIndex)'), + self.onItemSelected) + + def loadList(self): + self.AlertListWidget.clear() + alerts = self.manager.get_all_alerts() + for alert in alerts: + item_name = QtGui.QListWidgetItem(alert.text) + item_name.setData( + QtCore.Qt.UserRole, QtCore.QVariant(alert.id)) + self.AlertListWidget.addItem(item_name) + self.AddButton.setEnabled(True) + self.ClearButton.setEnabled(False) + self.SaveButton.setEnabled(False) + self.EditButton.setEnabled(False) + self.DeleteButton.setEnabled(False) + + def onItemSelected(self): + if len(self.AlertLineEdit.text()) > 0: + QtGui.QMessageBox.information(self, + self.trUtf8('Item selected to Edit'), + self.trUtf8('Please Save or Clear seletced item')) + else: + self.EditButton.setEnabled(True) + self.DeleteButton.setEnabled(True) + + def onDeleteClick(self): + item = self.AlertListWidget.currentItem() + if item: + item_id = (item.data(QtCore.Qt.UserRole)).toInt()[0] + self.parent.manager.delete_alert(item_id) + row = self.AlertListWidget.row(item) + self.AlertListWidget.takeItem(row) + self.AddButton.setEnabled(True) + self.SaveButton.setEnabled(False) + self.DeleteButton.setEnabled(False) + self.EditButton.setEnabled(False) + + def onEditClick(self): + item = self.AlertListWidget.currentItem() + if item: + self.item_id = (item.data(QtCore.Qt.UserRole)).toInt()[0] + self.AlertLineEdit.setText(unicode(item.text())) + self.AddButton.setEnabled(True) + self.ClearButton.setEnabled(True) + self.SaveButton.setEnabled(True) + self.DeleteButton.setEnabled(True) + self.EditButton.setEnabled(False) + + def onClearClick(self): + self.AlertLineEdit.setText(u'') + self.AddButton.setEnabled(False) + self.ClearButton.setEnabled(True) + self.SaveButton.setEnabled(False) + self.DeleteButton.setEnabled(False) + self.EditButton.setEnabled(False) + + def onAddClick(self): + if len(self.AlertLineEdit.text()) == 0: + QtGui.QMessageBox.information(self, + self.trUtf8('Item selected to Add'), + self.trUtf8('Missing data')) + else: + alert = AlertItem() + alert.text = unicode(self.AlertLineEdit.text()) + self.manager.save_alert(alert) + self.onClearClick() + self.loadList() + + def onSaveClick(self): + alert = self.manager.get_alert(self.item_id) + alert.text = unicode(self.AlertLineEdit.text()) + self.manager.save_alert(alert) + self.onClearClick() + self.loadList() + + def onTextChanged(self): + self.AddButton.setEnabled(True) + + def onDoubleClick(self): + """ + List item has been double clicked to display it + """ + items = self.AlertListWidget.selectedIndexes() + for item in items: + bitem = self.AlertListWidget.item(item.row()) + self.triggerAlert(bitem.text()) + + def onSingleClick(self): + """ + List item has been single clicked to add it to + the edit field so it can be changed. + """ + items = self.AlertListWidget.selectedIndexes() + for item in items: + bitem = self.AlertListWidget.item(item.row()) + self.AlertEntryEditItem.setText(bitem.text()) + + def triggerAlert(self, text): + self.parent.alertsmanager.displayAlert(text) diff --git a/openlp/plugins/alerts/forms/alertform.py b/openlp/plugins/alerts/forms/alertform.py index 882da7cfa..26f78e9b2 100644 --- a/openlp/plugins/alerts/forms/alertform.py +++ b/openlp/plugins/alerts/forms/alertform.py @@ -43,9 +43,6 @@ class AlertForm(QtGui.QDialog, Ui_AlertDialog): self.history_required = True QtGui.QDialog.__init__(self, None) self.setupUi(self) - QtCore.QObject.connect(self.CancelButton, - QtCore.SIGNAL(u'clicked()'), - AlertForm.close) QtCore.QObject.connect(self.DisplayButton, QtCore.SIGNAL(u'clicked()'), self.onDisplayClicked) diff --git a/openlp/plugins/alerts/forms/alertstab.py b/openlp/plugins/alerts/forms/alertstab.py index 7c50ccc19..c842c2e20 100644 --- a/openlp/plugins/alerts/forms/alertstab.py +++ b/openlp/plugins/alerts/forms/alertstab.py @@ -31,8 +31,9 @@ class AlertsTab(SettingsTab): """ AlertsTab is the alerts settings tab in the settings dialog. """ - def __init__(self, title, section=None): - SettingsTab.__init__(self, title, section) + def __init__(self, parent, section=None): + self.parent = parent + SettingsTab.__init__(self, parent.name, section) def setupUi(self): self.setObjectName(u'AlertsTab') @@ -209,6 +210,8 @@ class AlertsTab(SettingsTab): QtCore.SIGNAL(u'pressed()'), self.onBackgroundColorButtonClicked) QtCore.QObject.connect(self.FontColorButton, QtCore.SIGNAL(u'pressed()'), self.onFontColorButtonClicked) + QtCore.QObject.connect(self.HistoryEditPushButton, + QtCore.SIGNAL(u'pressed()'), self.onHistoryEditButtonClicked) QtCore.QObject.connect(self.FontComboBox, QtCore.SIGNAL(u'activated(int)'), self.onFontComboBoxClicked) QtCore.QObject.connect(self.LocationComboBox, @@ -268,6 +271,9 @@ class AlertsTab(SettingsTab): self.font_size = self.FontSizeSpinBox.value() self.updateDisplay() + def onHistoryEditButtonClicked(self): + self.parent.onAlertsEdit() + def load(self): self.timeout = int(self.config.get_config(u'timeout', 5)) self.font_color = unicode( diff --git a/openlp/plugins/alerts/lib/manager.py b/openlp/plugins/alerts/lib/manager.py index 79ed928e1..3480b3103 100644 --- a/openlp/plugins/alerts/lib/manager.py +++ b/openlp/plugins/alerts/lib/manager.py @@ -78,10 +78,10 @@ class DBManager(): return True except: self.session.rollback() - log.excertion(u'Alert save failed') + log.exception(u'Alert save failed') return False - def get_alerts(self, id=None): + def get_alert(self, id=None): """ Returns the details of a Alert """ @@ -94,15 +94,15 @@ class DBManager(): """ Delete a Alert show """ - if id !=0: - Alerts = self.get_Alert(id) + if id != 0: + AlertItem = self.get_alert(id) try: self.session.delete(AlertItem) self.session.commit() return True except: self.session.rollback() - log.excertion(u'Alert deleton failed') + log.exception(u'Alert deleton failed') return False else: return True diff --git a/openlp/plugins/custom/lib/manager.py b/openlp/plugins/custom/lib/manager.py index 387368016..95ac873bd 100644 --- a/openlp/plugins/custom/lib/manager.py +++ b/openlp/plugins/custom/lib/manager.py @@ -78,7 +78,7 @@ class CustomManager(): return True except: self.session.rollback() - log.excertion(u'Custom Slide save failed') + log.exceptiontion(u'Custom Slide save failed') return False def get_custom(self, id=None): @@ -94,7 +94,7 @@ class CustomManager(): """ Delete a Custom slide show """ - if id !=0: + if id != 0: customslide = self.get_custom(id) try: self.session.delete(customslide) @@ -102,7 +102,7 @@ class CustomManager(): return True except: self.session.rollback() - log.excertion(u'Custom Slide deleton failed') + log.exception(u'Custom Slide deleton failed') return False else: - return True \ No newline at end of file + return True diff --git a/openlp/plugins/songusage/lib/manager.py b/openlp/plugins/songusage/lib/manager.py index 0b29d6c98..6cae4c372 100644 --- a/openlp/plugins/songusage/lib/manager.py +++ b/openlp/plugins/songusage/lib/manager.py @@ -96,7 +96,7 @@ class SongUsageManager(): """ Delete a SongUsage record """ - if id !=0: + if id != 0: songusageitem = self.get_songusage(id) try: self.session.delete(songusageitem) diff --git a/resources/forms/alertdialog.ui b/resources/forms/alertdialog.ui index c3bfdaf8e..da56f3847 100644 --- a/resources/forms/alertdialog.ui +++ b/resources/forms/alertdialog.ui @@ -52,7 +52,11 @@ <widget class="QLineEdit" name="AlertEntryEditItem"/> </item> <item> - <widget class="QListWidget" name="AlertListWidget"/> + <widget class="QListWidget" name="AlertListWidget"> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + </widget> </item> </layout> </item> diff --git a/resources/forms/alerteditdialog.ui b/resources/forms/alerteditdialog.ui index a4b63ccb7..352e3d7b1 100644 --- a/resources/forms/alerteditdialog.ui +++ b/resources/forms/alerteditdialog.ui @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> - <class>AlertList</class> - <widget class="QWidget" name="AlertList"> + <class>AlertEditDialog</class> + <widget class="QWidget" name="AlertEditDialog"> <property name="geometry"> <rect> <x>0</x> @@ -11,7 +11,7 @@ </rect> </property> <property name="windowTitle"> - <string>Form</string> + <string>Maintain Alerts</string> </property> <widget class="QDialogButtonBox" name="buttonBox"> <property name="geometry"> @@ -26,7 +26,7 @@ <set>QDialogButtonBox::Cancel</set> </property> </widget> - <widget class="QWidget" name=""> + <widget class="QWidget" name="layoutWidget"> <property name="geometry"> <rect> <x>20</x> @@ -39,21 +39,14 @@ <item> <layout class="QHBoxLayout" name="horizontalLayout_2"> <item> - <widget class="QLineEdit" name="lineEdit"/> - </item> - <item> - <widget class="QPushButton" name="SaveButton"> - <property name="text"> - <string>Save</string> - </property> - </widget> + <widget class="QLineEdit" name="AlertLineEdit"/> </item> </layout> </item> <item> <layout class="QHBoxLayout" name="horizontalLayout"> <item> - <widget class="QListWidget" name="listWidget"> + <widget class="QListWidget" name="AlertListWidget"> <property name="alternatingRowColors"> <bool>true</bool> </property> @@ -61,6 +54,20 @@ </item> <item> <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QPushButton" name="SaveButton"> + <property name="text"> + <string>Save</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="ClearButton"> + <property name="text"> + <string>Clear</string> + </property> + </widget> + </item> <item> <widget class="QPushButton" name="AddButton"> <property name="text"> From 33d022704169a4e5f01cce9fdaa9c8a6048c5e89 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Sun, 14 Feb 2010 23:20:13 +0200 Subject: [PATCH 073/164] Working on a "Source Distribution" - aka source tarball --- MANIFEST.in | 11 +++++++++ setup.py | 69 ++++++++++++++++++++++++++++------------------------- 2 files changed, 47 insertions(+), 33 deletions(-) create mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 000000000..0e94db321 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,11 @@ +recursive-include openlp *.py +recursive-include openlp *.sqlite +recursive-include openlp *.csv +recursive-include documentation * +recursive-include resources/forms * +recursive-include resources/i18n * +recursive-include resources/images * +recursive-include scripts *.py +include copyright.txt +include LICENSE +include version.txt diff --git a/setup.py b/setup.py index 8c34238ff..aa7b27a67 100644 --- a/setup.py +++ b/setup.py @@ -1,38 +1,41 @@ -# -*- coding: utf-8 -*- -# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 +from setuptools import setup, find_packages +import sys, os -############################################################################### -# OpenLP - Open Source Lyrics Projection # -# --------------------------------------------------------------------------- # -# Copyright (c) 2008-2010 Raoul Snyman # -# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # -# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # -# Carsten Tinggaard # -# --------------------------------------------------------------------------- # -# 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 # -############################################################################### +VERSION_FILE = 'openlp/.version' -from setuptools import setup +try: + from bzrlib.branch import Branch + b = Branch.open_containing('.')[0] + b.lock_read() + try: + revno = b.revno() + # Add the latest tag in here too + finally: + b.unlock() +except: + revno = 0 -APP = ['openlp.pyw'] -OPTIONS = {'argv_emulation': True, 'includes': ['sip', 'PyQt4']} +version = '1.9.1-bzr%s' % revno setup( - name='openlp.org', - version='1.9.0', - url='http://www.openlp.org/', - app=APP, - options={'py2app': OPTIONS}, - setup_requires=['py2app'], -) \ No newline at end of file + name='OpenLP', + version=version, + description="Open source Church presentation and lyrics projection application.", + long_description="""\ +OpenLP (previously openlp.org) is free church presentation software, or lyrics projection software, used to display slides of songs, Bible verses, videos, images, and even presentations (if PowerPoint is installed) for church worship using a computer and a data projector.""", + classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers + keywords='open source church presentation lyrics projection song bible display project', + author='Raoul Snyman', + author_email='raoulsnyman@openlp.org', + url='http://openlp.org/', + license='GNU General Public License', + packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), + include_package_data=True, + zip_safe=False, + install_requires=[ + # -*- Extra requirements: -*- + ], + entry_points=""" + # -*- Entry points: -*- + """ +) From fef898655057d982973103bb19df2cebfe70cb67 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Tue, 16 Feb 2010 17:40:41 +0000 Subject: [PATCH 074/164] Fix theme import bug --- openlp/core/ui/alertstab.py | 2 +- openlp/core/ui/thememanager.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openlp/core/ui/alertstab.py b/openlp/core/ui/alertstab.py index d2e38e048..7efd6acfc 100644 --- a/openlp/core/ui/alertstab.py +++ b/openlp/core/ui/alertstab.py @@ -209,4 +209,4 @@ class AlertsTab(SettingsTab): font.setPointSize(16) self.FontPreview.setFont(font) self.FontPreview.setStyleSheet(u'background-color: %s; color: %s' % \ - (self.bg_color, self.font_color)) \ No newline at end of file + (self.bg_color, self.font_color)) diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index 0bf005891..8ece41ac2 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -236,7 +236,7 @@ class ThemeManager(QtGui.QWidget): log.info(u'New Themes %s', unicode(files)) if len(files) > 0: for file in files: - self.config.set_last_dir(filename) + self.config.set_last_dir(unicode(file)) self.unzipTheme(file, self.path) self.loadThemes() @@ -343,7 +343,7 @@ class ThemeManager(QtGui.QWidget): self, self.trUtf8('Error'), self.trUtf8('File is not a valid theme!'), QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok)) - log.exception(u'Importing theme from zip file failed') + log.exception(u'Importing theme from zip file failed %s' % filename) finally: if zip: zip.close() @@ -400,7 +400,7 @@ class ThemeManager(QtGui.QWidget): newtheme.add_display(unicode(shadow), unicode(theme.ShadowColor.name()), unicode(outline), unicode(theme.OutlineColor.name()), unicode(theme.HorizontalAlign), unicode(theme.VerticalAlign), - unicode(theme.WrapStyle), 0) + unicode(theme.WrapStyle), unicode(0)) return newtheme.extract_xml() def saveTheme(self, name, theme_xml, theme_pretty_xml, image_from, From e0b7c54ddc95ff1c8a34122bab27588339dab741 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Tue, 16 Feb 2010 18:47:26 +0000 Subject: [PATCH 075/164] Minor fixes and unicode changes --- openlp/plugins/alerts/alertsplugin.py | 8 ++-- openlp/plugins/alerts/forms/alertdialog.py | 34 ++++++++-------- .../plugins/alerts/forms/alerteditdialog.py | 40 +++++++++---------- openlp/plugins/custom/lib/manager.py | 2 +- openlp/plugins/images/lib/mediaitem.py | 2 +- openlp/plugins/media/lib/mediaitem.py | 2 +- 6 files changed, 44 insertions(+), 44 deletions(-) diff --git a/openlp/plugins/alerts/alertsplugin.py b/openlp/plugins/alerts/alertsplugin.py index 169e4b506..60e718bb3 100644 --- a/openlp/plugins/alerts/alertsplugin.py +++ b/openlp/plugins/alerts/alertsplugin.py @@ -34,11 +34,11 @@ from openlp.plugins.alerts.forms import AlertsTab, AlertForm, AlertEditForm class alertsPlugin(Plugin): global log - log = logging.getLogger(u'alertsPlugin') - log.info(u'alerts Plugin loaded') + log = logging.getLogger(u'AlertsPlugin') + log.info(u'Alerts Plugin loaded') def __init__(self, plugin_helpers): - Plugin.__init__(self, u'alerts', u'1.9.1', plugin_helpers) + Plugin.__init__(self, u'Alerts', u'1.9.1', plugin_helpers) self.weight = -3 self.icon = build_icon(u':/media/media_image.png') self.alertsmanager = AlertsManager(self) @@ -74,7 +74,7 @@ class alertsPlugin(Plugin): self.toolsAlertItem.setVisible(False) def initialise(self): - log.info(u'alerts Initialising') + log.info(u'Alerts Initialising') Plugin.initialise(self) self.toolsAlertItem.setVisible(True) diff --git a/openlp/plugins/alerts/forms/alertdialog.py b/openlp/plugins/alerts/forms/alertdialog.py index 3e467dd0e..53fc1fff5 100644 --- a/openlp/plugins/alerts/forms/alertdialog.py +++ b/openlp/plugins/alerts/forms/alertdialog.py @@ -11,61 +11,61 @@ from PyQt4 import QtCore, QtGui class Ui_AlertDialog(object): def setupUi(self, AlertForm): - AlertForm.setObjectName("AlertDialog") + AlertForm.setObjectName(u'AlertDialog') AlertForm.resize(430, 320) icon = QtGui.QIcon() - icon.addPixmap(QtGui.QPixmap(":/icon/openlp.org-icon-32.bmp"), QtGui.QIcon.Normal, QtGui.QIcon.Off) + icon.addPixmap(QtGui.QPixmap(u':/icon/openlp.org-icon-32.bmp'), QtGui.QIcon.Normal, QtGui.QIcon.Off) AlertForm.setWindowIcon(icon) self.AlertFormLayout = QtGui.QVBoxLayout(AlertForm) self.AlertFormLayout.setSpacing(8) self.AlertFormLayout.setMargin(8) - self.AlertFormLayout.setObjectName("AlertFormLayout") + self.AlertFormLayout.setObjectName(u'AlertFormLayout') self.AlertEntryWidget = QtGui.QWidget(AlertForm) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.AlertEntryWidget.sizePolicy().hasHeightForWidth()) self.AlertEntryWidget.setSizePolicy(sizePolicy) - self.AlertEntryWidget.setObjectName("AlertEntryWidget") + self.AlertEntryWidget.setObjectName(u'AlertEntryWidget') self.verticalLayout_2 = QtGui.QVBoxLayout(self.AlertEntryWidget) - self.verticalLayout_2.setObjectName("verticalLayout_2") + self.verticalLayout_2.setObjectName(u'verticalLayout_2') self.verticalLayout = QtGui.QVBoxLayout() - self.verticalLayout.setObjectName("verticalLayout") + self.verticalLayout.setObjectName(u'verticalLayout') self.AlertEntryLabel = QtGui.QLabel(self.AlertEntryWidget) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.AlertEntryLabel.sizePolicy().hasHeightForWidth()) self.AlertEntryLabel.setSizePolicy(sizePolicy) - self.AlertEntryLabel.setObjectName("AlertEntryLabel") + self.AlertEntryLabel.setObjectName(u'AlertEntryLabel') self.verticalLayout.addWidget(self.AlertEntryLabel) self.AlertEntryEditItem = QtGui.QLineEdit(self.AlertEntryWidget) - self.AlertEntryEditItem.setObjectName("AlertEntryEditItem") + self.AlertEntryEditItem.setObjectName(u'AlertEntryEditItem') self.verticalLayout.addWidget(self.AlertEntryEditItem) self.AlertListWidget = QtGui.QListWidget(self.AlertEntryWidget) self.AlertListWidget.setAlternatingRowColors(True) - self.AlertListWidget.setObjectName("AlertListWidget") + self.AlertListWidget.setObjectName(u'AlertListWidget') self.verticalLayout.addWidget(self.AlertListWidget) self.verticalLayout_2.addLayout(self.verticalLayout) self.horizontalLayout = QtGui.QHBoxLayout() - self.horizontalLayout.setObjectName("horizontalLayout") + self.horizontalLayout.setObjectName(u'horizontalLayout') spacerItem = QtGui.QSpacerItem(181, 38, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem) self.DisplayButton = QtGui.QPushButton(self.AlertEntryWidget) - self.DisplayButton.setObjectName("DisplayButton") + self.DisplayButton.setObjectName(u'DisplayButton') self.horizontalLayout.addWidget(self.DisplayButton) self.CancelButton = QtGui.QPushButton(self.AlertEntryWidget) - self.CancelButton.setObjectName("CancelButton") + self.CancelButton.setObjectName(u'CancelButton') self.horizontalLayout.addWidget(self.CancelButton) self.verticalLayout_2.addLayout(self.horizontalLayout) self.AlertFormLayout.addWidget(self.AlertEntryWidget) self.retranslateUi(AlertForm) - QtCore.QObject.connect(self.CancelButton, QtCore.SIGNAL("clicked()"), self.close) + QtCore.QObject.connect(self.CancelButton, QtCore.SIGNAL(u'clicked()'), self.close) QtCore.QMetaObject.connectSlotsByName(AlertForm) def retranslateUi(self, AlertForm): - AlertForm.setWindowTitle(QtGui.QApplication.translate("AlertForm", "Alert Message", None, QtGui.QApplication.UnicodeUTF8)) - self.AlertEntryLabel.setText(QtGui.QApplication.translate("AlertForm", "Alert Text:", None, QtGui.QApplication.UnicodeUTF8)) - self.DisplayButton.setText(QtGui.QApplication.translate("AlertForm", "Display", None, QtGui.QApplication.UnicodeUTF8)) - self.CancelButton.setText(QtGui.QApplication.translate("AlertForm", "Cancel", None, QtGui.QApplication.UnicodeUTF8)) + AlertForm.setWindowTitle(self.trUtf8('Alert Message')) + self.AlertEntryLabel.setText(self.trUtf8('Alert Text:')) + self.DisplayButton.setText(self.trUtf8('Display')) + self.CancelButton.setText(self.trUtf8('Cancel')) diff --git a/openlp/plugins/alerts/forms/alerteditdialog.py b/openlp/plugins/alerts/forms/alerteditdialog.py index c382d5352..6cf4769ef 100644 --- a/openlp/plugins/alerts/forms/alerteditdialog.py +++ b/openlp/plugins/alerts/forms/alerteditdialog.py @@ -11,45 +11,45 @@ from PyQt4 import QtCore, QtGui class Ui_AlertEditDialog(object): def setupUi(self, AlertEditDialog): - AlertEditDialog.setObjectName("AlertEditDialog") + AlertEditDialog.setObjectName(u'AlertEditDialog') AlertEditDialog.resize(400, 300) self.buttonBox = QtGui.QDialogButtonBox(AlertEditDialog) self.buttonBox.setGeometry(QtCore.QRect(220, 270, 173, 27)) self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel) - self.buttonBox.setObjectName("buttonBox") + self.buttonBox.setObjectName(u'buttonBox') self.layoutWidget = QtGui.QWidget(AlertEditDialog) self.layoutWidget.setGeometry(QtCore.QRect(20, 10, 361, 251)) - self.layoutWidget.setObjectName("layoutWidget") + self.layoutWidget.setObjectName(u'layoutWidget') self.verticalLayout_2 = QtGui.QVBoxLayout(self.layoutWidget) - self.verticalLayout_2.setObjectName("verticalLayout_2") + self.verticalLayout_2.setObjectName(u'verticalLayout_2') self.horizontalLayout_2 = QtGui.QHBoxLayout() - self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.horizontalLayout_2.setObjectName(u'horizontalLayout_2') self.AlertLineEdit = QtGui.QLineEdit(self.layoutWidget) - self.AlertLineEdit.setObjectName("AlertLineEdit") + self.AlertLineEdit.setObjectName(u'AlertLineEdit') self.horizontalLayout_2.addWidget(self.AlertLineEdit) self.verticalLayout_2.addLayout(self.horizontalLayout_2) self.horizontalLayout = QtGui.QHBoxLayout() - self.horizontalLayout.setObjectName("horizontalLayout") + self.horizontalLayout.setObjectName(u'horizontalLayout') self.AlertListWidget = QtGui.QListWidget(self.layoutWidget) self.AlertListWidget.setAlternatingRowColors(True) - self.AlertListWidget.setObjectName("AlertListWidget") + self.AlertListWidget.setObjectName(u'AlertListWidget') self.horizontalLayout.addWidget(self.AlertListWidget) self.verticalLayout = QtGui.QVBoxLayout() - self.verticalLayout.setObjectName("verticalLayout") + self.verticalLayout.setObjectName(u'verticalLayout') self.SaveButton = QtGui.QPushButton(self.layoutWidget) - self.SaveButton.setObjectName("SaveButton") + self.SaveButton.setObjectName(u'SaveButton') self.verticalLayout.addWidget(self.SaveButton) self.ClearButton = QtGui.QPushButton(self.layoutWidget) - self.ClearButton.setObjectName("ClearButton") + self.ClearButton.setObjectName(u'ClearButton') self.verticalLayout.addWidget(self.ClearButton) self.AddButton = QtGui.QPushButton(self.layoutWidget) - self.AddButton.setObjectName("AddButton") + self.AddButton.setObjectName(u'AddButton') self.verticalLayout.addWidget(self.AddButton) self.EditButton = QtGui.QPushButton(self.layoutWidget) - self.EditButton.setObjectName("EditButton") + self.EditButton.setObjectName(u'EditButton') self.verticalLayout.addWidget(self.EditButton) self.DeleteButton = QtGui.QPushButton(self.layoutWidget) - self.DeleteButton.setObjectName("DeleteButton") + self.DeleteButton.setObjectName(u'DeleteButton') self.verticalLayout.addWidget(self.DeleteButton) self.horizontalLayout.addLayout(self.verticalLayout) self.verticalLayout_2.addLayout(self.horizontalLayout) @@ -58,10 +58,10 @@ class Ui_AlertEditDialog(object): QtCore.QMetaObject.connectSlotsByName(AlertEditDialog) def retranslateUi(self, AlertEditDialog): - AlertEditDialog.setWindowTitle(QtGui.QApplication.translate("AlertEditDialog", "Maintain Alerts", None, QtGui.QApplication.UnicodeUTF8)) - self.SaveButton.setText(QtGui.QApplication.translate("AlertEditDialog", "Save", None, QtGui.QApplication.UnicodeUTF8)) - self.ClearButton.setText(QtGui.QApplication.translate("AlertEditDialog", "Clear", None, QtGui.QApplication.UnicodeUTF8)) - self.AddButton.setText(QtGui.QApplication.translate("AlertEditDialog", "Add", None, QtGui.QApplication.UnicodeUTF8)) - self.EditButton.setText(QtGui.QApplication.translate("AlertEditDialog", "Edit", None, QtGui.QApplication.UnicodeUTF8)) - self.DeleteButton.setText(QtGui.QApplication.translate("AlertEditDialog", "Delete", None, QtGui.QApplication.UnicodeUTF8)) + AlertEditDialog.setWindowTitle(self.trUtf8('Maintain Alerts')) + self.SaveButton.setText(self.trUtf8('Save')) + self.ClearButton.setText(self.trUtf8('Clear')) + self.AddButton.setText(self.trUtf8('Add')) + self.EditButton.setText(self.trUtf8('Edit')) + self.DeleteButton.setText(self.trUtf8('Delete')) diff --git a/openlp/plugins/custom/lib/manager.py b/openlp/plugins/custom/lib/manager.py index 95ac873bd..d1d3e0349 100644 --- a/openlp/plugins/custom/lib/manager.py +++ b/openlp/plugins/custom/lib/manager.py @@ -78,7 +78,7 @@ class CustomManager(): return True except: self.session.rollback() - log.exceptiontion(u'Custom Slide save failed') + log.exception(u'Custom Slide save failed') return False def get_custom(self, id=None): diff --git a/openlp/plugins/images/lib/mediaitem.py b/openlp/plugins/images/lib/mediaitem.py index 8ea1df64b..05d55ddb6 100644 --- a/openlp/plugins/images/lib/mediaitem.py +++ b/openlp/plugins/images/lib/mediaitem.py @@ -172,6 +172,6 @@ class ImageMediaItem(MediaManagerItem): filename = unicode((bitem.data(QtCore.Qt.UserRole)).toString()) self.OverrideLabel.setText(bitem.text()) frame = QtGui.QImage(unicode(filename)) - self.parent.live_controller.parent.mainDisplay.addImageWithText(frame) + self.parent.mainDisplay.addImageWithText(frame) else: MediaManagerItem.onPreviewClick(self) diff --git a/openlp/plugins/media/lib/mediaitem.py b/openlp/plugins/media/lib/mediaitem.py index c2dbc5d93..e8a4da198 100644 --- a/openlp/plugins/media/lib/mediaitem.py +++ b/openlp/plugins/media/lib/mediaitem.py @@ -54,7 +54,7 @@ class MediaMediaItem(MediaManagerItem): self.PreviewFunction = self.video_get_preview MediaManagerItem.__init__(self, parent, icon, title) self.ServiceItemIconName = u':/media/media_video.png' - self.MainDisplay = self.parent.live_controller.parent.mainDisplay + self.MainDisplay = self.parent.mainDisplay def initPluginNameVisible(self): self.PluginNameVisible = self.trUtf8('Media') From 04d79f72a1aeef20659ac4216254733c2d5e92ee Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Tue, 16 Feb 2010 18:51:41 +0000 Subject: [PATCH 076/164] Fix typos --- openlp/plugins/images/lib/mediaitem.py | 2 +- openlp/plugins/media/lib/mediaitem.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openlp/plugins/images/lib/mediaitem.py b/openlp/plugins/images/lib/mediaitem.py index 05d55ddb6..145e07f04 100644 --- a/openlp/plugins/images/lib/mediaitem.py +++ b/openlp/plugins/images/lib/mediaitem.py @@ -172,6 +172,6 @@ class ImageMediaItem(MediaManagerItem): filename = unicode((bitem.data(QtCore.Qt.UserRole)).toString()) self.OverrideLabel.setText(bitem.text()) frame = QtGui.QImage(unicode(filename)) - self.parent.mainDisplay.addImageWithText(frame) + self.parent.maindisplay.addImageWithText(frame) else: MediaManagerItem.onPreviewClick(self) diff --git a/openlp/plugins/media/lib/mediaitem.py b/openlp/plugins/media/lib/mediaitem.py index e8a4da198..46b059c01 100644 --- a/openlp/plugins/media/lib/mediaitem.py +++ b/openlp/plugins/media/lib/mediaitem.py @@ -54,7 +54,7 @@ class MediaMediaItem(MediaManagerItem): self.PreviewFunction = self.video_get_preview MediaManagerItem.__init__(self, parent, icon, title) self.ServiceItemIconName = u':/media/media_video.png' - self.MainDisplay = self.parent.mainDisplay + self.MainDisplay = self.parent.maindisplay def initPluginNameVisible(self): self.PluginNameVisible = self.trUtf8('Media') From 959c2469a5c751b4c81169cb4e32d88ad491997f Mon Sep 17 00:00:00 2001 From: Jonathan Corwin <j@corwin.co.uk> Date: Tue, 16 Feb 2010 22:28:35 +0000 Subject: [PATCH 077/164] Fix PowerPoint and PowerPoint viewer screen lookup --- openlp/plugins/presentations/lib/powerpointcontroller.py | 2 +- openlp/plugins/presentations/lib/pptviewcontroller.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openlp/plugins/presentations/lib/powerpointcontroller.py b/openlp/plugins/presentations/lib/powerpointcontroller.py index 18b644112..a9775e086 100644 --- a/openlp/plugins/presentations/lib/powerpointcontroller.py +++ b/openlp/plugins/presentations/lib/powerpointcontroller.py @@ -208,7 +208,7 @@ class PowerpointController(PresentationController): self.presentation.SlideShowSettings.Run() self.presentation.SlideShowWindow.View.GotoSlide(1) rendermanager = self.plugin.render_manager - rect = rendermanager.screen_list[rendermanager.current_display][u'size'] + rect = rendermanager.screens.current[u'size'] self.presentation.SlideShowWindow.Top = rect.y() * 72 / dpi self.presentation.SlideShowWindow.Height = rect.height() * 72 / dpi self.presentation.SlideShowWindow.Left = rect.x() * 72 / dpi diff --git a/openlp/plugins/presentations/lib/pptviewcontroller.py b/openlp/plugins/presentations/lib/pptviewcontroller.py index 0cf2405f1..2ed457fc0 100644 --- a/openlp/plugins/presentations/lib/pptviewcontroller.py +++ b/openlp/plugins/presentations/lib/pptviewcontroller.py @@ -108,7 +108,7 @@ class PptviewController(PresentationController): if self.pptid >= 0: self.close_presentation() rendermanager = self.plugin.render_manager - rect = rendermanager.screen_list[rendermanager.current_display][u'size'] + rect = rendermanager.screens.current[u'size'] rect = RECT(rect.x(), rect.y(), rect.right(), rect.bottom()) filepath = str(presentation.replace(u'/', u'\\')); try: From 711bd3cf00fc1e4afeb3d9382992ea797b9f550c Mon Sep 17 00:00:00 2001 From: Jon Tibble <meths@btinternet.com> Date: Wed, 17 Feb 2010 19:13:04 +0000 Subject: [PATCH 078/164] Import fixes and copyright insertion --- openlp/plugins/alerts/alertsplugin.py | 3 +-- openlp/plugins/alerts/forms/alerteditform.py | 2 -- openlp/plugins/alerts/forms/alertform.py | 3 +-- openlp/plugins/alerts/lib/alertsmanager.py | 27 +++++++++++++++++-- .../songusage/forms/songusagedeleteform.py | 2 -- 5 files changed, 27 insertions(+), 10 deletions(-) diff --git a/openlp/plugins/alerts/alertsplugin.py b/openlp/plugins/alerts/alertsplugin.py index 60e718bb3..5bf268394 100644 --- a/openlp/plugins/alerts/alertsplugin.py +++ b/openlp/plugins/alerts/alertsplugin.py @@ -23,12 +23,11 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### -from datetime import datetime import logging from PyQt4 import QtCore, QtGui -from openlp.core.lib import Plugin, Receiver, str_to_bool, build_icon, PluginStatus +from openlp.core.lib import Plugin, build_icon, PluginStatus from openlp.plugins.alerts.lib import AlertsManager, DBManager from openlp.plugins.alerts.forms import AlertsTab, AlertForm, AlertEditForm diff --git a/openlp/plugins/alerts/forms/alerteditform.py b/openlp/plugins/alerts/forms/alerteditform.py index fef8a04e5..4abc8a660 100644 --- a/openlp/plugins/alerts/forms/alerteditform.py +++ b/openlp/plugins/alerts/forms/alerteditform.py @@ -23,8 +23,6 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### -from datetime import date - from PyQt4 import QtGui, QtCore from openlp.plugins.alerts.lib.models import AlertItem diff --git a/openlp/plugins/alerts/forms/alertform.py b/openlp/plugins/alerts/forms/alertform.py index 26f78e9b2..2d7dd1c21 100644 --- a/openlp/plugins/alerts/forms/alertform.py +++ b/openlp/plugins/alerts/forms/alertform.py @@ -23,9 +23,8 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### -from datetime import date - from PyQt4 import QtGui, QtCore + from openlp.plugins.alerts.lib.models import AlertItem from alertdialog import Ui_AlertDialog diff --git a/openlp/plugins/alerts/lib/alertsmanager.py b/openlp/plugins/alerts/lib/alertsmanager.py index 1f4369588..5ed48f939 100644 --- a/openlp/plugins/alerts/lib/alertsmanager.py +++ b/openlp/plugins/alerts/lib/alertsmanager.py @@ -1,10 +1,33 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard # +# --------------------------------------------------------------------------- # +# 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 # +############################################################################### import logging from PyQt4 import QtCore, QtGui -from openlp.core.lib import str_to_bool, Receiver -from openlp.core.lib import SettingsTab +from openlp.core.lib import Receiver class AlertsManager(QtCore.QObject): """ diff --git a/openlp/plugins/songusage/forms/songusagedeleteform.py b/openlp/plugins/songusage/forms/songusagedeleteform.py index 26fe2b7e2..56eb1954a 100644 --- a/openlp/plugins/songusage/forms/songusagedeleteform.py +++ b/openlp/plugins/songusage/forms/songusagedeleteform.py @@ -23,8 +23,6 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### -from datetime import date - from PyQt4 import QtGui from songusagedeletedialog import Ui_SongUsageDeleteDialog From 516b05e0ad526a5fec72875a327a6fd1e3719cf5 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Wed, 17 Feb 2010 20:17:33 +0000 Subject: [PATCH 079/164] Fix URF8, copyright and alert error --- openlp/core/ui/maindisplay.py | 2 +- openlp/plugins/alerts/lib/alertsmanager.py | 2 + .../songusage/forms/songusagedetaildialog.py | 78 ++++++++++++------- 3 files changed, 53 insertions(+), 29 deletions(-) diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index 1ba31ea62..00470c9f6 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -203,7 +203,7 @@ class MainDisplay(DisplayWidget): if not self.primary: self.setVisible(True) self.showFullScreen() - self.generateAlert() + Receiver.send_message(u'flush_alert') def addImageWithText(self, frame): frame = resize_image(frame, diff --git a/openlp/plugins/alerts/lib/alertsmanager.py b/openlp/plugins/alerts/lib/alertsmanager.py index 1f4369588..759edb12c 100644 --- a/openlp/plugins/alerts/lib/alertsmanager.py +++ b/openlp/plugins/alerts/lib/alertsmanager.py @@ -19,6 +19,8 @@ class AlertsManager(QtCore.QObject): self.parent = parent self.timer_id = 0 self.alertList = [] + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'flush_alert'), self.generateAlert) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'alert_text'), self.displayAlert) QtCore.QObject.connect(Receiver.get_receiver(), diff --git a/openlp/plugins/songusage/forms/songusagedetaildialog.py b/openlp/plugins/songusage/forms/songusagedetaildialog.py index 63866d1fd..d6ba2ecfb 100644 --- a/openlp/plugins/songusage/forms/songusagedetaildialog.py +++ b/openlp/plugins/songusage/forms/songusagedetaildialog.py @@ -1,69 +1,91 @@ # -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 -# Form implementation generated from reading ui file 'songusagedetaildialog.ui' -# -# Created: Tue Feb 9 07:34:05 2010 -# by: PyQt4 UI code generator 4.6.2 -# -# WARNING! All changes made in this file will be lost! +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard # +# --------------------------------------------------------------------------- # +# 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 # +############################################################################### from PyQt4 import QtCore, QtGui class Ui_SongUsageDetailDialog(object): def setupUi(self, AuditDetailDialog): - AuditDetailDialog.setObjectName("AuditDetailDialog") + AuditDetailDialog.setObjectName(u'AuditDetailDialog') AuditDetailDialog.resize(609, 413) self.verticalLayout = QtGui.QVBoxLayout(AuditDetailDialog) - self.verticalLayout.setObjectName("verticalLayout") + self.verticalLayout.setObjectName(u'verticalLayout') self.DateRangeGroupBox = QtGui.QGroupBox(AuditDetailDialog) - self.DateRangeGroupBox.setObjectName("DateRangeGroupBox") + self.DateRangeGroupBox.setObjectName(u'DateRangeGroupBox') self.verticalLayout_2 = QtGui.QVBoxLayout(self.DateRangeGroupBox) - self.verticalLayout_2.setObjectName("verticalLayout_2") + self.verticalLayout_2.setObjectName(u'verticalLayout_2') self.DateHorizontalLayout = QtGui.QHBoxLayout() - self.DateHorizontalLayout.setObjectName("DateHorizontalLayout") + self.DateHorizontalLayout.setObjectName(u'DateHorizontalLayout') self.FromDate = QtGui.QCalendarWidget(self.DateRangeGroupBox) - self.FromDate.setObjectName("FromDate") + self.FromDate.setObjectName(u'FromDate') self.DateHorizontalLayout.addWidget(self.FromDate) self.ToLabel = QtGui.QLabel(self.DateRangeGroupBox) self.ToLabel.setScaledContents(False) self.ToLabel.setAlignment(QtCore.Qt.AlignCenter) - self.ToLabel.setObjectName("ToLabel") + self.ToLabel.setObjectName(u'ToLabel') self.DateHorizontalLayout.addWidget(self.ToLabel) self.ToDate = QtGui.QCalendarWidget(self.DateRangeGroupBox) - self.ToDate.setObjectName("ToDate") + self.ToDate.setObjectName(u'ToDate') self.DateHorizontalLayout.addWidget(self.ToDate) self.verticalLayout_2.addLayout(self.DateHorizontalLayout) self.FileGroupBox = QtGui.QGroupBox(self.DateRangeGroupBox) - self.FileGroupBox.setObjectName("FileGroupBox") + self.FileGroupBox.setObjectName(u'FileGroupBox') self.verticalLayout_4 = QtGui.QVBoxLayout(self.FileGroupBox) - self.verticalLayout_4.setObjectName("verticalLayout_4") + self.verticalLayout_4.setObjectName(u'verticalLayout_4') self.horizontalLayout = QtGui.QHBoxLayout() - self.horizontalLayout.setObjectName("horizontalLayout") + self.horizontalLayout.setObjectName(u'horizontalLayout') self.FileLineEdit = QtGui.QLineEdit(self.FileGroupBox) - self.FileLineEdit.setObjectName("FileLineEdit") + self.FileLineEdit.setObjectName(u'FileLineEdit') self.horizontalLayout.addWidget(self.FileLineEdit) self.SaveFilePushButton = QtGui.QPushButton(self.FileGroupBox) icon = QtGui.QIcon() - icon.addPixmap(QtGui.QPixmap(":/exports/export_load.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) + icon.addPixmap(QtGui.QPixmap(u':/exports/export_load.png'), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.SaveFilePushButton.setIcon(icon) - self.SaveFilePushButton.setObjectName("SaveFilePushButton") + self.SaveFilePushButton.setObjectName(u'SaveFilePushButton') self.horizontalLayout.addWidget(self.SaveFilePushButton) self.verticalLayout_4.addLayout(self.horizontalLayout) self.verticalLayout_2.addWidget(self.FileGroupBox) self.verticalLayout.addWidget(self.DateRangeGroupBox) self.buttonBox = QtGui.QDialogButtonBox(AuditDetailDialog) self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok) - self.buttonBox.setObjectName("buttonBox") + self.buttonBox.setObjectName(u'buttonBox') self.verticalLayout.addWidget(self.buttonBox) self.retranslateUi(AuditDetailDialog) - QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL("accepted()"), AuditDetailDialog.accept) - QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL("rejected()"), AuditDetailDialog.close) - QtCore.QObject.connect(self.SaveFilePushButton, QtCore.SIGNAL("pressed()"), AuditDetailDialog.defineOutputLocation) + QtCore.QObject.connect(self.buttonBox, + QtCore.SIGNAL(u'accepted()'), + AuditDetailDialog.accept) + QtCore.QObject.connect(self.buttonBox, + QtCore.SIGNAL(u'rejected()'), + AuditDetailDialog.close) + QtCore.QObject.connect(self.SaveFilePushButton, + QtCore.SIGNAL(u'pressed()'), + AuditDetailDialog.defineOutputLocation) QtCore.QMetaObject.connectSlotsByName(AuditDetailDialog) def retranslateUi(self, AuditDetailDialog): - AuditDetailDialog.setWindowTitle(QtGui.QApplication.translate("AuditDetailDialog", "Audit Detail Extraction", None, QtGui.QApplication.UnicodeUTF8)) - self.DateRangeGroupBox.setTitle(QtGui.QApplication.translate("AuditDetailDialog", "Select Date Range", None, QtGui.QApplication.UnicodeUTF8)) - self.ToLabel.setText(QtGui.QApplication.translate("AuditDetailDialog", "to", None, QtGui.QApplication.UnicodeUTF8)) - self.FileGroupBox.setTitle(QtGui.QApplication.translate("AuditDetailDialog", "Report Location", None, QtGui.QApplication.UnicodeUTF8)) + AuditDetailDialog.setWindowTitle(self.trUtf8('Audit Detail Extraction')) + self.DateRangeGroupBox.setTitle(self.trUtf8('ASelect Date Range')) + self.ToLabel.setText(self.trUtf8('to')) + self.FileGroupBox.setTitle(self.trUtf8('Report Location')) From cf62d6187f75353e41d4a7ca5653fc2f38ef98cf Mon Sep 17 00:00:00 2001 From: Jonathan Corwin <j@corwin.co.uk> Date: Wed, 17 Feb 2010 22:57:59 +0000 Subject: [PATCH 080/164] More presentation fixes/tidies --- .../presentations/lib/impresscontroller.py | 6 +++--- .../presentations/lib/messagelistener.py | 20 ++++++++----------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/openlp/plugins/presentations/lib/impresscontroller.py b/openlp/plugins/presentations/lib/impresscontroller.py index b58a9affc..31b3ac5f6 100644 --- a/openlp/plugins/presentations/lib/impresscontroller.py +++ b/openlp/plugins/presentations/lib/impresscontroller.py @@ -201,7 +201,7 @@ class ImpressController(PresentationController): try: ctx = resolver.resolve(u'uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext') except: - log.exception(u'Unable to fine running instance ') + log.exception(u'Unable to find running instance ') self.start_process() loop += 1 try: @@ -233,8 +233,8 @@ class ImpressController(PresentationController): def close_presentation(self): """ Close presentation and clean up objects - Triggerent by new object being added to SlideController orOpenLP - being shut down + Triggered by new object being added to SlideController or OpenLP + being shutdown """ log.debug(u'close Presentation OpenOffice') if self.document: diff --git a/openlp/plugins/presentations/lib/messagelistener.py b/openlp/plugins/presentations/lib/messagelistener.py index 0b2fd6003..3c665004b 100644 --- a/openlp/plugins/presentations/lib/messagelistener.py +++ b/openlp/plugins/presentations/lib/messagelistener.py @@ -67,8 +67,8 @@ class Controller(object): def slide(self, slide, live): log.debug(u'Live = %s, slide' % live) -# if not isLive: -# return + if not live: + return self.activate() self.controller.goto_slide(int(slide) + 1) self.controller.poll_slidenumber(live) @@ -136,11 +136,13 @@ class Controller(object): self.controller.blank_screen() def unblank(self): - if not self.is_live: + if not self.isLive: return self.activate() self.controller.unblank_screen() + def poll(self): + self.controller.poll_slidenumber(self.isLive) class MessageListener(object): """ @@ -229,16 +231,10 @@ class MessageListener(object): self.previewHandler.shutdown() def blank(self): - if self.isLive: - self.liveHandler.blank() - else: - self.previewHandler.blank() + self.liveHandler.blank() def unblank(self): - if self.isLive: - self.liveHandler.unblank() - else: - self.previewHandler.unblank() + self.liveHandler.unblank() def splitMessage(self, message): """ @@ -263,4 +259,4 @@ class MessageListener(object): return message[0], file, message[4] def timeout(self): - self.controller.poll_slidenumber(self.is_live) + self.liveHandler.poll() From 77e55da0d33677f5ad0e2dbac733185df39a19a5 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Fri, 19 Feb 2010 21:14:39 +0200 Subject: [PATCH 081/164] Pulling in the tags and the revision number from bzr. --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index aa7b27a67..457e8a556 100644 --- a/setup.py +++ b/setup.py @@ -10,6 +10,7 @@ try: try: revno = b.revno() # Add the latest tag in here too + verno = b.tags.get_tag_dict().keys()[0] finally: b.unlock() except: From 7efd037024290641e3ca344bf5e0bad68fc0ddd1 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Fri, 19 Feb 2010 21:17:32 +0200 Subject: [PATCH 082/164] Pull the version number from the tag. --- setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 457e8a556..d891b7bbf 100644 --- a/setup.py +++ b/setup.py @@ -8,15 +8,15 @@ try: b = Branch.open_containing('.')[0] b.lock_read() try: - revno = b.revno() - # Add the latest tag in here too verno = b.tags.get_tag_dict().keys()[0] + revno = b.revno() finally: b.unlock() except: + verno = '1.9.0' revno = 0 -version = '1.9.1-bzr%s' % revno +version = '%s-bzr%s' % (verno, revno) setup( name='OpenLP', From 8182b418b732f8b97dda16d5dd68f000ce40c819 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Fri, 19 Feb 2010 21:39:52 +0200 Subject: [PATCH 083/164] Updated version.txt to the latest revision. bzr-revno: 709 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 97fc2f582..6fbb27109 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.9.0-707 +1.9.0-709 From 70251a4321ebe8150eb620141d6df026c3e0a2df Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Sun, 21 Feb 2010 08:32:43 +0200 Subject: [PATCH 084/164] - Created independant "get_config_directory" and "get_data_directory" methods. - Moved log file to configuration file location for non-root/administrator users. --- openlp.pyw | 4 +-- openlp/core/utils/__init__.py | 46 ++++++++++++++++++++++++++----- openlp/core/utils/confighelper.py | 33 ++++------------------ 3 files changed, 47 insertions(+), 36 deletions(-) diff --git a/openlp.pyw b/openlp.pyw index d35591a1c..5c18486b6 100755 --- a/openlp.pyw +++ b/openlp.pyw @@ -35,7 +35,7 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import Receiver, str_to_bool from openlp.core.resources import qInitResources from openlp.core.ui import MainWindow, SplashScreen, ScreenList -from openlp.core.utils import ConfigHelper +from openlp.core.utils import get_config_directory, ConfigHelper log = logging.getLogger() @@ -158,7 +158,7 @@ def main(): parser.add_option("-s", "--style", dest="style", help="Set the Qt4 style (passed directly to Qt4).") # Set up logging - filename = u'openlp.log' + filename = os.path.join(get_config_directory(), u'openlp.log') logfile = FileHandler(filename, u'w') logfile.setFormatter(logging.Formatter( u'%(asctime)s %(name)-15s %(levelname)-8s %(message)s')) diff --git a/openlp/core/utils/__init__.py b/openlp/core/utils/__init__.py index 9504c771e..e85b2d939 100644 --- a/openlp/core/utils/__init__.py +++ b/openlp/core/utils/__init__.py @@ -22,17 +22,12 @@ # with this program; if not, write to the Free Software Foundation, Inc., 59 # # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### + +import os import logging import urllib2 from datetime import datetime -from registry import Registry -from confighelper import ConfigHelper - -log = logging.getLogger(__name__) - -__all__ = ['Registry', 'ConfigHelper'] - log = logging.getLogger(__name__) def check_latest_version(config, current_version): @@ -54,3 +49,40 @@ def check_latest_version(config, current_version): if hasattr(e, u'reason'): log.exception(u'Reason for failure: %s', e.reason) return version_string + +def get_config_directory(): + path = u'' + if os.name == u'nt': + path = os.path.join(os.getenv(u'APPDATA'), u'openlp') + elif os.name == u'mac': + path = os.path.join(os.getenv(u'HOME'), u'Library', + u'Application Support', u'openlp') + else: + try: + from xdg import BaseDirectory + path = os.path.join(BaseDirectory.xdg_config_home, u'openlp') + except ImportError: + path = os.path.join(os.getenv(u'HOME'), u'.openlp') + return path + +def get_data_directory(): + path = u'' + if os.name == u'nt': + # ask OS for path to application data, set on Windows XP and Vista + path = os.path.join(os.getenv(u'APPDATA'), u'openlp', u'data') + elif os.name == u'mac': + path = os.path.join(os.getenv(u'HOME'), u'Library', + u'Application Support', u'openlp', u'Data') + else: + try: + from xdg import BaseDirectory + path = os.path.join(BaseDirectory.xdg_data_home, u'openlp') + except ImportError: + path = os.path.join(os.getenv(u'HOME'), u'.openlp', u'data') + return path + +from registry import Registry +from confighelper import ConfigHelper + +__all__ = [u'Registry', u'ConfigHelper', u'get_config_directory', + u'get_data_directory', u'check_latest_version'] diff --git a/openlp/core/utils/confighelper.py b/openlp/core/utils/confighelper.py index 112712675..d49157f55 100644 --- a/openlp/core/utils/confighelper.py +++ b/openlp/core/utils/confighelper.py @@ -24,6 +24,8 @@ ############################################################################### import os + +from openlp.core.utils import get_data_directory, get_config_directory from openlp.core.utils.registry import Registry class ConfigHelper(object): @@ -34,20 +36,7 @@ class ConfigHelper(object): @staticmethod def get_data_path(): - if os.name == u'nt': - # ask OS for path to application data, set on Windows XP and Vista - path = os.path.join(os.getenv(u'APPDATA'), u'openlp', u'data') - elif os.name == u'mac': - path = os.path.join(os.getenv(u'HOME'), u'Library', - u'Application Support', u'openlp', u'Data') - else: - try: - from xdg import BaseDirectory - path = os.path.join(BaseDirectory.xdg_data_home, u'openlp') - except ImportError: - path = os.path.join(os.getenv(u'HOME'), u'.openlp', u'data') - #reg = ConfigHelper.get_registry() - #path = ConfigHelper.get_config(u'main', 'data path', path) + path = get_data_directory() if not os.path.exists(path): os.makedirs(path) return path @@ -81,17 +70,7 @@ class ConfigHelper(object): current operating system, and returns an instantiation of that class. """ if ConfigHelper.__registry__ is None: - config_path = u'' - if os.name == u'nt': - config_path = os.path.join(os.getenv(u'APPDATA'), u'openlp') - elif os.name == u'mac': - config_path = os.path.join(os.getenv(u'HOME'), u'Library', - u'Application Support', u'openlp') - else: - try: - from xdg import BaseDirectory - config_path = os.path.join(BaseDirectory.xdg_config_home, u'openlp') - except ImportError: - config_path = os.path.join(os.getenv(u'HOME'), u'.openlp') + config_path = get_config_directory() ConfigHelper.__registry__ = Registry(config_path) - return ConfigHelper.__registry__ \ No newline at end of file + return ConfigHelper.__registry__ + From 174a85d96959c6f35f30cbc81da21faf4dff01aa Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Sun, 21 Feb 2010 12:28:35 +0200 Subject: [PATCH 085/164] Couple more visual tweaks, to make OpenLP look good in Windows. Print the location of file OpenLP is logging to in debug mode. --- openlp.pyw | 7 +++++-- openlp/core/ui/mainwindow.py | 8 ++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/openlp.pyw b/openlp.pyw index 5c18486b6..c7dbb77fe 100755 --- a/openlp.pyw +++ b/openlp.pyw @@ -47,9 +47,11 @@ QMainWindow::separator QDockWidget::title { - border: none; + /*background: palette(dark);*/ + border: 1px solid palette(dark); padding-left: 5px; - padding-top: 3px; + padding-top: 2px; + margin: 1px 0; } QToolBar @@ -169,6 +171,7 @@ def main(): qt_args = [] if options.loglevel.lower() in ['d', 'debug']: log.setLevel(logging.DEBUG) + print 'Logging to:', filename elif options.loglevel.lower() in ['w', 'warning']: log.setLevel(logging.WARNING) else: diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index df66d5e97..2e62398d9 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -39,16 +39,16 @@ from openlp.core.utils import check_latest_version media_manager_style = """ QToolBox::tab { background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - stop: 0 palette(midlight), stop: 1.0 palette(mid)); + stop: 0 palette(button), stop: 1.0 palette(dark)); border-width: 1px; border-style: outset; - border-color: palette(midlight); + border-color: palette(dark); border-radius: 5px; } QToolBox::tab:selected { background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - stop: 0 palette(light), stop: 1.0 palette(mid)); - border-color: palette(light); + stop: 0 palette(light), stop: 1.0 palette(button)); + border-color: palette(dark); } """ class versionThread(QtCore.QThread): From 6f4b4b2616cb3d7eb18920a087f066fec0133e14 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Sun, 21 Feb 2010 14:43:03 +0200 Subject: [PATCH 086/164] Moved all "get_xxxxx_directory" functions into one "AppLocation" class. Set the plugin manager to use this new AppLocation class as well. --- openlp.pyw | 5 +- openlp/core/lib/pluginmanager.py | 2 +- openlp/core/ui/mainwindow.py | 6 +-- openlp/core/utils/__init__.py | 82 ++++++++++++++++++------------- openlp/core/utils/confighelper.py | 6 +-- 5 files changed, 58 insertions(+), 43 deletions(-) diff --git a/openlp.pyw b/openlp.pyw index c7dbb77fe..d748b339a 100755 --- a/openlp.pyw +++ b/openlp.pyw @@ -35,7 +35,7 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import Receiver, str_to_bool from openlp.core.resources import qInitResources from openlp.core.ui import MainWindow, SplashScreen, ScreenList -from openlp.core.utils import get_config_directory, ConfigHelper +from openlp.core.utils import AppLocation, ConfigHelper log = logging.getLogger() @@ -160,7 +160,8 @@ def main(): parser.add_option("-s", "--style", dest="style", help="Set the Qt4 style (passed directly to Qt4).") # Set up logging - filename = os.path.join(get_config_directory(), u'openlp.log') + filename = os.path.join(AppLocation.get_directory(AppLocation.ConfigDir), + u'openlp.log') logfile = FileHandler(filename, u'w') logfile.setFormatter(logging.Formatter( u'%(asctime)s %(name)-15s %(levelname)-8s %(message)s')) diff --git a/openlp/core/lib/pluginmanager.py b/openlp/core/lib/pluginmanager.py index b06f23953..512b47870 100644 --- a/openlp/core/lib/pluginmanager.py +++ b/openlp/core/lib/pluginmanager.py @@ -77,7 +77,7 @@ class PluginManager(object): if name.endswith(u'.py') and not name.startswith(u'__'): path = os.path.abspath(os.path.join(root, name)) thisdepth = len(path.split(os.sep)) - if thisdepth-startdepth > 2: + if thisdepth - startdepth > 2: # skip anything lower down continue modulename, pyext = os.path.splitext(path) diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 2e62398d9..88575b5ec 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -34,7 +34,7 @@ from openlp.core.ui import AboutForm, SettingsForm, \ PluginForm, MediaDockManager from openlp.core.lib import RenderManager, PluginConfig, build_icon, \ OpenLPDockWidget, SettingsManager, PluginManager, Receiver, str_to_bool -from openlp.core.utils import check_latest_version +from openlp.core.utils import check_latest_version, AppLocation media_manager_style = """ QToolBox::tab { @@ -434,9 +434,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): self.aboutForm = AboutForm(self, applicationVersion) self.settingsForm = SettingsForm(self.screens, self, self) # Set up the path with plugins - pluginpath = os.path.split(os.path.abspath(__file__))[0] - pluginpath = os.path.abspath( - os.path.join(pluginpath, u'..', u'..', u'plugins')) + pluginpath = AppLocation.get_directory(AppLocation.PluginsDir) self.plugin_manager = PluginManager(pluginpath) self.plugin_helpers = {} # Set up the interface diff --git a/openlp/core/utils/__init__.py b/openlp/core/utils/__init__.py index e85b2d939..187d85cd2 100644 --- a/openlp/core/utils/__init__.py +++ b/openlp/core/utils/__init__.py @@ -24,12 +24,60 @@ ############################################################################### import os +import sys import logging import urllib2 from datetime import datetime log = logging.getLogger(__name__) +class AppLocation(object): + """ + Retrieve a directory based on the directory type. + """ + AppDir = 1 + ConfigDir = 2 + DataDir = 3 + PluginsDir = 4 + + @staticmethod + def get_directory(dir_type): + if dir_type == AppLocation.AppDir: + return os.path.abspath(os.path.split(sys.argv[0])[0]) + elif dir_type == AppLocation.ConfigDir: + if os.name == u'nt': + path = os.path.join(os.getenv(u'APPDATA'), u'openlp') + elif os.name == u'mac': + path = os.path.join(os.getenv(u'HOME'), u'Library', + u'Application Support', u'openlp') + else: + try: + from xdg import BaseDirectory + path = os.path.join(BaseDirectory.xdg_config_home, u'openlp') + except ImportError: + path = os.path.join(os.getenv(u'HOME'), u'.openlp') + return path + elif dir_type == AppLocation.DataDir: + if os.name == u'nt': + path = os.path.join(os.getenv(u'APPDATA'), u'openlp', u'data') + elif os.name == u'mac': + path = os.path.join(os.getenv(u'HOME'), u'Library', + u'Application Support', u'openlp', u'Data') + else: + try: + from xdg import BaseDirectory + path = os.path.join(BaseDirectory.xdg_data_home, u'openlp') + except ImportError: + path = os.path.join(os.getenv(u'HOME'), u'.openlp', u'data') + return path + elif dir_type == AppLocation.PluginsDir: + app_path = os.path.abspath(os.path.split(sys.argv[0])[0]) + if hasattr(sys, u'frozen') and sys.frozen == 1: + return os.path.join(app_path, u'plugins') + else: + return os.path.join(app_path, u'openlp', u'plugins') + + def check_latest_version(config, current_version): version_string = current_version #set to prod in the distribution confif file. @@ -50,39 +98,7 @@ def check_latest_version(config, current_version): log.exception(u'Reason for failure: %s', e.reason) return version_string -def get_config_directory(): - path = u'' - if os.name == u'nt': - path = os.path.join(os.getenv(u'APPDATA'), u'openlp') - elif os.name == u'mac': - path = os.path.join(os.getenv(u'HOME'), u'Library', - u'Application Support', u'openlp') - else: - try: - from xdg import BaseDirectory - path = os.path.join(BaseDirectory.xdg_config_home, u'openlp') - except ImportError: - path = os.path.join(os.getenv(u'HOME'), u'.openlp') - return path - -def get_data_directory(): - path = u'' - if os.name == u'nt': - # ask OS for path to application data, set on Windows XP and Vista - path = os.path.join(os.getenv(u'APPDATA'), u'openlp', u'data') - elif os.name == u'mac': - path = os.path.join(os.getenv(u'HOME'), u'Library', - u'Application Support', u'openlp', u'Data') - else: - try: - from xdg import BaseDirectory - path = os.path.join(BaseDirectory.xdg_data_home, u'openlp') - except ImportError: - path = os.path.join(os.getenv(u'HOME'), u'.openlp', u'data') - return path - from registry import Registry from confighelper import ConfigHelper -__all__ = [u'Registry', u'ConfigHelper', u'get_config_directory', - u'get_data_directory', u'check_latest_version'] +__all__ = [u'Registry', u'ConfigHelper', u'AppLocations', u'check_latest_version'] diff --git a/openlp/core/utils/confighelper.py b/openlp/core/utils/confighelper.py index d49157f55..7920013f2 100644 --- a/openlp/core/utils/confighelper.py +++ b/openlp/core/utils/confighelper.py @@ -25,7 +25,7 @@ import os -from openlp.core.utils import get_data_directory, get_config_directory +from openlp.core.utils import AppLocation from openlp.core.utils.registry import Registry class ConfigHelper(object): @@ -36,7 +36,7 @@ class ConfigHelper(object): @staticmethod def get_data_path(): - path = get_data_directory() + path = AppLocation.get_directory(AppLocation.DataDir) if not os.path.exists(path): os.makedirs(path) return path @@ -70,7 +70,7 @@ class ConfigHelper(object): current operating system, and returns an instantiation of that class. """ if ConfigHelper.__registry__ is None: - config_path = get_config_directory() + config_path = AppLocation.get_directory(AppLocation.ConfigDir) ConfigHelper.__registry__ = Registry(config_path) return ConfigHelper.__registry__ From 985ce73dfd06d0b8e858f08d01cb6cea2bda7791 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sun, 21 Feb 2010 14:06:56 +0000 Subject: [PATCH 087/164] Force regeneration of sevice item on theme change --- openlp/core/ui/servicemanager.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index b96e47f97..8e6b1331e 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -570,6 +570,8 @@ class ServiceManager(QtGui.QWidget): self.service_theme = unicode(self.ThemeComboBox.currentText()) self.parent.RenderManager.set_service_theme(self.service_theme) self.config.set_config(u'service theme', self.service_theme) + #force reset of renderer. + self.parent.RenderManager.themesata = None self.regenerateServiceItems() def regenerateServiceItems(self): From 20ab9d1ce2d12642dde9a0ac378393cc543b7539 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sun, 21 Feb 2010 14:12:47 +0000 Subject: [PATCH 088/164] Force regeneration of all changes --- openlp/core/ui/servicemanager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 8e6b1331e..ded360931 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -570,11 +570,11 @@ class ServiceManager(QtGui.QWidget): self.service_theme = unicode(self.ThemeComboBox.currentText()) self.parent.RenderManager.set_service_theme(self.service_theme) self.config.set_config(u'service theme', self.service_theme) - #force reset of renderer. - self.parent.RenderManager.themesata = None self.regenerateServiceItems() def regenerateServiceItems(self): + #force reset of renderer as theme data has changed + self.parent.RenderManager.themesata = None if len(self.serviceItems) > 0: tempServiceItems = self.serviceItems self.onNewService() From f7d95a8fdb41da2a5f4a878fd9cd8a4c6260770d Mon Sep 17 00:00:00 2001 From: Jon Tibble <meths@btinternet.com> Date: Sun, 21 Feb 2010 21:41:30 +0000 Subject: [PATCH 089/164] Remove debug prints --- openlp/core/ui/thememanager.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index 18744187f..9e2a8ecce 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -237,8 +237,6 @@ class ThemeManager(QtGui.QWidget): if len(files) > 0: for file in files: self.config.set_last_dir(unicode(file)) - print file - print self.path self.unzipTheme(file, self.path) self.loadThemes() From 962f1364e47b61ef1777927fd6aee0631f2f00c1 Mon Sep 17 00:00:00 2001 From: Jon Tibble <meths@btinternet.com> Date: Sun, 21 Feb 2010 23:05:03 +0000 Subject: [PATCH 090/164] Fix v2 theme importing --- openlp/core/ui/thememanager.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index 9e2a8ecce..a0168324f 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -314,6 +314,7 @@ class ThemeManager(QtGui.QWidget): themename = None for file in zip.namelist(): osfile = unicode(QtCore.QDir.toNativeSeparators(file)) + theme_dir = None if osfile.endswith(os.path.sep): theme_dir = os.path.join(dir, osfile) if not os.path.exists(theme_dir): @@ -325,6 +326,10 @@ class ThemeManager(QtGui.QWidget): # not preview file if themename is None: themename = names[0] + if theme_dir is None: + theme_dir = os.path.join(dir, names[0]) + if not os.path.exists(theme_dir): + os.mkdir(os.path.join(dir, names[0])) xml_data = zip.read(file) if os.path.splitext(file)[1].lower() in [u'.xml']: if self.checkVersion1(xml_data): From 45c7fad1d6f6f71ef0cc85c21a7b0f30b93aa9aa Mon Sep 17 00:00:00 2001 From: Jon Tibble <meths@btinternet.com> Date: Sun, 21 Feb 2010 23:40:16 +0000 Subject: [PATCH 091/164] Fix theme import colours --- openlp/core/theme/theme.py | 2 +- openlp/core/ui/thememanager.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/openlp/core/theme/theme.py b/openlp/core/theme/theme.py index 92ffbfed2..0295912fa 100644 --- a/openlp/core/theme/theme.py +++ b/openlp/core/theme/theme.py @@ -136,7 +136,7 @@ class Theme(object): if (element.tag.find(u'Color') > 0 or (element.tag.find(u'BackgroundParameter') == 0 and type(val) == type(0))): # convert to a wx.Colour - val = QtGui.QColor((val>>16) & 0xFF, (val>>8)&0xFF, val&0xFF) + val = QtGui.QColor(val&0xFF, (val>>8)&0xFF, (val>>16)&0xFF) setattr(self, element.tag, val) def __str__(self): diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index a0168324f..dff0723c7 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -390,7 +390,6 @@ class ThemeManager(QtGui.QWidget): unicode(theme.BackgroundParameter2.name()), direction) else: newtheme.add_background_image(unicode(theme.BackgroundParameter1)) - newtheme.add_font(unicode(theme.FontName), unicode(theme.FontColor.name()), unicode(theme.FontProportion * 3), u'False') From 60dd3b36cf34d28207054975d48935d496217d75 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Mon, 22 Feb 2010 06:14:44 +0000 Subject: [PATCH 092/164] Fix presentation tab and repaint service item --- openlp/core/ui/servicemanager.py | 7 +++++-- .../plugins/presentations/lib/presentationtab.py | 15 ++++----------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index ded360931..a51bdc1d0 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -579,9 +579,9 @@ class ServiceManager(QtGui.QWidget): tempServiceItems = self.serviceItems self.onNewService() for item in tempServiceItems: - self.addServiceItem(item[u'service_item']) + self.addServiceItem(item[u'service_item'], True) - def addServiceItem(self, item): + def addServiceItem(self, item, rebuild=False): """ Add a Service item to the list @@ -608,6 +608,9 @@ class ServiceManager(QtGui.QWidget): u'order': len(self.serviceItems)+1, u'expanded':True}) self.repaintServiceList(sitem + 1, 0) + #if rebuilding list make sure live is fixed. + if rebuild: + self.parent.LiveController.replaceServiceManagerItem(item) self.parent.serviceChanged(False, self.serviceName) def makePreview(self): diff --git a/openlp/plugins/presentations/lib/presentationtab.py b/openlp/plugins/presentations/lib/presentationtab.py index 40ab15e69..e7d16eb47 100644 --- a/openlp/plugins/presentations/lib/presentationtab.py +++ b/openlp/plugins/presentations/lib/presentationtab.py @@ -51,17 +51,10 @@ class PresentationTab(SettingsTab): self.PresentationLeftLayout.setMargin(0) self.VerseDisplayGroupBox = QtGui.QGroupBox(self) self.VerseDisplayGroupBox.setObjectName(u'VerseDisplayGroupBox') - self.VerseDisplayLayout = QtGui.QGridLayout(self.VerseDisplayGroupBox) + self.VerseDisplayLayout = QtGui.QVBoxLayout(self.VerseDisplayGroupBox) self.VerseDisplayLayout.setMargin(8) self.VerseDisplayLayout.setObjectName(u'VerseDisplayLayout') - self.VerseTypeWidget = QtGui.QWidget(self.VerseDisplayGroupBox) - self.VerseTypeWidget.setObjectName(u'VerseTypeWidget') - self.VerseTypeLayout = QtGui.QHBoxLayout(self.VerseTypeWidget) - self.VerseTypeLayout.setSpacing(8) - self.VerseTypeLayout.setMargin(0) - self.VerseTypeLayout.setObjectName(u'VerseTypeLayout') self.PresenterCheckboxes = {} - index = 0 for key in self.controllers: controller = self.controllers[key] checkbox = QtGui.QCheckBox(self.VerseDisplayGroupBox) @@ -69,8 +62,7 @@ class PresentationTab(SettingsTab): checkbox.setEnabled(controller.available) checkbox.setObjectName(controller.name + u'CheckBox') self.PresenterCheckboxes[controller.name] = checkbox - index = index + 1 - self.VerseDisplayLayout.addWidget(checkbox, index, 0, 1, 1) + self.VerseDisplayLayout.addWidget(checkbox) self.PresentationThemeWidget = QtGui.QWidget(self.VerseDisplayGroupBox) self.PresentationThemeWidget.setObjectName(u'PresentationThemeWidget') self.PresentationThemeLayout = QtGui.QHBoxLayout( @@ -96,6 +88,7 @@ class PresentationTab(SettingsTab): self.PresentationLayout.addWidget(self.PresentationRightWidget) def retranslateUi(self): + self.VerseDisplayGroupBox.setTitle(self.trUtf8('Avaliable Controllers')) for key in self.controllers: controller = self.controllers[key] checkbox = self.PresenterCheckboxes[controller.name] @@ -115,4 +108,4 @@ class PresentationTab(SettingsTab): controller = self.controllers[key] checkbox = self.PresenterCheckboxes[controller.name] self.config.set_config( - controller.name, unicode(checkbox.checkState())) \ No newline at end of file + controller.name, unicode(checkbox.checkState())) From 03371279854059a22add6c554463c42da060bac5 Mon Sep 17 00:00:00 2001 From: Jon Tibble <meths@btinternet.com> Date: Mon, 22 Feb 2010 15:53:23 +0000 Subject: [PATCH 093/164] Fix theme import alignment --- openlp/core/ui/thememanager.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index dff0723c7..630b1a485 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -402,9 +402,14 @@ class ThemeManager(QtGui.QWidget): shadow = True if theme.Outline == 1: outline = True + vAlignCorrection = 0 + if theme.VerticalAlign == 2: + vAlignCorrection = 1 + elif theme.VerticalAlign == 1: + vAlignCorrection = 2 newtheme.add_display(unicode(shadow), unicode(theme.ShadowColor.name()), unicode(outline), unicode(theme.OutlineColor.name()), - unicode(theme.HorizontalAlign), unicode(theme.VerticalAlign), + unicode(theme.HorizontalAlign), unicode(vAlignCorrection), unicode(theme.WrapStyle), unicode(0)) return newtheme.extract_xml() From 61c7b6479fed352d8853dc6159447e4ff24942cd Mon Sep 17 00:00:00 2001 From: Jon Tibble <meths@btinternet.com> Date: Mon, 22 Feb 2010 16:42:10 +0000 Subject: [PATCH 094/164] Fix theme import font colours --- openlp/core/theme/theme.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/openlp/core/theme/theme.py b/openlp/core/theme/theme.py index 0295912fa..27969c9d9 100644 --- a/openlp/core/theme/theme.py +++ b/openlp/core/theme/theme.py @@ -30,7 +30,7 @@ from PyQt4 import QtGui DelphiColors={"clRed":0xFF0000, "clBlue":0x0000FF, - "clYellow":0x0FFFF00, + "clYellow":0xFFFF00, "clBlack":0x000000, "clWhite":0xFFFFFF} @@ -113,6 +113,7 @@ class Theme(object): root = ElementTree(element=XML(xml)) iter = root.getiterator() for element in iter: + delphiColorChange = False if element.tag != u'Theme': t = element.text val = 0 @@ -128,6 +129,7 @@ class Theme(object): pass elif DelphiColors.has_key(t): val = DelphiColors[t] + delphiColorChange = True else: try: val = int(t) @@ -136,7 +138,10 @@ class Theme(object): if (element.tag.find(u'Color') > 0 or (element.tag.find(u'BackgroundParameter') == 0 and type(val) == type(0))): # convert to a wx.Colour - val = QtGui.QColor(val&0xFF, (val>>8)&0xFF, (val>>16)&0xFF) + if not delphiColorChange: + val = QtGui.QColor(val&0xFF, (val>>8)&0xFF, (val>>16)&0xFF) + else: + val = QtGui.QColor((val>>16)&0xFF, (val>>8)&0xFF, val&0xFF) setattr(self, element.tag, val) def __str__(self): From d02fc68a721bf8b1996e2c8f19ccce4d72ae8522 Mon Sep 17 00:00:00 2001 From: Jon Tibble <meths@btinternet.com> Date: Mon, 22 Feb 2010 16:57:08 +0000 Subject: [PATCH 095/164] Fix unzipping theme files --- openlp/core/ui/thememanager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index 630b1a485..577021418 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -341,7 +341,7 @@ class ThemeManager(QtGui.QWidget): outfile = open(fullpath, u'w') outfile.write(filexml) else: - outfile = open(fullpath, u'w') + outfile = open(fullpath, u'wb') outfile.write(zip.read(file)) self.generateAndSaveImage(dir, themename, filexml) except: From b80bfb5a80ab9fe302bc43ee2187004d58959319 Mon Sep 17 00:00:00 2001 From: Michael Gorven <michael@gorven.za.net> Date: Tue, 23 Feb 2010 17:42:17 +0200 Subject: [PATCH 096/164] Add menu entry from old branch. --- resources/openlp.desktop | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 resources/openlp.desktop diff --git a/resources/openlp.desktop b/resources/openlp.desktop new file mode 100644 index 000000000..8791c2d8f --- /dev/null +++ b/resources/openlp.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=OpenLP +GenericName=Church lyrics projection +Exec=openlp +Icon=openlp +StartupNotify=true +Terminal=False +Type=Application +Categories=AudioVideo From a0e56c0d8509dbfad52348406ddcdf2c5027265c Mon Sep 17 00:00:00 2001 From: Michael Gorven <michael@gorven.za.net> Date: Tue, 23 Feb 2010 17:45:06 +0200 Subject: [PATCH 097/164] Include proper vesion file in tarball and fix reading thereof. --- MANIFEST.in | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 0e94db321..3b76a706c 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -8,4 +8,4 @@ recursive-include resources/images * recursive-include scripts *.py include copyright.txt include LICENSE -include version.txt +include openlp/.version diff --git a/setup.py b/setup.py index 29db13c4b..ccdbd09af 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ try: finally: b.unlock() except: - ver_file = open(VERSION_FILE, u'w') + ver_file = open(VERSION_FILE, u'r') version = ver_file.read().strip() ver_file.close() From 3b0970d7735942ca2edf2c9d424bc6f07528ef25 Mon Sep 17 00:00:00 2001 From: Michael Gorven <michael@gorven.za.net> Date: Tue, 23 Feb 2010 17:45:26 +0200 Subject: [PATCH 098/164] Make setup.py executable. --- setup.py | 2 ++ 1 file changed, 2 insertions(+) mode change 100644 => 100755 setup.py diff --git a/setup.py b/setup.py old mode 100644 new mode 100755 index ccdbd09af..975de929d --- a/setup.py +++ b/setup.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + from setuptools import setup, find_packages import sys, os From 03ae1781c4130d17e9cc6e4dfa9631948dbd7899 Mon Sep 17 00:00:00 2001 From: Michael Gorven <michael@gorven.za.net> Date: Tue, 23 Feb 2010 17:45:55 +0200 Subject: [PATCH 099/164] Install menu entry. --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 3b76a706c..73ff3c545 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -6,6 +6,7 @@ recursive-include resources/forms * recursive-include resources/i18n * recursive-include resources/images * recursive-include scripts *.py +include resources/*.desktop include copyright.txt include LICENSE include openlp/.version From 7f8c6b6fdd8b0cc4baf9f33709273e9f58152ef6 Mon Sep 17 00:00:00 2001 From: Michael Gorven <michael@gorven.za.net> Date: Tue, 23 Feb 2010 17:49:39 +0200 Subject: [PATCH 100/164] Install scripts as scripts. --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 975de929d..2429f0d73 100755 --- a/setup.py +++ b/setup.py @@ -45,6 +45,7 @@ OpenLP (previously openlp.org) is free church presentation software, or lyrics p url='http://openlp.org/', license='GNU General Public License', packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), + scripts=['openlp.pyw', 'openlp-1to2-converter.py', 'bible-1to2-converter.py'], include_package_data=True, zip_safe=False, install_requires=[ From 57f685cc60827e6e1e27d8bdf9c20f489aa757b2 Mon Sep 17 00:00:00 2001 From: Michael Gorven <michael@gorven.za.net> Date: Tue, 23 Feb 2010 17:52:38 +0200 Subject: [PATCH 101/164] Specify correct directory. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2429f0d73..73cecd97a 100755 --- a/setup.py +++ b/setup.py @@ -45,7 +45,7 @@ OpenLP (previously openlp.org) is free church presentation software, or lyrics p url='http://openlp.org/', license='GNU General Public License', packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), - scripts=['openlp.pyw', 'openlp-1to2-converter.py', 'bible-1to2-converter.py'], + scripts=['openlp.pyw', 'scripts/openlp-1to2-converter.py', 'scripts/bible-1to2-converter.py'], include_package_data=True, zip_safe=False, install_requires=[ From 8e2efcb18391e2289370874b2dcba3a006e1edd4 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Tue, 23 Feb 2010 19:19:06 +0000 Subject: [PATCH 102/164] Fix spelllling --- openlp/plugins/presentations/lib/presentationtab.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openlp/plugins/presentations/lib/presentationtab.py b/openlp/plugins/presentations/lib/presentationtab.py index e7d16eb47..0ef7e17d1 100644 --- a/openlp/plugins/presentations/lib/presentationtab.py +++ b/openlp/plugins/presentations/lib/presentationtab.py @@ -88,7 +88,7 @@ class PresentationTab(SettingsTab): self.PresentationLayout.addWidget(self.PresentationRightWidget) def retranslateUi(self): - self.VerseDisplayGroupBox.setTitle(self.trUtf8('Avaliable Controllers')) + self.VerseDisplayGroupBox.setTitle(self.trUtf8('Available Controllers')) for key in self.controllers: controller = self.controllers[key] checkbox = self.PresenterCheckboxes[controller.name] From 3cf8b491eacc8e57bac434b9880c622daa3c37f4 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Tue, 23 Feb 2010 19:37:07 +0000 Subject: [PATCH 103/164] Fix field name --- openlp/core/ui/servicemanager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index d0307b860..5ee248a3b 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -574,7 +574,7 @@ class ServiceManager(QtGui.QWidget): def regenerateServiceItems(self): #force reset of renderer as theme data has changed - self.parent.RenderManager.themesata = None + self.parent.RenderManager.themedata = None if len(self.serviceItems) > 0: tempServiceItems = self.serviceItems self.onNewService() From 6012641a538213823c01b00e59b972a79ef7b5d4 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Wed, 24 Feb 2010 20:37:33 +0200 Subject: [PATCH 104/164] Trying to set a tag on trunk. bzr-revno: 715 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index f5e7e4e33..34d78cfd2 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.9.0-713 +1.9.0-715 From 0ddaa2bf4a52653c5372004dfe9c5804b5cd6662 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Wed, 24 Feb 2010 20:39:12 +0200 Subject: [PATCH 105/164] Some more visual tweaks. Fixes for older versions of PyQt4. --- openlp/core/ui/slidecontroller.py | 7 +++-- openlp/plugins/alerts/__init__.py | 2 +- .../plugins/bibles/forms/importwizardform.py | 26 +++++++++---------- openlp/plugins/bibles/lib/common.py | 2 +- 4 files changed, 18 insertions(+), 19 deletions(-) diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index e88699b71..a8c7042a6 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -108,15 +108,14 @@ class SlideController(QtGui.QWidget): # Type label for the top of the slide controller self.TypeLabel = QtGui.QLabel(self.Panel) if self.isLive: - self.TypeLabel.setText(u'<strong>%s</strong>' % - self.trUtf8('Live')) + self.TypeLabel.setText(self.trUtf8('Live')) self.split = 1 prefix = u'live_slidecontroller' else: - self.TypeLabel.setText(u'<strong>%s</strong>' % - self.trUtf8('Preview')) + self.TypeLabel.setText(self.trUtf8('Preview')) self.split = 0 prefix = u'preview_slidecontroller' + self.TypeLabel.setStyleSheet(u'font-weight: bold; font-size: 12pt;') self.TypeLabel.setAlignment(QtCore.Qt.AlignCenter) self.PanelLayout.addWidget(self.TypeLabel) # Splitter diff --git a/openlp/plugins/alerts/__init__.py b/openlp/plugins/alerts/__init__.py index a78b7d0d7..bc50edda3 100644 --- a/openlp/plugins/alerts/__init__.py +++ b/openlp/plugins/alerts/__init__.py @@ -21,4 +21,4 @@ # 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 # -############################################################################### +############################################################################### \ No newline at end of file diff --git a/openlp/plugins/bibles/forms/importwizardform.py b/openlp/plugins/bibles/forms/importwizardform.py index a684c01a3..b4de19da2 100644 --- a/openlp/plugins/bibles/forms/importwizardform.py +++ b/openlp/plugins/bibles/forms/importwizardform.py @@ -237,22 +237,22 @@ class ImportWizardForm(QtGui.QWizard, Ui_BibleImportWizard): u'license_permission', self.PermissionEdit) def setDefaults(self): - self.setField(u'source_format', 0) - self.setField(u'osis_location', '') - self.setField(u'csv_booksfile', '') - self.setField(u'csv_versefile', '') - self.setField(u'opensong_file', '') - self.setField(u'web_location', DownloadLocation.Crosswalk) - self.setField(u'web_biblename', self.BibleComboBox) + self.setField(u'source_format', QtCore.QVariant(0)) + self.setField(u'osis_location', QtCore.QVariant('')) + self.setField(u'csv_booksfile', QtCore.QVariant('')) + self.setField(u'csv_versefile', QtCore.QVariant('')) + self.setField(u'opensong_file', QtCore.QVariant('')) + self.setField(u'web_location', QtCore.QVariant(DownloadLocation.Crosswalk)) + self.setField(u'web_biblename', QtCore.QVariant(self.BibleComboBox)) self.setField(u'proxy_server', - self.config.get_config(u'proxy address', '')) + QtCore.QVariant(self.config.get_config(u'proxy address', ''))) self.setField(u'proxy_username', - self.config.get_config(u'proxy username','')) + QtCore.QVariant(self.config.get_config(u'proxy username',''))) self.setField(u'proxy_password', - self.config.get_config(u'proxy password','')) - self.setField(u'license_version', self.VersionNameEdit) - self.setField(u'license_copyright', self.CopyrightEdit) - self.setField(u'license_permission', self.PermissionEdit) + QtCore.QVariant(self.config.get_config(u'proxy password',''))) + self.setField(u'license_version', QtCore.QVariant(self.VersionNameEdit)) + self.setField(u'license_copyright', QtCore.QVariant(self.CopyrightEdit)) + self.setField(u'license_permission', QtCore.QVariant(self.PermissionEdit)) self.onLocationComboBoxChanged(DownloadLocation.Crosswalk) def loadWebBibles(self): diff --git a/openlp/plugins/bibles/lib/common.py b/openlp/plugins/bibles/lib/common.py index 5152ca496..b8ad245bb 100644 --- a/openlp/plugins/bibles/lib/common.py +++ b/openlp/plugins/bibles/lib/common.py @@ -24,10 +24,10 @@ ############################################################################### import urllib2 -import chardet import logging import re import sqlite3 +import chardet only_verses = re.compile(r'([\w .]+)[ ]+([0-9]+)[ ]*[:|v|V][ ]*([0-9]+)' r'(?:[ ]*-[ ]*([0-9]+|end))?(?:[ ]*,[ ]*([0-9]+)(?:[ ]*-[ ]*([0-9]+|end))?)?', From 804b18b03426bfb919ec29d03c45fc6371c2d48a Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Wed, 24 Feb 2010 20:55:34 +0200 Subject: [PATCH 106/164] Add PyInstaller hooks. --- .../pyinstaller/hook-openlp.plugins.bibles.lib.common.py | 1 + ...ok-openlp.plugins.presentations.presentationplugin.py | 3 +++ resources/pyinstaller/hook-openlp.py | 9 +++++++++ 3 files changed, 13 insertions(+) create mode 100644 resources/pyinstaller/hook-openlp.plugins.bibles.lib.common.py create mode 100644 resources/pyinstaller/hook-openlp.plugins.presentations.presentationplugin.py create mode 100644 resources/pyinstaller/hook-openlp.py diff --git a/resources/pyinstaller/hook-openlp.plugins.bibles.lib.common.py b/resources/pyinstaller/hook-openlp.plugins.bibles.lib.common.py new file mode 100644 index 000000000..dc71dc088 --- /dev/null +++ b/resources/pyinstaller/hook-openlp.plugins.bibles.lib.common.py @@ -0,0 +1 @@ +hiddenimports = ['chardet'] \ No newline at end of file diff --git a/resources/pyinstaller/hook-openlp.plugins.presentations.presentationplugin.py b/resources/pyinstaller/hook-openlp.plugins.presentations.presentationplugin.py new file mode 100644 index 000000000..8b7d6b8a2 --- /dev/null +++ b/resources/pyinstaller/hook-openlp.plugins.presentations.presentationplugin.py @@ -0,0 +1,3 @@ +hiddenimports = ['openlp.plugins.presentations.lib.impresscontroller', + 'openlp.plugins.presentations.lib.powerpointcontroller', + 'openlp.plugins.presentations.lib.pptviewcontroller'] \ No newline at end of file diff --git a/resources/pyinstaller/hook-openlp.py b/resources/pyinstaller/hook-openlp.py new file mode 100644 index 000000000..bd97e4aec --- /dev/null +++ b/resources/pyinstaller/hook-openlp.py @@ -0,0 +1,9 @@ +hiddenimports = ['plugins.songs.songsplugin', + 'plugins.bibles.bibleplugin', + 'plugins.presentations.presentationplugin', + 'plugins.media.mediaplugin', + 'plugins.images.imageplugin', + 'plugins.custom.customplugin', + 'plugins.songusage.songusageplugin', + 'plugins.remotes.remoteplugin', + 'plugins.alerts.alertsplugin'] \ No newline at end of file From 38a8575989c65eb545353aee3e784039372dc3ed Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Thu, 25 Feb 2010 21:09:27 +0000 Subject: [PATCH 107/164] Sort out startup http gets. --- openlp/core/ui/mainwindow.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index df66d5e97..96a089df1 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -52,12 +52,17 @@ media_manager_style = """ } """ class versionThread(QtCore.QThread): - def __init__(self, parent): + def __init__(self, parent, app_version, generalConfig): QtCore.QThread.__init__(self, parent) self.parent = parent + self.app_version = app_version + self.generalConfig = generalConfig def run (self): time.sleep(2) - Receiver.send_message(u'version_check') + version = check_latest_version(self.generalConfig, self.app_version) + #new version has arrived + if version != self.app_version: + Receiver.send_message(u'version_check', u'%s' % version) class Ui_MainWindow(object): @@ -536,20 +541,18 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): log.info(u'Load data from Settings') self.settingsForm.postSetUp() - def versionCheck(self): + def versionCheck(self, version): """ Checks the version of the Application called from openlp.pyw """ app_version = self.applicationVersion[u'full'] - version = check_latest_version(self.generalConfig, app_version) - if app_version != version: - version_text = unicode(self.trUtf8('OpenLP version %s has been updated ' - 'to version %s\n\nYou can obtain the latest version from http://openlp.org')) - QtGui.QMessageBox.question(self, - self.trUtf8('OpenLP Version Updated'), - version_text % (app_version, version), - QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok), - QtGui.QMessageBox.Ok) + version_text = unicode(self.trUtf8('OpenLP version %s has been updated ' + 'to version %s\n\nYou can obtain the latest version from http://openlp.org')) + QtGui.QMessageBox.question(self, + self.trUtf8('OpenLP Version Updated'), + version_text % (app_version, version), + QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok), + QtGui.QMessageBox.Ok) def getMonitorNumber(self): """ @@ -584,7 +587,8 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): QtGui.QMessageBox.Ok) def versionThread(self): - vT = versionThread(self) + app_version = self.applicationVersion[u'full'] + vT = versionThread(self, app_version, self.generalConfig) vT.start() def onHelpAboutItemClicked(self): From e0875fa90e0335906e946d19b7cc370556628214 Mon Sep 17 00:00:00 2001 From: Jon Tibble <meths@btinternet.com> Date: Fri, 26 Feb 2010 20:22:34 +0000 Subject: [PATCH 108/164] Import fixes --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 73cecd97a..d6f4b7503 100755 --- a/setup.py +++ b/setup.py @@ -1,7 +1,6 @@ #!/usr/bin/env python from setuptools import setup, find_packages -import sys, os VERSION_FILE = 'openlp/.version' From 2185f4e84271e61ee6cbb28617f8d0cbab9fe5a3 Mon Sep 17 00:00:00 2001 From: Jon Tibble <meths@btinternet.com> Date: Fri, 26 Feb 2010 23:47:59 +0000 Subject: [PATCH 109/164] Remove C-style semi-colons --- openlp/core/lib/__init__.py | 2 +- openlp/core/lib/renderer.py | 10 +++++----- openlp/core/ui/maindisplay.py | 2 +- openlp/core/ui/slidecontroller.py | 4 ++-- openlp/migration/display.py | 8 ++++---- openlp/migration/migratebibles.py | 4 ++-- openlp/migration/migratefiles.py | 18 +++++++++--------- .../presentations/lib/pptviewcontroller.py | 2 +- .../songs/forms/songmaintenancedialog.py | 8 ++++---- 9 files changed, 29 insertions(+), 29 deletions(-) diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index 93512f946..899b5cf73 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -146,7 +146,7 @@ def resize_image(image, width, height): preview = QtGui.QImage(image) preview = preview.scaled(width, height, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) - realw = preview.width(); + realw = preview.width() realh = preview.height() # and move it to the centre of the preview space newImage = QtGui.QImage(width, height, QtGui.QImage.Format_ARGB32_Premultiplied) diff --git a/openlp/core/lib/renderer.py b/openlp/core/lib/renderer.py index cc976dae5..03b53eca8 100644 --- a/openlp/core/lib/renderer.py +++ b/openlp/core/lib/renderer.py @@ -406,8 +406,8 @@ class Renderer(object): Defaults to *False*. Whether or not this is a live screen. """ x, y = tlcorner - maxx = self._rect.width(); - maxy = self._rect.height(); + maxx = self._rect.width() + maxy = self._rect.height() lines = [] lines.append(line) startx = x @@ -415,11 +415,11 @@ class Renderer(object): rightextent = None self.painter = QtGui.QPainter() self.painter.begin(self._frame) - self.painter.setRenderHint(QtGui.QPainter.Antialiasing); + self.painter.setRenderHint(QtGui.QPainter.Antialiasing) if self._theme.display_slideTransition: self.painter2 = QtGui.QPainter() self.painter2.begin(self._frameOp) - self.painter2.setRenderHint(QtGui.QPainter.Antialiasing); + self.painter2.setRenderHint(QtGui.QPainter.Antialiasing) self.painter2.setOpacity(0.7) # dont allow alignment messing with footers if footer: @@ -458,7 +458,7 @@ class Renderer(object): x = maxx - w # centre elif align == 2: - x = (maxx - w) / 2; + x = (maxx - w) / 2 rightextent = x + w if live: # now draw the text, and any outlines/shadows diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index bdb396498..2671732ec 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -65,7 +65,7 @@ class DisplayWidget(QtGui.QWidget): Receiver.send_message(u'live_slidecontroller_last') event.accept() elif event.key() in self.hotkey_map: - Receiver.send_message(self.hotkey_map[event.key()]); + Receiver.send_message(self.hotkey_map[event.key()]) event.accept() elif event.key() == QtCore.Qt.Key_Escape: self.resetDisplay() diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index e88699b71..c21afa4f5 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -31,7 +31,7 @@ from PyQt4 import QtCore, QtGui from PyQt4.phonon import Phonon from openlp.core.lib import OpenLPToolbar, Receiver, str_to_bool, \ -PluginConfig, resize_image + PluginConfig, resize_image class SlideList(QtGui.QTableWidget): """ @@ -63,7 +63,7 @@ class SlideList(QtGui.QTableWidget): self.parent.onSlideSelectedLast() event.accept() elif event.key() in self.hotkey_map and self.parent.isLive: - Receiver.send_message(self.hotkey_map[event.key()]); + Receiver.send_message(self.hotkey_map[event.key()]) event.accept() event.ignore() else: diff --git a/openlp/migration/display.py b/openlp/migration/display.py index 9a3f6e44e..f427c5fb6 100644 --- a/openlp/migration/display.py +++ b/openlp/migration/display.py @@ -32,11 +32,11 @@ class Display(): @staticmethod def output(string): - log.debug(string); - print (string) + log.debug(string) + #print (string) @staticmethod def sub_output(string): if not string is None: - log.debug(u' '+string); - print (u' '+string) \ No newline at end of file + log.debug(u' '+string) + #print (u' '+string) diff --git a/openlp/migration/migratebibles.py b/openlp/migration/migratebibles.py index 92504e907..f9e10b756 100644 --- a/openlp/migration/migratebibles.py +++ b/openlp/migration/migratebibles.py @@ -28,5 +28,5 @@ class MigrateBibles(): self.display = display def process(self): - self.display.output(u'Bible process started'); - self.display.output(u'Bible process finished'); \ No newline at end of file + self.display.output(u'Bible process started') + self.display.output(u'Bible process finished') \ No newline at end of file diff --git a/openlp/migration/migratefiles.py b/openlp/migration/migratefiles.py index 9c38fbb88..f1c9435ab 100644 --- a/openlp/migration/migratefiles.py +++ b/openlp/migration/migratefiles.py @@ -30,20 +30,20 @@ class MigrateFiles(): self.display = display def process(self): - self.display.output(u'Files process started'); + self.display.output(u'Files process started') self._initial_setup() - self.display.output(u'Files process finished'); + self.display.output(u'Files process finished') def _initial_setup(self): - self.display.output(u'Initial Setup started'); + self.display.output(u'Initial Setup started') ConfigHelper.get_data_path() - self.display.sub_output(u'Config created'); + self.display.sub_output(u'Config created') ConfigHelper.get_config(u'bible', u'data path') - self.display.sub_output(u'Config created'); + self.display.sub_output(u'Config created') ConfigHelper.get_config(u'videos', u'data path') - self.display.sub_output(u'videos created'); + self.display.sub_output(u'videos created') ConfigHelper.get_config(u'images', u'data path') - self.display.sub_output(u'images created'); + self.display.sub_output(u'images created') ConfigHelper.get_config(u'presentations', u'data path') - self.display.sub_output(u'presentations created'); - self.display.output(u'Initial Setup finished'); \ No newline at end of file + self.display.sub_output(u'presentations created') + self.display.output(u'Initial Setup finished') \ No newline at end of file diff --git a/openlp/plugins/presentations/lib/pptviewcontroller.py b/openlp/plugins/presentations/lib/pptviewcontroller.py index 2ed457fc0..56372e352 100644 --- a/openlp/plugins/presentations/lib/pptviewcontroller.py +++ b/openlp/plugins/presentations/lib/pptviewcontroller.py @@ -110,7 +110,7 @@ class PptviewController(PresentationController): rendermanager = self.plugin.render_manager rect = rendermanager.screens.current[u'size'] rect = RECT(rect.x(), rect.y(), rect.right(), rect.bottom()) - filepath = str(presentation.replace(u'/', u'\\')); + filepath = str(presentation.replace(u'/', u'\\')) try: self.pptid = self.process.OpenPPT(filepath, None, rect, str(os.path.join(self.thumbnailpath, self.thumbnailprefix))) diff --git a/openlp/plugins/songs/forms/songmaintenancedialog.py b/openlp/plugins/songs/forms/songmaintenancedialog.py index 5730e8c56..643676ff6 100644 --- a/openlp/plugins/songs/forms/songmaintenancedialog.py +++ b/openlp/plugins/songs/forms/songmaintenancedialog.py @@ -51,10 +51,10 @@ class Ui_SongMaintenanceDialog(object): self.TypeListWidget.sizePolicy().hasHeightForWidth()) self.TypeListWidget.setSizePolicy(sizePolicy) self.TypeListWidget.setViewMode(QtGui.QListView.IconMode) - self.TypeListWidget.setIconSize(QtCore.QSize(112, 100)); - self.TypeListWidget.setMovement(QtGui.QListView.Static); - self.TypeListWidget.setMaximumWidth(118); - self.TypeListWidget.setSpacing(0); + self.TypeListWidget.setIconSize(QtCore.QSize(112, 100)) + self.TypeListWidget.setMovement(QtGui.QListView.Static) + self.TypeListWidget.setMaximumWidth(118) + self.TypeListWidget.setSpacing(0) self.TypeListWidget.setSortingEnabled(False) self.TypeListWidget.setUniformItemSizes(True) self.TypeListWidget.setObjectName(u'TypeListWidget') From 80d54dc1be05a2582095203ad2827a254b22041e Mon Sep 17 00:00:00 2001 From: Jon Tibble <meths@btinternet.com> Date: Sat, 27 Feb 2010 00:11:26 +0000 Subject: [PATCH 110/164] None tests --- openlp/core/ui/servicemanager.py | 2 +- openlp/plugins/presentations/lib/powerpointcontroller.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 22fbb7ecc..ab36660b4 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -694,7 +694,7 @@ class ServiceManager(QtGui.QWidget): if plugin == u'ServiceManager': startpos, startCount = self.findServiceItem() item = self.ServiceManagerList.itemAt(event.pos()) - if item == None: + if item is None: endpos = len(self.serviceItems) else: parentitem = item.parent() diff --git a/openlp/plugins/presentations/lib/powerpointcontroller.py b/openlp/plugins/presentations/lib/powerpointcontroller.py index a9775e086..4ac7ada66 100644 --- a/openlp/plugins/presentations/lib/powerpointcontroller.py +++ b/openlp/plugins/presentations/lib/powerpointcontroller.py @@ -150,7 +150,7 @@ class PowerpointController(PresentationController): Triggerent by new object being added to SlideController orOpenLP being shut down """ - if self.presentation == None: + if self.presentation is None: return try: self.presentation.Close() @@ -165,9 +165,9 @@ class PowerpointController(PresentationController): if not self.is_loaded(): return False try: - if self.presentation.SlideShowWindow == None: + if self.presentation.SlideShowWindow is None: return False - if self.presentation.SlideShowWindow.View == None: + if self.presentation.SlideShowWindow.View is None: return False except: return False From d47fc53b7815fd4e7ada91dab5958eca5243717a Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sat, 27 Feb 2010 07:39:47 +0000 Subject: [PATCH 111/164] Add Alternate rows to slide controller --- openlp/core/ui/slidecontroller.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index e88699b71..515fb2d3c 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -144,6 +144,7 @@ class SlideController(QtGui.QWidget): self.PreviewListWidget.setEditTriggers( QtGui.QAbstractItemView.NoEditTriggers) self.PreviewListWidget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.PreviewListWidget.setAlternatingRowColors(True) self.ControllerLayout.addWidget(self.PreviewListWidget) # Build the full toolbar self.Toolbar = OpenLPToolbar(self) From 4a83e572e96de9afd75021b2a914e073cec6cd8e Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sat, 27 Feb 2010 09:18:26 +0000 Subject: [PATCH 112/164] Fix bugs in last merge and fix comments --- openlp/plugins/bibles/lib/common.py | 2 -- openlp/plugins/bibles/lib/db.py | 2 +- openlp/plugins/bibles/lib/http.py | 10 +++++----- openlp/plugins/bibles/lib/mediaitem.py | 7 +++---- openlp/plugins/custom/lib/mediaitem.py | 2 +- openlp/plugins/images/lib/mediaitem.py | 2 +- openlp/plugins/media/mediaplugin.py | 1 + 7 files changed, 12 insertions(+), 14 deletions(-) diff --git a/openlp/plugins/bibles/lib/common.py b/openlp/plugins/bibles/lib/common.py index cd9b5cf35..4ba985842 100644 --- a/openlp/plugins/bibles/lib/common.py +++ b/openlp/plugins/bibles/lib/common.py @@ -166,8 +166,6 @@ class BibleCommon(object): """ A common ancestor for bible download sites. """ - global log - log = logging.getLogger(u'BibleCommon') log.info(u'BibleCommon') def _get_web_text(self, urlstring, proxyurl): diff --git a/openlp/plugins/bibles/lib/db.py b/openlp/plugins/bibles/lib/db.py index 67c71ca10..be4112a54 100644 --- a/openlp/plugins/bibles/lib/db.py +++ b/openlp/plugins/bibles/lib/db.py @@ -59,7 +59,7 @@ class BibleDB(QtCore.QObject): ``config`` The configuration object, passed in from the plugin. """ - log.info(u'BibleDBimpl loaded') + log.info(u'BibleDB loaded') QtCore.QObject.__init__(self) if u'path' not in kwargs: raise KeyError(u'Missing keyword argument "path".') diff --git a/openlp/plugins/bibles/lib/http.py b/openlp/plugins/bibles/lib/http.py index 1cf92e4d2..d00c1f88a 100644 --- a/openlp/plugins/bibles/lib/http.py +++ b/openlp/plugins/bibles/lib/http.py @@ -35,6 +35,8 @@ from common import BibleCommon, SearchResults from db import BibleDB from openlp.plugins.bibles.lib.models import Book +log = logging.getLogger(__name__) + class HTTPBooks(object): cursor = None @@ -119,9 +121,7 @@ class HTTPBooks(object): class BGExtract(BibleCommon): - global log - log = logging.getLogger(u'BibleHTTPMgr(BG_extract)') - log.info(u'BG_extract loaded') + log.info(u'%s BGExtract loaded', __name__) def __init__(self, proxyurl=None): log.debug(u'init %s', proxyurl) @@ -184,7 +184,7 @@ class BGExtract(BibleCommon): return SearchResults(bookname, chapter, bible) class CWExtract(BibleCommon): - log.info(u'%s loaded', __name__) + log.info(u'%s CWExtract loaded', __name__) def __init__(self, proxyurl=None): log.debug(u'init %s', proxyurl) @@ -229,7 +229,7 @@ class CWExtract(BibleCommon): class HTTPBible(BibleDB): - log.info(u'%s loaded', __name__) + log.info(u'%s HTTPBible loaded' , __name__) def __init__(self, parent, **kwargs): """ diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index 44d37c4c4..c0a3bde35 100644 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -43,7 +43,6 @@ class BibleListView(BaseListWithDnD): def resizeEvent(self, event): self.parent.onListViewResize(event.size().width(), event.size().width()) - class BibleMediaItem(MediaManagerItem): """ This is the custom media manager item for Bibles. @@ -435,7 +434,7 @@ class BibleMediaItem(MediaManagerItem): raw_slides = [] raw_footer = [] bible_text = u'' - self.service_item.autoPreviewAllowed = True + service_item.autoPreviewAllowed = True #If we want to use a 2nd translation / version bible2 = u'' if self.SearchTabWidget.currentIndex() == 0: @@ -471,12 +470,12 @@ class BibleMediaItem(MediaManagerItem): verse_text = self.formatVerse(old_chapter, chapter, verse, u'', u'') old_chapter = chapter footer = u'%s (%s %s)' % (book, version, copyright) - #If not found throws and error so add.s + #If not found add to footer if footer not in raw_footer: raw_footer.append(footer) if bible2: footer = u'%s (%s %s)' % (book, version, copyright) - #If not found throws and error so add.s + #If not found add to footer if footer not in raw_footer: raw_footer.append(footer) bible_text = u'%s %s \n\n %s %s' % \ diff --git a/openlp/plugins/custom/lib/mediaitem.py b/openlp/plugins/custom/lib/mediaitem.py index 0750a467e..c4b9ef16a 100644 --- a/openlp/plugins/custom/lib/mediaitem.py +++ b/openlp/plugins/custom/lib/mediaitem.py @@ -144,7 +144,7 @@ class CustomMediaItem(MediaManagerItem): item_id = (item.data(QtCore.Qt.UserRole)).toInt()[0] else: item_id = self.remoteCustom - self.service_item.autoPreviewAllowed = True + service_item.autoPreviewAllowed = True customSlide = self.parent.custommanager.get_custom(item_id) title = customSlide.title credit = customSlide.credits diff --git a/openlp/plugins/images/lib/mediaitem.py b/openlp/plugins/images/lib/mediaitem.py index d538c70af..e418ee44e 100644 --- a/openlp/plugins/images/lib/mediaitem.py +++ b/openlp/plugins/images/lib/mediaitem.py @@ -144,7 +144,7 @@ class ImageMediaItem(MediaManagerItem): items = self.ListView.selectedIndexes() if items: service_item.title = self.trUtf8('Image(s)') - self.service_item.autoPreviewAllowed = True + service_item.autoPreviewAllowed = True for item in items: bitem = self.ListView.item(item.row()) filename = unicode((bitem.data(QtCore.Qt.UserRole)).toString()) diff --git a/openlp/plugins/media/mediaplugin.py b/openlp/plugins/media/mediaplugin.py index 640de1cb3..532a50410 100644 --- a/openlp/plugins/media/mediaplugin.py +++ b/openlp/plugins/media/mediaplugin.py @@ -27,6 +27,7 @@ import logging from openlp.core.lib import Plugin, build_icon, PluginStatus from openlp.plugins.media.lib import MediaMediaItem +from PyQt4.phonon import Phonon class MediaPlugin(Plugin): global log From 1979f2450249251620c5f1d1d6acc1b2b9d3004d Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sat, 27 Feb 2010 09:55:44 +0000 Subject: [PATCH 113/164] Fix song editing where text is lost --- openlp/plugins/media/lib/mediaitem.py | 6 +++--- openlp/plugins/media/mediaplugin.py | 9 ++++++--- openlp/plugins/songs/forms/editsongform.py | 6 +++--- openlp/plugins/songs/forms/editverseform.py | 9 +++++++-- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/openlp/plugins/media/lib/mediaitem.py b/openlp/plugins/media/lib/mediaitem.py index 46b059c01..67f1024a9 100644 --- a/openlp/plugins/media/lib/mediaitem.py +++ b/openlp/plugins/media/lib/mediaitem.py @@ -30,6 +30,8 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import MediaManagerItem, BaseListWithDnD, build_icon +log = logging.getLogger(__name__) + class MediaListView(BaseListWithDnD): def __init__(self, parent=None): self.PluginName = u'Media' @@ -39,9 +41,7 @@ class MediaMediaItem(MediaManagerItem): """ This is the custom media manager item for Media Slides. """ - global log - log = logging.getLogger(u'MediaMediaItem') - log.info(u'Media Media Item loaded') + log.info(u'%s MediaMediaItem loaded', __name__) def __init__(self, parent, icon, title): self.PluginNameShort = u'Media' diff --git a/openlp/plugins/media/mediaplugin.py b/openlp/plugins/media/mediaplugin.py index 532a50410..b403ab0de 100644 --- a/openlp/plugins/media/mediaplugin.py +++ b/openlp/plugins/media/mediaplugin.py @@ -29,10 +29,10 @@ from openlp.core.lib import Plugin, build_icon, PluginStatus from openlp.plugins.media.lib import MediaMediaItem from PyQt4.phonon import Phonon +log = logging.getLogger(__name__) + class MediaPlugin(Plugin): - global log - log = logging.getLogger(u'MediaPlugin') - log.info(u'Media Plugin loaded') + log.info(u'%s MediaPlugin loaded', __name__) def __init__(self, plugin_helpers): Plugin.__init__(self, u'Media', u'1.9.1', plugin_helpers) @@ -41,6 +41,9 @@ class MediaPlugin(Plugin): # passed with drag and drop messages self.dnd_id = u'Media' self.status = PluginStatus.Active +# print Phonon.BackendCapabilities.availableMimeTypes() +# for mimetype in Phonon.BackendCapabilities.availableMimeTypes(): +# print mimetype def initialise(self): log.info(u'Plugin Initialising') diff --git a/openlp/plugins/songs/forms/editsongform.py b/openlp/plugins/songs/forms/editsongform.py index 477910108..dd3af03c3 100644 --- a/openlp/plugins/songs/forms/editsongform.py +++ b/openlp/plugins/songs/forms/editsongform.py @@ -33,13 +33,13 @@ from openlp.plugins.songs.forms import EditVerseForm from openlp.plugins.songs.lib.models import Song from editsongdialog import Ui_EditSongDialog +log = logging.getLogger(__name__) + class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): """ Class to manage the editing of a song """ - global log - log = logging.getLogger(u'EditSongForm') - log.info(u'Song Editor loaded') + log.info(u'%s EditSongForm loaded', __name__) def __init__(self, songmanager, parent=None): """ diff --git a/openlp/plugins/songs/forms/editverseform.py b/openlp/plugins/songs/forms/editverseform.py index 148fd164a..756d2a102 100644 --- a/openlp/plugins/songs/forms/editverseform.py +++ b/openlp/plugins/songs/forms/editverseform.py @@ -77,6 +77,8 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog): def setVerse(self, text, verseCount=0, single=False, tag=u'Verse:1'): posVerse = 0 posSub = 0 + if len(text) == 0: + text = u'---[Verse:1]---' if single: id = tag.split(u':') posVerse = self.VerseListComboBox.findText(id[0], QtCore.Qt.MatchExactly) @@ -119,11 +121,14 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog): unicode(self.SubVerseListComboBox.currentText()) def getVerseAll(self): - return self.VerseTextEdit.toPlainText() + text = self.VerseTextEdit.toPlainText() + if not text.startsWith(u'---['): + text = u'---[Verse:1]---\n%s' % text + return text def onVerseComboChanged(self, id): if unicode(self.VerseListComboBox.currentText()) == u'Verse': self.SubVerseListComboBox.setEnabled(True) else: self.SubVerseListComboBox.setEnabled(False) - self.SubVerseListComboBox.setCurrentIndex(0) \ No newline at end of file + self.SubVerseListComboBox.setCurrentIndex(0) From 86e03f0c116f58810ee02a1ae918104739cea8bf Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sat, 27 Feb 2010 10:10:03 +0000 Subject: [PATCH 114/164] Move editing to line 2 --- openlp/plugins/songs/forms/editverseform.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openlp/plugins/songs/forms/editverseform.py b/openlp/plugins/songs/forms/editverseform.py index 756d2a102..cee84aae5 100644 --- a/openlp/plugins/songs/forms/editverseform.py +++ b/openlp/plugins/songs/forms/editverseform.py @@ -78,7 +78,7 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog): posVerse = 0 posSub = 0 if len(text) == 0: - text = u'---[Verse:1]---' + text = u'---[Verse:1]---\n' if single: id = tag.split(u':') posVerse = self.VerseListComboBox.findText(id[0], QtCore.Qt.MatchExactly) @@ -114,6 +114,7 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog): self.VerseTextEdit.setPlainText(text) self.VerseTextEdit.setFocus(QtCore.Qt.OtherFocusReason) self.onVerseComboChanged(0) + self.VerseTextEdit.moveCursor(QtGui.QTextCursor.Down) def getVerse(self): return self.VerseTextEdit.toPlainText(), \ From 065986c33cae3e2a85a7133a4362fc64b0a7cdf8 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sat, 27 Feb 2010 14:57:33 +0000 Subject: [PATCH 115/164] Automate Media type list from phonon --- openlp/plugins/media/lib/mediaitem.py | 6 +++--- openlp/plugins/media/mediaplugin.py | 22 +++++++++++++++++++--- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/openlp/plugins/media/lib/mediaitem.py b/openlp/plugins/media/lib/mediaitem.py index 67f1024a9..c91440c5c 100644 --- a/openlp/plugins/media/lib/mediaitem.py +++ b/openlp/plugins/media/lib/mediaitem.py @@ -61,9 +61,9 @@ class MediaMediaItem(MediaManagerItem): def retranslateUi(self): self.OnNewPrompt = self.trUtf8('Select Media') - self.OnNewFileMasks = self.trUtf8('Videos (*.avi *.mpeg *.mpg *.wmv ' - '*.mov *.mp4 *.flv);;Audio (*.ogg *.mp3 *.wma *.wav *.flac)' - ';;All files (*)') + self.OnNewFileMasks = self.trUtf8('Videos (%s);;' + 'Audio (%s);;' + 'All files (*)' % (self.parent.video_list, self.parent.audio_list)) def requiredIcons(self): MediaManagerItem.requiredIcons(self) diff --git a/openlp/plugins/media/mediaplugin.py b/openlp/plugins/media/mediaplugin.py index b403ab0de..096d53aea 100644 --- a/openlp/plugins/media/mediaplugin.py +++ b/openlp/plugins/media/mediaplugin.py @@ -41,9 +41,25 @@ class MediaPlugin(Plugin): # passed with drag and drop messages self.dnd_id = u'Media' self.status = PluginStatus.Active -# print Phonon.BackendCapabilities.availableMimeTypes() -# for mimetype in Phonon.BackendCapabilities.availableMimeTypes(): -# print mimetype + self.audio_list = u'' + self.video_list = u'' + for mimetype in Phonon.BackendCapabilities.availableMimeTypes(): + mimetype = unicode(mimetype) + type = mimetype.split(u'audio/x-') + self.audio_list, mimetype = self._add_to_list(self.audio_list, type, mimetype) + type = mimetype.split(u'audio/') + self.audio_list, mimetype = self._add_to_list(self.audio_list, type, mimetype) + type = mimetype.split(u'video/x-') + self.video_list, mimetype = self._add_to_list(self.video_list, type, mimetype) + type = mimetype.split(u'video/') + self.video_list, mimetype = self._add_to_list(self.video_list, type, mimetype) + + def _add_to_list(self, list, value, type): + if len(value) == 2: + if list.find(value[1]) == -1: + list += u'*.%s ' % value[1] + type = u'' + return list, type def initialise(self): log.info(u'Plugin Initialising') From 325fab92dabe3724dcea5dd2f3f317b1eb5839ca Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sat, 27 Feb 2010 15:31:23 +0000 Subject: [PATCH 116/164] Clean up all the Gloab log statements --- openlp.pyw | 2 +- openlp/core/lib/eventreceiver.py | 5 ++--- openlp/core/lib/mediamanageritem.py | 5 ++--- openlp/core/lib/plugin.py | 4 ++-- openlp/core/lib/pluginmanager.py | 4 ++-- openlp/core/lib/renderer.py | 4 ++-- openlp/core/lib/rendermanager.py | 4 ++-- openlp/core/lib/serviceitem.py | 4 ++-- openlp/core/lib/songxmlhandler.py | 8 +++----- openlp/core/ui/maindisplay.py | 6 ++---- openlp/core/ui/mainwindow.py | 4 ++-- openlp/core/ui/pluginform.py | 6 +++--- openlp/core/ui/screen.py | 4 ++-- openlp/core/ui/servicemanager.py | 5 ++--- openlp/core/ui/slidecontroller.py | 5 ++--- openlp/core/ui/thememanager.py | 5 ++--- openlp/migration/display.py | 6 +++--- openlp/plugins/alerts/alertsplugin.py | 4 ++-- openlp/plugins/alerts/lib/alertsmanager.py | 6 +++--- openlp/plugins/alerts/lib/manager.py | 5 ++--- openlp/plugins/bibles/bibleplugin.py | 4 ++-- openlp/plugins/bibles/forms/importwizardform.py | 7 +++---- openlp/plugins/bibles/lib/biblestab.py | 4 ++-- openlp/plugins/bibles/lib/manager.py | 4 ++-- openlp/plugins/bibles/lib/mediaitem.py | 4 ++-- openlp/plugins/bibles/lib/osis.py | 4 ++-- openlp/plugins/custom/customplugin.py | 4 +--- openlp/plugins/custom/forms/editcustomform.py | 4 ++-- openlp/plugins/custom/lib/manager.py | 5 ++--- openlp/plugins/custom/lib/mediaitem.py | 4 ++-- openlp/plugins/images/imageplugin.py | 4 ++-- openlp/plugins/images/lib/mediaitem.py | 4 ++-- openlp/plugins/presentations/lib/impresscontroller.py | 6 +++--- openlp/plugins/presentations/lib/mediaitem.py | 4 ++-- openlp/plugins/presentations/lib/messagelistener.py | 6 ++---- openlp/plugins/presentations/lib/powerpointcontroller.py | 6 +++--- openlp/plugins/presentations/lib/pptviewcontroller.py | 6 +++--- .../plugins/presentations/lib/presentationcontroller.py | 4 ++-- openlp/plugins/presentations/presentationplugin.py | 4 ++-- openlp/plugins/remotes/remoteplugin.py | 5 ++--- openlp/plugins/songs/lib/manager.py | 5 ++--- openlp/plugins/songs/lib/mediaitem.py | 4 ++-- openlp/plugins/songs/songsplugin.py | 5 ++--- openlp/plugins/songusage/forms/songusagedetailform.py | 4 ++-- openlp/plugins/songusage/lib/manager.py | 5 ++--- openlp/plugins/songusage/songusageplugin.py | 4 ++-- 46 files changed, 98 insertions(+), 118 deletions(-) diff --git a/openlp.pyw b/openlp.pyw index 5c18486b6..31869e797 100755 --- a/openlp.pyw +++ b/openlp.pyw @@ -59,13 +59,13 @@ QToolBar padding: 0; } """ +log = logging.getLogger(__name__) class OpenLP(QtGui.QApplication): """ The core application class. This class inherits from Qt's QApplication class in order to provide the core of the application. """ - global log log.info(u'OpenLP Application Loaded') def notify(self, obj, evt): diff --git a/openlp/core/lib/eventreceiver.py b/openlp/core/lib/eventreceiver.py index 9bd7cd652..be7dff67a 100644 --- a/openlp/core/lib/eventreceiver.py +++ b/openlp/core/lib/eventreceiver.py @@ -27,6 +27,8 @@ import logging from PyQt4 import QtCore +log = logging.getLogger(__name__) + class EventReceiver(QtCore.QObject): """ Class to allow events to be passed from different parts of the @@ -108,9 +110,6 @@ class EventReceiver(QtCore.QObject): Informs all components of the presentation types supported. """ - global log - log = logging.getLogger(u'EventReceiver') - def __init__(self): """ Initialise the event receiver, calling the parent constructor. diff --git a/openlp/core/lib/mediamanageritem.py b/openlp/core/lib/mediamanageritem.py index 5f490eed1..ff87f0b0d 100644 --- a/openlp/core/lib/mediamanageritem.py +++ b/openlp/core/lib/mediamanageritem.py @@ -32,6 +32,8 @@ from openlp.core.lib.toolbar import * from openlp.core.lib import contextMenuAction, contextMenuSeparator from serviceitem import ServiceItem +log = logging.getLogger(__name__) + class MediaManagerItem(QtGui.QWidget): """ MediaManagerItem is a helper widget for plugins. @@ -92,9 +94,6 @@ class MediaManagerItem(QtGui.QWidget): method is not defined, a default will be used (treat the filename as an image). """ - - global log - log = logging.getLogger(u'MediaManagerItem') log.info(u'Media Item loaded') def __init__(self, parent=None, icon=None, title=None): diff --git a/openlp/core/lib/plugin.py b/openlp/core/lib/plugin.py index ca291d578..e15369145 100644 --- a/openlp/core/lib/plugin.py +++ b/openlp/core/lib/plugin.py @@ -28,6 +28,8 @@ from PyQt4 import QtCore from openlp.core.lib import PluginConfig, Receiver +log = logging.getLogger(__name__) + class PluginStatus(object): """ Defines the status of the plugin @@ -88,8 +90,6 @@ class Plugin(QtCore.QObject): Used in the plugin manager, when a person clicks on the 'About' button. """ - global log - log = logging.getLogger(u'Plugin') log.info(u'loaded') def __init__(self, name, version=None, plugin_helpers=None): diff --git a/openlp/core/lib/pluginmanager.py b/openlp/core/lib/pluginmanager.py index b06f23953..2ea5751ec 100644 --- a/openlp/core/lib/pluginmanager.py +++ b/openlp/core/lib/pluginmanager.py @@ -29,13 +29,13 @@ import logging from openlp.core.lib import Plugin, PluginStatus +log = logging.getLogger(__name__) + class PluginManager(object): """ This is the Plugin manager, which loads all the plugins, and executes all the hooks, as and when necessary. """ - global log - log = logging.getLogger(u'PluginMgr') log.info(u'Plugin manager loaded') def __init__(self, dir): diff --git a/openlp/core/lib/renderer.py b/openlp/core/lib/renderer.py index cc976dae5..ea6e849d8 100644 --- a/openlp/core/lib/renderer.py +++ b/openlp/core/lib/renderer.py @@ -28,13 +28,13 @@ import logging from PyQt4 import QtGui, QtCore from openlp.core.lib import resize_image +log = logging.getLogger(__name__) + class Renderer(object): """ Genarates a pixmap image of a array of text. The Text is formatted to make sure it fits on the screen and if not extra frames are generated. """ - global log - log = logging.getLogger(u'Renderer') log.info(u'Renderer Loaded') def __init__(self): diff --git a/openlp/core/lib/rendermanager.py b/openlp/core/lib/rendermanager.py index 1e8363228..edab3d309 100644 --- a/openlp/core/lib/rendermanager.py +++ b/openlp/core/lib/rendermanager.py @@ -30,6 +30,8 @@ from PyQt4 import QtCore from renderer import Renderer from openlp.core.lib import ThemeLevel +log = logging.getLogger(__name__) + class RenderManager(object): """ Class to pull all Renderer interactions into one place. The plugins will @@ -45,8 +47,6 @@ class RenderManager(object): ``screen_number`` Defaults to *0*. The index of the output/display screen. """ - global log - log = logging.getLogger(u'RenderManager') log.info(u'RenderManager Loaded') def __init__(self, theme_manager, screens, screen_number=0): diff --git a/openlp/core/lib/serviceitem.py b/openlp/core/lib/serviceitem.py index e62b24d8e..52deafc06 100644 --- a/openlp/core/lib/serviceitem.py +++ b/openlp/core/lib/serviceitem.py @@ -32,6 +32,8 @@ from PyQt4 import QtGui from openlp.core.lib import build_icon, Receiver, resize_image +log = logging.getLogger(__name__) + class ServiceItemType(object): """ Defines the type of service item @@ -46,8 +48,6 @@ class ServiceItem(object): the service manager, the slide controller, and the projection screen compositor. """ - global log - log = logging.getLogger(u'ServiceItem') log.info(u'Service Item created') def __init__(self, plugin=None): diff --git a/openlp/core/lib/songxmlhandler.py b/openlp/core/lib/songxmlhandler.py index 596866934..7a532970d 100644 --- a/openlp/core/lib/songxmlhandler.py +++ b/openlp/core/lib/songxmlhandler.py @@ -27,6 +27,8 @@ import logging from xml.dom.minidom import Document from xml.etree.ElementTree import ElementTree, XML, dump +log = logging.getLogger(__name__) + class SongXMLBuilder(object): """ This class builds the XML used to describe songs. @@ -42,8 +44,6 @@ class SongXMLBuilder(object): </lyrics> </song> """ - global log - log = logging.getLogger(u'SongXMLBuilder') log.info(u'SongXMLBuilder Loaded') def __init__(self): @@ -123,8 +123,6 @@ class SongXMLParser(object): </lyrics> </song> """ - global log - log = logging.getLogger(u'SongXMLParser') log.info(u'SongXMLParser Loaded') def __init__(self, xml): @@ -158,4 +156,4 @@ class SongXMLParser(object): """ Debugging aid to dump XML so that we can see what we have. """ - return dump(self.song_xml) \ No newline at end of file + return dump(self.song_xml) diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index bdb396498..87935cf5e 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -31,13 +31,13 @@ from PyQt4.phonon import Phonon from openlp.core.lib import Receiver, resize_image +log = logging.getLogger(__name__) + class DisplayWidget(QtGui.QWidget): """ Customised version of QTableWidget which can respond to keyboard events. """ - global log - log = logging.getLogger(u'MainDisplay') log.info(u'MainDisplay loaded') def __init__(self, parent=None, name=None): @@ -78,8 +78,6 @@ class MainDisplay(DisplayWidget): """ This is the form that is used to display things on the projector. """ - global log - log = logging.getLogger(u'MainDisplay') log.info(u'MainDisplay Loaded') def __init__(self, parent, screens): diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 96a089df1..7e822fb8a 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -36,6 +36,8 @@ from openlp.core.lib import RenderManager, PluginConfig, build_icon, \ OpenLPDockWidget, SettingsManager, PluginManager, Receiver, str_to_bool from openlp.core.utils import check_latest_version +log = logging.getLogger(__name__) + media_manager_style = """ QToolBox::tab { background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, @@ -420,8 +422,6 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): """ The main window. """ - global log - log = logging.getLogger(u'MainWindow') log.info(u'MainWindow loaded') def __init__(self, screens, applicationVersion): diff --git a/openlp/core/ui/pluginform.py b/openlp/core/ui/pluginform.py index fa48c0723..9af8b7ca3 100644 --- a/openlp/core/ui/pluginform.py +++ b/openlp/core/ui/pluginform.py @@ -30,9 +30,9 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib.plugin import PluginStatus from plugindialog import Ui_PluginViewDialog +log = logging.getLogger(__name__) + class PluginForm(QtGui.QDialog, Ui_PluginViewDialog): - global log - log = logging.getLogger(u'PluginForm') def __init__(self, parent=None): QtGui.QDialog.__init__(self, parent) @@ -126,4 +126,4 @@ class PluginForm(QtGui.QDialog, Ui_PluginViewDialog): elif self.activePlugin.status == PluginStatus.Disabled: status_text = 'Disabled' self.PluginListWidget.currentItem().setText( - u'%s (%s)' % (self.activePlugin.name, status_text)) \ No newline at end of file + u'%s (%s)' % (self.activePlugin.name, status_text)) diff --git a/openlp/core/ui/screen.py b/openlp/core/ui/screen.py index 2321c3020..9fad9ca9b 100644 --- a/openlp/core/ui/screen.py +++ b/openlp/core/ui/screen.py @@ -24,12 +24,12 @@ ############################################################################### import logging +log = logging.getLogger(__name__) + class ScreenList(object): """ Wrapper to handle the parameters of the display screen """ - global log - log = logging.getLogger(u'Screen') log.info(u'Screen loaded') def __init__(self): diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 22fbb7ecc..f13cca760 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -28,6 +28,8 @@ import logging import cPickle import zipfile +log = logging.getLogger(__name__) + from PyQt4 import QtCore, QtGui from openlp.core.lib import PluginConfig, OpenLPToolbar, ServiceItem, \ contextMenuAction, contextMenuSeparator, contextMenu, Receiver, \ @@ -105,9 +107,6 @@ class ServiceManager(QtGui.QWidget): the resources used into one OSZ file for use on any OpenLP v2 installation. Also handles the UI tasks of moving things up and down etc. """ - global log - log = logging.getLogger(u'ServiceManager') - def __init__(self, parent): """ Sets up the service manager, toolbars, list view, et al. diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 515fb2d3c..81ff030ae 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -33,6 +33,8 @@ from PyQt4.phonon import Phonon from openlp.core.lib import OpenLPToolbar, Receiver, str_to_bool, \ PluginConfig, resize_image +log = logging.getLogger(__name__) + class SlideList(QtGui.QTableWidget): """ Customised version of QTableWidget which can respond to keyboard @@ -74,9 +76,6 @@ class SlideController(QtGui.QWidget): SlideController is the slide controller widget. This widget is what the user uses to control the displaying of verses/slides/etc on the screen. """ - global log - log = logging.getLogger(u'SlideController') - def __init__(self, parent, settingsmanager, isLive=False): """ Set up the Slide Controller. diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index 577021418..ba541aa6e 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -38,13 +38,12 @@ from openlp.core.lib import PluginConfig, OpenLPToolbar, contextMenuAction, \ contextMenuSeparator from openlp.core.utils import ConfigHelper +log = logging.getLogger(__name__) + class ThemeManager(QtGui.QWidget): """ Manages the orders of Theme. """ - global log - log = logging.getLogger(u'ThemeManager') - def __init__(self, parent): QtGui.QWidget.__init__(self, parent) self.parent = parent diff --git a/openlp/migration/display.py b/openlp/migration/display.py index 9a3f6e44e..bffef5b17 100644 --- a/openlp/migration/display.py +++ b/openlp/migration/display.py @@ -25,9 +25,9 @@ import logging +log = logging.getLogger(__name__) + class Display(): - global log - log = logging.getLogger(u'Display Logger') log.info(u'Display Class loaded') @staticmethod @@ -39,4 +39,4 @@ class Display(): def sub_output(string): if not string is None: log.debug(u' '+string); - print (u' '+string) \ No newline at end of file + print (u' '+string) diff --git a/openlp/plugins/alerts/alertsplugin.py b/openlp/plugins/alerts/alertsplugin.py index 5bf268394..3e065f6af 100644 --- a/openlp/plugins/alerts/alertsplugin.py +++ b/openlp/plugins/alerts/alertsplugin.py @@ -31,9 +31,9 @@ from openlp.core.lib import Plugin, build_icon, PluginStatus from openlp.plugins.alerts.lib import AlertsManager, DBManager from openlp.plugins.alerts.forms import AlertsTab, AlertForm, AlertEditForm +log = logging.getLogger(__name__) + class alertsPlugin(Plugin): - global log - log = logging.getLogger(u'AlertsPlugin') log.info(u'Alerts Plugin loaded') def __init__(self, plugin_helpers): diff --git a/openlp/plugins/alerts/lib/alertsmanager.py b/openlp/plugins/alerts/lib/alertsmanager.py index deda11240..6838fefa0 100644 --- a/openlp/plugins/alerts/lib/alertsmanager.py +++ b/openlp/plugins/alerts/lib/alertsmanager.py @@ -29,12 +29,12 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import Receiver +log = logging.getLogger(__name__) + class AlertsManager(QtCore.QObject): """ - BiblesTab is the Bibles settings tab in the settings dialog. + AlertsTab is the Alerts settings tab in the settings dialog. """ - global log - log = logging.getLogger(u'AlertManager') log.info(u'Alert Manager loaded') def __init__(self, parent): diff --git a/openlp/plugins/alerts/lib/manager.py b/openlp/plugins/alerts/lib/manager.py index 3480b3103..a3ed1ee7e 100644 --- a/openlp/plugins/alerts/lib/manager.py +++ b/openlp/plugins/alerts/lib/manager.py @@ -27,14 +27,13 @@ import logging from openlp.plugins.alerts.lib.models import init_models, metadata, AlertItem +log = logging.getLogger(__name__) + class DBManager(): """ The Song Manager provides a central location for all database code. This class takes care of connecting to the database and running all the queries. """ - - global log - log = logging.getLogger(u'AlertsDBManager') log.info(u'Alerts DB loaded') def __init__(self, config): diff --git a/openlp/plugins/bibles/bibleplugin.py b/openlp/plugins/bibles/bibleplugin.py index 9d85a90a0..8cf15e942 100644 --- a/openlp/plugins/bibles/bibleplugin.py +++ b/openlp/plugins/bibles/bibleplugin.py @@ -30,9 +30,9 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import Plugin, build_icon, PluginStatus from openlp.plugins.bibles.lib import BibleManager, BiblesTab, BibleMediaItem +log = logging.getLogger(__name__) + class BiblePlugin(Plugin): - global log - log = logging.getLogger(u'BiblePlugin') log.info(u'Bible Plugin loaded') def __init__(self, plugin_helpers): diff --git a/openlp/plugins/bibles/forms/importwizardform.py b/openlp/plugins/bibles/forms/importwizardform.py index 2f5e84867..dfca754a8 100644 --- a/openlp/plugins/bibles/forms/importwizardform.py +++ b/openlp/plugins/bibles/forms/importwizardform.py @@ -34,6 +34,8 @@ from bibleimportwizard import Ui_BibleImportWizard from openlp.core.lib import Receiver from openlp.plugins.bibles.lib.manager import BibleFormat +log = logging.getLogger(__name__) + class DownloadLocation(object): Unknown = -1 Crosswalk = 0 @@ -54,9 +56,6 @@ class ImportWizardForm(QtGui.QWizard, Ui_BibleImportWizard): This is the Bible Import Wizard, which allows easy importing of Bibles into OpenLP from other formats like OSIS, CSV and OpenSong. """ - - global log - log = logging.getLogger(u'BibleImportForm') log.info(u'BibleImportForm loaded') def __init__(self, parent, config, manager, bibleplugin): @@ -371,4 +370,4 @@ class ImportWizardForm(QtGui.QWizard, Ui_BibleImportWizard): self.ImportProgressBar.setValue(self.ImportProgressBar.maximum()) self.finishButton.setVisible(True) self.cancelButton.setVisible(False) - Receiver.send_message(u'process_events') \ No newline at end of file + Receiver.send_message(u'process_events') diff --git a/openlp/plugins/bibles/lib/biblestab.py b/openlp/plugins/bibles/lib/biblestab.py index ef8e6a090..398040fd8 100644 --- a/openlp/plugins/bibles/lib/biblestab.py +++ b/openlp/plugins/bibles/lib/biblestab.py @@ -29,12 +29,12 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import str_to_bool, Receiver, SettingsTab +log = logging.getLogger(__name__) + class BiblesTab(SettingsTab): """ BiblesTab is the Bibles settings tab in the settings dialog. """ - global log - log = logging.getLogger(u'BibleTab') log.info(u'Bible Tab loaded') def __init__(self, title, section=None): diff --git a/openlp/plugins/bibles/lib/manager.py b/openlp/plugins/bibles/lib/manager.py index 965d0433f..d24982532 100644 --- a/openlp/plugins/bibles/lib/manager.py +++ b/openlp/plugins/bibles/lib/manager.py @@ -33,6 +33,8 @@ from csvbible import CSVBible from db import BibleDB from http import HTTPBible +log = logging.getLogger(__name__) + class BibleMode(object): """ This is basically an enumeration class which specifies the mode of a Bible. @@ -85,8 +87,6 @@ class BibleManager(object): """ The Bible manager which holds and manages all the Bibles. """ - global log - log = logging.getLogger(u'BibleManager') log.info(u'Bible manager loaded') def __init__(self, parent, config): diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index c0a3bde35..30403e69d 100644 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -32,6 +32,8 @@ from openlp.core.lib import MediaManagerItem, Receiver, str_to_bool, \ BaseListWithDnD from openlp.plugins.bibles.forms import ImportWizardForm +log = logging.getLogger(__name__) + class BibleListView(BaseListWithDnD): """ Drag and drop capable list for Bibles. @@ -47,8 +49,6 @@ class BibleMediaItem(MediaManagerItem): """ This is the custom media manager item for Bibles. """ - global log - log = logging.getLogger(u'BibleMediaItem') log.info(u'Bible Media Item loaded') def __init__(self, parent, icon, title): diff --git a/openlp/plugins/bibles/lib/osis.py b/openlp/plugins/bibles/lib/osis.py index cfa68b213..6498e4d34 100644 --- a/openlp/plugins/bibles/lib/osis.py +++ b/openlp/plugins/bibles/lib/osis.py @@ -33,12 +33,12 @@ import re from openlp.core.lib import Receiver from db import BibleDB +log = logging.getLogger(__name__) + class OSISBible(BibleDB): """ OSIS Bible format importer class. """ - global log - log = logging.getLogger(u'BibleOSISImpl') log.info(u'BibleOSISImpl loaded') def __init__(self, parent, **kwargs): diff --git a/openlp/plugins/custom/customplugin.py b/openlp/plugins/custom/customplugin.py index fb30bed9f..3e21228df 100644 --- a/openlp/plugins/custom/customplugin.py +++ b/openlp/plugins/custom/customplugin.py @@ -29,6 +29,7 @@ from forms import EditCustomForm from openlp.core.lib import Plugin, build_icon, PluginStatus from openlp.plugins.custom.lib import CustomManager, CustomMediaItem, CustomTab +log = logging.getLogger(__name__) class CustomPlugin(Plugin): """ @@ -39,9 +40,6 @@ class CustomPlugin(Plugin): the songs plugin has become restrictive. Examples could be Welcome slides, Bible Reading information, Orders of service. """ - - global log - log = logging.getLogger(u'CustomPlugin') log.info(u'Custom Plugin loaded') def __init__(self, plugin_helpers): diff --git a/openlp/plugins/custom/forms/editcustomform.py b/openlp/plugins/custom/forms/editcustomform.py index e701c0938..577142f23 100644 --- a/openlp/plugins/custom/forms/editcustomform.py +++ b/openlp/plugins/custom/forms/editcustomform.py @@ -30,12 +30,12 @@ from editcustomdialog import Ui_customEditDialog from openlp.core.lib import SongXMLBuilder, SongXMLParser, Receiver from openlp.plugins.custom.lib.models import CustomSlide +log = logging.getLogger(__name__) + class EditCustomForm(QtGui.QDialog, Ui_customEditDialog): """ Class documentation goes here. """ - global log - log = logging.getLogger(u'EditCustomForm') log.info(u'Custom Editor loaded') def __init__(self, custommanager, parent = None): """ diff --git a/openlp/plugins/custom/lib/manager.py b/openlp/plugins/custom/lib/manager.py index d1d3e0349..e88e72b5c 100644 --- a/openlp/plugins/custom/lib/manager.py +++ b/openlp/plugins/custom/lib/manager.py @@ -27,14 +27,13 @@ import logging from openlp.plugins.custom.lib.models import init_models, metadata, CustomSlide +log = logging.getLogger(__name__) + class CustomManager(): """ The Song Manager provides a central location for all database code. This class takes care of connecting to the database and running all the queries. """ - - global log - log = logging.getLogger(u'CustomManager') log.info(u'Custom manager loaded') def __init__(self, config): diff --git a/openlp/plugins/custom/lib/mediaitem.py b/openlp/plugins/custom/lib/mediaitem.py index c4b9ef16a..2a3090cf2 100644 --- a/openlp/plugins/custom/lib/mediaitem.py +++ b/openlp/plugins/custom/lib/mediaitem.py @@ -30,6 +30,8 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import MediaManagerItem, SongXMLParser, BaseListWithDnD,\ Receiver, str_to_bool +log = logging.getLogger(__name__) + class CustomListView(BaseListWithDnD): def __init__(self, parent=None): self.PluginName = u'Custom' @@ -39,8 +41,6 @@ class CustomMediaItem(MediaManagerItem): """ This is the custom media manager item for Custom Slides. """ - global log - log = logging.getLogger(u'CustomMediaItem') log.info(u'Custom Media Item loaded') def __init__(self, parent, icon, title): diff --git a/openlp/plugins/images/imageplugin.py b/openlp/plugins/images/imageplugin.py index e6895c767..40e9c1e1d 100644 --- a/openlp/plugins/images/imageplugin.py +++ b/openlp/plugins/images/imageplugin.py @@ -28,9 +28,9 @@ import logging from openlp.core.lib import Plugin, build_icon, PluginStatus from openlp.plugins.images.lib import ImageMediaItem, ImageTab +log = logging.getLogger(__name__) + class ImagePlugin(Plugin): - global log - log = logging.getLogger(u'ImagePlugin') log.info(u'Image Plugin loaded') def __init__(self, plugin_helpers): diff --git a/openlp/plugins/images/lib/mediaitem.py b/openlp/plugins/images/lib/mediaitem.py index e418ee44e..830459843 100644 --- a/openlp/plugins/images/lib/mediaitem.py +++ b/openlp/plugins/images/lib/mediaitem.py @@ -29,6 +29,8 @@ import os from PyQt4 import QtCore, QtGui from openlp.core.lib import MediaManagerItem, BaseListWithDnD, build_icon +log = logging.getLogger(__name__) + # We have to explicitly create separate classes for each plugin # in order for DnD to the Service manager to work correctly. class ImageListView(BaseListWithDnD): @@ -40,8 +42,6 @@ class ImageMediaItem(MediaManagerItem): """ This is the custom media manager item for images. """ - global log - log = logging.getLogger(u'ImageMediaItem') log.info(u'Image Media Item loaded') def __init__(self, parent, icon, title): diff --git a/openlp/plugins/presentations/lib/impresscontroller.py b/openlp/plugins/presentations/lib/impresscontroller.py index 31b3ac5f6..0ea5c4d3a 100644 --- a/openlp/plugins/presentations/lib/impresscontroller.py +++ b/openlp/plugins/presentations/lib/impresscontroller.py @@ -46,15 +46,15 @@ from PyQt4 import QtCore from presentationcontroller import PresentationController +log = logging.getLogger(__name__) + class ImpressController(PresentationController): """ Class to control interactions with Impress presentations. It creates the runtime environment, loads and closes the presentation as well as triggering the correct activities based on the users input """ - global log - log = logging.getLogger(u'ImpressController') - log.info(u'loaded') + log.info(u'ImpressController loaded') def __init__(self, plugin): """ diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index 590074ead..90487cdf5 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -31,6 +31,8 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import MediaManagerItem, BaseListWithDnD from openlp.plugins.presentations.lib import MessageListener +log = logging.getLogger(__name__) + # We have to explicitly create separate classes for each plugin # in order for DnD to the Service manager to work correctly. class PresentationListView(BaseListWithDnD): @@ -43,8 +45,6 @@ class PresentationMediaItem(MediaManagerItem): This is the Presentation media manager item for Presentation Items. It can present files using Openoffice """ - global log - log = logging.getLogger(u'PresentationsMediaItem') log.info(u'Presentations Media Item loaded') def __init__(self, parent, icon, title, controllers): diff --git a/openlp/plugins/presentations/lib/messagelistener.py b/openlp/plugins/presentations/lib/messagelistener.py index 3c665004b..5d4bef4db 100644 --- a/openlp/plugins/presentations/lib/messagelistener.py +++ b/openlp/plugins/presentations/lib/messagelistener.py @@ -30,13 +30,13 @@ from PyQt4 import QtCore from openlp.core.lib import Receiver +log = logging.getLogger(__name__) + class Controller(object): """ This is the Presentation listener who acts on events from the slide controller and passes the messages on the the correct presentation handlers """ - global log - log = logging.getLogger(u'Controller') log.info(u'Controller loaded') def __init__(self, live): @@ -149,8 +149,6 @@ class MessageListener(object): This is the Presentation listener who acts on events from the slide controller and passes the messages on the the correct presentation handlers """ - global log - log = logging.getLogger(u'MessageListener') log.info(u'Message Listener loaded') def __init__(self, controllers): diff --git a/openlp/plugins/presentations/lib/powerpointcontroller.py b/openlp/plugins/presentations/lib/powerpointcontroller.py index a9775e086..42bc160de 100644 --- a/openlp/plugins/presentations/lib/powerpointcontroller.py +++ b/openlp/plugins/presentations/lib/powerpointcontroller.py @@ -33,6 +33,8 @@ if os.name == u'nt': from presentationcontroller import PresentationController +log = logging.getLogger(__name__) + # PPT API documentation: # http://msdn.microsoft.com/en-us/library/aa269321(office.10).aspx @@ -42,9 +44,7 @@ class PowerpointController(PresentationController): It creates the runtime Environment , Loads the and Closes the Presentation As well as triggering the correct activities based on the users input """ - global log - log = logging.getLogger(u'PowerpointController') - log.info(u'loaded') + log.info(u'PowerpointController loaded') def __init__(self, plugin): """ diff --git a/openlp/plugins/presentations/lib/pptviewcontroller.py b/openlp/plugins/presentations/lib/pptviewcontroller.py index 2ed457fc0..ddfa5ab7a 100644 --- a/openlp/plugins/presentations/lib/pptviewcontroller.py +++ b/openlp/plugins/presentations/lib/pptviewcontroller.py @@ -32,15 +32,15 @@ if os.name == u'nt': from presentationcontroller import PresentationController +log = logging.getLogger(__name__) + class PptviewController(PresentationController): """ Class to control interactions with PowerPOint Viewer Presentations It creates the runtime Environment , Loads the and Closes the Presentation As well as triggering the correct activities based on the users input """ - global log - log = logging.getLogger(u'PptviewController') - log.info(u'loaded') + log.info(u'PPTViewController loaded') def __init__(self, plugin): """ diff --git a/openlp/plugins/presentations/lib/presentationcontroller.py b/openlp/plugins/presentations/lib/presentationcontroller.py index db42a482d..b0f3e4e79 100644 --- a/openlp/plugins/presentations/lib/presentationcontroller.py +++ b/openlp/plugins/presentations/lib/presentationcontroller.py @@ -31,6 +31,8 @@ from PyQt4 import QtCore from openlp.core.lib import Receiver +log = logging.getLogger(__name__) + class PresentationController(object): """ Base class for presentation controllers to inherit from @@ -116,8 +118,6 @@ class PresentationController(object): Returns a path to an image containing a preview for the requested slide """ - global log - log = logging.getLogger(u'PresentationController') log.info(u'PresentationController loaded') def __init__(self, plugin=None, name=u'PresentationController'): diff --git a/openlp/plugins/presentations/presentationplugin.py b/openlp/plugins/presentations/presentationplugin.py index 7103a3a2c..061eb737f 100644 --- a/openlp/plugins/presentations/presentationplugin.py +++ b/openlp/plugins/presentations/presentationplugin.py @@ -29,9 +29,9 @@ import logging from openlp.core.lib import Plugin, build_icon, Receiver, PluginStatus from openlp.plugins.presentations.lib import * -class PresentationPlugin(Plugin): +log = logging.getLogger(__name__) - global log +class PresentationPlugin(Plugin): log = logging.getLogger(u'PresentationPlugin') def __init__(self, plugin_helpers): diff --git a/openlp/plugins/remotes/remoteplugin.py b/openlp/plugins/remotes/remoteplugin.py index bd8d8974b..acfa6f97d 100644 --- a/openlp/plugins/remotes/remoteplugin.py +++ b/openlp/plugins/remotes/remoteplugin.py @@ -30,10 +30,9 @@ from PyQt4 import QtNetwork, QtCore from openlp.core.lib import Plugin, Receiver from openlp.plugins.remotes.lib import RemoteTab -class RemotesPlugin(Plugin): +log = logging.getLogger(__name__) - global log - log = logging.getLogger(u'RemotesPlugin') +class RemotesPlugin(Plugin): log.info(u'Remote Plugin loaded') def __init__(self, plugin_helpers): diff --git a/openlp/plugins/songs/lib/manager.py b/openlp/plugins/songs/lib/manager.py index ceb596942..74bd4cf82 100644 --- a/openlp/plugins/songs/lib/manager.py +++ b/openlp/plugins/songs/lib/manager.py @@ -28,14 +28,13 @@ import logging from openlp.plugins.songs.lib.models import init_models, metadata, Song, \ Author, Topic, Book +log = logging.getLogger(__name__) + class SongManager(): """ The Song Manager provides a central location for all database code. This class takes care of connecting to the database and running all the queries. """ - - global log - log = logging.getLogger(u'SongManager') log.info(u'Song manager loaded') def __init__(self, config): diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py index 63fd69d48..cd440e54f 100644 --- a/openlp/plugins/songs/lib/mediaitem.py +++ b/openlp/plugins/songs/lib/mediaitem.py @@ -31,6 +31,8 @@ from openlp.core.lib import MediaManagerItem, SongXMLParser, \ BaseListWithDnD, Receiver, str_to_bool from openlp.plugins.songs.forms import EditSongForm, SongMaintenanceForm +log = logging.getLogger(__name__) + class SongListView(BaseListWithDnD): def __init__(self, parent=None): self.PluginName = u'Songs' @@ -40,8 +42,6 @@ class SongMediaItem(MediaManagerItem): """ This is the custom media manager item for Songs. """ - global log - log = logging.getLogger(u'SongMediaItem') log.info(u'Song Media Item loaded') def __init__(self, parent, icon, title): diff --git a/openlp/plugins/songs/songsplugin.py b/openlp/plugins/songs/songsplugin.py index 088b9a1cf..eb6eb2c2a 100644 --- a/openlp/plugins/songs/songsplugin.py +++ b/openlp/plugins/songs/songsplugin.py @@ -32,6 +32,8 @@ from openlp.plugins.songs.lib import SongManager, SongMediaItem, SongsTab from openlp.plugins.songs.forms import OpenLPImportForm, OpenSongExportForm, \ OpenSongImportForm, OpenLPExportForm +log = logging.getLogger(__name__) + class SongsPlugin(Plugin): """ This is the number 1 plugin, if importance were placed on any @@ -40,9 +42,6 @@ class SongsPlugin(Plugin): specified. Authors, topics and song books can be assigned to songs as well. """ - - global log - log = logging.getLogger(u'SongsPlugin') log.info(u'Song Plugin loaded') def __init__(self, plugin_helpers): diff --git a/openlp/plugins/songusage/forms/songusagedetailform.py b/openlp/plugins/songusage/forms/songusagedetailform.py index df749d8c0..be548ac35 100644 --- a/openlp/plugins/songusage/forms/songusagedetailform.py +++ b/openlp/plugins/songusage/forms/songusagedetailform.py @@ -29,9 +29,9 @@ import logging from songusagedetaildialog import Ui_SongUsageDetailDialog +log = logging.getLogger(__name__) + class SongUsageDetailForm(QtGui.QDialog, Ui_SongUsageDetailDialog): - global log - log = logging.getLogger(u'SongUsageDetailForm') log.info(u'SongUsage Detail Form loaded') """ Class documentation goes here. diff --git a/openlp/plugins/songusage/lib/manager.py b/openlp/plugins/songusage/lib/manager.py index 6cae4c372..d9f7feb6f 100644 --- a/openlp/plugins/songusage/lib/manager.py +++ b/openlp/plugins/songusage/lib/manager.py @@ -27,14 +27,13 @@ import logging from openlp.plugins.songusage.lib.models import init_models, metadata, SongUsageItem +log = logging.getLogger(__name__) + class SongUsageManager(): """ The Song Manager provides a central location for all database code. This class takes care of connecting to the database and running all the queries. """ - - global log - log = logging.getLogger(u'SongUsageManager') log.info(u'SongUsage manager loaded') def __init__(self, config): diff --git a/openlp/plugins/songusage/songusageplugin.py b/openlp/plugins/songusage/songusageplugin.py index d0ebe68ef..d30bd4bea 100644 --- a/openlp/plugins/songusage/songusageplugin.py +++ b/openlp/plugins/songusage/songusageplugin.py @@ -33,9 +33,9 @@ from openlp.plugins.songusage.lib import SongUsageManager from openlp.plugins.songusage.forms import SongUsageDetailForm, SongUsageDeleteForm from openlp.plugins.songusage.lib.models import SongUsageItem +log = logging.getLogger(__name__) + class SongUsagePlugin(Plugin): - global log - log = logging.getLogger(u'SongUsagePlugin') log.info(u'SongUsage Plugin loaded') def __init__(self, plugin_helpers): From 447446fe9e05fda4b15e60cf63e7597f564e4b54 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Sun, 28 Feb 2010 19:43:05 +0200 Subject: [PATCH 117/164] Changed location and name of version file. --- openlp.pyw | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/openlp.pyw b/openlp.pyw index d748b339a..1bdfa8424 100755 --- a/openlp.pyw +++ b/openlp.pyw @@ -32,6 +32,7 @@ from logging import FileHandler from optparse import OptionParser from PyQt4 import QtCore, QtGui +import openlp from openlp.core.lib import Receiver, str_to_bool from openlp.core.resources import qInitResources from openlp.core.ui import MainWindow, SplashScreen, ScreenList @@ -79,8 +80,10 @@ class OpenLP(QtGui.QApplication): Run the OpenLP application. """ #Load and store current Application Version - filepath = os.path.split(os.path.abspath(__file__))[0] - filepath = os.path.abspath(os.path.join(filepath, u'version.txt')) + filepath = AppLocation.get_directory(AppLocation.AppDir) + if not hasattr(sys, u'frozen'): + filepath = os.path.join(filepath, u'openlp') + filepath = os.path.join(filepath, u'.version') fversion = None try: fversion = open(filepath, u'r') @@ -97,9 +100,9 @@ class OpenLP(QtGui.QApplication): app_version[u'version'], app_version[u'build'])) except: app_version = { - u'full': u'1.9.0-000', + u'full': u'1.9.0-bzr000', u'version': u'1.9.0', - u'build': u'000' + u'build': u'bzr000' } finally: if fversion: From e37ab565ad8a86f56c4722d3a5c41969475914f5 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Sun, 28 Feb 2010 22:50:14 +0200 Subject: [PATCH 118/164] A fix to the OSIS importer, and added the version file. --- openlp.pyw | 5 +++-- openlp/.version | 1 + openlp/plugins/bibles/lib/osis.py | 5 ++++- 3 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 openlp/.version diff --git a/openlp.pyw b/openlp.pyw index 5c18486b6..cdf762c1c 100755 --- a/openlp.pyw +++ b/openlp.pyw @@ -32,6 +32,7 @@ from logging import FileHandler from optparse import OptionParser from PyQt4 import QtCore, QtGui +import openlp from openlp.core.lib import Receiver, str_to_bool from openlp.core.resources import qInitResources from openlp.core.ui import MainWindow, SplashScreen, ScreenList @@ -77,8 +78,8 @@ class OpenLP(QtGui.QApplication): Run the OpenLP application. """ #Load and store current Application Version - filepath = os.path.split(os.path.abspath(__file__))[0] - filepath = os.path.abspath(os.path.join(filepath, u'version.txt')) + filepath = os.path.split(os.path.abspath(openlp.__file__))[0] + filepath = os.path.abspath(os.path.join(filepath, u'.version')) fversion = None try: fversion = open(filepath, u'r') diff --git a/openlp/.version b/openlp/.version new file mode 100644 index 000000000..90a9f5c8f --- /dev/null +++ b/openlp/.version @@ -0,0 +1 @@ +1.9.0-bzr718 \ No newline at end of file diff --git a/openlp/plugins/bibles/lib/osis.py b/openlp/plugins/bibles/lib/osis.py index cfa68b213..41f11e8a0 100644 --- a/openlp/plugins/bibles/lib/osis.py +++ b/openlp/plugins/bibles/lib/osis.py @@ -30,6 +30,8 @@ import chardet import codecs import re +from PyQt4 import QtCore + from openlp.core.lib import Receiver from db import BibleDB @@ -94,10 +96,11 @@ class OSISBible(BibleDB): Loads a Bible from file. """ log.debug(u'Starting OSIS import from "%s"' % self.filename) + self.wizard.incrementProgressBar(u'Detecting encoding (this may take a few minutes)...') detect_file = None try: detect_file = open(self.filename, u'r') - details = chardet.detect(detect_file.read(3000)) + details = chardet.detect(detect_file.read()) except: log.exception(u'Failed to detect OSIS file encoding') return From e7f7fe88617af4ac5df04b7c0054a450273cc5e3 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Tue, 2 Mar 2010 09:01:56 +0200 Subject: [PATCH 119/164] Added Service Edit and Service Notes icons. --- openlp/core/ui/servicemanager.py | 2 +- resources/images/openlp-2.qrc | 36 +++++++++++++++-------------- resources/images/service_edit.png | Bin 0 -> 726 bytes resources/images/service_notes.png | Bin 0 -> 668 bytes resources/images/song_edit.png | Bin 668 -> 726 bytes 5 files changed, 20 insertions(+), 18 deletions(-) create mode 100644 resources/images/service_edit.png create mode 100644 resources/images/service_notes.png diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 22fbb7ecc..b39ac2dcf 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -170,7 +170,7 @@ class ServiceManager(QtGui.QWidget): self.ServiceManagerList.setContextMenuPolicy( QtCore.Qt.ActionsContextMenu) self.editAction = contextMenuAction( - self.ServiceManagerList, ':/system/system_live.png', + self.ServiceManagerList, ':/services/service_edit.png', self.trUtf8('&Edit Item'), self.remoteEdit) self.ServiceManagerList.addAction(self.editAction) self.ServiceManagerList.addAction(contextMenuSeparator( diff --git a/resources/images/openlp-2.qrc b/resources/images/openlp-2.qrc index 4bea9e51b..b6509b528 100644 --- a/resources/images/openlp-2.qrc +++ b/resources/images/openlp-2.qrc @@ -1,5 +1,5 @@ <RCC> - <qresource prefix="songs" > + <qresource prefix="songs"> <file>topic_edit.png</file> <file>author_add.png</file> <file>author_delete.png</file> @@ -21,7 +21,7 @@ <file>song_topic_edit.png</file> <file>song_book_edit.png</file> </qresource> - <qresource prefix="slides" > + <qresource prefix="slides"> <file>slide_close.png</file> <file>slide_first.png</file> <file>slide_last.png</file> @@ -31,7 +31,7 @@ <file>media_playback_stop.png</file> <file>media_playback_pause.png</file> </qresource> - <qresource prefix="icon" > + <qresource prefix="icon"> <file>openlp-logo-16x16.png</file> <file>openlp-logo-32x32.png</file> <file>openlp-logo-48x48.png</file> @@ -39,43 +39,45 @@ <file>openlp-logo-128x128.png</file> <file>openlp-logo-256x256.png</file> </qresource> - <qresource prefix="graphics" > + <qresource prefix="graphics"> <file>openlp-about-logo.png</file> <file>openlp-splash-screen.png</file> </qresource> - <qresource prefix="imports" > + <qresource prefix="imports"> <file>import_selectall.png</file> <file>import_move_to_list.png</file> <file>import_remove.png</file> <file>import_load.png</file> </qresource> - <qresource prefix="exports" > + <qresource prefix="exports"> <file>export_selectall.png</file> <file>export_remove.png</file> <file>export_load.png</file> <file>export_move_to_list.png</file> </qresource> - <qresource prefix="custom" > + <qresource prefix="custom"> <file>custom_new.png</file> <file>custom_edit.png</file> <file>custom_delete.png</file> </qresource> - <qresource prefix="wizards" > + <qresource prefix="wizards"> <file>wizard_importbible.bmp</file> </qresource> - <qresource prefix="presentations" > + <qresource prefix="presentations"> <file>presentation_delete.png</file> <file>presentation_load.png</file> </qresource> - <qresource prefix="videos" > + <qresource prefix="videos"> <file>video_delete.png</file> <file>video_load.png</file> </qresource> - <qresource prefix="images" > + <qresource prefix="images"> <file>image_delete.png</file> <file>image_load.png</file> </qresource> - <qresource prefix="services" > + <qresource prefix="services"> + <file>service_edit.png</file> + <file>service_notes.png</file> <file>service_bottom.png</file> <file>service_down.png</file> <file>service_top.png</file> @@ -85,7 +87,7 @@ <file>service_open.png</file> <file>service_save.png</file> </qresource> - <qresource prefix="system" > + <qresource prefix="system"> <file>system_close.png</file> <file>system_about.png</file> <file>system_help_contents.png</file> @@ -99,7 +101,7 @@ <file>system_exit.png</file> <file>system_settings.png</file> </qresource> - <qresource prefix="media" > + <qresource prefix="media"> <file>media_custom.png</file> <file>media_presentation.png</file> <file>media_image.png</file> @@ -110,16 +112,16 @@ <file>media_stop.png</file> <file>image_clapperboard.png</file> </qresource> - <qresource prefix="messagebox" > + <qresource prefix="messagebox"> <file>messagebox_critical.png</file> <file>messagebox_info.png</file> <file>messagebox_warning.png</file> </qresource> - <qresource prefix="tools" > + <qresource prefix="tools"> <file>tools_add.png</file> <file>tools_alert.png</file> </qresource> - <qresource prefix="themes" > + <qresource prefix="themes"> <file>theme_delete.png</file> <file>theme_new.png</file> <file>theme_edit.png</file> diff --git a/resources/images/service_edit.png b/resources/images/service_edit.png new file mode 100644 index 0000000000000000000000000000000000000000..84e345d22430175e80c4ea1f1f17e6790649ee35 GIT binary patch literal 726 zcmV;{0xA88P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00001b5ch_0Itp) z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L00!&;00!&<9(6c100007bV*G`2iOY{ z7a2C-AH01400LG?L_t(|+KrK2NK<hbho5t1QsFX{FtXJW!Z4^Xy)iGk(TkA4piiP8 zvP`Rsj9@_$7(s%OV1XY+7t;4d7h$BKVJM-fky%RW6kT(+OwZ1Ay0iT|9N1V}P!GJk z@40xN-^>3<V6Yb0&VNv_TV8p4V?mwfnj0e_;Y3J~FNA2}9|Zi1czTO?dEZV(dw>O( zt1}KT&;|T(yOCwtC`po#_0k7ph9JvdOL^V;h3l;{2P{)smPaJXL|`(R!U9vMn@q;+ zw^z~I)5VRZcy?}fbNa00YRQt%#X%S&(MZ=zpTpfZ^$5qeH`waqS#O@<R_SUg>X&Ms zw`tRn7yOwXN}hd>GX8`M)jn289%?QISxBuwU-kf#VSyA%jF1G>A++hoT)FFEUQwwo zQP5J$P~{x}4q!3nf{-NOa}u0>i0#f8pNHMtJT-@T(?Mned04GhfF)xm>J5w%nu5@m zs~QJ+*X_i1bdD|CavAj%l9rZ+#bN;<MqvCd;V0mz<H56jo;FWVQs^N!C!PLZM@UXi zAu}@*0b$~z1@n__v^BR=+cZg0-Xezz5_vJWpDE`6<~2$5_4TpJoCu)(RiJt$yg@5_ zca2egSmu@eIKv~~sjRHjFGxmJRW6m6zXiHt1V3jS%sM}izRpQTQ8wf8`^edojX7n= zSoV6o*sj_J8XFo;0zM+h7drQ9Ys9t7$An{~Q~3SMA{Y$n-7h*iI)tJqqU?OxCtxcf zq6HK7Q4!6u77*PZ2So9i;uc^7QAl8r0%V21W6^+4V2LPx1DV4L^aGFf_5c6?07*qo IM6N<$f-WFG`~Uy| literal 0 HcmV?d00001 diff --git a/resources/images/service_notes.png b/resources/images/service_notes.png new file mode 100644 index 0000000000000000000000000000000000000000..d79aa515177ef7252c144f87900e3c77b9449d10 GIT binary patch literal 668 zcmV;N0%QG&P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004b3#c}2nYxW zd<bNS00009a7bBm0005I0005I0XppC;s5{u8FWQhbW?9;ba!ELWdL_~cP?peYja~^ zaAhuUa%Y?FJQ@H10vSm}K~#9!g^)>T6hRb*|LUG4ipy2(4j2_h1WCNe;6X2X7X%do zV(?;M)I{Slc*r3}BN&7X2BHMqz+m(uxI|I#Af7zvXpe!w2qw53oWa4dt9#~IujopX zG2m<d{=2IB|K6hn5rInx(W=Z*(o6imI5?XyE7x_`eVU$r?*H*cgkUK2!#nyr_CzvC zOIliX%mZxO7J&@xU)3rsODNfqTUw$SkVp`(48V-dKP#BU0Co;)S&<t6e{;ovXj4rR z0U~k0OkflPgtY)*2_4oD6=L~P;`-PqI`{7}`|OKPz}>j3(Zjm~2>bxsHxg#NWw7H( z^m+3gm;()ZGC7Eq{$uow{KT<N2VF<<CS<zn;+?o6z|?~wEUitUXZSnvr&c2nF525W zOan6oEqaMh?+;<Qe1YC8-!OJ=4ZN=o4xhMXy3RWh;UB=Gi3?Z}zQ*y(Uod*s!N)l@ zdhczd)9Ge_sTi=e?MGMo_cL@~oWXd18$QgrIMj0ksZ<K4N~IE{l&IJ13~d0AdX(;c zt;6^3jQE>3t)-Xa+bDm2FIB5m3W9*bFeDYAMx#N6LP2}2R-;_*%uN6`gR>nQ@P7BM zo#DH;uP8rwfGU*=sZI+3TnylF-|3SRJiWPxRYHi_E$dUCuU<OX+}U*3@;pywk7lm{ zNOG7Z_?5|IoMN$<E0@cY074}Z3G7DE*&CISi0Uu<T=>24!A&6m0000<MNUMnLSTXi CT_^+q literal 0 HcmV?d00001 diff --git a/resources/images/song_edit.png b/resources/images/song_edit.png index d79aa515177ef7252c144f87900e3c77b9449d10..84e345d22430175e80c4ea1f1f17e6790649ee35 100644 GIT binary patch delta 702 zcmV;v0zv(p1=a<SB!2;OQb$4nuFf3k00006VoOIv0RI600RN!9r;`8x010qNS#tmY z2J8R;2J8VIbvQKu000McNliru*b5OC88+Y_ynO%w0#->xK~#9!jgehQQ*juFpL1qX z;WCvlvegp8FsLxSF)zB&i;%#ePof~QOsk8GU_lZXL4uKBfqx%G7t;4d7h$BKVJM-f zky%RW6kT(+OwZ1Ay0iT|9N1V}P!GJk@40xN-^>3<V6Yb0&VNv_TV8p4V?mwfnj0e_ z;Y3J~FNA2}9|Zi1czTO?dEZV(dw>O(t1}KT&;|T(yOCwtC`po#_0k7ph9JvdOL^V; zh3l;{2P{)smVZYi$wXi>nZg27sGCg2?6+6Z+tbC3rg(O4c60iy<Z8*1&&5F)Bhg6L zOP|BtH}wd|w>Q}8<5_Q>;a2HtD(aVNp0{b!kr(`#9!j2lk23y*3)MbWNFHh~23bh0 zKwtI%lVO1rN{o;M)FHI#$6UGVVO~+GE>X}@%TVPV0Dlf(G3J7hB;a!roPLPy&KRGE z-P}Aihk4UMW&(LwtyX{~V<+kjj1roH(3h(k2YJ`+#CCL!E!%P#^%atqmWIV*0U$<T z{4U`q;Hcxlvwog7Pf$|mAvY(T{$EE(PEH{+GZO(};-UrflWnv$w^Q3RNm1S+hYAvT zF}R;8=YIg^HA(dK^|8vF2%!B{pn4^|K`VQAjZuDB=9T?8!z16RtgO^8NJdpvE|r(R z1-fDcKW7}wIzN!U&Phg5HskU8$k~&PIc3OL_Ikb8uG$6~8yZdmJ|f5$I`?X8#I?)E zgkz*r`2EWw7!2y&FFHCpgrX>-?0nfLU@IY_1veA+Q4!6u77*PZ2So9i;uc^7QAl8r k0%V21W6^+4V2LPx1DV4L^aGFf_5c6?07*qoM6N<$f|zhWVE_OC delta 643 zcmV-}0(||}1)K$tB!2{RLP=Bz2nYy#2xN!=000SaNLh0L00FxI00FxJI_%@(0000P zbVXQnQ*UN;cVTj60C#tHE@^ISb7Ns}WiD@WXPfRk8UO$S8A(JzRCwBjkV$A1K@^7n z>YgQv%T??S7!^eXNxaD5K`(k21Qh~e@M2)pMB_4e$RS1}7=MHe2BHMqz+m(uxI|I# zAf7zvXpe!w2qw53oWa4dt9#~IujopXG2m<d{=2IB|K6hn5rInx(W=Z*(o6imI5?Xy zE7x_`eVU$r?*H*cgkUK2!#nyr_CzvCOIliX%mZxO7J&@xU)3rsODNfqTUw$SkVp`( z48V-dKP#BU0DpE4YFUvR0Dp7EfM`=q5&<G{z)WBi1BA5zU<n=85EWwiQsVm9C_4A= zG5hR`Pr%){tI@-|0|@*8+cy$syk)TCN%VR19hd_RdNMhPmHuP&jr_#1P6u5_@+M@u z>*AfbBEZyxAuO#;p=bCz@~2iK5H8x=J4^#J1uc4sPk-+ZVYz&P-YefQc5V&4uMQ5M zxMjM|I}za@z@v!^SP{O)@ylN@de*_mIW>CkZKTubW`L;}u(j<+SNiuebYGmocz+u{ z%(*z!a|5YV3Z_b>5~P%<*Xs;z0FZi=?tQJp_wJ1Nn>Vecm*d+ge||4jt5phufWj~& z6`)3=L41WmL3^!Mqg?LHO#n87vmG1oe)q1O;k&o5C_i|BDwPVUP745B4B&9z>5~&Y zy}5@~LWtQd>r<bvUOL#^*>u<PJWpniX0HKAa+oFfmC0nBVzHPjm&=m?LM0Ii>_*Yq d8<mlW>M#3T_`UGKO(6gP002ovPDHLkV1i`%Cocd1 From 4abcbe86b285150e832c1f83b98fcc13597c1084 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Tue, 2 Mar 2010 19:44:55 +0000 Subject: [PATCH 120/164] ServiceItem Cleanups --- openlp/core/lib/serviceitem.py | 3 ++ openlp/core/ui/maindisplay.py | 1 + openlp/core/ui/servicemanager.py | 45 +++++++++++++++++---------- openlp/core/ui/slidecontroller.py | 4 +++ openlp/plugins/images/lib/imagetab.py | 3 +- 5 files changed, 39 insertions(+), 17 deletions(-) diff --git a/openlp/core/lib/serviceitem.py b/openlp/core/lib/serviceitem.py index 52deafc06..00d043870 100644 --- a/openlp/core/lib/serviceitem.py +++ b/openlp/core/lib/serviceitem.py @@ -73,6 +73,7 @@ class ServiceItem(object): self._display_frames = [] self._uuid = unicode(uuid.uuid1()) self.autoPreviewAllowed = False + self.notes = u'' def addIcon(self, icon): """ @@ -202,6 +203,7 @@ class ServiceItem(object): u'footer':self.raw_footer, u'type':self.service_item_type, u'audit':self.audit, + u'notes':self.notes, u'preview':self.autoPreviewAllowed } service_data = [] @@ -237,6 +239,7 @@ class ServiceItem(object): self.raw_footer = header[u'footer'] self.audit = header[u'audit'] self.autoPreviewAllowed = header[u'preview'] + self.notes = header[u'notes'] if self.service_item_type == ServiceItemType.Text: for slide in serviceitem[u'serviceitem'][u'data']: self._raw_frames.append(slide) diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index 87935cf5e..7082bad8d 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -186,6 +186,7 @@ class MainDisplay(DisplayWidget): Receiver.send_message(u'screen_changed') def resetDisplay(self): + Receiver.send_message(u'stop_display_loop') if self.primary: self.setVisible(False) else: diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index f13cca760..eda29d187 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -41,24 +41,31 @@ class ServiceManagerList(QtGui.QTreeWidget): QtGui.QTreeWidget.__init__(self,parent) self.parent = parent -# def mousePressEvent(self, event): -# if event.button() == QtCore.Qt.RightButton: -# item = self.itemAt(event.pos()) -# parentitem = item.parent() -# if parentitem is None: -# pos = item.data(0, QtCore.Qt.UserRole).toInt()[0] -# else: -# pos = parentitem.data(0, QtCore.Qt.UserRole).toInt()[0] -# serviceItem = self.parent.serviceItems[pos - 1] -# if serviceItem[u'data'].edit_enabled: -# self.parent.editAction.setVisible(True) -# else: -# self.parent.editAction.setVisible(False) -# event.accept() -# else: -# event.ignore() + def mouseDoubleClickEvent(self, event): + self.parent.makeLive() + event.ignore() + + def mousePressEvent(self, event): + if event.button() == QtCore.Qt.RightButton: + item = self.itemAt(event.pos()) + parentitem = item.parent() + self.parent.noteAction.setVisible(False) + if parentitem is None: + pos = item.data(0, QtCore.Qt.UserRole).toInt()[0] + self.parent.noteAction.setVisible(True) + else: + pos = parentitem.data(0, QtCore.Qt.UserRole).toInt()[0] + serviceItem = self.parent.serviceItems[pos - 1] + if serviceItem[u'service_item'].edit_enabled: + self.parent.editAction.setVisible(True) + else: + self.parent.editAction.setVisible(False) + event.accept() + else: + event.ignore() def keyPressEvent(self, event): + print event.isAutoRepeat() if type(event) == QtGui.QKeyEvent: #here accept the event and do something if event.key() == QtCore.Qt.Key_Enter: @@ -93,6 +100,7 @@ class ServiceManagerList(QtGui.QTreeWidget): just tell it what plugin to call """ if event.buttons() != QtCore.Qt.LeftButton: + event.ignore() return drag = QtGui.QDrag(self) mimeData = QtCore.QMimeData() @@ -171,7 +179,11 @@ class ServiceManager(QtGui.QWidget): self.editAction = contextMenuAction( self.ServiceManagerList, ':/system/system_live.png', self.trUtf8('&Edit Item'), self.remoteEdit) + self.noteAction = contextMenuAction( + self.ServiceManagerList, ':/system/system_live.png', + self.trUtf8('&Notes'), self.remoteEdit) self.ServiceManagerList.addAction(self.editAction) + self.ServiceManagerList.addAction(self.noteAction) self.ServiceManagerList.addAction(contextMenuSeparator( self.ServiceManagerList)) self.ServiceManagerList.addAction(contextMenuAction( @@ -625,6 +637,7 @@ class ServiceManager(QtGui.QWidget): """ Send the current item to the Live slide controller """ + print "ml" item, count = self.findServiceItem() self.parent.LiveController.addServiceManagerItem( self.serviceItems[item][u'service_item'], count) diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 81ff030ae..46280ed2b 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -193,6 +193,8 @@ class SlideController(QtGui.QWidget): u'Stop Loop', u':/media/media_stop.png', self.trUtf8('Stop continuous loop'), self.onStopLoop) self.DelaySpinBox = QtGui.QSpinBox() + self.DelaySpinBox.setMinimum(1) + self.DelaySpinBox.setMaximum(180) self.Toolbar.addToolbarWidget( u'Image SpinBox', self.DelaySpinBox) self.DelaySpinBox.setSuffix(self.trUtf8('s')) @@ -279,6 +281,8 @@ class SlideController(QtGui.QWidget): else: self.Toolbar.makeWidgetsInvisible(self.song_edit_list) self.Mediabar.setVisible(False) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'stop_display_loop'), self.onStopLoop) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'%s_first' % prefix), self.onSlideSelectedFirst) QtCore.QObject.connect(Receiver.get_receiver(), diff --git a/openlp/plugins/images/lib/imagetab.py b/openlp/plugins/images/lib/imagetab.py index 2dbea8181..b70006bdb 100644 --- a/openlp/plugins/images/lib/imagetab.py +++ b/openlp/plugins/images/lib/imagetab.py @@ -49,6 +49,7 @@ class ImageTab(SettingsTab): self.TimeoutLabel.setObjectName(u'TimeoutLabel') self.TimeoutLayout.addWidget(self.TimeoutLabel) self.TimeoutSpinBox = QtGui.QSpinBox(self.ImageSettingsGroupBox) + self.TimeoutSpinBox.setMinimum(1) self.TimeoutSpinBox.setMaximum(180) self.TimeoutSpinBox.setObjectName(u'TimeoutSpinBox') self.TimeoutLayout.addWidget(self.TimeoutSpinBox) @@ -78,4 +79,4 @@ class ImageTab(SettingsTab): Receiver.send_message(u'update_spin_delay', self.loop_delay) def postSetUp(self): - Receiver.send_message(u'update_spin_delay', self.loop_delay) \ No newline at end of file + Receiver.send_message(u'update_spin_delay', self.loop_delay) From bfd3f28c6ed71bedeb3ec5987c1342fc0f3340d2 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Tue, 2 Mar 2010 22:40:01 +0200 Subject: [PATCH 121/164] Create the config directory if it doesn't exist yet, in order to save the log file. --- openlp.pyw | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openlp.pyw b/openlp.pyw index 5c18486b6..3910156ce 100755 --- a/openlp.pyw +++ b/openlp.pyw @@ -158,7 +158,10 @@ def main(): parser.add_option("-s", "--style", dest="style", help="Set the Qt4 style (passed directly to Qt4).") # Set up logging - filename = os.path.join(get_config_directory(), u'openlp.log') + log_path = get_config_directory() + if not os.path.exists(log_path): + os.makedirs(log_path) + filename = os.path.join(log_path, u'openlp.log') logfile = FileHandler(filename, u'w') logfile.setFormatter(logging.Formatter( u'%(asctime)s %(name)-15s %(levelname)-8s %(message)s')) From 743ae230a87423effdfd11d5a6ed3e727364df60 Mon Sep 17 00:00:00 2001 From: Jonathan Corwin <j@corwin.co.uk> Date: Tue, 2 Mar 2010 21:01:23 +0000 Subject: [PATCH 122/164] Split live/preview presentation documents --- .../presentations/lib/impresscontroller.py | 232 ++++++++++-------- openlp/plugins/presentations/lib/mediaitem.py | 15 +- .../presentations/lib/messagelistener.py | 57 +++-- .../presentations/lib/powerpointcontroller.py | 37 ++- .../presentations/lib/pptviewcontroller.py | 41 ++-- .../lib/presentationcontroller.py | 176 +++++++------ 6 files changed, 320 insertions(+), 238 deletions(-) diff --git a/openlp/plugins/presentations/lib/impresscontroller.py b/openlp/plugins/presentations/lib/impresscontroller.py index 31b3ac5f6..507ebb3ba 100644 --- a/openlp/plugins/presentations/lib/impresscontroller.py +++ b/openlp/plugins/presentations/lib/impresscontroller.py @@ -44,7 +44,7 @@ else: from PyQt4 import QtCore -from presentationcontroller import PresentationController +from presentationcontroller import PresentationController, PresentationDocument class ImpressController(PresentationController): """ @@ -62,11 +62,8 @@ class ImpressController(PresentationController): """ log.debug(u'Initialising') PresentationController.__init__(self, plugin, u'Impress') - self.supports= [u'.odp', u'.ppt', u'.pps', u'.pptx', u'.ppsx'] + self.supports = [u'.odp', u'.ppt', u'.pps', u'.pptx', u'.ppsx'] self.process = None - self.document = None - self.presentation = None - self.controller = None self.desktop = None def check_available(self): @@ -99,97 +96,6 @@ class ImpressController(PresentationController): self.process.startDetached(cmd) self.process.waitForStarted() - def kill(self): - """ - Called at system exit to clean up any running presentations - """ - log.debug(u'Kill OpenOffice') - self.close_presentation() - if os.name != u'nt': - desktop = self.get_uno_desktop() - try: - desktop.terminate() - except: - pass - - def load_presentation(self, presentation): - """ - Called when a presentation is added to the SlideController. - It builds the environment, starts communcations with the background - OpenOffice task started earlier. If OpenOffice is not present is is - started. Once the environment is available the presentation is loaded - and started. - - ``presentation`` - The file name of the presentatios to the run. - """ - log.debug(u'Load Presentation OpenOffice') - self.store_filename(presentation) - #print "s.dsk1 ", self.desktop - if os.name == u'nt': - desktop = self.get_com_desktop() - if desktop is None: - self.start_process() - desktop = self.get_com_desktop() - url = u'file:///' + presentation.replace(u'\\', u'/').replace(u':', u'|').replace(u' ', u'%20') - else: - desktop = self.get_uno_desktop() - url = uno.systemPathToFileUrl(presentation) - if desktop is None: - return - self.desktop = desktop - #print "s.dsk2 ", self.desktop - properties = [] - properties.append(self.create_property(u'Minimized', True)) - properties = tuple(properties) - try: - self.document = desktop.loadComponentFromURL(url, u'_blank', - 0, properties) - except: - log.exception(u'Failed to load presentation') - return - self.presentation = self.document.getPresentation() - self.presentation.Display = self.plugin.render_manager.screens.current_display + 1 - self.controller = None - self.create_thumbnails() - - def create_thumbnails(self): - """ - Create thumbnail images for presentation - """ - log.debug(u'create thumbnails OpenOffice') - if self.check_thumbnails(): - return - if os.name == u'nt': - thumbdir = u'file:///' + self.thumbnailpath.replace( - u'\\', u'/').replace(u':', u'|').replace(u' ', u'%20') - else: - thumbdir = uno.systemPathToFileUrl(self.thumbnailpath) - props = [] - props.append(self.create_property(u'FilterName', u'impress_png_Export')) - props = tuple(props) - doc = self.document - pages = doc.getDrawPages() - for idx in range(pages.getCount()): - page = pages.getByIndex(idx) - doc.getCurrentController().setCurrentPage(page) - path = u'%s/%s%s.png'% (thumbdir, self.thumbnailprefix, - unicode(idx + 1)) - try: - doc.storeToURL(path , props) - except: - log.exception(u'%s\nUnable to store preview' % path) - - def create_property(self, name, value): - log.debug(u'create property OpenOffice') - if os.name == u'nt': - prop = self.manager.Bridge_GetStruct(u'com.sun.star.beans.PropertyValue') - else: - prop = PropertyValue() - prop.Name = name - prop.Value = value - return prop - def get_uno_desktop(self): log.debug(u'get UNO Desktop Openoffice') ctx = None @@ -230,6 +136,113 @@ class ImpressController(PresentationController): log.exception(u'Failed to get COM service manager') return None + def kill(self): + """ + Called at system exit to clean up any running presentations + """ + log.debug(u'Kill OpenOffice') + for doc in self.docs: + doc.close_presentation() + if os.name != u'nt': + desktop = self.get_uno_desktop() + try: + desktop.terminate() + except: + pass + + def add_doc(self, name): + log.debug(u'Add Doc OpenOffice') + doc = ImpressDocument(self, name) + self.docs.append(doc) + return doc + +class ImpressDocument(PresentationDocument): + + def __init__(self, controller, presentation): + log.debug(u'Init Presentation OpenOffice') + self.controller = controller + self.document = None + self.presentation = None + self.control = None + self.store_filename(presentation) + + def load_presentation(self): + """ + Called when a presentation is added to the SlideController. + It builds the environment, starts communcations with the background + OpenOffice task started earlier. If OpenOffice is not present is is + started. Once the environment is available the presentation is loaded + and started. + + ``presentation`` + The file name of the presentatios to the run. + """ + log.debug(u'Load Presentation OpenOffice') + #print "s.dsk1 ", self.desktop + if os.name == u'nt': + desktop = self.get_com_desktop() + if desktop is None: + self.controller.start_process() + desktop = self.controller.get_com_desktop() + url = u'file:///' + self.filepath.replace(u'\\', u'/').replace(u':', u'|').replace(u' ', u'%20') + else: + desktop = self.controller.get_uno_desktop() + url = uno.systemPathToFileUrl(self.filepath) + if desktop is None: + return + self.desktop = desktop + #print "s.dsk2 ", self.desktop + properties = [] + properties.append(self.create_property(u'Minimized', True)) + properties = tuple(properties) + try: + self.document = desktop.loadComponentFromURL(url, u'_blank', + 0, properties) + except: + log.exception(u'Failed to load presentation') + return + self.presentation = self.document.getPresentation() + self.presentation.Display = self.controller.plugin.render_manager.screens.current_display + 1 + self.control = None + self.create_thumbnails() + + def create_thumbnails(self): + """ + Create thumbnail images for presentation + """ + log.debug(u'create thumbnails OpenOffice') + if self.check_thumbnails(): + return + if os.name == u'nt': + thumbdir = u'file:///' + self.thumbnailpath.replace( + u'\\', u'/').replace(u':', u'|').replace(u' ', u'%20') + else: + thumbdir = uno.systemPathToFileUrl(self.thumbnailpath) + props = [] + props.append(self.create_property(u'FilterName', u'impress_png_Export')) + props = tuple(props) + doc = self.document + pages = doc.getDrawPages() + for idx in range(pages.getCount()): + page = pages.getByIndex(idx) + doc.getCurrentController().setCurrentPage(page) + path = u'%s/%s%s.png'% (thumbdir, self.thumbnailprefix, + unicode(idx + 1)) + try: + doc.storeToURL(path , props) + except: + log.exception(u'%s\nUnable to store preview' % path) + + def create_property(self, name, value): + log.debug(u'create property OpenOffice') + if os.name == u'nt': + prop = self.manager.Bridge_GetStruct(u'com.sun.star.beans.PropertyValue') + else: + prop = PropertyValue() + prop.Name = name + prop.Value = value + return prop + def close_presentation(self): """ Close presentation and clean up objects @@ -247,6 +260,7 @@ class ImpressController(PresentationController): #We tried! pass self.document = None + self.controller.remove_doc(self) def is_loaded(self): log.debug(u'is loaded OpenOffice') @@ -268,57 +282,57 @@ class ImpressController(PresentationController): if not self.is_loaded(): #print "False " return False - #print "self.con ", self.controller - if self.controller is None: + #print "self.con ", self.control + if self.control is None: return False return True def unblank_screen(self): log.debug(u'unblank screen OpenOffice') - return self.controller.resume() + return self.control.resume() def blank_screen(self): log.debug(u'blank screen OpenOffice') - self.controller.blankScreen(0) + self.control.blankScreen(0) def stop_presentation(self): log.debug(u'stop presentation OpenOffice') - self.controller.deactivate() + self.control.deactivate() def start_presentation(self): log.debug(u'start presentation OpenOffice') - if self.controller is None or not self.controller.isRunning(): + if self.control is None or not self.control.isRunning(): self.presentation.start() # start() returns before the getCurrentComponent is ready. Try for 5 seconds i = 1 while self.desktop.getCurrentComponent() is None and i < 50: time.sleep(0.1) i = i + 1 - self.controller = self.desktop.getCurrentComponent().Presentation.getController() + self.control = self.desktop.getCurrentComponent().Presentation.getController() else: - self.controller.activate() + self.control.activate() self.goto_slide(1) def get_slide_number(self): - return self.controller.getCurrentSlideIndex() + 1 + return self.control.getCurrentSlideIndex() + 1 def get_slide_count(self): return self.document.getDrawPages().getCount() def goto_slide(self, slideno): - self.controller.gotoSlideIndex(slideno-1) + self.control.gotoSlideIndex(slideno-1) def next_step(self): """ Triggers the next effect of slide on the running presentation """ - self.controller.gotoNextEffect() + self.control.gotoNextEffect() def previous_step(self): """ Triggers the previous slide on the running presentation """ - self.controller.gotoPreviousSlide() + self.control.gotoPreviousSlide() def get_slide_preview_file(self, slide_no): """ @@ -328,7 +342,7 @@ class ImpressController(PresentationController): The slide an image is required for, starting at 1 """ path = os.path.join(self.thumbnailpath, - self.thumbnailprefix + unicode(slide_no) + u'.png') + self.controller.thumbnailprefix + unicode(slide_no) + u'.png') if os.path.isfile(path): return path else: diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index 590074ead..51f234f3d 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -135,7 +135,9 @@ class PresentationMediaItem(MediaManagerItem): self.ConfigSection, self.getFileList()) filepath = unicode((item.data(QtCore.Qt.UserRole)).toString()) for cidx in self.controllers: - self.controllers[cidx].presentation_deleted(filepath) + doc = self.controllers[cidx].add_doc(filepath) + doc.presentation_deleted() + self.controllers[cidx].remove_doc(doc) def generateSlideData(self, service_item): items = self.ListView.selectedIndexes() @@ -148,13 +150,14 @@ class PresentationMediaItem(MediaManagerItem): bitem = self.ListView.item(item.row()) filename = unicode((bitem.data(QtCore.Qt.UserRole)).toString()) (path, name) = os.path.split(filename) - controller.store_filename(filename) - if controller.get_slide_preview_file(1) is None: - controller.load_presentation(filename) + doc = controller.add_doc(filename) + if doc.get_slide_preview_file(1) is None: + doc.load_presentation() i = 1 - img = controller.get_slide_preview_file(i) + img = doc.get_slide_preview_file(i) while img: service_item.add_from_command(path, name, img) i = i + 1 - img = controller.get_slide_preview_file(i) + img = doc.get_slide_preview_file(i) + controller.remove_doc(doc) return True diff --git a/openlp/plugins/presentations/lib/messagelistener.py b/openlp/plugins/presentations/lib/messagelistener.py index 3c665004b..762665ace 100644 --- a/openlp/plugins/presentations/lib/messagelistener.py +++ b/openlp/plugins/presentations/lib/messagelistener.py @@ -41,37 +41,39 @@ class Controller(object): def __init__(self, live): self.isLive = live + self.doc = None log.info(u'%s controller loaded' % live) def addHandler(self, controller, file): log.debug(u'Live = %s, addHandler %s' % (self.isLive, file)) self.controller = controller - if self.controller.is_loaded(): + if self.doc is not None: self.shutdown() - self.controller.load_presentation(file) + self.doc = self.controller.add_doc(file) + self.doc.load_presentation() if self.isLive: - self.controller.start_presentation() + self.doc.start_presentation() Receiver.send_message(u'live_slide_hide') - self.controller.slidenumber = 0 + self.doc.slidenumber = 0 def activate(self): log.debug(u'Live = %s, activate' % self.isLive) - if self.controller.is_active(): + if self.doc.is_active(): return - if not self.controller.is_loaded(): - self.controller.load_presentation(self.controller.filepath) + if not self.doc.is_loaded(): + self.doc.load_presentation() if self.isLive: - self.controller.start_presentation() - if self.controller.slidenumber > 1: - self.controller.goto_slide(self.controller.slidenumber) + self.doc.start_presentation() + if self.doc.slidenumber > 1: + self.doc.goto_slide(self.doc.slidenumber) def slide(self, slide, live): log.debug(u'Live = %s, slide' % live) if not live: return self.activate() - self.controller.goto_slide(int(slide) + 1) - self.controller.poll_slidenumber(live) + self.doc.goto_slide(int(slide) + 1) + self.doc.poll_slidenumber(live) def first(self): """ @@ -81,8 +83,8 @@ class Controller(object): if not self.isLive: return self.activate() - self.controller.start_presentation() - self.controller.poll_slidenumber(self.isLive) + self.doc.start_presentation() + self.doc.poll_slidenumber(self.isLive) def last(self): """ @@ -92,8 +94,8 @@ class Controller(object): if not self.isLive: return self.activate() - self.controller.goto_slide(self.controller.get_slide_count()) - self.controller.poll_slidenumber(self.isLive) + self.doc.goto_slide(self.controller.get_slide_count()) + self.doc.poll_slidenumber(self.isLive) def next(self): """ @@ -103,8 +105,8 @@ class Controller(object): if not self.isLive: return self.activate() - self.controller.next_step() - self.controller.poll_slidenumber(self.isLive) + self.doc.next_step() + self.doc.poll_slidenumber(self.isLive) def previous(self): """ @@ -114,35 +116,36 @@ class Controller(object): if not self.isLive: return self.activate() - self.controller.previous_step() - self.controller.poll_slidenumber(self.isLive) + self.doc.previous_step() + self.doc.poll_slidenumber(self.isLive) def shutdown(self): """ Based on the handler passed at startup triggers slide show to shut down """ log.debug(u'Live = %s, shutdown' % self.isLive) - self.controller.close_presentation() - self.controller.slidenumber = 0 + self.doc.close_presentation() + self.doc = None + #self.doc.slidenumber = 0 #self.timer.stop() def blank(self): if not self.isLive: return - if not self.controller.is_loaded(): + if not self.doc.is_loaded(): return - if not self.controller.is_active(): + if not self.doc.is_active(): return - self.controller.blank_screen() + self.doc.blank_screen() def unblank(self): if not self.isLive: return self.activate() - self.controller.unblank_screen() + self.doc.unblank_screen() def poll(self): - self.controller.poll_slidenumber(self.isLive) + self.doc.poll_slidenumber(self.isLive) class MessageListener(object): """ diff --git a/openlp/plugins/presentations/lib/powerpointcontroller.py b/openlp/plugins/presentations/lib/powerpointcontroller.py index a9775e086..2a73ed6a9 100644 --- a/openlp/plugins/presentations/lib/powerpointcontroller.py +++ b/openlp/plugins/presentations/lib/powerpointcontroller.py @@ -31,7 +31,7 @@ if os.name == u'nt': import _winreg import win32ui -from presentationcontroller import PresentationController +from presentationcontroller import PresentationController, PresentationDocument # PPT API documentation: # http://msdn.microsoft.com/en-us/library/aa269321(office.10).aspx @@ -52,9 +52,8 @@ class PowerpointController(PresentationController): """ log.debug(u'Initialising') PresentationController.__init__(self, plugin, u'Powerpoint') - self.supports= [u'.ppt', u'.pps'] + self.supports = [u'.ppt', u'.pps'] self.process = None - self.presentation = None def check_available(self): """ @@ -97,6 +96,8 @@ class PowerpointController(PresentationController): """ Called at system exit to clean up any running presentations """ + for doc in self.docs: + doc.close_presentation() if self.process is None: return try: @@ -105,7 +106,21 @@ class PowerpointController(PresentationController): pass self.process = None - def load_presentation(self, presentation): + def add_doc(self, name): + log.debug(u'Add Doc PowerPoint') + doc = PowerPointDocument(self, name) + self.docs.append(doc) + return doc + + class PowerPointDocument(PresentationDocument): + + def __init__(self, controller, presentation): + log.debug(u'Init Presentation PowerPoint') + self.presentation = None + self.controller = controller + self.store_filename(presentation) + + def load_presentation(self): """ Called when a presentation is added to the SlideController. It builds the environment, starts communcations with the background @@ -117,17 +132,16 @@ class PowerpointController(PresentationController): The file name of the presentations to run. """ log.debug(u'LoadPresentation') - self.store_filename(presentation) try: - if not self.process.Visible: - self.start_process() + if not self.controller.process.Visible: + self.controller.start_process() except: - self.start_process() + self.controller.start_process() try: - self.process.Presentations.Open(presentation, False, False, True) + self.controller.process.Presentations.Open(self.filepath, False, False, True) except: return - self.presentation = self.process.Presentations(self.process.Presentations.Count) + self.presentation = self.controller.process.Presentations(self.process.Presentations.Count) self.create_thumbnails() def create_thumbnails(self): @@ -157,6 +171,7 @@ class PowerpointController(PresentationController): except: pass self.presentation = None + self.controller.remove_doc(self) def is_active(self): """ @@ -252,7 +267,7 @@ class PowerpointController(PresentationController): The slide an image is required for, starting at 1 """ path = os.path.join(self.thumbnailpath, - self.thumbnailprefix + unicode(slide_no) + u'.png') + self.controller.thumbnailprefix + unicode(slide_no) + u'.png') if os.path.isfile(path): return path else: diff --git a/openlp/plugins/presentations/lib/pptviewcontroller.py b/openlp/plugins/presentations/lib/pptviewcontroller.py index 2ed457fc0..f7ff2bec7 100644 --- a/openlp/plugins/presentations/lib/pptviewcontroller.py +++ b/openlp/plugins/presentations/lib/pptviewcontroller.py @@ -30,7 +30,7 @@ if os.name == u'nt': from ctypes import * from ctypes.wintypes import RECT -from presentationcontroller import PresentationController +from presentationcontroller import PresentationController, PresentationDocument class PptviewController(PresentationController): """ @@ -49,8 +49,7 @@ class PptviewController(PresentationController): log.debug(u'Initialising') self.process = None PresentationController.__init__(self, plugin, u'Powerpoint Viewer') - self.supports= [u'.ppt', u'.pps'] - self.pptid = None + self.supports = [u'.ppt', u'.pps'] def check_available(self): """ @@ -90,29 +89,42 @@ class PptviewController(PresentationController): Called at system exit to clean up any running presentations """ log.debug(u'Kill') - self.close_presentation() + for doc in self.docs: + doc.close_presentation() - def load_presentation(self, presentation): + def add_doc(self, name): + log.debug(u'Add Doc PPTView') + doc = PptviewDocument(self, name) + self.docs.append(doc) + return doc + + class PptViewDocument(PresentationDocument): + + def __init__(self, controller, presentation): + log.debug(u'Init Presentation PowerPoint') + self.presentation = None + self.pptid = None + self.controller = controller + self.store_filename(presentation) + + def load_presentation(self): """ Called when a presentation is added to the SlideController. It builds the environment, starts communcations with the background - OpenOffice task started earlier. If OpenOffice is not present is is - started. Once the environment is available the presentation is loaded - and started. + PptView task started earlier. ``presentation`` The file name of the presentations to run. """ log.debug(u'LoadPresentation') - self.store_filename(presentation) - if self.pptid >= 0: - self.close_presentation() + #if self.pptid >= 0: + # self.close_presentation() rendermanager = self.plugin.render_manager rect = rendermanager.screens.current[u'size'] rect = RECT(rect.x(), rect.y(), rect.right(), rect.bottom()) - filepath = str(presentation.replace(u'/', u'\\')); + filepath = str(self.filepath.replace(u'/', u'\\')); try: - self.pptid = self.process.OpenPPT(filepath, None, rect, + self.pptid = self.controller.process.OpenPPT(filepath, None, rect, str(os.path.join(self.thumbnailpath, self.thumbnailprefix))) self.stop_presentation() except: @@ -126,6 +138,7 @@ class PptviewController(PresentationController): """ self.process.ClosePPT(self.pptid) self.pptid = -1 + self.controller.remove_doc(self) def is_loaded(self): """ @@ -205,7 +218,7 @@ class PptviewController(PresentationController): The slide an image is required for, starting at 1 """ path = os.path.join(self.thumbnailpath, - self.thumbnailprefix + unicode(slide_no) + u'.bmp') + self.controller.thumbnailprefix + unicode(slide_no) + u'.bmp') if os.path.isfile(path): return path else: diff --git a/openlp/plugins/presentations/lib/presentationcontroller.py b/openlp/plugins/presentations/lib/presentationcontroller.py index db42a482d..0818c6b4e 100644 --- a/openlp/plugins/presentations/lib/presentationcontroller.py +++ b/openlp/plugins/presentations/lib/presentationcontroller.py @@ -35,12 +35,10 @@ class PresentationController(object): """ Base class for presentation controllers to inherit from Class to control interactions with presentations. - It creates the runtime environment, loads and closes the presentation as - well as triggering the correct activities based on the users input - + It creates the runtime environment To create a new controller, take a copy of this file and name it so it ends in controller.py, i.e. foobarcontroller.py - Make sure it inhetits PresentationController + Make sure it inherits PresentationController Then fill in the blanks. If possible try and make sure it loads on all platforms, using for example os.name checks, although __init__, check_available and presentation_deleted should always work. @@ -73,6 +71,88 @@ class PresentationController(object): ``presentation_deleted()`` Deletes presentation specific files, e.g. thumbnails + """ + global log + log = logging.getLogger(u'PresentationController') + log.info(u'PresentationController loaded') + + def __init__(self, plugin=None, name=u'PresentationController'): + """ + This is the constructor for the presentationcontroller object. + This provides an easy way for descendent plugins to populate common data. + This method *must* be overridden, like so:: + + class MyPresentationController(PresentationController): + def __init__(self, plugin): + PresentationController.__init(self, plugin, u'My Presenter App') + + ``plugin`` + Defaults to *None*. The presentationplugin object + + ``name`` + Name of the application, to appear in the application + """ + self.supports = [] + self.docs = [] + self.plugin = plugin + self.name = name + self.available = self.check_available() + if self.available: + self.enabled = int(plugin.config.get_config( + name, QtCore.Qt.Unchecked)) == QtCore.Qt.Checked + else: + self.enabled = False + self.thumbnailroot = os.path.join(plugin.config.get_data_path(), + name, u'thumbnails') + self.thumbnailprefix = u'slide' + if not os.path.isdir(self.thumbnailroot): + os.makedirs(self.thumbnailroot) + + def check_available(self): + """ + Presentation app is able to run on this machine + """ + return False + + + def start_process(self): + """ + Loads a running version of the presentation application in the background. + """ + pass + + def kill(self): + """ + Called at system exit to clean up any running presentations and + close the application + """ + log.debug(u'Kill') + self.close_presentation() + + def add_doc(self, name): + """ + Called when a new presentation document is opened + """ + doc = PresentationDocument(self, name) + self.docs.append(doc) + return doc + + def remove_doc(self, doc): + """ + Called to remove an open document from the collection + """ + log.debug(u'remove_doc Presentation') + self.docs.remove(doc) + + +class PresentationDocument(object): + """ + Base class for presentation documents to inherit from. + Loads and closes the presentation as well as triggering the correct + activities based on the users input + + **Hook Functions** + ``load_presentation(presentation)`` Load a presentation file @@ -116,71 +196,12 @@ class PresentationController(object): Returns a path to an image containing a preview for the requested slide """ - global log - log = logging.getLogger(u'PresentationController') - log.info(u'PresentationController loaded') - - def __init__(self, plugin=None, name=u'PresentationController'): - """ - This is the constructor for the presentationcontroller object. - This provides an easy way for descendent plugins to populate common data. - This method *must* be overridden, like so:: - - class MyPresentationController(PresentationController): - def __init__(self, plugin): - PresentationController.__init(self, plugin, u'My Presenter App') - - ``plugin`` - Defaults to *None*. The presentationplugin object - - ``name`` - Name of the application, to appear in the application - """ - self.supports = [] - self.plugin = plugin - self.name = name - self.available = self.check_available() + def __init__(self, controller, name): self.slidenumber = 0 - if self.available: - self.enabled = int(plugin.config.get_config( - name, QtCore.Qt.Unchecked)) == QtCore.Qt.Checked - else: - self.enabled = False - self.thumbnailroot = os.path.join(plugin.config.get_data_path(), - name, u'thumbnails') - self.thumbnailprefix = u'slide' - if not os.path.isdir(self.thumbnailroot): - os.makedirs(self.thumbnailroot) + self.controller = controller + self.store_filename(name) - def check_available(self): - """ - Presentation app is able to run on this machine - """ - return False - - def presentation_deleted(self, presentation): - """ - Cleans up/deletes any controller specific files created for - a file, e.g. thumbnails - """ - self.store_filename(presentation) - shutil.rmtree(self.thumbnailpath) - - def start_process(self): - """ - Loads a running version of the presentation application in the background. - """ - pass - - def kill(self): - """ - Called at system exit to clean up any running presentations and - close the application - """ - log.debug(u'Kill') - self.close_presentation() - - def load_presentation(self, presentation): + def load_presentation(self): """ Called when a presentation is added to the SlideController. Loads the presentation and starts it @@ -191,16 +212,29 @@ class PresentationController(object): """ pass + def presentation_deleted(self): + """ + Cleans up/deletes any controller specific files created for + a file, e.g. thumbnails + """ + shutil.rmtree(self.thumbnailpath) + def store_filename(self, presentation): """ Set properties for the filename and thumbnail paths """ self.filepath = presentation - self.filename = os.path.split(presentation)[1] - self.thumbnailpath = os.path.join(self.thumbnailroot, self.filename) + self.filename = self.get_file_name(presentation) + self.thumbnailpath = self.get_thumbnail_path(presentation) if not os.path.isdir(self.thumbnailpath): os.mkdir(self.thumbnailpath) + def get_file_name(self, presentation): + return os.path.split(presentation)[1] + + def get_thumbnail_path(self, presentation): + return os.path.join(self.controller.thumbnailroot, self.get_file_name(presentation)) + def check_thumbnails(self): """ Returns true if the thumbnail images look to exist and are more @@ -218,10 +252,10 @@ class PresentationController(object): Close presentation and clean up objects Triggered by new object being added to SlideController """ - pass + self.controller.delete_doc(self) def is_active(self): - """ + """ Returns True if a presentation is currently running """ return False From aa5f21b50d7d26c3bcd5b669e662a8900ed1195f Mon Sep 17 00:00:00 2001 From: Jonathan Corwin <j@corwin.co.uk> Date: Tue, 2 Mar 2010 23:15:13 +0000 Subject: [PATCH 123/164] Live/Preview presentation changes for Windows --- .../presentations/lib/impresscontroller.py | 6 +- .../presentations/lib/messagelistener.py | 2 +- .../presentations/lib/powerpointcontroller.py | 248 +++++++++--------- .../presentations/lib/pptviewcontroller.py | 216 +++++++-------- 4 files changed, 237 insertions(+), 235 deletions(-) diff --git a/openlp/plugins/presentations/lib/impresscontroller.py b/openlp/plugins/presentations/lib/impresscontroller.py index 507ebb3ba..75108d850 100644 --- a/openlp/plugins/presentations/lib/impresscontroller.py +++ b/openlp/plugins/presentations/lib/impresscontroller.py @@ -180,7 +180,7 @@ class ImpressDocument(PresentationDocument): log.debug(u'Load Presentation OpenOffice') #print "s.dsk1 ", self.desktop if os.name == u'nt': - desktop = self.get_com_desktop() + desktop = self.controller.get_com_desktop() if desktop is None: self.controller.start_process() desktop = self.controller.get_com_desktop() @@ -226,7 +226,7 @@ class ImpressDocument(PresentationDocument): for idx in range(pages.getCount()): page = pages.getByIndex(idx) doc.getCurrentController().setCurrentPage(page) - path = u'%s/%s%s.png'% (thumbdir, self.thumbnailprefix, + path = u'%s/%s%s.png'% (thumbdir, self.controller.thumbnailprefix, unicode(idx + 1)) try: doc.storeToURL(path , props) @@ -236,7 +236,7 @@ class ImpressDocument(PresentationDocument): def create_property(self, name, value): log.debug(u'create property OpenOffice') if os.name == u'nt': - prop = self.manager.Bridge_GetStruct(u'com.sun.star.beans.PropertyValue') + prop = self.controller.manager.Bridge_GetStruct(u'com.sun.star.beans.PropertyValue') else: prop = PropertyValue() prop.Name = name diff --git a/openlp/plugins/presentations/lib/messagelistener.py b/openlp/plugins/presentations/lib/messagelistener.py index 762665ace..9065bf796 100644 --- a/openlp/plugins/presentations/lib/messagelistener.py +++ b/openlp/plugins/presentations/lib/messagelistener.py @@ -94,7 +94,7 @@ class Controller(object): if not self.isLive: return self.activate() - self.doc.goto_slide(self.controller.get_slide_count()) + self.doc.goto_slide(self.doc.get_slide_count()) self.doc.poll_slidenumber(self.isLive) def next(self): diff --git a/openlp/plugins/presentations/lib/powerpointcontroller.py b/openlp/plugins/presentations/lib/powerpointcontroller.py index 2a73ed6a9..64c435ebf 100644 --- a/openlp/plugins/presentations/lib/powerpointcontroller.py +++ b/openlp/plugins/presentations/lib/powerpointcontroller.py @@ -108,106 +108,108 @@ class PowerpointController(PresentationController): def add_doc(self, name): log.debug(u'Add Doc PowerPoint') - doc = PowerPointDocument(self, name) + doc = PowerpointDocument(self, name) self.docs.append(doc) return doc - class PowerPointDocument(PresentationDocument): +class PowerpointDocument(PresentationDocument): - def __init__(self, controller, presentation): - log.debug(u'Init Presentation PowerPoint') - self.presentation = None - self.controller = controller - self.store_filename(presentation) + def __init__(self, controller, presentation): + log.debug(u'Init Presentation Powerpoint') + self.presentation = None + self.controller = controller + self.store_filename(presentation) - def load_presentation(self): - """ - Called when a presentation is added to the SlideController. - It builds the environment, starts communcations with the background - OpenOffice task started earlier. If OpenOffice is not present is is - started. Once the environment is available the presentation is loaded - and started. + def load_presentation(self): + """ + Called when a presentation is added to the SlideController. + It builds the environment, starts communcations with the background + OpenOffice task started earlier. If OpenOffice is not present is is + started. Once the environment is available the presentation is loaded + and started. - ``presentation`` - The file name of the presentations to run. - """ - log.debug(u'LoadPresentation') - try: - if not self.controller.process.Visible: - self.controller.start_process() - except: - self.controller.start_process() - try: - self.controller.process.Presentations.Open(self.filepath, False, False, True) - except: - return - self.presentation = self.controller.process.Presentations(self.process.Presentations.Count) - self.create_thumbnails() + ``presentation`` + The file name of the presentations to run. + """ + log.debug(u'LoadPresentation') + #try: + if not self.controller.process.Visible: + self.controller.start_process() + #except: + # self.controller.start_process() + #try: + self.controller.process.Presentations.Open(self.filepath, False, False, True) + #except: + # return + self.presentation = self.controller.process.Presentations( + self.controller.process.Presentations.Count) + self.create_thumbnails() - def create_thumbnails(self): - """ - Create the thumbnail images for the current presentation. - Note an alternative and quicker method would be do - self.presentation.Slides[n].Copy() - thumbnail = QApplication.clipboard.image() - But for now we want a physical file since it makes - life easier elsewhere - """ - if self.check_thumbnails(): - return - self.presentation.Export(os.path.join(self.thumbnailpath, '') - , 'png', 600, 480) + def create_thumbnails(self): + """ + Create the thumbnail images for the current presentation. + Note an alternative and quicker method would be do + self.presentation.Slides[n].Copy() + thumbnail = QApplication.clipboard.image() + But for now we want a physical file since it makes + life easier elsewhere + """ + if self.check_thumbnails(): + return + self.presentation.Export(os.path.join(self.thumbnailpath, '') + , 'png', 600, 480) - def close_presentation(self): - """ - Close presentation and clean up objects - Triggerent by new object being added to SlideController orOpenLP - being shut down - """ - if self.presentation == None: - return - try: - self.presentation.Close() - except: - pass - self.presentation = None - self.controller.remove_doc(self) + def close_presentation(self): + """ + Close presentation and clean up objects + Triggerent by new object being added to SlideController orOpenLP + being shut down + """ + if self.presentation == None: + return + try: + self.presentation.Close() + except: + pass + self.presentation = None + self.controller.remove_doc(self) - def is_active(self): - """ - Returns true if a presentation is currently active - """ - if not self.is_loaded(): + def is_active(self): + """ + Returns true if a presentation is currently active + """ + if not self.controller.is_loaded(): + return False + try: + if self.presentation.SlideShowWindow == None: return False - try: - if self.presentation.SlideShowWindow == None: - return False - if self.presentation.SlideShowWindow.View == None: - return False - except: + if self.presentation.SlideShowWindow.View == None: return False - return True + except: + return False + return True - def unblank_screen(self): - """ - Unblanks (restores) the presentationn - """ - self.presentation.SlideShowSettings.Run() - self.presentation.SlideShowWindow.View.State = 1 - self.presentation.SlideShowWindow.Activate() + def unblank_screen(self): + """ + Unblanks (restores) the presentationn + """ + self.presentation.SlideShowSettings.Run() + self.presentation.SlideShowWindow.View.State = 1 + self.presentation.SlideShowWindow.Activate() - def blank_screen(self): - """ - Blanks the screen - """ - self.presentation.SlideShowWindow.View.State = 3 + def blank_screen(self): + """ + Blanks the screen + """ + self.presentation.SlideShowWindow.View.State = 3 - def stop_presentation(self): - """ - Stops the current presentation and hides the output - """ - self.presentation.SlideShowWindow.View.Exit() + def stop_presentation(self): + """ + Stops the current presentation and hides the output + """ + self.presentation.SlideShowWindow.View.Exit() + if os.name == u'nt': def start_presentation(self): """ Starts a presentation from the beginning @@ -222,53 +224,53 @@ class PowerpointController(PresentationController): dpi = 96 self.presentation.SlideShowSettings.Run() self.presentation.SlideShowWindow.View.GotoSlide(1) - rendermanager = self.plugin.render_manager + rendermanager = self.controller.plugin.render_manager rect = rendermanager.screens.current[u'size'] self.presentation.SlideShowWindow.Top = rect.y() * 72 / dpi self.presentation.SlideShowWindow.Height = rect.height() * 72 / dpi self.presentation.SlideShowWindow.Left = rect.x() * 72 / dpi self.presentation.SlideShowWindow.Width = rect.width() * 72 / dpi - def get_slide_number(self): - """ - Returns the current slide number - """ - return self.presentation.SlideShowWindow.View.CurrentShowPosition + def get_slide_number(self): + """ + Returns the current slide number + """ + return self.presentation.SlideShowWindow.View.CurrentShowPosition - def get_slide_count(self): - """ - Returns total number of slides - """ - return self.presentation.Slides.Count + def get_slide_count(self): + """ + Returns total number of slides + """ + return self.presentation.Slides.Count - def goto_slide(self, slideno): - """ - Moves to a specific slide in the presentation - """ - self.presentation.SlideShowWindow.View.GotoSlide(slideno) + def goto_slide(self, slideno): + """ + Moves to a specific slide in the presentation + """ + self.presentation.SlideShowWindow.View.GotoSlide(slideno) - def next_step(self): - """ - Triggers the next effect of slide on the running presentation - """ - self.presentation.SlideShowWindow.View.Next() + def next_step(self): + """ + Triggers the next effect of slide on the running presentation + """ + self.presentation.SlideShowWindow.View.Next() - def previous_step(self): - """ - Triggers the previous slide on the running presentation - """ - self.presentation.SlideShowWindow.View.Previous() + def previous_step(self): + """ + Triggers the previous slide on the running presentation + """ + self.presentation.SlideShowWindow.View.Previous() - def get_slide_preview_file(self, slide_no): - """ - Returns an image path containing a preview for the requested slide + def get_slide_preview_file(self, slide_no): + """ + Returns an image path containing a preview for the requested slide - ``slide_no`` - The slide an image is required for, starting at 1 - """ - path = os.path.join(self.thumbnailpath, - self.controller.thumbnailprefix + unicode(slide_no) + u'.png') - if os.path.isfile(path): - return path - else: - return None + ``slide_no`` + The slide an image is required for, starting at 1 + """ + path = os.path.join(self.thumbnailpath, + self.controller.thumbnailprefix + unicode(slide_no) + u'.png') + if os.path.isfile(path): + return path + else: + return None diff --git a/openlp/plugins/presentations/lib/pptviewcontroller.py b/openlp/plugins/presentations/lib/pptviewcontroller.py index f7ff2bec7..ddfbbd204 100644 --- a/openlp/plugins/presentations/lib/pptviewcontroller.py +++ b/openlp/plugins/presentations/lib/pptviewcontroller.py @@ -98,128 +98,128 @@ class PptviewController(PresentationController): self.docs.append(doc) return doc - class PptViewDocument(PresentationDocument): +class PptviewDocument(PresentationDocument): - def __init__(self, controller, presentation): - log.debug(u'Init Presentation PowerPoint') - self.presentation = None - self.pptid = None - self.controller = controller - self.store_filename(presentation) + def __init__(self, controller, presentation): + log.debug(u'Init Presentation PowerPoint') + self.presentation = None + self.pptid = None + self.controller = controller + self.store_filename(presentation) - def load_presentation(self): - """ - Called when a presentation is added to the SlideController. - It builds the environment, starts communcations with the background - PptView task started earlier. + def load_presentation(self): + """ + Called when a presentation is added to the SlideController. + It builds the environment, starts communcations with the background + PptView task started earlier. - ``presentation`` - The file name of the presentations to run. - """ - log.debug(u'LoadPresentation') - #if self.pptid >= 0: - # self.close_presentation() - rendermanager = self.plugin.render_manager - rect = rendermanager.screens.current[u'size'] - rect = RECT(rect.x(), rect.y(), rect.right(), rect.bottom()) - filepath = str(self.filepath.replace(u'/', u'\\')); - try: - self.pptid = self.controller.process.OpenPPT(filepath, None, rect, - str(os.path.join(self.thumbnailpath, self.thumbnailprefix))) - self.stop_presentation() - except: - log.exception(u'Failed to load presentation') + ``presentation`` + The file name of the presentations to run. + """ + log.debug(u'LoadPresentation') + #if self.pptid >= 0: + # self.close_presentation() + rendermanager = self.controller.plugin.render_manager + rect = rendermanager.screens.current[u'size'] + rect = RECT(rect.x(), rect.y(), rect.right(), rect.bottom()) + filepath = str(self.filepath.replace(u'/', u'\\')); + try: + self.pptid = self.controller.process.OpenPPT(filepath, None, rect, + str(os.path.join(self.thumbnailpath, self.controller.thumbnailprefix))) + self.stop_presentation() + except: + log.exception(u'Failed to load presentation') - def close_presentation(self): - """ - Close presentation and clean up objects - Triggerent by new object being added to SlideController orOpenLP - being shut down - """ - self.process.ClosePPT(self.pptid) - self.pptid = -1 - self.controller.remove_doc(self) + def close_presentation(self): + """ + Close presentation and clean up objects + Triggerent by new object being added to SlideController orOpenLP + being shut down + """ + self.controller.process.ClosePPT(self.pptid) + self.pptid = -1 + self.controller.remove_doc(self) - def is_loaded(self): - """ - Returns true if a presentation is loaded - """ - if self.pptid < 0: - return False - if self.get_slide_count() < 0: - return False - return True + def is_loaded(self): + """ + Returns true if a presentation is loaded + """ + if self.pptid < 0: + return False + if self.get_slide_count() < 0: + return False + return True - def is_active(self): - """ - Returns true if a presentation is currently active - """ - return self.is_loaded() + def is_active(self): + """ + Returns true if a presentation is currently active + """ + return self.is_loaded() - def blank_screen(self): - """ - Blanks the screen - """ - self.process.Blank(self.pptid) + def blank_screen(self): + """ + Blanks the screen + """ + self.controller.process.Blank(self.pptid) - def unblank_screen(self): - """ - Unblanks (restores) the presentationn - """ - self.process.Unblank(self.pptid) + def unblank_screen(self): + """ + Unblanks (restores) the presentationn + """ + self.controller.process.Unblank(self.pptid) - def stop_presentation(self): - """ - Stops the current presentation and hides the output - """ - self.process.Stop(self.pptid) + def stop_presentation(self): + """ + Stops the current presentation and hides the output + """ + self.controller.process.Stop(self.pptid) - def start_presentation(self): - """ - Starts a presentation from the beginning - """ - self.process.RestartShow(self.pptid) + def start_presentation(self): + """ + Starts a presentation from the beginning + """ + self.controller.process.RestartShow(self.pptid) - def get_slide_number(self): - """ - Returns the current slide number - """ - return self.process.GetCurrentSlide(self.pptid) + def get_slide_number(self): + """ + Returns the current slide number + """ + return self.controller.process.GetCurrentSlide(self.pptid) - def get_slide_count(self): - """ - Returns total number of slides - """ - return self.process.GetSlideCount(self.pptid) + def get_slide_count(self): + """ + Returns total number of slides + """ + return self.controller.process.GetSlideCount(self.pptid) - def goto_slide(self, slideno): - """ - Moves to a specific slide in the presentation - """ - self.process.GotoSlide(self.pptid, slideno) + def goto_slide(self, slideno): + """ + Moves to a specific slide in the presentation + """ + self.controller.process.GotoSlide(self.pptid, slideno) - def next_step(self): - """ - Triggers the next effect of slide on the running presentation - """ - self.process.NextStep(self.pptid) + def next_step(self): + """ + Triggers the next effect of slide on the running presentation + """ + self.controller.process.NextStep(self.pptid) - def previous_step(self): - """ - Triggers the previous slide on the running presentation - """ - self.process.PrevStep(self.pptid) + def previous_step(self): + """ + Triggers the previous slide on the running presentation + """ + self.controller.process.PrevStep(self.pptid) - def get_slide_preview_file(self, slide_no): - """ - Returns an image path containing a preview for the requested slide + def get_slide_preview_file(self, slide_no): + """ + Returns an image path containing a preview for the requested slide - ``slide_no`` - The slide an image is required for, starting at 1 - """ - path = os.path.join(self.thumbnailpath, - self.controller.thumbnailprefix + unicode(slide_no) + u'.bmp') - if os.path.isfile(path): - return path - else: - return None + ``slide_no`` + The slide an image is required for, starting at 1 + """ + path = os.path.join(self.thumbnailpath, + self.controller.thumbnailprefix + unicode(slide_no) + u'.bmp') + if os.path.isfile(path): + return path + else: + return None From 6bff7936eb1502f48af772d724414020b16dba89 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Wed, 3 Mar 2010 17:48:37 +0000 Subject: [PATCH 124/164] Add ServiceItemNoteForm and handling --- openlp.pyw | 4 +-- openlp/core/ui/__init__.py | 3 +- openlp/core/ui/serviceitemdialog.py | 34 +++++++++++++++++++++ openlp/core/ui/serviceitemform.py | 44 ++++++++++++++++++++++++++++ openlp/core/ui/servicemanager.py | 30 ++++++++++++++----- openlp/core/ui/slidecontroller.py | 2 +- resources/forms/serviceitemdialog.ui | 41 ++++++++++++++++++++++++++ 7 files changed, 146 insertions(+), 12 deletions(-) create mode 100644 openlp/core/ui/serviceitemdialog.py create mode 100644 openlp/core/ui/serviceitemform.py create mode 100644 resources/forms/serviceitemdialog.ui diff --git a/openlp.pyw b/openlp.pyw index 31869e797..3fd7c051a 100755 --- a/openlp.pyw +++ b/openlp.pyw @@ -32,13 +32,13 @@ from logging import FileHandler from optparse import OptionParser from PyQt4 import QtCore, QtGui +log = logging.getLogger() + from openlp.core.lib import Receiver, str_to_bool from openlp.core.resources import qInitResources from openlp.core.ui import MainWindow, SplashScreen, ScreenList from openlp.core.utils import get_config_directory, ConfigHelper -log = logging.getLogger() - application_stylesheet = u""" QMainWindow::separator { diff --git a/openlp/core/ui/__init__.py b/openlp/core/ui/__init__.py index 6e37afc7f..c2f571c3b 100644 --- a/openlp/core/ui/__init__.py +++ b/openlp/core/ui/__init__.py @@ -23,6 +23,7 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### +from serviceitemform import ServiceItemNoteForm from screen import ScreenList from maindisplay import MainDisplay from amendthemeform import AmendThemeForm @@ -40,4 +41,4 @@ from mainwindow import MainWindow __all__ = ['SplashScreen', 'AboutForm', 'SettingsForm', 'MainWindow', 'MainDisplay', 'SlideController', 'ServiceManager', 'ThemeManager', - 'AmendThemeForm', 'MediaDockManager'] + 'AmendThemeForm', 'MediaDockManager', 'ServiceItemNoteForm'] diff --git a/openlp/core/ui/serviceitemdialog.py b/openlp/core/ui/serviceitemdialog.py new file mode 100644 index 000000000..150375da9 --- /dev/null +++ b/openlp/core/ui/serviceitemdialog.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'serviceitemdialog.ui' +# +# Created: Tue Mar 2 20:17:21 2010 +# by: PyQt4 UI code generator 4.7 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +class Ui_ServiceNoteEdit(object): + def setupUi(self, ServiceNoteEdit): + ServiceNoteEdit.setObjectName("ServiceNoteEdit") + ServiceNoteEdit.resize(400, 243) + self.widget = QtGui.QWidget(ServiceNoteEdit) + self.widget.setGeometry(QtCore.QRect(20, 10, 361, 223)) + self.widget.setObjectName("widget") + self.verticalLayout = QtGui.QVBoxLayout(self.widget) + self.verticalLayout.setObjectName("verticalLayout") + self.textEdit = QtGui.QTextEdit(self.widget) + self.textEdit.setObjectName("textEdit") + self.verticalLayout.addWidget(self.textEdit) + self.buttonBox = QtGui.QDialogButtonBox(self.widget) + self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Save) + self.buttonBox.setObjectName("buttonBox") + self.verticalLayout.addWidget(self.buttonBox) + + self.retranslateUi(ServiceNoteEdit) + QtCore.QMetaObject.connectSlotsByName(ServiceNoteEdit) + + def retranslateUi(self, ServiceNoteEdit): + ServiceNoteEdit.setWindowTitle(QtGui.QApplication.translate("ServiceNoteEdit", "Service Item Notes", None, QtGui.QApplication.UnicodeUTF8)) + diff --git a/openlp/core/ui/serviceitemform.py b/openlp/core/ui/serviceitemform.py new file mode 100644 index 000000000..43011ead8 --- /dev/null +++ b/openlp/core/ui/serviceitemform.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard # +# --------------------------------------------------------------------------- # +# 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 # +############################################################################### + +from PyQt4 import QtCore, QtGui +from serviceitemdialog import Ui_ServiceNoteEdit + +class ServiceItemNoteForm(QtGui.QDialog, Ui_ServiceNoteEdit): + """ + This is the form that is used to edit the verses of the song. + """ + def __init__(self, parent=None): + """ + Constructor + """ + QtGui.QDialog.__init__(self, parent) + self.setupUi(self) + QtCore.QObject.connect(self.buttonBox, + QtCore.SIGNAL(u'accepted()'), + self.accept) + QtCore.QObject.connect(self.buttonBox, + QtCore.SIGNAL(u'rejected()'), + self.reject) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index a36b44fed..e55ce7936 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -31,9 +31,11 @@ import zipfile log = logging.getLogger(__name__) from PyQt4 import QtCore, QtGui + from openlp.core.lib import PluginConfig, OpenLPToolbar, ServiceItem, \ contextMenuAction, contextMenuSeparator, contextMenu, Receiver, \ contextMenu, str_to_bool +from openlp.core.ui import ServiceItemNoteForm class ServiceManagerList(QtGui.QTreeWidget): @@ -56,6 +58,11 @@ class ServiceManagerList(QtGui.QTreeWidget): else: pos = parentitem.data(0, QtCore.Qt.UserRole).toInt()[0] serviceItem = self.parent.serviceItems[pos - 1] + self.parent.menuServiceItem = serviceItem + if serviceItem[u'service_item'].is_text(): + self.parent.themeMenu.menuAction().setVisible(True) + else: + self.parent.themeMenu.menuAction().setVisible(False) if serviceItem[u'service_item'].edit_enabled: self.parent.editAction.setVisible(True) else: @@ -128,6 +135,7 @@ class ServiceManager(QtGui.QWidget): #Indicates if remoteTriggering is active. If it is the next addServiceItem call #will replace the currently selected one. self.remoteEditTriggered = False + self.serviceItemNoteForm = ServiceItemNoteForm() #start with the layout self.Layout = QtGui.QVBoxLayout(self) self.Layout.setSpacing(0) @@ -180,8 +188,8 @@ class ServiceManager(QtGui.QWidget): self.ServiceManagerList, ':/services/service_edit.png', self.trUtf8('&Edit Item'), self.remoteEdit) self.noteAction = contextMenuAction( - self.ServiceManagerList, ':/system/system_live.png', - self.trUtf8('&Notes'), self.remoteEdit) + self.ServiceManagerList, ':/services/service_notes.png', + self.trUtf8('&Notes'), self.onServiceItemNoteForm) self.ServiceManagerList.addAction(self.editAction) self.ServiceManagerList.addAction(self.noteAction) self.ServiceManagerList.addAction(contextMenuSeparator( @@ -199,10 +207,10 @@ class ServiceManager(QtGui.QWidget): self.trUtf8('&Remove from Service'), self.onDeleteFromService)) self.ServiceManagerList.addAction(contextMenuSeparator( self.ServiceManagerList)) - self.ThemeMenu = contextMenu( + self.themeMenu = contextMenu( self.ServiceManagerList, '', self.trUtf8('&Change Item Theme')) - self.ServiceManagerList.addAction(self.ThemeMenu.menuAction()) + self.ServiceManagerList.addAction(self.themeMenu.menuAction()) self.Layout.addWidget(self.ServiceManagerList) # Add the bottom toolbar self.OrderToolbar = OpenLPToolbar(self) @@ -226,8 +234,6 @@ class ServiceManager(QtGui.QWidget): # Connect up our signals and slots QtCore.QObject.connect(self.ThemeComboBox, QtCore.SIGNAL(u'activated(int)'), self.onThemeComboBoxSelected) - QtCore.QObject.connect(self.ServiceManagerList, - QtCore.SIGNAL(u'doubleClicked(QModelIndex)'), self.makeLive) QtCore.QObject.connect(self.ServiceManagerList, QtCore.SIGNAL(u'itemCollapsed(QTreeWidgetItem*)'), self.collapsed) QtCore.QObject.connect(self.ServiceManagerList, @@ -249,6 +255,14 @@ class ServiceManager(QtGui.QWidget): def onPresentationTypes(self, presentation_types): self.presentation_types = presentation_types + def onServiceItemNoteForm(self): + item, count = self.findServiceItem() + self.serviceItemNoteForm.textEdit.setPlainText( + self.menuServiceItem[u'service_item'].notes) + if self.serviceItemNoteForm.exec_(): + self.menuServiceItem[u'service_item'].notes = \ + self.serviceItemNoteForm.textEdit.toPlainText() + def nextItem(self): """ Called by the SlideController to select the @@ -734,7 +748,7 @@ class ServiceManager(QtGui.QWidget): A list of current themes to be displayed """ self.ThemeComboBox.clear() - self.ThemeMenu.clear() + self.themeMenu.clear() self.ThemeComboBox.addItem(u'') for theme in theme_list: self.ThemeComboBox.addItem(theme) @@ -742,7 +756,7 @@ class ServiceManager(QtGui.QWidget): self.ServiceManagerList, None, theme , self.onThemeChangeAction) - self.ThemeMenu.addAction(action) + self.themeMenu.addAction(action) id = self.ThemeComboBox.findText(self.service_theme, QtCore.Qt.MatchExactly) # Not Found diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 46280ed2b..ff8507f5d 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -182,7 +182,7 @@ class SlideController(QtGui.QWidget): self.trUtf8('Move to live'), self.onGoLive) self.Toolbar.addToolbarSeparator(u'Close Separator') self.Toolbar.addToolbarButton( - u'Edit Song', u':/songs/song_edit.png', + u'Edit Song', u':/services/service_edit.png', self.trUtf8('Edit and re-preview Song'), self.onEditSong) if isLive: self.Toolbar.addToolbarSeparator(u'Loop Separator') diff --git a/resources/forms/serviceitemdialog.ui b/resources/forms/serviceitemdialog.ui new file mode 100644 index 000000000..6615b08aa --- /dev/null +++ b/resources/forms/serviceitemdialog.ui @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ServiceNoteEdit</class> + <widget class="QWidget" name="ServiceNoteEdit"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>243</height> + </rect> + </property> + <property name="windowTitle"> + <string>Service Item Notes</string> + </property> + <widget class="QWidget" name=""> + <property name="geometry"> + <rect> + <x>20</x> + <y>10</y> + <width>361</width> + <height>223</height> + </rect> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QTextEdit" name="textEdit"/> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Save</set> + </property> + </widget> + </item> + </layout> + </widget> + </widget> + <resources/> + <connections/> +</ui> From 3084140741da63615ed44108f86deb824b2fe53b Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Wed, 3 Mar 2010 17:49:28 +0000 Subject: [PATCH 125/164] Remove extra line --- openlp/core/ui/servicemanager.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index e55ce7936..e28826696 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -256,7 +256,6 @@ class ServiceManager(QtGui.QWidget): self.presentation_types = presentation_types def onServiceItemNoteForm(self): - item, count = self.findServiceItem() self.serviceItemNoteForm.textEdit.setPlainText( self.menuServiceItem[u'service_item'].notes) if self.serviceItemNoteForm.exec_(): From 132022205edc7aea4e74c5fcafa572f6be11f42c Mon Sep 17 00:00:00 2001 From: Jon Tibble <meths@btinternet.com> Date: Thu, 4 Mar 2010 18:43:53 +0000 Subject: [PATCH 126/164] None tests redux --- openlp/plugins/presentations/lib/powerpointcontroller.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openlp/plugins/presentations/lib/powerpointcontroller.py b/openlp/plugins/presentations/lib/powerpointcontroller.py index 64c435ebf..935fa9929 100644 --- a/openlp/plugins/presentations/lib/powerpointcontroller.py +++ b/openlp/plugins/presentations/lib/powerpointcontroller.py @@ -165,7 +165,7 @@ class PowerpointDocument(PresentationDocument): Triggerent by new object being added to SlideController orOpenLP being shut down """ - if self.presentation == None: + if self.presentation is None: return try: self.presentation.Close() @@ -181,9 +181,9 @@ class PowerpointDocument(PresentationDocument): if not self.controller.is_loaded(): return False try: - if self.presentation.SlideShowWindow == None: + if self.presentation.SlideShowWindow is None: return False - if self.presentation.SlideShowWindow.View == None: + if self.presentation.SlideShowWindow.View is None: return False except: return False From 9ada2f00b0a3504cc96ab4c8a4e0cd6bc08e7b40 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Thu, 4 Mar 2010 19:03:09 +0000 Subject: [PATCH 127/164] Theme Deletes only work for unused themes --- openlp/core/lib/plugin.py | 6 ++++++ openlp/core/ui/thememanager.py | 13 +++++++++++++ openlp/plugins/bibles/bibleplugin.py | 6 ++++++ openlp/plugins/custom/customplugin.py | 5 +++++ openlp/plugins/custom/lib/manager.py | 3 +++ openlp/plugins/songs/lib/manager.py | 3 +++ openlp/plugins/songs/songsplugin.py | 5 +++++ 7 files changed, 41 insertions(+) diff --git a/openlp/core/lib/plugin.py b/openlp/core/lib/plugin.py index e15369145..e98c789d0 100644 --- a/openlp/core/lib/plugin.py +++ b/openlp/core/lib/plugin.py @@ -254,3 +254,9 @@ class Plugin(QtCore.QObject): self.mediadock.insert_dock(self.media_item, self.icon, self.weight) if self.settings_tab: self.settings.insertTab(self.settings_tab, self.weight) + + def can_delete_theme(self, theme): + """ + Called to ask the plugin if a theme can be deleted + """ + return True diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index ba541aa6e..b6396c7db 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -180,6 +180,19 @@ class ThemeManager(QtGui.QWidget): self.trUtf8('You are unable to delete the default theme!'), QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok)) else: + for plugin in self.parent.plugin_manager.plugins: + if not plugin.can_delete_theme(theme): + QtGui.QMessageBox.critical( + self, self.trUtf8('Error'), + self.trUtf8('theme %s is use in %s plugin' % (theme, plugin.name)), + QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok)) + return + if unicode(self.parent.ServiceManagerContents.ThemeComboBox.currentText()) == theme: + QtGui.QMessageBox.critical( + self, self.trUtf8('Error'), + self.trUtf8('theme %s is use Service Manager' % theme), + QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok)) + return self.themelist.remove(theme) th = theme + u'.png' row = self.ThemeListWidget.row(item) diff --git a/openlp/plugins/bibles/bibleplugin.py b/openlp/plugins/bibles/bibleplugin.py index 8cf15e942..bd65b6622 100644 --- a/openlp/plugins/bibles/bibleplugin.py +++ b/openlp/plugins/bibles/bibleplugin.py @@ -92,3 +92,9 @@ class BiblePlugin(Plugin): 'plugin allows bible verses from different sources to be ' 'displayed on the screen during the service.') return about_text + + + def can_delete_theme(self, theme): + if self.settings_tab.bible_theme == theme: + return False + return True diff --git a/openlp/plugins/custom/customplugin.py b/openlp/plugins/custom/customplugin.py index 3e21228df..ac5384390 100644 --- a/openlp/plugins/custom/customplugin.py +++ b/openlp/plugins/custom/customplugin.py @@ -72,3 +72,8 @@ class CustomPlugin(Plugin): 'songs are. This plugin provides greater freedom over the ' 'songs plugin.<br>') return about_text + + def can_delete_theme(self, theme): + if len(self.custommanager.get_customs_for_theme(theme)) == 0: + return True + return False diff --git a/openlp/plugins/custom/lib/manager.py b/openlp/plugins/custom/lib/manager.py index e88e72b5c..1368b89ee 100644 --- a/openlp/plugins/custom/lib/manager.py +++ b/openlp/plugins/custom/lib/manager.py @@ -105,3 +105,6 @@ class CustomManager(): return False else: return True + + def get_customs_for_theme(self, theme): + return self.session.query(CustomSlide).filter(CustomSlide.theme_name == theme).all() diff --git a/openlp/plugins/songs/lib/manager.py b/openlp/plugins/songs/lib/manager.py index 74bd4cf82..0e662dcbc 100644 --- a/openlp/plugins/songs/lib/manager.py +++ b/openlp/plugins/songs/lib/manager.py @@ -237,3 +237,6 @@ class SongManager(): self.session.rollback() log.exception(u'Could not delete book from song database') return False + + def get_songs_for_theme(self, theme): + return self.session.query(Song).filter(Song.theme_name == theme).all() diff --git a/openlp/plugins/songs/songsplugin.py b/openlp/plugins/songs/songsplugin.py index eb6eb2c2a..9ebaa99b2 100644 --- a/openlp/plugins/songs/songsplugin.py +++ b/openlp/plugins/songs/songsplugin.py @@ -179,3 +179,8 @@ class SongsPlugin(Plugin): about_text = self.trUtf8('<b>Song Plugin</b> <br>This plugin allows ' 'Songs to be managed and displayed.<br>') return about_text + + def can_delete_theme(self, theme): + if len(self.songmanager.get_songs_for_theme(theme)) == 0: + return True + return False From 181c7c99468157bc968b9f7d0eb1aff94f414257 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Thu, 4 Mar 2010 22:03:43 +0000 Subject: [PATCH 128/164] Event clean ups and fixes --- openlp.pyw | 1 - openlp/core/ui/servicemanager.py | 68 ++++++++++++++++++-------------- 2 files changed, 39 insertions(+), 30 deletions(-) diff --git a/openlp.pyw b/openlp.pyw index cb08e419c..6763ea6c1 100755 --- a/openlp.pyw +++ b/openlp.pyw @@ -60,7 +60,6 @@ QToolBar padding: 0; } """ -log = logging.getLogger(__name__) class OpenLP(QtGui.QApplication): """ diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index e28826696..6a9223508 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -42,37 +42,47 @@ class ServiceManagerList(QtGui.QTreeWidget): def __init__(self, parent=None, name=None): QtGui.QTreeWidget.__init__(self,parent) self.parent = parent + self.setExpandsOnDoubleClick(False) + + def mousePressEvent(self, event): + #Implement item selection + item = self.itemAt(event.pos()) + if item is not None: + self.setCurrentItem(item) + parentitem = item.parent() + if event.button() == QtCore.Qt.RightButton: + self.parent.noteAction.setVisible(False) + if parentitem is None: + pos = item.data(0, QtCore.Qt.UserRole).toInt()[0] + self.parent.noteAction.setVisible(True) + else: + pos = parentitem.data(0, QtCore.Qt.UserRole).toInt()[0] + serviceItem = self.parent.serviceItems[pos - 1] + if serviceItem[u'service_item'].is_text(): + self.parent.themeMenu.menuAction().setVisible(True) + else: + self.parent.themeMenu.menuAction().setVisible(False) + if serviceItem[u'service_item'].edit_enabled: + self.parent.editAction.setVisible(True) + else: + self.parent.editAction.setVisible(False) + event.accept() + elif event.button() == QtCore.Qt.LeftButton: + if parentitem is None: + if self.isItemExpanded(item): + self.collapseItem(item) + else: + self.expandItem(item) + else: + event.accept() + else: + event.accept() def mouseDoubleClickEvent(self, event): self.parent.makeLive() - event.ignore() - - def mousePressEvent(self, event): - if event.button() == QtCore.Qt.RightButton: - item = self.itemAt(event.pos()) - parentitem = item.parent() - self.parent.noteAction.setVisible(False) - if parentitem is None: - pos = item.data(0, QtCore.Qt.UserRole).toInt()[0] - self.parent.noteAction.setVisible(True) - else: - pos = parentitem.data(0, QtCore.Qt.UserRole).toInt()[0] - serviceItem = self.parent.serviceItems[pos - 1] - self.parent.menuServiceItem = serviceItem - if serviceItem[u'service_item'].is_text(): - self.parent.themeMenu.menuAction().setVisible(True) - else: - self.parent.themeMenu.menuAction().setVisible(False) - if serviceItem[u'service_item'].edit_enabled: - self.parent.editAction.setVisible(True) - else: - self.parent.editAction.setVisible(False) - event.accept() - else: - event.ignore() + event.accept() def keyPressEvent(self, event): - print event.isAutoRepeat() if type(event) == QtGui.QKeyEvent: #here accept the event and do something if event.key() == QtCore.Qt.Key_Enter: @@ -256,10 +266,11 @@ class ServiceManager(QtGui.QWidget): self.presentation_types = presentation_types def onServiceItemNoteForm(self): + item, count = self.findServiceItem() self.serviceItemNoteForm.textEdit.setPlainText( - self.menuServiceItem[u'service_item'].notes) + self.serviceItems[item][u'service_item'].notes) if self.serviceItemNoteForm.exec_(): - self.menuServiceItem[u'service_item'].notes = \ + self.serviceItems[item][u'service_item'].notes = \ self.serviceItemNoteForm.textEdit.toPlainText() def nextItem(self): @@ -650,7 +661,6 @@ class ServiceManager(QtGui.QWidget): """ Send the current item to the Live slide controller """ - print "ml" item, count = self.findServiceItem() self.parent.LiveController.addServiceManagerItem( self.serviceItems[item][u'service_item'], count) From f4e25bf329fe7acc3c49785f3ed4d35a291a2409 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Thu, 4 Mar 2010 22:09:03 +0000 Subject: [PATCH 129/164] Finish logging cleanup --- openlp.pyw | 2 +- openlp/core/lib/dockwidget.py | 7 ++++--- openlp/core/lib/toolbar.py | 9 +++++---- openlp/core/ui/mediadockmanager.py | 4 ++-- openlp/core/ui/settingsform.py | 2 +- 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/openlp.pyw b/openlp.pyw index 6763ea6c1..7fd7fcbe1 100755 --- a/openlp.pyw +++ b/openlp.pyw @@ -161,7 +161,7 @@ def main(): filename = os.path.join(get_config_directory(), u'openlp.log') logfile = FileHandler(filename, u'w') logfile.setFormatter(logging.Formatter( - u'%(asctime)s %(name)-15s %(levelname)-8s %(message)s')) + u'%(asctime)s %(name)-20s %(levelname)-8s %(message)s')) log.addHandler(logfile) logging.addLevelName(15, u'Timer') # Parse command line options and deal with them. diff --git a/openlp/core/lib/dockwidget.py b/openlp/core/lib/dockwidget.py index 6d205cb29..ce8302b43 100644 --- a/openlp/core/lib/dockwidget.py +++ b/openlp/core/lib/dockwidget.py @@ -27,6 +27,8 @@ import logging from PyQt4 import QtGui +log = logging.getLogger(__name__) + class OpenLPDockWidget(QtGui.QDockWidget): """ Custom DockWidget class to handle events @@ -40,10 +42,9 @@ class OpenLPDockWidget(QtGui.QDockWidget): if name: self.setObjectName(name) self.setFloating(False) - self.log = logging.getLogger(u'OpenLPDockWidget') - self.log.debug(u'Init done') + log.debug(u'Init done') def closeEvent(self, event): self.parent.settingsmanager.setUIItemVisibility( self.objectName(), False) - event.accept() \ No newline at end of file + event.accept() diff --git a/openlp/core/lib/toolbar.py b/openlp/core/lib/toolbar.py index d4985fd70..3753fedc0 100644 --- a/openlp/core/lib/toolbar.py +++ b/openlp/core/lib/toolbar.py @@ -29,6 +29,8 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import build_icon +log = logging.getLogger(__name__) + class OpenLPToolbar(QtGui.QToolBar): """ Lots of toolbars around the place, so it makes sense to have a common way @@ -43,8 +45,7 @@ class OpenLPToolbar(QtGui.QToolBar): self.icons = {} self.setIconSize(QtCore.QSize(20, 20)) self.actions = {} - self.log = logging.getLogger(u'OpenLPToolbar') - self.log.debug(u'Init done') + log.debug(u'Init done') def addToolbarButton(self, title, icon, tooltip=None, slot=None, checkable=False): @@ -119,7 +120,7 @@ class OpenLPToolbar(QtGui.QToolBar): if self.icons[title]: return self.icons[title] else: - self.log.error(u'getIconFromTitle - no icon for %s' % title) + log.error(u'getIconFromTitle - no icon for %s' % title) return QtGui.QIcon() def makeWidgetsInvisible(self, widgets): @@ -152,4 +153,4 @@ class OpenLPToolbar(QtGui.QToolBar): push_button.setCheckable(True) push_button.setFlat(True) self.addWidget(push_button) - return push_button \ No newline at end of file + return push_button diff --git a/openlp/core/ui/mediadockmanager.py b/openlp/core/ui/mediadockmanager.py index 4d76075df..0873133b2 100644 --- a/openlp/core/ui/mediadockmanager.py +++ b/openlp/core/ui/mediadockmanager.py @@ -25,7 +25,7 @@ import logging -log = logging.getLogger(u'MediaDockManager') +log = logging.getLogger(__name__) class MediaDockManager(object): @@ -58,4 +58,4 @@ class MediaDockManager(object): if self.media_dock.widget(dock_index): if self.media_dock.widget(dock_index).ConfigSection == name: self.media_dock.widget(dock_index).hide() - self.media_dock.removeItem(dock_index) \ No newline at end of file + self.media_dock.removeItem(dock_index) diff --git a/openlp/core/ui/settingsform.py b/openlp/core/ui/settingsform.py index c8989625d..4a3902347 100644 --- a/openlp/core/ui/settingsform.py +++ b/openlp/core/ui/settingsform.py @@ -31,7 +31,7 @@ from openlp.core.ui import GeneralTab, ThemesTab from openlp.core.lib import Receiver from settingsdialog import Ui_SettingsDialog -log = logging.getLogger(u'SettingsForm') +log = logging.getLogger(__name__) class SettingsForm(QtGui.QDialog, Ui_SettingsDialog): From 77838615b36cdc7563e64f17d0b2a76bd66723b4 Mon Sep 17 00:00:00 2001 From: Jonathan Corwin <j@corwin.co.uk> Date: Fri, 5 Mar 2010 08:36:32 +0000 Subject: [PATCH 130/164] Presentations - close app down correctly, and mode to automatically detect file type --- .../presentations/lib/impresscontroller.py | 13 ++++++-- openlp/plugins/presentations/lib/mediaitem.py | 32 ++++++++++++++++--- .../presentations/lib/messagelistener.py | 11 +++++-- .../presentations/lib/powerpointcontroller.py | 4 ++- .../presentations/lib/pptviewcontroller.py | 2 +- .../lib/presentationcontroller.py | 1 + .../presentations/presentationplugin.py | 14 ++++++++ 7 files changed, 67 insertions(+), 10 deletions(-) diff --git a/openlp/plugins/presentations/lib/impresscontroller.py b/openlp/plugins/presentations/lib/impresscontroller.py index 75108d850..d365747e3 100644 --- a/openlp/plugins/presentations/lib/impresscontroller.py +++ b/openlp/plugins/presentations/lib/impresscontroller.py @@ -25,6 +25,7 @@ # OOo API documentation: # http://api.openoffice.org/docs/common/ref/com/sun/star/presentation/XSlideShowController.html +# http://wiki.services.openoffice.org/wiki/Documentation/DevGuide/ProUNO/Basic/Getting_Information_about_UNO_Objects#Inspecting_interfaces_during_debugging # http://docs.go-oo.org/sd/html/classsd_1_1SlideShow.html # http://www.oooforum.org/forum/viewtopic.phtml?t=5252 # http://wiki.services.openoffice.org/wiki/Documentation/DevGuide/Working_with_Presentations @@ -62,7 +63,8 @@ class ImpressController(PresentationController): """ log.debug(u'Initialising') PresentationController.__init__(self, plugin, u'Impress') - self.supports = [u'.odp', u'.ppt', u'.pps', u'.pptx', u'.ppsx'] + self.supports = [u'.odp'] + self.alsosupports = [u'.ppt', u'.pps', u'.pptx', u'.ppsx'] self.process = None self.desktop = None @@ -145,10 +147,17 @@ class ImpressController(PresentationController): doc.close_presentation() if os.name != u'nt': desktop = self.get_uno_desktop() + else: + desktop = self.get_com_desktop() + docs = desktop.getComponents() + if docs.hasElements(): + log.debug(u'OpenOffice not terminated') + else: try: desktop.terminate() + log.debug(u'OpenOffice killed') except: - pass + log.exception(u'Failed to terminate OpenOffice') def add_doc(self, name): log.debug(u'Add Doc OpenOffice') diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index 51f234f3d..963babbcd 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -56,7 +56,7 @@ class PresentationMediaItem(MediaManagerItem): # be instanced by the base MediaManagerItem self.ListViewWithDnD_class = PresentationListView MediaManagerItem.__init__(self, parent, icon, title) - self.message_listener = MessageListener(controllers) + self.message_listener = MessageListener(self) def initPluginNameVisible(self): self.PluginNameVisible = self.trUtf8('Presentation') @@ -66,7 +66,8 @@ class PresentationMediaItem(MediaManagerItem): fileType = u'' for controller in self.controllers: if self.controllers[controller].enabled: - for type in self.controllers[controller].supports: + types = self.controllers[controller].supports + self.controllers[controller].alsosupports + for type in types: if fileType.find(type) == -1: fileType += u'*%s ' % type self.OnNewFileMasks = self.trUtf8('Presentations (%s)' % fileType) @@ -106,6 +107,9 @@ class PresentationMediaItem(MediaManagerItem): #load the drop down selection if self.controllers[item].enabled: self.DisplayTypeComboBox.addItem(item) + if self.DisplayTypeComboBox.count > 1: + self.DisplayTypeComboBox.insertItem(0, u'Automatic') + self.DisplayTypeComboBox.setCurrentIndex(0) def loadList(self, list): currlist = self.getFileList() @@ -145,10 +149,16 @@ class PresentationMediaItem(MediaManagerItem): return False service_item.title = unicode(self.DisplayTypeComboBox.currentText()) service_item.shortname = unicode(self.DisplayTypeComboBox.currentText()) - controller = self.controllers[service_item.shortname] + shortname = service_item.shortname + for item in items: bitem = self.ListView.item(item.row()) filename = unicode((bitem.data(QtCore.Qt.UserRole)).toString()) + if shortname==u'Automatic': + service_item.shortname = self.findControllerByType(filename) + if not service_item.shortname: + return False + controller = self.controllers[service_item.shortname] (path, name) = os.path.split(filename) doc = controller.add_doc(filename) if doc.get_slide_preview_file(1) is None: @@ -159,5 +169,19 @@ class PresentationMediaItem(MediaManagerItem): service_item.add_from_command(path, name, img) i = i + 1 img = doc.get_slide_preview_file(i) - controller.remove_doc(doc) + controller.remove_doc(doc) return True + + def findControllerByType(self, filename): + filetype = os.path.splitext(filename)[1] + if not filetype: + return None + for controller in self.controllers: + if self.controllers[controller].enabled: + if filetype in self.controllers[controller].supports: + return controller + for controller in self.controllers: + if self.controllers[controller].enabled: + if filetype in self.controllers[controller].alsosupports: + return controller + return None diff --git a/openlp/plugins/presentations/lib/messagelistener.py b/openlp/plugins/presentations/lib/messagelistener.py index 9065bf796..8f9d129dc 100644 --- a/openlp/plugins/presentations/lib/messagelistener.py +++ b/openlp/plugins/presentations/lib/messagelistener.py @@ -156,8 +156,9 @@ class MessageListener(object): log = logging.getLogger(u'MessageListener') log.info(u'Message Listener loaded') - def __init__(self, controllers): - self.controllers = controllers + def __init__(self, mediaitem): + self.controllers = mediaitem.controllers + self.mediaitem = mediaitem self.previewHandler = Controller(False) self.liveHandler = Controller(True) # messages are sent from core.ui.slidecontroller @@ -190,6 +191,12 @@ class MessageListener(object): """ log.debug(u'Startup called with message %s' % message) self.handler, file, isLive = self.decodeMessage(message) + filetype = os.path.splitext(file)[1][1:] + if self.handler==u'Automatic': + self.handler = self.mediaitem.findControllerByType(file) + if not self.handler: + return + if isLive: self.liveHandler.addHandler(self.controllers[self.handler], file) else: diff --git a/openlp/plugins/presentations/lib/powerpointcontroller.py b/openlp/plugins/presentations/lib/powerpointcontroller.py index 64c435ebf..b0c6a689c 100644 --- a/openlp/plugins/presentations/lib/powerpointcontroller.py +++ b/openlp/plugins/presentations/lib/powerpointcontroller.py @@ -52,7 +52,7 @@ class PowerpointController(PresentationController): """ log.debug(u'Initialising') PresentationController.__init__(self, plugin, u'Powerpoint') - self.supports = [u'.ppt', u'.pps'] + self.supports = [u'.ppt', u'.pps', u'.pptx', u'.ppsx'] self.process = None def check_available(self): @@ -100,6 +100,8 @@ class PowerpointController(PresentationController): doc.close_presentation() if self.process is None: return + if self.process.Presentations.Count > 0: + return try: self.process.Quit() except: diff --git a/openlp/plugins/presentations/lib/pptviewcontroller.py b/openlp/plugins/presentations/lib/pptviewcontroller.py index ddfbbd204..72c7cd5c1 100644 --- a/openlp/plugins/presentations/lib/pptviewcontroller.py +++ b/openlp/plugins/presentations/lib/pptviewcontroller.py @@ -49,7 +49,7 @@ class PptviewController(PresentationController): log.debug(u'Initialising') self.process = None PresentationController.__init__(self, plugin, u'Powerpoint Viewer') - self.supports = [u'.ppt', u'.pps'] + self.supports = [u'.ppt', u'.pps', u'.pptx', u'.ppsx'] def check_available(self): """ diff --git a/openlp/plugins/presentations/lib/presentationcontroller.py b/openlp/plugins/presentations/lib/presentationcontroller.py index 0818c6b4e..e069fce84 100644 --- a/openlp/plugins/presentations/lib/presentationcontroller.py +++ b/openlp/plugins/presentations/lib/presentationcontroller.py @@ -93,6 +93,7 @@ class PresentationController(object): Name of the application, to appear in the application """ self.supports = [] + self.alsosupports = [] self.docs = [] self.plugin = plugin self.name = name diff --git a/openlp/plugins/presentations/presentationplugin.py b/openlp/plugins/presentations/presentationplugin.py index 7103a3a2c..b7fe68153 100644 --- a/openlp/plugins/presentations/presentationplugin.py +++ b/openlp/plugins/presentations/presentationplugin.py @@ -114,3 +114,17 @@ class PresentationPlugin(Plugin): 'programs. The choice of available presentation programs is ' 'available to the user in a drop down box.') return about_text + + def find_controller_by_type(self, filename): + filetype = os.path.splitext(filename)[1][1:] + if not filetype: + return None + for controller in self.controllers: + if self.controllers[controller].enabled: + if filetype in self.controllers[controller].supports: + return controller + for controller in self.controllers: + if self.controllers[controller].enabled: + if filetype in self.controllers[self.handler].alsosupports: + return controller + return None From 763b7cfeedca1277743c5bd40049f55874a8e992 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Fri, 5 Mar 2010 09:24:42 +0000 Subject: [PATCH 131/164] Fix up context menu for serviceitem --- openlp/core/ui/servicemanager.py | 116 +++++++++++++------------------ 1 file changed, 47 insertions(+), 69 deletions(-) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 6a9223508..324dd1ec3 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -34,7 +34,7 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import PluginConfig, OpenLPToolbar, ServiceItem, \ contextMenuAction, contextMenuSeparator, contextMenu, Receiver, \ - contextMenu, str_to_bool + contextMenu, str_to_bool, build_icon from openlp.core.ui import ServiceItemNoteForm class ServiceManagerList(QtGui.QTreeWidget): @@ -44,44 +44,6 @@ class ServiceManagerList(QtGui.QTreeWidget): self.parent = parent self.setExpandsOnDoubleClick(False) - def mousePressEvent(self, event): - #Implement item selection - item = self.itemAt(event.pos()) - if item is not None: - self.setCurrentItem(item) - parentitem = item.parent() - if event.button() == QtCore.Qt.RightButton: - self.parent.noteAction.setVisible(False) - if parentitem is None: - pos = item.data(0, QtCore.Qt.UserRole).toInt()[0] - self.parent.noteAction.setVisible(True) - else: - pos = parentitem.data(0, QtCore.Qt.UserRole).toInt()[0] - serviceItem = self.parent.serviceItems[pos - 1] - if serviceItem[u'service_item'].is_text(): - self.parent.themeMenu.menuAction().setVisible(True) - else: - self.parent.themeMenu.menuAction().setVisible(False) - if serviceItem[u'service_item'].edit_enabled: - self.parent.editAction.setVisible(True) - else: - self.parent.editAction.setVisible(False) - event.accept() - elif event.button() == QtCore.Qt.LeftButton: - if parentitem is None: - if self.isItemExpanded(item): - self.collapseItem(item) - else: - self.expandItem(item) - else: - event.accept() - else: - event.accept() - - def mouseDoubleClickEvent(self, event): - self.parent.makeLive() - event.accept() - def keyPressEvent(self, event): if type(event) == QtGui.QKeyEvent: #here accept the event and do something @@ -186,41 +148,13 @@ class ServiceManager(QtGui.QWidget): self.ServiceManagerList.setAlternatingRowColors(True) self.ServiceManagerList.setHeaderHidden(True) self.ServiceManagerList.setExpandsOnDoubleClick(False) + self.ServiceManagerList.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + self.ServiceManagerList.customContextMenuRequested.connect(self.contextMenu) self.ServiceManagerList.setObjectName(u'ServiceManagerList') # enable drop self.ServiceManagerList.__class__.dragEnterEvent = self.dragEnterEvent self.ServiceManagerList.__class__.dragMoveEvent = self.dragEnterEvent self.ServiceManagerList.__class__.dropEvent = self.dropEvent - # Add a context menu to the service manager list - self.ServiceManagerList.setContextMenuPolicy( - QtCore.Qt.ActionsContextMenu) - self.editAction = contextMenuAction( - self.ServiceManagerList, ':/services/service_edit.png', - self.trUtf8('&Edit Item'), self.remoteEdit) - self.noteAction = contextMenuAction( - self.ServiceManagerList, ':/services/service_notes.png', - self.trUtf8('&Notes'), self.onServiceItemNoteForm) - self.ServiceManagerList.addAction(self.editAction) - self.ServiceManagerList.addAction(self.noteAction) - self.ServiceManagerList.addAction(contextMenuSeparator( - self.ServiceManagerList)) - self.ServiceManagerList.addAction(contextMenuAction( - self.ServiceManagerList, ':/system/system_preview.png', - self.trUtf8('&Preview Verse'), self.makePreview)) - self.ServiceManagerList.addAction(contextMenuAction( - self.ServiceManagerList, ':/system/system_live.png', - self.trUtf8('&Show Live'), self.makeLive)) - self.ServiceManagerList.addAction(contextMenuSeparator( - self.ServiceManagerList)) - self.ServiceManagerList.addAction(contextMenuAction( - self.ServiceManagerList, ':/services/service_delete', - self.trUtf8('&Remove from Service'), self.onDeleteFromService)) - self.ServiceManagerList.addAction(contextMenuSeparator( - self.ServiceManagerList)) - self.themeMenu = contextMenu( - self.ServiceManagerList, '', - self.trUtf8('&Change Item Theme')) - self.ServiceManagerList.addAction(self.themeMenu.menuAction()) self.Layout.addWidget(self.ServiceManagerList) # Add the bottom toolbar self.OrderToolbar = OpenLPToolbar(self) @@ -244,6 +178,8 @@ class ServiceManager(QtGui.QWidget): # Connect up our signals and slots QtCore.QObject.connect(self.ThemeComboBox, QtCore.SIGNAL(u'activated(int)'), self.onThemeComboBoxSelected) + QtCore.QObject.connect(self.ServiceManagerList, + QtCore.SIGNAL(u'doubleClicked(QModelIndex)'), self.makeLive) QtCore.QObject.connect(self.ServiceManagerList, QtCore.SIGNAL(u'itemCollapsed(QTreeWidgetItem*)'), self.collapsed) QtCore.QObject.connect(self.ServiceManagerList, @@ -261,6 +197,48 @@ class ServiceManager(QtGui.QWidget): self.servicePath = self.config.get_data_path() self.service_theme = unicode( self.config.get_config(u'service theme', u'')) + #build the context menu + self.menu = QtGui.QMenu() + self.editAction = self.menu.addAction(self.trUtf8('&Edit Item')) + self.editAction.setIcon(build_icon(':/services/service_edit.png')) + self.notesAction = self.menu.addAction(self.trUtf8('&Notes')) + self.notesAction.setIcon(build_icon(':/services/service_notes.png')) + self.sep1 = self.menu.addAction(u'') + self.sep1.setSeparator(True) + self.previewAction = self.menu.addAction(self.trUtf8('&Preview Verse')) + self.previewAction.setIcon(build_icon(':/system/system_preview.png')) + self.liveAction = self.menu.addAction(self.trUtf8('&Live Verse')) + self.liveAction.setIcon(build_icon(':/system/system_live.png')) + self.sep2 = self.menu.addAction(u'') + self.sep2.setSeparator(True) + self.themeMenu = QtGui.QMenu(self.trUtf8('&Change Item Theme')) + self.menu.addMenu(self.themeMenu) + + def contextMenu(self, point): + item = self.ServiceManagerList.itemAt(point) + if item.parent() is None: + pos = item.data(0, QtCore.Qt.UserRole).toInt()[0] + else: + pos = item.parent().data(0, QtCore.Qt.UserRole).toInt()[0] + serviceItem = self.serviceItems[pos - 1] + self.editAction.setVisible(False) + self.notesAction.setVisible(False) + if serviceItem[u'service_item'].edit_enabled: + self.editAction.setVisible(True) + if item.parent() is None: + self.notesAction.setVisible(True) + self.themeMenu.menuAction().setVisible(False) + if serviceItem[u'service_item'].is_text(): + self.themeMenu.menuAction().setVisible(True) + action = self.menu.exec_(self.ServiceManagerList.mapToGlobal(point)) + if action == self.editAction: + self.remoteEdit() + if action == self.notesAction: + self.onServiceItemNoteForm() + if action == self.previewAction: + self.makePreview() + if action == self.liveAction: + self.makeLive() def onPresentationTypes(self, presentation_types): self.presentation_types = presentation_types From d2b4bc4314a13d173bc3456ddde0286cf708a5cc Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Fri, 5 Mar 2010 10:04:49 +0000 Subject: [PATCH 132/164] Fix up Copyright and utf8 --- openlp/core/ui/serviceitemdialog.py | 41 ++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/openlp/core/ui/serviceitemdialog.py b/openlp/core/ui/serviceitemdialog.py index 150375da9..1fe86a913 100644 --- a/openlp/core/ui/serviceitemdialog.py +++ b/openlp/core/ui/serviceitemdialog.py @@ -1,34 +1,49 @@ # -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 -# Form implementation generated from reading ui file 'serviceitemdialog.ui' -# -# Created: Tue Mar 2 20:17:21 2010 -# by: PyQt4 UI code generator 4.7 -# -# WARNING! All changes made in this file will be lost! +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard # +# --------------------------------------------------------------------------- # +# 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 # +############################################################################### from PyQt4 import QtCore, QtGui class Ui_ServiceNoteEdit(object): def setupUi(self, ServiceNoteEdit): - ServiceNoteEdit.setObjectName("ServiceNoteEdit") + ServiceNoteEdit.setObjectName(u'ServiceNoteEdit') ServiceNoteEdit.resize(400, 243) self.widget = QtGui.QWidget(ServiceNoteEdit) self.widget.setGeometry(QtCore.QRect(20, 10, 361, 223)) - self.widget.setObjectName("widget") + self.widget.setObjectName(u'widget') self.verticalLayout = QtGui.QVBoxLayout(self.widget) - self.verticalLayout.setObjectName("verticalLayout") + self.verticalLayout.setObjectName(u'verticalLayout') self.textEdit = QtGui.QTextEdit(self.widget) - self.textEdit.setObjectName("textEdit") + self.textEdit.setObjectName(u'textEdit') self.verticalLayout.addWidget(self.textEdit) self.buttonBox = QtGui.QDialogButtonBox(self.widget) self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Save) - self.buttonBox.setObjectName("buttonBox") + self.buttonBox.setObjectName(u'buttonBox') self.verticalLayout.addWidget(self.buttonBox) self.retranslateUi(ServiceNoteEdit) QtCore.QMetaObject.connectSlotsByName(ServiceNoteEdit) def retranslateUi(self, ServiceNoteEdit): - ServiceNoteEdit.setWindowTitle(QtGui.QApplication.translate("ServiceNoteEdit", "Service Item Notes", None, QtGui.QApplication.UnicodeUTF8)) - + ServiceNoteEdit.setWindowTitle(self.trUtf8('Service Item Notes')) From eec672c944e70d6974fe1d8a18d32729f6b623ab Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Fri, 5 Mar 2010 12:06:19 +0000 Subject: [PATCH 133/164] Fix up bug for Theme savings and OOS --- openlp/core/ui/servicemanager.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 324dd1ec3..ad0da357b 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -590,9 +590,14 @@ class ServiceManager(QtGui.QWidget): self.parent.RenderManager.themedata = None if len(self.serviceItems) > 0: tempServiceItems = self.serviceItems - self.onNewService() + self.ServiceManagerList.clear() + self.serviceItems = [] + self.isNew = True for item in tempServiceItems: self.addServiceItem(item[u'service_item'], True) + #Set to False as items may have changed rendering + #does not impact the saved song so True may aslo be valid + self.parent.serviceChanged(False, self.serviceName) def addServiceItem(self, item, rebuild=False): """ From 97fc65163a83259abf2aa8d939a62d5e9122a068 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Fri, 5 Mar 2010 20:34:19 +0200 Subject: [PATCH 134/164] Ignoring a few things. --- .bzrignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.bzrignore b/.bzrignore index e7b399b49..00884055d 100644 --- a/.bzrignore +++ b/.bzrignore @@ -12,3 +12,5 @@ documentation/build/doctrees *.log* dist OpenLP.egg-info +build +resources/innosetup/Output From edad400a737974fd40fe79e19b6819ce1ffd4ce0 Mon Sep 17 00:00:00 2001 From: Jonathan Corwin <j@corwin.co.uk> Date: Fri, 5 Mar 2010 19:50:51 +0000 Subject: [PATCH 135/164] Minor fixes --- openlp/plugins/presentations/lib/mediaitem.py | 2 +- openlp/plugins/presentations/presentationplugin.py | 14 -------------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index addfbcc76..7fed27145 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -107,7 +107,7 @@ class PresentationMediaItem(MediaManagerItem): #load the drop down selection if self.controllers[item].enabled: self.DisplayTypeComboBox.addItem(item) - if self.DisplayTypeComboBox.count > 1: + if self.DisplayTypeComboBox.count() > 1: self.DisplayTypeComboBox.insertItem(0, u'Automatic') self.DisplayTypeComboBox.setCurrentIndex(0) diff --git a/openlp/plugins/presentations/presentationplugin.py b/openlp/plugins/presentations/presentationplugin.py index a7fa3bb6c..061eb737f 100644 --- a/openlp/plugins/presentations/presentationplugin.py +++ b/openlp/plugins/presentations/presentationplugin.py @@ -114,17 +114,3 @@ class PresentationPlugin(Plugin): 'programs. The choice of available presentation programs is ' 'available to the user in a drop down box.') return about_text - - def find_controller_by_type(self, filename): - filetype = os.path.splitext(filename)[1][1:] - if not filetype: - return None - for controller in self.controllers: - if self.controllers[controller].enabled: - if filetype in self.controllers[controller].supports: - return controller - for controller in self.controllers: - if self.controllers[controller].enabled: - if filetype in self.controllers[self.handler].alsosupports: - return controller - return None From a33a6d53ca7e23b58ed1cd6b0a4e5771d095eed0 Mon Sep 17 00:00:00 2001 From: Jonathan Corwin <j@corwin.co.uk> Date: Fri, 5 Mar 2010 22:10:53 +0000 Subject: [PATCH 136/164] coding standards --- openlp/plugins/presentations/lib/messagelistener.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openlp/plugins/presentations/lib/messagelistener.py b/openlp/plugins/presentations/lib/messagelistener.py index 2b2375912..62f5df56c 100644 --- a/openlp/plugins/presentations/lib/messagelistener.py +++ b/openlp/plugins/presentations/lib/messagelistener.py @@ -189,8 +189,7 @@ class MessageListener(object): """ log.debug(u'Startup called with message %s' % message) self.handler, file, isLive = self.decodeMessage(message) - filetype = os.path.splitext(file)[1][1:] - if self.handler==u'Automatic': + if self.handler == u'Automatic': self.handler = self.mediaitem.findControllerByType(file) if not self.handler: return From a355c381c2dfff3d3eecd4cd48edf836cb2e01fd Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Sat, 6 Mar 2010 01:03:00 +0200 Subject: [PATCH 137/164] Fixed up a bug introduced in the merge. --- openlp.pyw | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openlp.pyw b/openlp.pyw index ea30b6a28..c4a5fb428 100755 --- a/openlp.pyw +++ b/openlp.pyw @@ -32,6 +32,8 @@ from logging import FileHandler from optparse import OptionParser from PyQt4 import QtCore, QtGui +log = logging.getLogger() + import openlp from openlp.core.lib import Receiver, str_to_bool from openlp.core.resources import qInitResources From 61ff8ec91ae5358b7746367606259c0f75330b27 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Sat, 6 Mar 2010 01:09:18 +0200 Subject: [PATCH 138/164] Removed an unnecessary file. --- resources/pyinstaller/hook-openlp.plugins.bibles.lib.common.py | 1 - 1 file changed, 1 deletion(-) delete mode 100644 resources/pyinstaller/hook-openlp.plugins.bibles.lib.common.py diff --git a/resources/pyinstaller/hook-openlp.plugins.bibles.lib.common.py b/resources/pyinstaller/hook-openlp.plugins.bibles.lib.common.py deleted file mode 100644 index dc71dc088..000000000 --- a/resources/pyinstaller/hook-openlp.plugins.bibles.lib.common.py +++ /dev/null @@ -1 +0,0 @@ -hiddenimports = ['chardet'] \ No newline at end of file From 1932ed886e0893b40051ec483422376bea628c14 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sat, 6 Mar 2010 08:00:36 +0000 Subject: [PATCH 139/164] Fix up service notes --- openlp/core/ui/servicemanager.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index ad0da357b..0c955f392 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -42,7 +42,6 @@ class ServiceManagerList(QtGui.QTreeWidget): def __init__(self, parent=None, name=None): QtGui.QTreeWidget.__init__(self,parent) self.parent = parent - self.setExpandsOnDoubleClick(False) def keyPressEvent(self, event): if type(event) == QtGui.QKeyEvent: @@ -250,6 +249,7 @@ class ServiceManager(QtGui.QWidget): if self.serviceItemNoteForm.exec_(): self.serviceItems[item][u'service_item'].notes = \ self.serviceItemNoteForm.textEdit.toPlainText() + self.repaintServiceList(item, 0) def nextItem(self): """ @@ -429,15 +429,20 @@ class ServiceManager(QtGui.QWidget): for itemcount, item in enumerate(self.serviceItems): serviceitem = item[u'service_item'] treewidgetitem = QtGui.QTreeWidgetItem(self.ServiceManagerList) - treewidgetitem.setText(0,serviceitem.title) - treewidgetitem.setIcon(0,serviceitem.iconic_representation) + if len(serviceitem.notes) > 0: + title = self.trUtf8(u'(N) - %s' % serviceitem.title) + else: + title = serviceitem.title + treewidgetitem.setText(0, title) + treewidgetitem.setToolTip(0, serviceitem.notes) + treewidgetitem.setIcon(0, serviceitem.iconic_representation) treewidgetitem.setData(0, QtCore.Qt.UserRole, QtCore.QVariant(item[u'order'])) treewidgetitem.setExpanded(item[u'expanded']) for count, frame in enumerate(serviceitem.get_frames()): treewidgetitem1 = QtGui.QTreeWidgetItem(treewidgetitem) text = frame[u'title'] - treewidgetitem1.setText(0,text[:40]) + treewidgetitem1.setText(0, text[:40]) treewidgetitem1.setData(0, QtCore.Qt.UserRole, QtCore.QVariant(count)) if serviceItem == itemcount and serviceItemCount == count: From d3564b2df7b122bd760caf60cbf42a62b00fb05e Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sat, 6 Mar 2010 14:08:51 +0000 Subject: [PATCH 140/164] Fix translation --- openlp/core/ui/servicemanager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 0c955f392..4b29fd7e7 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -430,7 +430,7 @@ class ServiceManager(QtGui.QWidget): serviceitem = item[u'service_item'] treewidgetitem = QtGui.QTreeWidgetItem(self.ServiceManagerList) if len(serviceitem.notes) > 0: - title = self.trUtf8(u'(N) - %s' % serviceitem.title) + title = u'%s - %s' % (self.trUtf8('(N)'), serviceitem.title) else: title = serviceitem.title treewidgetitem.setText(0, title) From 714135a94f1046792c9b35ae64bd3602052dcfe6 Mon Sep 17 00:00:00 2001 From: Jonathan Corwin <j@corwin.co.uk> Date: Sat, 6 Mar 2010 23:33:10 +0000 Subject: [PATCH 141/164] presentation text/notes routines. blanking issues --- openlp/core/ui/slidecontroller.py | 4 +- .../presentations/lib/impresscontroller.py | 37 +++++++++- openlp/plugins/presentations/lib/mediaitem.py | 8 ++- .../presentations/lib/messagelistener.py | 14 ++-- .../presentations/lib/powerpointcontroller.py | 68 +++++++++++++------ .../lib/presentationcontroller.py | 18 +++++ 6 files changed, 118 insertions(+), 31 deletions(-) diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index ff8507f5d..7221e9a21 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -415,7 +415,7 @@ class SlideController(QtGui.QWidget): elif item.is_command(): Receiver.send_message(u'%s_start' % item.name.lower(), \ [item.title, item.service_item_path, - item.get_frame_title(), slideno, self.isLive]) + item.get_frame_title(), slideno, self.isLive, self.blankButton.isChecked()]) self.displayServiceManagerItems(item, slideno) def displayServiceManagerItems(self, serviceItem, slideno): @@ -678,7 +678,7 @@ class SlideController(QtGui.QWidget): if self.isLive: Receiver.send_message(u'%s_start' % item.name.lower(), \ [item.title, item.service_item_path, - item.get_frame_title(), self.isLive]) + item.get_frame_title(), self.isLive, self.blankButton.isChecked()]) else: self.mediaObject.stop() self.mediaObject.clearQueue() diff --git a/openlp/plugins/presentations/lib/impresscontroller.py b/openlp/plugins/presentations/lib/impresscontroller.py index 196763cce..56a223f06 100644 --- a/openlp/plugins/presentations/lib/impresscontroller.py +++ b/openlp/plugins/presentations/lib/impresscontroller.py @@ -214,7 +214,7 @@ class ImpressDocument(PresentationDocument): self.presentation.Display = self.controller.plugin.render_manager.screens.current_display + 1 self.control = None self.create_thumbnails() - + def create_thumbnails(self): """ Create thumbnail images for presentation @@ -356,3 +356,38 @@ class ImpressDocument(PresentationDocument): return path else: return None + + def get_slide_text(self, slide_no): + """ + Returns the text on the slide + + ``slide_no`` + The slide the text is required for, starting at 1 + """ + doc = self.document + pages = doc.getDrawPages() + text = '' + page = pages.getByIndex(slide_no - 1) + for idx in range(page.getCount()): + shape = page.getByIndex(idx) + if shape.supportsService("com.sun.star.drawing.Text"): + text += shape.getString() + '\n' + return text + + def get_slide_notes(self, slide_no): + """ + Returns the text on the slide + + ``slide_no`` + The slide the notes are required for, starting at 1 + """ + doc = self.document + pages = doc.getDrawPages() + text = '' + page = pages.getByIndex(slide_no - 1) + notes = page.getNotesPage() + for idx in range(notes.getCount()): + shape = notes.getByIndex(idx) + if shape.supportsService("com.sun.star.drawing.Text"): + text += shape.getString() + '\n' + return text diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index 7fed27145..37d50d01c 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -52,17 +52,19 @@ class PresentationMediaItem(MediaManagerItem): self.PluginNameShort = u'Presentation' self.ConfigSection = title self.IconPath = u'presentations/presentation' + self.Automatic = u'' # this next is a class, not an instance of a class - it will # be instanced by the base MediaManagerItem self.ListViewWithDnD_class = PresentationListView MediaManagerItem.__init__(self, parent, icon, title) self.message_listener = MessageListener(self) - + def initPluginNameVisible(self): self.PluginNameVisible = self.trUtf8('Presentation') def retranslateUi(self): self.OnNewPrompt = self.trUtf8('Select Presentation(s)') + self.Automatic = self.trUtf8('Automatic') fileType = u'' for controller in self.controllers: if self.controllers[controller].enabled: @@ -108,7 +110,7 @@ class PresentationMediaItem(MediaManagerItem): if self.controllers[item].enabled: self.DisplayTypeComboBox.addItem(item) if self.DisplayTypeComboBox.count() > 1: - self.DisplayTypeComboBox.insertItem(0, u'Automatic') + self.DisplayTypeComboBox.insertItem(0, self.Automatic) self.DisplayTypeComboBox.setCurrentIndex(0) def loadList(self, list): @@ -154,7 +156,7 @@ class PresentationMediaItem(MediaManagerItem): for item in items: bitem = self.ListView.item(item.row()) filename = unicode((bitem.data(QtCore.Qt.UserRole)).toString()) - if shortname==u'Automatic': + if shortname == self.Automatic: service_item.shortname = self.findControllerByType(filename) if not service_item.shortname: return False diff --git a/openlp/plugins/presentations/lib/messagelistener.py b/openlp/plugins/presentations/lib/messagelistener.py index 62f5df56c..17a2492ea 100644 --- a/openlp/plugins/presentations/lib/messagelistener.py +++ b/openlp/plugins/presentations/lib/messagelistener.py @@ -44,7 +44,7 @@ class Controller(object): self.doc = None log.info(u'%s controller loaded' % live) - def addHandler(self, controller, file): + def addHandler(self, controller, file, isBlank): log.debug(u'Live = %s, addHandler %s' % (self.isLive, file)) self.controller = controller if self.doc is not None: @@ -53,6 +53,8 @@ class Controller(object): self.doc.load_presentation() if self.isLive: self.doc.start_presentation() + if isBlank: + self.blank() Receiver.send_message(u'live_slide_hide') self.doc.slidenumber = 0 @@ -130,6 +132,7 @@ class Controller(object): #self.timer.stop() def blank(self): + log.debug(u'Live = %s, blank' % self.isLive) if not self.isLive: return if not self.doc.is_loaded(): @@ -139,6 +142,7 @@ class Controller(object): self.doc.blank_screen() def unblank(self): + log.debug(u'Live = %s, unblank' % self.isLive) if not self.isLive: return self.activate() @@ -188,14 +192,14 @@ class MessageListener(object): Save the handler as any new presentations start here """ log.debug(u'Startup called with message %s' % message) - self.handler, file, isLive = self.decodeMessage(message) - if self.handler == u'Automatic': + self.handler, file, isLive, isBlank = self.decodeMessage(message) + if self.handler == self.mediaitem.Automatic: self.handler = self.mediaitem.findControllerByType(file) if not self.handler: return if isLive: - self.liveHandler.addHandler(self.controllers[self.handler], file) + self.liveHandler.addHandler(self.controllers[self.handler], file, isBlank) else: self.previewHandler.addHandler(self.controllers[self.handler], file) @@ -263,7 +267,7 @@ class MessageListener(object): Message containing Presentaion handler name and file to be presented. """ file = os.path.join(message[1], message[2]) - return message[0], file, message[4] + return message[0], file, message[4], message[5] def timeout(self): self.liveHandler.poll() diff --git a/openlp/plugins/presentations/lib/powerpointcontroller.py b/openlp/plugins/presentations/lib/powerpointcontroller.py index bbd2c528a..838ab6c8c 100644 --- a/openlp/plugins/presentations/lib/powerpointcontroller.py +++ b/openlp/plugins/presentations/lib/powerpointcontroller.py @@ -77,21 +77,6 @@ class PowerpointController(PresentationController): self.process.Visible = True self.process.WindowState = 2 - def is_loaded(self): - """ - Returns true if a presentation is loaded - """ - try: - if not self.process.Visible: - return False - if self.process.Windows.Count == 0: - return False - if self.process.Presentations.Count == 0: - return False - except: - return False - return True - def kill(self): """ Called at system exit to clean up any running presentations @@ -134,11 +119,8 @@ class PowerpointDocument(PresentationDocument): The file name of the presentations to run. """ log.debug(u'LoadPresentation') - #try: if not self.controller.process.Visible: self.controller.start_process() - #except: - # self.controller.start_process() #try: self.controller.process.Presentations.Open(self.filepath, False, False, True) #except: @@ -159,7 +141,7 @@ class PowerpointDocument(PresentationDocument): if self.check_thumbnails(): return self.presentation.Export(os.path.join(self.thumbnailpath, '') - , 'png', 600, 480) + , 'png', 640, 480) def close_presentation(self): """ @@ -168,7 +150,7 @@ class PowerpointDocument(PresentationDocument): being shut down """ if self.presentation == None: - return + return try: self.presentation.Close() except: @@ -176,6 +158,22 @@ class PowerpointDocument(PresentationDocument): self.presentation = None self.controller.remove_doc(self) + def is_loaded(self): + """ + Returns true if a presentation is loaded + """ + try: + if not self.controller.process.Visible: + return False + if self.controller.process.Windows.Count == 0: + return False + if self.controller.process.Presentations.Count == 0: + return False + except: + return False + return True + + def is_active(self): """ Returns true if a presentation is currently active @@ -276,3 +274,33 @@ class PowerpointDocument(PresentationDocument): return path else: return None + + def get_slide_text(self, slide_no): + """ + Returns the text on the slide + + ``slide_no`` + The slide the text is required for, starting at 1 + """ + text = '' + shapes = self.presentation.Slides(slide_no).Shapes + for idx in range(shapes.Count): + shape = shapes(idx + 1) + if shape.HasTextFrame: + text += shape.TextFrame.TextRange.Text + '\n' + return text + + def get_slide_notes(self, slide_no): + """ + Returns the text on the slide + + ``slide_no`` + The slide the notes are required for, starting at 1 + """ + text = '' + shapes = self.presentation.Slides(slide_no).NotesPage.Shapes + for idx in range(shapes.Count): + shape = shapes(idx + 1) + if shape.HasTextFrame: + text += shape.TextFrame.TextRange.Text + '\n' + return text diff --git a/openlp/plugins/presentations/lib/presentationcontroller.py b/openlp/plugins/presentations/lib/presentationcontroller.py index 0783d40f4..5a914e1cc 100644 --- a/openlp/plugins/presentations/lib/presentationcontroller.py +++ b/openlp/plugins/presentations/lib/presentationcontroller.py @@ -350,3 +350,21 @@ class PresentationDocument(object): prefix = u'preview' Receiver.send_message(u'%s_slidecontroller_change' % prefix, self.slidenumber - 1) + + def get_slide_text(self, slide_no): + """ + Returns the text on the slide + + ``slide_no`` + The slide the text is required for, starting at 1 + """ + return '' + + def get_slide_notes(self, slide_no): + """ + Returns the text on the slide + + ``slide_no`` + The slide the notes are required for, starting at 1 + """ + return '' From 77e671bd78192072d8d1435cfb3adeebcac658ab Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sun, 7 Mar 2010 06:42:22 +0000 Subject: [PATCH 142/164] Remove unneeded version tagging --- openlp/core/utils/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openlp/core/utils/__init__.py b/openlp/core/utils/__init__.py index e85b2d939..256d5a090 100644 --- a/openlp/core/utils/__init__.py +++ b/openlp/core/utils/__init__.py @@ -33,13 +33,12 @@ log = logging.getLogger(__name__) def check_latest_version(config, current_version): version_string = current_version #set to prod in the distribution confif file. - environment = config.get_config(u'run environment', u'dev') last_test = config.get_config(u'last version test', datetime.now().date()) this_test = unicode(datetime.now().date()) config.set_config(u'last version test', this_test) if last_test != this_test: version_string = u'' - req = urllib2.Request(u'http://www.openlp.org/files/%s_version.txt' % environment) + req = urllib2.Request(u'http://www.openlp.org/files/version.txt') req.add_header(u'User-Agent', u'OpenLP/%s' % current_version) try: handle = urllib2.urlopen(req, None) From 5560aac57580502e0388199953422fcda13d5a1b Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sun, 7 Mar 2010 19:22:54 +0000 Subject: [PATCH 143/164] Fix expanding service items --- openlp/core/ui/servicemanager.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 4b29fd7e7..ba9269244 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -599,12 +599,12 @@ class ServiceManager(QtGui.QWidget): self.serviceItems = [] self.isNew = True for item in tempServiceItems: - self.addServiceItem(item[u'service_item'], True) + self.addServiceItem(item[u'service_item'], False, item[u'expanded']) #Set to False as items may have changed rendering #does not impact the saved song so True may aslo be valid self.parent.serviceChanged(False, self.serviceName) - def addServiceItem(self, item, rebuild=False): + def addServiceItem(self, item, rebuild=False, expand=True): """ Add a Service item to the list @@ -624,12 +624,12 @@ class ServiceManager(QtGui.QWidget): if sitem == -1: self.serviceItems.append({u'service_item': item, u'order': len(self.serviceItems) + 1, - u'expanded':True}) + u'expanded':expand}) self.repaintServiceList(len(self.serviceItems) + 1, 0) else: self.serviceItems.insert(sitem + 1, {u'service_item': item, u'order': len(self.serviceItems)+1, - u'expanded':True}) + u'expanded':expand}) self.repaintServiceList(sitem + 1, 0) #if rebuilding list make sure live is fixed. if rebuild: From 6f87c362d1ff54f42d76acb3c6fe35d65bf3e081 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sun, 7 Mar 2010 20:45:27 +0000 Subject: [PATCH 144/164] Use Icon to show we have Notes --- openlp/core/ui/servicemanager.py | 17 +++++++++++++---- openlp/plugins/images/lib/mediaitem.py | 2 +- resources/images/openlp-2.qrc | 1 + resources/images/service_item_notes.png | Bin 0 -> 876 bytes 4 files changed, 15 insertions(+), 5 deletions(-) create mode 100644 resources/images/service_item_notes.png diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index ba9269244..6765334dc 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -430,12 +430,21 @@ class ServiceManager(QtGui.QWidget): serviceitem = item[u'service_item'] treewidgetitem = QtGui.QTreeWidgetItem(self.ServiceManagerList) if len(serviceitem.notes) > 0: - title = u'%s - %s' % (self.trUtf8('(N)'), serviceitem.title) + icon = QtGui.QImage(serviceitem.icon) + icon = icon.scaled(80, 80, QtCore.Qt.KeepAspectRatio, + QtCore.Qt.SmoothTransformation) + + overlay = QtGui.QImage(':/services/service_item_notes.png') + overlay = overlay.scaled(80, 80, QtCore.Qt.KeepAspectRatio, + QtCore.Qt.SmoothTransformation) + painter = QtGui.QPainter(icon) + painter.drawImage(0, 0, overlay) + painter.end() + treewidgetitem.setIcon(0, build_icon(icon)) else: - title = serviceitem.title - treewidgetitem.setText(0, title) + treewidgetitem.setIcon(0, serviceitem.iconic_representation) + treewidgetitem.setText(0, serviceitem.title) treewidgetitem.setToolTip(0, serviceitem.notes) - treewidgetitem.setIcon(0, serviceitem.iconic_representation) treewidgetitem.setData(0, QtCore.Qt.UserRole, QtCore.QVariant(item[u'order'])) treewidgetitem.setExpanded(item[u'expanded']) diff --git a/openlp/plugins/images/lib/mediaitem.py b/openlp/plugins/images/lib/mediaitem.py index 830459843..75f2fd981 100644 --- a/openlp/plugins/images/lib/mediaitem.py +++ b/openlp/plugins/images/lib/mediaitem.py @@ -61,7 +61,7 @@ class ImageMediaItem(MediaManagerItem): def retranslateUi(self): self.OnNewPrompt = self.trUtf8('Select Image(s)') self.OnNewFileMasks = \ - self.trUtf8('Images (*.jpg *jpeg *.gif *.png *.bmp)') + self.trUtf8('Images (*.jpg *jpeg *.gif *.png *.bmp);; All files (*)') def requiredIcons(self): MediaManagerItem.requiredIcons(self) diff --git a/resources/images/openlp-2.qrc b/resources/images/openlp-2.qrc index b6509b528..28af4c31a 100644 --- a/resources/images/openlp-2.qrc +++ b/resources/images/openlp-2.qrc @@ -78,6 +78,7 @@ <qresource prefix="services"> <file>service_edit.png</file> <file>service_notes.png</file> + <file>service_item_notes.png</file> <file>service_bottom.png</file> <file>service_down.png</file> <file>service_top.png</file> diff --git a/resources/images/service_item_notes.png b/resources/images/service_item_notes.png new file mode 100644 index 0000000000000000000000000000000000000000..59991dba85e745b69fbec46267c1e6414dc506c8 GIT binary patch literal 876 zcmV-y1C#uTP)<h;3K|Lk000e1NJLTq000&M000&U1^@s6#I$TX00001b5ch_0Itp) z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L00LnE00LnF!7x?&00007bV*G`2igM% z6A(GBp-}Ju00QhuL_t(I%k7lSYn)XS#((GB_nkL0X3VJ37<FYUghmC4*#uHST_`9P zp>F&O1hM}?L0kxK6<jH<ESQB*sUJlbO>v3}W)Y)E)e@67Y1)`%(wR)=ejFDw3F%b9 zwe(yL&xLz<?)iA`h1+)9{#RzgbMF@aC35}D-DB6jxa-5U&#K4&Ek*z)CML8fit0k; znbG{AC;h9FLs#b(Z)CIYys&BZiMAI`kD6Z{PoXnT?7WnG`>2JMM8ADK#>Uva&o|d^ zA-?j};~$mEgU8ywqODk!+VJ2&xiXaHna3$+2!e@;fEff&3cDn~Lu_6qwpM0p&;I#J zKe3*l=bN(I7#gl_RY&eG57zD^E8H5I8d%&A!j@Rv1iyu(ZD14p6|D6W64p@V_~zLM zkKK~-)YMcqb8>xoxF#bq;1Ed|h9%fb@*pB$(Xg0E!LVSY7*>$%NcE}TH}^BH*X!Zu zMRz8VaJitFqPd_sXx5Ltvyz}C#qD{Pk1F=TO|1a<daihTcQ<+r#3||&%>>E%QMzZj zpap25Xzp;iLv#0XbN0}Qn^XZXck1x`kumpZHjp7sFiT*)h}26VbuUH@NSGPc5mE=t z8!4n+i#A=EzW9Ug5r6kf>HW_34!+$X?0RA+l0xcYOeCL3F<~)bsoQXIg5;pNqPgNS zS2(9LJF|y|+#c~?{*k$-A8Wi`R$q>tL<$KDh9&C`D}i9Ww`BX}iYS622qFq-O|%%_ zCtg}wN=GN}n|OGnJ&pm%Fzc0T{iHpaq%M7TIYb=d?ztf42j0|!ll*q!j+4zcA;mz9 zkrX2-L@ey>(*d`hCt@r@qgDCs-2CWU)30@2%*H=GFa5&){JpUsCl6nHLdESXL{t#x z&@3ZTiaxkz+h@&2t91E~mCE<0&hq7@Wxot;0N3mF`i-og`1FP0H_FxGW8d7`@zHIy zI{8|om91_x%ZtA*l`bw^vL)auunIKm^>g-5pU~eVT7kkm>?h0s0000<MNUMnLSTX$ CcclIR literal 0 HcmV?d00001 From 55c676dc03383f29cef9a318855d4683795b6c7d Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Mon, 8 Mar 2010 19:50:35 +0000 Subject: [PATCH 145/164] Minor servicemanager fixes --- openlp/core/ui/servicemanager.py | 34 ++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 6765334dc..c813e787e 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -158,20 +158,20 @@ class ServiceManager(QtGui.QWidget): # Add the bottom toolbar self.OrderToolbar = OpenLPToolbar(self) self.OrderToolbar.addToolbarButton( - self.trUtf8('Move to top'), u':/services/service_top.png', + self.trUtf8('Move to &top'), u':/services/service_top.png', self.trUtf8('Move to top'), self.onServiceTop) self.OrderToolbar.addToolbarButton( - self.trUtf8('Move up'), u':/services/service_up.png', + self.trUtf8('Move &up'), u':/services/service_up.png', self.trUtf8('Move up order'), self.onServiceUp) self.OrderToolbar.addToolbarButton( - self.trUtf8('Move down'), u':/services/service_down.png', + self.trUtf8('Move &down'), u':/services/service_down.png', self.trUtf8('Move down order'), self.onServiceDown) self.OrderToolbar.addToolbarButton( - self.trUtf8('Move to bottom'), u':/services/service_bottom.png', + self.trUtf8('Move to &bottom'), u':/services/service_bottom.png', self.trUtf8('Move to end'), self.onServiceEnd) self.OrderToolbar.addSeparator() self.OrderToolbar.addToolbarButton( - self.trUtf8('Delete From Service'), u':/services/service_delete.png', + self.trUtf8('&Delete From Service'), u':/services/service_delete.png', self.trUtf8('Delete From Service'), self.onDeleteFromService) self.Layout.addWidget(self.OrderToolbar) # Connect up our signals and slots @@ -199,18 +199,20 @@ class ServiceManager(QtGui.QWidget): #build the context menu self.menu = QtGui.QMenu() self.editAction = self.menu.addAction(self.trUtf8('&Edit Item')) - self.editAction.setIcon(build_icon(':/services/service_edit.png')) + self.editAction.setIcon(build_icon(u':/services/service_edit.png')) self.notesAction = self.menu.addAction(self.trUtf8('&Notes')) - self.notesAction.setIcon(build_icon(':/services/service_notes.png')) + self.notesAction.setIcon(build_icon(u':/services/service_notes.png')) + self.deleteAction = self.menu.addAction(self.trUtf8('&Delete From Service')) + self.deleteAction.setIcon(build_icon(u':/services/service_delete.png')) self.sep1 = self.menu.addAction(u'') self.sep1.setSeparator(True) self.previewAction = self.menu.addAction(self.trUtf8('&Preview Verse')) - self.previewAction.setIcon(build_icon(':/system/system_preview.png')) + self.previewAction.setIcon(build_icon(u':/system/system_preview.png')) self.liveAction = self.menu.addAction(self.trUtf8('&Live Verse')) - self.liveAction.setIcon(build_icon(':/system/system_live.png')) + self.liveAction.setIcon(build_icon(u':/system/system_live.png')) self.sep2 = self.menu.addAction(u'') self.sep2.setSeparator(True) - self.themeMenu = QtGui.QMenu(self.trUtf8('&Change Item Theme')) + self.themeMenu = QtGui.QMenu(self.trUtf8(u'&Change Item Theme')) self.menu.addMenu(self.themeMenu) def contextMenu(self, point): @@ -232,6 +234,8 @@ class ServiceManager(QtGui.QWidget): action = self.menu.exec_(self.ServiceManagerList.mapToGlobal(point)) if action == self.editAction: self.remoteEdit() + if action == self.deleteAction: + self.onDeleteFromService() if action == self.notesAction: self.onServiceItemNoteForm() if action == self.previewAction: @@ -327,6 +331,7 @@ class ServiceManager(QtGui.QWidget): Record if an item is collapsed Used when repainting the list to get the correct state """ + print "expand" pos = item.data(0, QtCore.Qt.UserRole).toInt()[0] self.serviceItems[pos -1 ][u'expanded'] = True @@ -428,12 +433,12 @@ class ServiceManager(QtGui.QWidget): self.ServiceManagerList.clear() for itemcount, item in enumerate(self.serviceItems): serviceitem = item[u'service_item'] + print item[u'expanded'] treewidgetitem = QtGui.QTreeWidgetItem(self.ServiceManagerList) if len(serviceitem.notes) > 0: icon = QtGui.QImage(serviceitem.icon) icon = icon.scaled(80, 80, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) - overlay = QtGui.QImage(':/services/service_item_notes.png') overlay = overlay.scaled(80, 80, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) @@ -447,7 +452,6 @@ class ServiceManager(QtGui.QWidget): treewidgetitem.setToolTip(0, serviceitem.notes) treewidgetitem.setData(0, QtCore.Qt.UserRole, QtCore.QVariant(item[u'order'])) - treewidgetitem.setExpanded(item[u'expanded']) for count, frame in enumerate(serviceitem.get_frames()): treewidgetitem1 = QtGui.QTreeWidgetItem(treewidgetitem) text = frame[u'title'] @@ -455,7 +459,11 @@ class ServiceManager(QtGui.QWidget): treewidgetitem1.setData(0, QtCore.Qt.UserRole, QtCore.QVariant(count)) if serviceItem == itemcount and serviceItemCount == count: - self.ServiceManagerList.setCurrentItem(treewidgetitem1) + #preserve expanding status as setCurrentItem sets it to True + temp = item[u'expanded'] + self.ServiceManagerList.setCurrentItem(treewidgetitem1) + item[u'expanded'] = temp + treewidgetitem.setExpanded(item[u'expanded']) def onSaveService(self, quick=False): """ From b75bc2abbf64b792f4eb54066c0db6b4594438c8 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Mon, 8 Mar 2010 19:55:13 +0000 Subject: [PATCH 146/164] Remove Print statements --- openlp/core/ui/servicemanager.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index c813e787e..b263e2366 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -331,7 +331,6 @@ class ServiceManager(QtGui.QWidget): Record if an item is collapsed Used when repainting the list to get the correct state """ - print "expand" pos = item.data(0, QtCore.Qt.UserRole).toInt()[0] self.serviceItems[pos -1 ][u'expanded'] = True @@ -433,7 +432,6 @@ class ServiceManager(QtGui.QWidget): self.ServiceManagerList.clear() for itemcount, item in enumerate(self.serviceItems): serviceitem = item[u'service_item'] - print item[u'expanded'] treewidgetitem = QtGui.QTreeWidgetItem(self.ServiceManagerList) if len(serviceitem.notes) > 0: icon = QtGui.QImage(serviceitem.icon) From 0ef8005b52df384cbed49268a9ae731634b4209f Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Tue, 9 Mar 2010 08:07:42 +0200 Subject: [PATCH 147/164] Fixed up some problems and inadvertant bugs from the move to the scripts directory. --- openlp/plugins/alerts/lib/alertsmanager.py | 2 +- resources/i18n/openlp_en.ts | 5488 +++++++++++++++++--- scripts/get-strings.py | 21 +- 3 files changed, 4684 insertions(+), 827 deletions(-) diff --git a/openlp/plugins/alerts/lib/alertsmanager.py b/openlp/plugins/alerts/lib/alertsmanager.py index 6838fefa0..41fc25562 100644 --- a/openlp/plugins/alerts/lib/alertsmanager.py +++ b/openlp/plugins/alerts/lib/alertsmanager.py @@ -76,7 +76,7 @@ class AlertsManager(QtCore.QObject): display text """ log.debug(u'display alert called %s' % text) - self.parent.maindisplay.parent.StatusBar.showMessage(self.trUtf8(u'')) + self.parent.maindisplay.parent.StatusBar.showMessage(u'') self.alertList.append(text) if self.timer_id != 0 or self.parent.maindisplay.mediaLoaded: self.parent.maindisplay.parent.StatusBar.showMessage(\ diff --git a/resources/i18n/openlp_en.ts b/resources/i18n/openlp_en.ts index d92273c1d..6f9d4a26a 100644 --- a/resources/i18n/openlp_en.ts +++ b/resources/i18n/openlp_en.ts @@ -4,879 +4,2296 @@ <context> <name>BibleMediaItem</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/bibles/lib/mediaitem.py" line="131"/> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="141"/> <source>Quick</source> <translation type="unfinished"></translation> </message> </context> - <context> - <name>AlertsTab</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/alertstab.py" line="36"/> - <source>Alerts</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/alertstab.py" line="151"/> - <source>Font</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_OpenLPExportDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/openlpexportdialog.py" line="291"/> - <source>Author</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_AmendThemeDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="579"/> - <source>Vertical</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>AlertForm</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/alertform.py" line="97"/> - <source>Display</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>SplashScreen</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/splashscreen.py" line="32"/> - <source>Starting</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>MediaManagerItem</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/lib/mediamanageritem.py" line="215"/> - <source>New</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>EditCustomForm</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/custom/forms/editcustomform.py" line="119"/> - <source>Error</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_AmendThemeDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="640"/> - <source>Center</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>TestMediaManager:</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/test/test_mediamanageritem.py" line="74"/> - <source>Item2</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>AboutForm</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/about.py" line="167"/> - <source>License</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>TestMediaManager:</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/test/test_mediamanageritem.py" line="72"/> - <source>Item1</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_MainWindow</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/mainwindow.py" line="394"/> - <source>English</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_AmendThemeDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="588"/> - <source>pt</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_OpenSongImportDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/opensongimportdialog.py" line="107"/> - <source>Close</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_AmendThemeDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="643"/> - <source>Middle</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="568"/> - <source>Opaque</source> - <translation type="unfinished"></translation> - </message> - </context> <context> <name>Ui_customEditDialog</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/custom/forms/editcustomdialog.py" line="154"/> - <source>Clear</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/custom/forms/editcustomdialog.py" line="150"/> - <source>Save</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>BibleImportForm</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/bibles/forms/bibleimportform.py" line="187"/> - <source>Information</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_OpenSongExportDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/opensongexportdialog.py" line="293"/> - <source>Lyrics</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_customEditDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/custom/forms/editcustomdialog.py" line="152"/> - <source>Delete</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_OpenLPImportDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/openlpimportdialog.py" line="304"/> - <source>Close</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>ImageTab</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/images/lib/imagetab.py" line="66"/> - <source>sec</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_OpenSongExportDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/opensongexportdialog.py" line="304"/> - <source>Close</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>BibleMediaItem</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/bibles/lib/mediaitem.py" line="263"/> - <source>Clear</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_OpenSongExportDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/opensongexportdialog.py" line="291"/> - <source>Author</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_PluginViewDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/plugindialog.py" line="108"/> - <source>Inactive</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_OpenLPImportDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/openlpimportdialog.py" line="303"/> - <source>Import</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_MainWindow</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/mainwindow.py" line="372"/> - <source>F9</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/mainwindow.py" line="361"/> - <source>F8</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_SongMaintenanceDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/songmaintenancedialog.py" line="212"/> - <source>Topics</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_AmendThemeDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="638"/> - <source>Left</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>SongMaintenanceForm</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/songmaintenanceform.py" line="134"/> - <source>Error</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_AmendThemeDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="642"/> - <source>Top</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_MainWindow</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/mainwindow.py" line="380"/> - <source>F7</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_AmendThemeDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="636"/> - <source>Alignment</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>MediaManagerItem</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/lib/mediamanageritem.py" line="249"/> - <source>Add</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>BibleMediaItem</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/bibles/lib/mediaitem.py" line="250"/> - <source>Search</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_SongMaintenanceDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/songmaintenancedialog.py" line="215"/> - <source>Edit</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_EditSongDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/editsongdialog.py" line="441"/> - <source>Theme</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>SongsPlugin</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/songsplugin.py" line="116"/> - <source>OpenSong</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>MediaManagerItem</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/lib/mediamanageritem.py" line="222"/> - <source>Edit</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/lib/mediamanageritem.py" line="208"/> - <source>Load</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_AmendThemeDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="644"/> - <source>Bottom</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_BibleImportDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/bibles/forms/bibleimportdialog.py" line="250"/> - <source>NIV</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_AmendThemeDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="573"/> - <source>Image</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>GeneralTab</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/generaltab.py" line="170"/> - <source>Screen</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>ThemeManager</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/thememanager.py" line="166"/> - <source>Error</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_AmendThemeDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="590"/> - <source>Normal</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>AlertsTab</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/alertstab.py" line="156"/> - <source>s</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_OpenLPExportDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/openlpexportdialog.py" line="304"/> - <source>Close</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_OpenLPImportDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/openlpimportdialog.py" line="291"/> - <source>Author</source> + <location filename="openlp/plugins/custom/forms/editcustomdialog.py" line="162"/> + <source>Delete selected slide</source> <translation type="unfinished"></translation> </message> </context> <context> <name>BiblesTab</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/bibles/lib/biblestab.py" line="45"/> - <source>Bibles</source> + <location filename="openlp/plugins/bibles/lib/biblestab.py" line="161"/> + <source>( and )</source> <translation type="unfinished"></translation> </message> </context> <context> - <name>Ui_AuditDetailDialog</name> + <name>RemoteTab</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/audit/forms/auditdetaildialog.py" line="163"/> - <source>Summary</source> + <location filename="openlp/plugins/remotes/lib/remotetab.py" line="39"/> + <source>Remotes</source> <translation type="unfinished"></translation> </message> </context> <context> - <name>Ui_BibleImportDialog</name> + <name>ServiceManager</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/bibles/forms/bibleimportdialog.py" line="234"/> - <source>Import</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>SongBookForm</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/songbookform.py" line="51"/> - <source>Error</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_OpenLPImportDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/openlpimportdialog.py" line="294"/> - <source>Title</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_PluginViewDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/plugindialog.py" line="104"/> - <source>TextLabel</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>AboutForm</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/about.py" line="182"/> - <source>Contribute</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>GeneralTab</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/generaltab.py" line="136"/> - <source>Monitors</source> + <location filename="openlp/core/ui/servicemanager.py" line="123"/> + <source>Save Service</source> <translation type="unfinished"></translation> </message> </context> <context> <name>Ui_AmendThemeDialog</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="591"/> - <source>Bold</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>TopicsForm</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/topicsform.py" line="50"/> - <source>Error</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>SlideController</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/slidecontroller.py" line="107"/> - <source>Preview</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_OpenLPExportDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/openlpexportdialog.py" line="303"/> - <source>Export</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>BibleMediaItem</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/bibles/lib/mediaitem.py" line="264"/> - <source>Keep</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_AmendThemeDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="639"/> - <source>Right</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_EditSongDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/editsongdialog.py" line="446"/> - <source>Comments</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>AboutForm</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/about.py" line="181"/> - <source>Credits</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>AlertForm</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/alertform.py" line="98"/> - <source>Cancel</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>AuthorsForm</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/authorsform.py" line="78"/> - <source>Error</source> + <location filename="openlp/core/ui/amendthemedialog.py" line="670"/> + <source>Shadow Size:</source> <translation type="unfinished"></translation> </message> </context> <context> <name>Ui_OpenSongExportDialog</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/opensongexportdialog.py" line="303"/> - <source>Export</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>MediaManagerItem</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/lib/mediamanageritem.py" line="237"/> - <source>Preview</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_SettingsDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/settingsdialog.py" line="57"/> - <source>Settings</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>BibleMediaItem</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/bibles/lib/mediaitem.py" line="597"/> - <source>Information</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_OpenLPExportDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/openlpexportdialog.py" line="293"/> - <source>Lyrics</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>ImageTab</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/images/lib/imagetab.py" line="34"/> - <source>Images</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_AmendThemeDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="578"/> - <source>Horizontal</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="580"/> - <source>Circular</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_EditSongDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/editsongdialog.py" line="434"/> - <source>Topic</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>BibleMediaItem</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/bibles/lib/mediaitem.py" line="209"/> - <source>Advanced</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>MediaTab</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/media/lib/mediatab.py" line="35"/> - <source>Media</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>AboutForm</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/about.py" line="183"/> + <location filename="openlp/plugins/songs/forms/opensongexportdialog.py" line="305"/> <source>Close</source> <translation type="unfinished"></translation> </message> </context> <context> - <name>Ui_EditSongDialog</name> + <name>ThemeManager</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/editsongdialog.py" line="423"/> - <source>Edit</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_OpenLPImportDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/openlpimportdialog.py" line="293"/> - <source>Lyrics</source> + <location filename="openlp/core/ui/thememanager.py" line="66"/> + <source>Import Theme</source> <translation type="unfinished"></translation> </message> </context> <context> <name>Ui_AmendThemeDialog</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="601"/> - <source>px</source> + <location filename="openlp/core/ui/amendthemedialog.py" line="683"/> + <source>Slide Transition</source> <translation type="unfinished"></translation> </message> </context> <context> - <name>Ui_EditSongDialog</name> + <name>ImportWizardForm</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/editsongdialog.py" line="425"/> - <source>Delete</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_OpenSongExportDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/opensongexportdialog.py" line="294"/> - <source>Title</source> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="166"/> + <source>Bible Exists</source> <translation type="unfinished"></translation> </message> </context> <context> <name>ThemesTab</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/themestab.py" line="37"/> - <source>Themes</source> + <location filename="openlp/core/ui/themestab.py" line="110"/> + <source>Theme level</source> <translation type="unfinished"></translation> </message> </context> <context> - <name>Ui_EditSongDialog</name> + <name>BibleMediaItem</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/editsongdialog.py" line="429"/> - <source>Authors</source> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="68"/> + <source>Bible</source> <translation type="unfinished"></translation> </message> </context> <context> - <name>Ui_BibleImportDialog</name> + <name>ServiceManager</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/bibles/forms/bibleimportdialog.py" line="251"/> - <source>KJV</source> + <location filename="openlp/core/ui/servicemanager.py" line="391"/> + <source>Save Changes to Service?</source> <translation type="unfinished"></translation> </message> </context> <context> - <name>Ui_SongMaintenanceDialog</name> + <name>SongUsagePlugin</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/songmaintenancedialog.py" line="211"/> - <source>Authors</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_AuditDetailDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/audit/forms/auditdetaildialog.py" line="164"/> - <source>Detailed</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_SongMaintenanceDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/songmaintenancedialog.py" line="216"/> - <source>Delete</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>SlideController</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/slidecontroller.py" line="175"/> - <source>s</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>SongMediaItem</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/lib/mediaitem.py" line="136"/> - <source>Titles</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/lib/mediaitem.py" line="132"/> - <source>Clear</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_BibleImportDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/bibles/forms/bibleimportdialog.py" line="248"/> - <source>Crosswalk</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_customEditDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/custom/forms/editcustomdialog.py" line="146"/> - <source>Edit</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>PresentationTab</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/presentations/lib/presentationtab.py" line="101"/> - <source>available</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>ThemeManager</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/thememanager.py" line="120"/> - <source>default</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>EditSongForm</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/editsongform.py" line="399"/> - <source>Error</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_SongMaintenanceDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/songmaintenancedialog.py" line="214"/> - <source>Add</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_BibleImportDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/bibles/forms/bibleimportdialog.py" line="235"/> - <source>Cancel</source> + <location filename="openlp/plugins/songusage/songusageplugin.py" line="65"/> + <source>&Delete recorded data</source> <translation type="unfinished"></translation> </message> </context> <context> <name>Ui_OpenLPExportDialog</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/openlpexportdialog.py" line="294"/> - <source>Title</source> + <location filename="openlp/plugins/songs/forms/openlpexportdialog.py" line="298"/> + <source>Song Title</source> <translation type="unfinished"></translation> </message> </context> <context> - <name>GeneralTab</name> + <name>Ui_customEditDialog</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/generaltab.py" line="37"/> - <source>General</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/generaltab.py" line="172"/> - <source>primary</source> + <location filename="openlp/plugins/custom/forms/editcustomdialog.py" line="156"/> + <source>Edit selected slide</source> <translation type="unfinished"></translation> </message> </context> <context> - <name>Ui_AmendThemeDialog</name> + <name>SongMediaItem</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="648"/> - <source>Preview</source> + <location filename="openlp/plugins/songs/lib/mediaitem.py" line="343"/> + <source>CCLI Licence: </source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_SongUsageDeleteDialog</name> + <message> + <location filename="openlp/plugins/songusage/forms/songusagedeletedialog.py" line="60"/> + <source>Audit Delete</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BibleMediaItem</name> + <message> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="299"/> + <source>Clear</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="310"/> + <source>Bible Import Wizard</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_customEditDialog</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomdialog.py" line="157"/> + <source>Edit All</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_ServiceNoteEdit</name> + <message> + <location filename="openlp/core/ui/serviceitemdialog.py" line="49"/> + <source>Service Item Notes</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMaintenanceForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenanceform.py" line="185"/> + <source>Couldn't save your author!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_customEditDialog</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomdialog.py" line="163"/> + <source>Clear</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemesTab</name> + <message> + <location filename="openlp/core/ui/themestab.py" line="109"/> + <source>Global theme</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongUsagePlugin</name> + <message> + <location filename="openlp/plugins/songusage/songusageplugin.py" line="84"/> + <source>Start/Stop live song usage recording</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="585"/> + <source>The Main Display has been blanked out</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenSongExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/opensongexportdialog.py" line="294"/> + <source>Lyrics</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AlertDialog</name> + <message> + <location filename="openlp/plugins/alerts/forms/alertdialog.py" line="70"/> + <source>Display</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_customEditDialog</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomdialog.py" line="161"/> + <source>Delete</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMaintenanceForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenanceform.py" line="229"/> + <source>This author can't be deleted, they are currently assigned to at least one song!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemeManager</name> + <message> + <location filename="openlp/core/ui/thememanager.py" line="57"/> + <source>Create a new theme</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="356"/> + <source>Open an existing service</source> <translation type="unfinished"></translation> </message> </context> <context> <name>SlideController</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/slidecontroller.py" line="105"/> - <source>Live</source> + <location filename="openlp/core/ui/slidecontroller.py" line="163"/> + <source>Move to previous</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/slidecontroller.py" line="186"/> + <source>Edit and re-preview Song</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_PluginViewDialog</name> + <message> + <location filename="openlp/core/ui/plugindialog.py" line="102"/> + <source>Plugin Details</source> <translation type="unfinished"></translation> </message> </context> <context> <name>AlertsTab</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/alertstab.py" line="157"/> - <source>Preview</source> + <location filename="openlp/plugins/alerts/forms/alertstab.py" line="230"/> + <source>pt</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/plugins/alerts/forms/alertstab.py" line="235"/> + <source>Edit History:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SlideController</name> + <message> + <location filename="openlp/core/ui/slidecontroller.py" line="201"/> + <source>Delay between slides in seconds</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMaintenanceForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenanceform.py" line="165"/> + <source>Couldn't add your book!</source> <translation type="unfinished"></translation> </message> </context> <context> <name>BiblesTab</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/bibles/lib/biblestab.py" line="159"/> - <source>continuous</source> + <location filename="openlp/plugins/bibles/lib/biblestab.py" line="158"/> + <source>verse per line</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_customEditDialog</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomdialog.py" line="165"/> + <source>Theme:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMaintenanceForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenanceform.py" line="216"/> + <source>Error</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="333"/> + <source>Bible:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="121"/> + <source>You need to specify a file with books of the Bible to use in the import!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemeManager</name> + <message> + <location filename="openlp/core/ui/thememanager.py" line="62"/> + <source>Delete Theme</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SplashScreen</name> + <message> + <location filename="openlp/core/ui/splashscreen.py" line="61"/> + <source>Splash Screen</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMediaItem</name> + <message> + <location filename="openlp/plugins/songs/lib/mediaitem.py" line="62"/> + <source>Song</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongUsageDeleteForm</name> + <message> + <location filename="openlp/plugins/songusage/forms/songusagedeleteform.py" line="44"/> + <source>Delete Selected Audit Events?</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenSongExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/opensongexportdialog.py" line="298"/> + <source>Song Title</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BibleMediaItem</name> + <message> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="294"/> + <source>Search</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="399"/> + <source>List the Plugins</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMaintenanceForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenanceform.py" line="231"/> + <source>No author selected!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongUsagePlugin</name> + <message> + <location filename="openlp/plugins/songusage/songusageplugin.py" line="154"/> + <source><b>SongUsage Plugin</b><br>This plugin records the use of songs and when they have been used during a live service</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_customEditDialog</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomdialog.py" line="149"/> + <source>Move slide Up 1</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongsPlugin</name> + <message> + <location filename="openlp/plugins/songs/songsplugin.py" line="156"/> + <source>OpenSong</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AlertsManager</name> + <message> + <location filename="openlp/plugins/alerts/lib/alertsmanager.py" line="83"/> + <source>Alert message created and delayed</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="420"/> + <source>Alternative Title:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="526"/> + <source>Open Service</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BiblesTab</name> + <message> + <location filename="openlp/plugins/bibles/lib/biblestab.py" line="155"/> + <source>Display Style:</source> <translation type="unfinished"></translation> </message> </context> <context> <name>Ui_AmendThemeDialog</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="569"/> + <location filename="openlp/core/ui/amendthemedialog.py" line="606"/> + <source>Image</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>EditSongForm</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongform.py" line="403"/> + <source>You need to enter a song title.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemeManager</name> + <message> + <location filename="openlp/core/ui/thememanager.py" line="361"/> + <source>Error</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="111"/> + <source>Invalid Bible Location</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemesTab</name> + <message> + <location filename="openlp/core/ui/themestab.py" line="120"/> + <source>Global level</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemeManager</name> + <message> + <location filename="openlp/core/ui/thememanager.py" line="91"/> + <source>Make Global</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="387"/> + <source>&Service Manager</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpimportdialog.py" line="299"/> + <source>Author</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="656"/> + <source>Height:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="327"/> + <source>Books Location:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemeManager</name> + <message> + <location filename="openlp/core/ui/thememanager.py" line="63"/> + <source>Delete a theme</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="331"/> + <source>Crosswalk</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongBookForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songbookform.py" line="51"/> + <source>Error</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AuthorsDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/authorsdialog.py" line="79"/> + <source>Last name:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_customEditDialog</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomdialog.py" line="152"/> + <source>Title:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="157"/> + <source>You need to set a copyright for your Bible! Bibles in the Public Domain need to be marked as such.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMediaItem</name> + <message> + <location filename="openlp/plugins/songs/lib/mediaitem.py" line="72"/> + <source>Maintain the lists of authors, topics and books</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AlertEditDialog</name> + <message> + <location filename="openlp/plugins/alerts/forms/alerteditdialog.py" line="62"/> + <source>Save</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>EditCustomForm</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomform.py" line="259"/> + <source>You have unsaved data</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BibleMediaItem</name> + <message> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="292"/> + <source>To:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="664"/> + <source>Outline</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BibleMediaItem</name> + <message> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="296"/> + <source>Text Search</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpexportdialog.py" line="288"/> + <source>openlp.org Song Exporter</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongUsagePlugin</name> + <message> + <location filename="openlp/plugins/songusage/songusageplugin.py" line="67"/> + <source>Delete song usage to specified date</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_SongUsageDetailDialog</name> + <message> + <location filename="openlp/plugins/songusage/forms/songusagedetaildialog.py" line="91"/> + <source>Report Location</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="324"/> + <source>OpenSong</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="355"/> + <source>Open Service</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMediaItem</name> + <message> + <location filename="openlp/plugins/songs/lib/mediaitem.py" line="146"/> + <source>Titles</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImageMediaItem</name> + <message> + <location filename="openlp/plugins/images/lib/mediaitem.py" line="62"/> + <source>Select Image(s)</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BibleMediaItem</name> + <message> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="282"/> + <source>Search Type:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="345"/> + <source>Media Manager</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImageMediaItem</name> + <message> + <location filename="openlp/plugins/images/lib/mediaitem.py" line="64"/> + <source>Images (*.jpg *jpeg *.gif *.png *.bmp);; All files (*)</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="370"/> + <source>Alt+F4</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MediaManagerItem</name> + <message> + <location filename="openlp/core/lib/mediamanageritem.py" line="278"/> + <source>&Preview</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>GeneralTab</name> + <message> + <location filename="openlp/core/ui/generaltab.py" line="163"/> + <source>CCLI Details</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/generaltab.py" line="166"/> + <source>SongSelect Password:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="396"/> + <source>Toggle the visibility of the Preview Panel</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMaintenanceForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenanceform.py" line="254"/> + <source>Are you sure you want to delete the selected book?</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="401"/> + <source>&User Guide</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongUsageDeleteForm</name> + <message> + <location filename="openlp/plugins/songusage/forms/songusagedeleteform.py" line="45"/> + <source>Are you sure you want to delete selected Audit Data?</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="413"/> + <source>Set the interface language to English</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="617"/> + <source>Main Font</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="156"/> + <source>Empty Copyright</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>CustomPlugin</name> + <message> + <location filename="openlp/plugins/custom/customplugin.py" line="70"/> + <source><b>Custom Plugin</b><br>This plugin allows slides to be displayed on the screen in the same way songs are. This plugin provides greater freedom over the songs plugin.<br></source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AuthorsForm</name> + <message> + <location filename="openlp/plugins/songs/forms/authorsform.py" line="80"/> + <source>You need to type in the first name of the author.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongsTab</name> + <message> + <location filename="openlp/plugins/songs/lib/songstab.py" line="68"/> + <source>Display Verses on Live Tool bar:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="162"/> + <source>Move to top</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImageMediaItem</name> + <message> + <location filename="openlp/plugins/images/lib/mediaitem.py" line="102"/> + <source>Override background</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_SongMaintenanceDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenancedialog.py" line="222"/> + <source>Edit</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenSongExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/opensongexportdialog.py" line="300"/> + <source>Select All</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemesTab</name> + <message> + <location filename="openlp/core/ui/themestab.py" line="112"/> + <source>Use the theme from each song in the database. If a song doesn't have a theme associated with it, then use the service's theme. If the service doesn't have a theme, then use the global theme.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>PresentationMediaItem</name> + <message> + <location filename="openlp/plugins/presentations/lib/mediaitem.py" line="62"/> + <source>Presentation</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="604"/> + <source>Solid Color</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>CustomTab</name> + <message> + <location filename="openlp/plugins/custom/lib/customtab.py" line="39"/> + <source>Custom</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpimportdialog.py" line="303"/> + <source>Ready to import</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="549"/> + <source>OpenLP version %s has been updated to version %s + +You can obtain the latest version from http://openlp.org</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="326"/> + <source>File Location:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SlideController</name> + <message> + <location filename="openlp/core/ui/slidecontroller.py" line="226"/> + <source>Go to Verse</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="335"/> + <source>&Import</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="369"/> + <source>Quit OpenLP</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="315"/> + <source>This wizard will help you to import Bibles from a variety of formats. Click the next button below to start the process by selecting a format to import from.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpexportdialog.py" line="295"/> + <source>Title</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="148"/> + <source>Empty Version Name</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="393"/> + <source>&Preview Panel</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SlideController</name> + <message> + <location filename="openlp/core/ui/slidecontroller.py" line="191"/> + <source>Start continuous loop</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AboutDialog</name> + <message> + <location filename="openlp/core/ui/aboutdialog.py" line="548"/> + <source>License</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>GeneralTab</name> + <message> + <location filename="openlp/core/ui/generaltab.py" line="199"/> + <source>primary</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="443"/> + <source>Add a Theme</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="350"/> + <source>&New</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_customEditDialog</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomdialog.py" line="167"/> + <source>Credits:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SlideController</name> + <message> + <location filename="openlp/core/ui/slidecontroller.py" line="111"/> + <source>Live</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="129"/> + <source>You need to specify a file of Bible verses to import!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BiblesTab</name> + <message> + <location filename="openlp/plugins/bibles/lib/biblestab.py" line="159"/> + <source>continuous</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditVerseDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editversedialog.py" line="121"/> + <source>Number</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>GeneralTab</name> + <message> + <location filename="openlp/core/ui/generaltab.py" line="156"/> + <source>Application Startup</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="652"/> + <source>Use Default Location:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenSongImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/opensongimportdialog.py" line="107"/> + <source>Import</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="353"/> + <source>Ctrl+N</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="422"/> + <source>Verse Order:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_SongUsageDetailDialog</name> + <message> + <location filename="openlp/plugins/songusage/forms/songusagedetaildialog.py" line="89"/> + <source>ASelect Date Range</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="331"/> + <source>Default Theme: </source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="394"/> + <source>Toggle Preview Panel</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMediaItem</name> + <message> + <location filename="openlp/plugins/songs/lib/mediaitem.py" line="147"/> + <source>Lyrics</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpimportdialog.py" line="302"/> + <source>Progress:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="669"/> + <source>Shadow</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>GeneralTab</name> + <message> + <location filename="openlp/core/ui/generaltab.py" line="155"/> + <source>Select monitor for output display:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="648"/> + <source>Italics</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="118"/> + <source>Create a new service</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="600"/> + <source>Background:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpimportdialog.py" line="288"/> + <source>openlp.org Song Importer</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="347"/> + <source>Copyright:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemesTab</name> + <message> + <location filename="openlp/core/ui/themestab.py" line="116"/> + <source>Service level</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BiblesTab</name> + <message> + <location filename="openlp/plugins/bibles/lib/biblestab.py" line="163"/> + <source>[ and ]</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_customEditDialog</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomdialog.py" line="159"/> + <source>Save</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MediaManagerItem</name> + <message> + <location filename="openlp/core/lib/mediamanageritem.py" line="381"/> + <source>You must select one or more items</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>GeneralTab</name> + <message> + <location filename="openlp/core/ui/generaltab.py" line="160"/> + <source>Application Settings</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="124"/> + <source>Save this service</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="187"/> + <source>Open Books CSV file</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>GeneralTab</name> + <message> + <location filename="openlp/core/ui/generaltab.py" line="165"/> + <source>SongSelect Username:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="653"/> + <source>X Position:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BibleMediaItem</name> + <message> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="353"/> + <source>No matching book could be found in this Bible.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="337"/> + <source>Server:</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="336"/> + <source>Download Options</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="137"/> + <source>Invalid OpenSong Bible</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>GeneralTab</name> + <message> + <location filename="openlp/core/ui/generaltab.py" line="164"/> + <source>CCLI Number:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="678"/> + <source>Center</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="126"/> + <source>Theme:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="418"/> + <source>&Live</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMaintenanceForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenanceform.py" line="240"/> + <source>Delete Topic</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="411"/> + <source>English</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImageMediaItem</name> + <message> + <location filename="openlp/plugins/images/lib/mediaitem.py" line="169"/> + <source>You must select one or more items</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AuthorsDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/authorsdialog.py" line="78"/> + <source>First name:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="348"/> + <source>Permission:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenSongImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/opensongimportdialog.py" line="108"/> + <source>Close</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="601"/> + <source>Opaque</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMaintenanceForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenanceform.py" line="255"/> + <source>This book can't be deleted, it is currently assigned to at least one song!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="367"/> + <source>Your Bible import failed.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SlideController</name> + <message> + <location filename="openlp/core/ui/slidecontroller.py" line="213"/> + <source>Start playing media</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMediaItem</name> + <message> + <location filename="openlp/plugins/songs/lib/mediaitem.py" line="141"/> + <source>Type:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AboutDialog</name> + <message> + <location filename="openlp/core/ui/aboutdialog.py" line="550"/> + <source>Close</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>TopicsForm</name> + <message> + <location filename="openlp/plugins/songs/forms/topicsform.py" line="51"/> + <source>You need to type in a topic name!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenSongExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/opensongexportdialog.py" line="297"/> + <source>Song Export List</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BibleMediaItem</name> + <message> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="287"/> + <source>Dual:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImageTab</name> + <message> + <location filename="openlp/plugins/images/lib/imagetab.py" line="68"/> + <source>sec</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="175"/> + <source>Delete From Service</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>GeneralTab</name> + <message> + <location filename="openlp/core/ui/generaltab.py" line="158"/> + <source>Automatically open the last service</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpimportdialog.py" line="297"/> + <source>Song Import List</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenSongExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/opensongexportdialog.py" line="299"/> + <source>Author</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="667"/> + <source>Outline Color:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="318"/> + <source>Select Import Source</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="392"/> + <source>F9</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="381"/> + <source>F8</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="213"/> + <source>&Change Item Theme</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_SongMaintenanceDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenancedialog.py" line="213"/> + <source>Topics</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpimportdialog.py" line="290"/> + <source>Import File Song List</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_customEditDialog</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomdialog.py" line="151"/> + <source>Edit Custom Slides</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="432"/> + <source>&Remove</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="345"/> + <source>Set up the Bible's license details.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="674"/> + <source>Alignment</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMaintenanceForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenanceform.py" line="253"/> + <source>Delete Book</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemeManager</name> + <message> + <location filename="openlp/core/ui/thememanager.py" line="80"/> + <source>Edit a theme</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="332"/> + <source>BibleGateway</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>GeneralTab</name> + <message> + <location filename="openlp/core/ui/generaltab.py" line="162"/> + <source>Preview Next Song from Service Manager</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="429"/> + <source>Title && Lyrics</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMaintenanceForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenanceform.py" line="257"/> + <source>No book selected!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SlideController</name> + <message> + <location filename="openlp/core/ui/slidecontroller.py" line="182"/> + <source>Move to live</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditVerseDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editversedialog.py" line="127"/> + <source>Other</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="442"/> + <source>Theme</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditVerseDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editversedialog.py" line="123"/> + <source>Verse</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="361"/> + <source>Save the current service to disk</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BibleMediaItem</name> + <message> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="289"/> + <source>Chapter:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="682"/> + <source>Bottom</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>PresentationTab</name> + <message> + <location filename="openlp/plugins/presentations/lib/presentationtab.py" line="91"/> + <source>Available Controllers</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="191"/> + <source>Open Verses CSV file</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>TopicsForm</name> + <message> + <location filename="openlp/plugins/songs/forms/topicsform.py" line="50"/> + <source>Error</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>RemoteTab</name> + <message> + <location filename="openlp/plugins/remotes/lib/remotetab.py" line="56"/> + <source>Remotes Receiver Port</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="338"/> + <source>&View</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="646"/> + <source>Normal</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpexportdialog.py" line="305"/> + <source>Close</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="338"/> + <source>Username:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemeManager</name> + <message> + <location filename="openlp/core/ui/thememanager.py" line="59"/> + <source>Edit Theme</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="207"/> + <source>&Preview Verse</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AlertDialog</name> + <message> + <location filename="openlp/plugins/alerts/forms/alertdialog.py" line="68"/> + <source>Alert Message</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="364"/> + <source>Finished import.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>GeneralTab</name> + <message> + <location filename="openlp/core/ui/generaltab.py" line="157"/> + <source>Show blank screen warning</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AlertsTab</name> + <message> + <location filename="openlp/plugins/alerts/forms/alertstab.py" line="233"/> + <source>Location:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="441"/> + <source>Authors, Topics && Book</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>EditSongForm</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongform.py" line="407"/> + <source>You need to enter some verses.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BibleMediaItem</name> + <message> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="543"/> + <source>Bible not fully loaded</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>CustomTab</name> + <message> + <location filename="openlp/plugins/custom/lib/customtab.py" line="60"/> + <source>Display Footer:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BiblePlugin</name> + <message> + <location filename="openlp/plugins/bibles/bibleplugin.py" line="91"/> + <source><strong>Bible Plugin</strong><br />This plugin allows bible verses from different sources to be displayed on the screen during the service.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="444"/> + <source>Copyright Information</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="336"/> + <source>&Export</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="647"/> + <source>Bold</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongsPlugin</name> + <message> + <location filename="openlp/plugins/songs/songsplugin.py" line="122"/> + <source>Export songs in OpenLP 2.0 format</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MediaManagerItem</name> + <message> + <location filename="openlp/core/lib/mediamanageritem.py" line="214"/> + <source>Load a new</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AlertEditForm</name> + <message> + <location filename="openlp/plugins/alerts/forms/alerteditform.py" line="128"/> + <source>Missing data</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongsPlugin</name> + <message> + <location filename="openlp/plugins/songs/songsplugin.py" line="179"/> + <source><b>Song Plugin</b> <br>This plugin allows Songs to be managed and displayed.<br></source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="641"/> + <source>Footer Font</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>EditSongForm</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongform.py" line="420"/> + <source>Invalid verse entry - vX</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BibleMediaItem</name> + <message> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="352"/> + <source>No Book Found</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpexportdialog.py" line="304"/> + <source>Export</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="330"/> + <source>Location:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BibleMediaItem</name> + <message> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="300"/> + <source>Keep</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongUsagePlugin</name> + <message> + <location filename="openlp/plugins/songusage/songusageplugin.py" line="74"/> + <source>Generate report on Song Usage</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="435"/> + <source>Topic</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="354"/> + <source>&Open</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>PresentationMediaItem</name> + <message> + <location filename="openlp/plugins/presentations/lib/mediaitem.py" line="99"/> + <source>Present using:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="209"/> + <source>&Live Verse</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditVerseDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editversedialog.py" line="125"/> + <source>Pre-Chorus</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="421"/> + <source>Lyrics:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AboutDialog</name> + <message> + <location filename="openlp/core/ui/aboutdialog.py" line="135"/> + <source>Project Lead + Raoul "superfly" Snyman + +Developers + Tim "TRB143" Bentley + Jonathan "gushie" Corwin + Michael "cocooncrash" Gorven + Scott "sguerrieri" Guerrieri + Raoul "superfly" Snyman + Maikel Stuivenberg + Martin "mijiti" Thompson + Jon "Meths" Tibble + Carsten "catini" Tingaard + +Testers + Wesley "wrst" Stout</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpexportdialog.py" line="294"/> + <source>Lyrics</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AuthorsForm</name> + <message> + <location filename="openlp/plugins/songs/forms/authorsform.py" line="94"/> + <source>You haven't set a display name for the author, would you like me to combine the first and last names for you?</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMediaItem</name> + <message> + <location filename="openlp/plugins/songs/lib/mediaitem.py" line="142"/> + <source>Clear</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AmendThemeForm</name> + <message> + <location filename="openlp/core/ui/amendthemeform.py" line="708"/> + <source>Slide Height is %s rows</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenSongImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/opensongimportdialog.py" line="105"/> + <source>Progress:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="383"/> + <source>Toggle Theme Manager</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AlertDialog</name> + <message> + <location filename="openlp/plugins/alerts/forms/alertdialog.py" line="69"/> + <source>Alert Text:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="424"/> + <source>Edit</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AlertsTab</name> + <message> + <location filename="openlp/plugins/alerts/forms/alertstab.py" line="227"/> + <source>Font Color:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="598"/> + <source>Theme Maintenance</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>CustomTab</name> + <message> + <location filename="openlp/plugins/custom/lib/customtab.py" line="58"/> + <source>Custom Display</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenSongExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/opensongexportdialog.py" line="295"/> + <source>Title</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="607"/> + <source><Color1></source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="430"/> + <source>Authors</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemeManager</name> + <message> + <location filename="openlp/core/ui/thememanager.py" line="69"/> + <source>Export Theme</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImageMediaItem</name> + <message> + <location filename="openlp/plugins/images/lib/mediaitem.py" line="168"/> + <source>No items selected...</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_SongBookDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/songbookdialog.py" line="69"/> + <source>Name:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AuthorsDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/authorsdialog.py" line="76"/> + <source>Author Maintenance</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="663"/> + <source>Font Footer</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="376"/> + <source>&Settings</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="337"/> + <source>&Options</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BibleMediaItem</name> + <message> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="293"/> + <source>Results:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpexportdialog.py" line="290"/> + <source>Full Song List</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenSongImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/opensongimportdialog.py" line="104"/> + <source>OpenSong Folder:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SlideController</name> + <message> + <location filename="openlp/core/ui/slidecontroller.py" line="170"/> + <source>Move to last</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpexportdialog.py" line="302"/> + <source>Progress:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_SongMaintenanceDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenancedialog.py" line="221"/> + <source>Add</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMaintenanceForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenanceform.py" line="228"/> + <source>Are you sure you want to delete the selected author?</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongUsagePlugin</name> + <message> + <location filename="openlp/plugins/songusage/songusageplugin.py" line="82"/> + <source>Song Usage Status</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BibleMediaItem</name> + <message> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="295"/> + <source>Verse Search</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_SongBookDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/songbookdialog.py" line="68"/> + <source>Edit Book</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>EditSongForm</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongform.py" line="460"/> + <source>Save && Preview</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_SongBookDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/songbookdialog.py" line="70"/> + <source>Publisher:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="650"/> + <source>Font Weight:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="329"/> + <source>Bible Filename:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="602"/> <source>Transparent</source> <translation type="unfinished"></translation> </message> @@ -884,84 +2301,63 @@ <context> <name>SongMediaItem</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/lib/mediaitem.py" line="133"/> + <location filename="openlp/plugins/songs/lib/mediaitem.py" line="143"/> <source>Search</source> <translation type="unfinished"></translation> </message> </context> <context> - <name>Ui_OpenSongImportDialog</name> + <name>Ui_BibleImportWizard</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/opensongimportdialog.py" line="106"/> - <source>Import</source> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="321"/> + <source>Format:</source> <translation type="unfinished"></translation> </message> </context> <context> <name>Ui_AmendThemeDialog</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="583"/> + <location filename="openlp/core/ui/amendthemedialog.py" line="616"/> <source>Background</source> <translation type="unfinished"></translation> </message> </context> <context> - <name>Ui_EditSongDialog</name> + <name>Ui_BibleImportWizard</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/editsongdialog.py" line="422"/> - <source>Add</source> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="349"/> + <source>Importing</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_customEditDialog</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomdialog.py" line="158"/> + <source>Edit all slides</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MediaMediaItem</name> + <message> + <location filename="openlp/plugins/media/lib/mediaitem.py" line="63"/> + <source>Select Media</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>PresentationMediaItem</name> + <message> + <location filename="openlp/plugins/presentations/lib/mediaitem.py" line="65"/> + <source>Select Presentation(s)</source> <translation type="unfinished"></translation> </message> </context> <context> <name>SongMediaItem</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/lib/mediaitem.py" line="137"/> - <source>Lyrics</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>MediaManagerItem</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/lib/mediamanageritem.py" line="229"/> - <source>Delete</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_MainWindow</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/mainwindow.py" line="377"/> - <source>F11</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/mainwindow.py" line="366"/> - <source>F10</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_AuditDetailDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/audit/forms/auditdetaildialog.py" line="167"/> - <source>to</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_MainWindow</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/mainwindow.py" line="347"/> - <source>F12</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>SongMediaItem</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/lib/mediaitem.py" line="138"/> + <location filename="openlp/plugins/songs/lib/mediaitem.py" line="148"/> <source>Authors</source> <translation type="unfinished"></translation> </message> @@ -969,23 +2365,2479 @@ <context> <name>Ui_PluginViewDialog</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/plugindialog.py" line="107"/> + <location filename="openlp/core/ui/plugindialog.py" line="107"/> <source>Active</source> <translation type="unfinished"></translation> </message> </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="366"/> + <source>Save the current service under a new name</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="357"/> + <source>Ctrl+O</source> + <translation type="unfinished"></translation> + </message> + </context> <context> <name>Ui_AmendThemeDialog</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="592"/> - <source>Italics</source> + <location filename="openlp/core/ui/amendthemedialog.py" line="687"/> + <source>Other Options</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMaintenanceForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenanceform.py" line="142"/> + <source>Couldn't add your author!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AlertEditDialog</name> + <message> + <location filename="openlp/plugins/alerts/forms/alerteditdialog.py" line="65"/> + <source>Edit</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="418"/> + <source>Song Editor</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AlertsTab</name> + <message> + <location filename="openlp/plugins/alerts/forms/alertstab.py" line="225"/> + <source>Font</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongsPlugin</name> + <message> + <location filename="openlp/plugins/songs/songsplugin.py" line="155"/> + <source>&Song</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="334"/> + <source>&File</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MediaManagerItem</name> + <message> + <location filename="openlp/core/lib/mediamanageritem.py" line="272"/> + <source>&Edit</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="612"/> + <source>Vertical</source> <translation type="unfinished"></translation> </message> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="572"/> + <location filename="openlp/core/ui/amendthemedialog.py" line="655"/> + <source>Width:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemeManager</name> + <message> + <location filename="openlp/core/ui/thememanager.py" line="180"/> + <source>You are unable to delete the default theme!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemesTab</name> + <message> + <location filename="openlp/core/ui/themestab.py" line="121"/> + <source>Use the global theme, overriding any themes associated with either the service or the songs.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BibleMediaItem</name> + <message> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="286"/> + <source>Version:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AboutDialog</name> + <message> + <location filename="openlp/core/ui/aboutdialog.py" line="117"/> + <source>OpenLP <version> build <revision> - Open Source Lyrics Projection + +OpenLP is free church presentation software, or lyrics projection software, used to display slides of songs, Bible verses, videos, images, and even presentations (if OpenOffice.org, PowerPoint or PowerPoint Viewer is installed) for church worship using a computer and a data projector. + +Find out more about OpenLP: http://openlp.org/ + +OpenLP is written and maintained by volunteers. If you would like to see more free Christian software being written, please consider contributing by using the button below.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongsPlugin</name> + <message> + <location filename="openlp/plugins/songs/songsplugin.py" line="158"/> + <source>OpenLP 2.0</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="117"/> + <source>New Service</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_TopicsDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/topicsdialog.py" line="63"/> + <source>Topic name:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemeManager</name> + <message> + <location filename="openlp/core/ui/thememanager.py" line="362"/> + <source>File is not a valid theme!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="343"/> + <source>License Details</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="167"/> + <source>Move down</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="437"/> + <source>R&emove</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="681"/> + <source>Middle</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="328"/> + <source>Verse Location:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AlertEditForm</name> + <message> + <location filename="openlp/plugins/alerts/forms/alerteditform.py" line="87"/> + <source>Item selected to Edit</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BibleMediaItem</name> + <message> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="291"/> + <source>From:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="672"/> + <source>Shadow Color:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="203"/> + <source>&Notes</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="368"/> + <source>E&xit</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpimportdialog.py" line="305"/> + <source>Close</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="552"/> + <source>OpenLP Version Updated</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_customEditDialog</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomdialog.py" line="160"/> + <source>Replace edited slide</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/plugins/custom/forms/editcustomdialog.py" line="154"/> + <source>Add new slide at bottom</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>EditCustomForm</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomform.py" line="252"/> + <source>You need to enter a title</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemeManager</name> + <message> + <location filename="openlp/core/ui/thememanager.py" line="444"/> + <source>Theme Exists</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="343"/> + <source>&Help</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenSongExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/opensongexportdialog.py" line="288"/> + <source>OpenSong Song Exporter</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="679"/> + <source>Vertical Align:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>TestMediaManager</name> + <message> + <location filename="openlp/core/test/test_mediamanageritem.py" line="74"/> + <source>Item2</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/test/test_mediamanageritem.py" line="72"/> + <source>Item1</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="680"/> + <source>Top</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BiblesTab</name> + <message> + <location filename="openlp/plugins/bibles/lib/biblestab.py" line="166"/> + <source>Display Dual Bible Verses</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="389"/> + <source>Toggle Service Manager</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MediaManagerItem</name> + <message> + <location filename="openlp/core/lib/mediamanageritem.py" line="287"/> + <source>&Add to Service</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AmendThemeForm</name> + <message> + <location filename="openlp/core/ui/amendthemeform.py" line="639"/> + <source>First Color:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemesTab</name> + <message> + <location filename="openlp/core/ui/themestab.py" line="111"/> + <source>Song level</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>alertsPlugin</name> + <message> + <location filename="openlp/plugins/alerts/alertsplugin.py" line="68"/> + <source>Show an alert message</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="405"/> + <source>Ctrl+F1</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMaintenanceForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenanceform.py" line="200"/> + <source>Couldn't save your topic!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpexportdialog.py" line="301"/> + <source>Remove Selected</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemeManager</name> + <message> + <location filename="openlp/core/ui/thememanager.py" line="86"/> + <source>Delete theme</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImageTab</name> + <message> + <location filename="openlp/plugins/images/lib/imagetab.py" line="66"/> + <source>Image Settings</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenSongImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/opensongimportdialog.py" line="103"/> + <source>OpenSong Song Importer</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BiblesTab</name> + <message> + <location filename="openlp/plugins/bibles/lib/biblestab.py" line="48"/> + <source>Bibles</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongUsagePlugin</name> + <message> + <location filename="openlp/plugins/songusage/songusageplugin.py" line="72"/> + <source>&Extract recorded data</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AlertsTab</name> + <message> + <location filename="openlp/plugins/alerts/forms/alertstab.py" line="226"/> + <source>Font Name:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="407"/> + <source>&Web Site</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MediaManagerItem</name> + <message> + <location filename="openlp/core/lib/mediamanageritem.py" line="245"/> + <source>Send the selected item live</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="339"/> + <source>M&ode</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="410"/> + <source>Translate the interface to your language</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="347"/> + <source>Service Manager</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>CustomMediaItem</name> + <message> + <location filename="openlp/plugins/custom/lib/mediaitem.py" line="70"/> + <source>Custom</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="322"/> + <source>OSIS</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongsPlugin</name> + <message> + <location filename="openlp/plugins/songs/songsplugin.py" line="157"/> + <source>openlp.org 1.0</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="373"/> + <source>&Theme</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditVerseDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editversedialog.py" line="112"/> + <source>Edit Verse</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="374"/> + <source>&Language</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SlideController</name> + <message> + <location filename="openlp/core/ui/slidecontroller.py" line="450"/> + <source>Verse</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="138"/> + <source>You need to specify an OpenSong Bible file to import!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="392"/> + <source>Your service is unsaved, do you want to save those changes before creating a new one ?</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenSongExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/opensongexportdialog.py" line="301"/> + <source>Remove Selected</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMediaItem</name> + <message> + <location filename="openlp/plugins/songs/lib/mediaitem.py" line="140"/> + <source>Search:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="626"/> + <source>Save Changes to Service?</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="627"/> + <source>Your service has changed, do you want to save those changes?</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>EditSongForm</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongform.py" line="428"/> + <source>Invalid verse entry - values must be Numeric, I,B,C,T,P,E,O</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="431"/> + <source>&Add to Song</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="402"/> + <source>&About</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BiblesTab</name> + <message> + <location filename="openlp/plugins/bibles/lib/biblestab.py" line="153"/> + <source>Only show new chapter numbers</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="149"/> + <source>You need to specify a version name for your Bible!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AlertEditDialog</name> + <message> + <location filename="openlp/plugins/alerts/forms/alerteditdialog.py" line="66"/> + <source>Delete</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>EditCustomForm</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomform.py" line="145"/> + <source>Error</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>RemotesPlugin</name> + <message> + <location filename="openlp/plugins/remotes/remoteplugin.py" line="81"/> + <source><b>Remote Plugin</b><br>This plugin provides the ability to send messages to a running version of openlp on a different computer.<br>The Primary use for this would be to send alerts from a creche</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMaintenanceForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenanceform.py" line="242"/> + <source>This topic can't be deleted, it is currently assigned to at least one song!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BibleMediaItem</name> + <message> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="283"/> + <source>Find:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AlertEditForm</name> + <message> + <location filename="openlp/plugins/alerts/forms/alerteditform.py" line="127"/> + <source>Item selected to Add</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="677"/> + <source>Right</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemeManager</name> + <message> + <location filename="openlp/core/ui/thememanager.py" line="224"/> + <source>Save Theme - (%s)</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImageMediaItem</name> + <message> + <location filename="openlp/plugins/images/lib/mediaitem.py" line="104"/> + <source>Allow background of live slide to be overridden</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MediaManagerItem</name> + <message> + <location filename="openlp/core/lib/mediamanageritem.py" line="250"/> + <source>Add the selected item(s) to the service</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AuthorsForm</name> + <message> + <location filename="openlp/plugins/songs/forms/authorsform.py" line="93"/> + <source>Error</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BibleMediaItem</name> + <message> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="288"/> + <source>Book:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="643"/> + <source>Font Color:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpexportdialog.py" line="289"/> + <source>Select openlp.org export filename:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_SettingsDialog</name> + <message> + <location filename="openlp/core/ui/settingsdialog.py" line="62"/> + <source>Settings</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BiblesTab</name> + <message> + <location filename="openlp/plugins/bibles/lib/biblestab.py" line="152"/> + <source>Verse Display</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MediaManagerItem</name> + <message> + <location filename="openlp/core/lib/mediamanageritem.py" line="226"/> + <source>Edit the selected</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SlideController</name> + <message> + <location filename="openlp/core/ui/slidecontroller.py" line="166"/> + <source>Move to next</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="398"/> + <source>&Plugin List</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BiblePlugin</name> + <message> + <location filename="openlp/plugins/bibles/bibleplugin.py" line="83"/> + <source>&Bible</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="325"/> + <source>Web Download</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="611"/> + <source>Horizontal</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="183"/> + <source>Open OSIS file</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMaintenanceForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenanceform.py" line="217"/> + <source>Couldn't save your book!</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenanceform.py" line="153"/> + <source>Couldn't add your topic!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="645"/> + <source>pt</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="414"/> + <source>&Add Tool...</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="608"/> + <source><Color2></source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="164"/> + <source>Move up</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpimportdialog.py" line="294"/> + <source>Lyrics</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BiblesTab</name> + <message> + <location filename="openlp/plugins/bibles/lib/biblestab.py" line="160"/> + <source>No brackets</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AlertEditDialog</name> + <message> + <location filename="openlp/plugins/alerts/forms/alerteditdialog.py" line="61"/> + <source>Maintain Alerts</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="671"/> + <source>px</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="131"/> + <source>Select a theme for the service</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemesTab</name> + <message> + <location filename="openlp/core/ui/themestab.py" line="40"/> + <source>Themes</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="170"/> + <source>Move to bottom</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_PluginViewDialog</name> + <message> + <location filename="openlp/core/ui/plugindialog.py" line="106"/> + <source>Status:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="446"/> + <source>CCLI Number:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="167"/> + <source>This Bible already exists! Please import a different Bible or first delete the existing one.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="408"/> + <source>&Translate</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AlertEditForm</name> + <message> + <location filename="openlp/plugins/alerts/forms/alerteditform.py" line="88"/> + <source>Please Save or Clear seletced item</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="364"/> + <source>Save Service As</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_SongMaintenanceDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenancedialog.py" line="212"/> + <source>Authors</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongUsageDetailForm</name> + <message> + <location filename="openlp/plugins/songusage/forms/songusagedetailform.py" line="59"/> + <source>Output File Location</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BiblesTab</name> + <message> + <location filename="openlp/plugins/bibles/lib/biblestab.py" line="162"/> + <source>{ and }</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>GeneralTab</name> + <message> + <location filename="openlp/core/ui/generaltab.py" line="161"/> + <source>Prompt to save Service before starting New</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="313"/> + <source>Starting import...</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BiblesTab</name> + <message> + <location filename="openlp/plugins/bibles/lib/biblestab.py" line="165"/> + <source>Note: +Changes don't affect verses already in the service</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditVerseDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editversedialog.py" line="126"/> + <source>Intro</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="165"/> + <source>Move up order</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>PresentationTab</name> + <message> + <location filename="openlp/plugins/presentations/lib/presentationtab.py" line="96"/> + <source>available</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemeManager</name> + <message> + <location filename="openlp/core/ui/thememanager.py" line="275"/> + <source>default</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMaintenanceForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenanceform.py" line="227"/> + <source>Delete Author</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="651"/> + <source>Display Location</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_PluginViewDialog</name> + <message> + <location filename="openlp/core/ui/plugindialog.py" line="103"/> + <source>Version:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AlertEditDialog</name> + <message> + <location filename="openlp/plugins/alerts/forms/alerteditdialog.py" line="64"/> + <source>Add</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>GeneralTab</name> + <message> + <location filename="openlp/core/ui/generaltab.py" line="40"/> + <source>General</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="654"/> + <source>Y Position:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="168"/> + <source>Move down order</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BiblesTab</name> + <message> + <location filename="openlp/plugins/bibles/lib/biblestab.py" line="157"/> + <source>verse per slide</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="313"/> + <source>Welcome to the Bible Import Wizard</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="673"/> + <source>Show Shadow:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AlertsTab</name> + <message> + <location filename="openlp/plugins/alerts/forms/alertstab.py" line="236"/> + <source>Preview</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>alertsPlugin</name> + <message> + <location filename="openlp/plugins/alerts/alertsplugin.py" line="98"/> + <source><b>Alerts Plugin</b><br>This plugin controls the displaying of alerts on the presentations screen</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>GeneralTab</name> + <message> + <location filename="openlp/core/ui/generaltab.py" line="159"/> + <source>Show the splash screen</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="351"/> + <source>New Service</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SlideController</name> + <message> + <location filename="openlp/core/ui/slidecontroller.py" line="160"/> + <source>Move to first</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="406"/> + <source>&Online Help</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SlideController</name> + <message> + <location filename="openlp/core/ui/slidecontroller.py" line="175"/> + <source>Blank Screen</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="359"/> + <source>Save Service</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="363"/> + <source>Save &As...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="380"/> + <source>Toggle the visibility of the Media Manager</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MediaManagerItem</name> + <message> + <location filename="openlp/core/lib/mediamanageritem.py" line="233"/> + <source>Delete the selected item</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="423"/> + <source>Add</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>alertsPlugin</name> + <message> + <location filename="openlp/plugins/alerts/alertsplugin.py" line="67"/> + <source>&Alert</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BibleMediaItem</name> + <message> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="225"/> + <source>Advanced</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImageMediaItem</name> + <message> + <location filename="openlp/plugins/images/lib/mediaitem.py" line="146"/> + <source>Image(s)</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="397"/> + <source>F11</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="386"/> + <source>F10</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="367"/> + <source>F12</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="320"/> + <source>Select the import format, and where to import from.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="400"/> + <source>Alt+F7</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="416"/> + <source>Add an application to the list of tools</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MediaPlugin</name> + <message> + <location filename="openlp/plugins/media/mediaplugin.py" line="78"/> + <source><b>Media Plugin</b><br>This plugin allows the playing of audio and video media</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BiblesTab</name> + <message> + <location filename="openlp/plugins/bibles/lib/biblestab.py" line="156"/> + <source>Bible Theme:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongsPlugin</name> + <message> + <location filename="openlp/plugins/songs/songsplugin.py" line="117"/> + <source>Export songs in openlp.org 1.0 format</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="349"/> + <source>Theme Manager</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AlertsTab</name> + <message> + <location filename="openlp/plugins/alerts/forms/alertstab.py" line="40"/> + <source>Alerts</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_customEditDialog</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomdialog.py" line="150"/> + <source>Move slide down 1</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="642"/> + <source>Font:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="121"/> + <source>Load an existing service</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="385"/> + <source>Toggle the visibility of the Theme Manager</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>PresentationTab</name> + <message> + <location filename="openlp/plugins/presentations/lib/presentationtab.py" line="40"/> + <source>Presentations</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SplashScreen</name> + <message> + <location filename="openlp/core/ui/splashscreen.py" line="33"/> + <source>Starting</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImageTab</name> + <message> + <location filename="openlp/plugins/images/lib/imagetab.py" line="67"/> + <source>Slide Loop Delay:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="171"/> + <source>Move to end</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AlertsTab</name> + <message> + <location filename="openlp/plugins/alerts/forms/alertstab.py" line="231"/> + <source>Alert timeout:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="417"/> + <source>&Preview Pane</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MediaManagerItem</name> + <message> + <location filename="openlp/core/lib/mediamanageritem.py" line="220"/> + <source>Add a new</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemeManager</name> + <message> + <location filename="openlp/core/ui/thememanager.py" line="246"/> + <source>Select Theme Import File</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/thememanager.py" line="504"/> + <source>New Theme</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MediaMediaItem</name> + <message> + <location filename="openlp/plugins/media/lib/mediaitem.py" line="84"/> + <source>Media</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="339"/> + <source>Password:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="665"/> + <source>Outline Size:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenSongExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/opensongexportdialog.py" line="302"/> + <source>Progress:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AmendThemeForm</name> + <message> + <location filename="openlp/core/ui/amendthemeform.py" line="640"/> + <source>Second Color:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="450"/> + <source>Theme, Copyright Info && Comments</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="382"/> + <source>&Theme Manager</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpimportdialog.py" line="289"/> + <source>Select openlp.org songfile to import:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="438"/> + <source>Song Book</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>alertsPlugin</name> + <message> + <location filename="openlp/plugins/alerts/alertsplugin.py" line="69"/> + <source>F7</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpexportdialog.py" line="299"/> + <source>Author</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="622"/> + <source>Wrap Indentation</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemeManager</name> + <message> + <location filename="openlp/core/ui/thememanager.py" line="67"/> + <source>Import a theme</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>PresentationPlugin</name> + <message> + <location filename="openlp/plugins/presentations/presentationplugin.py" line="112"/> + <source><b>Presentation Plugin</b> <br> Delivers the ability to show presentations using a number of different programs. The choice of available presentation programs is available to the user in a drop down box.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImageMediaItem</name> + <message> + <location filename="openlp/plugins/images/lib/mediaitem.py" line="59"/> + <source>Image</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongsTab</name> + <message> + <location filename="openlp/plugins/songs/lib/songstab.py" line="66"/> + <source>Enable search as you type:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AlertDialog</name> + <message> + <location filename="openlp/plugins/alerts/forms/alertdialog.py" line="71"/> + <source>Cancel</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpimportdialog.py" line="304"/> + <source>Import</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditVerseDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editversedialog.py" line="124"/> + <source>Chorus</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="425"/> + <source>Edit All</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AuthorsForm</name> + <message> + <location filename="openlp/plugins/songs/forms/authorsform.py" line="87"/> + <source>You need to type in the last name of the author.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongsTab</name> + <message> + <location filename="openlp/plugins/songs/lib/songstab.py" line="64"/> + <source>Songs Mode</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="676"/> + <source>Left</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemesTab</name> + <message> + <location filename="openlp/core/ui/themestab.py" line="117"/> + <source>Use the theme from the service, overriding any of the individual songs' themes. If the service doesn't have a theme, then use the global theme.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImageTab</name> + <message> + <location filename="openlp/plugins/images/lib/imagetab.py" line="39"/> + <source>Images</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BibleMediaItem</name> + <message> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="290"/> + <source>Verse:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="323"/> + <source>CSV</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpexportdialog.py" line="297"/> + <source>Song Export List</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemeManager</name> + <message> + <location filename="openlp/core/ui/thememanager.py" line="96"/> + <source>Export theme</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_SongMaintenanceDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenancedialog.py" line="223"/> + <source>Delete</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="599"/> + <source>Theme Name:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AboutDialog</name> + <message> + <location filename="openlp/core/ui/aboutdialog.py" line="116"/> + <source>About OpenLP</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="391"/> + <source>Toggle the visibility of the Service Manager</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>PresentationMediaItem</name> + <message> + <location filename="openlp/plugins/presentations/lib/mediaitem.py" line="125"/> + <source>A presentation with that filename already exists.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AlertsTab</name> + <message> + <location filename="openlp/plugins/alerts/forms/alertstab.py" line="237"/> + <source>openlp.org</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="120"/> + <source>Invalid Books File</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpimportdialog.py" line="298"/> + <source>Song Title</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MediaManagerItem</name> + <message> + <location filename="openlp/core/lib/mediamanageritem.py" line="283"/> + <source>&Show Live</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AlertsTab</name> + <message> + <location filename="openlp/plugins/alerts/forms/alertstab.py" line="234"/> + <source>Keep History:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="609"/> + <source>Image:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_customEditDialog</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomdialog.py" line="166"/> + <source>Set Theme for Slides</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="404"/> + <source>More information about OpenLP</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AlertsTab</name> + <message> + <location filename="openlp/plugins/alerts/forms/alertstab.py" line="228"/> + <source>Background Color:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMaintenanceForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenanceform.py" line="244"/> + <source>No topic selected!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="377"/> + <source>&Media Manager</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="342"/> + <source>&Tools</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AmendThemeForm</name> + <message> + <location filename="openlp/core/ui/amendthemeform.py" line="624"/> + <source>Background Color:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="436"/> + <source>A&dd to Song</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="419"/> + <source>Title:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>GeneralTab</name> + <message> + <location filename="openlp/core/ui/generaltab.py" line="197"/> + <source>Screen</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AlertsTab</name> + <message> + <location filename="openlp/plugins/alerts/forms/alertstab.py" line="232"/> + <source>s</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImagePlugin</name> + <message> + <location filename="openlp/plugins/images/imageplugin.py" line="59"/> + <source><b>Image Plugin</b><br>Allows images of all types to be displayed. If a number of images are selected together and presented on the live controller it is possible to turn them into a timed loop.<br<br>From the plugin if the <i>Override background</i> is chosen and an image is selected any somgs which are rendered will use the selected image from the background instead of the one provied by the theme.<br></source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AlertEditDialog</name> + <message> + <location filename="openlp/plugins/alerts/forms/alerteditdialog.py" line="63"/> + <source>Clear</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="351"/> + <source>Please wait while your Bible is imported.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MediaManagerItem</name> + <message> + <location filename="openlp/core/lib/mediamanageritem.py" line="380"/> + <source>No items selected...</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpimportdialog.py" line="300"/> + <source>Select All</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="640"/> + <source>Font Main</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpimportdialog.py" line="295"/> + <source>Title</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenSongExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/opensongexportdialog.py" line="289"/> + <source>Select OpenSong song folder:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="378"/> + <source>Toggle Media Manager</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongUsagePlugin</name> + <message> + <location filename="openlp/plugins/songusage/songusageplugin.py" line="61"/> + <source>&Song Usage</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>GeneralTab</name> + <message> + <location filename="openlp/core/ui/generaltab.py" line="154"/> + <source>Monitors</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>EditCustomForm</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomform.py" line="256"/> + <source>You need to enter a slide</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="112"/> + <source>You need to specify a file to import your Bible from!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditVerseDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editversedialog.py" line="113"/> + <source>Verse Type</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemeManager</name> + <message> + <location filename="openlp/core/ui/thememanager.py" line="219"/> + <source>You have not selected a theme!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="447"/> + <source>Comments</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AlertsTab</name> + <message> + <location filename="openlp/plugins/alerts/forms/alertstab.py" line="239"/> + <source>Bottom</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="352"/> + <source>Create a new Service</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AlertsTab</name> + <message> + <location filename="openlp/plugins/alerts/forms/alertstab.py" line="238"/> + <source>Top</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SlideController</name> + <message> + <location filename="openlp/core/ui/slidecontroller.py" line="116"/> + <source>Preview</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_PluginViewDialog</name> + <message> + <location filename="openlp/core/ui/plugindialog.py" line="104"/> + <source>TextLabel</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/plugindialog.py" line="105"/> + <source>About:</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/plugindialog.py" line="108"/> + <source>Inactive</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenSongExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/opensongexportdialog.py" line="303"/> + <source>Ready to export</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/plugins/songs/forms/opensongexportdialog.py" line="304"/> + <source>Export</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_PluginViewDialog</name> + <message> + <location filename="openlp/core/ui/plugindialog.py" line="101"/> + <source>Plugin List</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="684"/> + <source>Transition Active:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="342"/> + <source>Proxy Server (Optional)</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="434"/> + <source>&Manage Authors, Topics, Books</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_SongUsageDetailDialog</name> + <message> + <location filename="openlp/plugins/songusage/forms/songusagedetaildialog.py" line="88"/> + <source>Audit Detail Extraction</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpexportdialog.py" line="303"/> + <source>Ready to export</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>EditCustomForm</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomform.py" line="84"/> + <source>Save && Preview</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpexportdialog.py" line="300"/> + <source>Select All</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_SongUsageDetailDialog</name> + <message> + <location filename="openlp/plugins/songusage/forms/songusagedetaildialog.py" line="90"/> + <source>to</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="644"/> + <source>Size:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="584"/> + <source>OpenLP Main Display Blanked</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpimportdialog.py" line="301"/> + <source>Remove Selected</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>OpenSongBible</name> + <message> + <location filename="openlp/plugins/bibles/lib/opensong.py" line="96"/> + <source>Importing</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="426"/> + <source>Delete</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="362"/> + <source>Ctrl+S</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>PresentationMediaItem</name> + <message> + <location filename="openlp/plugins/presentations/lib/mediaitem.py" line="125"/> + <source>File exists</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenSongImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/opensongimportdialog.py" line="106"/> + <source>Ready to import</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SlideController</name> + <message> + <location filename="openlp/core/ui/slidecontroller.py" line="194"/> + <source>Stop continuous loop</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/slidecontroller.py" line="200"/> + <source>s</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMediaItem</name> + <message> + <location filename="openlp/plugins/songs/lib/mediaitem.py" line="71"/> + <source>Song Maintenance</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_customEditDialog</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomdialog.py" line="155"/> + <source>Edit</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="610"/> + <source>Gradient :</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BiblesTab</name> + <message> + <location filename="openlp/plugins/bibles/lib/biblestab.py" line="154"/> + <source>Layout Style:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="128"/> + <source>Invalid Verse File</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>EditSongForm</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongform.py" line="478"/> + <source>Error</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_customEditDialog</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomdialog.py" line="153"/> + <source>Add New</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AuthorsDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/authorsdialog.py" line="77"/> + <source>Display name:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMaintenanceForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenanceform.py" line="241"/> + <source>Are you sure you want to delete the selected topic?</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="649"/> + <source>Bold/Italics</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_SongMaintenanceDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenancedialog.py" line="211"/> + <source>Song Maintenance</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditVerseDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editversedialog.py" line="122"/> + <source>Bridge</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongsTab</name> + <message> + <location filename="openlp/plugins/songs/lib/songstab.py" line="39"/> + <source>Songs</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="688"/> + <source>Preview</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AboutDialog</name> + <message> + <location filename="openlp/core/ui/aboutdialog.py" line="155"/> + <source>Credits</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MediaManagerItem</name> + <message> + <location filename="openlp/core/lib/mediamanageritem.py" line="240"/> + <source>Preview the selected item</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="346"/> + <source>Version Name:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AboutDialog</name> + <message> + <location filename="openlp/core/ui/aboutdialog.py" line="134"/> + <source>About</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditVerseDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editversedialog.py" line="128"/> + <source>Ending</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="675"/> + <source>Horizontal Align:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="201"/> + <source>&Edit Item</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="603"/> + <source>Background Type:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="358"/> + <source>&Save</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="330"/> + <source>OpenLP 2.0</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemeManager</name> + <message> + <location filename="openlp/core/ui/thememanager.py" line="445"/> + <source>A theme with this name already exists, would you like to overwrite it?</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/thememanager.py" line="70"/> + <source>Export a theme</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AmendThemeForm</name> + <message> + <location filename="openlp/core/ui/amendthemeform.py" line="208"/> + <source>Open file</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_TopicsDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/topicsdialog.py" line="62"/> + <source>Topic Maintenance</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_customEditDialog</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomdialog.py" line="164"/> + <source>Clear edit area</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="668"/> + <source>Show Outline:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongBookForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songbookform.py" line="52"/> + <source>You need to type in a book name!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="195"/> + <source>Open OpenSong Bible</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="375"/> + <source>Look && &Feel</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="352"/> + <source>Ready.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_SongMaintenanceDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenancedialog.py" line="214"/> + <source>Books/Hymnals</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AboutDialog</name> + <message> + <location filename="openlp/core/ui/aboutdialog.py" line="549"/> + <source>Contribute</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="605"/> <source>Gradient</source> <translation type="unfinished"></translation> </message> </context> + <context> + <name>AlertsTab</name> + <message> + <location filename="openlp/plugins/alerts/forms/alertstab.py" line="229"/> + <source>Font Size:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenSongExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/opensongexportdialog.py" line="290"/> + <source>Full Song List</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="613"/> + <source>Circular</source> + <translation type="unfinished"></translation> + </message> + </context> </TS> diff --git a/scripts/get-strings.py b/scripts/get-strings.py index 8d9dad174..ed3cdcb41 100755 --- a/scripts/get-strings.py +++ b/scripts/get-strings.py @@ -24,6 +24,7 @@ ############################################################################### import os +from cgi import escape from ast import parse, NodeVisitor, Str ts_file = u"""<?xml version="1.0" encoding="utf-8"?> @@ -45,7 +46,8 @@ ts_message = u""" <message> class StringExtractor(NodeVisitor): - def __init__(self, strings, filename): + def __init__(self, strings, filename, base_path): + self.base_path = base_path self.filename = filename self.strings = strings self.classname = 'unknown' @@ -58,10 +60,10 @@ class StringExtractor(NodeVisitor): if hasattr(node.func, 'attr') and node.func.attr == 'trUtf8' and isinstance(node.args[0], Str): string = node.args[0].s key = '%s-%s' % (self.classname, string) - self.strings[key] = [self.classname, self.filename, node.lineno, string] + self.strings[key] = [self.classname, self.filename[len(self.base_path) + 1:], node.lineno, escape(string)] self.generic_visit(node) -def parse_file(filename, strings): +def parse_file(base_path, filename, strings): file = open(filename, u'r') try: ast = parse(file.read()) @@ -70,7 +72,7 @@ def parse_file(filename, strings): return file.close() - StringExtractor(strings, filename).visit(ast) + StringExtractor(strings, filename, base_path).visit(ast) def write_file(filename, strings): translation_file = u'' @@ -94,15 +96,18 @@ def write_file(filename, strings): def main(): strings = {} - start_dir = os.path.abspath(u'.') + start_dir = os.path.abspath(u'..') for root, dirs, files in os.walk(start_dir): for file in files: if file.endswith(u'.py'): print u'Parsing "%s"' % file - parse_file(os.path.join(root, file), strings) + parse_file(start_dir, os.path.join(root, file), strings) print u'Generating TS file...', - write_file(os.path.join(start_dir, u'i18n', u'openlp_en.ts'), strings) + write_file(os.path.join(start_dir, u'resources', u'i18n', u'openlp_en.ts'), strings) print u'done.' if __name__ == u'__main__': - main() \ No newline at end of file + if os.path.split(os.path.abspath(u'.'))[1] != u'scripts': + print u'You need to run this script from the scripts directory.' + else: + main() From 0b1ce89017f4fa2ac5bddf777f027211f961c627 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Tue, 9 Mar 2010 18:19:58 +0000 Subject: [PATCH 148/164] Fix translation --- openlp/plugins/alerts/alertsplugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openlp/plugins/alerts/alertsplugin.py b/openlp/plugins/alerts/alertsplugin.py index 3e065f6af..90e7946c7 100644 --- a/openlp/plugins/alerts/alertsplugin.py +++ b/openlp/plugins/alerts/alertsplugin.py @@ -66,7 +66,7 @@ class alertsPlugin(Plugin): self.toolsAlertItem.setObjectName(u'toolsAlertItem') self.toolsAlertItem.setText(self.trUtf8('&Alert')) self.toolsAlertItem.setStatusTip(self.trUtf8('Show an alert message')) - self.toolsAlertItem.setShortcut(self.trUtf8('F7')) + self.toolsAlertItem.setShortcut(u'F7') self.service_manager.parent.ToolsMenu.addAction(self.toolsAlertItem) QtCore.QObject.connect(self.toolsAlertItem, QtCore.SIGNAL(u'triggered()'), self.onAlertsTrigger) From e40f1b41ef47e35d3119334cbd6bbe7c52afda6d Mon Sep 17 00:00:00 2001 From: Jon Tibble <meths@btinternet.com> Date: Tue, 9 Mar 2010 19:43:11 +0000 Subject: [PATCH 149/164] Truth tests and some style fixes --- openlp/core/lib/mediamanageritem.py | 2 +- openlp/core/lib/renderer.py | 6 +++--- openlp/core/lib/themexmlhandler.py | 9 ++++---- openlp/core/ui/servicemanager.py | 6 +++--- openlp/core/ui/thememanager.py | 2 +- openlp/plugins/alerts/forms/alerteditform.py | 2 +- openlp/plugins/bibles/lib/http.py | 4 ++-- openlp/plugins/custom/forms/editcustomform.py | 2 +- openlp/plugins/custom/lib/mediaitem.py | 8 +++---- .../presentations/presentationplugin.py | 2 +- openlp/plugins/remotes/remoteclient.py | 2 +- openlp/plugins/songs/lib/songxml.py | 21 +++++++++---------- 12 files changed, 33 insertions(+), 33 deletions(-) diff --git a/openlp/core/lib/mediamanageritem.py b/openlp/core/lib/mediamanageritem.py index ff87f0b0d..fd6d37ca6 100644 --- a/openlp/core/lib/mediamanageritem.py +++ b/openlp/core/lib/mediamanageritem.py @@ -314,7 +314,7 @@ class MediaManagerItem(QtGui.QWidget): self, self.OnNewPrompt, self.parent.config.get_last_dir(), self.OnNewFileMasks) log.info(u'New files(s)%s', unicode(files)) - if len(files) > 0: + if files: self.loadList(files) dir, filename = os.path.split(unicode(files[0])) self.parent.config.set_last_dir(dir) diff --git a/openlp/core/lib/renderer.py b/openlp/core/lib/renderer.py index 9b2d666ac..4ed0fcf76 100644 --- a/openlp/core/lib/renderer.py +++ b/openlp/core/lib/renderer.py @@ -174,7 +174,7 @@ class Renderer(object): #Must be a blank line so keep it. if len(line) == 0: line = u' ' - while len(line) > 0: + while line: pos = char_per_line split_text = line[:pos] #line needs splitting @@ -199,7 +199,7 @@ class Renderer(object): split_lines.append(split_text) line = line[pos:].lstrip() #if we have more text add up to 10 spaces on the front. - if len(line) > 0 and self._theme.font_main_indentation > 0: + if line and self._theme.font_main_indentation > 0: line = u'%s%s' % \ (u' '[:int(self._theme.font_main_indentation)], line) #Text fits in a line now @@ -210,7 +210,7 @@ class Renderer(object): len(page) == page_length: split_pages.append(page) page = [] - if len(page) > 0 and page != u' ': + if page and page != u' ': split_pages.append(page) return split_pages diff --git a/openlp/core/lib/themexmlhandler.py b/openlp/core/lib/themexmlhandler.py index 697c161e2..cbd46d597 100644 --- a/openlp/core/lib/themexmlhandler.py +++ b/openlp/core/lib/themexmlhandler.py @@ -171,7 +171,8 @@ class ThemeXML(object): self.child_element(background, u'filename', filename) def add_font(self, name, color, proportion, override, fonttype=u'main', - weight=u'Normal', italics=u'False', indentation=0, xpos=0, ypos=0, width=0, height=0): + weight=u'Normal', italics=u'False', indentation=0, xpos=0, ypos=0, + width=0, height=0): """ Add a Font. @@ -363,14 +364,14 @@ class ThemeXML(object): master = u'' for element in iter: element.text = unicode(element.text).decode('unicode-escape') - if len(element.getchildren()) > 0: + if element.getchildren(): master = element.tag + u'_' else: #background transparent tags have no children so special case if element.tag == u'background': for e in element.attrib.iteritems(): setattr(self, element.tag + u'_' + e[0], e[1]) - if len(element.attrib) > 0: + if element.attrib: for e in element.attrib.iteritems(): if master == u'font_' and e[0] == u'type': master += e[1] + u'_' @@ -402,4 +403,4 @@ class ThemeXML(object): for key in dir(self): if key[0:1] != u'_': theme_strings.append(u'%30s: %s' % (key, getattr(self, key))) - return u'\n'.join(theme_strings) \ No newline at end of file + return u'\n'.join(theme_strings) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index b4ce85e5d..9afd07f51 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -428,7 +428,7 @@ class ServiceManager(QtGui.QWidget): for itemcount, item in enumerate(self.serviceItems): serviceitem = item[u'service_item'] treewidgetitem = QtGui.QTreeWidgetItem(self.ServiceManagerList) - if len(serviceitem.notes) > 0: + if serviceitem.notes: icon = QtGui.QImage(serviceitem.icon) icon = icon.scaled(80, 80, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) @@ -601,7 +601,7 @@ class ServiceManager(QtGui.QWidget): def regenerateServiceItems(self): #force reset of renderer as theme data has changed self.parent.RenderManager.themedata = None - if len(self.serviceItems) > 0: + if self.serviceItems: tempServiceItems = self.serviceItems self.ServiceManagerList.clear() self.serviceItems = [] @@ -663,7 +663,7 @@ class ServiceManager(QtGui.QWidget): if str_to_bool(PluginConfig(u'General'). get_config(u'auto preview', u'False')): item += 1 - if len(self.serviceItems) > 0 and item < len(self.serviceItems) and \ + if self.serviceItems and item < len(self.serviceItems) and \ self.serviceItems[item][u'service_item'].autoPreviewAllowed: self.parent.PreviewController.addServiceManagerItem( self.serviceItems[item][u'service_item'], 0) diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index b6396c7db..5cad41f58 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -246,7 +246,7 @@ class ThemeManager(QtGui.QWidget): self, self.trUtf8('Select Theme Import File'), self.config.get_last_dir(), u'Theme (*.*)') log.info(u'New Themes %s', unicode(files)) - if len(files) > 0: + if files: for file in files: self.config.set_last_dir(unicode(file)) self.unzipTheme(file, self.path) diff --git a/openlp/plugins/alerts/forms/alerteditform.py b/openlp/plugins/alerts/forms/alerteditform.py index 4abc8a660..62c129508 100644 --- a/openlp/plugins/alerts/forms/alerteditform.py +++ b/openlp/plugins/alerts/forms/alerteditform.py @@ -82,7 +82,7 @@ class AlertEditForm(QtGui.QDialog, Ui_AlertEditDialog): self.DeleteButton.setEnabled(False) def onItemSelected(self): - if len(self.AlertLineEdit.text()) > 0: + if self.AlertLineEdit.text(): QtGui.QMessageBox.information(self, self.trUtf8('Item selected to Edit'), self.trUtf8('Please Save or Clear seletced item')) diff --git a/openlp/plugins/bibles/lib/http.py b/openlp/plugins/bibles/lib/http.py index d00c1f88a..7c675dee9 100644 --- a/openlp/plugins/bibles/lib/http.py +++ b/openlp/plugins/bibles/lib/http.py @@ -77,7 +77,7 @@ class HTTPBooks(object): books = HTTPBooks.run_sql(u'SELECT id, testament_id, name, ' u'abbreviation, chapters FROM books WHERE name = ? OR ' u'abbreviation = ?', (name, name)) - if len(books) > 0: + if books: return { u'id': books[0][0], u'testament_id': books[0][1], @@ -95,7 +95,7 @@ class HTTPBooks(object): book = HTTPBooks.get_book(name) chapters = HTTPBooks.run_sql(u'SELECT id, book_id, chapter, ' u'verses FROM chapters WHERE book_id = ?', (book[u'id'],)) - if len(chapters) > 0: + if chapters: return { u'id': chapters[0][0], u'book_id': chapters[0][1], diff --git a/openlp/plugins/custom/forms/editcustomform.py b/openlp/plugins/custom/forms/editcustomform.py index 577142f23..b4402ceb3 100644 --- a/openlp/plugins/custom/forms/editcustomform.py +++ b/openlp/plugins/custom/forms/editcustomform.py @@ -254,7 +254,7 @@ class EditCustomForm(QtGui.QDialog, Ui_customEditDialog): if self.VerseListView.count() == 0: self.VerseTextEdit.setFocus() return False, self.trUtf8('You need to enter a slide') - if len(self.VerseTextEdit.toPlainText()) > 0: + if self.VerseTextEdit.toPlainText(): self.VerseTextEdit.setFocus() return False, self.trUtf8('You have unsaved data') return True, u'' diff --git a/openlp/plugins/custom/lib/mediaitem.py b/openlp/plugins/custom/lib/mediaitem.py index 2a3090cf2..61d1b05d7 100644 --- a/openlp/plugins/custom/lib/mediaitem.py +++ b/openlp/plugins/custom/lib/mediaitem.py @@ -151,7 +151,7 @@ class CustomMediaItem(MediaManagerItem): service_item.edit_enabled = True service_item.editId = item_id theme = customSlide.theme_name - if len(theme) is not 0 : + if theme: service_item.theme = theme songXML = SongXMLParser(customSlide.text) verseList = songXML.get_verses() @@ -160,9 +160,9 @@ class CustomMediaItem(MediaManagerItem): service_item.title = title for slide in raw_slides: service_item.add_from_text(slide[:30], slide) - if str_to_bool(self.parent.config.get_config(u'display footer', True)) or \ - len(credit) > 0: - raw_footer.append(title + u' '+ credit) + if str_to_bool(self.parent.config.get_config(u'display footer', True)) \ + or credit: + raw_footer.append(title + u' ' + credit) else: raw_footer.append(u'') service_item.raw_footer = raw_footer diff --git a/openlp/plugins/presentations/presentationplugin.py b/openlp/plugins/presentations/presentationplugin.py index 061eb737f..8353611ab 100644 --- a/openlp/plugins/presentations/presentationplugin.py +++ b/openlp/plugins/presentations/presentationplugin.py @@ -103,7 +103,7 @@ class PresentationPlugin(Plugin): self.registerControllers(controller) if controller.enabled: controller.start_process() - if len(self.controllers) > 0: + if self.controllers: return True else: return False diff --git a/openlp/plugins/remotes/remoteclient.py b/openlp/plugins/remotes/remoteclient.py index 6d1abe877..857a7fc7e 100755 --- a/openlp/plugins/remotes/remoteclient.py +++ b/openlp/plugins/remotes/remoteclient.py @@ -54,7 +54,7 @@ def main(): help="Message to be passed for the action") (options, args) = parser.parse_args() - if len(args) > 0: + if args: parser.print_help() parser.error("incorrect number of arguments") elif options.address is None: diff --git a/openlp/plugins/songs/lib/songxml.py b/openlp/plugins/songs/lib/songxml.py index 7d356d848..f9cef7fce 100644 --- a/openlp/plugins/songs/lib/songxml.py +++ b/openlp/plugins/songs/lib/songxml.py @@ -153,12 +153,12 @@ class _OpenSong(XmlRootClass): tmpVerse = [] finalLyrics = [] tag = "" - for l in lyrics: - line = l.rstrip() + for lyric in lyrics: + line = lyric.rstrip() if not line.startswith(u'.'): # drop all chords tmpVerse.append(line) - if len(line) > 0: + if line: if line.startswith(u'['): tag = line else: @@ -298,9 +298,9 @@ class Song(object): chars are: .,:;!?&%#/\@`$'|"^~*- """ punctuation = ".,:;!?&%#'\"/\\@`$|^~*-" - s = title - for c in punctuation: - s = s.replace(c, '') + string = title + for char in punctuation: + string = string.replace(char, '') return s def set_title(self, title): @@ -582,17 +582,16 @@ class Song(object): self.slideList = [] tmpSlide = [] metContent = False - for l in self.lyrics: - if len(l) > 0: + for lyric in self.lyrics: + if lyric: metContent = True - tmpSlide.append(l) + tmpSlide.append(lyric) else: if metContent: metContent = False self.slideList.append(tmpSlide) tmpSlide = [] - # - if len(tmpSlide) > 0: + if tmpSlide: self.slideList.append(tmpSlide) def get_number_of_slides(self): From 16ea5dd0adf9614fca2b866262accdcf31341cb1 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Tue, 9 Mar 2010 22:26:12 +0200 Subject: [PATCH 150/164] Tidied up the tabs on the media manager. --- openlp/core/ui/mainwindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 6218e35aa..01f3cabbe 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -50,7 +50,7 @@ media_manager_style = """ QToolBox::tab:selected { background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 palette(light), stop: 1.0 palette(button)); - border-color: palette(dark); + border-color: palette(button); } """ class versionThread(QtCore.QThread): From 1e79260c5c01aa0fe6947a8441097df519457188 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Tue, 9 Mar 2010 22:29:45 +0200 Subject: [PATCH 151/164] Renamed the versionThread class to VersionThread to fit with our coding standards. --- openlp/core/ui/mainwindow.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 01f3cabbe..41b6d245f 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -53,13 +53,21 @@ media_manager_style = """ border-color: palette(button); } """ -class versionThread(QtCore.QThread): +class VersionThread(QtCore.QThread): + """ + A special Qt thread class to fetch the version of OpenLP from the website. + This is threaded so that it doesn't affect the loading time of OpenLP. + """ def __init__(self, parent, app_version, generalConfig): QtCore.QThread.__init__(self, parent) self.parent = parent self.app_version = app_version self.generalConfig = generalConfig - def run (self): + + def run(self): + """ + Run the thread. + """ time.sleep(2) version = check_latest_version(self.generalConfig, self.app_version) #new version has arrived @@ -586,7 +594,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): def versionThread(self): app_version = self.applicationVersion[u'full'] - vT = versionThread(self, app_version, self.generalConfig) + vT = VersionThread(self, app_version, self.generalConfig) vT.start() def onHelpAboutItemClicked(self): From 92e515a7631b2a77a929b301dc67454ce89971f2 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Tue, 9 Mar 2010 22:32:13 +0200 Subject: [PATCH 152/164] Removed an unnecessary hook file. --- resources/pyinstaller/hook-openlp.plugins.bibles.lib.common.py | 1 - 1 file changed, 1 deletion(-) delete mode 100644 resources/pyinstaller/hook-openlp.plugins.bibles.lib.common.py diff --git a/resources/pyinstaller/hook-openlp.plugins.bibles.lib.common.py b/resources/pyinstaller/hook-openlp.plugins.bibles.lib.common.py deleted file mode 100644 index dc71dc088..000000000 --- a/resources/pyinstaller/hook-openlp.plugins.bibles.lib.common.py +++ /dev/null @@ -1 +0,0 @@ -hiddenimports = ['chardet'] \ No newline at end of file From ef48079c7924370739c6bde8ffc39aec37bc0cf4 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Tue, 9 Mar 2010 22:42:29 +0200 Subject: [PATCH 153/164] Remove now unnecessary and unused version.txt file. --- version.txt | 1 - 1 file changed, 1 deletion(-) delete mode 100644 version.txt diff --git a/version.txt b/version.txt deleted file mode 100644 index fe8ffff0a..000000000 --- a/version.txt +++ /dev/null @@ -1 +0,0 @@ -1.9.0-725 From 9bace991fa4d7c87e84827667ea3b45c3ed75cb9 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Wed, 10 Mar 2010 16:57:33 +0000 Subject: [PATCH 154/164] renderer changes --- openlp/core/lib/renderer.py | 37 ++++--------------------------- openlp/core/ui/slidecontroller.py | 3 +++ 2 files changed, 7 insertions(+), 33 deletions(-) diff --git a/openlp/core/lib/renderer.py b/openlp/core/lib/renderer.py index ea6e849d8..817fe3311 100644 --- a/openlp/core/lib/renderer.py +++ b/openlp/core/lib/renderer.py @@ -466,38 +466,9 @@ class Renderer(object): self._get_extent_and_render(line, footer, tlcorner=(x + display_shadow_size, y + display_shadow_size), draw=True, color = self._theme.display_shadow_color) - if self._theme.display_outline: - self._get_extent_and_render(line, footer, - (x + display_outline_size, y), draw=True, - color = self._theme.display_outline_color) - self._get_extent_and_render(line, footer, - (x, y + display_outline_size), draw=True, - color = self._theme.display_outline_color) - self._get_extent_and_render(line, footer, - (x, y - display_outline_size), draw=True, - color = self._theme.display_outline_color) - self._get_extent_and_render(line, footer, - (x - display_outline_size, y), draw=True, - color = self._theme.display_outline_color) - if display_outline_size > 1: - self._get_extent_and_render(line, footer, - (x + display_outline_size, y + display_outline_size), - draw=True, - color = self._theme.display_outline_color) - self._get_extent_and_render(line, footer, - (x - display_outline_size, y + display_outline_size), - draw=True, - color = self._theme.display_outline_color) - self._get_extent_and_render(line, footer, - (x + display_outline_size, y - display_outline_size), - draw=True, - color = self._theme.display_outline_color) - self._get_extent_and_render(line, footer, - (x - display_outline_size, y - display_outline_size), - draw=True, - color = self._theme.display_outline_color) - self._get_extent_and_render(line, footer,tlcorner=(x, y), - draw=True) + self._get_extent_and_render(line, footer, tlcorner=(x, y), draw=True, + outline_size=display_outline_size, + outline_color=self._theme.display_outline_color) y += h if linenum == 0: self._first_line_right_extent = rightextent @@ -535,7 +506,7 @@ class Renderer(object): self.mainFont.setPixelSize(self._theme.font_main_proportion) def _get_extent_and_render(self, line, footer, tlcorner=(0, 0), draw=False, - color=None): + color=None, outline_size=0, outline_color=None): """ Find bounding box of text - as render_single_line. If draw is set, actually draw the text to the current DC as well return width and diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index ff8507f5d..58268aecb 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -449,8 +449,10 @@ class SlideController(QtGui.QWidget): #If verse handle verse number else tag only if bits[0] == self.trUtf8('Verse'): tag = u'%s%s' % (bits[0][0], bits[1][0:] ) + row = bits[1][0:] else: tag = bits[0] + row = bits[0][0:1] try: test = self.slideList[tag] except: @@ -469,6 +471,7 @@ class SlideController(QtGui.QWidget): self.PreviewListWidget.setCellWidget(framenumber, 0, label) slide_height = width * self.parent.RenderManager.screen_ratio self.PreviewListWidget.setItem(framenumber, 0, item) + print row if slide_height != 0: self.PreviewListWidget.setRowHeight(framenumber, slide_height) if self.serviceItem.is_text(): From d48046c0c349d9cc2d864c2676a2b67574989593 Mon Sep 17 00:00:00 2001 From: Jonathan Corwin <j@corwin.co.uk> Date: Wed, 10 Mar 2010 22:24:09 +0000 Subject: [PATCH 155/164] Blanking issues --- .../presentations/lib/impresscontroller.py | 11 ++++++++++ .../presentations/lib/messagelistener.py | 21 +++++++++++++++++++ .../presentations/lib/powerpointcontroller.py | 8 ++++++- .../presentations/lib/pptviewcontroller.py | 10 +++++++++ .../lib/presentationcontroller.py | 9 ++++++++ 5 files changed, 58 insertions(+), 1 deletion(-) diff --git a/openlp/plugins/presentations/lib/impresscontroller.py b/openlp/plugins/presentations/lib/impresscontroller.py index 56a223f06..2756f718e 100644 --- a/openlp/plugins/presentations/lib/impresscontroller.py +++ b/openlp/plugins/presentations/lib/impresscontroller.py @@ -102,11 +102,14 @@ class ImpressController(PresentationController): log.debug(u'get UNO Desktop Openoffice') ctx = None loop = 0 + log.debug(u'get UNO Desktop Openoffice - getComponentContext') context = uno.getComponentContext() + log.debug(u'get UNO Desktop Openoffice - createInstaneWithContext - UnoUrlResolver') resolver = context.ServiceManager.createInstanceWithContext( u'com.sun.star.bridge.UnoUrlResolver', context) while ctx is None and loop < 3: try: + log.debug(u'get UNO Desktop Openoffice - resolve') ctx = resolver.resolve(u'uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext') except: log.exception(u'Unable to find running instance ') @@ -114,6 +117,7 @@ class ImpressController(PresentationController): loop += 1 try: self.manager = ctx.ServiceManager + log.debug(u'get UNO Desktop Openoffice - createInstanceWithContext - Desktop') desktop = self.manager.createInstanceWithContext( "com.sun.star.frame.Desktop", ctx ) return desktop @@ -303,6 +307,13 @@ class ImpressDocument(PresentationDocument): def blank_screen(self): log.debug(u'blank screen OpenOffice') self.control.blankScreen(0) + + def is_blank(self): + """ + Returns true if screen is blank + """ + log.debug(u'is blank OpenOffice') + return self.control.isPaused() def stop_presentation(self): log.debug(u'stop presentation OpenOffice') diff --git a/openlp/plugins/presentations/lib/messagelistener.py b/openlp/plugins/presentations/lib/messagelistener.py index 17a2492ea..c3c9a91f5 100644 --- a/openlp/plugins/presentations/lib/messagelistener.py +++ b/openlp/plugins/presentations/lib/messagelistener.py @@ -73,6 +73,9 @@ class Controller(object): log.debug(u'Live = %s, slide' % live) if not live: return + if self.doc.is_blank(): + self.doc.slidenumber = int(slide) + 1 + return self.activate() self.doc.goto_slide(int(slide) + 1) self.doc.poll_slidenumber(live) @@ -84,6 +87,9 @@ class Controller(object): log.debug(u'Live = %s, first' % self.isLive) if not self.isLive: return + if self.doc.is_blank(): + self.doc.slidenumber = 1 + return self.activate() self.doc.start_presentation() self.doc.poll_slidenumber(self.isLive) @@ -95,6 +101,9 @@ class Controller(object): log.debug(u'Live = %s, last' % self.isLive) if not self.isLive: return + if self.doc.is_blank(): + self.doc.slidenumber = self.doc.get_slide_count() + return self.activate() self.doc.goto_slide(self.doc.get_slide_count()) self.doc.poll_slidenumber(self.isLive) @@ -106,6 +115,10 @@ class Controller(object): log.debug(u'Live = %s, next' % self.isLive) if not self.isLive: return + if self.doc.is_blank(): + if self.doc.slidenumber < self.doc.get_slide_count(): + self.doc.slidenumber = self.doc.slidenumber + 1 + return self.activate() self.doc.next_step() self.doc.poll_slidenumber(self.isLive) @@ -117,6 +130,10 @@ class Controller(object): log.debug(u'Live = %s, previous' % self.isLive) if not self.isLive: return + if self.doc.is_blank(): + if self.doc.slidenumber > 1: + self.doc.slidenumber = self.doc.slidenumber - 1 + return self.activate() self.doc.previous_step() self.doc.poll_slidenumber(self.isLive) @@ -126,6 +143,8 @@ class Controller(object): Based on the handler passed at startup triggers slide show to shut down """ log.debug(u'Live = %s, shutdown' % self.isLive) + if self.isLive: + Receiver.send_message(u'live_slide_show') self.doc.close_presentation() self.doc = None #self.doc.slidenumber = 0 @@ -146,6 +165,8 @@ class Controller(object): if not self.isLive: return self.activate() + if self.doc.slidenumber and self.doc.slidenumber != self.doc.get_slide_number(): + self.doc.goto_slide(self.doc.slidenumber) self.doc.unblank_screen() def poll(self): diff --git a/openlp/plugins/presentations/lib/powerpointcontroller.py b/openlp/plugins/presentations/lib/powerpointcontroller.py index 838ab6c8c..909a8439b 100644 --- a/openlp/plugins/presentations/lib/powerpointcontroller.py +++ b/openlp/plugins/presentations/lib/powerpointcontroller.py @@ -178,7 +178,7 @@ class PowerpointDocument(PresentationDocument): """ Returns true if a presentation is currently active """ - if not self.controller.is_loaded(): + if not self.is_loaded(): return False try: if self.presentation.SlideShowWindow == None: @@ -203,6 +203,12 @@ class PowerpointDocument(PresentationDocument): """ self.presentation.SlideShowWindow.View.State = 3 + def is_blank(self): + """ + Returns true if screen is blank + """ + return self.presentation.SlideShowWindow.View.State == 3 + def stop_presentation(self): """ Stops the current presentation and hides the output diff --git a/openlp/plugins/presentations/lib/pptviewcontroller.py b/openlp/plugins/presentations/lib/pptviewcontroller.py index d3332f68c..3780d1225 100644 --- a/openlp/plugins/presentations/lib/pptviewcontroller.py +++ b/openlp/plugins/presentations/lib/pptviewcontroller.py @@ -104,6 +104,7 @@ class PptviewDocument(PresentationDocument): log.debug(u'Init Presentation PowerPoint') self.presentation = None self.pptid = None + self.blanked = False self.controller = controller self.store_filename(presentation) @@ -161,12 +162,21 @@ class PptviewDocument(PresentationDocument): Blanks the screen """ self.controller.process.Blank(self.pptid) + self.blanked = True def unblank_screen(self): """ Unblanks (restores) the presentationn """ self.controller.process.Unblank(self.pptid) + self.blanked = False + + def is_blank(self): + """ + Returns true if screen is blank + """ + log.debug(u'is blank OpenOffice') + return self.blanked def stop_presentation(self): """ diff --git a/openlp/plugins/presentations/lib/presentationcontroller.py b/openlp/plugins/presentations/lib/presentationcontroller.py index 5a914e1cc..ae41a56b1 100644 --- a/openlp/plugins/presentations/lib/presentationcontroller.py +++ b/openlp/plugins/presentations/lib/presentationcontroller.py @@ -172,6 +172,9 @@ class PresentationDocument(object): ``unblank_screen()`` Unblanks the screen, restoring the output + ``is_blank`` + Returns true if screen is blank + ``stop_presentation()`` Stops the presentation, removing it from the output display @@ -279,6 +282,12 @@ class PresentationDocument(object): """ pass + def is_blank(self): + """ + Returns true if screen is blank + """ + return False + def stop_presentation(self): """ Stops the presentation, removing it from the output display From 1613610853e7a0eca145a181228fcaefeef521fb Mon Sep 17 00:00:00 2001 From: Jonathan Corwin <j@corwin.co.uk> Date: Wed, 10 Mar 2010 22:32:27 +0000 Subject: [PATCH 156/164] Good idea to test conflict resolutions before committing --- openlp/plugins/presentations/lib/powerpointcontroller.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openlp/plugins/presentations/lib/powerpointcontroller.py b/openlp/plugins/presentations/lib/powerpointcontroller.py index c6683fb20..2137496a5 100644 --- a/openlp/plugins/presentations/lib/powerpointcontroller.py +++ b/openlp/plugins/presentations/lib/powerpointcontroller.py @@ -310,4 +310,3 @@ class PowerpointDocument(PresentationDocument): if shape.HasTextFrame: text += shape.TextFrame.TextRange.Text + '\n' return text -s \ No newline at end of file From bfee664c94b3daa5cbf1483aae5bbaca534358ec Mon Sep 17 00:00:00 2001 From: Jonathan Corwin <j@corwin.co.uk> Date: Wed, 10 Mar 2010 23:00:48 +0000 Subject: [PATCH 157/164] fixes --- openlp/core/ui/slidecontroller.py | 6 +++++- openlp/plugins/presentations/lib/messagelistener.py | 7 ++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 0143be4cf..a8f33baea 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -412,9 +412,13 @@ class SlideController(QtGui.QWidget): if item.is_media(): self.onMediaStart(item) elif item.is_command(): + if self.isLive: + blanked = self.blankButton.isChecked() + else: + blanked = False Receiver.send_message(u'%s_start' % item.name.lower(), \ [item.title, item.service_item_path, - item.get_frame_title(), slideno, self.isLive, self.blankButton.isChecked()]) + item.get_frame_title(), slideno, self.isLive, blanked]) self.displayServiceManagerItems(item, slideno) def displayServiceManagerItems(self, serviceItem, slideno): diff --git a/openlp/plugins/presentations/lib/messagelistener.py b/openlp/plugins/presentations/lib/messagelistener.py index c3c9a91f5..08aa7e73a 100644 --- a/openlp/plugins/presentations/lib/messagelistener.py +++ b/openlp/plugins/presentations/lib/messagelistener.py @@ -218,11 +218,12 @@ class MessageListener(object): self.handler = self.mediaitem.findControllerByType(file) if not self.handler: return - + if isLive: - self.liveHandler.addHandler(self.controllers[self.handler], file, isBlank) + controller = self.liveHandler else: - self.previewHandler.addHandler(self.controllers[self.handler], file) + controller = self.previewHandler + controller.addHandler(self.controllers[self.handler], file, isBlank) def slide(self, message): slide, live = self.splitMessage(message) From 142f679098190115b7efc41f032578d1959c8d33 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul.snyman@saturnlaboratories.co.za> Date: Thu, 11 Mar 2010 08:15:29 +0200 Subject: [PATCH 158/164] Fix "red letter" versions of Crosswalk Bibles. --- openlp/plugins/bibles/lib/http.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openlp/plugins/bibles/lib/http.py b/openlp/plugins/bibles/lib/http.py index 7c675dee9..bd4ad8e0b 100644 --- a/openlp/plugins/bibles/lib/http.py +++ b/openlp/plugins/bibles/lib/http.py @@ -223,6 +223,10 @@ class CWExtract(BibleCommon): for part in verse.contents: if str(part)[0] != u'<': versetext = versetext + part + elif part and part.attrMap and part.attrMap[u'class'] == u'WordsOfChrist': + for subpart in part.contents: + if str(subpart)[0] != '<': + versetext = versetext + subpart versetext = versetext.strip(u'\n\r\t ') verses[versenumber] = versetext return SearchResults(bookname, chapter, verses) From b3ce7bf940ca29fb89e411a505abd05b30671e6a Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Thu, 11 Mar 2010 08:11:36 +0000 Subject: [PATCH 159/164] Refactor theme outlines --- openlp/core/lib/renderer.py | 42 ++++++++++++++++++------------- openlp/core/ui/slidecontroller.py | 1 - 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/openlp/core/lib/renderer.py b/openlp/core/lib/renderer.py index 63da0f156..f0da82b0e 100644 --- a/openlp/core/lib/renderer.py +++ b/openlp/core/lib/renderer.py @@ -506,7 +506,7 @@ class Renderer(object): self.mainFont.setPixelSize(self._theme.font_main_proportion) def _get_extent_and_render(self, line, footer, tlcorner=(0, 0), draw=False, - color=None, outline_size=0, outline_color=None): + color=None, outline_size=None, outline_color=None): """ Find bounding box of text - as render_single_line. If draw is set, actually draw the text to the current DC as well return width and @@ -532,31 +532,37 @@ class Renderer(object): font = self.footerFont else: font = self.mainFont - self.painter.setFont(font) - if color is None: - if footer: - self.painter.setPen(QtGui.QColor(self._theme.font_footer_color)) - else: - self.painter.setPen(QtGui.QColor(self._theme.font_main_color)) - else: - self.painter.setPen(QtGui.QColor(color)) - x, y = tlcorner metrics = QtGui.QFontMetrics(font) w = metrics.width(line) h = metrics.height() if draw: - self.painter.drawText(x, y + metrics.ascent(), line) - if self._theme.display_slideTransition: - # Print 2nd image with 70% weight - self.painter2.setFont(font) + self.painter.setFont(font) if color is None: if footer: - self.painter2.setPen(QtGui.QColor(self._theme.font_footer_color)) + pen = QtGui.QColor(self._theme.font_footer_color) else: - self.painter2.setPen(QtGui.QColor(self._theme.font_main_color)) + pen = QtGui.QColor(self._theme.font_main_color) else: - self.painter2.setPen(QtGui.QColor(color)) - if draw: + pen = QtGui.QColor(color) + x, y = tlcorner + if outline_size: + path = QtGui.QPainterPath() + path.addText(QtCore.QPointF(x, y + metrics.ascent()), font, line) + self.painter.setBrush(self.painter.pen().brush()) + self.painter.setPen(QtGui.QPen(QtGui.QColor(outline_color), outline_size)) + self.painter.drawPath(path) + self.painter.setPen(pen) + self.painter.drawText(x, y + metrics.ascent(), line) + if self._theme.display_slideTransition: + # Print 2nd image with 70% weight + if outline_size: + path = QtGui.QPainterPath() + path.addText(QtCore.QPointF(x, y + metrics.ascent()), font, line) + self.painter2.setBrush(self.painter2.pen().brush()) + self.painter2.setPen(QtGui.QPen(QtGui.QColor(outline_color), outline_size)) + self.painter2.drawPath(path) + self.painter2.setFont(font) + self.painter2.setPen(pen) self.painter2.drawText(x, y + metrics.ascent(), line) return (w, h) diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 0fc3fe4a4..31d2e08a5 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -470,7 +470,6 @@ class SlideController(QtGui.QWidget): self.PreviewListWidget.setCellWidget(framenumber, 0, label) slide_height = width * self.parent.RenderManager.screen_ratio self.PreviewListWidget.setItem(framenumber, 0, item) - print row if slide_height != 0: self.PreviewListWidget.setRowHeight(framenumber, slide_height) if self.serviceItem.is_text(): From 27d09b8be8b0561745edc731ebcc2676a4505e54 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Thu, 11 Mar 2010 08:29:32 +0000 Subject: [PATCH 160/164] Blank text screens do not have footers --- openlp/core/lib/serviceitem.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/openlp/core/lib/serviceitem.py b/openlp/core/lib/serviceitem.py index 00d043870..1d609c318 100644 --- a/openlp/core/lib/serviceitem.py +++ b/openlp/core/lib/serviceitem.py @@ -134,8 +134,11 @@ class ServiceItem(object): else: self.RenderManager.set_override_theme(self.theme) format = self._display_frames[row][u'text'].split(u'\n') - frame = self.RenderManager.generate_slide(format, - self.raw_footer) + if format[0]: + frame = self.RenderManager.generate_slide(format, + self.raw_footer) + else: + frame = self.RenderManager.generate_slide(format,u'') return frame def add_from_image(self, path, title, image): From e092dc85cdcc772407ff05681260513393383962 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Thu, 11 Mar 2010 12:53:20 +0000 Subject: [PATCH 161/164] Song edit bug - save and preview on new song --- openlp/core/lib/serviceitem.py | 1 + openlp/plugins/songs/forms/editsongform.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/openlp/core/lib/serviceitem.py b/openlp/core/lib/serviceitem.py index 1d609c318..7d869a610 100644 --- a/openlp/core/lib/serviceitem.py +++ b/openlp/core/lib/serviceitem.py @@ -134,6 +134,7 @@ class ServiceItem(object): else: self.RenderManager.set_override_theme(self.theme) format = self._display_frames[row][u'text'].split(u'\n') + #if screen blank then do not display footer if format[0]: frame = self.RenderManager.generate_slide(format, self.raw_footer) diff --git a/openlp/plugins/songs/forms/editsongform.py b/openlp/plugins/songs/forms/editsongform.py index dd3af03c3..183af6a44 100644 --- a/openlp/plugins/songs/forms/editsongform.py +++ b/openlp/plugins/songs/forms/editsongform.py @@ -169,6 +169,8 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): self.loadAuthors() self.loadTopics() self.loadBooks() + #it's a new song to preview is not possible + self.previewButton.setVisible(False) def loadSong(self, id, preview): log.debug(u'Load Song') From 68287b960405904c886aaba4656a4a5ef309a388 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Thu, 11 Mar 2010 19:28:49 +0000 Subject: [PATCH 162/164] String fixes --- openlp/plugins/songusage/forms/songusagedeletedialog.py | 2 +- openlp/plugins/songusage/forms/songusagedetaildialog.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openlp/plugins/songusage/forms/songusagedeletedialog.py b/openlp/plugins/songusage/forms/songusagedeletedialog.py index 03c9f63ec..e9a9a8603 100644 --- a/openlp/plugins/songusage/forms/songusagedeletedialog.py +++ b/openlp/plugins/songusage/forms/songusagedeletedialog.py @@ -57,4 +57,4 @@ class Ui_SongUsageDeleteDialog(object): QtCore.QMetaObject.connectSlotsByName(AuditDeleteDialog) def retranslateUi(self, AuditDeleteDialog): - AuditDeleteDialog.setWindowTitle(self.trUtf8('Audit Delete')) + AuditDeleteDialog.setWindowTitle(self.trUtf8('Song Usage Delete')) diff --git a/openlp/plugins/songusage/forms/songusagedetaildialog.py b/openlp/plugins/songusage/forms/songusagedetaildialog.py index d6ba2ecfb..411187086 100644 --- a/openlp/plugins/songusage/forms/songusagedetaildialog.py +++ b/openlp/plugins/songusage/forms/songusagedetaildialog.py @@ -85,7 +85,7 @@ class Ui_SongUsageDetailDialog(object): QtCore.QMetaObject.connectSlotsByName(AuditDetailDialog) def retranslateUi(self, AuditDetailDialog): - AuditDetailDialog.setWindowTitle(self.trUtf8('Audit Detail Extraction')) - self.DateRangeGroupBox.setTitle(self.trUtf8('ASelect Date Range')) + AuditDetailDialog.setWindowTitle(self.trUtf8('Song Usage Extraction')) + self.DateRangeGroupBox.setTitle(self.trUtf8('Select Date Range')) self.ToLabel.setText(self.trUtf8('to')) self.FileGroupBox.setTitle(self.trUtf8('Report Location')) From 2607d23c002ff761849a9ca698ec87888693c7fc Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Thu, 11 Mar 2010 21:01:19 +0000 Subject: [PATCH 163/164] String more strings and song edit error --- openlp/plugins/songs/forms/editverseform.py | 2 +- .../songs/forms/songmaintenanceform.py | 20 +++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/openlp/plugins/songs/forms/editverseform.py b/openlp/plugins/songs/forms/editverseform.py index cee84aae5..99dfd6d69 100644 --- a/openlp/plugins/songs/forms/editverseform.py +++ b/openlp/plugins/songs/forms/editverseform.py @@ -77,7 +77,7 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog): def setVerse(self, text, verseCount=0, single=False, tag=u'Verse:1'): posVerse = 0 posSub = 0 - if len(text) == 0: + if len(text) == 0 and not single: text = u'---[Verse:1]---\n' if single: id = tag.split(u':') diff --git a/openlp/plugins/songs/forms/songmaintenanceform.py b/openlp/plugins/songs/forms/songmaintenanceform.py index 4d1d39cce..713cd55a8 100644 --- a/openlp/plugins/songs/forms/songmaintenanceform.py +++ b/openlp/plugins/songs/forms/songmaintenanceform.py @@ -139,7 +139,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): else: QtGui.QMessageBox.critical( self, self.trUtf8('Error'), - self.trUtf8('Couldn\'t add your author!'), + self.trUtf8('Couldn\'t add your author'), QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok)) def onTopicAddButtonClick(self): @@ -150,7 +150,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): else: QtGui.QMessageBox.critical( self, self.trUtf8('Error'), - self.trUtf8('Couldn\'t add your topic!'), + self.trUtf8('Couldn\'t add your topic'), QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok)) def onBookAddButtonClick(self): @@ -162,7 +162,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): else: QtGui.QMessageBox.critical( self, self.trUtf8('Error'), - self.trUtf8('Couldn\'t add your book!'), + self.trUtf8('Couldn\'t add your book'), QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok)) def onAuthorEditButtonClick(self): @@ -182,7 +182,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): else: QtGui.QMessageBox.critical( self, self.trUtf8('Error'), - self.trUtf8('Couldn\'t save your author!'), + self.trUtf8('Couldn\'t save your author'), QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok)) def onTopicEditButtonClick(self): @@ -197,7 +197,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): else: QtGui.QMessageBox.critical( self, self.trUtf8('Error'), - self.trUtf8('Couldn\'t save your topic!'), + self.trUtf8('Couldn\'t save your topic'), QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok)) def onBookEditButtonClick(self): @@ -214,7 +214,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): else: QtGui.QMessageBox.critical( self, self.trUtf8('Error'), - self.trUtf8('Couldn\'t save your book!'), + self.trUtf8('Couldn\'t save your book'), QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok)) def onAuthorDeleteButtonClick(self): @@ -227,7 +227,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): self.trUtf8('Delete Author'), self.trUtf8('Are you sure you want to delete the selected author?'), self.trUtf8('This author can\'t be deleted, they are currently ' - 'assigned to at least one song!'), + 'assigned to at least one song'), self.trUtf8('No author selected!')) def onTopicDeleteButtonClick(self): @@ -240,7 +240,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): self.trUtf8('Delete Topic'), self.trUtf8('Are you sure you want to delete the selected topic?'), self.trUtf8('This topic can\'t be deleted, it is currently ' - 'assigned to at least one song!'), + 'assigned to at least one song'), self.trUtf8('No topic selected!')) def onBookDeleteButtonClick(self): @@ -253,5 +253,5 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): self.trUtf8('Delete Book'), self.trUtf8('Are you sure you want to delete the selected book?'), self.trUtf8('This book can\'t be deleted, it is currently ' - 'assigned to at least one song!'), - self.trUtf8('No book selected!')) \ No newline at end of file + 'assigned to at least one song'), + self.trUtf8('No book selected!')) From cd873b919697178bd3b7bf8a853937bf39b8f92e Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Fri, 12 Mar 2010 07:31:59 +0000 Subject: [PATCH 164/164] add full stops --- openlp/plugins/images/imageplugin.py | 2 +- .../plugins/songs/forms/songmaintenanceform.py | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/openlp/plugins/images/imageplugin.py b/openlp/plugins/images/imageplugin.py index 40e9c1e1d..37219b5db 100644 --- a/openlp/plugins/images/imageplugin.py +++ b/openlp/plugins/images/imageplugin.py @@ -61,6 +61,6 @@ class ImagePlugin(Plugin): 'together and presented on the live controller it is possible ' 'to turn them into a timed loop.<br<br>From the plugin if the ' '<i>Override background</i> is chosen and an image is selected ' - 'any somgs which are rendered will use the selected image from ' + 'any songs which are rendered will use the selected image from ' 'the background instead of the one provied by the theme.<br>') return about_text diff --git a/openlp/plugins/songs/forms/songmaintenanceform.py b/openlp/plugins/songs/forms/songmaintenanceform.py index 713cd55a8..d8d6bb5b2 100644 --- a/openlp/plugins/songs/forms/songmaintenanceform.py +++ b/openlp/plugins/songs/forms/songmaintenanceform.py @@ -139,7 +139,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): else: QtGui.QMessageBox.critical( self, self.trUtf8('Error'), - self.trUtf8('Couldn\'t add your author'), + self.trUtf8('Couldn\'t add your author.'), QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok)) def onTopicAddButtonClick(self): @@ -150,7 +150,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): else: QtGui.QMessageBox.critical( self, self.trUtf8('Error'), - self.trUtf8('Couldn\'t add your topic'), + self.trUtf8('Couldn\'t add your topic.'), QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok)) def onBookAddButtonClick(self): @@ -162,7 +162,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): else: QtGui.QMessageBox.critical( self, self.trUtf8('Error'), - self.trUtf8('Couldn\'t add your book'), + self.trUtf8('Couldn\'t add your book.'), QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok)) def onAuthorEditButtonClick(self): @@ -182,7 +182,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): else: QtGui.QMessageBox.critical( self, self.trUtf8('Error'), - self.trUtf8('Couldn\'t save your author'), + self.trUtf8('Couldn\'t save your author.'), QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok)) def onTopicEditButtonClick(self): @@ -197,7 +197,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): else: QtGui.QMessageBox.critical( self, self.trUtf8('Error'), - self.trUtf8('Couldn\'t save your topic'), + self.trUtf8('Couldn\'t save your topic.'), QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok)) def onBookEditButtonClick(self): @@ -214,7 +214,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): else: QtGui.QMessageBox.critical( self, self.trUtf8('Error'), - self.trUtf8('Couldn\'t save your book'), + self.trUtf8('Couldn\'t save your book.'), QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok)) def onAuthorDeleteButtonClick(self): @@ -227,7 +227,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): self.trUtf8('Delete Author'), self.trUtf8('Are you sure you want to delete the selected author?'), self.trUtf8('This author can\'t be deleted, they are currently ' - 'assigned to at least one song'), + 'assigned to at least one song.'), self.trUtf8('No author selected!')) def onTopicDeleteButtonClick(self): @@ -240,7 +240,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): self.trUtf8('Delete Topic'), self.trUtf8('Are you sure you want to delete the selected topic?'), self.trUtf8('This topic can\'t be deleted, it is currently ' - 'assigned to at least one song'), + 'assigned to at least one song.'), self.trUtf8('No topic selected!')) def onBookDeleteButtonClick(self): @@ -253,5 +253,5 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): self.trUtf8('Delete Book'), self.trUtf8('Are you sure you want to delete the selected book?'), self.trUtf8('This book can\'t be deleted, it is currently ' - 'assigned to at least one song'), + 'assigned to at least one song.'), self.trUtf8('No book selected!'))