diff --git a/openlp.pyw b/openlp.pyw index a1a616462..017e12774 100755 --- a/openlp.pyw +++ b/openlp.pyw @@ -34,7 +34,7 @@ from subprocess import Popen, PIPE from PyQt4 import QtCore, QtGui -from openlp.core.lib import Receiver +from openlp.core.lib import Receiver, check_directory_exists from openlp.core.resources import qInitResources from openlp.core.ui.mainwindow import MainWindow from openlp.core.ui.exceptionform import ExceptionForm @@ -150,16 +150,16 @@ class OpenLP(QtGui.QApplication): log.info(u'Openlp version %s' % app_version[u'version']) return app_version - def notify(self, obj, evt): - #TODO needed for presentation exceptions - return QtGui.QApplication.notify(self, obj, evt) +# def notify(self, obj, evt): +# #TODO needed for presentation exceptions +# return QtGui.QApplication.notify(self, obj, evt) def run(self): """ Run the OpenLP application. """ app_version = self._get_version() - #provide a listener for widgets to reqest a screen update. + # provide a listener for widgets to reqest a screen update. QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'openlp_process_events'), self.processEvents) QtCore.QObject.connect(Receiver.get_receiver(), @@ -216,7 +216,7 @@ class OpenLP(QtGui.QApplication): def setNormalCursor(self): """ - Sets the Normal Cursor forthe Application + Sets the Normal Cursor for the Application """ self.restoreOverrideCursor() @@ -243,8 +243,7 @@ def main(): help='Set the Qt4 style (passed directly to Qt4).') # Set up logging log_path = AppLocation.get_directory(AppLocation.CacheDir) - if not os.path.exists(log_path): - os.makedirs(log_path) + check_directory_exists(log_path) filename = os.path.join(log_path, u'openlp.log') logfile = logging.FileHandler(filename, u'w') logfile.setFormatter(logging.Formatter( @@ -281,4 +280,4 @@ if __name__ == u'__main__': """ Instantiate and run the application. """ - main() + main() \ No newline at end of file diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index ebbe31597..7ad377817 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -35,52 +35,51 @@ from PyQt4 import QtCore, QtGui log = logging.getLogger(__name__) -# TODO make external and configurable in alpha 4 via a settings dialog -html_expands = [] +base_html_expands = [] -html_expands.append({u'desc': u'Red', u'start tag': u'{r}', +base_html_expands.append({u'desc': u'Red', u'start tag': u'{r}', u'start html': u'', - u'end tag': u'{/r}', u'end html': u'', u'protected': False}) -html_expands.append({u'desc': u'Black', u'start tag': u'{b}', + u'end tag': u'{/r}', u'end html': u'', u'protected': True}) +base_html_expands.append({u'desc': u'Black', u'start tag': u'{b}', u'start html': u'', - u'end tag': u'{/b}', u'end html': u'', u'protected': False}) -html_expands.append({u'desc': u'Blue', u'start tag': u'{bl}', + u'end tag': u'{/b}', u'end html': u'', u'protected': True}) +base_html_expands.append({u'desc': u'Blue', u'start tag': u'{bl}', u'start html': u'', - u'end tag': u'{/bl}', u'end html': u'', u'protected': False}) -html_expands.append({u'desc': u'Yellow', u'start tag': u'{y}', + u'end tag': u'{/bl}', u'end html': u'', u'protected': True}) +base_html_expands.append({u'desc': u'Yellow', u'start tag': u'{y}', u'start html': u'', - u'end tag': u'{/y}', u'end html': u'', u'protected': False}) -html_expands.append({u'desc': u'Green', u'start tag': u'{g}', + u'end tag': u'{/y}', u'end html': u'', u'protected': True}) +base_html_expands.append({u'desc': u'Green', u'start tag': u'{g}', u'start html': u'', - u'end tag': u'{/g}', u'end html': u'', u'protected': False}) -html_expands.append({u'desc': u'Pink', u'start tag': u'{pk}', + u'end tag': u'{/g}', u'end html': u'', u'protected': True}) +base_html_expands.append({u'desc': u'Pink', u'start tag': u'{pk}', u'start html': u'', - u'end tag': u'{/pk}', u'end html': u'', u'protected': False}) -html_expands.append({u'desc': u'Orange', u'start tag': u'{o}', + u'end tag': u'{/pk}', u'end html': u'', u'protected': True}) +base_html_expands.append({u'desc': u'Orange', u'start tag': u'{o}', u'start html': u'', - u'end tag': u'{/o}', u'end html': u'', u'protected': False}) -html_expands.append({u'desc': u'Purple', u'start tag': u'{pp}', + u'end tag': u'{/o}', u'end html': u'', u'protected': True}) +base_html_expands.append({u'desc': u'Purple', u'start tag': u'{pp}', u'start html': u'', - u'end tag': u'{/pp}', u'end html': u'', u'protected': False}) -html_expands.append({u'desc': u'White', u'start tag': u'{w}', + u'end tag': u'{/pp}', u'end html': u'', u'protected': True}) +base_html_expands.append({u'desc': u'White', u'start tag': u'{w}', u'start html': u'', - u'end tag': u'{/w}', u'end html': u'', u'protected': False}) -html_expands.append({u'desc': u'Superscript', u'start tag': u'{su}', + u'end tag': u'{/w}', u'end html': u'', u'protected': True}) +base_html_expands.append({u'desc': u'Superscript', u'start tag': u'{su}', u'start html': u'', u'end tag': u'{/su}', u'end html': u'', u'protected': True}) -html_expands.append({u'desc': u'Subscript', u'start tag': u'{sb}', +base_html_expands.append({u'desc': u'Subscript', u'start tag': u'{sb}', u'start html': u'', u'end tag': u'{/sb}', u'end html': u'', u'protected': True}) -html_expands.append({u'desc': u'Paragraph', u'start tag': u'{p}', +base_html_expands.append({u'desc': u'Paragraph', u'start tag': u'{p}', u'start html': u'

', u'end tag': u'{/p}', u'end html': u'

', u'protected': True}) -html_expands.append({u'desc': u'Bold', u'start tag': u'{st}', +base_html_expands.append({u'desc': u'Bold', u'start tag': u'{st}', u'start html': u'', u'end tag': u'{/st}', u'end html': u'', u'protected': True}) -html_expands.append({u'desc': u'Italics', u'start tag': u'{it}', +base_html_expands.append({u'desc': u'Italics', u'start tag': u'{it}', u'start html': u'', u'end tag': u'{/it}', u'end html': u'', u'protected': True}) -html_expands.append({u'desc': u'Underline', u'start tag': u'{u}', +base_html_expands.append({u'desc': u'Underline', u'start tag': u'{u}', u'start html': u'', u'end tag': u'{/u}', u'end html': u'', u'protected': True}) @@ -102,7 +101,8 @@ def translate(context, text, comment=None, An identifying string for when the same text is used in different roles within the same context. """ - return QtCore.QCoreApplication.translate(context, text, comment, encoding, n) + return QtCore.QCoreApplication.translate( + context, text, comment, encoding, n) def get_text_file_string(text_file): """ @@ -293,7 +293,7 @@ def clean_tags(text): Remove Tags from text for display """ text = text.replace(u'
', u'\n') - for tag in html_expands: + for tag in DisplayTags.get_html_tags(): text = text.replace(tag[u'start tag'], u'') text = text.replace(tag[u'end tag'], u'') return text @@ -302,13 +302,25 @@ def expand_tags(text): """ Expand tags HTML for display """ - for tag in html_expands: + for tag in DisplayTags.get_html_tags(): text = text.replace(tag[u'start tag'], tag[u'start html']) text = text.replace(tag[u'end tag'], tag[u'end html']) return text +def check_directory_exists(dir): + """ + Check a theme directory exists and if not create it + + ``dir`` + Theme directory to make sure exists + """ + log.debug(u'check_directory_exists') + if not os.path.exists(dir): + os.makedirs(dir) + from theme import ThemeLevel, ThemeXML, BackgroundGradientType, \ BackgroundType, HorizontalType, VerticalType +from displaytags import DisplayTags from spelltextedit import SpellTextEdit from eventreceiver import Receiver from imagemanager import ImageManager diff --git a/openlp/core/lib/displaytags.py b/openlp/core/lib/displaytags.py new file mode 100644 index 000000000..dd7276a7d --- /dev/null +++ b/openlp/core/lib/displaytags.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2011 Raoul Snyman # +# Portions copyright (c) 2008-2011 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Meinert Jordan, Andreas Preikschat, Christian # +# Richter, Philip Ridout, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard, 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 # +############################################################################### +""" +Provide Html Tag management and Display Tag access class +""" + +from openlp.core.lib import base_html_expands + +class DisplayTags(object): + """ + Static Class to HTML Tags to be access around the code the list is managed + by the Options Tab. + """ + html_expands = [] + + @staticmethod + def get_html_tags(): + """ + Provide access to the html_expands list. + """ + return DisplayTags.html_expands + + @staticmethod + def reset_html_tags(): + """ + Resets the html_expands list. + """ + DisplayTags.html_expands = [] + for html in base_html_expands: + DisplayTags.html_expands.append(html) + + @staticmethod + def add_html_tag(tag): + """ + Add a new tag to the list + """ + DisplayTags.html_expands.append(tag) + + @staticmethod + def remove_html_tag(id): + """ + Removes amd individual html_expands list. + """ + DisplayTags.html_expands.pop(id) diff --git a/openlp/core/lib/mediamanageritem.py b/openlp/core/lib/mediamanageritem.py index c92960163..2d6bcce46 100644 --- a/openlp/core/lib/mediamanageritem.py +++ b/openlp/core/lib/mediamanageritem.py @@ -176,7 +176,8 @@ class MediaManagerItem(QtGui.QWidget): # break compatability), but it makes sense for the icon to # come before the tooltip (as you have to have an icon, but # not neccesarily a tooltip) - return self.toolbar.addToolbarButton(title, icon, tooltip, slot, checkable) + return self.toolbar.addToolbarButton(title, icon, tooltip, slot, + checkable) def addToolbarSeparator(self): """ diff --git a/openlp/core/lib/plugin.py b/openlp/core/lib/plugin.py index 8bc5fdb96..5a3fa41ce 100644 --- a/openlp/core/lib/plugin.py +++ b/openlp/core/lib/plugin.py @@ -175,6 +175,10 @@ class Plugin(QtCore.QObject): self.status = new_status QtCore.QSettings().setValue( self.settingsSection + u'/status', QtCore.QVariant(self.status)) + if new_status == PluginStatus.Active: + self.initialise() + elif new_status == PluginStatus.Inactive: + self.finalise() def isActive(self): """ @@ -314,4 +318,4 @@ class Plugin(QtCore.QObject): """ Called to define all translatable texts of the plugin """ - pass \ No newline at end of file + pass diff --git a/openlp/core/lib/settingstab.py b/openlp/core/lib/settingstab.py index 5081d9769..297185127 100644 --- a/openlp/core/lib/settingstab.py +++ b/openlp/core/lib/settingstab.py @@ -114,6 +114,12 @@ class SettingsTab(QtGui.QWidget): """ pass + def cancel(self): + """ + Reset any settings + """ + pass + def postSetUp(self, postUpdate=False): """ Changes which need to be made after setup of application diff --git a/openlp/core/lib/spelltextedit.py b/openlp/core/lib/spelltextedit.py index ebd4046c0..30811e08b 100644 --- a/openlp/core/lib/spelltextedit.py +++ b/openlp/core/lib/spelltextedit.py @@ -36,7 +36,7 @@ except ImportError: # http://john.nachtimwald.com/2009/08/22/qplaintextedit-with-in-line-spell-check from PyQt4 import QtCore, QtGui -from openlp.core.lib import html_expands, translate +from openlp.core.lib import translate, DisplayTags class SpellTextEdit(QtGui.QPlainTextEdit): """ @@ -88,7 +88,7 @@ class SpellTextEdit(QtGui.QPlainTextEdit): popupMenu.insertMenu(popupMenu.actions()[0], spell_menu) tagMenu = QtGui.QMenu(translate('OpenLP.SpellTextEdit', 'Formatting Tags')) - for html in html_expands: + for html in DisplayTags.get_html_tags(): action = SpellAction( html[u'desc'], tagMenu) action.correct.connect(self.htmlTag) tagMenu.addAction(action) @@ -110,7 +110,7 @@ class SpellTextEdit(QtGui.QPlainTextEdit): """ Replaces the selected text with word. """ - for html in html_expands: + for html in DisplayTags.get_html_tags(): if tag == html[u'desc']: cursor = self.textCursor() if self.textCursor().hasSelection(): @@ -158,4 +158,4 @@ class SpellAction(QtGui.QAction): def __init__(self, *args): QtGui.QAction.__init__(self, *args) self.triggered.connect(lambda x: self.correct.emit( - unicode(self.text()))) \ No newline at end of file + unicode(self.text()))) diff --git a/openlp/core/lib/theme.py b/openlp/core/lib/theme.py index 1e4a9854e..35b62ddda 100644 --- a/openlp/core/lib/theme.py +++ b/openlp/core/lib/theme.py @@ -598,4 +598,4 @@ class ThemeXML(object): self.font_footer_shadow_size) self.add_display(self.display_horizontal_align, self.display_vertical_align, - self.display_slide_transition) \ No newline at end of file + self.display_slide_transition) diff --git a/openlp/core/ui/__init__.py b/openlp/core/ui/__init__.py index 26c59ed0f..698216365 100644 --- a/openlp/core/ui/__init__.py +++ b/openlp/core/ui/__init__.py @@ -35,11 +35,11 @@ class HideMode(object): ``Blank`` This mode is used to hide all output, specifically by covering the display with a black screen. - + ``Theme`` This mode is used to hide all output, but covers the display with the current theme background, as opposed to black. - + ``Desktop`` This mode hides all output by minimising the display, leaving the user's desktop showing. @@ -59,6 +59,7 @@ from splashscreen import SplashScreen from generaltab import GeneralTab from themestab import ThemesTab from advancedtab import AdvancedTab +from displaytagtab import DisplayTagTab from aboutform import AboutForm from pluginform import PluginForm from settingsform import SettingsForm diff --git a/openlp/core/ui/displaytagtab.py b/openlp/core/ui/displaytagtab.py new file mode 100644 index 000000000..3d6cd813d --- /dev/null +++ b/openlp/core/ui/displaytagtab.py @@ -0,0 +1,350 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2011 Raoul Snyman # +# Portions copyright (c) 2008-2011 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Meinert Jordan, Andreas Preikschat, Christian # +# Richter, Philip Ridout, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard, 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:`DisplayTagTab` provides an Tag Edit facility. The Base set are +protected and included each time loaded. Custom tags can be defined and saved. +The Custom Tag arrays are saved in a pickle so QSettings works on them. Base +Tags cannot be changed. + +''' +import cPickle +from PyQt4 import QtCore, QtGui + +from openlp.core.lib import SettingsTab, translate, DisplayTags + +class DisplayTagTab(SettingsTab): + ''' + The :class:`DisplayTagTab` manages the settings tab . + ''' + def __init__(self): + ''' + Initialise the settings tab + ''' + SettingsTab.__init__(self, u'Display Tags') + + def resizeEvent(self, event=None): + pass + + def preLoad(self): + """ + Initialise values before the Load takes place + """ + # Create initial copy from master + DisplayTags.reset_html_tags() + user_expands = QtCore.QSettings().value(u'displayTags/html_tags', + QtCore.QVariant(u'')).toString() + # cPickle only accepts str not unicode strings + user_expands_string = str(unicode(user_expands).encode(u'utf8')) + if user_expands_string: + user_tags = cPickle.loads(user_expand_string) + # If we have some user ones added them as well + for t in user_tags: + DisplayTags.add_html_tag(t) + self.selected = -1 + + def setupUi(self): + ''' + Configure the UI elements for the tab. + ''' + self.setObjectName(u'DisplayTagTab') + self.tabTitleVisible = \ + translate(u'OpenLP.DisplayTagTab', 'Display Tags') + self.displayTagEdit = QtGui.QWidget(self) + self.editGroupBox = QtGui.QGroupBox(self.displayTagEdit) + self.editGroupBox.setGeometry(QtCore.QRect(10, 220, 650, 181)) + self.editGroupBox.setObjectName(u'editGroupBox') + self.updatePushButton = QtGui.QPushButton(self.editGroupBox) + self.updatePushButton.setGeometry(QtCore.QRect(550, 140, 71, 26)) + self.updatePushButton.setObjectName(u'updatePushButton') + self.layoutWidget = QtGui.QWidget(self.editGroupBox) + self.layoutWidget.setGeometry(QtCore.QRect(5, 20, 571, 114)) + self.layoutWidget.setObjectName(u'layoutWidget') + self.formLayout = QtGui.QFormLayout(self.layoutWidget) + self.formLayout.setObjectName(u'formLayout') + self.descriptionLabel = QtGui.QLabel(self.layoutWidget) + self.descriptionLabel.setAlignment(QtCore.Qt.AlignCenter) + self.descriptionLabel.setObjectName(u'descriptionLabel') + self.formLayout.setWidget(0, QtGui.QFormLayout.LabelRole, + self.descriptionLabel) + self.descriptionLineEdit = QtGui.QLineEdit(self.layoutWidget) + self.descriptionLineEdit.setObjectName(u'descriptionLineEdit') + self.formLayout.setWidget(0, QtGui.QFormLayout.FieldRole, + self.descriptionLineEdit) + self.tagLabel = QtGui.QLabel(self.layoutWidget) + self.tagLabel.setAlignment(QtCore.Qt.AlignCenter) + self.tagLabel.setObjectName(u'tagLabel') + self.formLayout.setWidget(1, QtGui.QFormLayout.LabelRole, self.tagLabel) + self.tagLineEdit = QtGui.QLineEdit(self.layoutWidget) + self.tagLineEdit.setMaximumSize(QtCore.QSize(50, 16777215)) + self.tagLineEdit.setMaxLength(5) + self.tagLineEdit.setObjectName(u'tagLineEdit') + self.formLayout.setWidget(1, QtGui.QFormLayout.FieldRole, + self.tagLineEdit) + self.startTagLabel = QtGui.QLabel(self.layoutWidget) + self.startTagLabel.setAlignment(QtCore.Qt.AlignCenter) + self.startTagLabel.setObjectName(u'startTagLabel') + self.formLayout.setWidget(2, QtGui.QFormLayout.LabelRole, + self.startTagLabel) + self.startTagLineEdit = QtGui.QLineEdit(self.layoutWidget) + self.startTagLineEdit.setObjectName(u'startTagLineEdit') + self.formLayout.setWidget(2, QtGui.QFormLayout.FieldRole, + self.startTagLineEdit) + self.endTagLabel = QtGui.QLabel(self.layoutWidget) + self.endTagLabel.setAlignment(QtCore.Qt.AlignCenter) + self.endTagLabel.setObjectName(u'endTagLabel') + self.formLayout.setWidget(3, QtGui.QFormLayout.LabelRole, + self.endTagLabel) + self.endTagLineEdit = QtGui.QLineEdit(self.layoutWidget) + self.endTagLineEdit.setObjectName(u'endTagLineEdit') + self.formLayout.setWidget(3, QtGui.QFormLayout.FieldRole, + self.endTagLineEdit) + self.defaultPushButton = QtGui.QPushButton(self.displayTagEdit) + self.defaultPushButton.setGeometry(QtCore.QRect(430, 188, 71, 26)) + self.defaultPushButton.setObjectName(u'updatePushButton') + self.deletePushButton = QtGui.QPushButton(self.displayTagEdit) + self.deletePushButton.setGeometry(QtCore.QRect(510, 188, 71, 26)) + self.deletePushButton.setObjectName(u'deletePushButton') + self.newPushButton = QtGui.QPushButton(self.displayTagEdit) + self.newPushButton.setGeometry(QtCore.QRect(600, 188, 71, 26)) + self.newPushButton.setObjectName(u'newPushButton') + self.tagTableWidget = QtGui.QTableWidget(self.displayTagEdit) + self.tagTableWidget.setGeometry(QtCore.QRect(10, 10, 650, 171)) + self.tagTableWidget.setHorizontalScrollBarPolicy( + QtCore.Qt.ScrollBarAlwaysOff) + self.tagTableWidget.setEditTriggers( + QtGui.QAbstractItemView.NoEditTriggers) + self.tagTableWidget.setAlternatingRowColors(True) + self.tagTableWidget.setSelectionMode( + QtGui.QAbstractItemView.SingleSelection) + self.tagTableWidget.setSelectionBehavior( + QtGui.QAbstractItemView.SelectRows) + self.tagTableWidget.setCornerButtonEnabled(False) + self.tagTableWidget.setObjectName(u'tagTableWidget') + self.tagTableWidget.setColumnCount(4) + self.tagTableWidget.setRowCount(0) + item = QtGui.QTableWidgetItem() + self.tagTableWidget.setHorizontalHeaderItem(0, item) + item = QtGui.QTableWidgetItem() + self.tagTableWidget.setHorizontalHeaderItem(1, item) + item = QtGui.QTableWidgetItem() + self.tagTableWidget.setHorizontalHeaderItem(2, item) + item = QtGui.QTableWidgetItem() + self.tagTableWidget.setHorizontalHeaderItem(3, item) + self.editGroupBox.setTitle( + translate('OpenLP.DisplayTagTab', 'Edit Selection')) + self.updatePushButton.setText( + translate('OpenLP.DisplayTagTab', 'Update')) + self.descriptionLabel.setText( + translate('OpenLP.DisplayTagTab', 'Description')) + self.tagLabel.setText(translate('OpenLP.DisplayTagTab', 'Tag')) + self.startTagLabel.setText( + translate('OpenLP.DisplayTagTab', 'Start tag')) + self.endTagLabel.setText(translate('OpenLP.DisplayTagTab', 'End tag')) + self.deletePushButton.setText( + translate('OpenLP.DisplayTagTab', 'Delete')) + self.defaultPushButton.setText( + translate('OpenLP.DisplayTagTab', 'Default')) + self.newPushButton.setText(translate('OpenLP.DisplayTagTab', 'New')) + self.tagTableWidget.horizontalHeaderItem(0)\ + .setText(translate('OpenLP.DisplayTagTab', 'Description')) + self.tagTableWidget.horizontalHeaderItem(1)\ + .setText(translate('OpenLP.DisplayTagTab', 'Tag id')) + self.tagTableWidget.horizontalHeaderItem(2)\ + .setText(translate('OpenLP.DisplayTagTab', 'Start Html')) + self.tagTableWidget.horizontalHeaderItem(3)\ + .setText(translate('OpenLP.DisplayTagTab', 'End Html')) + QtCore.QMetaObject.connectSlotsByName(self.displayTagEdit) + self.tagTableWidget.setColumnWidth(0, 120) + self.tagTableWidget.setColumnWidth(1, 40) + self.tagTableWidget.setColumnWidth(2, 240) + self.tagTableWidget.setColumnWidth(3, 200) + QtCore.QObject.connect(self.tagTableWidget, + QtCore.SIGNAL(u'clicked(QModelIndex)'), self.onRowSelected) + QtCore.QObject.connect(self.defaultPushButton, + QtCore.SIGNAL(u'pressed()'), self.onDefaultPushed) + QtCore.QObject.connect(self.newPushButton, + QtCore.SIGNAL(u'pressed()'), self.onNewPushed) + QtCore.QObject.connect(self.updatePushButton, + QtCore.SIGNAL(u'pressed()'), self.onUpdatePushed) + QtCore.QObject.connect(self.deletePushButton, + QtCore.SIGNAL(u'pressed()'), self.onDeletePushed) + + def load(self): + """ + Load Display and set field state. + """ + self.newPushButton.setEnabled(True) + self.updatePushButton.setEnabled(False) + self.deletePushButton.setEnabled(False) + for linenumber, html in enumerate(DisplayTags.get_html_tags()): + self.tagTableWidget.setRowCount( + self.tagTableWidget.rowCount() + 1) + self.tagTableWidget.setItem(linenumber, 0, + QtGui.QTableWidgetItem(html[u'desc'])) + self.tagTableWidget.setItem(linenumber, 1, + QtGui.QTableWidgetItem(self._strip(html[u'start tag']))) + self.tagTableWidget.setItem(linenumber, 2, + QtGui.QTableWidgetItem(html[u'start html'])) + self.tagTableWidget.setItem(linenumber, 3, + QtGui.QTableWidgetItem(html[u'end html'])) + self.tagTableWidget.resizeRowsToContents() + self.descriptionLineEdit.setText(u'') + self.tagLineEdit.setText(u'') + self.startTagLineEdit.setText(u'') + self.endTagLineEdit.setText(u'') + self.descriptionLineEdit.setEnabled(False) + self.tagLineEdit.setEnabled(False) + self.startTagLineEdit.setEnabled(False) + self.endTagLineEdit.setEnabled(False) + + def save(self): + """ + Save Custom tags in a pickle . + """ + temp = [] + for tag in DisplayTags.get_html_tags(): + if not tag[u'protected']: + temp.append(tag) + if temp: + ctemp = cPickle.dumps(temp) + QtCore.QSettings().setValue(u'displayTags/html_tags', + QtCore.QVariant(ctemp)) + else: + QtCore.QSettings().setValue(u'displayTags/html_tags', + QtCore.QVariant(u'')) + + def cancel(self): + """ + Reset Custom tags from Settings. + """ + self.preLoad() + self._resetTable() + + def onRowSelected(self): + """ + Table Row selected so display items and set field state. + """ + row = self.tagTableWidget.currentRow() + html = DisplayTags.get_html_tags()[row] + self.selected = row + self.descriptionLineEdit.setText(html[u'desc']) + self.tagLineEdit.setText(self._strip(html[u'start tag'])) + self.startTagLineEdit.setText(html[u'start html']) + self.endTagLineEdit.setText(html[u'end html']) + if html[u'protected']: + self.descriptionLineEdit.setEnabled(False) + self.tagLineEdit.setEnabled(False) + self.startTagLineEdit.setEnabled(False) + self.endTagLineEdit.setEnabled(False) + self.updatePushButton.setEnabled(False) + self.deletePushButton.setEnabled(False) + else: + self.descriptionLineEdit.setEnabled(True) + self.tagLineEdit.setEnabled(True) + self.startTagLineEdit.setEnabled(True) + self.endTagLineEdit.setEnabled(True) + self.updatePushButton.setEnabled(True) + self.deletePushButton.setEnabled(True) + + def onNewPushed(self): + """ + Add a new tag to list only if it is not a duplicate. + """ + for html in DisplayTags.get_html_tags(): + if self._strip(html[u'start tag']) == u'n': + QtGui.QMessageBox.critical(self, + translate('OpenLP.DisplayTagTab', 'Update Error'), + translate('OpenLP.DisplayTagTab', + 'Tag "n" already defined.'), + QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok), + QtGui.QMessageBox.Ok) + return + # Add new tag to list + tag = {u'desc': u'New Item', u'start tag': u'{n}', + u'start html': u'', u'end tag': u'{/n}', + u'end html': u'', u'protected': False} + DisplayTags.add_html_tag(tag) + self._resetTable() + # Highlight new row + self.tagTableWidget.selectRow(self.tagTableWidget.rowCount() - 1) + + def onDefaultPushed(self): + """ + Remove all Custom Tags and reset to base set only. + """ + DisplayTags.reset_html_tags() + self._resetTable() + + def onDeletePushed(self): + """ + Delete selected custom tag. + """ + if self.selected != -1: + DisplayTags.remove_html_tag(self.selected) + self.selected = -1 + self._resetTable() + + def onUpdatePushed(self): + """ + Update Custom Tag details if not duplicate. + """ + html_expands = DisplayTags.get_html_tags() + if self.selected != -1: + html = html_expands[self.selected] + tag = unicode(self.tagLineEdit.text()) + for linenumber, html1 in enumerate(html_expands): + if self._strip(html1[u'start tag']) == tag and \ + linenumber != self.selected: + QtGui.QMessageBox.critical(self, + translate('OpenLP.DisplayTagTab', 'Update Error'), + unicode(translate('OpenLP.DisplayTagTab', + 'Tag %s already defined.')) % tag, + QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok), + QtGui.QMessageBox.Ok) + return + html[u'desc'] = unicode(self.descriptionLineEdit.text()) + html[u'start html'] = unicode(self.startTagLineEdit.text()) + html[u'end html'] = unicode(self.endTagLineEdit.text()) + html[u'start tag'] = u'{%s}' % tag + html[u'end tag'] = u'{/%s}' % tag + self.selected = -1 + self._resetTable() + + def _resetTable(self): + """ + Reset List for loading. + """ + self.tagTableWidget.clearContents() + self.tagTableWidget.setRowCount(0) + self.load() + + def _strip(self, tag): + """ + Remove tag wrappers for editing. + """ + tag = tag.replace(u'{', u'') + tag = tag.replace(u'}', u'') + return tag diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index a08d71e27..adc2cb5fa 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -32,7 +32,8 @@ from PyQt4 import QtCore, QtGui, QtWebKit from PyQt4.phonon import Phonon from openlp.core.lib import Receiver, build_html, ServiceItem, image_to_byte, \ - translate + build_icon, translate + from openlp.core.ui import HideMode log = logging.getLogger(__name__) @@ -102,6 +103,8 @@ class MainDisplay(DisplayWidget): self.isLive = live self.alertTab = None self.hideMode = None + mainIcon = build_icon(u':/icon/openlp-logo-16x16.png') + self.setWindowIcon(mainIcon) self.retranslateUi() self.setStyleSheet(u'border: 0px; margin: 0px; padding: 0px;') self.setWindowFlags(QtCore.Qt.FramelessWindowHint | diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 70b87966c..f2545766e 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -142,7 +142,8 @@ class Ui_MainWindow(object): build_icon(u':/system/system_servicemanager.png')) self.ServiceManagerDock.setMinimumWidth( self.settingsmanager.mainwindow_right) - self.ServiceManagerContents = ServiceManager(MainWindow) + self.ServiceManagerContents = ServiceManager(MainWindow, + self.ServiceManagerDock) self.ServiceManagerDock.setWidget(self.ServiceManagerContents) MainWindow.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.ServiceManagerDock) @@ -152,7 +153,8 @@ class Ui_MainWindow(object): build_icon(u':/system/system_thememanager.png')) self.ThemeManagerDock.setMinimumWidth( self.settingsmanager.mainwindow_right) - self.ThemeManagerContents = ThemeManager(MainWindow) + self.ThemeManagerContents = ThemeManager(MainWindow, + self.ThemeManagerDock) self.ThemeManagerContents.setObjectName(u'ThemeManagerContents') self.ThemeManagerDock.setWidget(self.ThemeManagerContents) MainWindow.addDockWidget(QtCore.Qt.RightDockWidgetArea, diff --git a/openlp/core/ui/pluginform.py b/openlp/core/ui/pluginform.py index 26e6cf06c..8e82f0003 100644 --- a/openlp/core/ui/pluginform.py +++ b/openlp/core/ui/pluginform.py @@ -61,6 +61,7 @@ class PluginForm(QtGui.QDialog, Ui_PluginViewDialog): self.programaticChange = True self._clearDetails() self.programaticChange = True + pluginListWidth = 0 for plugin in self.parent.pluginManager.plugins: item = QtGui.QListWidgetItem(self.pluginListWidget) # We do this just to make 100% sure the status is an integer as @@ -83,8 +84,11 @@ class PluginForm(QtGui.QDialog, Ui_PluginViewDialog): if plugin.icon: item.setIcon(plugin.icon) self.pluginListWidget.addItem(item) - self.pluginListWidget.setFixedWidth( - self.pluginListWidget.sizeHint().width()) + pluginListWidth = max(pluginListWidth, self.fontMetrics().width( + unicode(translate('OpenLP.PluginForm', '%s (Inactive)')) % + name_string[u'singular'])) + self.pluginListWidget.setFixedWidth(pluginListWidth + + self.pluginListWidget.iconSize().width() + 48) def _clearDetails(self): self.statusComboBox.setCurrentIndex(-1) @@ -126,10 +130,8 @@ class PluginForm(QtGui.QDialog, Ui_PluginViewDialog): return if status == 0: self.activePlugin.toggleStatus(PluginStatus.Active) - self.activePlugin.initialise() else: self.activePlugin.toggleStatus(PluginStatus.Inactive) - self.activePlugin.finalise() status_text = unicode( translate('OpenLP.PluginForm', '%s (Inactive)')) if self.activePlugin.status == PluginStatus.Active: diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index a9bf232d4..acd775ef7 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -28,7 +28,6 @@ import os import logging import cPickle import zipfile -from pprint import pformat log = logging.getLogger(__name__) @@ -44,33 +43,33 @@ class ServiceManagerList(QtGui.QTreeWidget): """ Set up key bindings and mouse behaviour for the service list """ - def __init__(self, parent=None, name=None): + def __init__(self, mainwindow, parent=None, name=None): QtGui.QTreeWidget.__init__(self, parent) - self.parent = parent + self.mainwindow = mainwindow def keyPressEvent(self, event): if isinstance(event, QtGui.QKeyEvent): #here accept the event and do something if event.key() == QtCore.Qt.Key_Enter: - self.parent.makeLive() + self.mainwindow.makeLive() event.accept() elif event.key() == QtCore.Qt.Key_Home: - self.parent.onServiceTop() + self.mainwindow.onServiceTop() event.accept() elif event.key() == QtCore.Qt.Key_End: - self.parent.onServiceEnd() + self.mainwindow.onServiceEnd() event.accept() elif event.key() == QtCore.Qt.Key_PageUp: - self.parent.onServiceUp() + self.mainwindow.onServiceUp() event.accept() elif event.key() == QtCore.Qt.Key_PageDown: - self.parent.onServiceDown() + self.mainwindow.onServiceDown() event.accept() elif event.key() == QtCore.Qt.Key_Up: - self.parent.onMoveSelectionUp() + self.mainwindow.onMoveSelectionUp() event.accept() elif event.key() == QtCore.Qt.Key_Down: - self.parent.onMoveSelectionDown() + self.mainwindow.onMoveSelectionDown() event.accept() event.ignore() else: @@ -99,12 +98,12 @@ class ServiceManager(QtGui.QWidget): the resources used into one OSZ file for use on any OpenLP v2 installation. Also handles the UI tasks of moving things up and down etc. """ - def __init__(self, parent): + def __init__(self, mainwindow, parent=None): """ Sets up the service manager, toolbars, list view, et al. """ QtGui.QWidget.__init__(self, parent) - self.parent = parent + self.mainwindow = mainwindow self.serviceItems = [] self.serviceName = u'' self.suffixes = [] @@ -113,8 +112,8 @@ class ServiceManager(QtGui.QWidget): # is a new service and has not been saved self._modified = False self._fileName = u'' - self.serviceNoteForm = ServiceNoteForm(self.parent) - self.serviceItemEditForm = ServiceItemEditForm(self.parent) + self.serviceNoteForm = ServiceNoteForm(self.mainwindow) + self.serviceItemEditForm = ServiceItemEditForm(self.mainwindow) # start with the layout self.layout = QtGui.QVBoxLayout(self) self.layout.setSpacing(0) @@ -145,8 +144,10 @@ class ServiceManager(QtGui.QWidget): self.themeComboBox = QtGui.QComboBox(self.toolbar) self.themeComboBox.setToolTip(translate('OpenLP.ServiceManager', 'Select a theme for the service')) - self.themeComboBox.setSizePolicy(QtGui.QSizePolicy.Expanding, - QtGui.QSizePolicy.Fixed) + self.themeComboBox.setSizeAdjustPolicy( + QtGui.QComboBox.AdjustToMinimumContentsLength) + self.themeComboBox.setSizePolicy( + QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed) self.themeComboBox.setObjectName(u'themeComboBox') self.toolbar.addToolbarWidget(u'ThemeWidget', self.themeComboBox) self.toolbar.setObjectName(u'toolbar') @@ -248,10 +249,10 @@ class ServiceManager(QtGui.QWidget): QtCore.SIGNAL(u'service_item_update'), self.serviceItemUpdate) # Last little bits of setting up self.service_theme = unicode(QtCore.QSettings().value( - self.parent.serviceSettingsSection + u'/service theme', + self.mainwindow.serviceSettingsSection + u'/service theme', QtCore.QVariant(u'')).toString()) self.servicePath = AppLocation.get_section_data_path(u'servicemanager') - #build the drag and drop context menu + # build the drag and drop context menu self.dndMenu = QtGui.QMenu() self.newAction = self.dndMenu.addAction( translate('OpenLP.ServiceManager', '&Add New Item')) @@ -295,7 +296,7 @@ class ServiceManager(QtGui.QWidget): """ self._modified = modified serviceFile = self.shortFileName() or u'Untitled Service' - self.parent.setServiceModified(modified, serviceFile) + self.mainwindow.setServiceModified(modified, serviceFile) def isModified(self): """ @@ -308,7 +309,8 @@ class ServiceManager(QtGui.QWidget): Setter for service file. """ self._fileName = unicode(fileName) - self.parent.setServiceModified(self.isModified(), self.shortFileName()) + self.mainwindow.setServiceModified(self.isModified(), + self.shortFileName()) QtCore.QSettings(). \ setValue(u'service/last file',QtCore.QVariant(fileName)) @@ -342,7 +344,7 @@ class ServiceManager(QtGui.QWidget): Create a new service. """ if self.isModified(): - result = QtGui.QMessageBox.question(self.parent, + result = QtGui.QMessageBox.question(self.mainwindow, translate('OpenLP.ServiceManager', 'Save Changes'), translate('OpenLP.ServiceManager', 'The current service has ' 'been modified, would you like to save it?'), @@ -357,7 +359,7 @@ class ServiceManager(QtGui.QWidget): def onLoadServiceClicked(self): if self.isModified(): - result = QtGui.QMessageBox.question(self.parent, + result = QtGui.QMessageBox.question(self.mainwindow, translate('OpenLP.ServiceManager', 'Save Changes'), translate('OpenLP.ServiceManager', 'The current service has ' 'been modified, would you like to save it?'), @@ -367,14 +369,14 @@ class ServiceManager(QtGui.QWidget): return False elif result == QtGui.QMessageBox.Save: self.saveFile() - fileName = unicode(QtGui.QFileDialog.getOpenFileName(self.parent, + fileName = unicode(QtGui.QFileDialog.getOpenFileName(self.mainwindow, translate('OpenLP.ServiceManager', 'Open File'), - SettingsManager.get_last_dir(self.parent.serviceSettingsSection), + SettingsManager.get_last_dir(self.mainwindow.serviceSettingsSection), translate('OpenLP.ServiceManager', 'OpenLP Service Files (*.osz)'))) if not fileName: return False - SettingsManager.set_last_dir(self.parent.serviceSettingsSection, + SettingsManager.set_last_dir(self.mainwindow.serviceSettingsSection, split_filename(fileName)[0]) self.loadFile(fileName) @@ -408,7 +410,7 @@ class ServiceManager(QtGui.QWidget): else: fileName = self.fileName() log.debug(u'ServiceManager.saveFile - %s' % fileName) - SettingsManager.set_last_dir(self.parent.serviceSettingsSection, + SettingsManager.set_last_dir(self.mainwindow.serviceSettingsSection, split_filename(fileName)[0]) service = [] serviceFileName = fileName.replace(u'.osz', u'.osd') @@ -448,7 +450,7 @@ class ServiceManager(QtGui.QWidget): except (IOError, OSError): # if not present do not worry pass - self.parent.addRecentFile(fileName) + self.mainwindow.addRecentFile(fileName) self.setModified(False) return True @@ -457,9 +459,9 @@ class ServiceManager(QtGui.QWidget): Get a file name and then call :function:`ServiceManager.saveFile` to save the file. """ - fileName = unicode(QtGui.QFileDialog.getSaveFileName(self.parent, + fileName = unicode(QtGui.QFileDialog.getSaveFileName(self.mainwindow, translate('OpenLP.ServiceManager', 'Save Service'), - SettingsManager.get_last_dir(self.parent.serviceSettingsSection), + SettingsManager.get_last_dir(self.mainwindow.serviceSettingsSection), translate('OpenLP.ServiceManager', 'OpenLP Service Files (*.osz)'))) if not fileName: @@ -509,7 +511,7 @@ class ServiceManager(QtGui.QWidget): self.newFile() for item in items: serviceItem = ServiceItem() - serviceItem.render_manager = self.parent.renderManager + serviceItem.render_manager = self.mainwindow.renderManager serviceItem.set_from_service(item, self.servicePath) self.validateItem(serviceItem) self.addServiceItem(serviceItem) @@ -536,7 +538,7 @@ class ServiceManager(QtGui.QWidget): if zip: zip.close() self.setFileName(fileName) - self.parent.addRecentFile(fileName) + self.mainwindow.addRecentFile(fileName) self.setModified(False) QtCore.QSettings(). \ setValue(u'service/last file',QtCore.QVariant(fileName)) @@ -887,9 +889,9 @@ class ServiceManager(QtGui.QWidget): """ log.debug(u'onThemeComboBoxSelected') self.service_theme = unicode(self.themeComboBox.currentText()) - self.parent.renderManager.set_service_theme(self.service_theme) + self.mainwindow.renderManager.set_service_theme(self.service_theme) QtCore.QSettings().setValue( - self.parent.serviceSettingsSection + u'/service theme', + self.mainwindow.serviceSettingsSection + u'/service theme', QtCore.QVariant(self.service_theme)) self.regenerateServiceItems() @@ -899,7 +901,7 @@ class ServiceManager(QtGui.QWidget): sure the theme combo box is in the correct state. """ log.debug(u'themeChange') - if self.parent.renderManager.theme_level == ThemeLevel.Global: + if self.mainwindow.renderManager.theme_level == ThemeLevel.Global: self.toolbar.actions[u'ThemeLabel'].setVisible(False) self.toolbar.actions[u'ThemeWidget'].setVisible(False) else: @@ -914,7 +916,7 @@ class ServiceManager(QtGui.QWidget): Receiver.send_message(u'cursor_busy') log.debug(u'regenerateServiceItems') # force reset of renderer as theme data has changed - self.parent.renderManager.themedata = None + self.mainwindow.renderManager.themedata = None if self.serviceItems: tempServiceItems = self.serviceItems self.serviceManagerList.clear() @@ -949,7 +951,7 @@ class ServiceManager(QtGui.QWidget): newItem.merge(item[u'service_item']) item[u'service_item'] = newItem self.repaintServiceList(itemcount + 1, 0) - self.parent.liveController.replaceServiceManagerItem(newItem) + self.mainwindow.liveController.replaceServiceManagerItem(newItem) self.setModified(True) def addServiceItem(self, item, rebuild=False, expand=None, replace=False): @@ -971,7 +973,7 @@ class ServiceManager(QtGui.QWidget): item.merge(self.serviceItems[sitem][u'service_item']) self.serviceItems[sitem][u'service_item'] = item self.repaintServiceList(sitem + 1, 0) - self.parent.liveController.replaceServiceManagerItem(item) + self.mainwindow.liveController.replaceServiceManagerItem(item) else: # nothing selected for dnd if self.dropPosition == 0: @@ -986,13 +988,13 @@ class ServiceManager(QtGui.QWidget): u'expanded':expand}) self.repaintServiceList(len(self.serviceItems) + 1, 0) else: - self.serviceItems.insert(self.dropPosition, {u'service_item': item, - u'order': self.dropPosition, + self.serviceItems.insert(self.dropPosition, + {u'service_item': item, u'order': self.dropPosition, u'expanded':expand}) self.repaintServiceList(self.dropPosition, 0) # if rebuilding list make sure live is fixed. if rebuild: - self.parent.liveController.replaceServiceManagerItem(item) + self.mainwindow.liveController.replaceServiceManagerItem(item) self.dropPosition = 0 self.setModified(True) @@ -1002,7 +1004,7 @@ class ServiceManager(QtGui.QWidget): """ item, count = self.findServiceItem() if self.serviceItems[item][u'service_item'].is_valid: - self.parent.previewController.addServiceManagerItem( + self.mainwindow.previewController.addServiceManagerItem( self.serviceItems[item][u'service_item'], count) else: QtGui.QMessageBox.critical(self, @@ -1026,18 +1028,18 @@ class ServiceManager(QtGui.QWidget): """ item, count = self.findServiceItem() if self.serviceItems[item][u'service_item'].is_valid: - self.parent.liveController.addServiceManagerItem( + self.mainwindow.liveController.addServiceManagerItem( self.serviceItems[item][u'service_item'], count) if QtCore.QSettings().value( - self.parent.generalSettingsSection + u'/auto preview', + self.mainwindow.generalSettingsSection + u'/auto preview', QtCore.QVariant(False)).toBool(): item += 1 if self.serviceItems and item < len(self.serviceItems) and \ self.serviceItems[item][u'service_item'].is_capable( ItemCapabilities.AllowsPreview): - self.parent.previewController.addServiceManagerItem( + self.mainwindow.previewController.addServiceManagerItem( self.serviceItems[item][u'service_item'], 0) - self.parent.liveController.PreviewListWidget.setFocus() + self.mainwindow.liveController.PreviewListWidget.setFocus() else: QtGui.QMessageBox.critical(self, translate('OpenLP.ServiceManager', 'Missing Display Handler'), @@ -1157,7 +1159,7 @@ class ServiceManager(QtGui.QWidget): index = 0 self.service_theme = u'' self.themeComboBox.setCurrentIndex(index) - self.parent.renderManager.set_service_theme(self.service_theme) + self.mainwindow.renderManager.set_service_theme(self.service_theme) self.regenerateServiceItems() def onThemeChangeAction(self): diff --git a/openlp/core/ui/settingsdialog.py b/openlp/core/ui/settingsdialog.py index 537e99cc2..61c73961c 100644 --- a/openlp/core/ui/settingsdialog.py +++ b/openlp/core/ui/settingsdialog.py @@ -31,7 +31,7 @@ from openlp.core.lib import translate, build_icon class Ui_SettingsDialog(object): def setupUi(self, settingsDialog): settingsDialog.setObjectName(u'settingsDialog') - settingsDialog.resize(700, 300) + settingsDialog.resize(700, 500) settingsDialog.setWindowIcon( build_icon(u':/system/system_settings.png')) self.settingsLayout = QtGui.QVBoxLayout(settingsDialog) diff --git a/openlp/core/ui/settingsform.py b/openlp/core/ui/settingsform.py index 068057902..afea928b6 100644 --- a/openlp/core/ui/settingsform.py +++ b/openlp/core/ui/settingsform.py @@ -31,7 +31,7 @@ import logging from PyQt4 import QtGui from openlp.core.lib import Receiver -from openlp.core.ui import AdvancedTab, GeneralTab, ThemesTab +from openlp.core.ui import AdvancedTab, GeneralTab, ThemesTab, DisplayTagTab from settingsdialog import Ui_SettingsDialog log = logging.getLogger(__name__) @@ -55,6 +55,9 @@ class SettingsForm(QtGui.QDialog, Ui_SettingsDialog): # Advanced tab self.advancedTab = AdvancedTab() self.addTab(u'Advanced', self.advancedTab) + # Edit Display Tags tab + self.displayTagTab = DisplayTagTab() + self.addTab(u'Display Tags', self.displayTagTab) def addTab(self, name, tab): """ @@ -68,9 +71,9 @@ class SettingsForm(QtGui.QDialog, Ui_SettingsDialog): Add a tab to the form at a specific location """ log.debug(u'Inserting %s tab' % tab.tabTitle) - # 14 : There are 3 tables currently and locations starts at -10 + # 15 : There are 4 tables currently and locations starts at -10 self.settingsTabWidget.insertTab( - location + 14, tab, tab.tabTitleVisible) + location + 15, tab, tab.tabTitleVisible) def removeTab(self, tab): """ @@ -93,6 +96,14 @@ class SettingsForm(QtGui.QDialog, Ui_SettingsDialog): Receiver.send_message(u'config_updated') return QtGui.QDialog.accept(self) + def reject(self): + """ + Process the form saving the settings + """ + for tabIndex in range(0, self.settingsTabWidget.count()): + self.settingsTabWidget.widget(tabIndex).cancel() + return QtGui.QDialog.reject(self) + def postSetUp(self): """ Run any post-setup code for the tabs on the form diff --git a/openlp/core/ui/themeform.py b/openlp/core/ui/themeform.py index eaf54bdb9..784c322d9 100644 --- a/openlp/core/ui/themeform.py +++ b/openlp/core/ui/themeform.py @@ -212,8 +212,8 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): """ Updates the lines on a page on the wizard """ - self.mainLineCountLabel.setText(unicode(translate('OpenLP.ThemeForm', \ - '(%d lines per slide)' % int(lines)))) + self.mainLineCountLabel.setText(unicode(translate('OpenLP.ThemeForm', + '(%d lines per slide)')) % int(lines)) def resizeEvent(self, event=None): """ @@ -291,9 +291,10 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): self.updateThemeAllowed = True self.themeNameLabel.setVisible(not edit) self.themeNameEdit.setVisible(not edit) + self.edit_mode = edit if edit: self.setWindowTitle(unicode(translate('OpenLP.ThemeWizard', - 'Edit Theme %s')) % self.theme.theme_name) + 'Edit Theme - %s')) % self.theme.theme_name) self.next() else: self.setWindowTitle(translate('OpenLP.ThemeWizard', 'New Theme')) @@ -581,7 +582,6 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): (QtGui.QMessageBox.Ok), QtGui.QMessageBox.Ok) return - self.accepted = True saveFrom = None saveTo = None if self.theme.background_type == \ @@ -590,8 +590,12 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): os.path.split(unicode(self.theme.background_filename))[1] saveTo = os.path.join(self.path, self.theme.theme_name, filename) saveFrom = self.theme.background_filename - if self.thememanager.saveTheme(self.theme, saveFrom, saveTo): - return QtGui.QDialog.accept(self) + if not self.edit_mode and \ + not self.thememanager.checkIfThemeExists(self.theme.theme_name): + return + self.accepted = True + self.thememanager.saveTheme(self.theme, saveFrom, saveTo) + return QtGui.QDialog.accept(self) def _colorButton(self, field): """ diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index 0703402fd..b39b22ab1 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -36,7 +36,7 @@ from openlp.core.ui import FileRenameForm, ThemeForm from openlp.core.theme import Theme from openlp.core.lib import OpenLPToolbar, ThemeXML, get_text_file_string, \ build_icon, Receiver, SettingsManager, translate, check_item_selected, \ - BackgroundType, BackgroundGradientType + BackgroundType, BackgroundGradientType, check_directory_exists from openlp.core.utils import AppLocation, get_filesystem_encoding log = logging.getLogger(__name__) @@ -45,13 +45,13 @@ class ThemeManager(QtGui.QWidget): """ Manages the orders of Theme. """ - def __init__(self, parent): + def __init__(self, mainwindow, parent=None): QtGui.QWidget.__init__(self, parent) - self.parent = parent + self.mainwindow = mainwindow self.settingsSection = u'themes' self.themeForm = ThemeForm(self) self.fileRenameForm = FileRenameForm(self) - self.serviceComboBox = self.parent.ServiceManagerContents.themeComboBox + self.serviceComboBox = self.mainwindow.ServiceManagerContents.themeComboBox # start with the layout self.layout = QtGui.QVBoxLayout(self) self.layout.setSpacing(0) @@ -119,27 +119,38 @@ class ThemeManager(QtGui.QWidget): self.exportAction = self.menu.addAction( translate('OpenLP.ThemeManager', '&Export Theme')) self.exportAction.setIcon(build_icon(u':/general/general_export.png')) - #Signals + # Signals QtCore.QObject.connect(self.themeListWidget, QtCore.SIGNAL(u'doubleClicked(QModelIndex)'), self.changeGlobalFromScreen) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'theme_update_global'), self.changeGlobalFromTab) - #Variables + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'config_updated'), self.configUpdated) + # Variables self.themelist = [] self.path = AppLocation.get_section_data_path(self.settingsSection) - self.checkThemesExists(self.path) + check_directory_exists(self.path) self.thumbPath = os.path.join(self.path, u'thumbnails') - self.checkThemesExists(self.thumbPath) + check_directory_exists(self.thumbPath) self.themeForm.path = self.path self.oldBackgroundImage = None - self.editingDefault = False # Last little bits of setting up + self.configUpdated() + + def configUpdated(self, firstTime=False): + """ + Triggered when Config dialog is updated. + """ self.global_theme = unicode(QtCore.QSettings().value( self.settingsSection + u'/global theme', QtCore.QVariant(u'')).toString()) def contextMenu(self, point): + """ + Build the Right Click Context menu and set state depending on + the type of theme. + """ item = self.themeListWidget.itemAt(point) if item is None: return @@ -220,7 +231,6 @@ class ThemeManager(QtGui.QWidget): editing form for the user to make their customisations. """ theme = ThemeXML() - self.saveThemeName = u'' self.themeForm.theme = theme self.themeForm.exec_() @@ -236,12 +246,15 @@ class ThemeManager(QtGui.QWidget): item = self.themeListWidget.currentItem() oldThemeName = unicode(item.data(QtCore.Qt.UserRole).toString()) self.fileRenameForm.fileNameEdit.setText(oldThemeName) - self.saveThemeName = oldThemeName if self.fileRenameForm.exec_(): - newThemeName = unicode(self.fileRenameForm.fileNameEdit.text()) - oldThemeData = self.getThemeData(oldThemeName) - self.deleteTheme(oldThemeName) - self.cloneThemeData(oldThemeData, newThemeName) + newThemeName = unicode(self.fileRenameForm.fileNameEdit.text()) + if self.checkIfThemeExists(newThemeName): + oldThemeData = self.getThemeData(oldThemeName) + self.deleteTheme(oldThemeName) + self.cloneThemeData(oldThemeData, newThemeName) + for plugin in self.mainwindow.pluginManager.plugins: + if plugin.usesTheme(oldThemeName): + plugin.renameTheme(oldThemeName, newThemeName) def onCopyTheme(self): """ @@ -250,12 +263,12 @@ class ThemeManager(QtGui.QWidget): item = self.themeListWidget.currentItem() oldThemeName = unicode(item.data(QtCore.Qt.UserRole).toString()) self.fileRenameForm.fileNameEdit.setText(oldThemeName) - self.saveThemeName = u'' if self.fileRenameForm.exec_(True): - newThemeName = unicode(self.fileRenameForm.fileNameEdit.text()) - themeData = self.getThemeData(oldThemeName) - self.cloneThemeData(themeData, newThemeName) - self.loadThemes() + newThemeName = unicode(self.fileRenameForm.fileNameEdit.text()) + if self.checkIfThemeExists(newThemeName): + themeData = self.getThemeData(oldThemeName) + self.cloneThemeData(themeData, newThemeName) + self.loadThemes() def cloneThemeData(self, themeData, newThemeName): """ @@ -281,14 +294,10 @@ class ThemeManager(QtGui.QWidget): 'You must select a theme to edit.')): item = self.themeListWidget.currentItem() themeName = unicode(item.text()) - if themeName != unicode(item.data(QtCore.Qt.UserRole).toString()): - self.editingDefault = True theme = self.getThemeData( unicode(item.data(QtCore.Qt.UserRole).toString())) if theme.background_type == u'image': self.oldBackgroundImage = theme.background_filename - self.saveThemeName = unicode( - item.data(QtCore.Qt.UserRole).toString()) self.themeForm.theme = theme self.themeForm.exec_(True) @@ -449,20 +458,9 @@ class ThemeManager(QtGui.QWidget): unicode(themeName) + u'.xml') xml = get_text_file_string(xmlFile) if not xml: - return self.baseTheme() + return self._baseTheme() else: - return self.createThemeFromXml(xml, self.path) - - def checkThemesExists(self, dir): - """ - Check a theme directory exists and if not create it - - ``dir`` - Theme directory to make sure exists - """ - log.debug(u'check themes') - if not os.path.exists(dir): - os.mkdir(dir) + return self._createThemeFromXml(xml, self.path) def unzipTheme(self, filename, dir): """ @@ -494,8 +492,7 @@ class ThemeManager(QtGui.QWidget): theme_dir = None if osfile.endswith(os.path.sep): theme_dir = os.path.join(dir, osfile) - if not os.path.exists(theme_dir): - os.mkdir(os.path.join(dir, osfile)) + check_directory_exists(theme_dir) else: fullpath = os.path.join(dir, osfile) names = osfile.split(os.path.sep) @@ -505,8 +502,7 @@ class ThemeManager(QtGui.QWidget): themename = names[0] if theme_dir is None: theme_dir = os.path.join(dir, names[0]) - if not os.path.exists(theme_dir): - os.mkdir(os.path.join(dir, names[0])) + check_directory_exists(theme_dir) if os.path.splitext(ucsfile)[1].lower() in [u'.xml']: xml_data = zip.read(file) try: @@ -522,19 +518,22 @@ class ThemeManager(QtGui.QWidget): outfile = open(fullpath, u'wb') outfile.write(zip.read(file)) if filexml: - theme = self.createThemeFromXml(filexml, self.path) + theme = self._createThemeFromXml(filexml, self.path) self.generateAndSaveImage(dir, themename, theme) else: - QtGui.QMessageBox.critical(self, - translate('OpenLP.ThemeManager', 'Error'), - translate('OpenLP.ThemeManager', - 'File is not a valid theme.')) + Receiver.send_message(u'openlp_error_message', { + u'title': translate('OpenLP.ThemeManager', + 'Validation Error'), + u'message':translate('OpenLP.ThemeManager', + 'File is not a valid theme.')}) log.exception(u'Theme file does not contain XML data %s' % filename) except (IOError, NameError): - QtGui.QMessageBox.critical(self, - translate('OpenLP.ThemeManager', 'Error'), - translate('OpenLP.ThemeManager', 'File is not a valid theme.')) + Receiver.send_message(u'openlp_error_message', { + u'title': translate('OpenLP.ThemeManager', + 'Validation Error'), + u'message':translate('OpenLP.ThemeManager', + 'File is not a valid theme.')}) log.exception(u'Importing theme from zip failed %s' % filename) finally: if zip: @@ -556,9 +555,161 @@ class ThemeManager(QtGui.QWidget): if tree.find(u'BackgroundType') is None: return xml_data else: - return self.migrateVersion122(xml_data) + return self._migrateVersion122(xml_data) - def migrateVersion122(self, xml_data): + def checkIfThemeExists(self, themeName): + """ + Check if theme already exists and displays error message + + ``themeName`` + Name of the Theme to test + """ + theme_dir = os.path.join(self.path, themeName) + if os.path.exists(theme_dir): + Receiver.send_message(u'openlp_error_message', { + u'title': translate('OpenLP.ThemeManager', + 'Validation Error'), + u'message':translate('OpenLP.ThemeManager', + 'A theme with this name already exists.')}) + return False + return True + + def saveTheme(self, theme, imageFrom, imageTo): + """ + Called by thememaintenance Dialog to save the theme + and to trigger the reload of the theme list + """ + name = theme.theme_name + theme_pretty_xml = theme.extract_formatted_xml() + log.debug(u'saveTheme %s %s', name, theme_pretty_xml) + theme_dir = os.path.join(self.path, name) + check_directory_exists(theme_dir) + theme_file = os.path.join(theme_dir, name + u'.xml') + if imageTo and self.oldBackgroundImage and \ + imageTo != self.oldBackgroundImage: + try: + os.remove(self.oldBackgroundImage) + except OSError: + log.exception(u'Unable to remove old theme background') + outfile = None + try: + outfile = open(theme_file, u'w') + outfile.write(theme_pretty_xml) + except IOError: + log.exception(u'Saving theme to file failed') + finally: + if outfile: + outfile.close() + if imageFrom and imageFrom != imageTo: + try: + encoding = get_filesystem_encoding() + shutil.copyfile( + unicode(imageFrom).encode(encoding), + unicode(imageTo).encode(encoding)) + except IOError: + log.exception(u'Failed to save theme image') + self.generateAndSaveImage(self.path, name, theme) + self.loadThemes() + self.pushThemes() + + def generateAndSaveImage(self, dir, name, theme): + log.debug(u'generateAndSaveImage %s %s', dir, name) + theme_xml = theme.extract_xml() + frame = self.generateImage(theme) + samplepathname = os.path.join(self.path, name + u'.png') + if os.path.exists(samplepathname): + os.unlink(samplepathname) + frame.save(samplepathname, u'png') + thumb = os.path.join(self.thumbPath, u'%s.png' % name) + icon = build_icon(frame) + pixmap = icon.pixmap(QtCore.QSize(88, 50)) + pixmap.save(thumb, u'png') + log.debug(u'Theme image written to %s', samplepathname) + + def generateImage(self, themeData, forcePage=False): + """ + Call the RenderManager to build a Sample Image + + ``themeData`` + The theme to generated a preview for. + + ``forcePage`` + Flag to tell message lines per page need to be generated. + """ + log.debug(u'generateImage \n%s ', themeData) + return self.mainwindow.renderManager.generate_preview(themeData, forcePage) + + def getPreviewImage(self, theme): + """ + Return an image representing the look of the theme + + ``theme`` + The theme to return the image for + """ + log.debug(u'getPreviewImage %s ', theme) + image = os.path.join(self.path, theme + u'.png') + return image + + def _baseTheme(self): + """ + Provide a base theme with sensible defaults + """ + log.debug(u'base theme created') + newtheme = ThemeXML() + return newtheme + + def _createThemeFromXml(self, themeXml, path): + """ + Return a theme object using information parsed from XML + + ``themeXml`` + The XML data to load into the theme + """ + theme = ThemeXML() + theme.parse(themeXml) + theme.extend_image_filename(path) + return theme + + def _validate_theme_action(self, select_text, confirm_title, confirm_text, + testPlugin=True): + """ + Check to see if theme has been selected and the destructive action + is allowed. + """ + self.global_theme = unicode(QtCore.QSettings().value( + self.settingsSection + u'/global theme', + QtCore.QVariant(u'')).toString()) + if check_item_selected(self.themeListWidget, select_text): + item = self.themeListWidget.currentItem() + theme = unicode(item.text()) + # confirm deletion + answer = QtGui.QMessageBox.question(self, confirm_title, + confirm_text % theme, QtGui.QMessageBox.StandardButtons( + QtGui.QMessageBox.Yes | QtGui.QMessageBox.No), + QtGui.QMessageBox.No) + if answer == QtGui.QMessageBox.No: + return False + # should be the same unless default + if theme != unicode(item.data(QtCore.Qt.UserRole).toString()): + QtGui.QMessageBox.critical(self, + translate('OpenLP.ThemeManager', 'Error'), + translate('OpenLP.ThemeManager', + 'You are unable to delete the default theme.')) + return False + # check for use in the system else where. + if testPlugin: + for plugin in self.mainwindow.pluginManager.plugins: + if plugin.usesTheme(theme): + Receiver.send_message(u'openlp_error_message', { + u'title': translate('OpenLP.ThemeManager', + 'Validation Error'), + u'message': unicode(translate('OpenLP.ThemeManager', + 'Theme %s is used in the %s plugin.')) % \ + (theme, plugin.name)}) + return False + return True + + def _migrateVersion122(self, xml_data): """ Convert the xml data from version 1 format to the current format. @@ -615,191 +766,3 @@ class ThemeManager(QtGui.QWidget): newtheme.display_horizontal_align = theme.HorizontalAlign newtheme.display_vertical_align = vAlignCorrection return newtheme.extract_xml() - - def saveTheme(self, theme, imageFrom, imageTo): - """ - Called by thememaintenance Dialog to save the theme - and to trigger the reload of the theme list - """ - name = theme.theme_name - theme_pretty_xml = theme.extract_formatted_xml() - log.debug(u'saveTheme %s %s', name, theme_pretty_xml) - theme_dir = os.path.join(self.path, name) - if not os.path.exists(theme_dir): - os.mkdir(os.path.join(self.path, name)) - theme_file = os.path.join(theme_dir, name + u'.xml') - log.debug(theme_file) - editedServiceTheme = False - result = QtGui.QMessageBox.Yes - if self.saveThemeName != name: - if os.path.exists(theme_file): - result = QtGui.QMessageBox.question(self, - translate('OpenLP.ThemeManager', 'Theme Exists'), - translate('OpenLP.ThemeManager', - 'A theme with this name already ' - 'exists. Would you like to overwrite it?'), - (QtGui.QMessageBox.Yes | QtGui.QMessageBox.No), - QtGui.QMessageBox.No) - if self.saveThemeName != u'': - for plugin in self.parent.pluginManager.plugins: - if plugin.usesTheme(self.saveThemeName): - plugin.renameTheme(self.saveThemeName, name) - if unicode(self.serviceComboBox.currentText()) == name: - editedServiceTheme = True - if result == QtGui.QMessageBox.Yes: - # Save the theme, overwriting the existing theme if necessary. - if imageTo and self.oldBackgroundImage and \ - imageTo != self.oldBackgroundImage: - try: - os.remove(self.oldBackgroundImage) - except OSError: - log.exception(u'Unable to remove old theme background') - outfile = None - try: - outfile = open(theme_file, u'w') - outfile.write(theme_pretty_xml) - except IOError: - log.exception(u'Saving theme to file failed') - finally: - if outfile: - outfile.close() - if imageFrom and imageFrom != imageTo: - try: - encoding = get_filesystem_encoding() - shutil.copyfile( - unicode(imageFrom).encode(encoding), - unicode(imageTo).encode(encoding)) - except IOError: - log.exception(u'Failed to save theme image') - self.generateAndSaveImage(self.path, name, theme) - self.loadThemes() - # Check if we need to set a new service theme - if editedServiceTheme: - newThemeIndex = self.serviceComboBox.findText(name) - if newThemeIndex != -1: - self.serviceComboBox.setCurrentIndex(newThemeIndex) - if self.editingDefault: - if self.saveThemeName != name: - newThemeItem = self.themeListWidget.findItems(name, - QtCore.Qt.MatchExactly)[0] - newThemeIndex = self.themeListWidget.indexFromItem( - newThemeItem).row() - self.global_theme = unicode( - self.themeListWidget.item(newThemeIndex).text()) - newName = unicode(translate('OpenLP.ThemeManager', - '%s (default)')) % self.global_theme - self.themeListWidget.item(newThemeIndex).setText(newName) - QtCore.QSettings().setValue( - self.settingsSection + u'/global theme', - QtCore.QVariant(self.global_theme)) - Receiver.send_message(u'theme_update_global', - self.global_theme) - self.editingDefault = False - self.pushThemes() - return True - else: - # Don't close the dialog - allow the user to change the name of - # the theme or to cancel the theme dialog completely. - return False - - def generateAndSaveImage(self, dir, name, theme): - log.debug(u'generateAndSaveImage %s %s', dir, name) - theme_xml = theme.extract_xml() - frame = self.generateImage(theme) - samplepathname = os.path.join(self.path, name + u'.png') - if os.path.exists(samplepathname): - os.unlink(samplepathname) - frame.save(samplepathname, u'png') - thumb = os.path.join(self.thumbPath, u'%s.png' % name) - icon = build_icon(frame) - pixmap = icon.pixmap(QtCore.QSize(88, 50)) - pixmap.save(thumb, u'png') - log.debug(u'Theme image written to %s', samplepathname) - - def generateImage(self, themeData, forcePage=False): - """ - Call the RenderManager to build a Sample Image - - ``themeData`` - The theme to generated a preview for. - - ``forcePage`` - Flag to tell message lines per page need to be generated. - """ - log.debug(u'generateImage \n%s ', themeData) - return self.parent.renderManager.generate_preview(themeData, forcePage) - - def getPreviewImage(self, theme): - """ - Return an image representing the look of the theme - - ``theme`` - The theme to return the image for - """ - log.debug(u'getPreviewImage %s ', theme) - image = os.path.join(self.path, theme + u'.png') - return image - - def baseTheme(self): - """ - Provide a base theme with sensible defaults - """ - log.debug(u'base theme created') - newtheme = ThemeXML() - return newtheme - - def createThemeFromXml(self, themeXml, path): - """ - Return a theme object using information parsed from XML - - ``themeXml`` - The XML data to load into the theme - """ - theme = ThemeXML() - theme.parse(themeXml) - theme.extend_image_filename(path) - return theme - - def _validate_theme_action(self, select_text, confirm_title, confirm_text, - testPlugin=True): - """ - Check to see if theme has been selected and the destructive action - is allowed. - """ - self.global_theme = unicode(QtCore.QSettings().value( - self.settingsSection + u'/global theme', - QtCore.QVariant(u'')).toString()) - if check_item_selected(self.themeListWidget, select_text): - item = self.themeListWidget.currentItem() - theme = unicode(item.text()) - # confirm deletion - answer = QtGui.QMessageBox.question(self, confirm_title, - confirm_text % theme, QtGui.QMessageBox.StandardButtons( - QtGui.QMessageBox.Yes | QtGui.QMessageBox.No), - QtGui.QMessageBox.No) - if answer == QtGui.QMessageBox.No: - return False - # should be the same unless default - if theme != unicode(item.data(QtCore.Qt.UserRole).toString()): - QtGui.QMessageBox.critical(self, - translate('OpenLP.ThemeManager', 'Error'), - translate('OpenLP.ThemeManager', - 'You are unable to delete the default theme.')) - return False - else: - if testPlugin: - for plugin in self.parent.pluginManager.plugins: - if plugin.usesTheme(theme): - QtGui.QMessageBox.critical(self, - translate('OpenLP.ThemeManager', 'Error'), - unicode(translate('OpenLP.ThemeManager', - 'Theme %s is used in the %s plugin.')) % \ - (theme, plugin.name)) - return False - if unicode(self.serviceComboBox.currentText()) == theme: - QtGui.QMessageBox.critical(self, - translate('OpenLP.ThemeManager', 'Error'), - unicode(translate('OpenLP.ThemeManager', - 'Theme %s is used by the service manager.')) % theme) - return False - return True diff --git a/openlp/core/ui/themestab.py b/openlp/core/ui/themestab.py index 229650739..a440a564e 100644 --- a/openlp/core/ui/themestab.py +++ b/openlp/core/ui/themestab.py @@ -46,8 +46,8 @@ class ThemesTab(SettingsTab): self.DefaultComboBox = QtGui.QComboBox(self.GlobalGroupBox) self.DefaultComboBox.setSizeAdjustPolicy( QtGui.QComboBox.AdjustToMinimumContentsLength) - self.DefaultComboBox.setSizePolicy(QtGui.QSizePolicy.Expanding, - QtGui.QSizePolicy.Fixed) + self.DefaultComboBox.setSizePolicy( + QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed) self.DefaultComboBox.setObjectName(u'DefaultComboBox') self.GlobalGroupBoxLayout.addWidget(self.DefaultComboBox) self.DefaultListView = QtGui.QLabel(self.GlobalGroupBox) diff --git a/openlp/core/ui/themewizard.py b/openlp/core/ui/themewizard.py index 1bf0a0038..50e8109c5 100644 --- a/openlp/core/ui/themewizard.py +++ b/openlp/core/ui/themewizard.py @@ -249,7 +249,8 @@ class Ui_ThemeWizard(object): self.footerSizeSpinBox.setMaximum(999) self.footerSizeSpinBox.setValue(10) self.footerSizeSpinBox.setObjectName(u'FooterSizeSpinBox') - self.footerAreaLayout.addRow(self.footerSizeLabel, self.footerSizeSpinBox) + self.footerAreaLayout.addRow(self.footerSizeLabel, + self.footerSizeSpinBox) ThemeWizard.addPage(self.footerAreaPage) # Alignment Page self.alignmentPage = QtGui.QWizardPage() @@ -317,9 +318,11 @@ class Ui_ThemeWizard(object): self.areaPositionLayout.addWidget(self.mainPositionGroupBox) self.footerPositionGroupBox = QtGui.QGroupBox(self.areaPositionPage) self.footerPositionGroupBox.setObjectName(u'FooterPositionGroupBox') - self.footerPositionLayout = QtGui.QFormLayout(self.footerPositionGroupBox) + self.footerPositionLayout = QtGui.QFormLayout( + self.footerPositionGroupBox) self.footerPositionLayout.setObjectName(u'FooterPositionLayout') - self.footerPositionCheckBox = QtGui.QCheckBox(self.footerPositionGroupBox) + self.footerPositionCheckBox = QtGui.QCheckBox( + self.footerPositionGroupBox) self.footerPositionCheckBox.setObjectName(u'FooterPositionCheckBox') self.footerPositionLayout.addRow(self.footerPositionCheckBox) self.footerXLabel = QtGui.QLabel(self.footerPositionGroupBox) @@ -473,8 +476,6 @@ class Ui_ThemeWizard(object): self.mainColorLabel.setText(translate('OpenLP.ThemeWizard', 'Color:')) self.mainSizeLabel.setText(translate('OpenLP.ThemeWizard', 'Size:')) self.mainSizeSpinBox.setSuffix(translate('OpenLP.ThemeWizard', 'pt')) - self.mainLineCountLabel.setText( - translate('OpenLP.ThemeWizard', '(%d lines per slide)')) self.lineSpacingLabel.setText( translate('OpenLP.ThemeWizard', 'Line Spacing:')) self.lineSpacingSpinBox.setSuffix(translate('OpenLP.ThemeWizard', 'pt')) @@ -566,17 +567,17 @@ class Ui_ThemeWizard(object): self.themeNameLabel.setText( translate('OpenLP.ThemeWizard', 'Theme name:')) # Align all QFormLayouts towards each other. - width = max(self.backgroundLabel.minimumSizeHint().width(), - self.colorLabel.minimumSizeHint().width()) - width = max(width, self.gradientStartLabel.minimumSizeHint().width()) - width = max(width, self.gradientEndLabel.minimumSizeHint().width()) - width = max(width, self.gradientTypeLabel.minimumSizeHint().width()) - width = max(width, self.imageLabel.minimumSizeHint().width()) - self.backgroundTypeSpacer.changeSize(width, 0, QtGui.QSizePolicy.Fixed, - QtGui.QSizePolicy.Fixed) - self.colorSpacer.changeSize(width, 0, QtGui.QSizePolicy.Fixed, - QtGui.QSizePolicy.Fixed) - self.gradientSpacer.changeSize(width, 0, QtGui.QSizePolicy.Fixed, - QtGui.QSizePolicy.Fixed) - self.imageSpacer.changeSize(width, 0, QtGui.QSizePolicy.Fixed, - QtGui.QSizePolicy.Fixed) + labelWidth = max(self.backgroundLabel.minimumSizeHint().width(), + self.colorLabel.minimumSizeHint().width(), + self.gradientStartLabel.minimumSizeHint().width(), + self.gradientEndLabel.minimumSizeHint().width(), + self.gradientTypeLabel.minimumSizeHint().width(), + self.imageLabel.minimumSizeHint().width()) + self.backgroundTypeSpacer.changeSize(labelWidth, 0, + QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) + self.colorSpacer.changeSize(labelWidth, 0, + QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) + self.gradientSpacer.changeSize(labelWidth, 0, + QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) + self.imageSpacer.changeSize(labelWidth, 0, + QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) diff --git a/openlp/plugins/alerts/alertsplugin.py b/openlp/plugins/alerts/alertsplugin.py index 4a7e18cef..61be922d5 100644 --- a/openlp/plugins/alerts/alertsplugin.py +++ b/openlp/plugins/alerts/alertsplugin.py @@ -73,7 +73,7 @@ class AlertsPlugin(Plugin): self.toolsAlertItem.setStatusTip( translate('AlertsPlugin', 'Show an alert message.')) self.toolsAlertItem.setShortcut(u'F7') - self.serviceManager.parent.ToolsMenu.addAction(self.toolsAlertItem) + self.serviceManager.mainwindow.ToolsMenu.addAction(self.toolsAlertItem) QtCore.QObject.connect(self.toolsAlertItem, QtCore.SIGNAL(u'triggered()'), self.onAlertsTrigger) self.toolsAlertItem.setVisible(False) diff --git a/openlp/plugins/bibles/forms/bibleimportwizard.py b/openlp/plugins/bibles/forms/bibleimportwizard.py index a85e430a1..c6a6775a5 100644 --- a/openlp/plugins/bibles/forms/bibleimportwizard.py +++ b/openlp/plugins/bibles/forms/bibleimportwizard.py @@ -234,7 +234,7 @@ class Ui_BibleImportWizard(object): QtGui.QSizePolicy.Minimum) self.openlp1Layout.setItem(1, QtGui.QFormLayout.LabelRole, self.openlp1Spacer) - self.selectStack.addWidget(self.openlp1Widget) + self.selectStack.addWidget(self.openlp1Widget) self.selectPageLayout.addLayout(self.selectStack) bibleImportWizard.addPage(self.selectPage) # License Page @@ -373,19 +373,19 @@ class Ui_BibleImportWizard(object): 'you want to use this importer, you will need to install the ' '"python-sqlite" module.')) # Align all QFormLayouts towards each other. - width = max(self.formatLabel.minimumSizeHint().width(), - self.osisFileLabel.minimumSizeHint().width()) - width = max(width, self.csvBooksLabel.minimumSizeHint().width()) - width = max(width, self.csvVersesLabel.minimumSizeHint().width()) - width = max(width, self.openSongFileLabel.minimumSizeHint().width()) - width = max(width, self.openlp1FileLabel.minimumSizeHint().width()) - self.formatSpacer.changeSize(width, 0, QtGui.QSizePolicy.Fixed, - QtGui.QSizePolicy.Fixed) - self.osisSpacer.changeSize(width, 0, QtGui.QSizePolicy.Fixed, - QtGui.QSizePolicy.Fixed) - self.csvSpacer.changeSize(width, 0, QtGui.QSizePolicy.Fixed, - QtGui.QSizePolicy.Fixed) - self.openSongSpacer.changeSize(width, 0, QtGui.QSizePolicy.Fixed, - QtGui.QSizePolicy.Fixed) - self.openlp1Spacer.changeSize(width, 0, QtGui.QSizePolicy.Fixed, - QtGui.QSizePolicy.Fixed) + labelWidth = max(self.formatLabel.minimumSizeHint().width(), + self.osisFileLabel.minimumSizeHint().width(), + self.csvBooksLabel.minimumSizeHint().width(), + self.csvVersesLabel.minimumSizeHint().width(), + self.openSongFileLabel.minimumSizeHint().width(), + self.openlp1FileLabel.minimumSizeHint().width()) + self.formatSpacer.changeSize(labelWidth, 0, + QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) + self.osisSpacer.changeSize(labelWidth, 0, + QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) + self.csvSpacer.changeSize(labelWidth, 0, + QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) + self.openSongSpacer.changeSize(labelWidth, 0, + QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) + self.openlp1Spacer.changeSize(labelWidth, 0, + QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) diff --git a/openlp/plugins/bibles/lib/biblestab.py b/openlp/plugins/bibles/lib/biblestab.py index 0a2effa5e..f6bd27324 100644 --- a/openlp/plugins/bibles/lib/biblestab.py +++ b/openlp/plugins/bibles/lib/biblestab.py @@ -76,8 +76,8 @@ class BiblesTab(SettingsTab): self.BibleThemeComboBox = QtGui.QComboBox(self.VerseDisplayGroupBox) self.BibleThemeComboBox.setSizeAdjustPolicy( QtGui.QComboBox.AdjustToMinimumContentsLength) - self.BibleThemeComboBox.setSizePolicy(QtGui.QSizePolicy.Expanding, - QtGui.QSizePolicy.Fixed) + self.BibleThemeComboBox.setSizePolicy( + QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed) self.BibleThemeComboBox.addItem(u'') self.BibleThemeComboBox.setObjectName(u'BibleThemeComboBox') self.VerseDisplayLayout.addRow(self.BibleThemeLabel, @@ -88,8 +88,8 @@ class BiblesTab(SettingsTab): self.VerseDisplayLayout.addRow(self.ChangeNoteLabel) self.leftLayout.addWidget(self.VerseDisplayGroupBox) self.leftLayout.addStretch() - self.rightColumn.setSizePolicy(QtGui.QSizePolicy.Expanding, - QtGui.QSizePolicy.Preferred) + self.rightColumn.setSizePolicy( + QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred) self.rightLayout.addStretch() # Signals and slots QtCore.QObject.connect( diff --git a/openlp/plugins/bibles/lib/http.py b/openlp/plugins/bibles/lib/http.py index ef663dfbe..b7e3504a6 100644 --- a/openlp/plugins/bibles/lib/http.py +++ b/openlp/plugins/bibles/lib/http.py @@ -231,6 +231,9 @@ class BGExtract(object): footnotes = soup.findAll(u'sup', u'footnote') if footnotes: [footnote.extract() for footnote in footnotes] + crossrefs = soup.findAll(u'sup', u'xref') + if crossrefs: + [crossref.extract() for crossref in crossrefs] cleanup = [(re.compile('\s+'), lambda match: ' ')] verses = BeautifulSoup(str(soup), markupMassage=cleanup) content = verses.find(u'div', u'result-text-style-normal') @@ -306,7 +309,7 @@ class BSExtract(object): finally: if not content: return None - verse_number = re.compile(r'v(\d{2})(\d{3})(\d{3}) verse') + verse_number = re.compile(r'v(\d{1,2})(\d{3})(\d{3}) verse') verses = {} for verse in content: Receiver.send_message(u'openlp_process_events') diff --git a/openlp/plugins/bibles/lib/manager.py b/openlp/plugins/bibles/lib/manager.py index 8e1dadd0e..63c6954fb 100644 --- a/openlp/plugins/bibles/lib/manager.py +++ b/openlp/plugins/bibles/lib/manager.py @@ -273,10 +273,10 @@ class BibleManager(object): Receiver.send_message(u'openlp_information_message', { u'title': translate('BiblesPlugin.BibleManager', 'Scripture Reference Error'), - u'message': translate('BiblesPlugin.BibleManager', 'Your scripture ' - 'reference is either not supported by OpenLP or is invalid. ' - 'Please make sure your reference conforms to one of the ' - 'following patterns:\n\n' + u'message': translate('BiblesPlugin.BibleManager', + 'Your scripture reference is either not supported by OpenLP ' + 'or is invalid. Please make sure your reference conforms to ' + 'one of the following patterns:\n\n' 'Book Chapter\n' 'Book Chapter-Chapter\n' 'Book Chapter:Verse-Verse\n' diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index a669a51be..066563bec 100644 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -86,6 +86,8 @@ class BibleMediaItem(MediaManagerItem): self.quickVersionLabel = QtGui.QLabel(self.quickTab) self.quickVersionLabel.setObjectName(u'quickVersionLabel') self.quickVersionComboBox = QtGui.QComboBox(self.quickTab) + self.quickVersionComboBox.setSizeAdjustPolicy( + QtGui.QComboBox.AdjustToMinimumContentsLength) self.quickVersionComboBox.setSizePolicy( QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed) self.quickVersionComboBox.setObjectName(u'quickVersionComboBox') @@ -95,6 +97,8 @@ class BibleMediaItem(MediaManagerItem): self.quickSecondLabel = QtGui.QLabel(self.quickTab) self.quickSecondLabel.setObjectName(u'quickSecondLabel') self.quickSecondComboBox = QtGui.QComboBox(self.quickTab) + self.quickSecondComboBox.setSizeAdjustPolicy( + QtGui.QComboBox.AdjustToMinimumContentsLength) self.quickSecondComboBox.setSizePolicy( QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed) self.quickSecondComboBox.setObjectName(u'quickSecondComboBox') @@ -103,6 +107,8 @@ class BibleMediaItem(MediaManagerItem): self.quickSearchTypeLabel = QtGui.QLabel(self.quickTab) self.quickSearchTypeLabel.setObjectName(u'quickSearchTypeLabel') self.quickSearchComboBox = QtGui.QComboBox(self.quickTab) + self.quickSearchComboBox.setSizeAdjustPolicy( + QtGui.QComboBox.AdjustToMinimumContentsLength) self.quickSearchComboBox.setSizePolicy( QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed) self.quickSearchComboBox.setObjectName(u'quickSearchComboBox') @@ -118,6 +124,8 @@ class BibleMediaItem(MediaManagerItem): self.quickClearLabel = QtGui.QLabel(self.quickTab) self.quickClearLabel.setObjectName(u'quickClearLabel') self.quickClearComboBox = QtGui.QComboBox(self.quickTab) + self.quickClearComboBox.setSizeAdjustPolicy( + QtGui.QComboBox.AdjustToMinimumContentsLength) self.quickClearComboBox.setSizePolicy( QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed) self.quickClearComboBox.setObjectName(u'quickClearComboBox') @@ -144,6 +152,8 @@ class BibleMediaItem(MediaManagerItem): self.advancedLayout.addWidget(self.advancedVersionLabel, 0, 0, QtCore.Qt.AlignRight) self.advancedVersionComboBox = QtGui.QComboBox(self.advancedTab) + self.advancedVersionComboBox.setSizeAdjustPolicy( + QtGui.QComboBox.AdjustToMinimumContentsLength) self.advancedVersionComboBox.setSizePolicy( QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed) self.advancedVersionComboBox.setObjectName(u'advancedVersionComboBox') @@ -154,6 +164,8 @@ class BibleMediaItem(MediaManagerItem): self.advancedLayout.addWidget(self.advancedSecondLabel, 1, 0, QtCore.Qt.AlignRight) self.advancedSecondComboBox = QtGui.QComboBox(self.advancedTab) + self.advancedSecondComboBox.setSizeAdjustPolicy( + QtGui.QComboBox.AdjustToMinimumContentsLength) self.advancedSecondComboBox.setSizePolicy( QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed) self.advancedSecondComboBox.setObjectName(u'advancedSecondComboBox') @@ -164,6 +176,8 @@ class BibleMediaItem(MediaManagerItem): self.advancedLayout.addWidget(self.advancedBookLabel, 2, 0, QtCore.Qt.AlignRight) self.advancedBookComboBox = QtGui.QComboBox(self.advancedTab) + self.advancedBookComboBox.setSizeAdjustPolicy( + QtGui.QComboBox.AdjustToMinimumContentsLength) self.advancedBookComboBox.setSizePolicy( QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed) self.advancedBookComboBox.setObjectName(u'advancedBookComboBox') @@ -201,6 +215,8 @@ class BibleMediaItem(MediaManagerItem): self.advancedLayout.addWidget(self.advancedClearLabel, 6, 0, QtCore.Qt.AlignRight) self.advancedClearComboBox = QtGui.QComboBox(self.quickTab) + self.advancedClearComboBox.setSizeAdjustPolicy( + QtGui.QComboBox.AdjustToMinimumContentsLength) self.advancedClearComboBox.setSizePolicy( QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed) self.advancedClearComboBox.setObjectName(u'advancedClearComboBox') @@ -709,11 +725,11 @@ class BibleMediaItem(MediaManagerItem): if len(items) == 0: return False bible_text = u'' + old_item = None old_chapter = -1 raw_footer = [] raw_slides = [] raw_title = [] - first_item = True for item in items: bitem = self.listView.item(item.row()) book = self._decodeQtObject(bitem, 'book') @@ -754,9 +770,8 @@ class BibleMediaItem(MediaManagerItem): # We have to be 'Continuous'. else: bible_text = u'%s %s\u00a0%s\n' % (bible_text, verse_text, text) - if first_item: + if not old_item: start_item = item - first_item = False elif self.checkTitle(item, old_item): raw_title.append(self.formatTitle(start_item, old_item)) start_item = item diff --git a/openlp/plugins/images/lib/mediaitem.py b/openlp/plugins/images/lib/mediaitem.py index 9f6d6c6df..063a80d02 100644 --- a/openlp/plugins/images/lib/mediaitem.py +++ b/openlp/plugins/images/lib/mediaitem.py @@ -30,7 +30,8 @@ import os from PyQt4 import QtCore, QtGui from openlp.core.lib import MediaManagerItem, BaseListWithDnD, build_icon, \ - ItemCapabilities, SettingsManager, translate, check_item_selected, Receiver + ItemCapabilities, SettingsManager, translate, check_item_selected, \ + Receiver, check_directory_exists from openlp.core.utils import AppLocation, get_images_filter log = logging.getLogger(__name__) @@ -87,8 +88,7 @@ class ImageMediaItem(MediaManagerItem): self.servicePath = os.path.join( AppLocation.get_section_data_path(self.settingsSection), u'thumbnails') - if not os.path.exists(self.servicePath): - os.mkdir(self.servicePath) + check_directory_exists(self.servicePath) self.loadList(SettingsManager.load_list( self.settingsSection, self.settingsSection)) @@ -216,4 +216,4 @@ class ImageMediaItem(MediaManagerItem): 'the image file "%s" no longer exists.')) % filename}) def onPreviewClick(self): - MediaManagerItem.onPreviewClick(self) + MediaManagerItem.onPreviewClick(self) \ No newline at end of file diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index 3c27801c8..1e65a3358 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -116,8 +116,10 @@ class PresentationMediaItem(MediaManagerItem): self.displayTypeLabel = QtGui.QLabel(self.presentationWidget) self.displayTypeLabel.setObjectName(u'displayTypeLabel') self.displayTypeComboBox = QtGui.QComboBox(self.presentationWidget) - self.displayTypeComboBox.setSizePolicy(QtGui.QSizePolicy.Expanding, - QtGui.QSizePolicy.Fixed) + self.displayTypeComboBox.setSizeAdjustPolicy( + QtGui.QComboBox.AdjustToMinimumContentsLength) + self.displayTypeComboBox.setSizePolicy( + QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed) self.displayTypeComboBox.setObjectName(u'displayTypeComboBox') self.displayTypeLabel.setBuddy(self.displayTypeComboBox) self.displayLayout.addRow(self.displayTypeLabel, diff --git a/openlp/plugins/presentations/lib/presentationtab.py b/openlp/plugins/presentations/lib/presentationtab.py index c4df4f4e1..fc82600df 100644 --- a/openlp/plugins/presentations/lib/presentationtab.py +++ b/openlp/plugins/presentations/lib/presentationtab.py @@ -116,7 +116,8 @@ class PresentationTab(SettingsTab): if controller.available: checkbox = self.PresenterCheckboxes[controller.name] setting_key = self.settingsSection + u'/' + controller.name - if QtCore.QSettings().value(setting_key) != checkbox.checkState(): + if QtCore.QSettings().value(setting_key) != \ + checkbox.checkState(): changed = True QtCore.QSettings().setValue(setting_key, QtCore.QVariant(checkbox.checkState())) diff --git a/openlp/plugins/remotes/lib/remotetab.py b/openlp/plugins/remotes/lib/remotetab.py index e97d33762..d06e69164 100644 --- a/openlp/plugins/remotes/lib/remotetab.py +++ b/openlp/plugins/remotes/lib/remotetab.py @@ -46,8 +46,8 @@ class RemoteTab(SettingsTab): self.addressLabel = QtGui.QLabel(self.serverSettingsGroupBox) self.addressLabel.setObjectName(u'addressLabel') self.addressEdit = QtGui.QLineEdit(self.serverSettingsGroupBox) - self.addressEdit.setSizePolicy(QtGui.QSizePolicy.Preferred, - QtGui.QSizePolicy.Fixed) + self.addressEdit.setSizePolicy( + QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed) self.addressEdit.setValidator(QtGui.QRegExpValidator(QtCore.QRegExp( u'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'), self)) self.addressEdit.setObjectName(u'addressEdit') diff --git a/openlp/plugins/songs/forms/authorsdialog.py b/openlp/plugins/songs/forms/authorsdialog.py index 28083ae05..6f1c7f2a4 100644 --- a/openlp/plugins/songs/forms/authorsdialog.py +++ b/openlp/plugins/songs/forms/authorsdialog.py @@ -54,7 +54,7 @@ class Ui_AuthorsDialog(object): self.displayEdit.setObjectName(u'displayEdit') self.displayLabel.setBuddy(self.displayEdit) self.authorLayout.addRow(self.displayLabel, self.displayEdit) - self.dialogLayout.addLayout(self.authorLayout) + self.dialogLayout.addLayout(self.authorLayout) self.buttonBox = QtGui.QDialogButtonBox(authorsDialog) self.buttonBox.setStandardButtons( QtGui.QDialogButtonBox.Save | QtGui.QDialogButtonBox.Cancel) diff --git a/openlp/plugins/songs/forms/editsongdialog.py b/openlp/plugins/songs/forms/editsongdialog.py index c0ccd5d6a..675108af7 100644 --- a/openlp/plugins/songs/forms/editsongdialog.py +++ b/openlp/plugins/songs/forms/editsongdialog.py @@ -114,8 +114,8 @@ class Ui_EditSongDialog(object): self.authorsComboBox = QtGui.QComboBox(self.authorsGroupBox) self.authorsComboBox.setSizeAdjustPolicy( QtGui.QComboBox.AdjustToMinimumContentsLength) - self.authorsComboBox.setSizePolicy(QtGui.QSizePolicy.Expanding, - QtGui.QSizePolicy.Fixed) + self.authorsComboBox.setSizePolicy( + QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed) self.authorsComboBox.setEditable(True) self.authorsComboBox.setInsertPolicy(QtGui.QComboBox.NoInsert) self.authorsComboBox.setObjectName(u'authorsComboBox') @@ -155,8 +155,8 @@ class Ui_EditSongDialog(object): self.topicsComboBox = QtGui.QComboBox(self.topicsGroupBox) self.topicsComboBox.setSizeAdjustPolicy( QtGui.QComboBox.AdjustToMinimumContentsLength) - self.topicsComboBox.setSizePolicy(QtGui.QSizePolicy.Expanding, - QtGui.QSizePolicy.Fixed) + self.topicsComboBox.setSizePolicy( + QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed) self.topicsComboBox.setEditable(True) self.topicsComboBox.setInsertPolicy(QtGui.QComboBox.NoInsert) self.topicsComboBox.setObjectName(u'topicsComboBox') @@ -184,8 +184,10 @@ class Ui_EditSongDialog(object): self.songBookNameLabel = QtGui.QLabel(self.songBookGroupBox) self.songBookNameLabel.setObjectName(u'songBookNameLabel') self.songBookComboBox = QtGui.QComboBox(self.songBookGroupBox) - self.songBookComboBox.setSizePolicy(QtGui.QSizePolicy.Expanding, - QtGui.QSizePolicy.Fixed) + self.songBookComboBox.setSizeAdjustPolicy( + QtGui.QComboBox.AdjustToMinimumContentsLength) + self.songBookComboBox.setSizePolicy( + QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed) self.songBookComboBox.setEditable(True) self.songBookComboBox.setInsertPolicy(QtGui.QComboBox.NoInsert) self.songBookComboBox.setObjectName(u'songBookComboBox') @@ -216,8 +218,8 @@ class Ui_EditSongDialog(object): self.themeComboBox = QtGui.QComboBox(self.themeGroupBox) self.themeComboBox.setSizeAdjustPolicy( QtGui.QComboBox.AdjustToMinimumContentsLength) - self.themeComboBox.setSizePolicy(QtGui.QSizePolicy.Expanding, - QtGui.QSizePolicy.Fixed) + self.themeComboBox.setSizePolicy( + QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed) self.themeComboBox.setEditable(True) self.themeComboBox.setInsertPolicy(QtGui.QComboBox.NoInsert) self.themeComboBox.setObjectName(u'themeComboBox') diff --git a/openlp/plugins/songs/forms/editsongform.py b/openlp/plugins/songs/forms/editsongform.py index 202cc43fe..86249f024 100644 --- a/openlp/plugins/songs/forms/editsongform.py +++ b/openlp/plugins/songs/forms/editsongform.py @@ -31,7 +31,7 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import Receiver, translate from openlp.plugins.songs.forms import EditVerseForm -from openlp.plugins.songs.lib import SongXMLBuilder, SongXMLParser, VerseType +from openlp.plugins.songs.lib import SongXML, VerseType from openlp.plugins.songs.lib.db import Book, Song, Author, Topic from editsongdialog import Ui_EditSongDialog @@ -263,8 +263,8 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): if isinstance(self.song.lyrics, buffer): self.song.lyrics = unicode(self.song.lyrics) if self.song.lyrics.startswith(u' - + + + +The XML of `OpenLyrics `_ songs is of the format:: + + + + + Amazing Grace + + + + + + Amazing grace how sweet the sound + + + + """ import logging import re from lxml import etree, objectify + +from openlp.core.lib import translate from openlp.plugins.songs.lib import VerseType -from openlp.plugins.songs.lib.db import Author, Song +from openlp.plugins.songs.lib.db import Author, Book, Song, Topic log = logging.getLogger(__name__) -class SongXMLBuilder(object): +class SongXML(object): """ - This class builds the XML used to describe songs. + This class builds and parses the XML used to describe songs. """ - log.info(u'SongXMLBuilder Loaded') + log.info(u'SongXML Loaded') - def __init__(self, song_language=None): + def __init__(self): """ - Set up the song builder. - - ``song_language`` - The language used in this song + Set up the default variables. """ - lang = u'en' - if song_language: - lang = song_language self.song_xml = objectify.fromstring(u'') - self.lyrics = etree.SubElement(self.song_xml, u'lyrics', language=lang) + self.lyrics = etree.SubElement(self.song_xml, u'lyrics') def add_verse_to_lyrics(self, type, number, content): """ - Add a verse to the ```` tag. + Add a verse to the ** tag. ``type`` - A string denoting the type of verse. Possible values are "Chorus", - "Verse", "Bridge", and "Custom". + A string denoting the type of verse. Possible values are "V", + "C", "B", "P", "I", "E" and "O". ``number`` An integer denoting the number of the item, for example: verse 1. @@ -80,18 +98,11 @@ class SongXMLBuilder(object): ``content`` The actual text of the verse to be stored. """ - verse = etree.Element(u'verse', type = unicode(type), - label = unicode(number)) + verse = etree.Element(u'verse', type=unicode(type), + label=unicode(number)) verse.text = etree.CDATA(content) self.lyrics.append(verse) - def dump_xml(self): - """ - Debugging aid to dump XML so that we can see what we have. - """ - return etree.tostring(self.song_xml, encoding=u'UTF-8', - xml_declaration=True, pretty_print=True) - def extract_xml(self): """ Extract our newly created XML song. @@ -99,16 +110,10 @@ class SongXMLBuilder(object): return etree.tostring(self.song_xml, encoding=u'UTF-8', xml_declaration=True) - -class SongXMLParser(object): - """ - A class to read in and parse a song's XML. - """ - log.info(u'SongXMLParser Loaded') - - def __init__(self, xml): + def get_verses(self, xml): """ - Set up our song XML parser. + Iterates through the verses in the XML and returns a list of verses + and their attributes. ``xml`` The XML of the song to be parsed. @@ -120,12 +125,6 @@ class SongXMLParser(object): self.song_xml = objectify.fromstring(xml) except etree.XMLSyntaxError: log.exception(u'Invalid xml %s', xml) - - def get_verses(self): - """ - Iterates through the verses in the XML and returns a list of verses - and their attributes. - """ xml_iter = self.song_xml.getiterator() verse_list = [] for element in xml_iter: @@ -142,137 +141,109 @@ class SongXMLParser(object): return etree.dump(self.song_xml) -class LyricsXML(object): +class OpenLyrics(object): """ - This class represents the XML in the ``lyrics`` field of a song. - """ - def __init__(self, song=None): - if song: - if song.lyrics.startswith(u'* + OpenLP does not support the attribute *type* and *lang*. - def extract(self, text): - """ - If the ``lyrics`` field in the database is not XML, this method is - called and used to construct the verse structure similar to the output - of the ``parse`` function. + ** + This property is not supported. - ``text`` - The text to pull verses out of. - """ - text = text.replace('\r\n', '\n') - verses = text.split('\n\n') - self.languages = [{u'language': u'en', u'verses': []}] - counter = 0 - for verse in verses: - counter = counter + 1 - self.languages[0][u'verses'].append({ - u'type': u'verse', - u'label': unicode(counter), - u'text': verse - }) - return True + ** + The ** property is fully supported. But comments in lyrics + are not supported. - def add_verse(self, type, label, text): - """ - Add a verse to the list of verses. + ** + This property is fully supported. - ``type`` - The type of list, one of "verse", "chorus", "bridge", "pre-chorus", - "intro", "outtro". + ** + This property is not supported. - ``label`` - The number associated with this verse, like 1 or 2. + ** + This property is not supported. - ``text`` - The text of the verse. - """ - self.verses.append({ - u'type': type, - u'label': label, - u'text': text - }) + ** + This property is not supported. - def export(self): - """ - Build up the XML for the verse structure. - """ - lyrics_output = u'' - for language in self.languages: - verse_output = u'' - for verse in language[u'verses']: - verse_output = verse_output + \ - u'' % \ - (verse[u'type'], verse[u'label'], verse[u'text']) - lyrics_output = lyrics_output + \ - u'%s' % \ - (language[u'language'], verse_output) - song_output = u'' + \ - u'%s' % lyrics_output - return song_output + ** + The attribute *part* is not supported. + ** + This property is not supported. -class OpenLyricsParser(object): - """ - This class represents the converter for Song to/from OpenLyrics XML. + ** + As OpenLP does only support one songbook, we cannot consider more than + one songbook. + + ** + This property is not supported. + + ** + Topics, as they are called in OpenLP, are fully supported, whereby only + the topic text (e. g. Grace) is considered, but neither the *id* nor + *lang*. + + ** + This property is not supported. + + ** + This property is not supported. + + ** + The attribute *translit* and *lang* are not supported. + + ** + OpenLP supports this property. """ def __init__(self, manager): self.manager = manager - def song_to_xml(self, song): + def song_to_xml(self, song, pretty_print=False): """ - Convert the song to OpenLyrics Format + Convert the song to OpenLyrics Format. """ - song_xml_parser = SongXMLParser(song.lyrics) - verse_list = song_xml_parser.get_verses() + sxml = SongXML() + verse_list = sxml.get_verses(song.lyrics) song_xml = objectify.fromstring( u'') properties = etree.SubElement(song_xml, u'properties') titles = etree.SubElement(properties, u'titles') - self._add_text_to_element(u'title', titles, song.title) + self._add_text_to_element(u'title', titles, song.title.strip()) if song.alternate_title: - self._add_text_to_element(u'title', titles, song.alternate_title) - if song.theme_name: - themes = etree.SubElement(properties, u'themes') - self._add_text_to_element(u'theme', themes, song.theme_name) - self._add_text_to_element(u'copyright', properties, song.copyright) - self._add_text_to_element(u'verseOrder', properties, song.verse_order) + self._add_text_to_element( + u'title', titles, song.alternate_title.strip()) + if song.comments: + comments = etree.SubElement(properties, u'comments') + self._add_text_to_element(u'comment', comments, song.comments) + if song.copyright: + self._add_text_to_element(u'copyright', properties, song.copyright) + if song.verse_order: + self._add_text_to_element( + u'verseOrder', properties, song.verse_order) if song.ccli_number: self._add_text_to_element(u'ccliNo', properties, song.ccli_number) - authors = etree.SubElement(properties, u'authors') - for author in song.authors: - self._add_text_to_element(u'author', authors, author.display_name) + if song.authors: + authors = etree.SubElement(properties, u'authors') + for author in song.authors: + self._add_text_to_element( + u'author', authors, author.display_name) + book = self.manager.get_object_filtered( + Book, Book.id == song.song_book_id) + if book is not None: + book = book.name + songbooks = etree.SubElement(properties, u'songbooks') + element = self._add_text_to_element( + u'songbook', songbooks, None, book) + element.set(u'entry', song.song_number) + if song.topics: + themes = etree.SubElement(properties, u'themes') + for topic in song.topics: + self._add_text_to_element(u'theme', themes, topic.name) lyrics = etree.SubElement(song_xml, u'lyrics') for verse in verse_list: verse_tag = u'%s%s' % ( @@ -282,78 +253,36 @@ class OpenLyricsParser(object): element = self._add_text_to_element(u'lines', element) for line in unicode(verse[1]).split(u'\n'): self._add_text_to_element(u'line', element, line) - return self._extract_xml(song_xml) + return self._extract_xml(song_xml, pretty_print) def xml_to_song(self, xml): """ - Create a Song from OpenLyrics format xml + Create and save a song from OpenLyrics format xml to the database. Since + we also export XML from external sources (e. g. OpenLyrics import), we + cannot ensure, that it completely conforms to the OpenLyrics standard. + + ``xml`` + The XML to parse (unicode). """ - # No xml get out of here + # No xml get out of here. if not xml: return 0 song = Song() if xml[:5] == u'').sub(u'', xml) song_xml = objectify.fromstring(xml) properties = song_xml.properties - song.copyright = unicode(properties.copyright.text) - if song.copyright == u'None': - song.copyright = u'' - song.verse_order = unicode(properties.verseOrder.text) - if song.verse_order == u'None': - song.verse_order = u'' - song.topics = [] - song.book = None - theme_name = None - try: - song.ccli_number = unicode(properties.ccliNo.text) - except: - song.ccli_number = u'' - try: - theme_name = unicode(properties.themes.theme) - except: - pass - if theme_name: - song.theme_name = theme_name - else: - song.theme_name = u'' - # Process Titles - for title in properties.titles.title: - if not song.title: - song.title = unicode(title.text) - song.search_title = unicode(song.title) - song.alternate_title = u'' - else: - song.alternate_title = unicode(title.text) - song.search_title += u'@' + song.alternate_title - song.search_title = re.sub(r'[\'"`,;:(){}?]+', u'', - unicode(song.search_title)).lower() - # Process Lyrics - sxml = SongXMLBuilder() - search_text = u'' - for lyrics in song_xml.lyrics: - for verse in song_xml.lyrics.verse: - text = u'' - for line in verse.lines.line: - line = unicode(line) - if not text: - text = line - else: - text += u'\n' + line - type = VerseType.expand_string(verse.attrib[u'name'][0]) - sxml.add_verse_to_lyrics(type, verse.attrib[u'name'][1], text) - search_text = search_text + text - song.search_lyrics = search_text.lower() - song.lyrics = unicode(sxml.extract_xml(), u'utf-8') - song.comments = u'' - song.song_number = u'' - # Process Authors - try: - for author in properties.authors.author: - self._process_author(author.text, song) - except: - # No Author in XML so ignore - pass + self._process_copyright(properties, song) + self._process_cclinumber(properties, song) + self._process_titles(properties, song) + # The verse order is processed with the lyrics! + self._process_lyrics(properties, song_xml.lyrics, song) + self._process_comments(properties, song) + self._process_authors(properties, song) + self._process_songbooks(properties, song) + self._process_topics(properties, song) self.manager.save_object(song) return song.id @@ -367,33 +296,258 @@ class OpenLyricsParser(object): parent.append(element) return element + def _extract_xml(self, xml, pretty_print): + """ + Extract our newly created XML song. + """ + return etree.tostring(xml, encoding=u'UTF-8', + xml_declaration=True, pretty_print=pretty_print) + + def _get(self, element, attribute): + """ + This returns the element's attribute as unicode string. + + ``element`` + The element. + + ``attribute`` + The element's attribute (unicode). + """ + if element.get(attribute) is not None: + return unicode(element.get(attribute)) + return u'' + + def _text(self, element): + """ + This returns the text of an element as unicode string. + + ``element`` + The element. + """ + if element.text is not None: + return unicode(element.text) + return u'' + + def _process_authors(self, properties, song): + """ + Adds the authors specified in the XML to the song. + + ``properties`` + The property object (lxml.objectify.ObjectifiedElement). + + ``song`` + The song object. + """ + authors = [] + try: + for author in properties.authors.author: + display_name = self._text(author) + if display_name: + authors.append(display_name) + except AttributeError: + pass + if not authors: + # Add "Author unknown" (can be translated). + authors.append((unicode(translate('SongsPlugin.XML', + 'Author unknown')))) + for display_name in authors: + author = self.manager.get_object_filtered(Author, + Author.display_name == display_name) + if author is None: + # We need to create a new author, as the author does not exist. + author = Author.populate(display_name=display_name, + last_name=display_name.split(u' ')[-1], + first_name=u' '.join(display_name.split(u' ')[:-1])) + self.manager.save_object(author) + song.authors.append(author) + + def _process_cclinumber(self, properties, song): + """ + Adds the CCLI number to the song. + + ``properties`` + The property object (lxml.objectify.ObjectifiedElement). + + ``song`` + The song object. + """ + try: + song.ccli_number = self._text(properties.ccliNo) + except AttributeError: + song.ccli_number = u'' + + def _process_comments(self, properties, song): + """ + Joins the comments specified in the XML and add it to the song. + + ``properties`` + The property object (lxml.objectify.ObjectifiedElement). + + ``song`` + The song object. + """ + try: + comments_list = [] + for comment in properties.comments.comment: + commenttext = self._text(comment) + if commenttext: + comments_list.append(commenttext) + song.comments = u'\n'.join(comments_list) + except AttributeError: + song.comments = u'' + + def _process_copyright(self, properties, song): + """ + Adds the copyright to the song. + + ``properties`` + The property object (lxml.objectify.ObjectifiedElement). + + ``song`` + The song object. + """ + try: + song.copyright = self._text(properties.copyright) + except AttributeError: + song.copyright = u'' + + def _process_lyrics(self, properties, lyrics, song): + """ + Processes the verses and search_lyrics for the song. + + ``properties`` + The properties object (lxml.objectify.ObjectifiedElement). + + ``lyrics`` + The lyrics object (lxml.objectify.ObjectifiedElement). + + ``song`` + The song object. + """ + sxml = SongXML() + search_text = u'' + temp_verse_order = [] + for verse in lyrics.verse: + text = u'' + for lines in verse.lines: + if text: + text += u'\n' + text += u'\n'.join([unicode(line) for line in lines.line]) + verse_name = self._get(verse, u'name') + verse_type = unicode(VerseType.expand_string(verse_name[0]))[0] + verse_number = re.compile(u'[a-zA-Z]*').sub(u'', verse_name) + verse_part = re.compile(u'[0-9]*').sub(u'', verse_name[1:]) + # OpenLyrics allows e. g. "c", but we need "c1". + if not verse_number: + verse_number = u'1' + temp_verse_order.append((verse_type, verse_number, verse_part)) + sxml.add_verse_to_lyrics(verse_type, verse_number, text) + search_text = search_text + text + song.search_lyrics = search_text.lower() + song.lyrics = unicode(sxml.extract_xml(), u'utf-8') + # Process verse order + try: + song.verse_order = self._text(properties.verseOrder) + except AttributeError: + # We have to process the temp_verse_order, as the verseOrder + # property is not present. + previous_type = u'' + previous_number = u'' + previous_part = u'' + verse_order = [] + # Currently we do not support different "parts"! + for name in temp_verse_order: + if name[0] == previous_type: + if name[1] != previous_number: + verse_order.append(u''.join((name[0], name[1]))) + else: + verse_order.append(u''.join((name[0], name[1]))) + previous_type = name[0] + previous_number = name[1] + previous_part = name[2] + song.verse_order = u' '.join(verse_order) + + def _process_songbooks(self, properties, song): + """ + Adds the song book and song number specified in the XML to the song. + + ``properties`` + The property object (lxml.objectify.ObjectifiedElement). + + ``song`` + The song object. + """ + song.song_book_id = 0 + song.song_number = u'' + try: + for songbook in properties.songbooks.songbook: + bookname = self._get(songbook, u'name') + if bookname: + book = self.manager.get_object_filtered(Book, + Book.name == bookname) + if book is None: + # We need to create a book, because it does not exist. + book = Book.populate(name=bookname, publisher=u'') + self.manager.save_object(book) + song.song_book_id = book.id + try: + if self._get(songbook, u'entry'): + song.song_number = self._get(songbook, u'entry') + except AttributeError: + pass + # We does only support one song book, so take the first one. + break + except AttributeError: + pass + + def _process_titles(self, properties, song): + """ + Processes the titles specified in the song's XML. + + ``properties`` + The property object (lxml.objectify.ObjectifiedElement). + + ``song`` + The song object. + """ + for title in properties.titles.title: + if not song.title: + song.title = self._text(title) + song.search_title = unicode(song.title) + song.alternate_title = u'' + else: + song.alternate_title = self._text(title) + song.search_title += u'@' + song.alternate_title + song.search_title = re.sub(r'[\'"`,;:(){}?]+', u'', + unicode(song.search_title)).lower() + + def _process_topics(self, properties, song): + """ + Adds the topics to the song. + + ``properties`` + The property object (lxml.objectify.ObjectifiedElement). + + ``song`` + The song object. + """ + try: + for topictext in properties.themes.theme: + topictext = self._text(topictext) + if topictext: + topic = self.manager.get_object_filtered(Topic, + Topic.name == topictext) + if topic is None: + # We need to create a topic, because it does not exist. + topic = Topic.populate(name=topictext) + self.manager.save_object(topic) + song.topics.append(topic) + except AttributeError: + pass + def _dump_xml(self, xml): """ Debugging aid to dump XML so that we can see what we have. """ return etree.tostring(xml, encoding=u'UTF-8', xml_declaration=True, pretty_print=True) - - def _extract_xml(self, xml): - """ - Extract our newly created XML song. - """ - return etree.tostring(xml, encoding=u'UTF-8', - xml_declaration=True) - - def _process_author(self, name, song): - """ - Find or create an Author from display_name. - """ - name = unicode(name) - author = self.manager.get_object_filtered(Author, - Author.display_name == name) - if author: - # should only be one! so take the first - song.authors.append(author) - else: - # Need a new author - new_author = Author.populate(first_name=name.rsplit(u' ', 1)[0], - last_name=name.rsplit(u' ', 1)[1], display_name=name) - self.manager.save_object(new_author) - song.authors.append(new_author) \ No newline at end of file diff --git a/openlp/plugins/songs/songsplugin.py b/openlp/plugins/songs/songsplugin.py index 545497acb..17e609fd4 100644 --- a/openlp/plugins/songs/songsplugin.py +++ b/openlp/plugins/songs/songsplugin.py @@ -31,7 +31,7 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import Plugin, StringContent, build_icon, translate from openlp.core.lib.db import Manager -from openlp.plugins.songs.lib import SongMediaItem, SongsTab, SongXMLParser +from openlp.plugins.songs.lib import SongMediaItem, SongsTab, SongXML from openlp.plugins.songs.lib.db import init_schema, Song from openlp.plugins.songs.lib.importer import SongFormat @@ -153,7 +153,7 @@ class SongsPlugin(Plugin): song.search_title = self.whitespace.sub(u' ', song.title.lower() + \ u' ' + song.alternate_title.lower()) lyrics = u'' - verses = SongXMLParser(song.lyrics).get_verses() + verses = SongXML().get_verses(song.lyrics) for verse in verses: lyrics = lyrics + self.whitespace.sub(u' ', verse[1]) + u' ' song.search_lyrics = lyrics.lower() diff --git a/resources/forms/displaytabeditdialog.ui b/resources/forms/displaytabeditdialog.ui new file mode 100644 index 000000000..3c748594f --- /dev/null +++ b/resources/forms/displaytabeditdialog.ui @@ -0,0 +1,209 @@ + + + displayTagEdit + + + + 0 + 0 + 717 + 554 + + + + Form + + + + + 10 + 320 + 691 + 181 + + + + Edit Selection + + + + + 600 + 140 + 73 + 26 + + + + Update + + + + + + 20 + 50 + 571 + 114 + + + + + + + Description + + + Qt::AlignCenter + + + + + + + + + + Tag + + + Qt::AlignCenter + + + + + + + + 50 + 16777215 + + + + 5 + + + + + + + Start tag + + + Qt::AlignCenter + + + + + + + + + + End tag + + + Qt::AlignCenter + + + + + + + + + + + + + 540 + 510 + 162 + 26 + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + 530 + 280 + 71 + 26 + + + + Delete + + + + + + 610 + 280 + 71 + 26 + + + + Add + + + + + + 10 + 10 + 691 + 271 + + + + Qt::ScrollBarAlwaysOff + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + false + + + + Description + + + + + Key + + + AlignHCenter|AlignVCenter|AlignCenter + + + + + Start Tag + + + + + End Tag + + + + + + +