← Back to team overview

openlp-core team mailing list archive

Re: [Merge] lp:~kirkstover/openlp/wysiwyg into lp:openlp

 

On 6/22/2014 2:55 PM, Tim Bentley wrote:
> Review: Needs Fixing
>
> See Below.
> The editor when display a verse does now manage the size based on the theme
> If using editall there is no clue where paging will happen.
It should show a ghosted background where a new page would be forced. 
Let me know if doesn't appear in your environment.
> You seem the be mixing themes with editing.
> Editing lays out how the text looks irrespective of Theme.
Yes, you are styling the text regardless of theme.
No, the font, font size, margins, outlines, etc. of a theme all affect 
how the text appears and fits on a slide.
I am not editing themes.  I am presenting a visual editor that shows 
exactly how the text will appear for the selected theme.
This removes the seemingly endless process of making changes, previewing 
said changes, and trying again.
Our church projects the entire liturgy, which usually has formatting 
errors (awkward slide breaks, line breaks, missing tags) from not seeing 
what they are creating.
>
> A theme will change this based on it's rules the editor is about laying out the text and should be usable by songs and custom.
I agree that songs could also use the editor.
> If we add images they need to be fixed and work independent of the theme (not a small job).
I guess I don't understand this statement.  Why would they need to 
fixed?  Can't they float just as text currently does?
They should work independent of a given theme.  I added max width and 
height style attributes to the image that are generated at run-time 
based on the theme.  This should guarantee that the
image will work regardless of theme.

Maybe with image support and visual editing, people could limit their 
use of the problematic PowerPoint.
> No need to update old UI files there are for info only.
OK
> Please no not mix changes in one merge request.
> Fixes / changes to Presentations should be separate,
These got included when I did a bzr update, and are not my changes. Sorry.
>
> You will need to add some tests but lets get the scope fixed and agreed.
I hope that we can come to an agreement of what a visual editor means.
>
> Diff comments:
>
>> === modified file 'openlp/.version'
>> --- openlp/.version	2013-08-10 14:51:01 +0000
>> +++ openlp/.version	2014-06-20 18:05:18 +0000
>> @@ -1,1 +1,1 @@
>> -2.1.0-bzr2234
>> +b'2.1.0'-bzrb'2338\r\n'
>> \ No newline at end of file
>>
>> === modified file 'openlp/core/lib/formattingtags.py'
>> --- openlp/core/lib/formattingtags.py	2014-03-17 19:05:55 +0000
>> +++ openlp/core/lib/formattingtags.py	2014-06-20 18:05:18 +0000
>> @@ -157,6 +157,11 @@
>>               'start tag': '{br}', 'start html': '<br>', 'end tag': '',
>>               'end html': '', 'protected': True,
>>               'temporary': False})
>> +        base_tags.append({
>> +            'desc': translate('OpenLP.FormatingTags', 'Image'),
>> +            'start tag': '{img}', 'start html': '<img src=',
>> +            'end tag': '{/img}', 'end html': ' />',
>> +            'protected': True, 'temporary': False})
>>           FormattingTags.add_html_tags(base_tags)
>>           FormattingTags.add_html_tags(temporary_tags)
>>           user_expands_string = str(Settings().value('formattingTags/html_tags'))
>>
>> === modified file 'openlp/core/lib/renderer.py'
>> --- openlp/core/lib/renderer.py	2014-04-29 11:04:19 +0000
>> +++ openlp/core/lib/renderer.py	2014-06-20 18:05:18 +0000
>> @@ -71,6 +71,7 @@
>>           self.web = QtWebKit.QWebView()
>>           self.web.setVisible(False)
>>           self.web_frame = self.web.page().mainFrame()
>> +        self.temp_tags = []
>>           Registry().register_function('theme_update_global', self.set_global_theme)
>>   
>>       def bootstrap_initialise(self):
>> @@ -246,6 +247,8 @@
>>               pages = self._paginate_slide_words(text.split('\n'), line_end)
>>           # Songs and Custom
>>           elif item.is_capable(ItemCapabilities.CanSoftBreak):
>> +            text = self._create_img_tags(text, self.page_width,
>> +                                         self.page_height - (item.theme_data.font_main_size * 2))
>>               pages = []
>>               if '[---]' in text:
>>                   # Remove two or more option slide breaks next to each other (causing infinite loop).
>> @@ -309,6 +312,54 @@
>>               new_pages.append(page)
>>           return new_pages
>>   
>> +    def _create_img_tags(self, text, max_width, max_height):
>> +        """
>> +        Create a temporary image tag for every '{img}' tag in text and changes text
>> +            from: {img}"file:/filename..." style="width:100px;...{/img}
>> +            to: {temp tag}{/temp tag}
>> +        Adds max-width and max-height to style string to always fit within a slide - even when a '\n' is appended
>> +
>> +        :param text: slide text
>> +        :param max_width: maximum width of image
>> +        :param max_height: maximum height of image
>> +        """
>> +        if self.temp_tags:
>> +            self._remove_temp_tags()
>> +        next_tag = 0
>> +        while True:
>> +            start_idx = text.find('{img}')
>> +            if start_idx == -1:
>> +                break
>> +            end_idx = text.find('{/img}')
>> +            next_tag += 1
>> +            new_tag = '{$%d}' % next_tag
>> +            end_tag = '{/$%d}' % next_tag
>> +            tag_html = '<img src=%smax-width:%dpx;max-height:%dpx;"' % (text[start_idx + 5:end_idx - 1],
>> +                                                                        max_width,
>> +                                                                        max_height)
>> +            self.temp_tags.append({'start tag': new_tag,
>> +                                   'desc': 'Temporary Image Tag',
>> +                                   'start html': tag_html,
>> +                                   'end tag': end_tag,
>> +                                   'end html': ' />',
>> +                                   'protected': True,
>> +                                   'temporary': True})
>> +            text = text.replace(text[start_idx:end_idx + 6], new_tag + end_tag, 1)
>> +        if self.temp_tags:
>> +            FormattingTags.add_html_tags(self.temp_tags)
>> +        return text
>> +
>> +    def _remove_temp_tags(self):
>> +        """
> Why here.  The tag handers and in one of the __init__ files.
The temp tags are created on the fly.  Every time an item preview is 
generated, new tags are generated.  It made sense to me to clean them up 
each time.
>
>> +        Remove the temporary image tags from FormattingTags
>> +        """
>> +        for tag in self.temp_tags:
>> +            for idx, html in enumerate(FormattingTags.get_html_tags()):
>> +                if html['temporary'] and html['start tag'] == tag['start tag']:
>> +                    FormattingTags.remove_html_tag(idx)
>> +                    break
>> +        self.temp_tags = []
>> +
>>       def _calculate_default(self):
>>           """
>>           Calculate the default dimensions of the screen.
>>
>> === modified file 'openlp/core/ui/slidecontroller.py'
>> --- openlp/core/ui/slidecontroller.py	2014-05-02 06:33:18 +0000
>> +++ openlp/core/ui/slidecontroller.py	2014-06-20 18:05:18 +0000
>> @@ -1226,7 +1226,7 @@
>>           if event.timerId() == self.timer_id:
>>               self.on_slide_selected_next(self.play_slides_loop.isChecked())
>>   
>> -    def on_edit_song(self):
>> +    def on_edit_song(self, field=None):
>>           """
>>           From the preview display requires the service Item to be editied
>>           """
>> @@ -1235,7 +1235,7 @@
>>           if new_item:
>>               self.add_service_item(new_item)
>>   
>> -    def on_preview_add_to_service(self):
>> +    def on_preview_add_to_service(self, field=None):
>>           """
>>           From the preview display request the Item to be added to service
>>           """
>>
>> === modified file 'openlp/plugins/custom/forms/editcustomform.py'
>> --- openlp/plugins/custom/forms/editcustomform.py	2014-05-01 17:49:43 +0000
>> +++ openlp/plugins/custom/forms/editcustomform.py	2014-06-20 18:05:18 +0000
>> @@ -148,10 +148,24 @@
>>               self.slide_list_view.insertItem(selected_row + 1, qw)
>>               self.slide_list_view.setCurrentRow(selected_row + 1)
>>   
>> +    def update_service_item(self):
>> +        """
>> +        Update the service item to reflect the currently selected theme and credit text.  The service item will be used
>> +        by the HtmlEditor for WYSIWYG display
>> +        """
>> +        item = self.edit_slide_form.service_item
>> +        item.raw_footer = self.credit_edit.text()
>> +        item.update_theme(self.theme_combo_box.currentText())
>> +        theme_data, main, footer = item.renderer.pre_render()
>> +        item.theme_data = theme_data
>> +        item.main = main
>> +        item.footer = footer
>> +
>>       def on_add_button_clicked(self):
>>           """
>>           Add a new blank slide.
>>           """
>> +        self.update_service_item()
>>           self.edit_slide_form.set_text('')
>>           if self.edit_slide_form.exec_():
>>               self.slide_list_view.addItems(self.edit_slide_form.get_text())
>> @@ -160,6 +174,7 @@
>>           """
>>           Edit the currently selected slide.
>>           """
>> +        self.update_service_item()
>>           self.edit_slide_form.set_text(self.slide_list_view.currentItem().text())
>>           if self.edit_slide_form.exec_():
>>               self.update_slide_list(self.edit_slide_form.get_text())
>> @@ -168,6 +183,7 @@
>>           """
>>           Edits all slides.
>>           """
>> +        self.update_service_item()
>>           slide_text = ''
>>           for row in range(self.slide_list_view.count()):
>>               item = self.slide_list_view.item(row)
>>
>> === modified file 'openlp/plugins/custom/forms/editcustomslidedialog.py'
>> --- openlp/plugins/custom/forms/editcustomslidedialog.py	2014-05-03 15:01:43 +0000
>> +++ openlp/plugins/custom/forms/editcustomslidedialog.py	2014-06-20 18:05:18 +0000
>> @@ -27,32 +27,62 @@
>>   # Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
>>   ###############################################################################
>>   
>> -from PyQt4 import QtGui
>> +from PyQt4 import QtCore, QtGui
>>   
>>   from openlp.core.common import UiStrings, translate
>> -from openlp.core.lib import SpellTextEdit, build_icon
>> +from openlp.core.lib import SpellTextEdit
>>   from openlp.core.lib.ui import create_button, create_button_box
>> +from openlp.plugins.custom.lib.htmleditor import HtmlEditor
>>   
>>   
>>   class Ui_CustomSlideEditDialog(object):
>> -    def setupUi(self, custom_slide_edit_dialog):
>> -        custom_slide_edit_dialog.setObjectName('custom_slide_edit_dialog')
>> -        custom_slide_edit_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
>> -        custom_slide_edit_dialog.resize(350, 300)
>> -        self.dialog_layout = QtGui.QVBoxLayout(custom_slide_edit_dialog)
>> -        self.slide_text_edit = SpellTextEdit(self)
>> -        self.slide_text_edit.setObjectName('slide_text_edit')
>> -        self.dialog_layout.addWidget(self.slide_text_edit)
>> -        self.split_button = create_button(custom_slide_edit_dialog, 'splitButton', icon=':/general/general_add.png')
>> -        self.insert_button = create_button(custom_slide_edit_dialog, 'insertButton',
>> -                                           icon=':/general/general_add.png')
>> -        self.button_box = create_button_box(custom_slide_edit_dialog, 'button_box', ['cancel', 'save'],
>> -                                            [self.split_button, self.insert_button])
>> +    def setupUi(self, edit_custom_slide_dialog):
>> +        edit_custom_slide_dialog.setObjectName("edit_custom_slide_dialog")
>> +        edit_custom_slide_dialog.resize(600, 450)
>> +        edit_custom_slide_dialog.setModal(True)
>> +        self.dialog_layout = QtGui.QVBoxLayout(edit_custom_slide_dialog)
>> +        self.dialog_layout.setObjectName("dialog_layout")
>> +        self.editor_tab_widget = QtGui.QTabWidget(edit_custom_slide_dialog)
>> +        self.editor_tab_widget.setObjectName("editor_tab_widget")
>> +        self.editor_tab_widget.setMinimumSize(QtCore.QSize(400, 300))
>> +        self.tag_editor_tab = QtGui.QWidget()
>> +        self.tag_editor_tab.setObjectName("tag_editor_tab")
>> +        self.tag_editor_layout = QtGui.QVBoxLayout(self.tag_editor_tab)
>> +        self.tag_editor_layout.setObjectName("tag_editor_layout")
>> +        self.plain_text_edit = SpellTextEdit(self.tag_editor_tab)
>> +        self.plain_text_edit.setObjectName("plain_text_edit")
>> +        self.tag_editor_layout.addWidget(self.plain_text_edit)
>> +        self.editor_tab_widget.addTab(self.tag_editor_tab, "")
>> +        self.visual_editor_tab = QtGui.QWidget()
>> +        self.visual_editor_tab.setObjectName("visual_editor_tab")
>> +        self.visual_editor_layout = QtGui.QVBoxLayout(self.visual_editor_tab)
>> +        self.visual_editor_layout.setObjectName("visual_editor_layout")
>> +        self.slide_html_edit = HtmlEditor(self.visual_editor_tab)
>> +        self.slide_html_edit.setObjectName("slide_html_edit")
>> +        self.visual_editor_layout.addWidget(self.slide_html_edit)
>> +        self.msg_label = QtGui.QLabel(self.visual_editor_tab)
>> +        self.msg_label.setObjectName("msg_label")
>> +        self.visual_editor_layout.addWidget(self.msg_label)
>> +        self.editor_tab_widget.addTab(self.visual_editor_tab, "")
>> +        self.dialog_layout.addWidget(self.editor_tab_widget)
>> +        spacer_item = QtGui.QSpacerItem(20, 6, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed)
>> +        self.dialog_layout.addItem(spacer_item)
>> +        self.button_layout = QtGui.QHBoxLayout()
>> +        self.button_layout.setObjectName('button_layout')
>> +        self.split_button = create_button(edit_custom_slide_dialog, 'splitButton', icon=':/general/general_add.png')
>> +        self.insert_button = create_button(edit_custom_slide_dialog, 'insertButton', icon=':/general/general_add.png')
>> +        self.button_layout.addWidget(self.split_button)
>> +        self.button_layout.addWidget(self.insert_button)
>> +        self.button_layout.addStretch()
>> +        self.dialog_layout.addLayout(self.button_layout)
>> +        self.button_box = create_button_box(edit_custom_slide_dialog, 'button_box', ['cancel', 'save'])
>>           self.dialog_layout.addWidget(self.button_box)
>> -        self.retranslateUi(custom_slide_edit_dialog)
>> +        self.retranslateUi(edit_custom_slide_dialog)
>>   
>> -    def retranslateUi(self, custom_slide_edit_dialog):
>> -        custom_slide_edit_dialog.setWindowTitle(translate('CustomPlugin.EditVerseForm', 'Edit Slide'))
>> +    def retranslateUi(self, edit_custom_slide_dialog):
>> +        edit_custom_slide_dialog.setWindowTitle(translate('CustomPlugin.EditCustomForm', "Edit Slide"))
>> +        self.editor_tab_widget.setTabText(0, translate('CustomPlugin.EditCustomForm', "Tag Editor"))
>> +        self.editor_tab_widget.setTabText(1, translate('CustomPlugin.EditCustomForm', "Visual Editor"))
>>           self.split_button.setText(UiStrings().Split)
>>           self.split_button.setToolTip(UiStrings().SplitToolTip)
>>           self.insert_button.setText(translate('CustomPlugin.EditCustomForm', 'Insert Slide'))
>>
>> === modified file 'openlp/plugins/custom/forms/editcustomslideform.py'
>> --- openlp/plugins/custom/forms/editcustomslideform.py	2014-04-12 20:19:22 +0000
>> +++ openlp/plugins/custom/forms/editcustomslideform.py	2014-06-20 18:05:18 +0000
>> @@ -28,9 +28,8 @@
>>   ###############################################################################
>>   
>>   import logging
>> -
>> -from PyQt4 import QtGui
>> -
>> +from PyQt4 import QtGui, QtCore
>> +from openlp.core.common import Registry, translate
>>   from .editcustomslidedialog import Ui_CustomSlideEditDialog
>>   
>>   log = logging.getLogger(__name__)
>> @@ -40,7 +39,7 @@
>>       """
>>       Class documentation goes here.
>>       """
>> -    log.info('Custom Verse Editor loaded')
>> +    log.info('Custom Slide Editor loaded')
>>   
>>       def __init__(self, parent=None):
>>           """
>> @@ -48,40 +47,103 @@
>>           """
>>           super(EditCustomSlideForm, self).__init__(parent)
>>           self.setupUi(self)
>> +        self.service_item = None
> Service items are for servicing and not helping editors.
> Editors work of the saved items.
This was done to maximize the reuse of the code base.  The editor may 
not always be able to work off of a saved item.  For example, the theme 
was changed, but not saved on the editcustomform,
and edit was clicked.

The service item was already created in the custom/mediaitem, I'm just 
making it available to the editor so it can use themedata info, and 
utilize the htmlbuilder.
It would be crazy to try to duplicate the existing functionality.
>
>> +        self.background_message_shown = False             # Only show the background image message once
>>           # Connecting signals and slots
>>           self.insert_button.clicked.connect(self.on_insert_button_clicked)
>>           self.split_button.clicked.connect(self.on_split_button_clicked)
>> +        self.connect(self.slide_html_edit, QtCore.SIGNAL('selectionChanged()'), self.on_selection_changed)
>> +        self.connect(self.editor_tab_widget, QtCore.SIGNAL('currentChanged(int)'), self.on_tab_changed)
>>   
>>       def set_text(self, text):
>>           """
>> -        Set the text for slide_text_edit.
>> +        Set the text for plain_text_edit or the slide_html_edit depending on current editor tab
>>   
>>           :param text: The text (unicode).
>>           """
>> -        self.slide_text_edit.clear()
>> -        if text:
>> -            self.slide_text_edit.setPlainText(text)
>> -        self.slide_text_edit.setFocus()
>> +        if self.editor_tab_widget.currentIndex() == 0:
>> +            self.plain_text_edit.clear()
>> +            if text:
>> +                self.plain_text_edit.setPlainText(text)
>> +            self.plain_text_edit.setFocus()
>> +        else:
>> +            self.slide_html_edit.set_text(text, self.service_item)
>> +            self.slide_html_edit.setFocus()
>>   
>>       def get_text(self):
>>           """
>>           Returns a list with all slides.
>>           """
>> -        return self.slide_text_edit.toPlainText().split('\n[===]\n')
>> +        if self.editor_tab_widget.currentIndex() == 0:
>> +            text = self.plain_text_edit.toPlainText()
>> +        else:
>> +            text = self.slide_html_edit.get_text()
>> +        return text.split('\n[===]\n')
>> +
>> +    def on_tab_changed(self, curr_tab):
>> +        """
>> +        Tab changed signal
>> +
>> +        :param curr_tab: Index of the current tab
>> +        """
>> +        if curr_tab == 0:
>> +            self.plain_text_edit.setPlainText(self.slide_html_edit.get_text())
>> +            self.slide_html_edit.hide()
>> +            self.plain_text_edit.show()
>> +            self.plain_text_edit.setFocus()
>> +        else:
>> +            self.slide_html_edit.set_text(self.plain_text_edit.toPlainText(), self.service_item)
>> +            self.plain_text_edit.hide()
>> +            self.slide_html_edit.show()
>> +            self.slide_html_edit.setFocus()
>> +
>> +    def on_selection_changed(self):
>> +        """
>> +        Selection changed signal
>> +        """
>> +        msg = '<strong>%s: </strong>' % translate('CustomPlugin.EditCustomSlideForm', 'Path')
>> +        if self.slide_html_edit.selectedHtml() == '':
>> +            path = self.slide_html_edit.get_path()
>> +            if path:
>> +                msg += path.replace(',', ' ')
>> +            else:
>> +                msg += '{}'
>> +        else:
>> +            msg += '(%s)' % translate('CustomPlugin.EditCustomSlideForm', 'selection')
>> +        msg += ' <strong>%s: </strong>%d%%' % (translate('CustomPlugin.EditCustomSlideForm', 'Zoom'),
>> +                                               int(self.slide_html_edit.zoomFactor() * 100))
>> +        if self.slide_html_edit.zoomFactor() != 1.0:
>> +            msg += ' (%s)' % translate('CustomPlugin.EditCustomSlideForm', 'use actual size for true representation')
>> +        self.msg_label.setText(msg)
>> +        if not self.background_message_shown and self.slide_html_edit.has_background_image():
>> +            self.background_message_shown = True
>> +            caption = translate('CustomPlugin.EditCustomSlideForm', 'Background Image')
>> +            msg = translate('CustomPlugin.EditCustomSlideForm', 'This slide contains a background image.\n\n'
>> +                                                                'To select a background image, hold down the CTRL key '
>> +                                                                'while clicking the image.')
>> +            Registry().get('main_window').information_message(caption, msg)
>>   
>>       def on_insert_button_clicked(self):
>>           """
>>           Adds a slide split at the cursor.
>>           """
>> -        self.insert_single_line_text_at_cursor('[===]')
>> -        self.slide_text_edit.setFocus()
>> +        if self.editor_tab_widget.currentIndex() == 0:
>> +            self.insert_single_line_text_at_cursor('[===]')
>> +            self.plain_text_edit.setFocus()
>> +        else:
>> +            self.slide_html_edit.insert()
>> +            self.slide_html_edit.setFocus()
>>   
>>       def on_split_button_clicked(self):
>>           """
>>           Adds an optional split at cursor.
>>           """
>> -        self.insert_single_line_text_at_cursor('[---]')
>> -        self.slide_text_edit.setFocus()
>> +        if self.editor_tab_widget.currentIndex() == 0:
>> +            self.insert_single_line_text_at_cursor('[---]')
>> +            self.plain_text_edit.setFocus()
>> +        else:
>> +            self.slide_html_edit.split()
>> +            self.slide_html_edit.setFocus()
>>   
>>       def insert_single_line_text_at_cursor(self, text):
>>           """
>> @@ -89,10 +151,10 @@
>>   
>>           :param text: The text to be inserted
>>           """
>> -        full_text = self.slide_text_edit.toPlainText()
>> -        position = self.slide_text_edit.textCursor().position()
>> +        full_text = self.plain_text_edit.toPlainText()
>> +        position = self.plain_text_edit.textCursor().position()
>>           if position and full_text[position - 1] != '\n':
>>               text = '\n' + text
>>           if position == len(full_text) or full_text[position] != '\n':
>>               text += '\n'
>> -        self.slide_text_edit.insertPlainText(text)
>> +        self.plain_text_edit.insertPlainText(text)
>>
>> === added file 'openlp/plugins/custom/forms/editimagedialog.py'
>> --- openlp/plugins/custom/forms/editimagedialog.py	1970-01-01 00:00:00 +0000
>> +++ openlp/plugins/custom/forms/editimagedialog.py	2014-06-20 18:05:18 +0000
>> @@ -0,0 +1,345 @@
>> +# -*- coding: utf-8 -*-
>> +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
>> +
>> +###############################################################################
>> +# OpenLP - Open Source Lyrics Projection                                      #
>> +# --------------------------------------------------------------------------- #
>> +# Copyright (c) 2008-2014 Raoul Snyman                                        #
>> +# Portions copyright (c) 2008-2014 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                          #
>> +###############################################################################
>> +
>> +from PyQt4 import QtCore, Qt, QtGui
>> +
>> +from openlp.core.lib import translate
>> +from openlp.core.lib.ui import create_button_box
>> +
>> +
>> +class Ui_EditImageDialog(object):
>> +    def setupUi(self, edit_image_dialog):
>> +        edit_image_dialog.setObjectName("edit_image_dialog")
>> +        edit_image_dialog.resize(589, 327)
>> +        edit_image_dialog.setModal(True)
>> +        self.dialog_layout = QtGui.QVBoxLayout(edit_image_dialog)
>> +        self.dialog_layout.setSpacing(12)
>> +        self.dialog_layout.setObjectName("dialog_layout")
>> +        # image group box
>> +        self.image_group_box = QtGui.QGroupBox(edit_image_dialog)
>> +        size_policy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Preferred)
>> +        size_policy.setHorizontalStretch(0)
>> +        size_policy.setVerticalStretch(0)
>> +        size_policy.setHeightForWidth(self.image_group_box.sizePolicy().hasHeightForWidth())
>> +        self.image_group_box.setSizePolicy(size_policy)
>> +        self.image_group_box.setObjectName("image_group_box")
>> +        self.image_layout = QtGui.QHBoxLayout(self.image_group_box)
>> +        self.image_layout.setSpacing(8)
>> +        self.image_layout.setObjectName("image_layout")
>> +        self.thumbnail_label = QtGui.QLabel(self.image_group_box)
>> +        size_policy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
>> +        size_policy.setHorizontalStretch(0)
>> +        size_policy.setVerticalStretch(0)
>> +        size_policy.setHeightForWidth(self.thumbnail_label.sizePolicy().hasHeightForWidth())
>> +        self.thumbnail_label.setSizePolicy(size_policy)
>> +        self.thumbnail_label.setMinimumSize(QtCore.QSize(48, 48))
>> +        self.thumbnail_label.setMaximumSize(QtCore.QSize(48, 48))
>> +        self.thumbnail_label.setObjectName("thumbnail_label")
>> +        self.image_layout.addWidget(self.thumbnail_label)
>> +        self.image_line_edit = QtGui.QLineEdit(self.image_group_box)
>> +        self.image_line_edit.setReadOnly(True)
>> +        self.image_line_edit.setObjectName("image_line_edit")
>> +        self.image_layout.addWidget(self.image_line_edit)
>> +        self.image_push_button = QtGui.QPushButton(self.image_group_box)
>> +        size_policy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed)
>> +        size_policy.setHorizontalStretch(0)
>> +        size_policy.setVerticalStretch(0)
>> +        size_policy.setHeightForWidth(self.image_push_button.sizePolicy().hasHeightForWidth())
>> +        self.image_push_button.setSizePolicy(size_policy)
>> +        self.image_push_button.setObjectName("image_push_button")
>> +        self.image_layout.addWidget(self.image_push_button)
>> +        self.dialog_layout.addWidget(self.image_group_box)
>> +        # properties layout - contains size, style, spacing and border group boxes
>> +        self.properties_layout = QtGui.QHBoxLayout()
>> +        self.properties_layout.setSpacing(12)
>> +        self.properties_layout.setContentsMargins(-1, 12, -1, 0)
>> +        self.properties_layout.setObjectName("properties_layout")
>> +        # size group box
>> +        self.size_group_box = QtGui.QGroupBox(edit_image_dialog)
>> +        size_policy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.MinimumExpanding)
>> +        size_policy.setHorizontalStretch(0)
>> +        size_policy.setVerticalStretch(0)
>> +        size_policy.setHeightForWidth(self.size_group_box.sizePolicy().hasHeightForWidth())
>> +        self.size_group_box.setSizePolicy(size_policy)
>> +        self.size_group_box.setObjectName("size_group_box")
>> +        self.size_layout = QtGui.QGridLayout(self.size_group_box)
>> +        self.size_layout.setVerticalSpacing(10)
>> +        self.size_layout.setObjectName("size_layout")
>> +        self.width_label = QtGui.QLabel(self.size_group_box)
>> +        self.width_label.setObjectName("width_label")
>> +        self.size_layout.addWidget(self.width_label, 0, 0, 1, 1)
>> +        self.width_spin_box = QtGui.QSpinBox(self.size_group_box)
>> +        size_policy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed)
>> +        size_policy.setHorizontalStretch(0)
>> +        size_policy.setVerticalStretch(0)
>> +        size_policy.setHeightForWidth(self.width_spin_box.sizePolicy().hasHeightForWidth())
>> +        self.width_spin_box.setSizePolicy(size_policy)
>> +        self.width_spin_box.setMinimum(1)
>> +        self.width_spin_box.setMaximum(9999)
>> +        self.width_spin_box.setProperty("value", 9999)
>> +        self.width_spin_box.setObjectName("width_spin_box")
>> +        self.size_layout.addWidget(self.width_spin_box, 0, 1, 1, 1)
>> +        self.height_label = QtGui.QLabel(self.size_group_box)
>> +        self.height_label.setObjectName("height_label")
>> +        self.size_layout.addWidget(self.height_label, 1, 0, 1, 1)
>> +        self.height_spin_box = QtGui.QSpinBox(self.size_group_box)
>> +        self.height_spin_box.setMinimum(1)
>> +        self.height_spin_box.setMaximum(9999)
>> +        self.height_spin_box.setProperty("value", 9999)
>> +        self.height_spin_box.setObjectName("height_spin_box")
>> +        self.size_layout.addWidget(self.height_spin_box, 1, 1, 1, 1)
>> +        self.proportional_check_box = QtGui.QCheckBox(self.size_group_box)
>> +        self.proportional_check_box.setObjectName("proportional_check_box")
>> +        self.size_layout.addWidget(self.proportional_check_box, 2, 1, 1, 1)
>> +        self.reset_push_button = QtGui.QPushButton(self.size_group_box)
>> +        size_policy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed)
>> +        size_policy.setHorizontalStretch(0)
>> +        size_policy.setVerticalStretch(0)
>> +        size_policy.setHeightForWidth(self.reset_push_button.sizePolicy().hasHeightForWidth())
>> +        self.reset_push_button.setSizePolicy(size_policy)
>> +        self.reset_push_button.setObjectName("reset_push_button")
>> +        self.size_layout.addWidget(self.reset_push_button, 3, 0, 1, 2)
>> +        self.properties_layout.addWidget(self.size_group_box)
>> +        # style group box
>> +        self.style_group_box = QtGui.QGroupBox(edit_image_dialog)
>> +        size_policy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Preferred)
>> +        size_policy.setHorizontalStretch(0)
>> +        size_policy.setVerticalStretch(0)
>> +        size_policy.setHeightForWidth(self.style_group_box.sizePolicy().hasHeightForWidth())
>> +        self.style_group_box.setSizePolicy(size_policy)
>> +        self.style_group_box.setObjectName("style_group_box")
>> +        self.style_layout = QtGui.QGridLayout(self.style_group_box)
>> +        self.style_layout.setVerticalSpacing(10)
>> +        self.style_layout.setObjectName("style_layout")
>> +        self.align_label = QtGui.QLabel(self.style_group_box)
>> +        self.align_label.setObjectName("align_label")
>> +        self.style_layout.addWidget(self.align_label, 0, 0, 1, 1)
>> +        self.align_combo_box = QtGui.QComboBox(self.style_group_box)
>> +        self.align_combo_box.setObjectName("align_combo_box")
>> +        self.align_combo_box.addItem("")
>> +        self.align_combo_box.addItem("")
>> +        self.align_combo_box.addItem("")
>> +        self.align_combo_box.addItem("")
>> +        self.align_combo_box.addItem("")
>> +        self.align_combo_box.addItem("")
>> +        self.align_combo_box.addItem("")
>> +        self.align_combo_box.addItem("")
>> +        self.align_combo_box.addItem("")
>> +        self.style_layout.addWidget(self.align_combo_box, 0, 1, 1, 1)
>> +        self.opacity_label = QtGui.QLabel(self.style_group_box)
>> +        self.opacity_label.setObjectName("opacity_label")
>> +        self.style_layout.addWidget(self.opacity_label, 1, 0, 1, 1)
>> +        self.opacity_horizontal_slider = QtGui.QSlider(self.style_group_box)
>> +        self.opacity_horizontal_slider.setMinimum(10)
>> +        self.opacity_horizontal_slider.setMaximum(100)
>> +        self.opacity_horizontal_slider.setSingleStep(10)
>> +        self.opacity_horizontal_slider.setProperty("value", 100)
>> +        self.opacity_horizontal_slider.setSliderPosition(100)
>> +        self.opacity_horizontal_slider.setOrientation(QtCore.Qt.Horizontal)
>> +        self.opacity_horizontal_slider.setInvertedAppearance(False)
>> +        self.opacity_horizontal_slider.setTickPosition(QtGui.QSlider.NoTicks)
>> +        self.opacity_horizontal_slider.setTickInterval(10)
>> +        self.opacity_horizontal_slider.setObjectName("opacity_horizontal_slider")
>> +        self.style_layout.addWidget(self.opacity_horizontal_slider, 1, 1, 1, 1)
>> +        self.shadow_label = QtGui.QLabel(self.style_group_box)
>> +        self.shadow_label.setObjectName("shadow_label")
>> +        self.style_layout.addWidget(self.shadow_label, 2, 0, 1, 1)
>> +        self.shadow_horizontal_slider = QtGui.QSlider(self.style_group_box)
>> +        self.shadow_horizontal_slider.setMaximum(50)
>> +        self.shadow_horizontal_slider.setOrientation(QtCore.Qt.Horizontal)
>> +        self.shadow_horizontal_slider.setTickPosition(QtGui.QSlider.NoTicks)
>> +        self.shadow_horizontal_slider.setTickInterval(5)
>> +        self.shadow_horizontal_slider.setObjectName("shadow_horizontal_slider")
>> +        self.style_layout.addWidget(self.shadow_horizontal_slider, 2, 1, 1, 1)
>> +        self.blur_label = QtGui.QLabel(self.style_group_box)
>> +        self.blur_label.setObjectName("blur_label")
>> +        self.style_layout.addWidget(self.blur_label, 3, 0, 1, 1)
>> +        self.blur_horizontal_slider = QtGui.QSlider(self.style_group_box)
>> +        self.blur_horizontal_slider.setMaximum(50)
>> +        self.blur_horizontal_slider.setOrientation(QtCore.Qt.Horizontal)
>> +        self.blur_horizontal_slider.setTickPosition(QtGui.QSlider.NoTicks)
>> +        self.blur_horizontal_slider.setTickInterval(5)
>> +        self.blur_horizontal_slider.setObjectName("blur_horizontal_slider")
>> +        self.style_layout.addWidget(self.blur_horizontal_slider, 3, 1, 1, 1)
>> +        self.properties_layout.addWidget(self.style_group_box)
>> +        # spacing group box
>> +        self.spacing_group_box = QtGui.QGroupBox(edit_image_dialog)
>> +        size_policy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Preferred)
>> +        size_policy.setHorizontalStretch(0)
>> +        size_policy.setVerticalStretch(0)
>> +        size_policy.setHeightForWidth(self.spacing_group_box.sizePolicy().hasHeightForWidth())
>> +        self.spacing_group_box.setSizePolicy(size_policy)
>> +        self.spacing_group_box.setObjectName("spacing_group_box")
>> +        self.spacing_layout = QtGui.QGridLayout(self.spacing_group_box)
>> +        self.spacing_layout.setVerticalSpacing(10)
>> +        self.spacing_layout.setObjectName("spacing_layout")
>> +        self.top_spin_box = QtGui.QSpinBox(self.spacing_group_box)
>> +        self.top_spin_box.setMaximum(9999)
>> +        self.top_spin_box.setProperty("value", 9999)
>> +        self.top_spin_box.setObjectName("top_spin_box")
>> +        self.spacing_layout.addWidget(self.top_spin_box, 6, 1, 1, 1)
>> +        self.top_label = QtGui.QLabel(self.spacing_group_box)
>> +        self.top_label.setObjectName("top_label")
>> +        self.spacing_layout.addWidget(self.top_label, 6, 0, 1, 1)
>> +        self.right_spin_box = QtGui.QSpinBox(self.spacing_group_box)
>> +        self.right_spin_box.setMaximum(9999)
>> +        self.right_spin_box.setProperty("value", 9999)
>> +        self.right_spin_box.setObjectName("right_spin_box")
>> +        self.spacing_layout.addWidget(self.right_spin_box, 2, 1, 1, 1)
>> +        self.bottom_label = QtGui.QLabel(self.spacing_group_box)
>> +        self.bottom_label.setObjectName("bottom_label")
>> +        self.spacing_layout.addWidget(self.bottom_label, 7, 0, 1, 1)
>> +        self.bottom_spin_box = QtGui.QSpinBox(self.spacing_group_box)
>> +        self.bottom_spin_box.setMaximum(9999)
>> +        self.bottom_spin_box.setProperty("value", 9999)
>> +        self.bottom_spin_box.setObjectName("bottom_spin_box")
>> +        self.spacing_layout.addWidget(self.bottom_spin_box, 7, 1, 1, 1)
>> +        self.right_label = QtGui.QLabel(self.spacing_group_box)
>> +        self.right_label.setObjectName("right_label")
>> +        self.spacing_layout.addWidget(self.right_label, 2, 0, 1, 1)
>> +        self.left_label = QtGui.QLabel(self.spacing_group_box)
>> +        self.left_label.setObjectName("left_label")
>> +        self.spacing_layout.addWidget(self.left_label, 1, 0, 1, 1)
>> +        self.left_spin_box = QtGui.QSpinBox(self.spacing_group_box)
>> +        self.left_spin_box.setMaximum(9999)
>> +        self.left_spin_box.setProperty("value", 9999)
>> +        self.left_spin_box.setObjectName("left_spin_box")
>> +        self.spacing_layout.addWidget(self.left_spin_box, 1, 1, 1, 1)
>> +        self.properties_layout.addWidget(self.spacing_group_box)
>> +        # border group box
>> +        self.border_group_box = QtGui.QGroupBox(edit_image_dialog)
>> +        size_policy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Preferred)
>> +        size_policy.setHorizontalStretch(0)
>> +        size_policy.setVerticalStretch(0)
>> +        size_policy.setHeightForWidth(self.border_group_box.sizePolicy().hasHeightForWidth())
>> +        self.border_group_box.setSizePolicy(size_policy)
>> +        self.border_group_box.setObjectName("border_group_box")
>> +        self.border_layout = QtGui.QGridLayout(self.border_group_box)
>> +        self.border_layout.setVerticalSpacing(10)
>> +        self.border_layout.setObjectName("border_layout")
>> +        self.border_color_frame = QtGui.QFrame(self.border_group_box)
>> +        size_policy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Fixed)
>> +        size_policy.setHorizontalStretch(0)
>> +        size_policy.setVerticalStretch(0)
>> +        size_policy.setHeightForWidth(self.border_color_frame.sizePolicy().hasHeightForWidth())
>> +        self.border_color_frame.setSizePolicy(size_policy)
>> +        self.border_color_frame.setMinimumSize(QtCore.QSize(16, 16))
>> +        self.border_color_frame.setStyleSheet("background-color: #FF0000;")
>> +        self.border_color_frame.setFrameShape(QtGui.QFrame.StyledPanel)
>> +        self.border_color_frame.setFrameShadow(QtGui.QFrame.Sunken)
>> +        self.border_color_frame.setObjectName("border_color_frame")
>> +        self.border_layout.addWidget(self.border_color_frame, 2, 0, 1, 1)
>> +        self.border_type_label = QtGui.QLabel(self.border_group_box)
>> +        self.border_type_label.setObjectName("border_type_label")
>> +        self.border_layout.addWidget(self.border_type_label, 0, 0, 1, 1)
>> +        self.border_color_push_button = QtGui.QPushButton(self.border_group_box)
>> +        self.border_color_push_button.setObjectName("border_color_push_button")
>> +        self.border_layout.addWidget(self.border_color_push_button, 2, 1, 1, 1)
>> +        self.radius_horizontal_slider = QtGui.QSlider(self.border_group_box)
>> +        self.radius_horizontal_slider.setMaximum(50)
>> +        self.radius_horizontal_slider.setPageStep(5)
>> +        self.radius_horizontal_slider.setOrientation(QtCore.Qt.Horizontal)
>> +        self.radius_horizontal_slider.setObjectName("radius_horizontal_slider")
>> +        self.border_layout.addWidget(self.radius_horizontal_slider, 4, 1, 1, 1)
>> +        self.radius_label = QtGui.QLabel(self.border_group_box)
>> +        self.radius_label.setObjectName("radius_label")
>> +        self.border_layout.addWidget(self.radius_label, 4, 0, 1, 1)
>> +        self.border_type_combo_box = QtGui.QComboBox(self.border_group_box)
>> +        self.border_type_combo_box.setObjectName("border_type_combo_box")
>> +        self.border_type_combo_box.addItem("")
>> +        self.border_type_combo_box.addItem("")
>> +        self.border_type_combo_box.addItem("")
>> +        self.border_type_combo_box.addItem("")
>> +        self.border_type_combo_box.addItem("")
>> +        self.border_type_combo_box.addItem("")
>> +        self.border_type_combo_box.addItem("")
>> +        self.border_type_combo_box.addItem("")
>> +        self.border_type_combo_box.addItem("")
>> +        self.border_layout.addWidget(self.border_type_combo_box, 0, 1, 1, 1)
>> +        self.border_width_label = QtGui.QLabel(self.border_group_box)
>> +        self.border_width_label.setObjectName("border_width_label")
>> +        self.border_layout.addWidget(self.border_width_label, 3, 0, 1, 1)
>> +        self.border_width_horizontal_slider = QtGui.QSlider(self.border_group_box)
>> +        self.border_width_horizontal_slider.setMinimum(1)
>> +        self.border_width_horizontal_slider.setMaximum(50)
>> +        self.border_width_horizontal_slider.setPageStep(5)
>> +        self.border_width_horizontal_slider.setOrientation(QtCore.Qt.Horizontal)
>> +        self.border_width_horizontal_slider.setObjectName("border_width_horizontal_slider")
>> +        self.border_layout.addWidget(self.border_width_horizontal_slider, 3, 1, 1, 1)
>> +        self.properties_layout.addWidget(self.border_group_box)
>> +        self.dialog_layout.addLayout(self.properties_layout)
>> +        spacerItem = QtGui.QSpacerItem(20, 12, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed)
>> +        self.dialog_layout.addItem(spacerItem)
>> +        # button box
>> +        self.button_box = create_button_box(edit_image_dialog, 'button_box', ['cancel', 'ok'])
>> +        self.dialog_layout.addWidget(self.button_box)
>> +        self.retranslateUi(edit_image_dialog)
>> +
>> +    def retranslateUi(self, edit_image_dialog):
>> +        edit_image_dialog.setWindowTitle(translate('CustomPlugin.EditImageForm', "Edit Image"))
>> +        self.image_group_box.setTitle(translate('CustomPlugin.EditImageForm', "Image"))
>> +        self.image_push_button.setText(translate('CustomPlugin.EditImageForm', "Select Image..."))
>> +        self.size_group_box.setTitle(translate('CustomPlugin.EditImageForm', "Size"))
>> +        self.width_label.setText(translate('CustomPlugin.EditImageForm', "Width"))
>> +        self.height_label.setText(translate('CustomPlugin.EditImageForm', "Height"))
>> +        self.proportional_check_box.setText(translate('CustomPlugin.EditImageForm', "Proportional"))
>> +        self.reset_push_button.setText(translate('CustomPlugin.EditImageForm', "Reset"))
>> +        self.style_group_box.setTitle(translate('CustomPlugin.EditImageForm', "Style"))
>> +        self.align_label.setText(translate('CustomPlugin.EditImageForm', "Align"))
>> +        self.align_combo_box.setItemText(0, translate('CustomPlugin.EditImageForm', "None"))
>> +        self.align_combo_box.setItemText(1, translate('CustomPlugin.EditImageForm', "Left"))
>> +        self.align_combo_box.setItemText(2, translate('CustomPlugin.EditImageForm', "Right"))
>> +        self.align_combo_box.setItemText(3, translate('CustomPlugin.EditImageForm', "Center"))
>> +        self.align_combo_box.setItemText(4, translate('CustomPlugin.EditImageForm', "Block"))
>> +        self.align_combo_box.setItemText(5, translate('CustomPlugin.EditImageForm', "Text Top"))
>> +        self.align_combo_box.setItemText(6, translate('CustomPlugin.EditImageForm', "Text Middle"))
>> +        self.align_combo_box.setItemText(7, translate('CustomPlugin.EditImageForm', "Text Bottom"))
>> +        self.align_combo_box.setItemText(8, translate('CustomPlugin.EditImageForm', "Background"))
>> +        self.opacity_label.setText(translate('CustomPlugin.EditImageForm', "Opacity"))
>> +        self.shadow_label.setText(translate('CustomPlugin.EditImageForm', "Shadow"))
>> +        self.blur_label.setText(translate('CustomPlugin.EditImageForm', "Blur"))
>> +        self.spacing_group_box.setTitle(translate('CustomPlugin.EditImageForm', "Spacing"))
>> +        self.top_label.setText(translate('CustomPlugin.EditImageForm', "Top"))
>> +        self.right_label.setText(translate('CustomPlugin.EditImageForm', "Right"))
>> +        self.bottom_label.setText(translate('CustomPlugin.EditImageForm', "Bottom"))
>> +        self.left_label.setText(translate('CustomPlugin.EditImageForm', "Left"))
>> +        self.border_group_box.setTitle(translate('CustomPlugin.EditImageForm', "Border"))
>> +        self.border_type_label.setText(translate('CustomPlugin.EditImageForm', "Type"))
>> +        self.border_type_combo_box.setItemText(0, translate('CustomPlugin.EditImageForm', "None"))
>> +        self.border_type_combo_box.setItemText(1, translate('CustomPlugin.EditImageForm', "Solid"))
>> +        self.border_type_combo_box.setItemText(2, translate('CustomPlugin.EditImageForm', "Dotted"))
>> +        self.border_type_combo_box.setItemText(3, translate('CustomPlugin.EditImageForm', "Dashed"))
>> +        self.border_type_combo_box.setItemText(4, translate('CustomPlugin.EditImageForm', "Double"))
>> +        self.border_type_combo_box.setItemText(5, translate('CustomPlugin.EditImageForm', "Groove"))
>> +        self.border_type_combo_box.setItemText(6, translate('CustomPlugin.EditImageForm', "Ridge"))
>> +        self.border_type_combo_box.setItemText(7, translate('CustomPlugin.EditImageForm', "Inset"))
>> +        self.border_type_combo_box.setItemText(8, translate('CustomPlugin.EditImageForm', "Outset"))
>> +        self.border_width_label.setText(translate('CustomPlugin.EditImageForm', "Width"))
>> +        self.border_color_push_button.setText(translate('CustomPlugin.EditImageForm', "Color..."))
>> +        self.radius_label.setText(translate('CustomPlugin.EditImageForm', "Radius"))
>>
>> === added file 'openlp/plugins/custom/forms/editimagedialog.ui'
>> --- openlp/plugins/custom/forms/editimagedialog.ui	1970-01-01 00:00:00 +0000
>> +++ openlp/plugins/custom/forms/editimagedialog.ui	2014-06-20 18:05:18 +0000
>> @@ -0,0 +1,605 @@
>> +<?xml version="1.0" encoding="UTF-8"?>
>> +<ui version="4.0">
>> + <class>EditImageDialog</class>
>> + <widget class="QDialog" name="EditImageDialog">
>> +  <property name="geometry">
>> +   <rect>
>> +    <x>0</x>
>> +    <y>0</y>
>> +    <width>629</width>
>> +    <height>308</height>
>> +   </rect>
>> +  </property>
>> +  <property name="windowTitle">
>> +   <string>Dialog</string>
>> +  </property>
>> +  <layout class="QVBoxLayout" name="verticalLayout">
>> +   <item>
>> +    <widget class="QGroupBox" name="ImageGroupBox">
>> +     <property name="sizePolicy">
>> +      <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
>> +       <horstretch>0</horstretch>
>> +       <verstretch>0</verstretch>
>> +      </sizepolicy>
>> +     </property>
>> +     <property name="title">
>> +      <string>Image</string>
>> +     </property>
>> +     <layout class="QHBoxLayout" name="horizontalLayout">
>> +      <item>
>> +       <layout class="QHBoxLayout" name="ImageLayout">
>> +        <property name="spacing">
>> +         <number>8</number>
>> +        </property>
>> +        <property name="margin">
>> +         <number>8</number>
>> +        </property>
>> +        <item>
>> +         <widget class="QLineEdit" name="ImageLineEdit"/>
>> +        </item>
>> +        <item>
>> +         <widget class="QPushButton" name="ImagePushButton">
>> +          <property name="sizePolicy">
>> +           <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
>> +            <horstretch>0</horstretch>
>> +            <verstretch>0</verstretch>
>> +           </sizepolicy>
>> +          </property>
>> +          <property name="text">
>> +           <string>Select Image...</string>
>> +          </property>
>> +         </widget>
>> +        </item>
>> +       </layout>
>> +      </item>
>> +     </layout>
>> +    </widget>
>> +   </item>
>> +   <item>
>> +    <layout class="QHBoxLayout" name="PropertiesLayout" stretch="0,0,0,0">
>> +     <property name="spacing">
>> +      <number>8</number>
>> +     </property>
>> +     <property name="topMargin">
>> +      <number>12</number>
>> +     </property>
>> +     <property name="bottomMargin">
>> +      <number>0</number>
>> +     </property>
>> +     <item>
>> +      <widget class="QGroupBox" name="SizeGroupBox">
>> +       <property name="sizePolicy">
>> +        <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
>> +         <horstretch>0</horstretch>
>> +         <verstretch>0</verstretch>
>> +        </sizepolicy>
>> +       </property>
>> +       <property name="title">
>> +        <string>Size</string>
>> +       </property>
>> +       <layout class="QHBoxLayout" name="horizontalLayout_2">
>> +        <item>
>> +         <layout class="QFormLayout" name="SizeFormLayout">
>> +          <property name="horizontalSpacing">
>> +           <number>8</number>
>> +          </property>
>> +          <property name="verticalSpacing">
>> +           <number>8</number>
>> +          </property>
>> +          <property name="margin">
>> +           <number>8</number>
>> +          </property>
>> +          <item row="0" column="0">
>> +           <widget class="QLabel" name="WidthLabel">
>> +            <property name="text">
>> +             <string>Width</string>
>> +            </property>
>> +           </widget>
>> +          </item>
>> +          <item row="0" column="1">
>> +           <widget class="QSpinBox" name="WidthSpinBox">
>> +            <property name="sizePolicy">
>> +             <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
>> +              <horstretch>0</horstretch>
>> +              <verstretch>0</verstretch>
>> +             </sizepolicy>
>> +            </property>
>> +            <property name="minimum">
>> +             <number>1</number>
>> +            </property>
>> +            <property name="maximum">
>> +             <number>9999</number>
>> +            </property>
>> +            <property name="value">
>> +             <number>9999</number>
>> +            </property>
>> +           </widget>
>> +          </item>
>> +          <item row="1" column="0">
>> +           <widget class="QLabel" name="HeightLabel">
>> +            <property name="text">
>> +             <string>Height</string>
>> +            </property>
>> +           </widget>
>> +          </item>
>> +          <item row="1" column="1">
>> +           <widget class="QSpinBox" name="HeightSpinBox">
>> +            <property name="minimum">
>> +             <number>1</number>
>> +            </property>
>> +            <property name="maximum">
>> +             <number>9999</number>
>> +            </property>
>> +            <property name="value">
>> +             <number>9999</number>
>> +            </property>
>> +           </widget>
>> +          </item>
>> +          <item row="2" column="0">
>> +           <widget class="QLabel" name="RatioLabel">
>> +            <property name="text">
>> +             <string>Ratio</string>
>> +            </property>
>> +           </widget>
>> +          </item>
>> +          <item row="2" column="1">
>> +           <widget class="QCheckBox" name="RatioCheckBox">
>> +            <property name="text">
>> +             <string/>
>> +            </property>
>> +           </widget>
>> +          </item>
>> +          <item row="3" column="1">
>> +           <widget class="QPushButton" name="ResetPushButton">
>> +            <property name="text">
>> +             <string>Reset</string>
>> +            </property>
>> +           </widget>
>> +          </item>
>> +         </layout>
>> +        </item>
>> +       </layout>
>> +      </widget>
>> +     </item>
>> +     <item>
>> +      <widget class="QGroupBox" name="StyleGroupBox">
>> +       <property name="sizePolicy">
>> +        <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
>> +         <horstretch>0</horstretch>
>> +         <verstretch>0</verstretch>
>> +        </sizepolicy>
>> +       </property>
>> +       <property name="title">
>> +        <string>Style</string>
>> +       </property>
>> +       <layout class="QHBoxLayout" name="horizontalLayout_3">
>> +        <item>
>> +         <layout class="QFormLayout" name="StyleFormLayout">
>> +          <property name="horizontalSpacing">
>> +           <number>8</number>
>> +          </property>
>> +          <property name="verticalSpacing">
>> +           <number>8</number>
>> +          </property>
>> +          <property name="margin">
>> +           <number>8</number>
>> +          </property>
>> +          <item row="0" column="0">
>> +           <widget class="QLabel" name="AlignLabel">
>> +            <property name="text">
>> +             <string>Align</string>
>> +            </property>
>> +           </widget>
>> +          </item>
>> +          <item row="0" column="1">
>> +           <widget class="QComboBox" name="AlignComboBox">
>> +            <item>
>> +             <property name="text">
>> +              <string>None</string>
>> +             </property>
>> +            </item>
>> +            <item>
>> +             <property name="text">
>> +              <string>Left</string>
>> +             </property>
>> +            </item>
>> +            <item>
>> +             <property name="text">
>> +              <string>Right</string>
>> +             </property>
>> +            </item>
>> +            <item>
>> +             <property name="text">
>> +              <string>Center</string>
>> +             </property>
>> +            </item>
>> +            <item>
>> +             <property name="text">
>> +              <string>Block</string>
>> +             </property>
>> +            </item>
>> +            <item>
>> +             <property name="text">
>> +              <string>Text Top</string>
>> +             </property>
>> +            </item>
>> +            <item>
>> +             <property name="text">
>> +              <string>Text Middle</string>
>> +             </property>
>> +            </item>
>> +            <item>
>> +             <property name="text">
>> +              <string>Text Bottom</string>
>> +             </property>
>> +            </item>
>> +            <item>
>> +             <property name="text">
>> +              <string>Background</string>
>> +             </property>
>> +            </item>
>> +           </widget>
>> +          </item>
>> +          <item row="1" column="0">
>> +           <widget class="QLabel" name="OpacityLabel">
>> +            <property name="text">
>> +             <string>Opacity</string>
>> +            </property>
>> +           </widget>
>> +          </item>
>> +          <item row="1" column="1">
>> +           <widget class="QSlider" name="OpacityHorizontalSlider">
>> +            <property name="minimum">
>> +             <number>10</number>
>> +            </property>
>> +            <property name="maximum">
>> +             <number>100</number>
>> +            </property>
>> +            <property name="value">
>> +             <number>100</number>
>> +            </property>
>> +            <property name="sliderPosition">
>> +             <number>100</number>
>> +            </property>
>> +            <property name="orientation">
>> +             <enum>Qt::Horizontal</enum>
>> +            </property>
>> +            <property name="invertedAppearance">
>> +             <bool>false</bool>
>> +            </property>
>> +            <property name="tickPosition">
>> +             <enum>QSlider::NoTicks</enum>
>> +            </property>
>> +            <property name="tickInterval">
>> +             <number>10</number>
>> +            </property>
>> +           </widget>
>> +          </item>
>> +          <item row="2" column="0">
>> +           <widget class="QLabel" name="LeftBackgroundLabel">
>> +            <property name="text">
>> +             <string>Left</string>
>> +            </property>
>> +           </widget>
>> +          </item>
>> +          <item row="2" column="1">
>> +           <widget class="QSpinBox" name="LeftBackgroundSpinBox">
>> +            <property name="maximum">
>> +             <number>9999</number>
>> +            </property>
>> +            <property name="value">
>> +             <number>9999</number>
>> +            </property>
>> +           </widget>
>> +          </item>
>> +          <item row="3" column="0">
>> +           <widget class="QLabel" name="TopBackgroundLabel">
>> +            <property name="text">
>> +             <string>Top</string>
>> +            </property>
>> +           </widget>
>> +          </item>
>> +          <item row="3" column="1">
>> +           <widget class="QSpinBox" name="TopBackgroundSpinBox">
>> +            <property name="maximum">
>> +             <number>9999</number>
>> +            </property>
>> +            <property name="value">
>> +             <number>9999</number>
>> +            </property>
>> +           </widget>
>> +          </item>
>> +         </layout>
>> +        </item>
>> +       </layout>
>> +      </widget>
>> +     </item>
>> +     <item>
>> +      <widget class="QGroupBox" name="SpacingGroupBox">
>> +       <property name="sizePolicy">
>> +        <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
>> +         <horstretch>0</horstretch>
>> +         <verstretch>0</verstretch>
>> +        </sizepolicy>
>> +       </property>
>> +       <property name="title">
>> +        <string>Spacing</string>
>> +       </property>
>> +       <layout class="QHBoxLayout" name="horizontalLayout_4">
>> +        <item>
>> +         <layout class="QFormLayout" name="SpacingFormLayout">
>> +          <property name="horizontalSpacing">
>> +           <number>8</number>
>> +          </property>
>> +          <property name="verticalSpacing">
>> +           <number>8</number>
>> +          </property>
>> +          <property name="margin">
>> +           <number>8</number>
>> +          </property>
>> +          <item row="0" column="0">
>> +           <widget class="QLabel" name="TopLabel">
>> +            <property name="text">
>> +             <string>Top</string>
>> +            </property>
>> +           </widget>
>> +          </item>
>> +          <item row="0" column="1">
>> +           <widget class="QSpinBox" name="TopSpinBox">
>> +            <property name="maximum">
>> +             <number>9999</number>
>> +            </property>
>> +            <property name="value">
>> +             <number>9999</number>
>> +            </property>
>> +           </widget>
>> +          </item>
>> +          <item row="1" column="0">
>> +           <widget class="QLabel" name="RightLabel">
>> +            <property name="text">
>> +             <string>Right</string>
>> +            </property>
>> +           </widget>
>> +          </item>
>> +          <item row="1" column="1">
>> +           <widget class="QSpinBox" name="RightSpinBox">
>> +            <property name="maximum">
>> +             <number>9999</number>
>> +            </property>
>> +            <property name="value">
>> +             <number>9999</number>
>> +            </property>
>> +           </widget>
>> +          </item>
>> +          <item row="2" column="0">
>> +           <widget class="QLabel" name="BottomLabel">
>> +            <property name="text">
>> +             <string>Bottom</string>
>> +            </property>
>> +           </widget>
>> +          </item>
>> +          <item row="2" column="1">
>> +           <widget class="QSpinBox" name="BottomSpinBox">
>> +            <property name="maximum">
>> +             <number>9999</number>
>> +            </property>
>> +            <property name="value">
>> +             <number>9999</number>
>> +            </property>
>> +           </widget>
>> +          </item>
>> +          <item row="3" column="0">
>> +           <widget class="QLabel" name="LeftLabel">
>> +            <property name="text">
>> +             <string>Left</string>
>> +            </property>
>> +           </widget>
>> +          </item>
>> +          <item row="3" column="1">
>> +           <widget class="QSpinBox" name="LeftSpinBox">
>> +            <property name="maximum">
>> +             <number>9999</number>
>> +            </property>
>> +            <property name="value">
>> +             <number>9999</number>
>> +            </property>
>> +           </widget>
>> +          </item>
>> +         </layout>
>> +        </item>
>> +       </layout>
>> +      </widget>
>> +     </item>
>> +     <item>
>> +      <widget class="QGroupBox" name="BorderGroupBox">
>> +       <property name="sizePolicy">
>> +        <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
>> +         <horstretch>0</horstretch>
>> +         <verstretch>0</verstretch>
>> +        </sizepolicy>
>> +       </property>
>> +       <property name="title">
>> +        <string>Border</string>
>> +       </property>
>> +       <layout class="QHBoxLayout" name="horizontalLayout_5">
>> +        <item>
>> +         <layout class="QFormLayout" name="BorderFormLayout">
>> +          <property name="horizontalSpacing">
>> +           <number>8</number>
>> +          </property>
>> +          <property name="verticalSpacing">
>> +           <number>8</number>
>> +          </property>
>> +          <property name="margin">
>> +           <number>8</number>
>> +          </property>
>> +          <item row="0" column="0">
>> +           <widget class="QLabel" name="BorderTypeLabel">
>> +            <property name="text">
>> +             <string>Type</string>
>> +            </property>
>> +           </widget>
>> +          </item>
>> +          <item row="0" column="1">
>> +           <widget class="QComboBox" name="BorderTypeComboBox">
>> +            <item>
>> +             <property name="text">
>> +              <string>None</string>
>> +             </property>
>> +            </item>
>> +            <item>
>> +             <property name="text">
>> +              <string>Solid</string>
>> +             </property>
>> +            </item>
>> +            <item>
>> +             <property name="text">
>> +              <string>Dotted</string>
>> +             </property>
>> +            </item>
>> +            <item>
>> +             <property name="text">
>> +              <string>Dashed</string>
>> +             </property>
>> +            </item>
>> +            <item>
>> +             <property name="text">
>> +              <string>Double</string>
>> +             </property>
>> +            </item>
>> +            <item>
>> +             <property name="text">
>> +              <string>Groove</string>
>> +             </property>
>> +            </item>
>> +            <item>
>> +             <property name="text">
>> +              <string>Ridge</string>
>> +             </property>
>> +            </item>
>> +            <item>
>> +             <property name="text">
>> +              <string>Inset</string>
>> +             </property>
>> +            </item>
>> +            <item>
>> +             <property name="text">
>> +              <string>Outset</string>
>> +             </property>
>> +            </item>
>> +           </widget>
>> +          </item>
>> +          <item row="1" column="0">
>> +           <widget class="QLabel" name="BorderWidthLabel">
>> +            <property name="text">
>> +             <string>Width</string>
>> +            </property>
>> +           </widget>
>> +          </item>
>> +          <item row="1" column="1">
>> +           <widget class="QSpinBox" name="BorderWidthSpinBox">
>> +            <property name="value">
>> +             <number>99</number>
>> +            </property>
>> +           </widget>
>> +          </item>
>> +          <item row="2" column="1">
>> +           <widget class="QPushButton" name="BorderColorPushButton">
>> +            <property name="text">
>> +             <string>Color...</string>
>> +            </property>
>> +           </widget>
>> +          </item>
>> +          <item row="2" column="0">
>> +           <widget class="QFrame" name="border_color_frame">
>> +            <property name="sizePolicy">
>> +             <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
>> +              <horstretch>0</horstretch>
>> +              <verstretch>0</verstretch>
>> +             </sizepolicy>
>> +            </property>
>> +            <property name="minimumSize">
>> +             <size>
>> +              <width>21</width>
>> +              <height>21</height>
>> +             </size>
>> +            </property>
>> +            <property name="styleSheet">
>> +             <string notr="true">background-color: #FF0000;</string>
>> +            </property>
>> +            <property name="frameShape">
>> +             <enum>QFrame::StyledPanel</enum>
>> +            </property>
>> +            <property name="frameShadow">
>> +             <enum>QFrame::Raised</enum>
>> +            </property>
>> +           </widget>
>> +          </item>
>> +         </layout>
>> +        </item>
>> +       </layout>
>> +      </widget>
>> +     </item>
>> +    </layout>
>> +   </item>
>> +   <item>
>> +    <spacer name="VerticalSpacer">
>> +     <property name="orientation">
>> +      <enum>Qt::Vertical</enum>
>> +     </property>
>> +     <property name="sizeHint" stdset="0">
>> +      <size>
>> +       <width>20</width>
>> +       <height>40</height>
>> +      </size>
>> +     </property>
>> +    </spacer>
>> +   </item>
>> +   <item>
>> +    <widget class="QDialogButtonBox" name="ButtonBox">
>> +     <property name="orientation">
>> +      <enum>Qt::Horizontal</enum>
>> +     </property>
>> +     <property name="standardButtons">
>> +      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
>> +     </property>
>> +    </widget>
>> +   </item>
>> +  </layout>
>> + </widget>
>> + <resources/>
>> + <connections>
>> +  <connection>
>> +   <sender>ButtonBox</sender>
>> +   <signal>accepted()</signal>
>> +   <receiver>EditImageDialog</receiver>
>> +   <slot>accept()</slot>
>> +   <hints>
>> +    <hint type="sourcelabel">
>> +     <x>248</x>
>> +     <y>254</y>
>> +    </hint>
>> +    <hint type="destinationlabel">
>> +     <x>157</x>
>> +     <y>274</y>
>> +    </hint>
>> +   </hints>
>> +  </connection>
>> +  <connection>
>> +   <sender>ButtonBox</sender>
>> +   <signal>rejected()</signal>
>> +   <receiver>EditImageDialog</receiver>
>> +   <slot>reject()</slot>
>> +   <hints>
>> +    <hint type="sourcelabel">
>> +     <x>316</x>
>> +     <y>260</y>
>> +    </hint>
>> +    <hint type="destinationlabel">
>> +     <x>286</x>
>> +     <y>274</y>
>> +    </hint>
>> +   </hints>
>> +  </connection>
>> + </connections>
>> +</ui>
>>
>> === added file 'openlp/plugins/custom/forms/editimageform.py'
>> --- openlp/plugins/custom/forms/editimageform.py	1970-01-01 00:00:00 +0000
>> +++ openlp/plugins/custom/forms/editimageform.py	2014-06-20 18:05:18 +0000
>> @@ -0,0 +1,503 @@
>> +# -*- coding: utf-8 -*-
>> +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
>> +
>> +###############################################################################
>> +# OpenLP - Open Source Lyrics Projection                                      #
>> +# --------------------------------------------------------------------------- #
>> +# Copyright (c) 2008-2014 Raoul Snyman                                        #
>> +# Portions copyright (c) 2008-2014 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:`~openlp.plugins.custom.forms.editimageform` module contains the form
>> +used to select and style images.
>> +"""
>> +
>> +import logging
>> +
>> +from PyQt4 import QtCore, Qt, QtGui
>> +from openlp.core.common import translate
>> +from openlp.core.lib.ui import critical_error_message_box
>> +from openlp.plugins.custom.forms.editimagedialog import Ui_EditImageDialog
>> +
>> +log = logging.getLogger(__name__)
>> +
>> +
>> +class AlignStyle(object):
>> +    """
>> +    Class for css alignment style - 1:1 relationship with alignment combo box
>> +    """
>> +    Align = ['',                                                               # None
>> +             ' float: left;',                                                  # Left
>> +             ' float: right;',                                                 # Right
>> +             ' display: block; margin-left: auto; margin-right: auto;',        # Center
>> +             ' display: block;',                                               # Block
>> +             ' vertical-align: text-top;',                                     # Text Top
>> +             ' vertical-align: text-middle;',                                  # Text Middle
>> +             ' vertical-align: text-bottom;',                                  # Text Bottom
>> +             ' position: absolute;']                                           # Background
>> +    Left = 1
>> +    Right = 2
>> +    Center = 3
>> +    Block = 4
>> +    Text_Top = 5
>> +    Text_Middle = 6
>> +    Text_Bottom = 7
>> +    Background = 8
>> +
>> +
>> +class BorderStyle(object):
>> +    """
>> +    Class for css border style - 1:1 relationship with border combo box
>> +    """
>> +    Border = ['',                               # None
>> +              ' border-style: solid;',
>> +              ' border-style: dotted;',
>> +              ' border-style: dashed;',
>> +              ' border-style: double;',
>> +              ' border-style: groove;',
>> +              ' border-style: ridge;',
>> +              ' border-style: inset;',
>> +              ' border-style: outset;']
>> +    Solid = 1
>> +    Dotted = 2
>> +    Dashed = 3
>> +    Double = 4
>> +    Groove = 5
>> +    Ridge = 6
>> +    Inset = 7
>> +    Outset = 8
>> +
>> +
>> +class EditImageForm(QtGui.QDialog, Ui_EditImageDialog):
>> +    """
>> +    Class to display the image editor
>> +    """
>> +    log.info('%s EditImageForm loaded', __name__)
>> +
>> +    def __init__(self, parent=None):
>> +        """
>> +        Constructor
>> +        """
>> +        super(EditImageForm, self).__init__(parent)
>> +        self.setupUi(self)
>> +        self.image_width = int
>> +        self.image_height = int
>> +        self.max_width = int
>> +        self.max_height = int
>> +        self.last_path = ''
>> +        self.border_color = Qt.QColor()
>> +        self.align_style = AlignStyle()
>> +        self.border_style = BorderStyle()
>> +        # Connecting signals and slots
>> +        self.image_push_button.clicked.connect(self.on_image_push_button)
>> +        self.reset_push_button.clicked.connect(self.on_reset_push_button)
>> +        self.align_combo_box.currentIndexChanged.connect(self.on_align_combo_box)
>> +        self.border_type_combo_box.currentIndexChanged.connect(self.on_border_type_combo_box)
>> +        self.border_color_push_button.clicked.connect(self.on_color_push_button)
>> +        self.width_spin_box.valueChanged.connect(self.on_width_spin_box)
>> +        self.height_spin_box.valueChanged.connect(self.on_height_spin_box)
>> +        self.proportional_check_box.stateChanged.connect(self.on_proportional_check_box)
>> +
>> +    def set_image(self, max_width, max_height, src=None, style=None):
>> +        """
>> +        Initialize the dialog to default values and set to style values if present
>> +
>> +        :param max_width: Width of slide text area
>> +        :param max_height: Height of slide text area
>> +        :param src: string containing image filename
>> +        :param style: string containing image information
>> +        """
>> +        self.max_width = max_width
>> +        self.max_height = max_height
>> +        self.image_width = 0
>> +        self.image_height = 0
>> +        self.thumbnail_label.hide()
>> +        self.image_line_edit.setText('')
>> +        self.proportional_check_box.setCheckState(Qt.Qt.Unchecked)
>> +        self.width_spin_box.setValue(0)
>> +        self.height_spin_box.setValue(0)
>> +        self.align_combo_box.setCurrentIndex(0)
>> +        self.opacity_horizontal_slider.setValue(100)
>> +        self.shadow_horizontal_slider.setValue(0)
>> +        self.blur_horizontal_slider.setValue(0)
>> +        self.top_spin_box.setValue(0)
>> +        self.right_spin_box.setValue(0)
>> +        self.bottom_spin_box.setValue(0)
>> +        self.left_spin_box.setValue(0)
>> +        self.border_type_combo_box.setCurrentIndex(0)
>> +        self.border_width_horizontal_slider.setValue(1)
>> +        self.border_color = Qt.QColor(255, 255, 255, 255)
>> +        self.radius_horizontal_slider.setValue(0)
>> +        self.set_frame_color()
>> +        self.size_group_box.setEnabled(False)
>> +        self.style_group_box.setEnabled(False)
>> +        self.spacing_group_box.setEnabled(False)
>> +        self.border_group_box.setEnabled(False)
>> +        if src:
>> +            self.setWindowTitle(translate('CustomPlugin.EditImageForm', "Edit Image"))
>> +            if self.open_image(src):
>> +                if style:
>> +                    self.parse_style(style)
>> +        else:
>> +            self.setWindowTitle(translate('CustomPlugin.EditImageForm', "Insert Image"))
>> +        self.image_push_button.setFocus()
>> +
>> +    def set_frame_color(self):
>> +        """
>> +        Set the background color of the frame to the selected color
>> +        """
>> +        self.border_color_frame.setStyleSheet('background-color: #%02X%02X%02X;' % (self.border_color.red(),
>> +                                                                                    self.border_color.green(),
>> +                                                                                    self.border_color.blue()))
>> +
>> +    def make_rgba_string(self):
>> +        """
>> +        Build an rgba string from the background color
>> +        """
>> +        text = 'rgba(%d,%d,%d,%1.1f)' % (self.border_color.red(),
>> +                                         self.border_color.green(),
>> +                                         self.border_color.blue(),
>> +                                         self.border_color.alphaF())
>> +        return text
>> +
>> +    def get_image_name(self):
>> +        """
>> +        Get the image file name
>> +        """
>> +        url_name = 'file:///' + self.image_line_edit.text()
>> +        return url_name
>> +
>> +    def get_image_style(self):
>> +        """
>> +        Build a style string based on the dialog selections
>> +        """
>> +        text = 'width: %dpx; height: %dpx;' %\
>> +               (self.width_spin_box.value(),
>> +                self.height_spin_box.value())
>> +        i = self.align_combo_box.currentIndex()
>> +        if i > 0:
>> +            text += self.align_style.Align[i]
>> +            if i == self.align_style.Background:
>> +                text += ' left: 0px; top: 0px; z-index: -1;'
>> +        if self.opacity_horizontal_slider.value() != 100:
>> +            text += ' opacity: %1.1f;' % (self.opacity_horizontal_slider.value() / 100.0)
>> +        if self.shadow_horizontal_slider.value() or self.blur_horizontal_slider.value():
>> +            text += ' box-shadow: 0px 0px %dpx %dpx rgba(0,0,0,0.5);' %\
>> +                    (self.blur_horizontal_slider.value(),
>> +                    self.shadow_horizontal_slider.value())
>> +        if self.top_spin_box.value():
>> +            text += ' margin-top: %dpx;' % self.top_spin_box.value()
>> +        if self.right_spin_box.value():
>> +            text += ' margin-right: %dpx;' % self.right_spin_box.value()
>> +        if self.bottom_spin_box.value():
>> +            text += ' margin-bottom: %dpx;' % self.bottom_spin_box.value()
>> +        if self.left_spin_box.value():
>> +            text += ' margin-left: %dpx;' % self.left_spin_box.value()
>> +        i = self.border_type_combo_box.currentIndex()
>> +        if i > 0:
>> +            text += '%s border-width: %dpx; border-color: %s;' %\
>> +                    (self.border_style.Border[i],
>> +                     self.border_width_horizontal_slider.value(),
>> +                     self.make_rgba_string())
>> +            if self.radius_horizontal_slider.value():
>> +                text += ' border-radius: %dpx;' % self.radius_horizontal_slider.value()
>> +        text += ' max-width: %dpx; max-height: %dpx;' %\
>> +                (self.max_width,
>> +                 self.max_height)
>> +        return text
>> +
>> +    def get_tag(self):
>> +        """
>> +        Returns the tag text when using the tag editor
>> +        """
>> +        text = '{img}"%s" style="%s"{/img}' % (self.get_image_name(), self.get_image_style())
>> +        return text
>> +
>> +    def open_image(self, img_name):
>> +        """
>> +        Open the image and retrieve its properties
>> +
>> +        :param img_name:
>> +        """
>> +        file_name = QtCore.QUrl(img_name).toLocalFile()
>> +        self.last_path = QtCore.QFileInfo(file_name).absolutePath()
>> +        img = QtGui.QImage(file_name)
>> +        if img.isNull():
>> +            msg = translate('CustomPlugin.EditImageForm',
>> +                            'Unable to open the image:') + '\n\n' + file_name
>> +            critical_error_message_box(title=translate('CustomPlugin.EditImageForm', 'Image Open Error'), message=msg)
>> +        else:
>> +            self.enable_dialog(file_name, img)
>> +        return not img.isNull()
>> +
>> +    def enable_dialog(self, file_name, img):
>> +        """
>> +        Enable the dialog widgets since we have a valid image
>> +        """
>> +        thumbnail = img.scaled(Qt.QSize(48, 48), Qt.Qt.KeepAspectRatio)
>> +        self.thumbnail_label.setPixmap(QtGui.QPixmap().fromImage(thumbnail))
>> +        self.thumbnail_label.show()
>> +        self.image_line_edit.setText(file_name)
>> +        self.image_width = img.width()
>> +        self.image_height = img.height()
>> +        self.size_group_box.setEnabled(True)
>> +        self.style_group_box.setEnabled(True)
>> +        self.spacing_group_box.setEnabled(True)
>> +        self.border_group_box.setEnabled(True)
>> +        self.on_reset_push_button()
>> +        self.on_align_combo_box()
>> +        self.on_border_type_combo_box()
>> +
>> +    def validate(self):
>> +        """
>> +        Verify that image has been selected and fits into slide text area
>> +
>> +        :return True if valid
>> +        """
>> +        valid = True
>> +        if not self.image_line_edit.text():
>> +            valid = False
>> +            msg = translate('CustomPlugin.EditImageForm', 'Select an image file.')
>> +            critical_error_message_box(title=translate('CustomPlugin.EditImageForm', 'No Image Selected'),
>> +                                       message=msg)
>> +            self.image_push_button.setFocus()
>> +        else:
>> +            if self.border_type_combo_box.currentIndex() > 0:
>> +                border = self.border_width_horizontal_slider.value() * 2
>> +            else:
>> +                border = 0
>> +            total_width = (self.width_spin_box.value() +
>> +                           border +
>> +                           self.left_spin_box.value() +
>> +                           self.right_spin_box.value())
>> +            total_height = (self.height_spin_box.value() +
>> +                            border +
>> +                            self.top_spin_box.value() +
>> +                            self.bottom_spin_box.value())
>> +            if total_width > self.max_width or total_height > self.max_height:
>> +                valid = False
>> +                msg = translate('CustomPlugin.EditImageForm', 'The image is too large for the slide area.\n\n'
>> +                                                              'Width: %d    Maximum: %d\n'
>> +                                                              'Height: %d    Maximum: %d\n\n'
>> +                                                              '(spacing and border width included)' %
>> +                                                              (total_width, self.max_width,
>> +                                                               total_height, self.max_height))
>> +                critical_error_message_box(title=translate('CustomPlugin.EditImageForm', 'Image Size Error'),
>> +                                           message=msg)
>> +        return valid
>> +
>> +    def accept(self):
>> +        """
>> +        Close dialog when validated
>> +        """
>> +        if self.validate():
>> +            QtGui.QDialog.accept(self)
>> +
>> +    @staticmethod
>> +    def parse(text, start_tag, end_tag):
>> +        """
>> +        Return the substring in text between the start_tag and end_tag
>> +
>> +        :param text: The text to search
>> +        :param start_tag:
>> +        :param end_tag:
>> +        :return: The substring if found, else null
>> +        """
>> +        item = ''
>> +        start = text.find(start_tag)
>> +        if start > -1:
>> +            start += len(start_tag)
>> +            end = text.find(end_tag, start)
>> +            if end > -1:
>> +                item = text[start:end]
>> +                if item.find(';') > -1:     # Did we jump attributes?
>> +                    item = ''
>> +        return item
>> +
>> +    def parse_style(self, style):
>> +        """
>> +        Parse the CSS style string to set the dialog values
>> +
>> +        :param style:
>> +        """
>> +        parse_error = False
>> +        style = ' ' + style
>> +        try:
>> +            text = self.parse(style, ' width: ', 'px;')
>> +            self.width_spin_box.setValue(int(text))
>> +            text = self.parse(style, ' height: ', 'px;')
>> +            self.height_spin_box.setValue(int(text))
>> +            for i, align in enumerate(self.align_style.Align):
>> +                if align:                                                           # Skip over 'None'
>> +                    if i == self.align_style.Center and style.find(' margin-left: auto;') > -1:
>> +                        self.align_combo_box.setCurrentIndex(i)
>> +                        break
>> +                    elif style.find(align) > -1:
>> +                        self.align_combo_box.setCurrentIndex(i)
>> +                        break
>> +            text = self.parse(style, ' opacity: ', ';')
>> +            if text:
>> +                self.opacity_horizontal_slider.setValue(int(float(text) * 100))
>> +            text = self.parse(style, ' box-shadow: 0px 0px', ' rgba')
>> +            if text:
>> +                blur = self.parse(text, ' ', 'px')
>> +                self.blur_horizontal_slider.setValue(int(blur))
>> +                shadow = self.parse(text, 'px ', 'px')
>> +                self.shadow_horizontal_slider.setValue(int(shadow))
>> +            text = self.parse(style, ' margin-top: ', 'px;')
>> +            if text:
>> +                self.top_spin_box.setValue(int(text))
>> +            text = self.parse(style, ' margin-right: ', 'px;')
>> +            if text:
>> +                self.right_spin_box.setValue(int(text))
>> +            text = self.parse(style, ' margin-bottom: ', 'px;')
>> +            if text:
>> +                self.bottom_spin_box.setValue(int(text))
>> +            text = self.parse(style, ' margin-left: ', 'px;')
>> +            if text:
>> +                self.left_spin_box.setValue(int(text))
>> +            for i, border in enumerate(self.border_style.Border):
>> +                if border:                                                          # Skip over 'None'
>> +                    if style.find(border) > -1:
>> +                        self.border_type_combo_box.setCurrentIndex(i)
>> +                        text = self.parse(style, ' border-width: ', 'px;')
>> +                        self.border_width_horizontal_slider.setValue(int(text))
>> +                        border_color = self.parse(style, ' border-color: rgba(', ');')
>> +                        rgba = border_color.split(',')
>> +                        red = int(rgba[0])
>> +                        green = int(rgba[1])
>> +                        blue = int(rgba[2])
>> +                        alpha = float(rgba[3])
>> +                        self.border_color.setRed(red)
>> +                        self.border_color.setGreen(green)
>> +                        self.border_color.setBlue(blue)
>> +                        self.border_color.setAlphaF(alpha)
>> +                        self.set_frame_color()
>> +                        text = self.parse(style, ' border-radius: ', 'px;')
>> +                        if text:
>> +                            self.radius_horizontal_slider.setValue(int(text))
>> +                        break
>> +        except ValueError:
>> +            parse_error = True
>> +        except IndexError:
>> +            parse_error = True
>> +        if parse_error:
>> +            msg = translate('CustomPlugin.EditImageForm',
>> +                            'Error(s) found in the formatting tag. Default values were used.')
>> +            critical_error_message_box(title=translate('CustomPlugin.EditImageForm', 'Invalid Tag'),
>> +                                       message=msg)
>> +
>> +    def on_image_push_button(self):
>> +        """
>> +        Display open file dialog and try to open the image
>> +        """
>> +        dlg_title = translate('CustomPlugin.EditImageForm', 'Select Image')
>> +        dlg_types = translate('CustomPlugin.EditImageForm', 'Images') + ' (*.bmp *.gif *.ico *.jpg *.jpeg *.png *.svg)'
>> +        file_dialog = QtGui.QFileDialog(self)
>> +        file_name = file_dialog.getOpenFileName(self, dlg_title, self.last_path, dlg_types)
>> +        if file_name:
>> +            self.last_path = QtCore.QFileInfo(file_name).absolutePath()
>> +            img = QtGui.QImage(file_name)
>> +            if img.isNull():
>> +                msg = translate('CustomPlugin.EditImageForm', 'The image cannot be opened.')
>> +                critical_error_message_box(title=translate('CustomPlugin.EditImageForm', 'Image Open Error'),
>> +                                           message=msg)
>> +            else:
>> +                self.enable_dialog(file_name, img)
>> +
>> +    def on_reset_push_button(self):
>> +        """
>> +        Sets the size spin boxes to the actual image size
>> +        """
>> +        checked = self.proportional_check_box.isChecked()
>> +        if checked:
>> +            self.proportional_check_box.setCheckState(Qt.Qt.Unchecked)            # Disable scaling
>> +        self.width_spin_box.setValue(self.image_width)
>> +        self.height_spin_box.setValue(self.image_height)
>> +        if checked:
>> +            self.proportional_check_box.setCheckState(Qt.Qt.Checked)              # Restore scaling
>> +
>> +    def on_color_push_button(self):
>> +        """
>> +        Display the color picker dialog to select a border color
>> +        """
>> +        color_dialog = QtGui.QColorDialog()
>> +        new_color = color_dialog.getColor(self.border_color, self, '', QtGui.QColorDialog.ShowAlphaChannel)
>> +        if new_color.isValid():
>> +            self.border_color = new_color
>> +            self.set_frame_color()
>> +
>> +    def on_align_combo_box(self):
>> +        """
>> +        Set the enabled status of the widgets based on the align selection
>> +        """
>> +        self.left_spin_box.setEnabled(True)
>> +        self.right_spin_box.setEnabled(True)
>> +        self.top_spin_box.setEnabled(True)
>> +        self.bottom_spin_box.setEnabled(True)
>> +        if self.align_combo_box.currentIndex() == self.align_style.Background:
>> +            self.right_spin_box.setValue(0)
>> +            self.right_spin_box.setEnabled(False)
>> +            self.bottom_spin_box.setValue(0)
>> +            self.bottom_spin_box.setEnabled(False)
>> +        elif self.align_combo_box.currentIndex() == self.align_style.Center:
>> +            self.left_spin_box.setValue(0)
>> +            self.left_spin_box.setEnabled(False)
>> +            self.right_spin_box.setValue(0)
>> +            self.right_spin_box.setEnabled(False)
>> +
>> +    def on_border_type_combo_box(self):
>> +        """
>> +        Set the enabled status of the width spin box and color button based on border selection
>> +        """
>> +        enable = self.border_type_combo_box.currentIndex() > 0
>> +        self.border_width_horizontal_slider.setEnabled(enable)
>> +        self.border_color_push_button.setEnabled(enable)
>> +        self.radius_horizontal_slider.setEnabled(enable)
>> +
>> +    def on_width_spin_box(self):
>> +        """
>> +        Set the height as a ratio of original size if proportional
>> +        """
>> +        if self.proportional_check_box.isChecked():
>> +            self.height_spin_box.valueChanged.disconnect()
>> +            ratio = self.image_height / self.image_width
>> +            self.height_spin_box.setValue(int(self.width_spin_box.value() * ratio))
>> +            self.height_spin_box.valueChanged.connect(self.on_height_spin_box)
>> +
>> +    def on_height_spin_box(self):
>> +        """
>> +        Set the width as a ratio of original size if proportional
>> +        """
>> +        if self.proportional_check_box.isChecked():
>> +            self.width_spin_box.valueChanged.disconnect()
>> +            ratio = self.image_width / self.image_height
>> +            self.width_spin_box.setValue(int(self.height_spin_box.value() * ratio))
>> +            self.width_spin_box.valueChanged.connect(self.on_width_spin_box)
>> +
>> +    def on_proportional_check_box(self):
>> +        """
>> +        Change the image height when checked
>> +        """
>> +        if self.proportional_check_box.isChecked():
>> +            if self.width_spin_box.value() != self.image_width | self.height_spin_box.value() != self.image_height:
>> +                self.on_width_spin_box()
>>
>> === added file 'openlp/plugins/custom/lib/htmleditor.py'
>> --- openlp/plugins/custom/lib/htmleditor.py	1970-01-01 00:00:00 +0000
>> +++ openlp/plugins/custom/lib/htmleditor.py	2014-06-20 18:05:18 +0000
>> @@ -0,0 +1,1786 @@
>> +# -*- coding: utf-8 -*-
>> +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
>> +
>> +# ##############################################################################
>> +# OpenLP - Open Source Lyrics Projection                                      #
>> +# --------------------------------------------------------------------------- #
>> +# Copyright (c) 2008-2014 Raoul Snyman                                        #
>> +# Portions copyright (c) 2008-2014 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:`~openlp.plugins.custom.lib.htmleditor` module provides a wysiwig editor for the editing of custom slides,
>> +and adds support for images. The editor is implemented with QWebView using the contenteditable=true attribute.
>> +
>> +Known issues:
>> +    - Spellchecking not implemented
>> +    - Strange highlighting after editing some blocks (selecting/deselecting block removes artifacts)
>> +    - Using the scrollbar loses text input focus
>> +"""
>> +
>> +import logging
>> +
>> +from PyQt4 import QtCore, QtGui, QtWebKit
>> +from PyQt4.QtGui import QStyle
>> +from openlp.core import Registry
>> +from openlp.core.lib import translate, FormattingTags
>> +from openlp.core.lib.ui import critical_error_message_box
>> +from openlp.core.lib.htmlbuilder import build_background_css, build_lyrics_css
>> +from openlp.plugins.custom.forms.editimageform import EditImageForm
>> +
>> +log = logging.getLogger(__name__)
>> +
>> +
>> +class HtmlEditor(QtWebKit.QWebView):
>> +    """
>> +    A custom slide editor using QWebView in editing mode
>> +    """
>> +
>> +    def __init__(self, parent=None):
>> +        """
>> +        Constructor
>> +        """
>> +        super(HtmlEditor, self).__init__(parent)
>> +        self.frame = self.page().mainFrame()
>> +        self.java = self.frame.evaluateJavaScript
>> +        self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
>> +        self.setAcceptDrops(False)
>> +        self.setUrl(QtCore.QUrl('about:blank'))
>> +        # self.page().settings().setAttribute(QtWebKit.QWebSettings.DeveloperExtrasEnabled, True)
>> +        # self.inspector = QtWebKit.QWebInspector(self)
>> +        # self.inspector.setPage(self.page())
>> +        # self.inspector.show()
>> +        self.clipboard = Registry().get('main_window').clipboard
>> +        self.clipboard_owned = False  # True if the clipboard data is ours
>> +        self.zoom = 0.0  # Default to window width
>> +        self.background = 'Theme'  # Default to theme background
>> +        self.background_color = QtGui.QColor(QtCore.Qt.white)  # Default to white background override color
>> +        self.screen_width = 0  # Screen width - set from the ServiceItem in set_text
>> +        self.screen_height = 0  # Screen Height - ''
>> +        self.main_width = 0  # Text area width - ''
>> +        self.main_height = 0  # Text area height - ''
>> +        self.max_image_width = 0  # Maximum image width for current theme
>> +        self.max_image_height = 0  # Maximum image height for current theme
>> +        self.marker = '!mArKeR!'  # Strange characters used to identify insert position
>> +        self.last_id = 0  # The last element id assigned
>> +        self.typing = False  # Used as a semaphore to ignore selection changed when typing
>> +        self.image_selected = None  # The image that has focus
>> +        self.active_slide = None  # The slide div that has focus
>> +        self.active_text = None  # The text div that has focus
>> +        self.text_undo_command = None  # The currently active text undo command
>> +        self.undo_stack = QtGui.QUndoStack(self)  # Use our own undo stack instead of the QWebView's
>> +        self.edit_image_form = EditImageForm(self)  # Image dialog form
>> +        # Signals and slots
>> +        self.connect(self, QtCore.SIGNAL('selectionChanged()'), self.on_selection_changed)
>> +        self.connect(self, QtCore.SIGNAL('customContextMenuRequested(const QPoint&)'),
>> +                     self.customContextMenuRequested)
>> +        self.connect(self.clipboard, QtCore.SIGNAL('dataChanged()'), self.on_clipboard_changed)
>> +
>> +    def insert_data_tag(self, tag, html):
>> +        """
>> +        Insert the data-tag into the start html string from FormattingTags
>> +
>> +        :param tag: The formatting {tag} string
>> +        :param html: The start html string
>> +        :return The start html string with id and data-tag="{tag}" inserted
>> +        """
>> +        self.last_id += 1
>> +        tag_text = u' id="elem%d" data-tag="%s"' % (self.last_id, tag)
>> +        if html.find(u' ') > -1:
>> +            html = html.replace(u' ', tag_text + u' ', 1)
>> +        else:
>> +            html = html.replace(u'>', tag_text + u'>', 1)
>> +        return html
>> +
>> +    def remove_empty_tags(self, slide_text):
>> +        """
>> +        Iterate through slide text to remove empty tags, both simple and nested
>> +
>> +        :param slide_text: A string containing the slide contents in text format
>> +        :return slide contents without empty tags
>> +        """
>> +        empty_tags = []
>> +        for html in FormattingTags.get_html_tags():
>> +            if html['end tag']:
>> +                empty_tags.append(html['start tag'] + html['end tag'])
>> +        empty_found = True
>> +        while empty_found:
>> +            empty_found = False
>> +            for empty_tag in empty_tags:
>> +                if slide_text.find(empty_tag) > -1:
>> +                    slide_text = slide_text.replace(empty_tag, '')
>> +                    empty_found = True
>> +                    break
>> +        return slide_text
>> +
>> +    def text_to_html(self, text):
>> +        """
>> +        Convert special characters, and replace formatting tags with html
>> +
>> +        :param text: One or more slides in plain text with formatting tags
>> +        :return The slide text in html format wrapped in divs
>> +        """
>> +        if not text:  # Build an empty slide
>> +            return self.build_slide_wrapper('')
>> +        text = self.remove_empty_tags(text)
>> +        text = text.replace(u'<', u'&lt;')
>> +        text = text.replace(u'>', u'&gt;')
>> +        slides_html = ''
>> +        slides = text.split(u'\n[===]\n')
>> +        for slide in slides:
>> +            slide = slide.replace(u'\n', u'<br />')
>> +            for html in FormattingTags.get_html_tags():
>> +                start_pos = 0
>> +                while True:
>> +                    start_pos = slide.find(html['start tag'], start_pos)
>> +                    if start_pos == -1:
>> +                        break
>> +                    start_html = self.insert_data_tag(html['start tag'], html['start html'])
>> +                    slide = slide[0:start_pos] + start_html + slide[start_pos + len(html['start tag']):]
>> +                    start_pos += len(start_html)
>> +                if html['end tag']:
>> +                    if html['end tag'] == '{/img}':  # add max-width and height to style string
>> +                        slide = slide.replace('"{/img}', ' max-width: %dpx; max-height: %dpx;" />' %
>> +                                              (self.max_image_width, self.max_image_height))
>> +                    else:
>> +                        slide = slide.replace(html['end tag'], html['end html'])
>> +            slides_html += self.build_slide_wrapper(slide)
>> +        return slides_html
>> +
>> +    def build_slide_wrapper(self, slide_html):
>> +        """
>> +        Create the div wrappers for a slide
>> +
>> +        :param slide_html: The slide content in html format
>> +        :return Wrapped slide content
>> +        """
>> +        self.last_id += 1
>> +        html = u'<div class="slide" id="slide%d">' % self.last_id
>> +        html += u'<a href="#slide%d"></a><div class="text_table">' % self.last_id
>> +        html += u'<div class="lyricscell lyricsmain" id="text%d" contenteditable="true">' % self.last_id
>> +        html += u'%s</div></div></div>' % slide_html
>> +        return html
>> +
>> +    def build_background_css(self, item):
>> +        """
>> +        Build the background css - use htmlbuilder unless we have an image or a transparency
>> +
>> +        :param item: ServiceItem
>> +        :return  The background css statement
>> +        """
>> +        if item.theme_data.background_type == 'image':
>> +            url_filename = QtCore.QUrl.fromLocalFile(item.theme_data.background_filename)
>> +            background = u"background: %s url('%s') repeat-y" % (item.theme_data.background_border_color,
>> +                                                                 url_filename.toString())
>> +        elif item.theme_data.background_type == 'transparent':
>> +            background = u'background-color: transparent'
>> +        else:
>> +            background = build_background_css(item, item.renderer.width)
>> +        return background
>> +
>> +    def set_text(self, text, item):
>> +        """
>> +        Build the html to display the custom slide in wysiwyg mode
>> +
>> +        :param text: The slide text (unicode)
>> +        :param item: ServiceItem
>> +        :return  The complete html
>> +        """
>> +        self.undo_stack.clear()
>> +        self.last_id = 0
>> +        self.background = 'Theme'
>> +        self.screen_width = item.renderer.width
>> +        self.screen_height = item.renderer.height
>> +        if item.theme_data.font_main_override:
>> +            self.main_width = item.theme_data.font_main_width -\
>> +                item.theme_data.font_main_outline_size -\
>> +                item.theme_data.font_main_shadow_size
>> +            self.main_height = item.theme_data.font_main_height -\
>> +                item.theme_data.font_main_outline_size -\
>> +                item.theme_data.font_main_shadow_size
>> +        else:
>> +            self.main_width = item.main.width() - 1
>> +            self.main_height = item.main.height() - 1
>> +        self.max_image_width = self.main_width
>> +        self.max_image_height = self.main_height - (item.theme_data.font_main_size * 2)
>> +        html = u"""<!DOCTYPE html>
>> +<html>
>> +<head>
>> +    <style>
>> +        * {
>> +            margin: 0;
>> +            padding: 0;
>> +            border: 0;
>> +        }
>> +        [contenteditable="true"]:focus {
>> +            outline: none;
>> +        }
>> +        sup {
>> +            font-size: 0.6em;
>> +            vertical-align: top;
>> +            position: relative;
>> +            top: -0.3em;
>> +        }
>> +        .slides {
>> +            width: %dpx;
>> +            min-height: %dpx;
>> +            margin: 0px;
>> +            padding: 0px;
>> +            z-index: -5;
>> +        }
>> +        .slide {
>> +            width: %dpx;
>> +            min-height: %dpx;
>> +            %s;                             /* background or background-color */
>> +            margin: 10px;
>> +            padding: %dpx 0px 0px %dpx;
>> +            outline: black solid 1px;
>> +            z-index: -4;
>> +            overflow-x: hidden;
>> +        }
>> +        %s                                  /* build_lyrics_css from htmlbuilder */
>> +        .lyricscell {                       /* override */
>> +            -webkit-transition: none;
>> +            z-index: 5;
>> +            position:relative;
>> +            top: 0px;
>> +        }
>> +        .text_table {                       /* use this in place of lyricstable */
>> +            display: table;
>> +            background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAA1JREFUCNdjaGhoqAcABQQCALTD+iYAAAAASUVORK5CYII=') no-repeat;
> This needs to be added via code so it is easy to understand what the image is.
I agree.  This is the custom_overflow.png that I can't figure out how to 
update the resources/path in the windows environment.
>
>> +            background-position: 0px %dpx;
>> +            background-size: cover;
>> +            min-height: %dpx;
>> +            overflow: hidden;
>> +        }
>> +    </style>
>> +    <script type="text/javascript">
>> +        function setFocus(id) {
>> +            /*
>> +            Set focus to element with id - QWebElement.setFocus() does NOT give keyboard focus (blinking caret)
>> +            */
>> +            document.getElementById(id).focus();
>> +        }
>> +        function insertHTML(html) {
>> +            /*
>> +            Insert html string at caret/selection
>> +            */
>> +            document.execCommand('insertHTML', false, html);
>> +        }
>> +        function getSlideId() {
>> +            /*
>> +            Get the slide id of the slide that is being edited
>> +            */
>> +            var slide = window.getSelection().anchorNode;
>> +            while (slide) {
>> +                if (slide.className == 'slide') {
>> +                    return slide.id;
>> +                }
>> +                slide = slide.parentNode;
>> +            }
>> +            return 'None';
>> +        }
>> +        function mergeSlide(currSlideId) {
>> +            /*
>> +            Add the contents of the current slide to the end of the previous and delete current slide
>> +            */
>> +            var slides = document.getElementById('slides');
>> +            var currSlide = document.getElementById(currSlideId);
>> +            var prevSlide = currSlide.previousSibling;
>> +            var prevText = prevSlide.lastChild.firstChild;
>> +            var sel = window.getSelection();
>> +            sel.modify('move', 'backward', 'documentboundary');     // select the entire slide contents
>> +            sel.modify('extend', 'forward', 'documentboundary');
>> +            var range = sel.getRangeAt(0);
>> +            var frag = range.extractContents();                     // store removed selection in a fragment
>> +            window.location.hash = '#' + prevSlide.id;              // scroll to previous slide
>> +            prevText.focus();
>> +            sel = window.getSelection();
>> +            sel.modify('move', 'forward', 'documentboundary');      // move insertion point to end of slide
>> +            sel.getRangeAt(0).insertNode(frag);                     // paste the fragment into new slide
>> +            slides.removeChild(currSlide);                          // delete the current slide
>> +        }
>> +        function walkTheDOM(node, func) {
>> +            /*
>> +            Recursive DOM walker - based on code from Douglas Crockford
>> +            */
>> +            func(node);
>> +            node = node.firstChild;
>> +            while (node) {
>> +                walkTheDOM(node, func);
>> +                node = node.nextSibling;
>> +            }
>> +        }
>> +        function removeEmptyTags(slideNode) {
>> +            /*
>> +            Remove all elements that have empty text nodes
>> +            */
>> +            var elemIds = [];
>> +            walkTheDOM(slideNode, function (node) {
>> +                if (node.nodeType == 1 && node.hasAttribute('data-tag') &&
>> +                    node.tagName != 'IMG' && node.textContent == '') {
>> +                    elemIds.push(node.id);
>> +                }
>> +            });
>> +            for (var i=0;i<elemIds.length;i++) {
>> +                var elem = document.getElementById(elemIds[i]);
>> +                if (elem) {
>> +                    elem.parentNode.removeChild(elem);
>> +                }
>> +            }
>> +        }
>> +        function insertSlide(wrapper, id) {
>> +            /*
>> +            Split the current slide at the insertion point and create new slide element structure
>> +            */
>> +            var slides = document.getElementById('slides');         // parent div for all slides
>> +            var sel = window.getSelection();
>> +            var slide = sel.anchorNode;
>> +            var pathIds = [];
>> +            var pathTags = [];
>> +            while (slide) {                                         // find the current slide
>> +                if (slide.className == 'slide') {
>> +                    break;
>> +                }
>> +                if (slide.nodeType == 1 && slide.hasAttribute('data-tag')) {
>> +                    pathIds.push(slide.id);                         // store the path id's
>> +                    pathTags.push(slide.getAttribute('data-tag'));  // store the path tags
>> +                }
>> +                slide = slide.parentNode;
>> +            }
>> +            sel.modify('extend', 'forward', 'documentboundary');    // select to the end of current slide
>> +            var selText = sel.toString();
>> +            var range = sel.getRangeAt(0);
>> +            var frag = range.extractContents();                     // store removed selection in a fragment
>> +            removeEmptyTags(slide);                                 // extract may have left elements without text
>> +            var lastId = id;
>> +            if (selText == '') {                                    // if the slide insertion point doesn't have any
>> +                pathTags = [];                                      // text after the caret, do not carry tags forward
>> +            }
>> +            else {
>> +                while (pathIds.length) {                            // determine if the fragment contains path elements
>> +                    var pathId = pathIds.shift();
>> +                    var elem = frag.querySelector('#' + pathId);
>> +                    if (!elem) {                                    // doesn't have element, return the remaining
>> +                        break;                                      // path tags
>> +                    }
>> +                    lastId += 1;
>> +                    elem.id = 'elem' + lastId;                      // set the frag element id to a unique value
>> +                    pathTags.shift();                               // remove the path tag from the open tag list
>> +                }
>> +            }
>> +            var div = document.createElement('div');                // Create a temporary div
>> +            div.innerHTML = wrapper;                                // Put slide structure into div
>> +            slides.insertBefore(div.firstChild, slide.nextSibling); // Insert wrapper
>> +            window.location.hash = '#slide' + id;                   // scroll to new slide
>> +            document.getElementById('text' + id).focus();
>> +            window.getSelection().getRangeAt(0).insertNode(frag);   // paste the fragment into new slide
>> +            return lastId.toString() + '/' + pathTags.toString();
>> +        }
>> +        function cutCopy(doCut) {
>> +            /*
>> +            Cut or copy the current selection and return it and the open formatting tags
>> +            */
>> +            var sel = window.getSelection();
>> +            var slide = sel.anchorNode;
>> +            var pathIds = [];
>> +            var pathTags = [];
>> +            while (slide) {                                         // find the current slide
>> +                if (slide.className == 'slide') {
>> +                    break;
>> +                }
>> +                if (slide.nodeType == 1 && slide.hasAttribute('data-tag') && slide.tagName != 'IMG') {
>> +                    pathIds.push(slide.id);                         // store the path id's
>> +                    pathTags.push(slide.getAttribute('data-tag'));  // store the path tags
>> +                }
>> +                slide = slide.parentNode;
>> +            }
>> +            var range = sel.getRangeAt(0);
>> +            if (doCut) {
>> +                var frag = range.extractContents();
>> +                removeEmptyTags(slide);                             // Extraction may have left empty tags
>> +            }
>> +            else {                                                  // Clone the selection's contents - we'll take
>> +                var frag = range.cloneContents();                   // care of the duplicate id's in Python
>> +            }
>> +            while (pathIds.length) {                                // determine if the fragment contains path elements
>> +                var pathId = pathIds.shift();                       // FIFO
>> +                var elem = frag.querySelector('#' + pathId);        // search the fragment for the path id
>> +                if (!elem) {                                        // doesn't have element, return the remaining
>> +                    break;                                          // path tags
>> +                }
>> +                pathTags.shift();                                   // path id found, remove the path tag and try again
>> +            }
>> +            var div = document.createElement('div');                // Create an empty div
>> +            div.appendChild(frag);                                  // and append the fragment
>> +            var html = div.innerHTML;                               // Extract the html
>> +            return pathTags.toString() + '/' + html;
>> +        }
>> +        function focusNode(id) {
>> +            /*
>> +            Select the img element
>> +            */
>> +            var elem = document.getElementById(id);
>> +            var range = document.createRange();
>> +            range.selectNode(elem);
>> +            var sel = window.getSelection();
>> +            sel.removeAllRanges();
>> +            sel.addRange(range);
>> +        }
>> +        function saveSelection() {
>> +            /*
>> +            Return a comma delimited string of the current selection, modifying it if it's right-to-left
>> +            */
>> +            var sel = window.getSelection();
>> +            var range = document.createRange();
>> +            range.setStart(sel.anchorNode, sel.anchorOffset);
>> +            range.setEnd(sel.focusNode, sel.focusOffset);
>> +            if (range.collapsed) {                                  // The current selection was right-to-left,
>> +                range.setStart(sel.focusNode, sel.focusOffset);     // change it to a left-to-right so that the
>> +                range.setEnd(sel.anchorNode, sel.anchorOffset);     // range.surroundContents method will work
>> +                sel.removeAllRanges();
>> +                sel.addRange(range);
>> +            }
>> +            else {
>> +                range.detach();
>> +            }
>> +            var saveSel = {};
>> +            var elem = sel.anchorNode;
>> +            if (elem.nodeType == 1) {
>> +                saveSel.anchorPosition = sel.anchorOffset;
>> +                saveSel.anchorOffset = 0;
>> +            }
>> +            else {
>> +                saveSel.anchorPosition = 0;
>> +                saveSel.anchorOffset = sel.anchorOffset;
>> +                while (elem.previousSibling) {
>> +                    saveSel.anchorPosition += 1;
>> +                    elem = elem.previousSibling;
>> +                }
>> +                elem = elem.parentNode;
>> +            }
>> +            saveSel.anchorId = elem.id;
>> +            elem = sel.focusNode;
>> +            if (elem.nodeType == 1) {
>> +                saveSel.focusPosition = sel.focusOffset;
>> +                saveSel.focusOffset = 0;
>> +            }
>> +            else {
>> +                saveSel.focusPosition = 0;
>> +                saveSel.focusOffset = sel.focusOffset;
>> +                while (elem.previousSibling) {
>> +                    saveSel.focusPosition += 1;
>> +                    elem = elem.previousSibling;
>> +                }
>> +                elem = elem.parentNode;
>> +            }
>> +            saveSel.focusId = elem.id;
>> +            return saveSel.anchorId + ',' + saveSel.anchorPosition + ',' + saveSel.anchorOffset + ',' +
>> +                   saveSel.focusId + ',' + saveSel.focusPosition + ',' + saveSel.focusOffset;
>> +        }
>> +        function restoreSelection(anchorId, anchorPosition, anchorOffset, focusId, focusPosition, focusOffset) {
>> +            /*
>> +            Restore the window selection object to a previous state
>> +            */
>> +            var anchorElem = document.getElementById(anchorId);
>> +            anchorElem = anchorElem.firstChild;
>> +            var i = anchorPosition.valueOf();
>> +            while (i > 0) {
>> +                anchorElem = anchorElem.nextSibling;
>> +                i -= 1;
>> +            }
>> +            var focusElem = document.getElementById(focusId);
>> +            focusElem = focusElem.firstChild;
>> +            i = focusPosition.valueOf();
>> +            while (i > 0) {
>> +                focusElem = focusElem.nextSibling;
>> +                i -= 1;
>> +            }
>> +            var sel = window.getSelection();
>> +            var range = document.createRange();
>> +            range.setStart(anchorElem, anchorOffset.valueOf());
>> +            range.setEnd(focusElem, focusOffset.valueOf());
>> +            sel.removeAllRanges();
>> +            sel.addRange(range);
>> +        }
>> +        function insertTag(tagHtml) {
>> +            /*
>> +            Surround the current selection with the formatting tag html.  Operation will fail if the new tag
>> +            spans a partial existing tag
>> +            */
>> +            var range = window.getSelection().getRangeAt(0);
>> +            var frag = range.createContextualFragment(tagHtml);
>> +            try {
>> +                range.surroundContents(frag.firstChild);
>> +            }
>> +            catch(err) {
>> +                return false;
>> +            }
>> +            return true;
>> +        }
>> +        function removeTag(tag) {
>> +            /*
>> +            Search backwards from the current anchor node until the formatting {tag} is found.
>> +            Remove the tag but keep the text and all other child tags
>> +            */
>> +            var elem = window.getSelection().anchorNode;
>> +            while (elem) {
>> +                if (elem.nodeType == 1) {
>> +                    if (elem.getAttribute('data-tag') == tag) {
>> +                        var frag = document.createDocumentFragment();
>> +                        while (elem.firstChild) {
>> +                            frag.appendChild(elem.firstChild);
>> +                        }
>> +                        elem.parentNode.insertBefore(frag, elem);
>> +                        elem.parentNode.removeChild(elem);
>> +                        return;
>> +                    }
>> +                }
>> +                elem = elem.parentNode;
>> +            }
>> +            alert('Tag not found!');        // Should never happen
>> +        }
>> +        function getPath() {
>> +            /*
>> +            Find any open formatting tags before the anchor node and return in comma delimited string
>> +            */
>> +            var path = '';
>> +            var elem = window.getSelection().anchorNode;
>> +            while (elem) {
>> +                if (elem.nodeType == 1 && elem.hasAttribute('data-tag') && elem.tagName != 'IMG') {
>> +                    var attr = elem.getAttribute('data-tag');
>> +                    if (path) {
>> +                        path = attr + ',' + path;
>> +                    }
>> +                    else {
>> +                        path = attr;
>> +                    }
>> +                }
>> +                elem = elem.parentNode;
>> +            }
>> +            return path;
>> +        }
>> +    </script>
>> +</head>
>> +<body style='width: 100%%; height: 100%%; color: %s;'>
>> +    <div id="slides" class="slides">%s</div>
>> +</body>
>> +</html>""" % (self.screen_width + 22,  # .slides: width = screen width + border + margins
>> +              self.screen_height + 22,  # .slides: height
>> +              self.screen_width - item.main.left(),  # .slide: width
>> +              self.screen_height - item.main.top(),  # .slide: min-height
>> +              self.build_background_css(item),  # .slide: background or background-color
>> +              item.main.top(),  # .slide: padding-top
>> +              item.main.left(),  # .slide: padding-left
>> +              build_lyrics_css(item),  # .lyricstable, .lyricscell, .lyricsmain from htmlbuilder
>> +              item.main.bottom(),  # .text_table: background-position (top) of overflow image
>> +              self.main_height,  # .text_table: min-height
>> +              item.theme_data.font_main_color,  # body color used for setting caret color
>> +              self.text_to_html(text))  # slide text
>> +        self.set_zoom()
>> +        self.setHtml(html)
>> +        elem = self.frame.findFirstElement('.lyricscell.lyricsmain')
>> +        self.java('setFocus("%s")' % elem.attribute('id'))
>> +
>> +    def get_text(self):
>> +        """
>> +        Convert all of the slides from html to plain text with formatting tags
>> +
>> +        return: A string containing all slides in plain text
>> +        """
>> +        text = ''
>> +        slides = self.frame.documentElement().findAll('div.lyricscell.lyricsmain').toList()
>> +        for slide in slides:
>> +            if text:
>> +                text += u'\n[===]\n'  # create an insert marker for every slide after the first
>> +            while True:
>> +                elem = slide.findFirst(u'[data-tag]')  # find a QWebElement with a data-tag attribute
>> +                if elem.isNull():
>> +                    break
>> +                tag = elem.attribute(u'data-tag')  # get the tag name from the data-tag
>> +                if tag == u'{img}':  # replace <img src="filename" /> with {img}filename{/img}
>> +                    img_style = elem.attribute('style')
>> +                    img_style = img_style[:img_style.find(u' max-width:')]  # strip out max-width and height
>> +                    tag_str = u'<span>{img}"%s" style="%s"{/img}</span>' % \
>> +                              (elem.attribute('src'), img_style)
>> +                    elem.setOuterXml(tag_str)
>> +                else:  # replace <?>xxx</?> with {tag}xxx{/tag}
>> +                    tag_str = u'%s%s{/%s' % (tag, elem.toInnerXml(), tag[1:])
>> +                    elem.replace(tag_str)
>> +            text += slide.toPlainText()
>> +        return text
>> +
>> +    def get_path(self):
>> +        """
>> +        Get formatting tags in path from the selection anchor node
>> +
>> +        :return str: formatting tags delimited with commas
>> +        """
>> +        return str(self.java('getPath()'))
>> +
>> +    def do_split(self):
>> +        """
>> +        Insert our marker into current selection and check resulting text to determine if it is surrounded by line
>> +        breaks.  Replace marker with [---] optional split.
>> +        """
>> +        self.java('insertHTML("%s")' % self.marker)
>> +        html = self.active_text.toInnerXml()
>> +        text = self.active_text.toPlainText()
>> +        if text.find(u'\n' + self.marker) == -1:
>> +            html = html.replace(self.marker, u'<br />' + self.marker)
>> +        if text.find(self.marker + u'\n') == -1:
>> +            html = html.replace(self.marker, self.marker + u'<br />')
>> +        html = html.replace(self.marker, u'[---]')
>> +        self.active_text.setInnerXml(html)
>> +        self.java('setFocus("%s")' % self.active_text.attribute('id'))
>> +
>> +    def split(self):
>> +        """
>> +        Create UndoCommand and do_split()
>> +        """
>> +        cmd = MethodUndoCommand(self, self.do_split, translate('CustomPlugin.Editor', 'Split Slide'))
>> +        self.undo_stack.push(cmd)
>> +
>> +    def do_insert(self):
>> +        """
>> +        Split the slide at the current selection and create a new slide.  Scroll new slide into view and sets focus.
>> +        Note:  Open formatting tags are added to the new slide
>> +        """
>> +        wrapper = self.build_slide_wrapper('')  # create an empty slide structure
>> +        save_id = self.last_id  # Save the id of the new slide
>> +        result = str(self.java("""insertSlide('%s', %d)""" %  # insert new slide into document
>> +                               (wrapper, self.last_id)))
>> +        results = result.split('/', 1)
>> +        self.last_id = int(results[0])  # javascript function may have modified
>> +        path = results[1]
>> +        if path:  # Open formatting tags found
>> +            path_tags = path.split(',')
>> +            new_tags_start = ''
>> +            new_tags_end = ''
>> +            for tag in path_tags:
>> +                for html in FormattingTags.get_html_tags():
>> +                    if tag == html['start tag']:
>> +                        new_tags_start += self.insert_data_tag(html['start tag'], html['start html'])
>> +                        new_tags_end += html['end html']
>> +                        break
>> +            slide = self.frame.findFirstElement(u'#text%d' % save_id)
>> +            slide_html = new_tags_start + slide.toInnerXml() + new_tags_end
>> +            slide.setInnerXml(slide_html)
>> +            self.java('setFocus("elem%d")' % save_id)
>> +        if self.background != 'Theme':
>> +            self.active_slide.setStyleProperty(u'background', self.background)
>> +
>> +    def insert(self):
>> +        """
>> +        Create UndoCommand and do_insert()
>> +        """
>> +        cmd = MethodUndoCommand(self, self.do_insert, translate('CustomPlugin.Editor', 'Insert Slide'))
>> +        self.undo_stack.push(cmd)
>> +
>> +    def set_zoom(self):
>> +        """
>> +        Set the zoom factor of the editor
>> +        """
>> +        if self.zoom == 0.0:  # set zoom to fill the body of the QWebView
>> +            ratio_x = round(((self.width() - QStyle.PM_ScrollBarExtent - 10) /
>> +                             (self.screen_width + 22 + QStyle.PM_ScrollBarExtent)) - 0.004, 2)
>> +            if self.zoomFactor() != ratio_x:
>> +                self.setZoomFactor(ratio_x)
>> +        else:
>> +            self.setZoomFactor(self.zoom)
>> +        self.selectionChanged.emit()  # Update the zoom label
>> +
>> +    def get_text_div(self, slide_div):
>> +        """
>> +        Return the text div wrapper element for a given slide with an id of 'slideXXX'
>> +
>> +        :param QWebElement slide_div: The slide div wrapper
>> +        :return QWebElement The slide text wrapper
>> +        """
>> +        text_id = u'#text' + slide_div.attribute('id')[5:]  # Skip over 'slide' to get numeric id
>> +        text_div = self.frame.findFirstElement(text_id)
>> +        return text_div
>> +
>> +    def image_clicked(self, ctrl_key_down, pos_x, pos_y):
>> +        """
>> +        Determine if the mouse click was on an img element.  If so, focus the img node
>> +
>> +        :param ctrl_key_down: True if CTRL was pushed
>> +        :param pos_x: from event.pos().x()
>> +        :param pos_y: from event.pos().y()
>> +        :return True if image under cursor
>> +        """
>> +        img_list = self.frame.findAllElements('img').toList()
>> +        rel_x = self.frame.scrollBarValue(QtCore.Qt.Horizontal) + pos_x
>> +        rel_y = self.frame.scrollBarValue(QtCore.Qt.Vertical) + pos_y
>> +        self.image_selected = None
>> +        for img in img_list:
>> +            if img.geometry().contains(rel_x, rel_y, True):
>> +                if img.attribute('style').find('position: absolute;') > -1:  # We have a background image
>> +                    if not ctrl_key_down:
>> +                        break  # Only select if Ctrl key is down
>> +                self.java('focusNode("%s")' % img.attribute('id'))
>> +                self.image_selected = img
>> +                break
>> +        return self.image_selected
>> +
>> +    def has_background_image(self):
>> +        """
>> +        Determine if the current slide has a background image
>> +        """
>> +        if self.active_text:
>> +            return self.active_text.toInnerXml().find('position: absolute;') > -1
>> +        else:
>> +            return False
>> +
>> +    def on_selection_changed(self):
>> +        """
>> +        Set the QWebElements active_slide and active_text to point to the div wrappers of the current selection anchor
>> +        """
>> +        if not self.typing:
>> +            self.text_undo_command = None  # Any typing must use a new text undo command after a selection change
>> +        slide_id = str(self.java('getSlideId()'))
>> +        if slide_id == 'None':
>> +            self.active_slide = None
>> +            self.active_slide = None
>> +        else:
>> +            self.active_slide = self.frame.findFirstElement('#' + slide_id)
>> +            self.active_text = self.get_text_div(self.active_slide)
>> +
>> +    def on_clipboard_changed(self):
>> +        """
>> +        Set the ownership flag to False - only paste HTML if we put it there
>> +        """
>> +        self.clipboard_owned = False
>> +
>> +    def keyPressEvent(self, event):
>> +        """
>> +        Intercept key press - send only the navigation key events to the web view
>> +
>> +        :param event:
>> +        """
>> +        if event.matches(QtGui.QKeySequence.Cut):
>> +            self.on_cut(translate('CustomPlugin.Editor', 'Cut'))
>> +        elif event.matches(QtGui.QKeySequence.Copy):
>> +            self.on_copy()
>> +        elif event.matches(QtGui.QKeySequence.Paste):
>> +            self.on_paste_html(translate('CustomPlugin.Editor', 'Paste HTML'))
>> +        elif event.matches(QtGui.QKeySequence.SelectAll):
>> +            self.on_select_all()
>> +        elif event.matches(QtGui.QKeySequence.Undo):
>> +            self.undo_stack.undo()
>> +        elif event.matches(QtGui.QKeySequence.Redo):
>> +            self.undo_stack.redo()
>> +        elif event.key() == QtCore.Qt.Key_Delete:
>> +            save_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, event.key(), event.modifiers(), event.text())
>> +            cmd = MethodUndoCommand(self, QtWebKit.QWebView.keyPressEvent,
>> +                                    translate('CustomPlugin.Editor', 'Delete'), self, save_event)
>> +            self.undo_stack.push(cmd)
>> +        elif event.key() == QtCore.Qt.Key_Backspace:
>> +            save_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, event.key(), event.modifiers(), event.text())
>> +            cmd = MethodUndoCommand(self, QtWebKit.QWebView.keyPressEvent,
>> +                                    translate('CustomPlugin.Editor', 'Backspace'), self, save_event)
>> +            self.undo_stack.push(cmd)
>> +        elif event.key() in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
>> +            # Set the shift key to force a <br> insertion
>> +            save_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, QtCore.Qt.Key_Return, QtCore.Qt.ShiftModifier, '\n')
>> +            cmd = MethodUndoCommand(self, QtWebKit.QWebView.keyPressEvent,
>> +                                    translate('CustomPlugin.Editor', 'New Line'), self, save_event)
>> +            self.undo_stack.push(cmd)
>> +        elif event.key() == QtCore.Qt.Key_PageUp:
>> +            # Handle this ourselves to prevent crash
>> +            self.frame.scroll(0, -self.height())
>> +        elif event.key() == QtCore.Qt.Key_PageDown:
>> +            # Handle this ourselves to prevent crash
>> +            self.frame.scroll(0, self.height())
>> +        elif event.text():
>> +            text = event.text()
>> +            if text == '<':
>> +                text = u'&lt;'
>> +            elif text == '>':
>> +                text = u'&gt;'
>> +            elif text == "'":
>> +                text = u'&#39;'
>> +            elif text == '"':
>> +                text = u'&quot;'
>> +            if text == ' ' and self.text_undo_command:
>> +                self.text_undo_command = None  # Don't append text on a word break
>> +            if self.text_undo_command:
>> +                self.text_undo_command.append_text(text)
>> +            else:
>> +                self.text_undo_command = TextUndoCommand(self, translate('CustomPlugin.Editor', 'Typing'), text)
>> +                self.undo_stack.push(self.text_undo_command)
>> +        elif event.key() in (QtCore.Qt.Key_Left, QtCore.Qt.Key_Right, QtCore.Qt.Key_Up, QtCore.Qt.Key_Down,
>> +                             QtCore.Qt.Key_Home, QtCore.Qt.Key_End):
>> +            QtWebKit.QWebView.keyPressEvent(self, event)
>> +
>> +    def resizeEvent(self, event):
>> +        """
>> +        Window size changed, the zoom factor may need changing
>> +
>> +        :param event:
>> +        """
>> +        QtWebKit.QWebView.resizeEvent(self, event)
>> +        if self.screen_width > 0:
>> +            self.set_zoom()
>> +
>> +    def mousePressEvent(self, event):
>> +        """
>> +        Handle mouse clicks within the QWebView
>> +
>> +        :param event:
>> +        """
>> +        if self.frame.scrollBarGeometry(QtCore.Qt.Vertical).contains(event.pos()) or \
>> +                self.frame.scrollBarGeometry(QtCore.Qt.Horizontal).contains(event.pos()):
>> +            # ToDo: The scrollbar was clicked - need a solution for the caret which has stopped blinking...
>> +            pass
>> +        elif self.image_clicked(event.modifiers() & QtCore.Qt.CTRL, event.pos().x(), event.pos().y()):
>> +            # An image was clicked, don't send mouse press to parent
>> +            return
>> +        elif event.button() == QtCore.Qt.RightButton:
>> +            # Rewrite the mouse event to a left button event so the cursor is moved to the location of the pointer
>> +            event = QtGui.QMouseEvent(QtCore.QEvent.MouseButtonPress,
>> +                                      event.pos(), QtCore.Qt.LeftButton, QtCore.Qt.LeftButton, QtCore.Qt.NoModifier)
>> +        QtWebKit.QWebView.mousePressEvent(self, event)
>> +
>> +    def wheelEvent(self, event):
>> +        """
>> +        Override the event to create a page scroll.  Without the override, WebView moves to the top/bottom
>> +
>> +        :param event:
>> +        """
>> +        if event.delta() > 0:  # page up
>> +            delta = -self.height()
>> +        else:  # page down
>> +            delta = self.height()
>> +        self.frame.scroll(0, delta)
>> +
>> +    def customContextMenuRequested(self, pos):
>> +        """
>> +        Build the custom context menu
>> +
>> +        :param pos:
>> +        """
>> +        popup_menu = QtGui.QMenu(self)
>> +        # Undo/Redo
>> +        action = self.undo_stack.createUndoAction(popup_menu)
>> +        action.setShortcut(QtGui.QKeySequence.Undo)
>> +        popup_menu.addAction(action)
>> +        action = self.undo_stack.createRedoAction(popup_menu)
>> +        action.setShortcut(QtGui.QKeySequence.Redo)
>> +        popup_menu.addAction(action)
>> +        popup_menu.addSeparator()
>> +        # Cut/Copy/Paste/Select Slide
>> +        action = MenuAction(translate('CustomPlugin.Editor', 'Cut'), popup_menu)
>> +        action.setShortcut(QtGui.QKeySequence.Cut)
>> +        action.menu_action.connect(self.on_cut)
>> +        action.setEnabled(self.selectedHtml() != '')
>> +        popup_menu.addAction(action)
>> +        action = MenuAction(translate('CustomPlugin.Editor', 'Copy'), popup_menu)
>> +        action.setShortcut(QtGui.QKeySequence.Copy)
>> +        action.menu_action.connect(self.on_copy)
>> +        action.setEnabled(self.selectedHtml() != '')
>> +        popup_menu.addAction(action)
>> +        mime = self.clipboard.mimeData()
>> +        action = MenuAction(translate('CustomPlugin.Editor', 'Paste HTML'), popup_menu)
>> +        action.setShortcut(QtGui.QKeySequence.Paste)
>> +        action.setEnabled(self.clipboard_owned)
>> +        action.menu_action.connect(self.on_paste_html)
>> +        popup_menu.addAction(action)
>> +        action = MenuAction(translate('CustomPlugin.Editor', 'Paste Text'), popup_menu)
>> +        action.setEnabled(mime.hasText())
>> +        action.menu_action.connect(self.on_paste_text)
>> +        popup_menu.addAction(action)
>> +        action = MenuAction(translate('CustomPlugin.Editor', 'Select Slide'), popup_menu)
>> +        action.setShortcut(QtGui.QKeySequence.SelectAll)
>> +        action.menu_action.connect(self.on_select_all)
>> +        popup_menu.addAction(action)
>> +        popup_menu.addSeparator()
>> +        # Insert punctuation
>> +        punctuation_menu = QtGui.QMenu(translate('CustomPlugin.Editor', 'Insert Punctuation'))
>> +        action = MenuAction('– ndash', punctuation_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        punctuation_menu.addAction(action)
>> +        action = MenuAction('— mdash', punctuation_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        punctuation_menu.addAction(action)
>> +        action = MenuAction('¡ iexcl', punctuation_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        punctuation_menu.addAction(action)
>> +        action = MenuAction('¿ iquest', punctuation_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        punctuation_menu.addAction(action)
>> +        action = MenuAction('“ ldquo', punctuation_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        punctuation_menu.addAction(action)
>> +        action = MenuAction('” rdquo', punctuation_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        punctuation_menu.addAction(action)
>> +        action = MenuAction('‘ lsquo', punctuation_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        punctuation_menu.addAction(action)
>> +        action = MenuAction('’ rsquo', punctuation_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        punctuation_menu.addAction(action)
>> +        action = MenuAction('« laquo', punctuation_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        punctuation_menu.addAction(action)
>> +        action = MenuAction('» raquo', punctuation_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        punctuation_menu.addAction(action)
>> +        action = MenuAction('_ nbsp', punctuation_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        punctuation_menu.addAction(action)
>> +        popup_menu.addMenu(punctuation_menu)
>> +        # Insert symbol
>> +        symbol_menu = QtGui.QMenu(translate('CustomPlugin.Editor', 'Insert Symbol'))
>> +        action = MenuAction('• bull', symbol_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        symbol_menu.addAction(action)
>> +        action = MenuAction('¢ cent', symbol_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        symbol_menu.addAction(action)
>> +        action = MenuAction('© copy', symbol_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        symbol_menu.addAction(action)
>> +        action = MenuAction('° deg', symbol_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        symbol_menu.addAction(action)
>> +        action = MenuAction('÷ divide', symbol_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        symbol_menu.addAction(action)
>> +        action = MenuAction('€ euro', symbol_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        symbol_menu.addAction(action)
>> +        action = MenuAction('· middot', symbol_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        symbol_menu.addAction(action)
>> +        action = MenuAction('¶ para', symbol_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        symbol_menu.addAction(action)
>> +        action = MenuAction('± plusmn', symbol_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        symbol_menu.addAction(action)
>> +        action = MenuAction('£ pound', symbol_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        symbol_menu.addAction(action)
>> +        action = MenuAction('® reg', symbol_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        symbol_menu.addAction(action)
>> +        action = MenuAction('§ sect', symbol_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        symbol_menu.addAction(action)
>> +        action = MenuAction('™ trade', symbol_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        symbol_menu.addAction(action)
>> +        action = MenuAction('¥ yen', symbol_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        symbol_menu.addAction(action)
>> +        popup_menu.addMenu(symbol_menu)
>> +        # Insert diacritic
>> +        diacritic_menu = QtGui.QMenu(translate('CustomPlugin.Editor', 'Insert Diacritic'))
>> +        action = MenuAction('á aacute', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('Á Aacute', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('à agrave', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('À Agrave', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('â acirc', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('Â Acirc', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('å aring', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('Å Aring', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('ã atilde', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('Ã Atilde', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('ä auml', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('Ä Auml', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('æ aelig', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('Æ AElig', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('ç ccedil', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('Ç Ccedil', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('é eacute', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('É Eacute', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('è egrave', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('È Egrave', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('ê ecirc', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('Ê Ecirc', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('ë euml', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('Ë Euml', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('í iacute', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('Í Iacute', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('ì igrave', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('Ì Igrave', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('î icirc', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('Î Icirc', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('ï iuml', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('Ï Iuml', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('ñ ntilde', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('Ñ Ntilde', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('ó oacute', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('Ó Oacute', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('ò ograve', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('Ò Ograve', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('ô ocirc', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('Ô Ocirc', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('ø oslash', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('Ø Oslash', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('õ otilde', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction(' Õ Otilde', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('ö ouml', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('Ö Ouml', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('ß szlig', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('ú uacute', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('Ú Uacute', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('ù ugrave', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('Ù Ugrave', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('û ucirc', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('Û Ucirc', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('ü uuml', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('Ü Uuml', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        action = MenuAction('ÿ yuml', diacritic_menu)
>> +        action.menu_action.connect(self.on_insert_html_character)
>> +        diacritic_menu.addAction(action)
>> +        popup_menu.addMenu(diacritic_menu)
>> +        popup_menu.addSeparator()
>> +        # Insert/Remove formatting tags
>> +        if self.selectedText():
>> +            tag_menu = QtGui.QMenu(translate('CustomPlugin.Editor', 'Insert Formatting Tag'))
>> +            tag_system_menu = QtGui.QMenu(translate('CustomPlugin.Editor', 'System'))
>> +            tag_custom_menu = QtGui.QMenu(translate('CustomPlugin.Editor', 'Custom'))
>> +            for html in FormattingTags.get_html_tags():
>> +                if html['start tag'] != '{img}':
>> +                    if html['protected']:
>> +                        active_menu = tag_system_menu
>> +                    else:
>> +                        active_menu = tag_custom_menu
>> +                    action = MenuAction(html['desc'], active_menu)
>> +                    action.menu_action.connect(self.on_insert_tag)
>> +                    active_menu.addAction(action)
>> +            tag_menu.addMenu(tag_system_menu)
>> +            tag_menu.addMenu(tag_custom_menu)
>> +            popup_menu.addMenu(tag_menu)
>> +            action = MenuAction(translate('CustomPlugin.Editor', 'Remove Formatting Tag'), popup_menu)
>> +            action.setEnabled(False)
>> +            popup_menu.addAction(action)
>> +        else:
>> +            action = MenuAction(translate('CustomPlugin.Editor', 'Insert Formatting Tag'), popup_menu)
>> +            action.setEnabled(False)
>> +            popup_menu.addAction(action)
>> +            path = self.get_path()
>> +            if path:
>> +                remove_tag_menu = QtGui.QMenu(translate('CustomPlugin.Editor', 'Remove Formatting Tag'))
>> +                for html in FormattingTags.get_html_tags():
>> +                    if path.find(html['start tag']) > -1:
>> +                        action = MenuAction(html['desc'], remove_tag_menu)
>> +                        action.menu_action.connect(self.on_remove_tag)
>> +                        remove_tag_menu.addAction(action)
>> +                popup_menu.addMenu(remove_tag_menu)
>> +            else:
>> +                action = MenuAction(translate('CustomPlugin.Editor', 'Remove Formatting Tag'), popup_menu)
>> +                action.setEnabled(False)
>> +                popup_menu.addAction(action)
>> +        popup_menu.addSeparator()
>> +        # Insert/Edit Image
>> +        action = MenuAction(translate('CustomPlugin.Editor', 'Insert Image...'), popup_menu)
>> +        action.setEnabled(self.image_selected is None)
>> +        action.menu_action.connect(self.on_insert_image)
>> +        popup_menu.addAction(action)
>> +        action = MenuAction(translate('CustomPlugin.Editor', 'Edit Image...'), popup_menu)
>> +        action.setEnabled(self.image_selected is not None)
>> +        action.menu_action.connect(self.on_edit_image)
>> +        popup_menu.addAction(action)
>> +        popup_menu.addSeparator()
>> +        # Merge/Up/Down/Delete
>> +        prev_slide_found = not self.active_slide.previousSibling().isNull()
>> +        next_slide_found = not self.active_slide.nextSibling().isNull()
>> +        action = MenuAction(translate('CustomPlugin.Editor', 'Merge With Previous Slide'), popup_menu)
>> +        action.setEnabled(prev_slide_found)
>> +        action.menu_action.connect(self.on_merge)
>> +        popup_menu.addAction(action)
>> +        action = MenuAction(translate('CustomPlugin.Editor', 'Move Slide Up'), popup_menu)
>> +        action.setEnabled(prev_slide_found)
>> +        action.menu_action.connect(self.on_move_up)
>> +        popup_menu.addAction(action)
>> +        action = MenuAction(translate('CustomPlugin.Editor', 'Move Slide Down'), popup_menu)
>> +        action.setEnabled(next_slide_found)
>> +        action.menu_action.connect(self.on_move_down)
>> +        popup_menu.addAction(action)
>> +        action = MenuAction(translate('CustomPlugin.Editor', 'Delete Slide'), popup_menu)
>> +        action.setEnabled(prev_slide_found or next_slide_found)
>> +        action.menu_action.connect(self.on_delete)
>> +        popup_menu.addAction(action)
>> +        popup_menu.addSeparator()
>> +        # Zoom/Background
>> +        zoom_menu = QtGui.QMenu(translate('CustomPlugin.Editor', 'Scale Slide'))
>> +        action = MenuAction(translate('CustomPlugin.Editor', 'Fit Width'), zoom_menu)
>> +        action.setCheckable(True)
>> +        action.setChecked(self.zoom == 0.0)
>> +        action.menu_action.connect(self.on_zoom_fit_width)
>> +        zoom_menu.addAction(action)
>> +        action = MenuAction(translate('CustomPlugin.Editor', 'Actual Size'), zoom_menu)
>> +        action.setCheckable(True)
>> +        action.setChecked(self.zoom == 1.0)
>> +        action.menu_action.connect(self.on_zoom_actual_size)
>> +        zoom_menu.addAction(action)
>> +        zoom = 75
>> +        while zoom > 0:
>> +            action = MenuAction('%d%%' % zoom, zoom_menu)
>> +            action.setCheckable(True)
>> +            action.setChecked(zoom / 100 == self.zoom)
>> +            action.menu_action.connect(self.on_zoom_percent)
>> +            zoom_menu.addAction(action)
>> +            zoom -= 25
>> +        popup_menu.addMenu(zoom_menu)
>> +        bg_menu = QtGui.QMenu(translate('CustomPlugin.Editor', 'Set Background'))
>> +        action = MenuAction(translate('CustomPlugin.Editor', 'Theme'), bg_menu)
>> +        action.setCheckable(True)
>> +        action.setChecked(self.background == 'Theme')
>> +        action.menu_action.connect(self.on_background_theme)
>> +        bg_menu.addAction(action)
>> +        action = MenuAction(translate('CustomPlugin.Editor', 'Override Color'), bg_menu)
>> +        action.setCheckable(True)
>> +        action.setChecked(self.background != 'Theme')
>> +        action.menu_action.connect(self.on_background_color)
>> +        bg_menu.addAction(action)
>> +        popup_menu.addMenu(bg_menu)
>> +        popup_menu.exec(self.mapToGlobal(pos))
>> +
>> +    def create_unique_ids(self, html):
>> +        """
>> +        Generate new id's for every element in html
>> +
>> +        :param html:
>> +        """
>> +        start_pos = 0
>> +        while True:
>> +            start_pos = html.find(u' id="elem', start_pos)
>> +            if start_pos == -1:
>> +                break
>> +            start_pos += len(u' id="')
>> +            end_pos = html.find(u'"', start_pos)
>> +            elem_id = html[start_pos:end_pos]
>> +            self.last_id += 1
>> +            html = html.replace(elem_id, u'elem%d' % self.last_id, 1)
>> +        return html
>> +
>> +    def add_path_tags(self, html_str, path):
>> +        """
>> +        Add open formatting tags to the html string
>> +
>> +        :param html_str:
>> +        :param path:
>> +        """
>> +        new_tags_start = ''
>> +        new_tags_end = ''
>> +        path_tags = path.split(',')
>> +        for tag in path_tags:
>> +            for html in FormattingTags.get_html_tags():
>> +                if tag == html['start tag']:
>> +                    new_tags_start += self.insert_data_tag(html['start tag'], html['start html'])
>> +                    new_tags_end += html['end html']
>> +                    break
>> +        return new_tags_start + html_str + new_tags_end
>> +
>> +    def do_cut_copy(self, cut):
>> +        """
>> +        Cut or copy the selection and place the HTML and plain text in the clipboard.  The built in execCommand('cut')
>> +        cannot be used because it combines all of the styles of the selection's parent and we lose the formatting tags
>> +
>> +        :param cut: True to cut selection
>> +        """
>> +        if cut:
>> +            result = self.java('cutCopy(true)')
>> +        else:
>> +            result = self.java('cutCopy(false)')
>> +        results = result.split('/', 1)
>> +        path = str(results[0])
>> +        selected_html = str(results[1])
>> +        if not cut:
>> +            selected_html = self.create_unique_ids(selected_html)
>> +        if path:
>> +            selected_html = self.add_path_tags(selected_html, path)
>> +        mime = QtCore.QMimeData()
>> +        mime.setHtml(selected_html)
>> +        if self.selectedText():
>> +            mime.setText(self.selectedText())
>> +        self.clipboard.setMimeData(mime)
>> +        self.clipboard_owned = True
>> +
>> +    def on_cut(self, menu_text):
>> +        """
>> +        Create undo command and do_cut_copy(True)
>> +
>> +        :param menu_text:
>> +        """
>> +        cmd = MethodUndoCommand(self, self.do_cut_copy, menu_text, True)
>> +        self.undo_stack.push(cmd)
>> +
>> +    def on_copy(self):
>> +        """
>> +        Copy the selection to the clipboard
>> +        """
>> +        self.do_cut_copy(False)
>> +
>> +    def do_paste(self, text_html):
>> +        """
>> +        Insert the text or html at current caret position
>> +
>> +        :param text_html:
>> +        """
>> +        self.java("""insertHTML('%s')""" % text_html)
>> +
>> +    def on_paste_html(self, menu_text):
>> +        """
>> +        Reformat the clipboard html data and create UndoCommand do_paste()
>> +
>> +        :param menu_text:
>> +        """
>> +        mime = self.clipboard.mimeData()
>> +        if mime.hasHtml() and self.clipboard_owned:
>> +            cmd = MethodUndoCommand(self, self.do_paste, menu_text, mime.html())
>> +            self.undo_stack.push(cmd)
>> +        else:
>> +            self.on_paste_text(translate('CustomPlugin.Editor', 'Paste Text'))
>> +
>> +    def on_paste_text(self, menu_text):
>> +        """
>> +        Get the plain text from the clipboard, reformat, and create UndoCommand do_paste()
>> +
>> +        :param menu_text:
>> +        """
>> +        mime = self.clipboard.mimeData()
>> +        if mime.hasText():
>> +            text = mime.text()
>> +            text.encode('utf-8')
>> +            text = text.replace(u'<', u'&lt;')
>> +            text = text.replace(u'>', u'&gt;')
>> +            text = text.replace(u'\n', u'<br />')
>> +            text = text.replace(u"'", u'&#39;')
>> +            text = text.replace(u'"', u'&quot;')
>> +            cmd = MethodUndoCommand(self, self.do_paste, menu_text, text)
>> +            self.undo_stack.push(cmd)
>> +
>> +    def on_select_all(self):
>> +        """
>> +        Select the contents of the currently active slide
>> +        """
>> +        self.image_selected = None
>> +        self.page().triggerAction(QtWebKit.QWebPage.SelectAll)
>> +
>> +    def on_insert_html_character(self, menu_text):
>> +        """
>> +        Insert the special character
>> +
>> +        :param menu_text:
>> +        """
>> +        param = 'insertHTML("&%s;")' % menu_text[menu_text.find(' ') + 1:]
>> +        cmd = MethodUndoCommand(self, self.java, translate('CustomPlugin.Editor', 'Insert HTML Character'), param)
>> +        self.undo_stack.push(cmd)
>> +
>> +    def on_insert_image(self, menu_text):
>> +        """
>> +        Display the edit image dialog and insert image at caret or selection
>> +
>> +        :param menu_text:
>> +        """
>> +        self.edit_image_form.set_image(self.max_image_width, self.max_image_height)
>> +        if self.edit_image_form.exec_():
>> +            name = self.edit_image_form.get_image_name()
>> +            style = self.edit_image_form.get_image_style()
>> +            self.last_id += 1
>> +            param = """insertHTML('<img id="elem%d" src="%s" data-tag="{img}" style="%s" />')""" % \
>> +                    (self.last_id, name, style)
>> +            cmd = MethodUndoCommand(self, self.java, menu_text, param)
>> +            self.undo_stack.push(cmd)
>> +
>> +    def on_edit_image(self, menu_text):
>> +        """
>> +        Display the edit image dialog for the currently selected image and set the image attributes
>> +
>> +        :param menu_text:
>> +        """
>> +        self.edit_image_form.set_image(self.max_image_width, self.max_image_height,
>> +                                       self.image_selected.attribute('src'), self.image_selected.attribute('style'))
>> +        if self.edit_image_form.exec_():
>> +            name = self.edit_image_form.get_image_name()
>> +            style = self.edit_image_form.get_image_style()
>> +            cmd = ImageUndoCommand(self, menu_text, name, style)
>> +            self.undo_stack.push(cmd)
>> +
>> +    def do_merge(self):
>> +        """
>> +        Add the contents of the current slide to the previous slide, then delete current slide
>> +        """
>> +        self.java('mergeSlide("%s")' % self.active_slide.attribute('id'))
>> +
>> +    def on_merge(self, menu_text):
>> +        """
>> +        Create undo command and do_merge()
>> +
>> +        :param menu_text:
>> +        """
>> +        cmd = MethodUndoCommand(self, self.do_merge, menu_text)
>> +        self.undo_stack.push(cmd)
>> +
>> +    def do_move_up(self):
>> +        """
>> +        Swap the contents of the current slide with the previous slide
>> +        """
>> +        prev_slide = self.active_slide.previousSibling()
>> +        prev_text = self.get_text_div(prev_slide)
>> +        html = prev_text.toInnerXml()
>> +        prev_text.setInnerXml(self.active_text.toInnerXml())
>> +        self.active_text.setInnerXml(html)
>> +        self.frame.scrollToAnchor(prev_slide.attribute('id'))
>> +        self.java('setFocus("%s")' % prev_text.attribute('id'))
>> +
>> +    def on_move_up(self, menu_text):
>> +        """
>> +        Create undo command and do_move_up()
>> +
>> +        :param menu_text:
>> +        """
>> +        cmd = MethodUndoCommand(self, self.do_move_up, menu_text)
>> +        self.undo_stack.push(cmd)
>> +
>> +    def do_move_down(self):
>> +        """
>> +        Swap the contents of the current slide with the next slide
>> +        """
>> +        next_slide = self.active_slide.nextSibling()
>> +        next_text = self.get_text_div(next_slide)
>> +        html = next_text.toInnerXml()
>> +        next_text.setInnerXml(self.active_text.toInnerXml())
>> +        self.active_text.setInnerXml(html)
>> +        self.frame.scrollToAnchor(next_slide.attribute('id'))
>> +        self.java('setFocus("%s")' % next_text.attribute('id'))
>> +
>> +    def on_move_down(self, menu_text):
>> +        """
>> +        Create undo command and do_move_down()
>> +
>> +        :param menu_text:
>> +        """
>> +        cmd = MethodUndoCommand(self, self.do_move_down, menu_text)
>> +        self.undo_stack.push(cmd)
>> +
>> +    def do_delete(self):
>> +        """
>> +        Delete the current slide
>> +        """
>> +        next_slide = self.active_slide.previousSibling()
>> +        if next_slide.isNull():
>> +            next_slide = self.active_slide.nextSibling()
>> +        next_text = self.get_text_div(next_slide)
>> +        self.active_slide.removeFromDocument()
>> +        self.frame.scrollToAnchor(next_slide.attribute('id'))
>> +        self.java('setFocus("%s")' % next_text.attribute('id'))
>> +
>> +    def on_delete(self, menu_text):
>> +        """
>> +        Create undo command and do_delete()
>> +
>> +        :param menu_text:
>> +        """
>> +        cmd = MethodUndoCommand(self, self.do_delete, menu_text)
>> +        self.undo_stack.push(cmd)
>> +
>> +    def on_zoom_fit_width(self):
>> +        """
>> +        Set the zoom factor to fit the width of the editor window
>> +        """
>> +        self.zoom = 0.0
>> +        self.set_zoom()
>> +
>> +    def on_zoom_actual_size(self):
>> +        """
>> +        Set the zoom factor to actual size
>> +        """
>> +        self.zoom = 1.0
>> +        self.set_zoom()
>> +
>> +    def on_zoom_percent(self, menu_text):
>> +        """
>> +        Set the zoom factor of the editor to the menu_text
>> +
>> +        :param menu_text:
>> +        """
>> +        self.zoom = float(str(menu_text[0:2])) / 100.0  # Convert the menu text (less % sign) to zoom value
>> +        self.set_zoom()
>> +
>> +    def on_background_theme(self):
>> +        """
>> +        Remove the background color override
>> +        """
>> +        self.background = 'Theme'
>> +        slides = self.frame.documentElement().findAll('div.slide')
>> +        for slide in slides:
>> +            slide.setStyleProperty('background', '')
>> +
>> +    def on_background_color(self):
>> +        """
>> +        Display the color picker and override the background with selected color
>> +        """
>> +        color_dialog = QtGui.QColorDialog()
>> +        new_color = color_dialog.getColor(self.background_color, self, '')
>> +        if new_color.isValid():
>> +            self.background_color = new_color
>> +            self.background = '#%02X%02X%02X' % (new_color.red(), new_color.green(), new_color.blue())
>> +            slides = self.frame.documentElement().findAll('div.slide')
>> +            for slide in slides:
>> +                slide.setStyleProperty('background', self.background)
>> +
>> +    def do_insert_tag(self, tag_html):
>> +        """
>> +        Wrap the formatting tag html around the current selection
>> +
>> +        :param tag_html:
>> +        """
>> +        result = bool(self.java("""insertTag('%s')""" % tag_html))
>> +        if not result:
>> +            msg = translate('CustomPlugin.Editor', 'Unable to insert tag.\n\n'
>> +                                                   'The selected text spans an existing tag boundary.')
>> +            critical_error_message_box(message=msg)
>> +            self.undo_stack.setIndex(self.undo_stack.index() - 1)
>> +
>> +    def on_insert_tag(self, menu_text):
>> +        """
>> +        Build the html from the formatting tag, create undo command and do_insert_tag()
>> +
>> +        :param menu_text:
>> +        """
>> +        for html in FormattingTags.get_html_tags():
>> +            if menu_text == html['desc']:
>> +                tag_html = '%s%s' % (self.insert_data_tag(html['start tag'], html['start html']),
>> +                                     html['end html'])
>> +                undo_name = translate('CustomPlugin.Editor', 'Insert Tag: ') + menu_text
>> +                cmd = MethodUndoCommand(self, self.do_insert_tag, undo_name, tag_html)
>> +                self.undo_stack.push(cmd)
>> +                break
>> +
>> +    def on_remove_tag(self, menu_text):
>> +        """
>> +        Remove the first matching formatting tag in path, starting from caret and searching backwards
>> +
>> +        :param menu_text:
>> +        """
>> +        for html in FormattingTags.get_html_tags():
>> +            if menu_text == html['desc']:
>> +                tag = html['start tag']
>> +                param = 'removeTag("%s")' % tag
>> +                undo_name = translate('CustomPlugin.Editor', 'Remove Tag: ') + menu_text
>> +                cmd = MethodUndoCommand(self, self.java, undo_name, param)
>> +                self.undo_stack.push(cmd)
>> +                break
>> +
>> +
>> +class CustomUndoCommand(QtGui.QUndoCommand):
>> +    """
>> +    The custom base class for all of the UndoCommands pushed to the undo_stack - saves editor state and defines a
>> +    method to restore the selection, and another to set the scrollbar positions.
>> +    The undo method overrides the base class and restores the editor state.
>> +    Ancestors must implement the redo function.
>> +    """
>> +
>> +    def __init__(self, editor, description):
>> +        """
>> +        :param editor: HtmlEditor
>> +        :param description: Text to display for undo/redo
>> +        """
>> +        super(CustomUndoCommand, self).__init__(description)
>> +        self.editor = editor
>> +        # Save the current editor state
>> +        self.slides = editor.frame.findFirstElement('#slides')
>> +        self.html = self.slides.toInnerXml()
>> +        self.scroll_x = editor.frame.scrollBarValue(QtCore.Qt.Horizontal)
>> +        self.scroll_y = editor.frame.scrollBarValue(QtCore.Qt.Vertical)
>> +        self.image_selected = editor.image_selected
>> +        # QWebKit does not give access to the window.getSelection() object, so we let javascript find which text node
>> +        # is currently selected for the anchor and focus nodes, find their relative position to previous siblings,
>> +        # and retrieve their parent element's id.  We can't just store the node objects, as they will change if/when
>> +        # the innerXml is set
>> +        select_data = str(editor.java('saveSelection()'))
>> +        select_fields = select_data.split(',')
>> +        self.anchor_id = select_fields[0]
>> +        self.anchor_pos = select_fields[1]
>> +        self.anchor_offset = select_fields[2]
>> +        self.focus_id = select_fields[3]
>> +        self.focus_pos = select_fields[4]
>> +        self.focus_offset = select_fields[5]
>> +
>> +    def restore_selection(self):
>> +        """
>> +        Move the caret/selection to original slide position.  This will trigger the selection changed event
>> +        to update the active slide and text properties
>> +        """
>> +        self.editor.image_selected = self.image_selected
>> +        if self.image_selected:
>> +            self.editor.java('focusNode("%s")' % self.image_selected.attribute('id'))
>> +        else:
>> +            self.editor.java('restoreSelection("%s", "%s", "%s", "%s", "%s", "%s")' % (
>> +                self.anchor_id, self.anchor_pos, self.anchor_offset,
>> +                self.focus_id, self.focus_pos, self.focus_offset))
>> +
>> +    def set_scroll_position(self):
>> +        """
>> +        Returns scroll bars to original position.  Will not always be accurate if the zoom level or window size
>> +        was changed after the UndoCommand was created
>> +        """
>> +        self.editor.frame.setScrollBarValue(QtCore.Qt.Horizontal, self.scroll_x)
>> +        self.editor.frame.setScrollBarValue(QtCore.Qt.Vertical, self.scroll_y)
>> +
>> +    def undo(self):
>> +        """
>> +        Restore the editor to the original state
>> +        """
>> +        self.slides.setInnerXml(self.html)
>> +        self.restore_selection()
>> +        self.set_scroll_position()
>> +
>> +
>> +class MethodUndoCommand(CustomUndoCommand):
>> +    """
>> +    UndoCommand class for executing html_editor commands
>> +    """
>> +
>> +    def __init__(self, editor, command, description, param1=None, param2=None):
>> +        """
>> +        :param editor: HtmlEditor
>> +        :param command: Command to be executed
>> +        :param description: Text to display for undo/redo
>> +        :param param1: Optional parameter for the command
>> +        :param param2: Optional parameter for the command
>> +        """
>> +        super(MethodUndoCommand, self).__init__(editor, description)
>> +        self.command = command
>> +        self.param1 = param1
>> +        self.param2 = param2
>> +
>> +    def redo(self):
>> +        """
>> +        Reset the selection and execute the command
>> +        """
>> +        self.restore_selection()
>> +        self.set_scroll_position()
>> +        if self.param1:
>> +            if self.param2:
>> +                self.command(self.param1, self.param2)
>> +            else:
>> +                self.command(self.param1)
>> +        else:
>> +            self.command()
>> +
>> +
>> +class TextUndoCommand(CustomUndoCommand):
>> +    """
>> +    This undo command is used for typing.  The insertHTML fires the selection changed event, which under normal
>> +    circumstances an open text undo command is closed.  To allow for the appending of text, we set the editor's
>> +    'typing' to true
>> +    """
>> +
>> +    def __init__(self, editor, description, text):
>> +        """
>> +        :param editor: HtmlEditor
>> +        :param description: Text to display for undo/redo
>> +        :param text: The text to insert
>> +        """
>> +        super(TextUndoCommand, self).__init__(editor, description)
>> +        self.new_text = text
>> +
>> +    def redo(self):
>> +        """
>> +        Reset the selection and insert the text
>> +        """
>> +        self.restore_selection()
>> +        self.set_scroll_position()
>> +        self.editor.typing = True  # Ignore selection changed caused by insertHTML
>> +        self.editor.text_undo_command = self
>> +        self.editor.java("""insertHTML('%s')""" % self.new_text)
>> +        self.editor.typing = False
>> +
>> +    def append_text(self, char):
>> +        """
>> +        Add the char to the new text so a new UndoCommand does not have to be created
>> +
>> +        :param char:
>> +        """
>> +        self.editor.typing = True
>> +        self.new_text += char
>> +        self.editor.java('insertHTML("%s")' % char)
>> +        self.editor.typing = False
>> +
>> +
>> +class ImageUndoCommand(CustomUndoCommand):
>> +    """
>> +    UndoCommand class for editing images
>> +    """
>> +
>> +    def __init__(self, editor, description, source, style):
>> +        """
>> +        :param editor: HtmlEditor
>> +        :param description: Text to display for undo/redo
>> +        :param source: The image source file name
>> +        :param style: The image css style
>> +        """
>> +        super(ImageUndoCommand, self).__init__(editor, description)
>> +        self.source = source
>> +        self.style = style
>> +        self.save_source = self.editor.image_selected.attribute('src')
>> +        self.save_style = self.editor.image_selected.attribute('style')
>> +
>> +    def redo(self):
>> +        """
>> +        Reset the selection and set the image attributes
>> +        """
>> +        self.restore_selection()
>> +        self.set_scroll_position()
>> +        self.image_selected.setAttribute('src', self.source)
>> +        self.image_selected.setAttribute('style', self.style)
>> +
>> +    def undo(self):
>> +        """
>> +        Reset the selection and restore the image attributes
>> +        """
>> +        self.restore_selection()
>> +        self.set_scroll_position()
>> +        self.image_selected.setAttribute('src', self.save_source)
>> +        self.image_selected.setAttribute('style', self.save_style)
>> +
>> +
>> +class MenuAction(QtGui.QAction):
>> +    """
>> +    A special QAction that returns the selected menu item text in a signal
>> +    """
>> +    menu_action = QtCore.pyqtSignal(str)
>> +
>> +    def __init__(self, *args):
>> +        """
>> +        Constructor
>> +        """
>> +        super(MenuAction, self).__init__(*args)
>> +        self.triggered.connect(lambda x: self.menu_action.emit(self.text()))
>>
>> === modified file 'openlp/plugins/custom/lib/mediaitem.py'
>> --- openlp/plugins/custom/lib/mediaitem.py	2014-03-21 21:38:08 +0000
>> +++ openlp/plugins/custom/lib/mediaitem.py	2014-06-20 18:05:18 +0000
>> @@ -33,7 +33,7 @@
>>   from sqlalchemy.sql import or_, func, and_
>>   
>>   from openlp.core.common import Registry, Settings, UiStrings, translate
>> -from openlp.core.lib import MediaManagerItem, ItemCapabilities, ServiceItemContext, PluginStatus, \
>> +from openlp.core.lib import MediaManagerItem, ItemCapabilities, ServiceItem, ServiceItemContext, PluginStatus, \
>>       check_item_selected
>>   from openlp.plugins.custom.forms.editcustomform import EditCustomForm
>>   from openlp.plugins.custom.lib import CustomXMLParser, CustomXMLBuilder
>> @@ -140,6 +140,9 @@
>>           Handle the New item event
>>           """
>>           self.edit_custom_form.load_custom(0)
>> +        service_item = ServiceItem(self.plugin)
>> +        service_item.from_plugin = True
>> +        self.edit_custom_form.edit_slide_form.service_item = service_item
>>           self.edit_custom_form.exec_()
>>           self.on_clear_text_button_click()
>>           self.on_selection_change()
>> @@ -156,6 +159,9 @@
>>           valid = self.plugin.db_manager.get_object(CustomSlide, custom_id)
>>           if valid:
>>               self.edit_custom_form.load_custom(custom_id, preview)
>> +            service_item = ServiceItem(self.plugin)
>> +            service_item.from_plugin = True
>> +            self.edit_custom_form.edit_slide_form.service_item = service_item
>>               if self.edit_custom_form.exec_() == QtGui.QDialog.Accepted:
>>                   self.remote_triggered = True
>>                   self.remote_custom = custom_id
>> @@ -173,6 +179,9 @@
>>           Edit a custom item
>>           """
>>           if check_item_selected(self.list_view, UiStrings().SelectEdit):
>> +            service_item = ServiceItem(self.plugin)
>> +            service_item.from_plugin = True
>> +            self.edit_custom_form.edit_slide_form.service_item = service_item
>>               item = self.list_view.currentItem()
>>               item_id = item.data(QtCore.Qt.UserRole)
>>               self.edit_custom_form.load_custom(item_id, False)
>>
>> === modified file 'openlp/plugins/presentations/lib/pptviewcontroller.py'
>> --- openlp/plugins/presentations/lib/pptviewcontroller.py	2014-03-29 19:56:20 +0000
>> +++ openlp/plugins/presentations/lib/pptviewcontroller.py	2014-06-20 18:05:18 +0000
>> @@ -35,6 +35,7 @@
>>       from ctypes.wintypes import RECT
>>   
>>   from openlp.core.utils import AppLocation
>> +
>>   from openlp.core.lib import ScreenList
>>   from .presentationcontroller import PresentationController, PresentationDocument
>>   
>> @@ -86,8 +87,10 @@
>>               if self.process:
>>                   return
>>               log.debug('start PPTView')
>> +
>>               dll_path = os.path.join(AppLocation.get_directory(AppLocation.AppDir),
>>                                       'plugins', 'presentations', 'lib', 'pptviewlib', 'pptviewlib.dll')
>> +
>>               self.process = cdll.LoadLibrary(dll_path)
>>               if log.isEnabledFor(logging.DEBUG):
>>                   self.process.SetDebug(1)
>>
>> === modified file 'openlp/plugins/presentations/presentationplugin.py'
>> --- openlp/plugins/presentations/presentationplugin.py	2014-04-12 20:19:22 +0000
>> +++ openlp/plugins/presentations/presentationplugin.py	2014-06-20 18:05:18 +0000
>> @@ -47,9 +47,9 @@
>>                           'presentations/enable_pdf_program': QtCore.Qt.Unchecked,
>>                           'presentations/pdf_program': '',
>>                           'presentations/Impress': QtCore.Qt.Checked,
>> -                        'presentations/Powerpoint': QtCore.Qt.Checked,
>> -                        'presentations/Powerpoint Viewer': QtCore.Qt.Checked,
>> -                        'presentations/Pdf': QtCore.Qt.Checked,
>> +                        'presentations/Powerpoint': QtCore.Qt.Unchecked,
>> +                        'presentations/Powerpoint Viewer': QtCore.Qt.Unchecked,
>> +                        'presentations/Pdf': QtCore.Qt.Unchecked,
>>                           'presentations/presentations files': []
>>                           }
>>   
>>
>> === modified file 'resources/forms/editcustomslidedialog.ui'
>> --- resources/forms/editcustomslidedialog.ui	2010-10-10 13:17:01 +0000
>> +++ resources/forms/editcustomslidedialog.ui	2014-06-20 18:05:18 +0000
>> @@ -6,8 +6,8 @@
>>      <rect>
>>       <x>0</x>
>>       <y>0</y>
>> -    <width>474</width>
>> -    <height>442</height>
>> +    <width>650</width>
>> +    <height>450</height>
>>      </rect>
>>     </property>
>>     <property name="windowTitle">
>> @@ -18,7 +18,7 @@
>>       <rect>
>>        <x>8</x>
>>        <y>407</y>
>> -     <width>458</width>
>> +     <width>634</width>
>>        <height>32</height>
>>       </rect>
>>      </property>
>> @@ -29,33 +29,232 @@
>>       <set>QDialogButtonBox::Cancel|QDialogButtonBox::Save</set>
>>      </property>
>>     </widget>
>> -  <widget class="QTextEdit" name="VerseTextEdit">
>> +  <widget class="QTabWidget" name="EditorTabWidget">
>>      <property name="geometry">
>>       <rect>
>>        <x>8</x>
>>        <y>8</y>
>> -     <width>458</width>
>> -     <height>349</height>
>> -    </rect>
>> -   </property>
>> -  </widget>
>> -  <widget class="QPushButton" name="SplitButton">
>> -   <property name="geometry">
>> -    <rect>
>> -     <x>380</x>
>> -     <y>370</y>
>> -     <width>85</width>
>> -     <height>27</height>
>> -    </rect>
>> -   </property>
>> -   <property name="toolTip">
>> -    <string extracomment="Add new slide split"/>
>> -   </property>
>> -   <property name="text">
>> -    <string>Split Slide</string>
>> -   </property>
>> +     <width>634</width>
>> +     <height>391</height>
>> +    </rect>
>> +   </property>
>> +   <property name="currentIndex">
>> +    <number>1</number>
>> +   </property>
>> +   <widget class="QWidget" name="TagEditorTab">
>> +    <attribute name="title">
>> +     <string>Tag Editor</string>
>> +    </attribute>
>> +    <widget class="QWidget" name="horizontalLayoutWidget">
>> +     <property name="geometry">
>> +      <rect>
>> +       <x>10</x>
>> +       <y>320</y>
>> +       <width>611</width>
>> +       <height>40</height>
>> +      </rect>
>> +     </property>
>> +     <layout class="QHBoxLayout" name="TagButtonLayout" stretch="0,0,0">
>> +      <property name="spacing">
>> +       <number>8</number>
>> +      </property>
>> +      <property name="margin">
>> +       <number>0</number>
>> +      </property>
>> +      <item>
>> +       <widget class="QPushButton" name="TagSplitButton">
>> +        <property name="text">
>> +         <string>Optional Split</string>
>> +        </property>
>> +        <property name="icon">
>> +         <iconset resource="../images/openlp-2.qrc">
>> +          <normaloff>:/general/general_add.png</normaloff>:/general/general_add.png</iconset>
>> +        </property>
>> +       </widget>
>> +      </item>
>> +      <item>
>> +       <widget class="QPushButton" name="TagInsertButton">
>> +        <property name="text">
>> +         <string>Insert</string>
>> +        </property>
>> +        <property name="icon">
>> +         <iconset resource="../images/openlp-2.qrc">
>> +          <normaloff>:/general/general_add.png</normaloff>:/general/general_add.png</iconset>
>> +        </property>
>> +       </widget>
>> +      </item>
>> +      <item>
>> +       <spacer name="VerseTypeSpacer">
>> +        <property name="orientation">
>> +         <enum>Qt::Horizontal</enum>
>> +        </property>
>> +        <property name="sizeHint" stdset="0">
>> +         <size>
>> +          <width>40</width>
>> +          <height>20</height>
>> +         </size>
>> +        </property>
>> +       </spacer>
>> +      </item>
>> +     </layout>
>> +    </widget>
>> +    <widget class="QPlainTextEdit" name="plainTextEdit">
>> +     <property name="geometry">
>> +      <rect>
>> +       <x>10</x>
>> +       <y>10</y>
>> +       <width>611</width>
>> +       <height>291</height>
>> +      </rect>
>> +     </property>
>> +    </widget>
>> +   </widget>
>> +   <widget class="QWidget" name="VisualEditorTag">
>> +    <attribute name="title">
>> +     <string>Visual Editor</string>
>> +    </attribute>
>> +    <widget class="QWebView" name="CustomWebView">
>> +     <property name="geometry">
>> +      <rect>
>> +       <x>8</x>
>> +       <y>8</y>
>> +       <width>612</width>
>> +       <height>261</height>
>> +      </rect>
>> +     </property>
>> +     <property name="sizePolicy">
>> +      <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
>> +       <horstretch>0</horstretch>
>> +       <verstretch>0</verstretch>
>> +      </sizepolicy>
>> +     </property>
>> +     <property name="minimumSize">
>> +      <size>
>> +       <width>200</width>
>> +       <height>200</height>
>> +      </size>
>> +     </property>
>> +     <property name="focusPolicy">
>> +      <enum>Qt::StrongFocus</enum>
>> +     </property>
>> +     <property name="url">
>> +      <url>
>> +       <string>about:blank</string>
>> +      </url>
>> +     </property>
>> +    </widget>
>> +    <widget class="QWidget" name="horizontalLayoutWidget_2">
>> +     <property name="geometry">
>> +      <rect>
>> +       <x>10</x>
>> +       <y>320</y>
>> +       <width>611</width>
>> +       <height>40</height>
>> +      </rect>
>> +     </property>
>> +     <layout class="QHBoxLayout" name="VisualButtonLayout" stretch="0,0,0">
>> +      <property name="spacing">
>> +       <number>8</number>
>> +      </property>
>> +      <property name="margin">
>> +       <number>0</number>
>> +      </property>
>> +      <item>
>> +       <widget class="QPushButton" name="VisualSplitButton">
>> +        <property name="text">
>> +         <string>Optional Split</string>
>> +        </property>
>> +        <property name="icon">
>> +         <iconset resource="../images/openlp-2.qrc">
>> +          <normaloff>:/general/general_add.png</normaloff>:/general/general_add.png</iconset>
>> +        </property>
>> +       </widget>
>> +      </item>
>> +      <item>
>> +       <widget class="QPushButton" name="VisualInsertButton">
>> +        <property name="text">
>> +         <string>Insert</string>
>> +        </property>
>> +        <property name="icon">
>> +         <iconset resource="../images/openlp-2.qrc">
>> +          <normaloff>:/general/general_add.png</normaloff>:/general/general_add.png</iconset>
>> +        </property>
>> +       </widget>
>> +      </item>
>> +      <item>
>> +       <spacer name="VisualButtonSpacer">
>> +        <property name="orientation">
>> +         <enum>Qt::Horizontal</enum>
>> +        </property>
>> +        <property name="sizeHint" stdset="0">
>> +         <size>
>> +          <width>40</width>
>> +          <height>20</height>
>> +         </size>
>> +        </property>
>> +       </spacer>
>> +      </item>
>> +     </layout>
>> +     <zorder>VisualInsertButton</zorder>
>> +     <zorder>VisualSplitButton</zorder>
>> +    </widget>
>> +    <widget class="QWidget" name="horizontalLayoutWidget_3">
>> +     <property name="geometry">
>> +      <rect>
>> +       <x>10</x>
>> +       <y>280</y>
>> +       <width>611</width>
>> +       <height>40</height>
>> +      </rect>
>> +     </property>
>> +     <layout class="QHBoxLayout" name="PathLayout" stretch="0,0">
>> +      <property name="spacing">
>> +       <number>8</number>
>> +      </property>
>> +      <property name="margin">
>> +       <number>0</number>
>> +      </property>
>> +      <item>
>> +       <widget class="QLabel" name="PathLabel">
>> +        <property name="text">
>> +         <string>Path:</string>
>> +        </property>
>> +       </widget>
>> +      </item>
>> +      <item>
>> +       <spacer name="PathSpacer">
>> +        <property name="orientation">
>> +         <enum>Qt::Horizontal</enum>
>> +        </property>
>> +        <property name="sizeHint" stdset="0">
>> +         <size>
>> +          <width>40</width>
>> +          <height>20</height>
>> +         </size>
>> +        </property>
>> +       </spacer>
>> +      </item>
>> +     </layout>
>> +    </widget>
>> +   </widget>
>>     </widget>
>>    </widget>
>> + <customwidgets>
>> +  <customwidget>
>> +   <class>QWebView</class>
>> +   <extends>QWidget</extends>
>> +   <header>QtWebKit/QWebView</header>
>> +  </customwidget>
>> + </customwidgets>
>> + <tabstops>
>> +  <tabstop>EditorTabWidget</tabstop>
>> +  <tabstop>TagSplitButton</tabstop>
>> +  <tabstop>TagInsertButton</tabstop>
>> +  <tabstop>buttonBox</tabstop>
>> +  <tabstop>VisualInsertButton</tabstop>
>> +  <tabstop>CustomWebView</tabstop>
>> +  <tabstop>VisualSplitButton</tabstop>
>> + </tabstops>
>>    <resources>
>>     <include location="../images/openlp-2.qrc"/>
>>    </resources>
>>
>> === added file 'resources/forms/editimagedialog.ui'
>> --- resources/forms/editimagedialog.ui	1970-01-01 00:00:00 +0000
>> +++ resources/forms/editimagedialog.ui	2014-06-20 18:05:18 +0000
>> @@ -0,0 +1,624 @@
>> +<?xml version="1.0" encoding="UTF-8"?>
>> +<ui version="4.0">
>> + <class>edit_image_dialog</class>
>> + <widget class="QDialog" name="edit_image_dialog">
>> +  <property name="geometry">
>> +   <rect>
>> +    <x>0</x>
>> +    <y>0</y>
>> +    <width>589</width>
>> +    <height>327</height>
>> +   </rect>
>> +  </property>
>> +  <property name="windowTitle">
>> +   <string>Edit Image</string>
>> +  </property>
>> +  <layout class="QVBoxLayout" name="dialog_layout">
>> +   <property name="spacing">
>> +    <number>12</number>
>> +   </property>
>> +   <item>
>> +    <widget class="QGroupBox" name="image_group_box">
>> +     <property name="sizePolicy">
>> +      <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
>> +       <horstretch>0</horstretch>
>> +       <verstretch>0</verstretch>
>> +      </sizepolicy>
>> +     </property>
>> +     <property name="title">
>> +      <string>Image</string>
>> +     </property>
>> +     <layout class="QHBoxLayout" name="image_layout">
>> +      <property name="spacing">
>> +       <number>8</number>
>> +      </property>
>> +      <item>
>> +       <widget class="QLabel" name="thumbnail_label">
>> +        <property name="sizePolicy">
>> +         <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
>> +          <horstretch>0</horstretch>
>> +          <verstretch>0</verstretch>
>> +         </sizepolicy>
>> +        </property>
>> +        <property name="minimumSize">
>> +         <size>
>> +          <width>48</width>
>> +          <height>48</height>
>> +         </size>
>> +        </property>
>> +        <property name="maximumSize">
>> +         <size>
>> +          <width>48</width>
>> +          <height>48</height>
>> +         </size>
>> +        </property>
>> +        <property name="frameShape">
>> +         <enum>QFrame::NoFrame</enum>
>> +        </property>
>> +        <property name="text">
>> +         <string/>
>> +        </property>
>> +       </widget>
>> +      </item>
>> +      <item>
>> +       <widget class="QLineEdit" name="image_line_edit">
>> +        <property name="focusPolicy">
>> +         <enum>Qt::NoFocus</enum>
>> +        </property>
>> +        <property name="readOnly">
>> +         <bool>true</bool>
>> +        </property>
>> +       </widget>
>> +      </item>
>> +      <item>
>> +       <widget class="QPushButton" name="image_push_button">
>> +        <property name="sizePolicy">
>> +         <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
>> +          <horstretch>0</horstretch>
>> +          <verstretch>0</verstretch>
>> +         </sizepolicy>
>> +        </property>
>> +        <property name="text">
>> +         <string>Select Image...</string>
>> +        </property>
>> +       </widget>
>> +      </item>
>> +     </layout>
>> +    </widget>
>> +   </item>
>> +   <item>
>> +    <layout class="QHBoxLayout" name="properties_layout" stretch="0,0,0,0">
>> +     <property name="spacing">
>> +      <number>12</number>
>> +     </property>
>> +     <property name="topMargin">
>> +      <number>12</number>
>> +     </property>
>> +     <property name="bottomMargin">
>> +      <number>0</number>
>> +     </property>
>> +     <item>
>> +      <widget class="QGroupBox" name="size_group_box">
>> +       <property name="sizePolicy">
>> +        <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
>> +         <horstretch>0</horstretch>
>> +         <verstretch>0</verstretch>
>> +        </sizepolicy>
>> +       </property>
>> +       <property name="title">
>> +        <string>Size</string>
>> +       </property>
>> +       <layout class="QGridLayout" name="size_layout">
>> +        <property name="verticalSpacing">
>> +         <number>10</number>
>> +        </property>
>> +        <item row="0" column="0">
>> +         <widget class="QLabel" name="width_label">
>> +          <property name="text">
>> +           <string>Width</string>
>> +          </property>
>> +         </widget>
>> +        </item>
>> +        <item row="0" column="1">
>> +         <widget class="QSpinBox" name="width_spin_box">
>> +          <property name="sizePolicy">
>> +           <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
>> +            <horstretch>0</horstretch>
>> +            <verstretch>0</verstretch>
>> +           </sizepolicy>
>> +          </property>
>> +          <property name="minimum">
>> +           <number>1</number>
>> +          </property>
>> +          <property name="maximum">
>> +           <number>9999</number>
>> +          </property>
>> +          <property name="value">
>> +           <number>9999</number>
>> +          </property>
>> +         </widget>
>> +        </item>
>> +        <item row="1" column="0">
>> +         <widget class="QLabel" name="height_label">
>> +          <property name="text">
>> +           <string>Height</string>
>> +          </property>
>> +         </widget>
>> +        </item>
>> +        <item row="1" column="1">
>> +         <widget class="QSpinBox" name="height_spin_box">
>> +          <property name="minimum">
>> +           <number>1</number>
>> +          </property>
>> +          <property name="maximum">
>> +           <number>9999</number>
>> +          </property>
>> +          <property name="value">
>> +           <number>9999</number>
>> +          </property>
>> +         </widget>
>> +        </item>
>> +        <item row="2" column="1">
>> +         <widget class="QCheckBox" name="proportional_check_box">
>> +          <property name="text">
>> +           <string>Proportional</string>
>> +          </property>
>> +         </widget>
>> +        </item>
>> +        <item row="3" column="0" colspan="2">
>> +         <widget class="QPushButton" name="reset_push_button">
>> +          <property name="sizePolicy">
>> +           <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
>> +            <horstretch>0</horstretch>
>> +            <verstretch>0</verstretch>
>> +           </sizepolicy>
>> +          </property>
>> +          <property name="text">
>> +           <string>Reset</string>
>> +          </property>
>> +         </widget>
>> +        </item>
>> +       </layout>
>> +      </widget>
>> +     </item>
>> +     <item>
>> +      <widget class="QGroupBox" name="style_group_box">
>> +       <property name="sizePolicy">
>> +        <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
>> +         <horstretch>0</horstretch>
>> +         <verstretch>0</verstretch>
>> +        </sizepolicy>
>> +       </property>
>> +       <property name="title">
>> +        <string>Style</string>
>> +       </property>
>> +       <layout class="QGridLayout" name="style_layout">
>> +        <property name="verticalSpacing">
>> +         <number>10</number>
>> +        </property>
>> +        <item row="1" column="0">
>> +         <widget class="QLabel" name="opacity_label">
>> +          <property name="text">
>> +           <string>Opacity</string>
>> +          </property>
>> +         </widget>
>> +        </item>
>> +        <item row="2" column="1">
>> +         <widget class="QSlider" name="shadow_horizontal_slider">
>> +          <property name="maximum">
>> +           <number>50</number>
>> +          </property>
>> +          <property name="pageStep">
>> +           <number>5</number>
>> +          </property>
>> +          <property name="orientation">
>> +           <enum>Qt::Horizontal</enum>
>> +          </property>
>> +         </widget>
>> +        </item>
>> +        <item row="2" column="0">
>> +         <widget class="QLabel" name="shadow_label">
>> +          <property name="text">
>> +           <string>Shadow</string>
>> +          </property>
>> +         </widget>
>> +        </item>
>> +        <item row="0" column="0">
>> +         <widget class="QLabel" name="align_label">
>> +          <property name="text">
>> +           <string>Align</string>
>> +          </property>
>> +         </widget>
>> +        </item>
>> +        <item row="1" column="1">
>> +         <widget class="QSlider" name="opacity_horizontal_slider">
>> +          <property name="minimum">
>> +           <number>10</number>
>> +          </property>
>> +          <property name="maximum">
>> +           <number>100</number>
>> +          </property>
>> +          <property name="singleStep">
>> +           <number>10</number>
>> +          </property>
>> +          <property name="value">
>> +           <number>100</number>
>> +          </property>
>> +          <property name="sliderPosition">
>> +           <number>100</number>
>> +          </property>
>> +          <property name="orientation">
>> +           <enum>Qt::Horizontal</enum>
>> +          </property>
>> +          <property name="invertedAppearance">
>> +           <bool>false</bool>
>> +          </property>
>> +          <property name="tickPosition">
>> +           <enum>QSlider::NoTicks</enum>
>> +          </property>
>> +          <property name="tickInterval">
>> +           <number>10</number>
>> +          </property>
>> +         </widget>
>> +        </item>
>> +        <item row="0" column="1">
>> +         <widget class="QComboBox" name="align_combo_box">
>> +          <item>
>> +           <property name="text">
>> +            <string>None</string>
>> +           </property>
>> +          </item>
>> +          <item>
>> +           <property name="text">
>> +            <string>Left</string>
>> +           </property>
>> +          </item>
>> +          <item>
>> +           <property name="text">
>> +            <string>Right</string>
>> +           </property>
>> +          </item>
>> +          <item>
>> +           <property name="text">
>> +            <string>Center</string>
>> +           </property>
>> +          </item>
>> +          <item>
>> +           <property name="text">
>> +            <string>Block</string>
>> +           </property>
>> +          </item>
>> +          <item>
>> +           <property name="text">
>> +            <string>Text Top</string>
>> +           </property>
>> +          </item>
>> +          <item>
>> +           <property name="text">
>> +            <string>Text Middle</string>
>> +           </property>
>> +          </item>
>> +          <item>
>> +           <property name="text">
>> +            <string>Text Bottom</string>
>> +           </property>
>> +          </item>
>> +          <item>
>> +           <property name="text">
>> +            <string>Background</string>
>> +           </property>
>> +          </item>
>> +         </widget>
>> +        </item>
>> +        <item row="3" column="0">
>> +         <widget class="QLabel" name="blur_label">
>> +          <property name="text">
>> +           <string>Blur</string>
>> +          </property>
>> +         </widget>
>> +        </item>
>> +        <item row="3" column="1">
>> +         <widget class="QSlider" name="blur_horizontal_slider">
>> +          <property name="maximum">
>> +           <number>50</number>
>> +          </property>
>> +          <property name="pageStep">
>> +           <number>5</number>
>> +          </property>
>> +          <property name="orientation">
>> +           <enum>Qt::Horizontal</enum>
>> +          </property>
>> +         </widget>
>> +        </item>
>> +       </layout>
>> +      </widget>
>> +     </item>
>> +     <item>
>> +      <widget class="QGroupBox" name="spacing_group_box">
>> +       <property name="sizePolicy">
>> +        <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
>> +         <horstretch>0</horstretch>
>> +         <verstretch>0</verstretch>
>> +        </sizepolicy>
>> +       </property>
>> +       <property name="title">
>> +        <string>Spacing</string>
>> +       </property>
>> +       <layout class="QGridLayout" name="spacing_layout">
>> +        <property name="verticalSpacing">
>> +         <number>10</number>
>> +        </property>
>> +        <item row="6" column="1">
>> +         <widget class="QSpinBox" name="top_spin_box">
>> +          <property name="maximum">
>> +           <number>9999</number>
>> +          </property>
>> +          <property name="value">
>> +           <number>9999</number>
>> +          </property>
>> +         </widget>
>> +        </item>
>> +        <item row="6" column="0">
>> +         <widget class="QLabel" name="top_label">
>> +          <property name="text">
>> +           <string>Top</string>
>> +          </property>
>> +         </widget>
>> +        </item>
>> +        <item row="2" column="1">
>> +         <widget class="QSpinBox" name="right_spin_box">
>> +          <property name="maximum">
>> +           <number>9999</number>
>> +          </property>
>> +          <property name="value">
>> +           <number>9999</number>
>> +          </property>
>> +         </widget>
>> +        </item>
>> +        <item row="7" column="0">
>> +         <widget class="QLabel" name="bottom_label">
>> +          <property name="text">
>> +           <string>Bottom</string>
>> +          </property>
>> +         </widget>
>> +        </item>
>> +        <item row="7" column="1">
>> +         <widget class="QSpinBox" name="bottom_spin_box">
>> +          <property name="maximum">
>> +           <number>9999</number>
>> +          </property>
>> +          <property name="value">
>> +           <number>9999</number>
>> +          </property>
>> +         </widget>
>> +        </item>
>> +        <item row="2" column="0">
>> +         <widget class="QLabel" name="right_label">
>> +          <property name="text">
>> +           <string>Right</string>
>> +          </property>
>> +         </widget>
>> +        </item>
>> +        <item row="1" column="0">
>> +         <widget class="QLabel" name="left_label">
>> +          <property name="text">
>> +           <string>Left</string>
>> +          </property>
>> +         </widget>
>> +        </item>
>> +        <item row="1" column="1">
>> +         <widget class="QSpinBox" name="left_spin_box">
>> +          <property name="maximum">
>> +           <number>9999</number>
>> +          </property>
>> +          <property name="value">
>> +           <number>9999</number>
>> +          </property>
>> +         </widget>
>> +        </item>
>> +       </layout>
>> +      </widget>
>> +     </item>
>> +     <item>
>> +      <widget class="QGroupBox" name="border_group_box">
>> +       <property name="sizePolicy">
>> +        <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
>> +         <horstretch>0</horstretch>
>> +         <verstretch>0</verstretch>
>> +        </sizepolicy>
>> +       </property>
>> +       <property name="title">
>> +        <string>Border</string>
>> +       </property>
>> +       <layout class="QGridLayout" name="border_layout">
>> +        <property name="verticalSpacing">
>> +         <number>10</number>
>> +        </property>
>> +        <item row="2" column="0">
>> +         <widget class="QFrame" name="border_color_frame">
>> +          <property name="sizePolicy">
>> +           <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
>> +            <horstretch>0</horstretch>
>> +            <verstretch>0</verstretch>
>> +           </sizepolicy>
>> +          </property>
>> +          <property name="minimumSize">
>> +           <size>
>> +            <width>16</width>
>> +            <height>16</height>
>> +           </size>
>> +          </property>
>> +          <property name="styleSheet">
>> +           <string notr="true">background-color: #FF0000;</string>
>> +          </property>
>> +          <property name="frameShape">
>> +           <enum>QFrame::StyledPanel</enum>
>> +          </property>
>> +          <property name="frameShadow">
>> +           <enum>QFrame::Sunken</enum>
>> +          </property>
>> +         </widget>
>> +        </item>
>> +        <item row="0" column="0">
>> +         <widget class="QLabel" name="border_type_label">
>> +          <property name="text">
>> +           <string>Type</string>
>> +          </property>
>> +         </widget>
>> +        </item>
>> +        <item row="2" column="1">
>> +         <widget class="QPushButton" name="border_color_push_button">
>> +          <property name="text">
>> +           <string>Color...</string>
>> +          </property>
>> +         </widget>
>> +        </item>
>> +        <item row="4" column="1">
>> +         <widget class="QSlider" name="radius_horizontal_slider">
>> +          <property name="maximum">
>> +           <number>50</number>
>> +          </property>
>> +          <property name="pageStep">
>> +           <number>5</number>
>> +          </property>
>> +          <property name="orientation">
>> +           <enum>Qt::Horizontal</enum>
>> +          </property>
>> +         </widget>
>> +        </item>
>> +        <item row="4" column="0">
>> +         <widget class="QLabel" name="radius_label">
>> +          <property name="text">
>> +           <string>Radius</string>
>> +          </property>
>> +         </widget>
>> +        </item>
>> +        <item row="0" column="1">
>> +         <widget class="QComboBox" name="border_type_combo_box">
>> +          <item>
>> +           <property name="text">
>> +            <string>None</string>
>> +           </property>
>> +          </item>
>> +          <item>
>> +           <property name="text">
>> +            <string>Solid</string>
>> +           </property>
>> +          </item>
>> +          <item>
>> +           <property name="text">
>> +            <string>Dotted</string>
>> +           </property>
>> +          </item>
>> +          <item>
>> +           <property name="text">
>> +            <string>Dashed</string>
>> +           </property>
>> +          </item>
>> +          <item>
>> +           <property name="text">
>> +            <string>Double</string>
>> +           </property>
>> +          </item>
>> +          <item>
>> +           <property name="text">
>> +            <string>Groove</string>
>> +           </property>
>> +          </item>
>> +          <item>
>> +           <property name="text">
>> +            <string>Ridge</string>
>> +           </property>
>> +          </item>
>> +          <item>
>> +           <property name="text">
>> +            <string>Inset</string>
>> +           </property>
>> +          </item>
>> +          <item>
>> +           <property name="text">
>> +            <string>Outset</string>
>> +           </property>
>> +          </item>
>> +         </widget>
>> +        </item>
>> +        <item row="3" column="0">
>> +         <widget class="QLabel" name="border_width_label">
>> +          <property name="text">
>> +           <string>Width</string>
>> +          </property>
>> +         </widget>
>> +        </item>
>> +        <item row="3" column="1">
>> +         <widget class="QSlider" name="border_width_horizontal_slider">
>> +          <property name="minimum">
>> +           <number>1</number>
>> +          </property>
>> +          <property name="maximum">
>> +           <number>50</number>
>> +          </property>
>> +          <property name="pageStep">
>> +           <number>5</number>
>> +          </property>
>> +          <property name="orientation">
>> +           <enum>Qt::Horizontal</enum>
>> +          </property>
>> +         </widget>
>> +        </item>
>> +       </layout>
>> +      </widget>
>> +     </item>
>> +    </layout>
>> +   </item>
>> +   <item>
>> +    <spacer name="vertical_spacer">
>> +     <property name="orientation">
>> +      <enum>Qt::Vertical</enum>
>> +     </property>
>> +     <property name="sizeType">
>> +      <enum>QSizePolicy::Fixed</enum>
>> +     </property>
>> +     <property name="sizeHint" stdset="0">
>> +      <size>
>> +       <width>20</width>
>> +       <height>12</height>
>> +      </size>
>> +     </property>
>> +    </spacer>
>> +   </item>
>> +   <item>
>> +    <widget class="QDialogButtonBox" name="button_box">
>> +     <property name="orientation">
>> +      <enum>Qt::Horizontal</enum>
>> +     </property>
>> +     <property name="standardButtons">
>> +      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
>> +     </property>
>> +    </widget>
>> +   </item>
>> +  </layout>
>> + </widget>
>> + <tabstops>
>> +  <tabstop>image_line_edit</tabstop>
>> +  <tabstop>image_push_button</tabstop>
>> +  <tabstop>width_spin_box</tabstop>
>> +  <tabstop>height_spin_box</tabstop>
>> +  <tabstop>proportional_check_box</tabstop>
>> +  <tabstop>reset_push_button</tabstop>
>> +  <tabstop>align_combo_box</tabstop>
>> +  <tabstop>opacity_horizontal_slider</tabstop>
>> +  <tabstop>shadow_horizontal_slider</tabstop>
>> +  <tabstop>blur_horizontal_slider</tabstop>
>> +  <tabstop>left_spin_box</tabstop>
>> +  <tabstop>right_spin_box</tabstop>
>> +  <tabstop>top_spin_box</tabstop>
>> +  <tabstop>bottom_spin_box</tabstop>
>> +  <tabstop>border_type_combo_box</tabstop>
>> +  <tabstop>border_color_push_button</tabstop>
>> +  <tabstop>border_width_horizontal_slider</tabstop>
>> +  <tabstop>radius_horizontal_slider</tabstop>
>> +  <tabstop>button_box</tabstop>
>> + </tabstops>
>> + <resources/>
>> + <connections/>
>> +</ui>
>>
>> === added file 'resources/images/custom_overflow.png'
>> Binary files resources/images/custom_overflow.png	1970-01-01 00:00:00 +0000 and resources/images/custom_overflow.png	2014-06-20 18:05:18 +0000 differ
>



-- 
https://code.launchpad.net/~kirkstover/openlp/wysiwyg/+merge/223973
Your team OpenLP Core is subscribed to branch lp:openlp.


References