From 21a88085e37b0f1eb5b94dea3c59529115241fdd Mon Sep 17 00:00:00 2001 From: Samuel Findlay Date: Mon, 30 Apr 2012 00:08:25 +1000 Subject: [PATCH 01/35] First attempt at class openlp.plugins.songs.lib.PowerSongImport --- openlp/core/ui/wizard.py | 1 + openlp/plugins/songs/lib/powersongimport.py | 142 ++++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 openlp/plugins/songs/lib/powersongimport.py diff --git a/openlp/core/ui/wizard.py b/openlp/core/ui/wizard.py index 5369c9799..74dcfceaf 100644 --- a/openlp/core/ui/wizard.py +++ b/openlp/core/ui/wizard.py @@ -53,6 +53,7 @@ class WizardStrings(object): OL = u'OpenLyrics' OS = u'OpenSong' OSIS = u'OSIS' + PS = u'PowerSong' SB = u'SongBeamer' SoF = u'Songs of Fellowship' SSP = u'SongShow Plus' diff --git a/openlp/plugins/songs/lib/powersongimport.py b/openlp/plugins/songs/lib/powersongimport.py new file mode 100644 index 000000000..973f87795 --- /dev/null +++ b/openlp/plugins/songs/lib/powersongimport.py @@ -0,0 +1,142 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2012 Raoul Snyman # +# Portions copyright (c) 2008-2012 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # +# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias # +# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### +""" +The :mod:`powersongimport` module provides the functionality for importing +PowerSong songs into the OpenLP database. +""" +import logging + +from openlp.core.lib import translate +from openlp.plugins.songs.lib.songimport import SongImport + +log = logging.getLogger(__name__) + +class PowerSongImport(SongImport): + """ + The :class:`PowerSongImport` class provides the ability to import song files + from PowerSong. + + **PowerSong Song File Format:** + + * Encoded as UTF-8. + * The file has a number of fields, with the song metadata fields first, + followed by the lyrics fields. + + Fields: + Each field begins with one of four labels, each of which begin with one + non-printing byte: + + * ``ENQ`` (0x05) ``TITLE`` + * ``ACK`` (0x06) ``AUTHOR`` + * ``CR`` (0x0D) ``COPYRIGHTLINE`` + * ``EOT`` (0x04) ``PART`` + + The field label is separated from the field contents by one random byte. + Each field ends at the next field label, or at the end of the file. + + Metadata fields: + * Every PowerSong file begins with a TITLE field. + * This is followed by zero or more AUTHOR fields. + * The next field is always COPYRIGHTLINE, but it may be empty (in which + case the byte following the label is the null byte 0x00). + When the field contents are not empty, the first byte is 0xC2 and + should be discarded. + This field may contain a CCLI number at the end: e.g. "CCLI 176263" + + Lyrics fields: + * The COPYRIGHTLINE field is followed by zero or more PART fields, each + of which contains one verse. + * Lines have Windows line endings ``CRLF`` (0x0D, 0x0A). + * There is no concept of verse types. + + Valid extensions for a PowerSong song file are: + + * .song + """ + + def __init__(self, manager, **kwargs): + """ + Initialise the PowerSong importer. + """ + SongImport.__init__(self, manager, **kwargs) + + def doImport(self): + """ + Receive a single file or a list of files to import. + """ + if isinstance(self.importSource, list): + self.importWizard.progressBar.setMaximum(len(self.importSource)) + for file in self.importSource: + if self.stopImportFlag: + return + self.setDefaults() + with open(file, 'rb') as song_file: + # Check file is valid PowerSong song format + if song_file.read(6) != u'\x05TITLE': + self.logError(file, unicode( + translate('SongsPlugin.PowerSongSongImport', + ('Invalid PowerSong song file. Missing ' + '"\x05TITLE" header.')))) + continue + song_data = song_file.read() + first_part, sep, song_data = song_data.partition( + u'\x0DCOPYRIGHTLINE') + if sep == '': + self.logError(file, unicode( + translate('SongsPlugin.PowerSongSongImport', + ('Invalid PowerSong song file. Missing ' + '"\x0DCOPYRIGHTLINE" string.')))) + continue + title_authors = first_part.split(u'\x06AUTHOR') + # Get the song title + self.title = title_authors[0][1:] + # Extract the author(s) + for author in title_authors[1:]: + self.parseAuthor(author[1:]) + # Get copyright and CCLI number + copyright, sep, song_data = song_data.partition( + u'\x04PART') + if sep == '': + self.logError(file, unicode( + translate('SongsPlugin.PowerSongSongImport', + ('No verses found. Missing ' + '"\x04PART" string(s).')))) + continue + copyright, sep, ccli_no = copyright[1:].rpartition(u'CCLI ') + if copyright[0] == u'\xC2': + copyright = copyright[1:] + self.addCopyright(copyright) + if ccli_no != '': + ccli_no = ccli_no.strip() + if ccli_no.isdigit(): + self.ccliNumber = ccli_no + # Get the verse(s) + verses = song_data.split(u'\x04PART') + for verse in verses: + self.addVerse(verse[1:]) + if not self.finish(): + self.logError(file) From ea9bfb160d4f6a3bb8f1900c25fbf6e433b5f4fc Mon Sep 17 00:00:00 2001 From: Samuel Findlay Date: Mon, 30 Apr 2012 16:24:04 +1000 Subject: [PATCH 02/35] Integrated module openlp.plugins.songs.lib.powersongimport --- openlp/plugins/songs/forms/songimportform.py | 43 ++++++++++++++++++++ openlp/plugins/songs/lib/importer.py | 15 ++++--- openlp/plugins/songs/lib/powersongimport.py | 1 + 3 files changed, 54 insertions(+), 5 deletions(-) diff --git a/openlp/plugins/songs/forms/songimportform.py b/openlp/plugins/songs/forms/songimportform.py index 4a44c30ef..686c3d69e 100644 --- a/openlp/plugins/songs/forms/songimportform.py +++ b/openlp/plugins/songs/forms/songimportform.py @@ -171,6 +171,12 @@ class SongImportForm(OpenLPWizard): QtCore.QObject.connect(self.foilPresenterRemoveButton, QtCore.SIGNAL(u'clicked()'), self.onFoilPresenterRemoveButtonClicked) + QtCore.QObject.connect(self.powerSongAddButton, + QtCore.SIGNAL(u'clicked()'), + self.powerSongAddButtonClicked) + QtCore.QObject.connect(self.powerSongRemoveButton, + QtCore.SIGNAL(u'clicked()'), + self.powerSongRemoveButtonClicked) def addCustomPages(self): """ @@ -217,6 +223,8 @@ class SongImportForm(OpenLPWizard): self.addFileSelectItem(u'foilPresenter') # Open Song self.addFileSelectItem(u'openSong', u'OpenSong') + # PowerSong + self.addFileSelectItem(u'powerSong') # SongBeamer self.addFileSelectItem(u'songBeamer') # Song Show Plus @@ -264,6 +272,8 @@ class SongImportForm(OpenLPWizard): self.formatComboBox.setItemText( SongFormat.FoilPresenter, WizardStrings.FP) self.formatComboBox.setItemText(SongFormat.OpenSong, WizardStrings.OS) + self.formatComboBox.setItemText( + SongFormat.PowerSong, WizardStrings.PS) self.formatComboBox.setItemText( SongFormat.SongBeamer, WizardStrings.SB) self.formatComboBox.setItemText( @@ -305,6 +315,10 @@ class SongImportForm(OpenLPWizard): translate('SongsPlugin.ImportWizardForm', 'Add Files...')) self.dreamBeamRemoveButton.setText( translate('SongsPlugin.ImportWizardForm', 'Remove File(s)')) + self.powerSongAddButton.setText( + translate('SongsPlugin.ImportWizardForm', 'Add Files...')) + self.powerSongRemoveButton.setText( + translate('SongsPlugin.ImportWizardForm', 'Remove File(s)')) self.songsOfFellowshipAddButton.setText( translate('SongsPlugin.ImportWizardForm', 'Add Files...')) self.songsOfFellowshipRemoveButton.setText( @@ -417,6 +431,12 @@ class SongImportForm(OpenLPWizard): WizardStrings.YouSpecifyFile % WizardStrings.DB) self.dreamBeamAddButton.setFocus() return False + elif source_format == SongFormat.PowerSong: + if self.powerSongFileListWidget.count() == 0: + critical_error_message_box(UiStrings().NFSp, + WizardStrings.YouSpecifyFile % WizardStrings.PS) + self.powerSongAddButton.setFocus() + return False elif source_format == SongFormat.SongsOfFellowship: if self.songsOfFellowshipFileListWidget.count() == 0: critical_error_message_box(UiStrings().NFSp, @@ -600,6 +620,22 @@ class SongImportForm(OpenLPWizard): """ self.removeSelectedItems(self.dreamBeamFileListWidget) + def onPowerSongAddButtonClicked(self): + """ + Get PowerSong song database files + """ + self.getFiles(WizardStrings.OpenTypeFile % WizardStrings.PS, + self.powerSongFileListWidget, u'%s (*.song)' + % translate('SongsPlugin.ImportWizardForm', + 'PowerSong Song Files') + ) + + def onPowerSongRemoveButtonClicked(self): + """ + Remove selected PowerSong files from the import list + """ + self.removeSelectedItems(self.powerSongFileListWidget) + def onSongsOfFellowshipAddButtonClicked(self): """ Get Songs of Fellowship song database files @@ -717,6 +753,7 @@ class SongImportForm(OpenLPWizard): self.wordsOfWorshipFileListWidget.clear() self.ccliFileListWidget.clear() self.dreamBeamFileListWidget.clear() + self.powerSongFileListWidget.clear() self.songsOfFellowshipFileListWidget.clear() self.genericFileListWidget.clear() self.easySlidesFilenameEdit.setText(u'') @@ -784,6 +821,12 @@ class SongImportForm(OpenLPWizard): filenames=self.getListOfFiles( self.dreamBeamFileListWidget) ) + elif source_format == SongFormat.PowerSong: + # Import PowerSong songs + importer = self.plugin.importSongs(SongFormat.PowerSong, + filenames=self.getListOfFiles( + self.powerSongFileListWidget) + ) elif source_format == SongFormat.SongsOfFellowship: # Import a Songs of Fellowship RTF file importer = self.plugin.importSongs(SongFormat.SongsOfFellowship, diff --git a/openlp/plugins/songs/lib/importer.py b/openlp/plugins/songs/lib/importer.py index 28a57339e..9dde9f0af 100644 --- a/openlp/plugins/songs/lib/importer.py +++ b/openlp/plugins/songs/lib/importer.py @@ -36,6 +36,7 @@ from openlyricsimport import OpenLyricsImport from wowimport import WowImport from cclifileimport import CCLIFileImport from dreambeamimport import DreamBeamImport +from powersongimport import PowerSongImport from ewimport import EasyWorshipSongImport from songbeamerimport import SongBeamerImport from songshowplusimport import SongShowPlusImport @@ -79,11 +80,12 @@ class SongFormat(object): EasyWorship = 7 FoilPresenter = 8 OpenSong = 9 - SongBeamer = 10 - SongShowPlus = 11 - SongsOfFellowship = 12 - WordsOfWorship = 13 - #CSV = 14 + PowerSong = 10 + SongBeamer = 11 + SongShowPlus = 12 + SongsOfFellowship = 13 + WordsOfWorship = 14 + #CSV = 15 @staticmethod def get_class(format): @@ -111,6 +113,8 @@ class SongFormat(object): return CCLIFileImport elif format == SongFormat.DreamBeam: return DreamBeamImport + elif format == SongFormat.PowerSong: + return PowerSongImport elif format == SongFormat.EasySlides: return EasySlidesImport elif format == SongFormat.EasyWorship: @@ -139,6 +143,7 @@ class SongFormat(object): SongFormat.EasyWorship, SongFormat.FoilPresenter, SongFormat.OpenSong, + SongFormat.PowerSong, SongFormat.SongBeamer, SongFormat.SongShowPlus, SongFormat.SongsOfFellowship, diff --git a/openlp/plugins/songs/lib/powersongimport.py b/openlp/plugins/songs/lib/powersongimport.py index 973f87795..37d46e35c 100644 --- a/openlp/plugins/songs/lib/powersongimport.py +++ b/openlp/plugins/songs/lib/powersongimport.py @@ -103,6 +103,7 @@ class PowerSongImport(SongImport): '"\x05TITLE" header.')))) continue song_data = song_file.read() + # Extract title and author fields first_part, sep, song_data = song_data.partition( u'\x0DCOPYRIGHTLINE') if sep == '': From 370603c779e58a83c8e34ce4097c144b0644eb90 Mon Sep 17 00:00:00 2001 From: Samuel Findlay Date: Mon, 30 Apr 2012 20:57:44 +1000 Subject: [PATCH 03/35] PowerSong importer working. Successful on test set of 1057 songs --- openlp/plugins/songs/forms/songimportform.py | 4 +- openlp/plugins/songs/lib/powersongimport.py | 48 ++++++++++++++------ 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/openlp/plugins/songs/forms/songimportform.py b/openlp/plugins/songs/forms/songimportform.py index 686c3d69e..0cacae612 100644 --- a/openlp/plugins/songs/forms/songimportform.py +++ b/openlp/plugins/songs/forms/songimportform.py @@ -173,10 +173,10 @@ class SongImportForm(OpenLPWizard): self.onFoilPresenterRemoveButtonClicked) QtCore.QObject.connect(self.powerSongAddButton, QtCore.SIGNAL(u'clicked()'), - self.powerSongAddButtonClicked) + self.onPowerSongAddButtonClicked) QtCore.QObject.connect(self.powerSongRemoveButton, QtCore.SIGNAL(u'clicked()'), - self.powerSongRemoveButtonClicked) + self.onPowerSongRemoveButtonClicked) def addCustomPages(self): """ diff --git a/openlp/plugins/songs/lib/powersongimport.py b/openlp/plugins/songs/lib/powersongimport.py index 37d46e35c..207777570 100644 --- a/openlp/plugins/songs/lib/powersongimport.py +++ b/openlp/plugins/songs/lib/powersongimport.py @@ -29,6 +29,7 @@ The :mod:`powersongimport` module provides the functionality for importing PowerSong songs into the OpenLP database. """ import logging +import re from openlp.core.lib import translate from openlp.plugins.songs.lib.songimport import SongImport @@ -52,7 +53,7 @@ class PowerSongImport(SongImport): * ``ENQ`` (0x05) ``TITLE`` * ``ACK`` (0x06) ``AUTHOR`` - * ``CR`` (0x0D) ``COPYRIGHTLINE`` + * ``CR`` (0x0d) ``COPYRIGHTLINE`` * ``EOT`` (0x04) ``PART`` The field label is separated from the field contents by one random byte. @@ -63,14 +64,14 @@ class PowerSongImport(SongImport): * This is followed by zero or more AUTHOR fields. * The next field is always COPYRIGHTLINE, but it may be empty (in which case the byte following the label is the null byte 0x00). - When the field contents are not empty, the first byte is 0xC2 and + When the field contents are not empty, the first byte is 0xc2 and should be discarded. This field may contain a CCLI number at the end: e.g. "CCLI 176263" Lyrics fields: * The COPYRIGHTLINE field is followed by zero or more PART fields, each of which contains one verse. - * Lines have Windows line endings ``CRLF`` (0x0D, 0x0A). + * Lines have Windows line endings ``CRLF`` (0x0d, 0x0a). * There is no concept of verse types. Valid extensions for a PowerSong song file are: @@ -102,11 +103,11 @@ class PowerSongImport(SongImport): ('Invalid PowerSong song file. Missing ' '"\x05TITLE" header.')))) continue - song_data = song_file.read() + song_data = unicode(song_file.read(), u'utf-8', u'replace') # Extract title and author fields first_part, sep, song_data = song_data.partition( u'\x0DCOPYRIGHTLINE') - if sep == '': + if not sep: self.logError(file, unicode( translate('SongsPlugin.PowerSongSongImport', ('Invalid PowerSong song file. Missing ' @@ -114,30 +115,47 @@ class PowerSongImport(SongImport): continue title_authors = first_part.split(u'\x06AUTHOR') # Get the song title - self.title = title_authors[0][1:] + self.title = self.stripControlChars(title_authors[0][1:]) # Extract the author(s) for author in title_authors[1:]: - self.parseAuthor(author[1:]) + self.parseAuthor(self.stripControlChars(author[1:])) # Get copyright and CCLI number copyright, sep, song_data = song_data.partition( u'\x04PART') - if sep == '': + if not sep: self.logError(file, unicode( translate('SongsPlugin.PowerSongSongImport', ('No verses found. Missing ' - '"\x04PART" string(s).')))) + '"\x04PART" string.')))) continue copyright, sep, ccli_no = copyright[1:].rpartition(u'CCLI ') - if copyright[0] == u'\xC2': - copyright = copyright[1:] - self.addCopyright(copyright) - if ccli_no != '': + if not sep: + copyright = ccli_no + ccli_no = u'' + if copyright: + if copyright[0] == u'\u00c2': + copyright = copyright[1:] + self.addCopyright(self.stripControlChars( + copyright.rstrip(u'\n'))) + if ccli_no: ccli_no = ccli_no.strip() if ccli_no.isdigit(): - self.ccliNumber = ccli_no + self.ccliNumber = self.stripControlChars(ccli_no) # Get the verse(s) verses = song_data.split(u'\x04PART') for verse in verses: - self.addVerse(verse[1:]) + self.addVerse(self.stripControlChars(verse[1:])) if not self.finish(): self.logError(file) + + def stripControlChars(self, text): + """ + Get rid of ASCII control characters. + + Illegals chars are ASCII code points 0-31 and 127, except: + * ``HT`` (0x09) - Tab + * ``LF`` (0x0a) - Line feed + * ``CR`` (0x0d) - Carriage return + """ + ILLEGAL_CHARS = u'([\x00-\x08\x0b-\x0c\x0e-\x1f\x7f])' + return re.sub(ILLEGAL_CHARS, '', text) \ No newline at end of file From 1184e9219d3d4e4abd9d34e4a18783a90de74db1 Mon Sep 17 00:00:00 2001 From: Samuel Findlay Date: Mon, 30 Apr 2012 22:19:36 +1000 Subject: [PATCH 04/35] Small fixes for comments typos in songs.lib modules --- openlp/plugins/songs/lib/importer.py | 2 +- openlp/plugins/songs/lib/powersongimport.py | 3 +-- openlp/plugins/songs/lib/songimport.py | 2 +- openlp/plugins/songs/lib/wowimport.py | 6 +++--- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/openlp/plugins/songs/lib/importer.py b/openlp/plugins/songs/lib/importer.py index 9dde9f0af..16d943a73 100644 --- a/openlp/plugins/songs/lib/importer.py +++ b/openlp/plugins/songs/lib/importer.py @@ -90,7 +90,7 @@ class SongFormat(object): @staticmethod def get_class(format): """ - Return the appropriate imeplementation class. + Return the appropriate implementation class. ``format`` The song format. diff --git a/openlp/plugins/songs/lib/powersongimport.py b/openlp/plugins/songs/lib/powersongimport.py index 207777570..3c3d9a641 100644 --- a/openlp/plugins/songs/lib/powersongimport.py +++ b/openlp/plugins/songs/lib/powersongimport.py @@ -75,8 +75,7 @@ class PowerSongImport(SongImport): * There is no concept of verse types. Valid extensions for a PowerSong song file are: - - * .song + * .song """ def __init__(self, manager, **kwargs): diff --git a/openlp/plugins/songs/lib/songimport.py b/openlp/plugins/songs/lib/songimport.py index 6fd9dd403..b3ceb49ec 100644 --- a/openlp/plugins/songs/lib/songimport.py +++ b/openlp/plugins/songs/lib/songimport.py @@ -111,7 +111,7 @@ class SongImport(QtCore.QObject): instance a database), then this should be the song's title. ``reason`` - The reason, why the import failed. The string should be as + The reason why the import failed. The string should be as informative as possible. """ self.setDefaults() diff --git a/openlp/plugins/songs/lib/wowimport.py b/openlp/plugins/songs/lib/wowimport.py index 99f448736..97a11d873 100644 --- a/openlp/plugins/songs/lib/wowimport.py +++ b/openlp/plugins/songs/lib/wowimport.py @@ -71,7 +71,7 @@ class WowImport(SongImport): * ``SOH`` (0x01) - Chorus * ``STX`` (0x02) - Bridge - Blocks are seperated by two bytes. The first byte is 0x01, and the + Blocks are separated by two bytes. The first byte is 0x01, and the second byte is 0x80. Lines: @@ -126,7 +126,7 @@ class WowImport(SongImport): ('Invalid Words of Worship song file. Missing ' '"CSongDoc::CBlock" string.')))) continue - # Seek to the beging of the first block + # Seek to the beginning of the first block song_data.seek(82) for block in range(no_of_blocks): self.linesToRead = ord(song_data.read(4)[:1]) @@ -140,7 +140,7 @@ class WowImport(SongImport): block_text += self.lineText self.linesToRead -= 1 block_type = BLOCK_TYPES[ord(song_data.read(4)[:1])] - # Blocks are seperated by 2 bytes, skip them, but not if + # Blocks are separated by 2 bytes, skip them, but not if # this is the last block! if block + 1 < no_of_blocks: song_data.seek(2, os.SEEK_CUR) From 63b71802abb7f4ff4c450c2dd5aa4e62affd6db6 Mon Sep 17 00:00:00 2001 From: Samuel Findlay Date: Tue, 1 May 2012 23:51:46 +1000 Subject: [PATCH 05/35] Rewrote PowerSongImport class to read variable-length strings directly from file, rather than searching for them. Other minor fixes. --- openlp/plugins/songs/lib/powersongimport.py | 155 ++++++++++---------- openlp/plugins/songs/lib/songimport.py | 2 +- 2 files changed, 77 insertions(+), 80 deletions(-) diff --git a/openlp/plugins/songs/lib/powersongimport.py b/openlp/plugins/songs/lib/powersongimport.py index 3c3d9a641..e2ba13f68 100644 --- a/openlp/plugins/songs/lib/powersongimport.py +++ b/openlp/plugins/songs/lib/powersongimport.py @@ -29,7 +29,6 @@ The :mod:`powersongimport` module provides the functionality for importing PowerSong songs into the OpenLP database. """ import logging -import re from openlp.core.lib import translate from openlp.plugins.songs.lib.songimport import SongImport @@ -43,34 +42,27 @@ class PowerSongImport(SongImport): **PowerSong Song File Format:** - * Encoded as UTF-8. - * The file has a number of fields, with the song metadata fields first, - followed by the lyrics fields. + The file has a number of label-field pairs of variable length. - Fields: - Each field begins with one of four labels, each of which begin with one - non-printing byte: - - * ``ENQ`` (0x05) ``TITLE`` - * ``ACK`` (0x06) ``AUTHOR`` - * ``CR`` (0x0d) ``COPYRIGHTLINE`` - * ``EOT`` (0x04) ``PART`` - - The field label is separated from the field contents by one random byte. - Each field ends at the next field label, or at the end of the file. + Labels and Fields: + * Every label and field is preceded by an integer which specifies its + byte-length. + * If the length < 128 bytes, only one byte is used to encode + the length integer. + * But if it's greater, as many bytes are used as necessary: + * the first byte = (length % 128) + 128 + * the next byte = length / 128 + * another byte is only used if (length / 128) >= 128 + * and so on (3 bytes needed iff length > 16383) Metadata fields: * Every PowerSong file begins with a TITLE field. * This is followed by zero or more AUTHOR fields. - * The next field is always COPYRIGHTLINE, but it may be empty (in which - case the byte following the label is the null byte 0x00). - When the field contents are not empty, the first byte is 0xc2 and - should be discarded. - This field may contain a CCLI number at the end: e.g. "CCLI 176263" + * The next label is always COPYRIGHTLINE, but its field may be empty. + This field may also contain a CCLI number: e.g. "CCLI 176263". Lyrics fields: - * The COPYRIGHTLINE field is followed by zero or more PART fields, each - of which contains one verse. + * Each verse is contained in a PART field. * Lines have Windows line endings ``CRLF`` (0x0d, 0x0a). * There is no concept of verse types. @@ -78,12 +70,6 @@ class PowerSongImport(SongImport): * .song """ - def __init__(self, manager, **kwargs): - """ - Initialise the PowerSong importer. - """ - SongImport.__init__(self, manager, **kwargs) - def doImport(self): """ Receive a single file or a list of files to import. @@ -94,67 +80,78 @@ class PowerSongImport(SongImport): if self.stopImportFlag: return self.setDefaults() - with open(file, 'rb') as song_file: - # Check file is valid PowerSong song format - if song_file.read(6) != u'\x05TITLE': - self.logError(file, unicode( - translate('SongsPlugin.PowerSongSongImport', - ('Invalid PowerSong song file. Missing ' - '"\x05TITLE" header.')))) - continue - song_data = unicode(song_file.read(), u'utf-8', u'replace') - # Extract title and author fields - first_part, sep, song_data = song_data.partition( - u'\x0DCOPYRIGHTLINE') - if not sep: + with open(file, 'rb') as self.song_file: + # Get title and check file is valid PowerSong song format + label, field = self.readLabelField() + if label != u'TITLE': self.logError(file, unicode( translate('SongsPlugin.PowerSongSongImport', ('Invalid PowerSong song file. Missing ' - '"\x0DCOPYRIGHTLINE" string.')))) + '"TITLE" header.')))) continue - title_authors = first_part.split(u'\x06AUTHOR') - # Get the song title - self.title = self.stripControlChars(title_authors[0][1:]) - # Extract the author(s) - for author in title_authors[1:]: - self.parseAuthor(self.stripControlChars(author[1:])) - # Get copyright and CCLI number - copyright, sep, song_data = song_data.partition( - u'\x04PART') - if not sep: + else: + self.title = field.replace(u'\n', u' ') + while label: + label, field = self.readLabelField() + # Get the author(s) + if label == u'AUTHOR': + self.parseAuthor(field) + # Get copyright and look for CCLI number + elif label == u'COPYRIGHTLINE': + found_copyright = True + copyright, sep, ccli_no = field.rpartition(u'CCLI') + if not sep: + copyright = ccli_no + ccli_no = u'' + if copyright: + self.addCopyright(copyright.rstrip( + u'\n').replace(u'\n', u' ')) + if ccli_no: + ccli_no = ccli_no.strip(u' :') + if ccli_no.isdigit(): + self.ccliNumber = ccli_no + # Get verse(s) + elif label == u'PART': + self.addVerse(field) + # Check for copyright label + if not found_copyright: self.logError(file, unicode( translate('SongsPlugin.PowerSongSongImport', - ('No verses found. Missing ' - '"\x04PART" string.')))) + ('"%s" Invalid PowerSong song file. Missing ' + '"COPYRIGHTLINE" string.' % self.title)))) + continue + # Check for at least one verse + if not self.verses: + self.logError(file, unicode( + translate('SongsPlugin.PowerSongSongImport', + ('"%s" No verses found. Missing "PART" string.' + % self.title)))) continue - copyright, sep, ccli_no = copyright[1:].rpartition(u'CCLI ') - if not sep: - copyright = ccli_no - ccli_no = u'' - if copyright: - if copyright[0] == u'\u00c2': - copyright = copyright[1:] - self.addCopyright(self.stripControlChars( - copyright.rstrip(u'\n'))) - if ccli_no: - ccli_no = ccli_no.strip() - if ccli_no.isdigit(): - self.ccliNumber = self.stripControlChars(ccli_no) - # Get the verse(s) - verses = song_data.split(u'\x04PART') - for verse in verses: - self.addVerse(self.stripControlChars(verse[1:])) if not self.finish(): self.logError(file) - def stripControlChars(self, text): + def readLabelField(self): """ - Get rid of ASCII control characters. + Return as a 2-tuple the next two variable-length strings from song file + """ + label = unicode(self.song_file.read( + self.readLength()), u'utf-8', u'ignore') + if label: + field = unicode(self.song_file.read( + self.readLength()), u'utf-8', u'ignore') + else: + field = u'' + return label, field - Illegals chars are ASCII code points 0-31 and 127, except: - * ``HT`` (0x09) - Tab - * ``LF`` (0x0a) - Line feed - * ``CR`` (0x0d) - Carriage return + def readLength(self): """ - ILLEGAL_CHARS = u'([\x00-\x08\x0b-\x0c\x0e-\x1f\x7f])' - return re.sub(ILLEGAL_CHARS, '', text) \ No newline at end of file + Return the byte-length of the next variable-length string in song file + """ + this_byte_char = self.song_file.read(1) + if not this_byte_char: + return 0 + this_byte = ord(this_byte_char) + if this_byte < 128: + return this_byte + else: + return (self.readLength() * 128) + (this_byte - 128) diff --git a/openlp/plugins/songs/lib/songimport.py b/openlp/plugins/songs/lib/songimport.py index b3ceb49ec..79e960919 100644 --- a/openlp/plugins/songs/lib/songimport.py +++ b/openlp/plugins/songs/lib/songimport.py @@ -107,7 +107,7 @@ class SongImport(QtCore.QObject): ``filepath`` This should be the file path if ``self.importSource`` is a list - with different files. If it is not a list, but a single file (for + with different files. If it is not a list, but a single file (for instance a database), then this should be the song's title. ``reason`` From 8877484aea388bd84ac786ab9f9ed1c44271c660 Mon Sep 17 00:00:00 2001 From: Samuel Findlay Date: Wed, 2 May 2012 19:14:30 +1000 Subject: [PATCH 06/35] Tidy up code, stonger error checking. --- openlp/plugins/songs/lib/powersongimport.py | 139 +++++++++++--------- 1 file changed, 78 insertions(+), 61 deletions(-) diff --git a/openlp/plugins/songs/lib/powersongimport.py b/openlp/plugins/songs/lib/powersongimport.py index e2ba13f68..1b99d756f 100644 --- a/openlp/plugins/songs/lib/powersongimport.py +++ b/openlp/plugins/songs/lib/powersongimport.py @@ -72,67 +72,67 @@ class PowerSongImport(SongImport): def doImport(self): """ - Receive a single file or a list of files to import. + Receive a list of files to import. """ - if isinstance(self.importSource, list): - self.importWizard.progressBar.setMaximum(len(self.importSource)) - for file in self.importSource: - if self.stopImportFlag: - return - self.setDefaults() - with open(file, 'rb') as self.song_file: - # Get title and check file is valid PowerSong song format + if not isinstance(self.importSource, list): + return + self.importWizard.progressBar.setMaximum(len(self.importSource)) + for file in self.importSource: + if self.stopImportFlag: + return + self.setDefaults() + parse_error = False + with open(file, 'rb') as self.song_file: + # Get title to check file is valid PowerSong song format + label, field = self.readLabelField() + if label == u'TITLE': + self.title = field.replace(u'\n', u' ') + else: + self.logError(file, unicode( + translate('SongsPlugin.PowerSongSongImport', \ + 'Invalid PowerSong file. Missing "TITLE" header.'))) + continue + # Get rest of fields from file + while True: label, field = self.readLabelField() - if label != u'TITLE': - self.logError(file, unicode( - translate('SongsPlugin.PowerSongSongImport', - ('Invalid PowerSong song file. Missing ' - '"TITLE" header.')))) - continue + if not label: + break + if label == u'AUTHOR': + self.parseAuthor(field) + elif label == u'COPYRIGHTLINE': + found_copyright = True + self.parseCopyrightCCLI(field) + elif label == u'PART': + self.addVerse(field) else: - self.title = field.replace(u'\n', u' ') - while label: - label, field = self.readLabelField() - # Get the author(s) - if label == u'AUTHOR': - self.parseAuthor(field) - # Get copyright and look for CCLI number - elif label == u'COPYRIGHTLINE': - found_copyright = True - copyright, sep, ccli_no = field.rpartition(u'CCLI') - if not sep: - copyright = ccli_no - ccli_no = u'' - if copyright: - self.addCopyright(copyright.rstrip( - u'\n').replace(u'\n', u' ')) - if ccli_no: - ccli_no = ccli_no.strip(u' :') - if ccli_no.isdigit(): - self.ccliNumber = ccli_no - # Get verse(s) - elif label == u'PART': - self.addVerse(field) - # Check for copyright label - if not found_copyright: + parse_error = True self.logError(file, unicode( - translate('SongsPlugin.PowerSongSongImport', - ('"%s" Invalid PowerSong song file. Missing ' - '"COPYRIGHTLINE" string.' % self.title)))) - continue - # Check for at least one verse - if not self.verses: - self.logError(file, unicode( - translate('SongsPlugin.PowerSongSongImport', - ('"%s" No verses found. Missing "PART" string.' - % self.title)))) - continue - if not self.finish(): - self.logError(file) + translate('SongsPlugin.PowerSongSongImport', \ + '"%s" Invalid PowerSong file. Unknown header: "%s".' + % (self.title, label)))) + break + if parse_error: + continue + # Check that file had COPYRIGHTLINE label + if not found_copyright: + self.logError(file, unicode( + translate('SongsPlugin.PowerSongSongImport', \ + '"%s" Invalid PowerSong file. Missing "COPYRIGHTLINE" \ + header.' % self.title))) + continue + # Check that file had at least one verse + if not self.verses: + self.logError(file, unicode( + translate('SongsPlugin.PowerSongSongImport', \ + '"%s" Verses not found. Missing "PART" header.' + % self.title))) + continue + if not self.finish(): + self.logError(file) def readLabelField(self): """ - Return as a 2-tuple the next two variable-length strings from song file + Read (as a 2-tuple) the next two variable-length strings """ label = unicode(self.song_file.read( self.readLength()), u'utf-8', u'ignore') @@ -145,13 +145,30 @@ class PowerSongImport(SongImport): def readLength(self): """ - Return the byte-length of the next variable-length string in song file + Read the byte-length of the next variable-length string + + If at the end of the file, returns 0. """ - this_byte_char = self.song_file.read(1) - if not this_byte_char: + this_byte = self.song_file.read(1) + if not this_byte: return 0 - this_byte = ord(this_byte_char) - if this_byte < 128: - return this_byte + this_byte_val = ord(this_byte) + if this_byte_val < 128: + return this_byte_val else: - return (self.readLength() * 128) + (this_byte - 128) + return (self.readLength() * 128) + (this_byte_val - 128) + + def parseCopyrightCCLI(self, field): + """ + Look for CCLI song number, and get copyright + """ + copyright, sep, ccli_no = field.rpartition(u'CCLI') + if not sep: + copyright = ccli_no + ccli_no = u'' + if copyright: + self.addCopyright(copyright.rstrip(u'\n').replace(u'\n', u' ')) + if ccli_no: + ccli_no = ccli_no.strip(u' :') + if ccli_no.isdigit(): + self.ccliNumber = ccli_no From 45c180308a5103ed03ed344060ae34522159bb33 Mon Sep 17 00:00:00 2001 From: Samuel Findlay Date: Thu, 3 May 2012 22:41:49 +1000 Subject: [PATCH 07/35] Implemented BinaryReader.Read7BitEncodedInt from .NET. Tidy code. --- openlp/plugins/songs/lib/powersongimport.py | 171 +++++++++++--------- 1 file changed, 96 insertions(+), 75 deletions(-) diff --git a/openlp/plugins/songs/lib/powersongimport.py b/openlp/plugins/songs/lib/powersongimport.py index 1b99d756f..9d5fa8f8e 100644 --- a/openlp/plugins/songs/lib/powersongimport.py +++ b/openlp/plugins/songs/lib/powersongimport.py @@ -42,31 +42,30 @@ class PowerSongImport(SongImport): **PowerSong Song File Format:** - The file has a number of label-field pairs of variable length. + The file has a number of label-field pairs. - Labels and Fields: - * Every label and field is preceded by an integer which specifies its - byte-length. - * If the length < 128 bytes, only one byte is used to encode - the length integer. - * But if it's greater, as many bytes are used as necessary: - * the first byte = (length % 128) + 128 - * the next byte = length / 128 - * another byte is only used if (length / 128) >= 128 - * and so on (3 bytes needed iff length > 16383) + Label and Field strings: + + * Every label and field is a variable length string preceded by an + integer specifying it's byte length. + * Integer is 32-bit but is encoded in 7-bit format to save space. Thus + if length will fit in 7 bits (ie <= 127) it takes up only one byte. Metadata fields: - * Every PowerSong file begins with a TITLE field. - * This is followed by zero or more AUTHOR fields. - * The next label is always COPYRIGHTLINE, but its field may be empty. + + * Every PowerSong file has a TITLE field. + * There is zero or more AUTHOR fields. + * There is always a COPYRIGHTLINE label, but its field may be empty. This field may also contain a CCLI number: e.g. "CCLI 176263". Lyrics fields: + * Each verse is contained in a PART field. * Lines have Windows line endings ``CRLF`` (0x0d, 0x0a). * There is no concept of verse types. Valid extensions for a PowerSong song file are: + * .song """ @@ -75,6 +74,8 @@ class PowerSongImport(SongImport): Receive a list of files to import. """ if not isinstance(self.importSource, list): + self.logError(unicode(translate('SongsPlugin.PowerSongImport', + 'No files to import.'))) return self.importWizard.progressBar.setMaximum(len(self.importSource)) for file in self.importSource: @@ -82,83 +83,103 @@ class PowerSongImport(SongImport): return self.setDefaults() parse_error = False - with open(file, 'rb') as self.song_file: - # Get title to check file is valid PowerSong song format - label, field = self.readLabelField() - if label == u'TITLE': - self.title = field.replace(u'\n', u' ') - else: - self.logError(file, unicode( - translate('SongsPlugin.PowerSongSongImport', \ - 'Invalid PowerSong file. Missing "TITLE" header.'))) - continue - # Get rest of fields from file + with open(file, 'rb') as song_data: while True: - label, field = self.readLabelField() - if not label: - break - if label == u'AUTHOR': - self.parseAuthor(field) - elif label == u'COPYRIGHTLINE': - found_copyright = True - self.parseCopyrightCCLI(field) - elif label == u'PART': - self.addVerse(field) - else: + try: + label = self._readString(song_data) + if not label: + break + field = self._readString(song_data) + except ValueError: parse_error = True self.logError(file, unicode( - translate('SongsPlugin.PowerSongSongImport', \ - '"%s" Invalid PowerSong file. Unknown header: "%s".' - % (self.title, label)))) + translate('SongsPlugin.PowerSongImport', + 'Invalid PowerSong file. Unexpected byte value.'))) break - if parse_error: - continue - # Check that file had COPYRIGHTLINE label - if not found_copyright: - self.logError(file, unicode( - translate('SongsPlugin.PowerSongSongImport', \ - '"%s" Invalid PowerSong file. Missing "COPYRIGHTLINE" \ - header.' % self.title))) - continue - # Check that file had at least one verse - if not self.verses: - self.logError(file, unicode( - translate('SongsPlugin.PowerSongSongImport', \ - '"%s" Verses not found. Missing "PART" header.' - % self.title))) - continue + else: + if label == u'TITLE': + self.title = field.replace(u'\n', u' ') + elif label == u'AUTHOR': + self.parseAuthor(field) + elif label == u'COPYRIGHTLINE': + found_copyright = True + self._parseCopyrightCCLI(field) + elif label == u'PART': + self.addVerse(field) + if parse_error: + continue + # Check that file had TITLE field + if not self.title: + self.logError(file, unicode( + translate('SongsPlugin.PowerSongImport', + 'Invalid PowerSong file. Missing "TITLE" header.'))) + continue + # Check that file had COPYRIGHTLINE label + if not found_copyright: + self.logError(file, unicode( + translate('SongsPlugin.PowerSongImport', + '"%s" Invalid PowerSong file. Missing "COPYRIGHTLINE" ' + 'header.' % self.title))) + continue + # Check that file had at least one verse + if not self.verses: + self.logError(file, unicode( + translate('SongsPlugin.PowerSongImport', + '"%s" Verses not found. Missing "PART" header.' + % self.title))) + continue if not self.finish(): self.logError(file) - def readLabelField(self): + def _readString(self, file_object): """ - Read (as a 2-tuple) the next two variable-length strings + Reads in next variable-length string. """ - label = unicode(self.song_file.read( - self.readLength()), u'utf-8', u'ignore') - if label: - field = unicode(self.song_file.read( - self.readLength()), u'utf-8', u'ignore') - else: - field = u'' - return label, field + string_len = self._read7BitEncodedInteger(file_object) + return unicode(file_object.read(string_len), u'utf-8', u'ignore') - def readLength(self): + def _read7BitEncodedInteger(self, file_object): """ - Read the byte-length of the next variable-length string + Reads in a 32-bit integer in compressed 7-bit format. - If at the end of the file, returns 0. + Accomplished by reading the integer 7 bits at a time. The high bit + of the byte when set means to continue reading more bytes. + If the integer will fit in 7 bits (ie <= 127), it only takes up one + byte. Otherwise, it may take up to 5 bytes. + + Reference: .NET method System.IO.BinaryReader.Read7BitEncodedInt """ - this_byte = self.song_file.read(1) - if not this_byte: + val = 0 + shift = 0 + i = 0 + while True: + # Check for corrupted stream (since max 5 bytes per 32-bit integer) + if i == 5: + raise ValueError + byte = self._readByte(file_object) + # Strip high bit and shift left + val += (byte & 0x7f) << shift + shift += 7 + high_bit_set = byte & 0x80 + if not high_bit_set: + break + i += 1 + return val + + def _readByte(self, file_object): + """ + Reads in next byte as an unsigned integer + + Note: returns 0 at end of file. + """ + byte_str = file_object.read(1) + # If read result is empty, then reached end of file + if not byte_str: return 0 - this_byte_val = ord(this_byte) - if this_byte_val < 128: - return this_byte_val else: - return (self.readLength() * 128) + (this_byte_val - 128) + return ord(byte_str) - def parseCopyrightCCLI(self, field): + def _parseCopyrightCCLI(self, field): """ Look for CCLI song number, and get copyright """ From 416cbe465ea9465b953093065a50fa69ea75f450 Mon Sep 17 00:00:00 2001 From: Samuel Findlay Date: Thu, 3 May 2012 22:50:10 +1000 Subject: [PATCH 08/35] Changed 'PowerSong' to 'PowerSong 1.0' --- openlp/core/ui/wizard.py | 2 +- openlp/plugins/songs/forms/songimportform.py | 2 +- openlp/plugins/songs/lib/powersongimport.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openlp/core/ui/wizard.py b/openlp/core/ui/wizard.py index 74dcfceaf..500d958fd 100644 --- a/openlp/core/ui/wizard.py +++ b/openlp/core/ui/wizard.py @@ -53,7 +53,7 @@ class WizardStrings(object): OL = u'OpenLyrics' OS = u'OpenSong' OSIS = u'OSIS' - PS = u'PowerSong' + PS = u'PowerSong 1.0' SB = u'SongBeamer' SoF = u'Songs of Fellowship' SSP = u'SongShow Plus' diff --git a/openlp/plugins/songs/forms/songimportform.py b/openlp/plugins/songs/forms/songimportform.py index 0cacae612..d5f7715ea 100644 --- a/openlp/plugins/songs/forms/songimportform.py +++ b/openlp/plugins/songs/forms/songimportform.py @@ -627,7 +627,7 @@ class SongImportForm(OpenLPWizard): self.getFiles(WizardStrings.OpenTypeFile % WizardStrings.PS, self.powerSongFileListWidget, u'%s (*.song)' % translate('SongsPlugin.ImportWizardForm', - 'PowerSong Song Files') + 'PowerSong 1.0 Song Files') ) def onPowerSongRemoveButtonClicked(self): diff --git a/openlp/plugins/songs/lib/powersongimport.py b/openlp/plugins/songs/lib/powersongimport.py index 9d5fa8f8e..31491398c 100644 --- a/openlp/plugins/songs/lib/powersongimport.py +++ b/openlp/plugins/songs/lib/powersongimport.py @@ -40,9 +40,9 @@ class PowerSongImport(SongImport): The :class:`PowerSongImport` class provides the ability to import song files from PowerSong. - **PowerSong Song File Format:** + **PowerSong 1.0 Song File Format:** - The file has a number of label-field pairs. + The file has a number of label-field (think key-value) pairs. Label and Field strings: From baf1c792e5cd0de28e868c39103277c1a952f784 Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Sat, 5 May 2012 14:24:25 +0200 Subject: [PATCH 09/35] clean ups --- openlp/core/lib/dockwidget.py | 3 +-- openlp/core/lib/renderer.py | 2 +- openlp/core/lib/serviceitem.py | 17 ++++++----------- openlp/core/ui/servicemanager.py | 3 +-- 4 files changed, 9 insertions(+), 16 deletions(-) diff --git a/openlp/core/lib/dockwidget.py b/openlp/core/lib/dockwidget.py index 4f74784ad..e08b5eee5 100644 --- a/openlp/core/lib/dockwidget.py +++ b/openlp/core/lib/dockwidget.py @@ -53,8 +53,7 @@ class OpenLPDockWidget(QtGui.QDockWidget): self.setWindowIcon(build_icon(icon)) # Sort out the minimum width. screens = ScreenList.get_instance() - screen_width = screens.current[u'size'].width() - mainwindow_docbars = screen_width / 5 + mainwindow_docbars = screens.current[u'size'].width() / 5 if mainwindow_docbars > 300: self.setMinimumWidth(300) else: diff --git a/openlp/core/lib/renderer.py b/openlp/core/lib/renderer.py index 2c9d2a5d1..32ea0fb90 100644 --- a/openlp/core/lib/renderer.py +++ b/openlp/core/lib/renderer.py @@ -125,7 +125,7 @@ class Renderer(object): Set the appropriate theme depending on the theme level. Called by the service item when building a display frame - ``theme`` + ``override_theme`` The name of the song-level theme. None means the service item wants to use the given value. diff --git a/openlp/core/lib/serviceitem.py b/openlp/core/lib/serviceitem.py index c8fbe6d8a..36314ac7f 100644 --- a/openlp/core/lib/serviceitem.py +++ b/openlp/core/lib/serviceitem.py @@ -313,17 +313,12 @@ class ServiceItem(object): self.from_plugin = header[u'from_plugin'] self.capabilities = header[u'capabilities'] # Added later so may not be present in older services. - if u'search' in header: - self.search_string = header[u'search'] - self.data_string = header[u'data'] - if u'xml_version' in header: - self.xml_version = header[u'xml_version'] - if u'start_time' in header: - self.start_time = header[u'start_time'] - if u'end_time' in header: - self.end_time = header[u'end_time'] - if u'media_length' in header: - self.media_length = header[u'media_length'] + self.search_string = header.get(u'search', u'') + self.data_string = header.get(u'data', u'') + self.xml_version = header.get(u'xml_version') + self.start_time = header.get(u'start_time', 0) + self.end_time = header.get(u'end_time', 0) + self.media_length = header.get(u'media_length', 0) if u'background_audio' in header: self.background_audio = [] for filename in header[u'background_audio']: diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index c9dfeae50..4fa36cc2f 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -51,7 +51,7 @@ class ServiceManagerList(QtGui.QTreeWidget): """ Set up key bindings and mouse behaviour for the service list """ - def __init__(self, serviceManager, parent=None, name=None): + def __init__(self, serviceManager, parent=None): QtGui.QTreeWidget.__init__(self, parent) self.serviceManager = serviceManager @@ -101,7 +101,6 @@ class ServiceManager(QtGui.QWidget): QtGui.QWidget.__init__(self, parent) self.mainwindow = mainwindow self.serviceItems = [] - self.serviceName = u'' self.suffixes = [] self.dropPosition = 0 self.expandTabs = False From 34b317398888e7bddcc291f06bf8aaef6f140b70 Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Sat, 5 May 2012 15:59:04 +0200 Subject: [PATCH 10/35] fixed bug 885874 (Song with mis matched formatting tags abends on render) Fixes: https://launchpad.net/bugs/885874 --- openlp/core/lib/renderer.py | 12 ++++---- openlp/plugins/songs/lib/xml.py | 50 +++++++++++++++++++++++++++++---- 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/openlp/core/lib/renderer.py b/openlp/core/lib/renderer.py index 39f69dda6..bc10bac95 100644 --- a/openlp/core/lib/renderer.py +++ b/openlp/core/lib/renderer.py @@ -236,8 +236,8 @@ class Renderer(object): # the first two slides (and neglect the last for now). if len(slides) == 3: html_text = expand_tags(u'\n'.join(slides[:2])) - # We check both slides to determine if the optional break is - # needed (there is only one optional break). + # We check both slides to determine if the optional split is + # needed (there is only one optional split). else: html_text = expand_tags(u'\n'.join(slides)) html_text = html_text.replace(u'\n', u'
') @@ -248,8 +248,8 @@ class Renderer(object): else: # The first optional slide fits, which means we have to # render the first optional slide. - text_contains_break = u'[---]' in text - if text_contains_break: + text_contains_split = u'[---]' in text + if text_contains_split: try: text_to_render, text = \ text.split(u'\n[---]\n', 1) @@ -264,7 +264,7 @@ class Renderer(object): if len(slides) > 1 and text: # Add all slides apart from the last one the list. pages.extend(slides[:-1]) - if text_contains_break: + if text_contains_split: text = slides[-1] + u'\n[---]\n' + text else: text = slides[-1] + u'\n'+ text @@ -493,7 +493,7 @@ class Renderer(object): (raw_text.find(tag[u'start tag']), tag[u'start tag'], tag[u'end tag'])) html_tags.append( - (raw_text.find(tag[u'start tag']), tag[u'start html'])) + (raw_text.find(tag[u'start tag']), tag[u'start html'])) # Sort the lists, so that the tags which were opened first on the first # slide (the text we are checking) will be opened first on the next # slide as well. diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py index 816742d11..f16833ac4 100644 --- a/openlp/plugins/songs/lib/xml.py +++ b/openlp/plugins/songs/lib/xml.py @@ -335,17 +335,57 @@ class OpenLyrics(object): if u'lang' in verse[0]: verse_element.set(u'lang', verse[0][u'lang']) # Create a list with all "virtual" verses. - virtual_verses = cgi.escape(verse[1]) - virtual_verses = virtual_verses.split(u'[---]') - for index, virtual_verse in enumerate(virtual_verses): + optional_verses = cgi.escape(verse[1]) + optional_verses = optional_verses.split(u'[---]') + start_tags = u'' + end_tags = u'' + for index, optional_verse in enumerate(optional_verses): + optional_verse = start_tags + optional_verse + start_tags, end_tags = self._get_start_tags(optional_verse) + optional_verse += end_tags # Add formatting tags to text lines_element = self._add_text_with_tags_to_lines(verse_element, - virtual_verse, tags_element) + optional_verse, tags_element) # Do not add the break attribute to the last lines element. - if index < len(virtual_verses) - 1: + if index < len(optional_verses) - 1: lines_element.set(u'break', u'optional') return self._extract_xml(song_xml) + def _get_start_tags(self, text): + """ + Tests the given text for not closed formatting tags and returns a tuple + consisting of two unicode strings:: + + (u'{st}{r}', u'{/r}{/st}') + + The first unicode string are the start tags (for the next slide). The + second unicode string are the end tags. + + ``text`` + The text to test. The text must **not** contain html tags, only + OpenLP formatting tags are allowed:: + + {st}{r}Text text text + """ + tags = [] + for tag in FormattingTags.get_html_tags(): + if tag[u'start tag'] == u'{br}': + continue + if text.count(tag[u'start tag']) != text.count(tag[u'end tag']): + tags.append((text.find(tag[u'start tag']), + tag[u'start tag'], tag[u'end tag'])) + # Sort the lists, so that the tags which were opened first on the first + # slide (the text we are checking) will be opened first on the next + # slide as well. + tags.sort(key=lambda tag: tag[0]) + end_tags = [] + start_tags = [] + for tag in tags: + start_tags.append(tag[1]) + end_tags.append(tag[2]) + end_tags.reverse() + return u''.join(start_tags), u''.join(end_tags) + def xml_to_song(self, xml, parse_and_temporary_save=False): """ Create and save a song from OpenLyrics format xml to the database. Since From 75f1041d27e97f0c89d9b8e53c402f231906078b Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Sat, 5 May 2012 16:03:48 +0200 Subject: [PATCH 11/35] removed occurrences of 'virtual' split --- openlp/plugins/songs/lib/xml.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py index e5f5f7ff7..17a8db91e 100644 --- a/openlp/plugins/songs/lib/xml.py +++ b/openlp/plugins/songs/lib/xml.py @@ -33,7 +33,7 @@ The basic XML for storing the lyrics in the song database looks like this:: - + @@ -135,7 +135,7 @@ class SongXML(object): The returned list has the following format:: [[{'type': 'v', 'label': '1'}, - u"virtual slide 1[---]virtual slide 2"], + u"optional slide split 1[---]optional slide split 2"], [{'lang': 'en', 'type': 'c', 'label': '1'}, u"English chorus"]] """ self.song_xml = None @@ -334,7 +334,7 @@ class OpenLyrics(object): self._add_text_to_element(u'verse', lyrics, None, verse_def) if u'lang' in verse[0]: verse_element.set(u'lang', verse[0][u'lang']) - # Create a list with all "virtual" verses. + # Create a list with all "optional" verses. optional_verses = cgi.escape(verse[1]) optional_verses = optional_verses.split(u'[---]') start_tags = u'' From 74ff2f92ee9bc3437b36fc3cbc299d3e85f011c4 Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Sat, 5 May 2012 16:08:07 +0200 Subject: [PATCH 12/35] added comment --- openlp/plugins/songs/lib/xml.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py index 17a8db91e..2e88a52a2 100644 --- a/openlp/plugins/songs/lib/xml.py +++ b/openlp/plugins/songs/lib/xml.py @@ -340,6 +340,7 @@ class OpenLyrics(object): start_tags = u'' end_tags = u'' for index, optional_verse in enumerate(optional_verses): + # Fix up missing end and start tags such as {r} or {/r}. optional_verse = start_tags + optional_verse start_tags, end_tags = self._get_start_tags(optional_verse) optional_verse += end_tags From f658c741ebdfc6ed7e346b737a4bd9ba6c2b569b Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Sat, 5 May 2012 16:12:11 +0200 Subject: [PATCH 13/35] renamed method --- openlp/plugins/songs/lib/xml.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py index 2e88a52a2..abc5a6dbe 100644 --- a/openlp/plugins/songs/lib/xml.py +++ b/openlp/plugins/songs/lib/xml.py @@ -342,7 +342,7 @@ class OpenLyrics(object): for index, optional_verse in enumerate(optional_verses): # Fix up missing end and start tags such as {r} or {/r}. optional_verse = start_tags + optional_verse - start_tags, end_tags = self._get_start_tags(optional_verse) + start_tags, end_tags = self._get_missing_tags(optional_verse) optional_verse += end_tags # Add formatting tags to text lines_element = self._add_text_with_tags_to_lines(verse_element, @@ -352,7 +352,7 @@ class OpenLyrics(object): lines_element.set(u'break', u'optional') return self._extract_xml(song_xml) - def _get_start_tags(self, text): + def _get_missing_tags(self, text): """ Tests the given text for not closed formatting tags and returns a tuple consisting of two unicode strings:: From b3f733233ab5a444a1fd34114363dd1694a32c30 Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Sat, 5 May 2012 16:25:55 +0200 Subject: [PATCH 14/35] fixed two extra new lines being added --- openlp/plugins/songs/lib/xml.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py index abc5a6dbe..1e35d07ad 100644 --- a/openlp/plugins/songs/lib/xml.py +++ b/openlp/plugins/songs/lib/xml.py @@ -336,7 +336,7 @@ class OpenLyrics(object): verse_element.set(u'lang', verse[0][u'lang']) # Create a list with all "optional" verses. optional_verses = cgi.escape(verse[1]) - optional_verses = optional_verses.split(u'[---]') + optional_verses = optional_verses.split(u'\n[---]\n') start_tags = u'' end_tags = u'' for index, optional_verse in enumerate(optional_verses): From b72101c7247401885e8f59c3e36539f35e112778 Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Sat, 5 May 2012 16:36:47 +0200 Subject: [PATCH 15/35] fixed formatting tag not being present on slides --- openlp/core/lib/renderer.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openlp/core/lib/renderer.py b/openlp/core/lib/renderer.py index 78e9324bc..30a02e94a 100644 --- a/openlp/core/lib/renderer.py +++ b/openlp/core/lib/renderer.py @@ -255,6 +255,10 @@ class Renderer(object): except: text_to_render = text.split(u'\n[---]\n')[0] text = u'' + text_to_render, raw_tags, html_tags = \ + self._get_start_tags(text_to_render) + if text: + text = raw_tags + text else: text_to_render = text text = u'' From 5959ee5158fc87ee3a82474320454f604ec2b17a Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Sat, 5 May 2012 18:03:40 +0200 Subject: [PATCH 16/35] fixed regression, reworked staticmethods, changed button behaviour to reflect the internal actions --- openlp/core/lib/formattingtags.py | 69 +++++++++++------------------ openlp/core/ui/formattingtagform.py | 42 ++++++++++++------ openlp/plugins/songs/lib/xml.py | 7 ++- 3 files changed, 58 insertions(+), 60 deletions(-) diff --git a/openlp/core/lib/formattingtags.py b/openlp/core/lib/formattingtags.py index bec2db63a..11fd898c3 100644 --- a/openlp/core/lib/formattingtags.py +++ b/openlp/core/lib/formattingtags.py @@ -46,13 +46,36 @@ class FormattingTags(object): """ Provide access to the html_expands list. """ - # Load user defined tags otherwise user defined tags are not present. return FormattingTags.html_expands @staticmethod - def reset_html_tags(): + def save_html_tags(): """ - Resets the html_expands list. + Saves all formatting tags except protected ones. + """ + tags = [] + for tag in FormattingTags.html_expands: + if not tag[u'protected'] and not tag.get(u'temporary'): + # Using dict ensures that copy is made and encoding of values + # a little later does not affect tags in the original list + tags.append(dict(tag)) + tag = tags[-1] + # Remove key 'temporary' from tags. + # It is not needed to be saved. + if u'temporary' in tag: + del tag[u'temporary'] + for element in tag: + if isinstance(tag[element], unicode): + tag[element] = tag[element].encode('utf8') + # Formatting Tags were also known as display tags. + QtCore.QSettings().setValue(u'displayTags/html_tags', + QtCore.QVariant(cPickle.dumps(tags) if tags else u'')) + + @staticmethod + def load_tags(): + """ + Load the Tags from store so can be used in the system or used to + update the display. """ temporary_tags = [tag for tag in FormattingTags.html_expands if tag.get(u'temporary')] @@ -140,38 +163,6 @@ class FormattingTags(object): FormattingTags.add_html_tags(base_tags) FormattingTags.add_html_tags(temporary_tags) - @staticmethod - def save_html_tags(): - """ - Saves all formatting tags except protected ones. - """ - tags = [] - for tag in FormattingTags.html_expands: - if not tag[u'protected'] and not tag.get(u'temporary'): - # Using dict ensures that copy is made and encoding of values - # a little later does not affect tags in the original list - tags.append(dict(tag)) - tag = tags[-1] - # Remove key 'temporary' from tags. - # It is not needed to be saved. - if u'temporary' in tag: - del tag[u'temporary'] - for element in tag: - if isinstance(tag[element], unicode): - tag[element] = tag[element].encode('utf8') - # Formatting Tags were also known as display tags. - QtCore.QSettings().setValue(u'displayTags/html_tags', - QtCore.QVariant(cPickle.dumps(tags) if tags else u'')) - - @staticmethod - def load_tags(): - """ - Load the Tags from store so can be used in the system or used to - update the display. If Cancel was selected this is needed to reset the - dsiplay to the correct version. - """ - # Initial Load of the Tags - FormattingTags.reset_html_tags() # Formatting Tags were also known as display tags. user_expands = QtCore.QSettings().value(u'displayTags/html_tags', QtCore.QVariant(u'')).toString() @@ -187,17 +178,13 @@ class FormattingTags(object): FormattingTags.add_html_tags(user_tags) @staticmethod - def add_html_tags(tags, save=False): + def add_html_tags(tags): """ Add a list of tags to the list. ``tags`` The list with tags to add. - ``save`` - Defaults to ``False``. If set to ``True`` the given ``tags`` are - saved to the config. - Each **tag** has to be a ``dict`` and should have the following keys: * desc @@ -225,8 +212,6 @@ class FormattingTags(object): displaying text containing the tag. It has to be a ``boolean``. """ FormattingTags.html_expands.extend(tags) - if save: - FormattingTags.save_html_tags() @staticmethod def remove_html_tag(tag_id): diff --git a/openlp/core/ui/formattingtagform.py b/openlp/core/ui/formattingtagform.py index d6f880e3f..1084d6a3d 100644 --- a/openlp/core/ui/formattingtagform.py +++ b/openlp/core/ui/formattingtagform.py @@ -57,6 +57,14 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog): QtCore.SIGNAL(u'clicked()'), self.onDeleteClicked) QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(u'rejected()'), self.close) + QtCore.QObject.connect(self.descriptionLineEdit, + QtCore.SIGNAL(u'textEdited(QString)'), self.onTextEdited) + QtCore.QObject.connect(self.tagLineEdit, + QtCore.SIGNAL(u'textEdited(QString)'), self.onTextEdited) + QtCore.QObject.connect(self.startTagLineEdit, + QtCore.SIGNAL(u'textEdited(QString)'), self.onTextEdited) + QtCore.QObject.connect(self.endTagLineEdit, + QtCore.SIGNAL(u'textEdited(QString)'), self.onTextEdited) # Forces reloading of tags from openlp configuration. FormattingTags.load_tags() @@ -65,7 +73,7 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog): Load Display and set field state. """ # Create initial copy from master - self._resetTable() + self._reloadTable() self.selected = -1 return QtGui.QDialog.exec_(self) @@ -73,9 +81,9 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog): """ Table Row selected so display items and set field state. """ - row = self.tagTableWidget.currentRow() - html = FormattingTags.html_expands[row] - self.selected = row + self.savePushButton.setEnabled(False) + self.selected = self.tagTableWidget.currentRow() + html = FormattingTags.get_html_tags()[self.selected] self.descriptionLineEdit.setText(html[u'desc']) self.tagLineEdit.setText(self._strip(html[u'start tag'])) self.startTagLineEdit.setText(html[u'start html']) @@ -85,21 +93,26 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog): self.tagLineEdit.setEnabled(False) self.startTagLineEdit.setEnabled(False) self.endTagLineEdit.setEnabled(False) - self.savePushButton.setEnabled(False) self.deletePushButton.setEnabled(False) else: self.descriptionLineEdit.setEnabled(True) self.tagLineEdit.setEnabled(True) self.startTagLineEdit.setEnabled(True) self.endTagLineEdit.setEnabled(True) - self.savePushButton.setEnabled(True) self.deletePushButton.setEnabled(True) + def onTextEdited(self, text): + """ + Enable the ``savePushButton`` when any of the selected tag's properties + has been changed. + """ + self.savePushButton.setEnabled(True) + def onNewClicked(self): """ Add a new tag to list only if it is not a duplicate. """ - for html in FormattingTags.html_expands: + for html in FormattingTags.get_html_tags(): if self._strip(html[u'start tag']) == u'n': critical_error_message_box( translate('OpenLP.FormattingTagForm', 'Update Error'), @@ -117,11 +130,13 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog): u'temporary': False } FormattingTags.add_html_tags([tag]) - self._resetTable() + FormattingTags.save_html_tags() + self._reloadTable() # Highlight new row self.tagTableWidget.selectRow(self.tagTableWidget.rowCount() - 1) self.onRowSelected() self.tagTableWidget.scrollToBottom() + #self.savePushButton.setEnabled(False) def onDeleteClicked(self): """ @@ -130,14 +145,14 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog): if self.selected != -1: FormattingTags.remove_html_tag(self.selected) self.selected = -1 - self._resetTable() - FormattingTags.save_html_tags() + FormattingTags.save_html_tags() + self._reloadTable() def onSavedClicked(self): """ Update Custom Tag details if not duplicate and save the data. """ - html_expands = FormattingTags.html_expands + html_expands = FormattingTags.get_html_tags() if self.selected != -1: html = html_expands[self.selected] tag = unicode(self.tagLineEdit.text()) @@ -157,14 +172,13 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog): # Keep temporary tags when the user changes one. html[u'temporary'] = False self.selected = -1 - self._resetTable() FormattingTags.save_html_tags() + self._reloadTable() - def _resetTable(self): + def _reloadTable(self): """ Reset List for loading. """ - FormattingTags.load_tags() self.tagTableWidget.clearContents() self.tagTableWidget.setRowCount(0) self.newPushButton.setEnabled(True) diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py index fdcb1dd60..f30dc0f24 100644 --- a/openlp/plugins/songs/lib/xml.py +++ b/openlp/plugins/songs/lib/xml.py @@ -317,9 +317,7 @@ class OpenLyrics(object): tags_element = None match = re.search(u'\{/?\w+\}', song.lyrics, re.UNICODE) if match: - # Reset available tags. - FormattingTags.reset_html_tags() - # Named 'formatting' - 'format' is built-in fuction in Python. + # Named 'format_' - 'format' is built-in fuction in Python. format_ = etree.SubElement(song_xml, u'format') tags_element = etree.SubElement(format_, u'tags') tags_element.set(u'application', u'OpenLP') @@ -572,7 +570,8 @@ class OpenLyrics(object): for tag in FormattingTags.get_html_tags()] new_tags = [tag for tag in found_tags if tag[u'start tag'] not in existing_tag_ids] - FormattingTags.add_html_tags(new_tags, True) + FormattingTags.add_html_tags(new_tags) + FormattingTags.save_html_tags() def _process_lines_mixed_content(self, element, newlines=True): """ From 5815d637a7184a33a7a5ff33fcc4a78a959400e7 Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Mon, 7 May 2012 08:40:34 +0100 Subject: [PATCH 17/35] Add Service delete to Shortcuts and allow from keyboard --- openlp/core/ui/servicemanager.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 4fa36cc2f..e0443a478 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -64,6 +64,10 @@ class ServiceManagerList(QtGui.QTreeWidget): elif event.key() == QtCore.Qt.Key_Down: self.serviceManager.onMoveSelectionDown() event.accept() + elif event.key() == QtCore.Qt.Key_Delete: + self.serviceManager.onDeleteFromService() + event.accept() + print "Key event " + unicode(event.key()) event.ignore() else: event.ignore() @@ -218,6 +222,7 @@ class ServiceManager(QtGui.QWidget): icon=u':/general/general_delete.png', tooltip=translate('OpenLP.ServiceManager', 'Delete the selected item from the service.'), + shortcuts=[QtCore.Qt.Key_Delete], category=UiStrings().Service, triggers=self.onDeleteFromService) self.orderToolbar.addSeparator() self.serviceManagerList.expand = self.orderToolbar.addToolbarAction( From bcb66687929d6c417cbc934ce421c5ac93e962e2 Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Mon, 7 May 2012 09:21:21 +0100 Subject: [PATCH 18/35] Delete clean ups --- openlp/core/ui/servicemanager.py | 36 +++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index e0443a478..96734bc47 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -67,7 +67,6 @@ class ServiceManagerList(QtGui.QTreeWidget): elif event.key() == QtCore.Qt.Key_Delete: self.serviceManager.onDeleteFromService() event.accept() - print "Key event " + unicode(event.key()) event.ignore() else: event.ignore() @@ -222,7 +221,7 @@ class ServiceManager(QtGui.QWidget): icon=u':/general/general_delete.png', tooltip=translate('OpenLP.ServiceManager', 'Delete the selected item from the service.'), - shortcuts=[QtCore.Qt.Key_Delete], category=UiStrings().Service, + shortcuts=[QtCore.Qt.Key_Delete], triggers=self.onDeleteFromService) self.orderToolbar.addSeparator() self.serviceManagerList.expand = self.orderToolbar.addToolbarAction( @@ -1322,7 +1321,32 @@ class ServiceManager(QtGui.QWidget): def findServiceItem(self): """ - Finds the selected ServiceItem in the list and returns the position of + Finds the first selected ServiceItem in the list and returns the + position of the serviceitem and its selected child item. For example, + if the third child item (in the Slidecontroller known as slide) in the + second service item is selected this will return:: + + (1, 2) + """ + items = self.serviceManagerList.selectedItems() + serviceItem = 0 + serviceItemChild = -1 + for item in items: + parentitem = item.parent() + if parentitem is None: + serviceItem = item.data(0, QtCore.Qt.UserRole).toInt()[0] + else: + serviceItem = parentitem.data(0, QtCore.Qt.UserRole).toInt()[0] + serviceItemChild = item.data(0, QtCore.Qt.UserRole).toInt()[0] + # Adjust for zero based arrays. + serviceItem -= 1 + # Only process the first item on the list for this method. + break + return serviceItem, serviceItemChild + + def findServiceItems(self): + """ + Finds all selected ServiceItems in the list and returns the position of the serviceitem and its selected child item. For example, if the third child item (in the Slidecontroller known as slide) in the second service item is selected this will return:: @@ -1339,8 +1363,10 @@ class ServiceManager(QtGui.QWidget): else: serviceItem = parentitem.data(0, QtCore.Qt.UserRole).toInt()[0] serviceItemChild = item.data(0, QtCore.Qt.UserRole).toInt()[0] - # Adjust for zero based arrays. - serviceItem -= 1 + # Adjust for zero based arrays. + serviceItem -= 1 + # Only process the first item on the list for this method. + break return serviceItem, serviceItemChild def dragEnterEvent(self, event): From f9247078bc4fc4ddf8db4d6b335cbdae09de816e Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Mon, 7 May 2012 09:35:52 +0100 Subject: [PATCH 19/35] more Delete clean ups --- openlp/core/ui/servicemanager.py | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 96734bc47..01e624831 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -1344,31 +1344,6 @@ class ServiceManager(QtGui.QWidget): break return serviceItem, serviceItemChild - def findServiceItems(self): - """ - Finds all selected ServiceItems in the list and returns the position of - the serviceitem and its selected child item. For example, if the third - child item (in the Slidecontroller known as slide) in the second service - item is selected this will return:: - - (1, 2) - """ - items = self.serviceManagerList.selectedItems() - serviceItem = 0 - serviceItemChild = -1 - for item in items: - parentitem = item.parent() - if parentitem is None: - serviceItem = item.data(0, QtCore.Qt.UserRole).toInt()[0] - else: - serviceItem = parentitem.data(0, QtCore.Qt.UserRole).toInt()[0] - serviceItemChild = item.data(0, QtCore.Qt.UserRole).toInt()[0] - # Adjust for zero based arrays. - serviceItem -= 1 - # Only process the first item on the list for this method. - break - return serviceItem, serviceItemChild - def dragEnterEvent(self, event): """ Accept Drag events From bf4dcdde925eec005a0ec3988591abacd5f25333 Mon Sep 17 00:00:00 2001 From: Samuel Findlay Date: Mon, 7 May 2012 20:36:39 +1000 Subject: [PATCH 20/35] Added getFolder to OpenLPWizard --- openlp/core/ui/wizard.py | 23 +++++++++++++++++--- openlp/plugins/songs/forms/songexportform.py | 10 ++------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/openlp/core/ui/wizard.py b/openlp/core/ui/wizard.py index 500d958fd..5f81e670d 100644 --- a/openlp/core/ui/wizard.py +++ b/openlp/core/ui/wizard.py @@ -254,7 +254,7 @@ class OpenLPWizard(QtGui.QWizard): The title of the dialog (unicode). ``editbox`` - A editbox (QLineEdit). + An editbox (QLineEdit). ``filters`` The file extension filters. It should contain the file description @@ -265,11 +265,28 @@ class OpenLPWizard(QtGui.QWizard): if filters: filters += u';;' filters += u'%s (*)' % UiStrings().AllFiles - filename = QtGui.QFileDialog.getOpenFileName(self, title, + filename = unicode(QtGui.QFileDialog.getOpenFileName(self, title, os.path.dirname(SettingsManager.get_last_dir( - self.plugin.settingsSection, 1)), filters) + self.plugin.settingsSection, 1)), filters)) if filename: editbox.setText(filename) SettingsManager.set_last_dir(self.plugin.settingsSection, filename, 1) + def getFolder(self, title, editbox): + """ + Opens a QFileDialog and saves the selected folder to the given editbox. + + ``title`` + The title of the dialog (unicode). + + ``editbox`` + An editbox (QLineEdit). + """ + folder = unicode(QtGui.QFileDialog.getExistingDirectory(self, title, + os.path.dirname(SettingsManager.get_last_dir( + self.plugin.settingsSection, 1)), QtGui.QFileDialog.ShowDirsOnly)) + if folder: + editbox.setText(folder) + SettingsManager.set_last_dir(self.plugin.settingsSection, + folder, 1) \ No newline at end of file diff --git a/openlp/plugins/songs/forms/songexportform.py b/openlp/plugins/songs/forms/songexportform.py index 397b52cfc..dcca098dc 100644 --- a/openlp/plugins/songs/forms/songexportform.py +++ b/openlp/plugins/songs/forms/songexportform.py @@ -363,11 +363,5 @@ class SongExportForm(OpenLPWizard): Called when the *directoryButton* was clicked. Opens a dialog and writes the path to *directoryLineEdit*. """ - path = unicode(QtGui.QFileDialog.getExistingDirectory(self, - translate('SongsPlugin.ExportWizardForm', - 'Select Destination Folder'), - SettingsManager.get_last_dir(self.plugin.settingsSection, 1), - options=QtGui.QFileDialog.ShowDirsOnly)) - SettingsManager.set_last_dir(self.plugin.settingsSection, path, 1) - self.directoryLineEdit.setText(path) - + self.getFolder(translate('SongsPlugin.ExportWizardForm', + 'Select Destination Folder'), self.directoryLineEdit) From 28d9a057319e947f5526dbdff914be88bc1e76d3 Mon Sep 17 00:00:00 2001 From: Samuel Findlay Date: Mon, 7 May 2012 21:24:46 +1000 Subject: [PATCH 21/35] Adapted all PowerSong import widgets for select folder --- openlp/core/lib/ui.py | 1 + openlp/core/ui/wizard.py | 3 + openlp/plugins/songs/forms/songimportform.py | 66 ++++++++------------ 3 files changed, 30 insertions(+), 40 deletions(-) diff --git a/openlp/core/lib/ui.py b/openlp/core/lib/ui.py index c4b1181b1..c0472cce8 100644 --- a/openlp/core/lib/ui.py +++ b/openlp/core/lib/ui.py @@ -94,6 +94,7 @@ class UiStrings(object): self.NewService = translate('OpenLP.Ui', 'New Service') self.NewTheme = translate('OpenLP.Ui', 'New Theme') self.NextTrack = translate('OpenLP.Ui', 'Next Track') + self.NFdSs = translate('OpenLP.Ui', 'No Folder Selected', 'Singular') self.NFSs = translate('OpenLP.Ui', 'No File Selected', 'Singular') self.NFSp = translate('OpenLP.Ui', 'No Files Selected', 'Plural') self.NISs = translate('OpenLP.Ui', 'No Item Selected', 'Singular') diff --git a/openlp/core/ui/wizard.py b/openlp/core/ui/wizard.py index 5f81e670d..7382fb7e0 100644 --- a/openlp/core/ui/wizard.py +++ b/openlp/core/ui/wizard.py @@ -72,11 +72,14 @@ class WizardStrings(object): 'importer, you will need to install the "python-sqlite" ' 'module.') OpenTypeFile = unicode(translate('OpenLP.Ui', 'Open %s File')) + OpenTypeFolder = unicode(translate('OpenLP.Ui', 'Open %s Folder')) PercentSymbolFormat = unicode(translate('OpenLP.Ui', '%p%')) Ready = translate('OpenLP.Ui', 'Ready.') StartingImport = translate('OpenLP.Ui', 'Starting import...') YouSpecifyFile = unicode(translate('OpenLP.Ui', 'You need to specify at ' 'least one %s file to import from.', 'A file type e.g. OpenSong')) + YouSpecifyFolder = unicode(translate('OpenLP.Ui', 'You need to specify a ' + '%s folder to import from.', 'A file type e.g. OpenSong')) class OpenLPWizard(QtGui.QWizard): diff --git a/openlp/plugins/songs/forms/songimportform.py b/openlp/plugins/songs/forms/songimportform.py index d5f7715ea..063efe34e 100644 --- a/openlp/plugins/songs/forms/songimportform.py +++ b/openlp/plugins/songs/forms/songimportform.py @@ -105,6 +105,9 @@ class SongImportForm(OpenLPWizard): QtCore.QObject.connect(self.openLP1BrowseButton, QtCore.SIGNAL(u'clicked()'), self.onOpenLP1BrowseButtonClicked) + QtCore.QObject.connect(self.powerSongBrowseButton, + QtCore.SIGNAL(u'clicked()'), + self.onPowerSongBrowseButtonClicked) QtCore.QObject.connect(self.openLyricsAddButton, QtCore.SIGNAL(u'clicked()'), self.onOpenLyricsAddButtonClicked) @@ -171,12 +174,6 @@ class SongImportForm(OpenLPWizard): QtCore.QObject.connect(self.foilPresenterRemoveButton, QtCore.SIGNAL(u'clicked()'), self.onFoilPresenterRemoveButtonClicked) - QtCore.QObject.connect(self.powerSongAddButton, - QtCore.SIGNAL(u'clicked()'), - self.onPowerSongAddButtonClicked) - QtCore.QObject.connect(self.powerSongRemoveButton, - QtCore.SIGNAL(u'clicked()'), - self.onPowerSongRemoveButtonClicked) def addCustomPages(self): """ @@ -224,7 +221,7 @@ class SongImportForm(OpenLPWizard): # Open Song self.addFileSelectItem(u'openSong', u'OpenSong') # PowerSong - self.addFileSelectItem(u'powerSong') + self.addFileSelectItem(u'powerSong', single_select=True) # SongBeamer self.addFileSelectItem(u'songBeamer') # Song Show Plus @@ -290,6 +287,9 @@ class SongImportForm(OpenLPWizard): translate('SongsPlugin.ImportWizardForm', 'Filename:')) self.openLP1BrowseButton.setText(UiStrings().Browse) self.openLP1DisabledLabel.setText(WizardStrings.NoSqlite) + self.powerSongFilenameLabel.setText( + translate('SongsPlugin.ImportWizardForm', 'Folder:')) + self.powerSongBrowseButton.setText(UiStrings().Browse) self.openLyricsAddButton.setText( translate('SongsPlugin.ImportWizardForm', 'Add Files...')) self.openLyricsRemoveButton.setText( @@ -315,10 +315,6 @@ class SongImportForm(OpenLPWizard): translate('SongsPlugin.ImportWizardForm', 'Add Files...')) self.dreamBeamRemoveButton.setText( translate('SongsPlugin.ImportWizardForm', 'Remove File(s)')) - self.powerSongAddButton.setText( - translate('SongsPlugin.ImportWizardForm', 'Add Files...')) - self.powerSongRemoveButton.setText( - translate('SongsPlugin.ImportWizardForm', 'Remove File(s)')) self.songsOfFellowshipAddButton.setText( translate('SongsPlugin.ImportWizardForm', 'Add Files...')) self.songsOfFellowshipRemoveButton.setText( @@ -401,6 +397,12 @@ class SongImportForm(OpenLPWizard): WizardStrings.YouSpecifyFile % UiStrings().OLPV1) self.openLP1BrowseButton.setFocus() return False + elif source_format == SongFormat.PowerSong: + if self.powerSongFilenameEdit.text().isEmpty(): + critical_error_message_box(UiStrings().NFdSs, + WizardStrings.YouSpecifyFolder % WizardStrings.PS) + self.powerSongBrowseButton.setFocus() + return False elif source_format == SongFormat.OpenLyrics: if self.openLyricsFileListWidget.count() == 0: critical_error_message_box(UiStrings().NFSp, @@ -431,12 +433,6 @@ class SongImportForm(OpenLPWizard): WizardStrings.YouSpecifyFile % WizardStrings.DB) self.dreamBeamAddButton.setFocus() return False - elif source_format == SongFormat.PowerSong: - if self.powerSongFileListWidget.count() == 0: - critical_error_message_box(UiStrings().NFSp, - WizardStrings.YouSpecifyFile % WizardStrings.PS) - self.powerSongAddButton.setFocus() - return False elif source_format == SongFormat.SongsOfFellowship: if self.songsOfFellowshipFileListWidget.count() == 0: critical_error_message_box(UiStrings().NFSp, @@ -546,6 +542,13 @@ class SongImportForm(OpenLPWizard): 'openlp.org v1.x Databases') ) + def onPowerSongBrowseButtonClicked(self): + """ + Get PowerSong song database folder + """ + self.getFolder(WizardStrings.OpenTypeFolder % WizardStrings.PS, + self.powerSongFilenameEdit) + def onOpenLyricsAddButtonClicked(self): """ Get OpenLyrics song database files @@ -620,22 +623,6 @@ class SongImportForm(OpenLPWizard): """ self.removeSelectedItems(self.dreamBeamFileListWidget) - def onPowerSongAddButtonClicked(self): - """ - Get PowerSong song database files - """ - self.getFiles(WizardStrings.OpenTypeFile % WizardStrings.PS, - self.powerSongFileListWidget, u'%s (*.song)' - % translate('SongsPlugin.ImportWizardForm', - 'PowerSong 1.0 Song Files') - ) - - def onPowerSongRemoveButtonClicked(self): - """ - Remove selected PowerSong files from the import list - """ - self.removeSelectedItems(self.powerSongFileListWidget) - def onSongsOfFellowshipAddButtonClicked(self): """ Get Songs of Fellowship song database files @@ -748,12 +735,12 @@ class SongImportForm(OpenLPWizard): self.formatComboBox.setCurrentIndex(last_import_type) self.openLP2FilenameEdit.setText(u'') self.openLP1FilenameEdit.setText(u'') + self.powerSongFilenameEdit.setText(u'') self.openLyricsFileListWidget.clear() self.openSongFileListWidget.clear() self.wordsOfWorshipFileListWidget.clear() self.ccliFileListWidget.clear() self.dreamBeamFileListWidget.clear() - self.powerSongFileListWidget.clear() self.songsOfFellowshipFileListWidget.clear() self.genericFileListWidget.clear() self.easySlidesFilenameEdit.setText(u'') @@ -794,6 +781,11 @@ class SongImportForm(OpenLPWizard): filename=unicode(self.openLP1FilenameEdit.text()), plugin=self.plugin ) + elif source_format == SongFormat.PowerSong: + # Import PowerSong folder + importer = self.plugin.importSongs(SongFormat.PowerSong, + filename=unicode(self.powerSongFilenameEdit.text()) + ) elif source_format == SongFormat.OpenLyrics: # Import OpenLyrics songs importer = self.plugin.importSongs(SongFormat.OpenLyrics, @@ -821,12 +813,6 @@ class SongImportForm(OpenLPWizard): filenames=self.getListOfFiles( self.dreamBeamFileListWidget) ) - elif source_format == SongFormat.PowerSong: - # Import PowerSong songs - importer = self.plugin.importSongs(SongFormat.PowerSong, - filenames=self.getListOfFiles( - self.powerSongFileListWidget) - ) elif source_format == SongFormat.SongsOfFellowship: # Import a Songs of Fellowship RTF file importer = self.plugin.importSongs(SongFormat.SongsOfFellowship, From 3483231a3217521cccc3ea723dd4cb404a130134 Mon Sep 17 00:00:00 2001 From: Samuel Findlay Date: Mon, 7 May 2012 23:10:26 +1000 Subject: [PATCH 22/35] Adapted PowerSongImport class to receive a folder --- openlp/plugins/songs/forms/songimportform.py | 3 +- openlp/plugins/songs/lib/powersongimport.py | 44 +++++++++++++------- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/openlp/plugins/songs/forms/songimportform.py b/openlp/plugins/songs/forms/songimportform.py index 063efe34e..cf9b876bb 100644 --- a/openlp/plugins/songs/forms/songimportform.py +++ b/openlp/plugins/songs/forms/songimportform.py @@ -398,7 +398,8 @@ class SongImportForm(OpenLPWizard): self.openLP1BrowseButton.setFocus() return False elif source_format == SongFormat.PowerSong: - if self.powerSongFilenameEdit.text().isEmpty(): + if self.powerSongFilenameEdit.text().isEmpty() or \ + not os.path.isdir(self.powerSongFilenameEdit.text()): critical_error_message_box(UiStrings().NFdSs, WizardStrings.YouSpecifyFolder % WizardStrings.PS) self.powerSongBrowseButton.setFocus() diff --git a/openlp/plugins/songs/lib/powersongimport.py b/openlp/plugins/songs/lib/powersongimport.py index 31491398c..901aaa737 100644 --- a/openlp/plugins/songs/lib/powersongimport.py +++ b/openlp/plugins/songs/lib/powersongimport.py @@ -29,8 +29,12 @@ The :mod:`powersongimport` module provides the functionality for importing PowerSong songs into the OpenLP database. """ import logging +import glob +import os +import fnmatch from openlp.core.lib import translate +from openlp.core.ui.wizard import WizardStrings from openlp.plugins.songs.lib.songimport import SongImport log = logging.getLogger(__name__) @@ -71,11 +75,22 @@ class PowerSongImport(SongImport): def doImport(self): """ - Receive a list of files to import. + Receive either a list of files or a folder (unicode) to import. """ - if not isinstance(self.importSource, list): + if isinstance(self.importSource, unicode): + if os.path.isdir(self.importSource): + dir = self.importSource + self.importSource = [] + for file in os.listdir(dir): + if fnmatch.fnmatch(file, u'*.song'): + self.importSource.append(os.path.join(dir, file)) + else: + self.importSource = u'' + if not self.importSource or not isinstance(self.importSource, list): self.logError(unicode(translate('SongsPlugin.PowerSongImport', - 'No files to import.'))) + 'No songs to import.')), + unicode(translate('SongsPlugin.PowerSongImport', + 'No %s files found.' % WizardStrings.PS))) return self.importWizard.progressBar.setMaximum(len(self.importSource)) for file in self.importSource: @@ -92,9 +107,10 @@ class PowerSongImport(SongImport): field = self._readString(song_data) except ValueError: parse_error = True - self.logError(file, unicode( + self.logError(os.path.basename(file), unicode( translate('SongsPlugin.PowerSongImport', - 'Invalid PowerSong file. Unexpected byte value.'))) + 'Invalid %s file. Unexpected byte value.' + % WizardStrings.PS))) break else: if label == u'TITLE': @@ -110,26 +126,26 @@ class PowerSongImport(SongImport): continue # Check that file had TITLE field if not self.title: - self.logError(file, unicode( + self.logError(os.path.basename(file), unicode( translate('SongsPlugin.PowerSongImport', - 'Invalid PowerSong file. Missing "TITLE" header.'))) + 'Invalid %s file. Missing "TITLE" header.' + % WizardStrings.PS))) continue # Check that file had COPYRIGHTLINE label if not found_copyright: - self.logError(file, unicode( + self.logError(self.title, unicode( translate('SongsPlugin.PowerSongImport', - '"%s" Invalid PowerSong file. Missing "COPYRIGHTLINE" ' - 'header.' % self.title))) + 'Invalid %s file. Missing "COPYRIGHTLINE" ' + 'header.' % WizardStrings.PS))) continue # Check that file had at least one verse if not self.verses: - self.logError(file, unicode( + self.logError(self.title, unicode( translate('SongsPlugin.PowerSongImport', - '"%s" Verses not found. Missing "PART" header.' - % self.title))) + 'Verses not found. Missing "PART" header.'))) continue if not self.finish(): - self.logError(file) + self.logError(self.title) def _readString(self, file_object): """ From 31debbe23840a8d1ad57aa3e3a300acab5f213a4 Mon Sep 17 00:00:00 2001 From: Samuel Findlay Date: Mon, 7 May 2012 23:26:52 +1000 Subject: [PATCH 23/35] Improved PowerSong import wizard validation --- openlp/plugins/songs/forms/songimportform.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/openlp/plugins/songs/forms/songimportform.py b/openlp/plugins/songs/forms/songimportform.py index cf9b876bb..8428daa70 100644 --- a/openlp/plugins/songs/forms/songimportform.py +++ b/openlp/plugins/songs/forms/songimportform.py @@ -28,6 +28,7 @@ The song import functions for OpenLP. """ import codecs +import fnmatch import logging import os @@ -399,7 +400,8 @@ class SongImportForm(OpenLPWizard): return False elif source_format == SongFormat.PowerSong: if self.powerSongFilenameEdit.text().isEmpty() or \ - not os.path.isdir(self.powerSongFilenameEdit.text()): + not self.isPowerSongFolder( + self.powerSongFilenameEdit.text()): critical_error_message_box(UiStrings().NFdSs, WizardStrings.YouSpecifyFolder % WizardStrings.PS) self.powerSongBrowseButton.setFocus() @@ -482,6 +484,16 @@ class SongImportForm(OpenLPWizard): elif self.currentPage() == self.progressPage: return True + def isPowerSongFolder(self, dir): + """ + Checks if a folder is a PowerSong 1.0 folder + """ + if os.path.isdir(dir): + for file in os.listdir(dir): + if fnmatch.fnmatch(file, u'*.song'): + return True + return False + def getFiles(self, title, listbox, filters=u''): """ Opens a QFileDialog and writes the filenames to the given listbox. From a03af240dfa1a0bfe30544852a8c4002b5123e9a Mon Sep 17 00:00:00 2001 From: Samuel Findlay Date: Mon, 7 May 2012 23:38:02 +1000 Subject: [PATCH 24/35] Minor fixes --- openlp/core/ui/wizard.py | 2 +- openlp/plugins/songs/lib/powersongimport.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/openlp/core/ui/wizard.py b/openlp/core/ui/wizard.py index 7382fb7e0..0b4e8ec37 100644 --- a/openlp/core/ui/wizard.py +++ b/openlp/core/ui/wizard.py @@ -292,4 +292,4 @@ class OpenLPWizard(QtGui.QWizard): if folder: editbox.setText(folder) SettingsManager.set_last_dir(self.plugin.settingsSection, - folder, 1) \ No newline at end of file + folder, 1) diff --git a/openlp/plugins/songs/lib/powersongimport.py b/openlp/plugins/songs/lib/powersongimport.py index 901aaa737..b80cf4497 100644 --- a/openlp/plugins/songs/lib/powersongimport.py +++ b/openlp/plugins/songs/lib/powersongimport.py @@ -29,9 +29,8 @@ The :mod:`powersongimport` module provides the functionality for importing PowerSong songs into the OpenLP database. """ import logging -import glob -import os import fnmatch +import os from openlp.core.lib import translate from openlp.core.ui.wizard import WizardStrings From 5f1afcf9de49af5c37d072e125d3434b9c02ed95 Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Mon, 7 May 2012 17:49:22 +0200 Subject: [PATCH 25/35] show shortcuts in service item menu --- openlp/core/ui/servicemanager.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 4fa36cc2f..86bb54ad2 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -298,17 +298,14 @@ class ServiceManager(QtGui.QWidget): self.timeAction = create_widget_action(self.menu, text=translate('OpenLP.ServiceManager', '&Start Time'), icon=u':/media/media_time.png', triggers=self.onStartTimeForm) - self.deleteAction = create_widget_action(self.menu, - text=translate('OpenLP.ServiceManager', '&Delete From Service'), - icon=u':/general/general_delete.png', - triggers=self.onDeleteFromService) + # Add already existing delete action to the menu. + self.menu.addAction(self.serviceManagerList.delete) self.menu.addSeparator() self.previewAction = create_widget_action(self.menu, text=translate('OpenLP.ServiceManager', 'Show &Preview'), icon=u':/general/general_preview.png', triggers=self.makePreview) - self.liveAction = create_widget_action(self.menu, - text=translate('OpenLP.ServiceManager', 'Show &Live'), - icon=u':/general/general_live.png', triggers=self.makeLive) + # Add already existing make live action to the menu. + self.menu.addAction(self.serviceManagerList.makeLive) self.menu.addSeparator() self.themeMenu = QtGui.QMenu( translate('OpenLP.ServiceManager', '&Change Item Theme')) From 7cb1603d12f3510c2331aec4d25af507bddfd240 Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Mon, 7 May 2012 17:01:53 +0100 Subject: [PATCH 26/35] Fix traceback --- 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 01e624831..7d3e8289c 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -1329,7 +1329,7 @@ class ServiceManager(QtGui.QWidget): (1, 2) """ items = self.serviceManagerList.selectedItems() - serviceItem = 0 + serviceItem = -1 serviceItemChild = -1 for item in items: parentitem = item.parent() From 8436d9d8c0e18afafdea19cb1e9f780a81ab8250 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Fri, 11 May 2012 21:09:12 +0200 Subject: [PATCH 27/35] Fixed a super-annoying bug where any newly imported Bibles would throw an exception. --- openlp/plugins/bibles/lib/manager.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openlp/plugins/bibles/lib/manager.py b/openlp/plugins/bibles/lib/manager.py index 9a82cc388..6b5cac3c1 100644 --- a/openlp/plugins/bibles/lib/manager.py +++ b/openlp/plugins/bibles/lib/manager.py @@ -332,7 +332,10 @@ class BibleManager(object): return None language_selection = self.get_meta_data(bible, u'book_name_language') if language_selection: - language_selection = int(language_selection.value) + try: + language_selection = int(language_selection.value) + except (ValueError, TypeError): + language_selection = 0 if language_selection is None or language_selection == -1: language_selection = QtCore.QSettings().value( self.settingsSection + u'/bookname language', From bfcb6a50b0a8466d31935a56ac9bfa7740c61c03 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Tue, 15 May 2012 23:06:09 +0200 Subject: [PATCH 28/35] Reworked according to Andreas' specifications. --- openlp/plugins/bibles/lib/manager.py | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/openlp/plugins/bibles/lib/manager.py b/openlp/plugins/bibles/lib/manager.py index 6b5cac3c1..bfba082af 100644 --- a/openlp/plugins/bibles/lib/manager.py +++ b/openlp/plugins/bibles/lib/manager.py @@ -330,16 +330,7 @@ class BibleManager(object): 'Import Wizard to install one or more Bibles.') }) return None - language_selection = self.get_meta_data(bible, u'book_name_language') - if language_selection: - try: - language_selection = int(language_selection.value) - except (ValueError, TypeError): - language_selection = 0 - if language_selection is None or language_selection == -1: - language_selection = QtCore.QSettings().value( - self.settingsSection + u'/bookname language', - QtCore.QVariant(0)).toInt()[0] + language_selection = self.get_language_selection(bible) reflist = parse_reference(versetext, self.db_cache[bible], language_selection, book_ref_id) if reflist: @@ -381,12 +372,16 @@ class BibleManager(object): """ log.debug(u'BibleManager.get_language_selection("%s")', bible) language_selection = self.get_meta_data(bible, u'book_name_language') - if language_selection and language_selection.value != u'None': - return int(language_selection.value) - if language_selection is None or language_selection.value == u'None': - return QtCore.QSettings().value( + if language_selection: + try: + language_selection = int(language_selection.value) + except (ValueError, TypeError): + language_selection = LanguageSelection.Application + if language_selection is None or language_selection == -1: + language_selection = QtCore.QSettings().value( self.settingsSection + u'/bookname language', QtCore.QVariant(0)).toInt()[0] + return language_selection def verse_search(self, bible, second_bible, text): """ From f5cba00ca978306c4084edfe64ecfcd48c8fbaf6 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Wed, 16 May 2012 18:42:33 +0200 Subject: [PATCH 29/35] Fixed a small bug left over from a previous refactoring. Fixes: https://launchpad.net/bugs/997174 --- openlp/plugins/bibles/lib/mediaitem.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index 3c723d760..288e0e2de 100644 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -843,10 +843,11 @@ class BibleMediaItem(MediaManagerItem): items = [] language_selection = self.plugin.manager.get_language_selection(bible) for count, verse in enumerate(search_results): + book = None if language_selection == LanguageSelection.Bible: book = verse.book.name elif language_selection == LanguageSelection.Application: - book_names = BibleStrings().Booknames + book_names = BibleStrings().BookNames data = BiblesResourcesDB.get_book_by_id( verse.book.book_reference_id) book = unicode(book_names[data[u'abbreviation']]) From 0b9923742eea38d4fb60c4aae488b022dcdf9665 Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Thu, 17 May 2012 14:59:57 +0200 Subject: [PATCH 30/35] trivial clean ups --- openlp/core/ui/servicemanager.py | 37 ++++++++++++++------------------ 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 79faad819..5aa9299bb 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -352,8 +352,8 @@ class ServiceManager(QtGui.QWidget): self._fileName = unicode(fileName) self.mainwindow.setServiceModified(self.isModified(), self.shortFileName()) - QtCore.QSettings(). \ - setValue(u'servicemanager/last file',QtCore.QVariant(fileName)) + QtCore.QSettings().setValue( + u'servicemanager/last file',QtCore.QVariant(fileName)) def fileName(self): """ @@ -372,8 +372,7 @@ class ServiceManager(QtGui.QWidget): Triggered when Config dialog is updated. """ self.expandTabs = QtCore.QSettings().value( - u'advanced/expand service item', - QtCore.QVariant(u'False')).toBool() + u'advanced/expand service item', QtCore.QVariant(u'False')).toBool() def supportedSuffixes(self, suffix): self.suffixes.append(suffix) @@ -444,8 +443,8 @@ class ServiceManager(QtGui.QWidget): self.setFileName(u'') self.serviceId += 1 self.setModified(False) - QtCore.QSettings(). \ - setValue(u'servicemanager/last file',QtCore.QVariant(u'')) + QtCore.QSettings().setValue( + u'servicemanager/last file',QtCore.QVariant(u'')) Receiver.send_message(u'servicemanager_new_service') def saveFile(self): @@ -469,8 +468,7 @@ class ServiceManager(QtGui.QWidget): service_file_name = '%s.osd' % basename log.debug(u'ServiceManager.saveFile - %s', path_file_name) SettingsManager.set_last_dir( - self.mainwindow.serviceManagerSettingsSection, - path) + self.mainwindow.serviceManagerSettingsSection, path) service = [] write_list = [] audio_files = [] @@ -561,14 +559,12 @@ class ServiceManager(QtGui.QWidget): zip.write(audio_from, audio_to.encode(u'utf-8')) except IOError: log.exception(u'Failed to save service to disk: %s', temp_file_name) - # Add this line in after the release to notify the user that saving - # their file failed. Commented out due to string freeze. - #Receiver.send_message(u'openlp_error_message', { - # u'title': translate(u'OpenLP.ServiceManager', - # u'Error Saving File'), - # u'message': translate(u'OpenLP.ServiceManager', - # u'There was an error saving your file.') - #}) + Receiver.send_message(u'openlp_error_message', { + u'title': translate(u'OpenLP.ServiceManager', + u'Error Saving File'), + u'message': translate(u'OpenLP.ServiceManager', + u'There was an error saving your file.') + }) success = False finally: if zip: @@ -734,8 +730,8 @@ class ServiceManager(QtGui.QWidget): service was last closed. Can be blank if there was no service present. """ - fileName = QtCore.QSettings(). \ - value(u'servicemanager/last file',QtCore.QVariant(u'')).toString() + fileName = QtCore.QSettings().value( + u'servicemanager/last file',QtCore.QVariant(u'')).toString() if fileName: self.loadFile(fileName) @@ -752,7 +748,7 @@ class ServiceManager(QtGui.QWidget): self.maintainAction.setVisible(False) self.notesAction.setVisible(False) self.timeAction.setVisible(False) - if serviceItem[u'service_item'].is_capable(ItemCapabilities.CanEdit)\ + if serviceItem[u'service_item'].is_capable(ItemCapabilities.CanEdit) \ and serviceItem[u'service_item'].edit_id: self.editAction.setVisible(True) if serviceItem[u'service_item']\ @@ -1107,8 +1103,7 @@ class ServiceManager(QtGui.QWidget): self.service_theme = unicode(self.themeComboBox.currentText()) self.mainwindow.renderer.set_service_theme(self.service_theme) QtCore.QSettings().setValue( - self.mainwindow.serviceManagerSettingsSection + - u'/service theme', + self.mainwindow.serviceManagerSettingsSection + u'/service theme', QtCore.QVariant(self.service_theme)) self.regenerateServiceItems(True) From 779a1201fccdf2dfc9270320cab3d0e60738ece6 Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Thu, 17 May 2012 22:26:31 +0200 Subject: [PATCH 31/35] reverted some changes to avoid conflicts + clean up --- openlp.pyw | 6 ------ openlp/core/ui/servicemanager.py | 23 +++++++++++++---------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/openlp.pyw b/openlp.pyw index cc58c6a21..8d5c90957 100755 --- a/openlp.pyw +++ b/openlp.pyw @@ -26,12 +26,6 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### -# Import uuid now, to avoid the rare bug described in the support system: -# http://support.openlp.org/issues/102 -# If https://bugs.gentoo.org/show_bug.cgi?id=317557 is fixed, the import can be -# removed. -import uuid - from openlp.core import main diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 5aa9299bb..30567f2d2 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -352,8 +352,8 @@ class ServiceManager(QtGui.QWidget): self._fileName = unicode(fileName) self.mainwindow.setServiceModified(self.isModified(), self.shortFileName()) - QtCore.QSettings().setValue( - u'servicemanager/last file',QtCore.QVariant(fileName)) + QtCore.QSettings(). \ + setValue(u'servicemanager/last file',QtCore.QVariant(fileName)) def fileName(self): """ @@ -372,7 +372,8 @@ class ServiceManager(QtGui.QWidget): Triggered when Config dialog is updated. """ self.expandTabs = QtCore.QSettings().value( - u'advanced/expand service item', QtCore.QVariant(u'False')).toBool() + u'advanced/expand service item', + QtCore.QVariant(u'False')).toBool() def supportedSuffixes(self, suffix): self.suffixes.append(suffix) @@ -443,8 +444,8 @@ class ServiceManager(QtGui.QWidget): self.setFileName(u'') self.serviceId += 1 self.setModified(False) - QtCore.QSettings().setValue( - u'servicemanager/last file',QtCore.QVariant(u'')) + QtCore.QSettings(). \ + setValue(u'servicemanager/last file',QtCore.QVariant(u'')) Receiver.send_message(u'servicemanager_new_service') def saveFile(self): @@ -468,7 +469,8 @@ class ServiceManager(QtGui.QWidget): service_file_name = '%s.osd' % basename log.debug(u'ServiceManager.saveFile - %s', path_file_name) SettingsManager.set_last_dir( - self.mainwindow.serviceManagerSettingsSection, path) + self.mainwindow.serviceManagerSettingsSection, + path) service = [] write_list = [] audio_files = [] @@ -730,8 +732,8 @@ class ServiceManager(QtGui.QWidget): service was last closed. Can be blank if there was no service present. """ - fileName = QtCore.QSettings().value( - u'servicemanager/last file',QtCore.QVariant(u'')).toString() + fileName = QtCore.QSettings(). \ + value(u'servicemanager/last file',QtCore.QVariant(u'')).toString() if fileName: self.loadFile(fileName) @@ -748,7 +750,7 @@ class ServiceManager(QtGui.QWidget): self.maintainAction.setVisible(False) self.notesAction.setVisible(False) self.timeAction.setVisible(False) - if serviceItem[u'service_item'].is_capable(ItemCapabilities.CanEdit) \ + if serviceItem[u'service_item'].is_capable(ItemCapabilities.CanEdit)\ and serviceItem[u'service_item'].edit_id: self.editAction.setVisible(True) if serviceItem[u'service_item']\ @@ -1103,7 +1105,8 @@ class ServiceManager(QtGui.QWidget): self.service_theme = unicode(self.themeComboBox.currentText()) self.mainwindow.renderer.set_service_theme(self.service_theme) QtCore.QSettings().setValue( - self.mainwindow.serviceManagerSettingsSection + u'/service theme', + self.mainwindow.serviceManagerSettingsSection + + u'/service theme', QtCore.QVariant(self.service_theme)) self.regenerateServiceItems(True) From cdd867a755c80575e2f0f161f6a4b89f2015483b Mon Sep 17 00:00:00 2001 From: Samuel Findlay Date: Sat, 19 May 2012 20:43:19 +1000 Subject: [PATCH 32/35] Added static method isValidSource so importers can validate their input. Also tidyup: removed SongImport.errorLog --- openlp/plugins/songs/forms/songimportform.py | 23 +++++--------------- openlp/plugins/songs/lib/powersongimport.py | 15 +++++++++++++ openlp/plugins/songs/lib/songimport.py | 13 +++++++++-- 3 files changed, 31 insertions(+), 20 deletions(-) diff --git a/openlp/plugins/songs/forms/songimportform.py b/openlp/plugins/songs/forms/songimportform.py index 8428daa70..17c854791 100644 --- a/openlp/plugins/songs/forms/songimportform.py +++ b/openlp/plugins/songs/forms/songimportform.py @@ -386,6 +386,7 @@ class SongImportForm(OpenLPWizard): source_format = self.formatComboBox.currentIndex() QtCore.QSettings().setValue(u'songs/last import type', source_format) + import_class = SongFormat.get_class(source_format) if source_format == SongFormat.OpenLP2: if self.openLP2FilenameEdit.text().isEmpty(): critical_error_message_box(UiStrings().NFSs, @@ -400,8 +401,8 @@ class SongImportForm(OpenLPWizard): return False elif source_format == SongFormat.PowerSong: if self.powerSongFilenameEdit.text().isEmpty() or \ - not self.isPowerSongFolder( - self.powerSongFilenameEdit.text()): + not import_class.isValidSource( + folder=self.powerSongFilenameEdit.text()): critical_error_message_box(UiStrings().NFdSs, WizardStrings.YouSpecifyFolder % WizardStrings.PS) self.powerSongBrowseButton.setFocus() @@ -484,16 +485,6 @@ class SongImportForm(OpenLPWizard): elif self.currentPage() == self.progressPage: return True - def isPowerSongFolder(self, dir): - """ - Checks if a folder is a PowerSong 1.0 folder - """ - if os.path.isdir(dir): - for file in os.listdir(dir): - if fnmatch.fnmatch(file, u'*.song'): - return True - return False - def getFiles(self, title, listbox, filters=u''): """ Opens a QFileDialog and writes the filenames to the given listbox. @@ -797,7 +788,7 @@ class SongImportForm(OpenLPWizard): elif source_format == SongFormat.PowerSong: # Import PowerSong folder importer = self.plugin.importSongs(SongFormat.PowerSong, - filename=unicode(self.powerSongFilenameEdit.text()) + folder=unicode(self.powerSongFilenameEdit.text()) ) elif source_format == SongFormat.OpenLyrics: # Import OpenLyrics songs @@ -863,11 +854,7 @@ class SongImportForm(OpenLPWizard): filenames=self.getListOfFiles(self.foilPresenterFileListWidget) ) importer.doImport() - if importer.errorLog: - self.progressLabel.setText(translate( - 'SongsPlugin.SongImportForm', 'Your song import failed.')) - else: - self.progressLabel.setText(WizardStrings.FinishedImport) + self.progressLabel.setText(WizardStrings.FinishedImport) def onErrorCopyToButtonClicked(self): """ diff --git a/openlp/plugins/songs/lib/powersongimport.py b/openlp/plugins/songs/lib/powersongimport.py index b80cf4497..9946d273d 100644 --- a/openlp/plugins/songs/lib/powersongimport.py +++ b/openlp/plugins/songs/lib/powersongimport.py @@ -72,6 +72,21 @@ class PowerSongImport(SongImport): * .song """ + @staticmethod + def isValidSource(**kwargs): + """ + Checks if source is a PowerSong 1.0 folder: + * is a directory + * contains at least one *.song file + """ + if u'folder' in kwargs: + dir = kwargs[u'folder'] + if os.path.isdir(dir): + for file in os.listdir(dir): + if fnmatch.fnmatch(file, u'*.song'): + return True + return False + def doImport(self): """ Receive either a list of files or a folder (unicode) to import. diff --git a/openlp/plugins/songs/lib/songimport.py b/openlp/plugins/songs/lib/songimport.py index 9bfdce124..ac6818184 100644 --- a/openlp/plugins/songs/lib/songimport.py +++ b/openlp/plugins/songs/lib/songimport.py @@ -50,6 +50,13 @@ class SongImport(QtCore.QObject): whether the authors etc already exist and add them or refer to them as necessary """ + @staticmethod + def isValidSource(**kwargs): + """ + Override this method to validate the source prior to import. + """ + pass + def __init__(self, manager, **kwargs): """ Initialise and create defaults for properties @@ -65,14 +72,16 @@ class SongImport(QtCore.QObject): self.importSource = kwargs[u'filename'] elif u'filenames' in kwargs: self.importSource = kwargs[u'filenames'] + elif u'folder' in kwargs: + self.importSource = kwargs[u'folder'] else: - raise KeyError(u'Keyword arguments "filename[s]" not supplied.') + raise KeyError( + u'Keyword arguments "filename[s]" or "folder" not supplied.') log.debug(self.importSource) self.importWizard = None self.song = None self.stopImportFlag = False self.setDefaults() - self.errorLog = [] QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'openlp_stop_wizard'), self.stopImport) From 0ba3ccf19900471d05dcbbe758d058a5675d9e5d Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Sat, 19 May 2012 20:56:28 +0200 Subject: [PATCH 33/35] random clean ups --- openlp/core/ui/themeform.py | 37 +++++++++++++------------------------ 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/openlp/core/ui/themeform.py b/openlp/core/ui/themeform.py index f8c061851..60e073b13 100644 --- a/openlp/core/ui/themeform.py +++ b/openlp/core/ui/themeform.py @@ -136,16 +136,14 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): """ self.backgroundPage.registerField( u'background_type', self.backgroundComboBox) - self.backgroundPage.registerField( - u'color', self.colorButton) + self.backgroundPage.registerField(u'color', self.colorButton) self.backgroundPage.registerField( u'grandient_start', self.gradientStartButton) self.backgroundPage.registerField( u'grandient_end', self.gradientEndButton) self.backgroundPage.registerField( u'background_image', self.imageFileEdit) - self.backgroundPage.registerField( - u'gradient', self.gradientComboBox) + self.backgroundPage.registerField(u'gradient', self.gradientComboBox) self.mainAreaPage.registerField( u'mainColorButton', self.mainColorButton) self.mainAreaPage.registerField( @@ -158,8 +156,7 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): u'outlineColorButton', self.outlineColorButton) self.mainAreaPage.registerField( u'outlineSizeSpinBox', self.outlineSizeSpinBox) - self.mainAreaPage.registerField( - u'shadowCheckBox', self.shadowCheckBox) + self.mainAreaPage.registerField(u'shadowCheckBox', self.shadowCheckBox) self.mainAreaPage.registerField( u'mainBoldCheckBox', self.mainBoldCheckBox) self.mainAreaPage.registerField( @@ -170,10 +167,8 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): u'shadowSizeSpinBox', self.shadowSizeSpinBox) self.mainAreaPage.registerField( u'footerSizeSpinBox', self.footerSizeSpinBox) - self.areaPositionPage.registerField( - u'mainPositionX', self.mainXSpinBox) - self.areaPositionPage.registerField( - u'mainPositionY', self.mainYSpinBox) + self.areaPositionPage.registerField(u'mainPositionX', self.mainXSpinBox) + self.areaPositionPage.registerField(u'mainPositionY', self.mainYSpinBox) self.areaPositionPage.registerField( u'mainPositionWidth', self.mainWidthSpinBox) self.areaPositionPage.registerField( @@ -188,12 +183,10 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): u'footerPositionHeight', self.footerHeightSpinBox) self.backgroundPage.registerField( u'horizontal', self.horizontalComboBox) - self.backgroundPage.registerField( - u'vertical', self.verticalComboBox) + self.backgroundPage.registerField(u'vertical', self.verticalComboBox) self.backgroundPage.registerField( u'slideTransition', self.transitionsCheckBox) - self.backgroundPage.registerField( - u'name', self.themeNameEdit) + self.backgroundPage.registerField(u'name', self.themeNameEdit) def calculateLines(self): """ @@ -269,10 +262,7 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): Change state as Outline check box changed """ if self.updateThemeAllowed: - if state == QtCore.Qt.Checked: - self.theme.font_main_outline = True - else: - self.theme.font_main_outline = False + self.theme.font_main_outline = state == QtCore.Qt.Checked self.outlineColorButton.setEnabled(self.theme.font_main_outline) self.outlineSizeSpinBox.setEnabled(self.theme.font_main_outline) self.calculateLines() @@ -350,19 +340,19 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): if self.theme.background_type == \ BackgroundType.to_string(BackgroundType.Solid): self.colorButton.setStyleSheet(u'background-color: %s' % - self.theme.background_color) + self.theme.background_color) self.setField(u'background_type', QtCore.QVariant(0)) elif self.theme.background_type == \ BackgroundType.to_string(BackgroundType.Gradient): self.gradientStartButton.setStyleSheet(u'background-color: %s' % - self.theme.background_start_color) + self.theme.background_start_color) self.gradientEndButton.setStyleSheet(u'background-color: %s' % - self.theme.background_end_color) + self.theme.background_end_color) self.setField(u'background_type', QtCore.QVariant(1)) elif self.theme.background_type == \ BackgroundType.to_string(BackgroundType.Image): self.imageColorButton.setStyleSheet(u'background-color: %s' % - self.theme.background_border_color) + self.theme.background_border_color) self.imageFileEdit.setText(self.theme.background_filename) self.setField(u'background_type', QtCore.QVariant(2)) elif self.theme.background_type == \ @@ -642,8 +632,7 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): """ Handle Color buttons """ - new_color = QtGui.QColorDialog.getColor( - QtGui.QColor(field), self) + new_color = QtGui.QColorDialog.getColor(QtGui.QColor(field), self) if new_color.isValid(): field = new_color.name() return field From e01995eafd5e2cc6fc2ee6b1846ee2641e965e07 Mon Sep 17 00:00:00 2001 From: Samuel Findlay Date: Sun, 20 May 2012 11:42:07 +1000 Subject: [PATCH 34/35] Tidy up --- openlp/plugins/songs/forms/songimportform.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openlp/plugins/songs/forms/songimportform.py b/openlp/plugins/songs/forms/songimportform.py index 17c854791..4bdabd1a2 100644 --- a/openlp/plugins/songs/forms/songimportform.py +++ b/openlp/plugins/songs/forms/songimportform.py @@ -28,7 +28,6 @@ The song import functions for OpenLP. """ import codecs -import fnmatch import logging import os From 1c57abf8abef22a24bc6e88e1b1f56f7f1970cbf Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Sun, 20 May 2012 22:56:11 +0200 Subject: [PATCH 35/35] Refactored ScreenList into a true Pythonic singleton, though I sadly had to go with a "create" classmethod in order to retain the functionality of the previous constructor.. --- openlp/core/__init__.py | 2 +- openlp/core/lib/dockwidget.py | 2 +- openlp/core/lib/imagemanager.py | 4 +-- openlp/core/lib/renderer.py | 2 +- openlp/core/ui/generaltab.py | 2 +- openlp/core/ui/maindisplay.py | 2 +- openlp/core/ui/mainwindow.py | 2 +- openlp/core/ui/screen.py | 42 +++++++++++++++++-------------- openlp/core/ui/slidecontroller.py | 2 +- 9 files changed, 32 insertions(+), 28 deletions(-) diff --git a/openlp/core/__init__.py b/openlp/core/__init__.py index 71c27a1d0..d40d7c758 100644 --- a/openlp/core/__init__.py +++ b/openlp/core/__init__.py @@ -109,7 +109,7 @@ class OpenLP(QtGui.QApplication): QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'cursor_normal'), self.setNormalCursor) # Decide how many screens we have and their size - screens = ScreenList(self.desktop()) + screens = ScreenList.create(self.desktop()) # First time checks in settings has_run_wizard = QtCore.QSettings().value( u'general/has run wizard', QtCore.QVariant(False)).toBool() diff --git a/openlp/core/lib/dockwidget.py b/openlp/core/lib/dockwidget.py index e08b5eee5..23ce9efcb 100644 --- a/openlp/core/lib/dockwidget.py +++ b/openlp/core/lib/dockwidget.py @@ -52,7 +52,7 @@ class OpenLPDockWidget(QtGui.QDockWidget): if icon: self.setWindowIcon(build_icon(icon)) # Sort out the minimum width. - screens = ScreenList.get_instance() + screens = ScreenList() mainwindow_docbars = screens.current[u'size'].width() / 5 if mainwindow_docbars > 300: self.setMinimumWidth(300) diff --git a/openlp/core/lib/imagemanager.py b/openlp/core/lib/imagemanager.py index b32e36194..47a7ed3f6 100644 --- a/openlp/core/lib/imagemanager.py +++ b/openlp/core/lib/imagemanager.py @@ -163,7 +163,7 @@ class ImageManager(QtCore.QObject): def __init__(self): QtCore.QObject.__init__(self) - current_screen = ScreenList.get_instance().current + current_screen = ScreenList().current self.width = current_screen[u'size'].width() self.height = current_screen[u'size'].height() self._cache = {} @@ -177,7 +177,7 @@ class ImageManager(QtCore.QObject): Screen has changed size so rebuild the cache to new size. """ log.debug(u'update_display') - current_screen = ScreenList.get_instance().current + current_screen = ScreenList().current self.width = current_screen[u'size'].width() self.height = current_screen[u'size'].height() # Mark the images as dirty for a rebuild by setting the image and byte diff --git a/openlp/core/lib/renderer.py b/openlp/core/lib/renderer.py index 8694ca6b6..aa39e779b 100644 --- a/openlp/core/lib/renderer.py +++ b/openlp/core/lib/renderer.py @@ -69,7 +69,7 @@ class Renderer(object): log.debug(u'Initialisation started') self.themeManager = themeManager self.imageManager = imageManager - self.screens = ScreenList.get_instance() + self.screens = ScreenList() self.service_theme = u'' self.theme_level = u'' self.override_background = None diff --git a/openlp/core/ui/generaltab.py b/openlp/core/ui/generaltab.py index baf28f40f..d0647d829 100644 --- a/openlp/core/ui/generaltab.py +++ b/openlp/core/ui/generaltab.py @@ -42,7 +42,7 @@ class GeneralTab(SettingsTab): """ Initialise the general settings tab """ - self.screens = ScreenList.get_instance() + self.screens = ScreenList() self.iconPath = u':/icon/openlp-logo-16x16.png' generalTranslated = translate('OpenLP.GeneralTab', 'General') SettingsTab.__init__(self, parent, u'General', generalTranslated) diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index 15fb9eefe..a04203387 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -119,7 +119,7 @@ class MainDisplay(Display): def __init__(self, parent, imageManager, live, controller): Display.__init__(self, parent, live, controller) self.imageManager = imageManager - self.screens = ScreenList.get_instance() + self.screens = ScreenList() self.plugins = PluginManager.get_instance().plugins self.rebuildCSS = False self.hideMode = None diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index e4a4e1616..bff9203db 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -795,7 +795,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): if answer == QtGui.QMessageBox.No: return Receiver.send_message(u'cursor_busy') - screens = ScreenList.get_instance() + screens = ScreenList() FirstTimeForm(screens, self).exec_() self.firstTime() for plugin in self.pluginManager.plugins: diff --git a/openlp/core/ui/screen.py b/openlp/core/ui/screen.py index 21fbd6144..72c88908e 100644 --- a/openlp/core/ui/screen.py +++ b/openlp/core/ui/screen.py @@ -41,36 +41,40 @@ class ScreenList(object): """ Wrapper to handle the parameters of the display screen. - To get access to the screen list call ``ScreenList.get_instance()``. + To get access to the screen list call ``ScreenList()``. """ log.info(u'Screen loaded') - instance = None + __instance__ = None - @staticmethod - def get_instance(): - return ScreenList.instance + def __new__(cls): + if not cls.__instance__: + cls.__instance__ = object.__new__(cls) + return cls.__instance__ - def __init__(self, desktop): + @classmethod + def create(cls, desktop): """ Initialise the screen list. ``desktop`` A ``QDesktopWidget`` object. """ - ScreenList.instance = self - self.desktop = desktop - self.preview = None - self.current = None - self.override = None - self.screen_list = [] - self.display_count = 0 - self.screen_count_changed() - self._load_screen_settings() + screen_list = cls() + screen_list.desktop = desktop + screen_list.preview = None + screen_list.current = None + screen_list.override = None + screen_list.screen_list = [] + screen_list.display_count = 0 + screen_list.screen_count_changed() + screen_list._load_screen_settings() QtCore.QObject.connect(desktop, - QtCore.SIGNAL(u'resized(int)'), self.screen_resolution_changed) + QtCore.SIGNAL(u'resized(int)'), + screen_list.screen_resolution_changed) QtCore.QObject.connect(desktop, QtCore.SIGNAL(u'screenCountChanged(int)'), - self.screen_count_changed) + screen_list.screen_count_changed) + return screen_list def screen_resolution_changed(self, number): """ @@ -233,8 +237,8 @@ class ScreenList(object): y = window.y() + (window.height() / 2) for screen in self.screen_list: size = screen[u'size'] - if x >= size.x() and x <= (size.x() + size.width()) \ - and y >= size.y() and y <= (size.y() + size.height()): + if x >= size.x() and x <= (size.x() + size.width()) and \ + y >= size.y() and y <= (size.y() + size.height()): return screen[u'number'] def _load_screen_settings(self): diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 1dc005aa6..99f2c8ad6 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -83,7 +83,7 @@ class SlideController(Controller): Set up the Slide Controller. """ Controller.__init__(self, parent, isLive) - self.screens = ScreenList.get_instance() + self.screens = ScreenList() try: self.ratio = float(self.screens.current[u'size'].width()) / \ float(self.screens.current[u'size'].height())