This commit is contained in:
Raoul Snyman 2013-10-05 20:06:23 +02:00
commit 47e4eacca8
10 changed files with 575 additions and 230 deletions

View File

@ -36,7 +36,7 @@ from openlp.core.lib import Settings, translate
class FormattingTags(object):
"""
Static Class to HTML Tags to be access around the code the list is managed by the Options Tab.
Static Class for HTML Tags to be access around the code the list is managed by the Options Tab.
"""
html_expands = []
@ -48,22 +48,15 @@ class FormattingTags(object):
return FormattingTags.html_expands
@staticmethod
def save_html_tags():
def save_html_tags(new_tags):
"""
Saves all formatting tags except protected ones.
Saves all formatting tags except protected ones
`new_tags`
The tags to be saved..
"""
tags = []
for tag in FormattingTags.html_expands:
if not tag['protected'] and not tag.get('temporary'):
# Using dict ensures that copy is made and encoding of values a little later does not affect tags in
# the original list
tags.append(dict(tag))
tag = tags[-1]
# Remove key 'temporary' from tags. It is not needed to be saved.
if 'temporary' in tag:
del tag['temporary']
# Formatting Tags were also known as display tags.
Settings().setValue('formattingTags/html_tags', json.dumps(tags) if tags else '')
Settings().setValue('formattingTags/html_tags', json.dumps(new_tags) if new_tags else '')
@staticmethod
def load_tags():

View File

@ -95,6 +95,7 @@ from .aboutform import AboutForm
from .pluginform import PluginForm
from .settingsform import SettingsForm
from .formattingtagform import FormattingTagForm
from .formattingtagcontroller import FormattingTagController
from .shortcutlistform import ShortcutListForm
from .mediadockmanager import MediaDockManager
from .servicemanager import ServiceManager
@ -104,4 +105,4 @@ __all__ = ['SplashScreen', 'AboutForm', 'SettingsForm', 'MainDisplay', 'SlideCon
'ThemeManager', 'MediaDockManager', 'ServiceItemEditForm', 'FirstTimeForm', 'FirstTimeLanguageForm', 'ThemeForm',
'ThemeLayoutForm', 'FileRenameForm', 'StartTimeForm', 'MainDisplay', 'Display', 'ServiceNoteForm',
'SlideController', 'DisplayController', 'GeneralTab', 'ThemesTab', 'AdvancedTab', 'PluginForm',
'FormattingTagForm', 'ShortcutListForm']
'FormattingTagForm', 'ShortcutListForm', 'FormattingTagController']

View File

@ -0,0 +1,176 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2013 Raoul Snyman #
# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# 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:`formattingtagform` 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 re
from openlp.core.lib import FormattingTags, translate
class FormattingTagController(object):
"""
The :class:`FormattingTagController` manages the non UI functions .
"""
def __init__(self):
"""
Initiator
"""
self.html_tag_regex = re.compile(r'<(?:(?P<close>/(?=[^\s/>]+>))?'
r'(?P<tag>[^\s/!\?>]+)(?:\s+[^\s=]+="[^"]*")*\s*(?P<empty>/)?'
r'|(?P<cdata>!\[CDATA\[(?:(?!\]\]>).)*\]\])'
r'|(?P<procinst>\?(?:(?!\?>).)*\?)'
r'|(?P<comment>!--(?:(?!-->).)*--))>', re.UNICODE)
self.html_regex = re.compile(r'^(?:[^<>]*%s)*[^<>]*$' % self.html_tag_regex.pattern)
def pre_save(self):
"""
Cleanup the array before save validation runs
"""
self.protected_tags = [tag for tag in FormattingTags.html_expands if tag.get('protected')]
self.custom_tags = []
def validate_for_save(self, desc, tag, start_html, end_html):
"""
Validate a custom tag and add to the tags array if valid..
`desc`
Explanation of the tag.
`tag`
The tag in the song used to mark the text.
`start_html`
The start html tag.
`end_html`
The end html tag.
"""
for linenumber, html1 in enumerate(self.protected_tags):
if self._strip(html1['start tag']) == tag:
return translate('OpenLP.FormattingTagForm', 'Tag %s already defined.') % tag
if self._strip(html1['desc']) == desc:
return translate('OpenLP.FormattingTagForm', 'Description %s already defined.') % tag
for linenumber, html1 in enumerate(self.custom_tags):
if self._strip(html1['start tag']) == tag:
return translate('OpenLP.FormattingTagForm', 'Tag %s already defined.') % tag
if self._strip(html1['desc']) == desc:
return translate('OpenLP.FormattingTagForm', 'Description %s already defined.') % tag
tag = {
'desc': desc,
'start tag': '{%s}' % tag,
'start html': start_html,
'end tag': '{/%s}' % tag,
'end html': end_html,
'protected': False,
'temporary': False
}
self.custom_tags.append(tag)
def save_tags(self):
"""
Save the new tags if they are valid.
"""
FormattingTags.save_html_tags(self.custom_tags)
FormattingTags.load_tags()
def _strip(self, tag):
"""
Remove tag wrappers for editing.
`tag`
Tag to be stripped
"""
tag = tag.replace('{', '')
tag = tag.replace('}', '')
return tag
def start_html_to_end_html(self, start_html):
"""
Return the end HTML for a given start HTML or None if invalid.
`start_html`
The start html tag.
"""
end_tags = []
match = self.html_regex.match(start_html)
if match:
match = self.html_tag_regex.search(start_html)
while match:
if match.group('tag'):
tag = match.group('tag').lower()
if match.group('close'):
if match.group('empty') or not end_tags or end_tags.pop() != tag:
return
elif not match.group('empty'):
end_tags.append(tag)
match = self.html_tag_regex.search(start_html, match.end())
return ''.join(map(lambda tag: '</%s>' % tag, reversed(end_tags)))
def start_tag_changed(self, start_html, end_html):
"""
Validate the HTML tags when the start tag has been changed.
`start_html`
The start html tag.
`end_html`
The end html tag.
"""
end = self.start_html_to_end_html(start_html)
if not end_html:
if not end:
return translate('OpenLP.FormattingTagForm', 'Start tag %s is not valid HTML' % start_html), None
return None, end
return None, None
def end_tag_changed(self, start_html, end_html):
"""
Validate the HTML tags when the end tag has been changed.
`start_html`
The start html tag.
`end_html`
The end html tag.
"""
end = self.start_html_to_end_html(start_html)
if not end_html:
return None, end
if end and end != end_html:
return translate('OpenLP.FormattingTagForm',
'End tag %s does not match end tag for start tag %s' % (end, start_html)), None
return None, None

View File

@ -31,7 +31,7 @@ The UI widgets for the formatting tags window.
"""
from PyQt4 import QtCore, QtGui
from openlp.core.lib import UiStrings, translate
from openlp.core.lib import UiStrings, translate, build_icon
from openlp.core.lib.ui import create_button_box
@ -45,12 +45,34 @@ class Ui_FormattingTagDialog(object):
"""
formatting_tag_dialog.setObjectName('formatting_tag_dialog')
formatting_tag_dialog.resize(725, 548)
self.list_data_grid_layout = QtGui.QGridLayout(formatting_tag_dialog)
self.list_data_grid_layout = QtGui.QVBoxLayout(formatting_tag_dialog)
self.list_data_grid_layout.setMargin(8)
self.list_data_grid_layout.setObjectName('list_data_grid_layout')
self.tag_table_widget_read_label = QtGui.QLabel()
self.list_data_grid_layout.addWidget(self.tag_table_widget_read_label)
self.tag_table_widget_read = QtGui.QTableWidget(formatting_tag_dialog)
self.tag_table_widget_read.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.tag_table_widget_read.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.tag_table_widget_read.setAlternatingRowColors(True)
self.tag_table_widget_read.setCornerButtonEnabled(False)
self.tag_table_widget_read.setObjectName('tag_table_widget_read')
self.tag_table_widget_read.setColumnCount(4)
self.tag_table_widget_read.setRowCount(0)
self.tag_table_widget_read.horizontalHeader().setStretchLastSection(True)
item = QtGui.QTableWidgetItem()
self.tag_table_widget_read.setHorizontalHeaderItem(0, item)
item = QtGui.QTableWidgetItem()
self.tag_table_widget_read.setHorizontalHeaderItem(1, item)
item = QtGui.QTableWidgetItem()
self.tag_table_widget_read.setHorizontalHeaderItem(2, item)
item = QtGui.QTableWidgetItem()
self.tag_table_widget_read.setHorizontalHeaderItem(3, item)
self.list_data_grid_layout.addWidget(self.tag_table_widget_read)
self.tag_table_widget_label = QtGui.QLabel()
self.list_data_grid_layout.addWidget(self.tag_table_widget_label)
self.tag_table_widget = QtGui.QTableWidget(formatting_tag_dialog)
self.tag_table_widget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.tag_table_widget.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.tag_table_widget.setEditTriggers(QtGui.QAbstractItemView.AllEditTriggers)
self.tag_table_widget.setAlternatingRowColors(True)
self.tag_table_widget.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
self.tag_table_widget.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
@ -67,59 +89,26 @@ class Ui_FormattingTagDialog(object):
self.tag_table_widget.setHorizontalHeaderItem(2, item)
item = QtGui.QTableWidgetItem()
self.tag_table_widget.setHorizontalHeaderItem(3, item)
self.list_data_grid_layout.addWidget(self.tag_table_widget, 0, 0, 1, 1)
self.horizontal_layout = QtGui.QHBoxLayout()
self.horizontal_layout.setObjectName('horizontal_layout')
spacer_item = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.horizontal_layout.addItem(spacer_item)
self.delete_push_button = QtGui.QPushButton(formatting_tag_dialog)
self.delete_push_button.setObjectName('delete_push_button')
self.horizontal_layout.addWidget(self.delete_push_button)
self.list_data_grid_layout.addLayout(self.horizontal_layout, 1, 0, 1, 1)
self.edit_group_box = QtGui.QGroupBox(formatting_tag_dialog)
self.edit_group_box.setObjectName('edit_group_box')
self.data_grid_layout = QtGui.QGridLayout(self.edit_group_box)
self.data_grid_layout.setObjectName('data_grid_layout')
self.description_label = QtGui.QLabel(self.edit_group_box)
self.description_label.setAlignment(QtCore.Qt.AlignCenter)
self.description_label.setObjectName('description_label')
self.data_grid_layout.addWidget(self.description_label, 0, 0, 1, 1)
self.description_line_edit = QtGui.QLineEdit(self.edit_group_box)
self.description_line_edit.setObjectName('description_line_edit')
self.data_grid_layout.addWidget(self.description_line_edit, 0, 1, 2, 1)
self.new_push_button = QtGui.QPushButton(self.edit_group_box)
self.new_push_button.setObjectName('new_push_button')
self.data_grid_layout.addWidget(self.new_push_button, 0, 2, 2, 1)
self.tag_label = QtGui.QLabel(self.edit_group_box)
self.tag_label.setAlignment(QtCore.Qt.AlignCenter)
self.tag_label.setObjectName('tag_label')
self.data_grid_layout.addWidget(self.tag_label, 2, 0, 1, 1)
self.tag_line_edit = QtGui.QLineEdit(self.edit_group_box)
self.tag_line_edit.setMaximumSize(QtCore.QSize(50, 16777215))
self.tag_line_edit.setMaxLength(5)
self.tag_line_edit.setObjectName('tag_line_edit')
self.data_grid_layout.addWidget(self.tag_line_edit, 2, 1, 1, 1)
self.start_tag_label = QtGui.QLabel(self.edit_group_box)
self.start_tag_label.setAlignment(QtCore.Qt.AlignCenter)
self.start_tag_label.setObjectName('start_tag_label')
self.data_grid_layout.addWidget(self.start_tag_label, 3, 0, 1, 1)
self.start_tag_line_edit = QtGui.QLineEdit(self.edit_group_box)
self.start_tag_line_edit.setObjectName('start_tag_line_edit')
self.data_grid_layout.addWidget(self.start_tag_line_edit, 3, 1, 1, 1)
self.end_tag_label = QtGui.QLabel(self.edit_group_box)
self.end_tag_label.setAlignment(QtCore.Qt.AlignCenter)
self.end_tag_label.setObjectName('end_tag_label')
self.data_grid_layout.addWidget(self.end_tag_label, 4, 0, 1, 1)
self.end_tag_line_edit = QtGui.QLineEdit(self.edit_group_box)
self.end_tag_line_edit.setObjectName('end_tag_line_edit')
self.data_grid_layout.addWidget(self.end_tag_line_edit, 4, 1, 1, 1)
self.save_push_button = QtGui.QPushButton(self.edit_group_box)
self.save_push_button.setObjectName('save_push_button')
self.data_grid_layout.addWidget(self.save_push_button, 4, 2, 1, 1)
self.list_data_grid_layout.addWidget(self.edit_group_box, 2, 0, 1, 1)
self.button_box = create_button_box(formatting_tag_dialog, 'button_box', ['close'])
self.list_data_grid_layout.addWidget(self.button_box, 3, 0, 1, 1)
self.list_data_grid_layout.addWidget(self.tag_table_widget)
self.edit_button_layout = QtGui.QHBoxLayout()
self.new_button = QtGui.QPushButton(formatting_tag_dialog)
self.new_button.setIcon(build_icon(':/general/general_new.png'))
self.new_button.setObjectName('new_button')
self.edit_button_layout.addWidget(self.new_button)
self.delete_button = QtGui.QPushButton(formatting_tag_dialog)
self.delete_button.setIcon(build_icon(':/general/general_delete.png'))
self.delete_button.setObjectName('delete_button')
self.edit_button_layout.addWidget(self.delete_button)
self.edit_button_layout.addStretch()
self.list_data_grid_layout.addLayout(self.edit_button_layout)
self.button_box = create_button_box(formatting_tag_dialog, 'button_box',
['cancel', 'save', 'defaults'])
self.save_button = self.button_box.button(QtGui.QDialogButtonBox.Save)
self.save_button.setObjectName('save_button')
self.restore_button = self.button_box.button(QtGui.QDialogButtonBox.RestoreDefaults)
self.restore_button.setIcon(build_icon(':/general/general_revert.png'))
self.restore_button.setObjectName('restore_button')
self.list_data_grid_layout.addWidget(self.button_box)
self.retranslateUi(formatting_tag_dialog)
def retranslateUi(self, formatting_tag_dialog):
@ -127,14 +116,19 @@ class Ui_FormattingTagDialog(object):
Translate the UI on the fly
"""
formatting_tag_dialog.setWindowTitle(translate('OpenLP.FormattingTagDialog', 'Configure Formatting Tags'))
self.edit_group_box.setTitle(translate('OpenLP.FormattingTagDialog', 'Edit Selection'))
self.save_push_button.setText(translate('OpenLP.FormattingTagDialog', 'Save'))
self.description_label.setText(translate('OpenLP.FormattingTagDialog', 'Description'))
self.tag_label.setText(translate('OpenLP.FormattingTagDialog', 'Tag'))
self.start_tag_label.setText(translate('OpenLP.FormattingTagDialog', 'Start HTML'))
self.end_tag_label.setText(translate('OpenLP.FormattingTagDialog', 'End HTML'))
self.delete_push_button.setText(UiStrings().Delete)
self.new_push_button.setText(UiStrings().New)
self.delete_button.setText(UiStrings().Delete)
self.new_button.setText(UiStrings().New)
self.tag_table_widget_read_label.setText(translate('OpenLP.FormattingTagDialog', 'Default Formatting'))
self.tag_table_widget_read.horizontalHeaderItem(0).\
setText(translate('OpenLP.FormattingTagDialog', 'Description'))
self.tag_table_widget_read.horizontalHeaderItem(1).setText(translate('OpenLP.FormattingTagDialog', 'Tag'))
self.tag_table_widget_read.horizontalHeaderItem(2).\
setText(translate('OpenLP.FormattingTagDialog', 'Start HTML'))
self.tag_table_widget_read.horizontalHeaderItem(3).setText(translate('OpenLP.FormattingTagDialog', 'End HTML'))
self.tag_table_widget_read.setColumnWidth(0, 120)
self.tag_table_widget_read.setColumnWidth(1, 80)
self.tag_table_widget_read.setColumnWidth(2, 330)
self.tag_table_widget_label.setText(translate('OpenLP.FormattingTagDialog', 'Custom Formatting'))
self.tag_table_widget.horizontalHeaderItem(0).setText(translate('OpenLP.FormattingTagDialog', 'Description'))
self.tag_table_widget.horizontalHeaderItem(1).setText(translate('OpenLP.FormattingTagDialog', 'Tag'))
self.tag_table_widget.horizontalHeaderItem(2).setText(translate('OpenLP.FormattingTagDialog', 'Start HTML'))

View File

@ -31,14 +31,25 @@ The :mod:`formattingtagform` provides an Tag Edit facility. The Base set are pro
Custom tags can be defined and saved. The Custom Tag arrays are saved in a json string so QSettings works on them.
Base Tags cannot be changed.
"""
from PyQt4 import QtGui
from openlp.core.lib import FormattingTags, translate
from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui.formattingtagdialog import Ui_FormattingTagDialog
from openlp.core.ui.formattingtagcontroller import FormattingTagController
class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog):
class EditColumn(object):
"""
Hides the magic numbers for the table columns
"""
Description = 0
Tag = 1
StartHtml = 2
EndHtml = 3
class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog, FormattingTagController):
"""
The :class:`FormattingTagForm` manages the settings tab .
"""
@ -48,17 +59,17 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog):
"""
super(FormattingTagForm, self).__init__(parent)
self.setupUi(self)
self.services = FormattingTagController()
self.tag_table_widget.itemSelectionChanged.connect(self.on_row_selected)
self.new_push_button.clicked.connect(self.on_new_clicked)
self.save_push_button.clicked.connect(self.on_saved_clicked)
self.delete_push_button.clicked.connect(self.on_delete_clicked)
self.new_button.clicked.connect(self.on_new_clicked)
#self.save_button.clicked.connect(self.on_saved_clicked)
self.delete_button.clicked.connect(self.on_delete_clicked)
self.tag_table_widget.currentCellChanged.connect(self.on_current_cell_changed)
self.button_box.rejected.connect(self.close)
self.description_line_edit.textEdited.connect(self.on_text_edited)
self.tag_line_edit.textEdited.connect(self.on_text_edited)
self.start_tag_line_edit.textEdited.connect(self.on_text_edited)
self.end_tag_line_edit.textEdited.connect(self.on_text_edited)
# Forces reloading of tags from openlp configuration.
FormattingTags.load_tags()
self.is_deleting = False
self.reloading = False
def exec_(self):
"""
@ -66,138 +77,128 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog):
"""
# Create initial copy from master
self._reloadTable()
self.selected = -1
return QtGui.QDialog.exec_(self)
def on_row_selected(self):
"""
Table Row selected so display items and set field state.
"""
self.save_push_button.setEnabled(False)
self.selected = self.tag_table_widget.currentRow()
html = FormattingTags.get_html_tags()[self.selected]
self.description_line_edit.setText(html['desc'])
self.tag_line_edit.setText(self._strip(html['start tag']))
self.start_tag_line_edit.setText(html['start html'])
self.end_tag_line_edit.setText(html['end html'])
if html['protected']:
self.description_line_edit.setEnabled(False)
self.tag_line_edit.setEnabled(False)
self.start_tag_line_edit.setEnabled(False)
self.end_tag_line_edit.setEnabled(False)
self.delete_push_button.setEnabled(False)
else:
self.description_line_edit.setEnabled(True)
self.tag_line_edit.setEnabled(True)
self.start_tag_line_edit.setEnabled(True)
self.end_tag_line_edit.setEnabled(True)
self.delete_push_button.setEnabled(True)
def on_text_edited(self, text):
"""
Enable the ``save_push_button`` when any of the selected tag's properties
has been changed.
"""
self.save_push_button.setEnabled(True)
self.delete_button.setEnabled(True)
def on_new_clicked(self):
"""
Add a new tag to list only if it is not a duplicate.
Add a new tag to edit list and select it for editing.
"""
for html in FormattingTags.get_html_tags():
if self._strip(html['start tag']) == 'n':
critical_error_message_box(
translate('OpenLP.FormattingTagForm', 'Update Error'),
translate('OpenLP.FormattingTagForm', 'Tag "n" already defined.'))
return
# Add new tag to list
tag = {
'desc': translate('OpenLP.FormattingTagForm', 'New Tag'),
'start tag': '{n}',
'start html': translate('OpenLP.FormattingTagForm', '<HTML here>'),
'end tag': '{/n}',
'end html': translate('OpenLP.FormattingTagForm', '</and here>'),
'protected': False,
'temporary': False
}
FormattingTags.add_html_tags([tag])
FormattingTags.save_html_tags()
self._reloadTable()
# Highlight new row
self.tag_table_widget.selectRow(self.tag_table_widget.rowCount() - 1)
self.on_row_selected()
new_row = self.tag_table_widget.rowCount()
self.tag_table_widget.insertRow(new_row)
self.tag_table_widget.setItem(new_row, 0,
QtGui.QTableWidgetItem(translate('OpenLP.FormattingTagForm', 'New Tag%s') % str(new_row)))
self.tag_table_widget.setItem(new_row, 1, QtGui.QTableWidgetItem('n%s' % str(new_row)))
self.tag_table_widget.setItem(new_row, 2,
QtGui.QTableWidgetItem(translate('OpenLP.FormattingTagForm', '<HTML here>')))
self.tag_table_widget.setItem(new_row, 3, QtGui.QTableWidgetItem(''))
self.tag_table_widget.resizeRowsToContents()
self.tag_table_widget.scrollToBottom()
self.tag_table_widget.selectRow(new_row)
def on_delete_clicked(self):
"""
Delete selected custom tag.
Delete selected custom row.
"""
if self.selected != -1:
FormattingTags.remove_html_tag(self.selected)
# As the first items are protected we should not have to take care
# of negative indexes causing tracebacks.
self.tag_table_widget.selectRow(self.selected - 1)
self.selected = -1
FormattingTags.save_html_tags()
self._reloadTable()
selected = self.tag_table_widget.currentRow()
if selected != -1:
self.is_deleting = True
self.tag_table_widget.removeRow(selected)
def on_saved_clicked(self):
def accept(self):
"""
Update Custom Tag details if not duplicate and save the data.
"""
html_expands = FormattingTags.get_html_tags()
if self.selected != -1:
html = html_expands[self.selected]
tag = self.tag_line_edit.text()
for linenumber, html1 in enumerate(html_expands):
if self._strip(html1['start tag']) == tag and linenumber != self.selected:
critical_error_message_box(
translate('OpenLP.FormattingTagForm', 'Update Error'),
translate('OpenLP.FormattingTagForm', 'Tag %s already defined.') % tag)
return
html['desc'] = self.description_line_edit.text()
html['start html'] = self.start_tag_line_edit.text()
html['end html'] = self.end_tag_line_edit.text()
html['start tag'] = '{%s}' % tag
html['end tag'] = '{/%s}' % tag
# Keep temporary tags when the user changes one.
html['temporary'] = False
self.selected = -1
FormattingTags.save_html_tags()
self._reloadTable()
count = 0
self.services.pre_save()
while count < self.tag_table_widget.rowCount():
error = self.services.validate_for_save(self.tag_table_widget.item(count, 0).text(),
self.tag_table_widget.item(count, 1).text(), self.tag_table_widget.item(count, 2).text(),
self.tag_table_widget.item(count, 3).text())
if error:
QtGui.QMessageBox.warning(self,
translate('OpenLP.FormattingTagForm', 'Validation Error'), error, QtGui.QMessageBox.Ok)
self.tag_table_widget.selectRow(count)
return
count += 1
self.services.save_tags()
QtGui.QDialog.accept(self)
def _reloadTable(self):
"""
Reset List for loading.
"""
self.reloading = True
self.tag_table_widget_read.clearContents()
self.tag_table_widget_read.setRowCount(0)
self.tag_table_widget.clearContents()
self.tag_table_widget.setRowCount(0)
self.new_push_button.setEnabled(True)
self.save_push_button.setEnabled(False)
self.delete_push_button.setEnabled(False)
self.new_button.setEnabled(True)
self.delete_button.setEnabled(False)
for linenumber, html in enumerate(FormattingTags.get_html_tags()):
self.tag_table_widget.setRowCount(self.tag_table_widget.rowCount() + 1)
self.tag_table_widget.setItem(linenumber, 0, QtGui.QTableWidgetItem(html['desc']))
self.tag_table_widget.setItem(linenumber, 1, QtGui.QTableWidgetItem(self._strip(html['start tag'])))
self.tag_table_widget.setItem(linenumber, 2, QtGui.QTableWidgetItem(html['start html']))
self.tag_table_widget.setItem(linenumber, 3, QtGui.QTableWidgetItem(html['end html']))
# Permanent (persistent) tags do not have this key.
if 'temporary' not in html:
if html['protected']:
line = self.tag_table_widget_read.rowCount()
self.tag_table_widget_read.setRowCount(line + 1)
self.tag_table_widget_read.setItem(line, 0, QtGui.QTableWidgetItem(html['desc']))
self.tag_table_widget_read.setItem(line, 1, QtGui.QTableWidgetItem(self._strip(html['start tag'])))
self.tag_table_widget_read.setItem(line, 2, QtGui.QTableWidgetItem(html['start html']))
self.tag_table_widget_read.setItem(line, 3, QtGui.QTableWidgetItem(html['end html']))
self.tag_table_widget_read.resizeRowsToContents()
else:
line = self.tag_table_widget.rowCount()
self.tag_table_widget.setRowCount(line + 1)
self.tag_table_widget.setItem(line, 0, QtGui.QTableWidgetItem(html['desc']))
self.tag_table_widget.setItem(line, 1, QtGui.QTableWidgetItem(self._strip(html['start tag'])))
self.tag_table_widget.setItem(line, 2, QtGui.QTableWidgetItem(html['start html']))
self.tag_table_widget.setItem(line, 3, QtGui.QTableWidgetItem(html['end html']))
self.tag_table_widget.resizeRowsToContents()
# Permanent (persistent) tags do not have this key
html['temporary'] = False
self.tag_table_widget.resizeRowsToContents()
self.description_line_edit.setText('')
self.tag_line_edit.setText('')
self.start_tag_line_edit.setText('')
self.end_tag_line_edit.setText('')
self.description_line_edit.setEnabled(False)
self.tag_line_edit.setEnabled(False)
self.start_tag_line_edit.setEnabled(False)
self.end_tag_line_edit.setEnabled(False)
self.reloading = False
def _strip(self, tag):
def on_current_cell_changed(self, cur_row, cur_col, pre_row, pre_col):
"""
Remove tag wrappers for editing.
This function processes all user edits in the table. It is called on each cell change.
"""
tag = tag.replace('{', '')
tag = tag.replace('}', '')
return tag
if self.is_deleting:
self.is_deleting = False
return
if self.reloading:
return
# only process for editable rows
if self.tag_table_widget.item(pre_row, 0):
item = self.tag_table_widget.item(pre_row, pre_col)
text = item.text()
errors = None
if pre_col is EditColumn.Description:
if not text:
errors = translate('OpenLP.FormattingTagForm', 'Description is missing')
elif pre_col is EditColumn.Tag:
if not text:
errors = translate('OpenLP.FormattingTagForm', 'Tag is missing')
elif pre_col is EditColumn.StartHtml:
# HTML edited
item = self.tag_table_widget.item(pre_row, 3)
end_html = item.text()
errors, tag = self.services.start_tag_changed(text, end_html)
if tag:
self.tag_table_widget.setItem(pre_row, 3, QtGui.QTableWidgetItem(tag))
self.tag_table_widget.resizeRowsToContents()
elif pre_col is EditColumn.EndHtml:
# HTML edited
item = self.tag_table_widget.item(pre_row, 2)
start_html = item.text()
errors, tag = self.services.end_tag_changed(start_html, text)
if tag:
self.tag_table_widget.setItem(pre_row, 3, QtGui.QTableWidgetItem(tag))
if errors:
QtGui.QMessageBox.warning(self,
translate('OpenLP.FormattingTagForm', 'Validation Error'), errors, QtGui.QMessageBox.Ok)
#self.tag_table_widget.selectRow(pre_row - 1)
self.tag_table_widget.resizeRowsToContents()

View File

@ -692,7 +692,6 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.verse_edit_button.setEnabled(False)
self.verse_delete_button.setEnabled(False)
def on_verse_order_text_changed(self, text):
"""
Checks if the verse order is complete or missing. Shows a error message according to the state of the verse

View File

@ -30,13 +30,14 @@
The :mod:`songshowplusimport` module provides the functionality for importing
SongShow Plus songs into the OpenLP database.
"""
import chardet
import os
import logging
import re
import struct
from openlp.core.ui.wizard import WizardStrings
from openlp.plugins.songs.lib import VerseType
from openlp.plugins.songs.lib import VerseType, retrieve_windows_encoding
from openlp.plugins.songs.lib.songimport import SongImport
TITLE = 1
@ -132,41 +133,43 @@ class SongShowPlusImport(SongImport):
else:
length_descriptor, = struct.unpack("B", song_data.read(1))
log.debug(length_descriptor_size)
data = song_data.read(length_descriptor).decode()
data = song_data.read(length_descriptor)
if block_key == TITLE:
self.title = data
self.title = self.decode(data)
elif block_key == AUTHOR:
authors = data.split(" / ")
authors = self.decode(data).split(" / ")
for author in authors:
if author.find(",") !=-1:
authorParts = author.split(", ")
author = authorParts[1] + " " + authorParts[0]
self.parse_author(author)
elif block_key == COPYRIGHT:
self.addCopyright(data)
self.addCopyright(self.decode(data))
elif block_key == CCLI_NO:
self.ccliNumber = int(data)
elif block_key == VERSE:
self.addVerse(data, "%s%s" % (VerseType.tags[VerseType.Verse], verse_no))
self.addVerse(self.decode(data), "%s%s" % (VerseType.tags[VerseType.Verse], verse_no))
elif block_key == CHORUS:
self.addVerse(data, "%s%s" % (VerseType.tags[VerseType.Chorus], verse_no))
self.addVerse(self.decode(data), "%s%s" % (VerseType.tags[VerseType.Chorus], verse_no))
elif block_key == BRIDGE:
self.addVerse(data, "%s%s" % (VerseType.tags[VerseType.Bridge], verse_no))
self.addVerse(self.decode(data), "%s%s" % (VerseType.tags[VerseType.Bridge], verse_no))
elif block_key == TOPIC:
self.topics.append(data)
self.topics.append(self.decode(data))
elif block_key == COMMENTS:
self.comments = data
self.comments = self.decode(data)
elif block_key == VERSE_ORDER:
verse_tag = self.to_openlp_verse_tag(data, True)
verse_tag = self.to_openlp_verse_tag(self.decode(data), True)
if verse_tag:
if not isinstance(verse_tag, str):
verse_tag = self.decode(verse_tag)
self.ssp_verse_order_list.append(verse_tag)
elif block_key == SONG_BOOK:
self.songBookName = data
self.songBookName = self.decode(data)
elif block_key == SONG_NUMBER:
self.songNumber = ord(data)
elif block_key == CUSTOM_VERSE:
verse_tag = self.to_openlp_verse_tag(verse_name)
self.addVerse(data, verse_tag)
self.addVerse(self.decode(data), verse_tag)
else:
log.debug("Unrecognised blockKey: %s, data: %s" % (block_key, data))
song_data.seek(next_block_starts)
@ -204,3 +207,9 @@ class SongShowPlusImport(SongImport):
verse_tag = VerseType.tags[VerseType.Other]
verse_number = self.other_list[verse_name]
return verse_tag + verse_number
def decode(self, data):
try:
return str(data, chardet.detect(data)['encoding'])
except:
return str(data, retrieve_windows_encoding())

View File

@ -0,0 +1,116 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2013 Raoul Snyman #
# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# 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 #
###############################################################################
"""
Package to test the openlp.core.ui.formattingtagscontroller package.
"""
from unittest import TestCase
from openlp.core.ui import FormattingTagController
class TestFormattingTagController(TestCase):
def setUp(self):
self.services = FormattingTagController()
def test_strip(self):
"""
Test that the _strip strips the correct chars
"""
# GIVEN: An instance of the Formatting Tag Form and a string containing a tag
tag = '{tag}'
# WHEN: Calling _strip
result = self.services._strip(tag)
# THEN: The tag should be returned with the wrappers removed.
self.assertEqual(result, 'tag', 'FormattingTagForm._strip should return u\'tag\' when called with u\'{tag}\'')
def test_end_tag_changed_processes_correctly(self):
"""
Test that the end html tags are generated correctly
"""
# GIVEN: A list of start , end tags and error messages
tests = []
test = {'start': '<b>', 'end': None, 'gen': '</b>', 'valid': None}
tests.append(test)
test = {'start': '<i>', 'end': '</i>', 'gen': None, 'valid': None}
tests.append(test)
test = {'start': '<b>', 'end': '</i>', 'gen': None,
'valid': 'End tag </b> does not match end tag for start tag <b>'}
tests.append(test)
# WHEN: Testing each one of them in turn
for test in tests:
error, result = self.services.end_tag_changed(test['start'], test['end'])
# THEN: The result should match the predetermined value.
self.assertTrue(result == test['gen'],
'Function should handle end tag correctly : %s and %s for %s ' % (test['gen'], result, test['start']))
self.assertTrue(error == test['valid'],
'Function should not generate unexpected error messages : %s ' % error)
def test_start_tag_changed_processes_correctly(self):
"""
Test that the end html tags are generated correctly
"""
# GIVEN: A list of start , end tags and error messages
tests = []
test = {'start': '<b>', 'end': '', 'gen': '</b>', 'valid': None}
tests.append(test)
test = {'start': '<i>', 'end': '</i>', 'gen': None, 'valid': None}
tests.append(test)
test = {'start': 'superfly', 'end': '', 'gen': None, 'valid': 'Start tag superfly is not valid HTML'}
tests.append(test)
# WHEN: Testing each one of them in turn
for test in tests:
error, result = self.services.start_tag_changed(test['start'], test['end'])
# THEN: The result should match the predetermined value.
self.assertTrue(result == test['gen'],
'Function should handle end tag correctly : %s and %s ' % (test['gen'], result))
self.assertTrue(error == test['valid'],
'Function should not generate unexpected error messages : %s ' % error)
def test_start_html_to_end_html(self):
"""
Test that the end html tags are generated correctly
"""
# GIVEN: A list of valid and invalid tags
tests = {'<b>': '</b>', '<i>': '</i>', 'superfly': '', '<HTML START>': None,
'<span style="-webkit-text-fill-color:red">': '</span>'}
# WHEN: Testing each one of them
for test1, test2 in tests.items():
result = self.services.start_html_to_end_html(test1)
# THEN: The result should match the predetermined value.
self.assertTrue(result == test2, 'Calculated end tag should be valid: %s and %s = %s'
% (test1, test2, result))

View File

@ -0,0 +1,77 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2013 Raoul Snyman #
# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# 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 #
###############################################################################
"""
Package to test the openlp.core.ui.formattingtagsform package.
"""
from unittest import TestCase
from tests.functional import MagicMock, patch
from openlp.core.ui.formattingtagform import FormattingTagForm
# TODO: Tests Still TODO
# __init__
# exec_
# on_new_clicked
# on_delete_clicked
# on_saved_clicked
# _reloadTable
class TestFormattingTagForm(TestCase):
def setUp(self):
self.init_patcher = patch('openlp.core.ui.formattingtagform.FormattingTagForm.__init__')
self.qdialog_patcher = patch('openlp.core.ui.formattingtagform.QtGui.QDialog')
self.ui_formatting_tag_dialog_patcher = patch('openlp.core.ui.formattingtagform.Ui_FormattingTagDialog')
self.mocked_init = self.init_patcher.start()
self.mocked_qdialog = self.qdialog_patcher.start()
self.mocked_ui_formatting_tag_dialog = self.ui_formatting_tag_dialog_patcher.start()
self.mocked_init.return_value = None
def tearDown(self):
self.init_patcher.stop()
self.qdialog_patcher.stop()
self.ui_formatting_tag_dialog_patcher.stop()
def test_on_text_edited(self):
"""
Test that the appropriate actions are preformed when on_text_edited is called
"""
# GIVEN: An instance of the Formatting Tag Form and a mocked save_push_button
form = FormattingTagForm()
form.save_button = MagicMock()
# WHEN: on_text_edited is called with an arbitrary value
#form.on_text_edited('text')
# THEN: setEnabled and setDefault should have been called on save_push_button
#form.save_button.setEnabled.assert_called_with(True)

View File

@ -150,35 +150,14 @@ class TestUtils(TestCase):
# THEN: The file name should be cleaned.
self.assertEqual(wanted_name, result, 'The file name should not contain any special characters.')
def get_locale_key_windows_test(self):
def get_locale_key_test(self):
"""
Test the get_locale_key(string) function on Windows
Test the get_locale_key(string) function
"""
with patch('openlp.core.utils.languagemanager.LanguageManager.get_language') as mocked_get_language, \
patch('openlp.core.utils.os') as mocked_os:
with patch('openlp.core.utils.languagemanager.LanguageManager.get_language') as mocked_get_language:
# GIVEN: The language is German
# 0x00C3 (A with diaresis) should be sorted as "A". 0x00DF (sharp s) should be sorted as "ss".
mocked_get_language.return_value = 'de'
mocked_os.name = 'nt'
unsorted_list = ['Auszug', 'Aushang', '\u00C4u\u00DFerung']
# WHEN: We sort the list and use get_locale_key() to generate the sorting keys
sorted_list = sorted(unsorted_list, key=get_locale_key)
# THEN: We get a properly sorted list
self.assertEqual(['Aushang', '\u00C4u\u00DFerung', 'Auszug'], sorted_list,
'Strings should be sorted properly')
def get_locale_key_linux_test(self):
"""
Test the get_locale_key(string) function on Linux
"""
with patch('openlp.core.utils.languagemanager.LanguageManager.get_language') as mocked_get_language, \
patch('openlp.core.utils.os.name') as mocked_os:
# GIVEN: The language is German
# 0x00C3 (A with diaresis) should be sorted as "A". 0x00DF (sharp s) should be sorted as "ss".
mocked_get_language.return_value = 'de'
mocked_os.name = 'linux'
unsorted_list = ['Auszug', 'Aushang', '\u00C4u\u00DFerung']
# WHEN: We sort the list and use get_locale_key() to generate the sorting keys