Source code for camelot.view.controls.editors.richtexteditor

#  ============================================================================
#
#  Copyright (C) 2007-2016 Conceptive Engineering bvba.
#  www.conceptive.be / info@conceptive.be
#
#  Redistribution and use in source and binary forms, with or without
#  modification, are permitted provided that the following conditions are met:
#      * Redistributions of source code must retain the above copyright
#        notice, this list of conditions and the following disclaimer.
#      * Redistributions in binary form must reproduce the above copyright
#        notice, this list of conditions and the following disclaimer in the
#        documentation and/or other materials provided with the distribution.
#      * Neither the name of Conceptive Engineering nor the
#        names of its contributors may be used to endorse or promote products
#        derived from this software without specific prior written permission.
#  
#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
#  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
#  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
#  DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
#  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
#  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
#  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
#  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
#  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
#  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
#  ============================================================================

import six

from ....core.qt import QtGui, QtCore, QtWidgets, Qt
from .wideeditor import WideEditor
from .customeditor import CustomEditor
from camelot.view.art import Icon

[docs]class CustomTextEdit(QtWidgets.QTextEdit): """ A TextEdit editor that sends editingFinished events when the text was changed and focus is lost. """ editingFinished = QtCore.qt_signal() receivedFocus = QtCore.qt_signal() def __init__(self, parent): super(CustomTextEdit, self).__init__(parent) self._changed = False self.setTabChangesFocus( True ) self.textChanged.connect( self._handle_text_changed ) def focusInEvent(self, event): super(CustomTextEdit, self).focusInEvent( event ) self.receivedFocus.emit() def focusOutEvent(self, event): if self._changed: self.editingFinished.emit() super(CustomTextEdit, self).focusOutEvent( event ) def _handle_text_changed(self): self._changed = True def setTextChanged(self, state=True): self._changed = state def setHtml(self, html): QtWidgets.QTextEdit.setHtml(self, html) self._changed = False
class RichTextEditor(CustomEditor, WideEditor): def __init__(self, parent = None, field_name = 'richtext', **kwargs): CustomEditor.__init__(self, parent) self.setObjectName( field_name ) self.layout = QtWidgets.QVBoxLayout(self) self.layout.setSpacing(0) self.layout.setContentsMargins( 0, 0, 0, 0) self.setSizePolicy( QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding ) self.textedit = CustomTextEdit(self) toolbar = self.initToolbar() # Has to be invoked before the connect's below. toolbar.hide() # Should only be visible when textedit is focused. self.textedit.editingFinished.connect(self.emit_editing_finished) self.textedit.receivedFocus.connect(toolbar.show) self.textedit.setAcceptRichText(True) # Layout self.layout.addWidget(toolbar) self.layout.addWidget(self.textedit) self.setLayout(self.layout) # Format self.textedit.setFontWeight(QtGui.QFont.Normal) self.textedit.setFontItalic(False) self.textedit.setFontUnderline(False) @QtCore.qt_slot() def emit_editing_finished(self): if self.textedit._changed: self.editingFinished.emit() def set_editable(self, editable): toolbar = self.findChild( QtWidgets.QToolBar ) if toolbar: toolbar.setEnabled(editable) self.textedit.setEnabled(editable) self.textedit.setReadOnly( not editable ) def set_field_attributes(self, **kwargs): super(RichTextEditor, self).set_field_attributes(**kwargs) self.set_editable(kwargs.get('editable', False)) def set_toolbar_hidden( self, hidden ): """Show or hide the toolbar, by default the toolbar is hidden until the user starts editing. :param hidden: `True` or `False` """ toolbar = self.findChild( QtWidgets.QToolBar ) if toolbar: toolbar.setHidden( hidden ) def initToolbar(self): toolbar = QtWidgets.QToolBar(self) toolbar.setObjectName( 'toolbar' ) toolbar.setOrientation(Qt.Horizontal) toolbar.setContentsMargins(0, 0, 0, 0) bold_button = QtWidgets.QToolButton(self) icon = Icon('tango/16x16/actions/format-text-bold.png').getQIcon() bold_button.setIcon(icon) bold_button.setMaximumSize(QtCore.QSize(20, 20)) bold_button.setShortcut(QtGui.QKeySequence('Ctrl+B')) bold_button.setToolTip('Bold') bold_button.clicked.connect(self.set_bold) italic_button = QtWidgets.QToolButton(self) icon = Icon('tango/16x16/actions/format-text-italic.png').getQIcon() italic_button.setIcon(icon) italic_button.setMaximumSize(QtCore.QSize(20, 20)) italic_button.setShortcut(QtGui.QKeySequence('Ctrl+I')) italic_button.setToolTip('Italic') italic_button.clicked.connect(self.set_italic) underline_button = QtWidgets.QToolButton(self) icon = Icon('tango/16x16/actions/format-text-underline.png').getQIcon() underline_button.setIcon(icon) underline_button.setMaximumSize(QtCore.QSize(20, 20)) underline_button.setShortcut(QtGui.QKeySequence('Ctrl+U')) underline_button.setToolTip('Underline') underline_button.clicked.connect(self.set_underline) copy_button = QtWidgets.QToolButton(self) icon = Icon('tango/16x16/actions/edit-copy.png').getQIcon() copy_button.setIcon(icon) copy_button.setMaximumSize(QtCore.QSize(20, 20)) copy_button.setToolTip('Copy') copy_button.clicked.connect(self.textedit.copy) cut_button = QtWidgets.QToolButton(self) icon = Icon('tango/16x16/actions/edit-cut.png').getQIcon() cut_button.setIcon(icon) cut_button.setMaximumSize(QtCore.QSize(20, 20)) cut_button.setToolTip('Cut') cut_button.clicked.connect(self.textedit.cut) paste_button = QtWidgets.QToolButton(self) icon = Icon('tango/16x16/actions/edit-paste.png').getQIcon() paste_button.setIcon(icon) paste_button.setMaximumSize(QtCore.QSize(20, 20)) paste_button.setToolTip('Paste') paste_button.clicked.connect(self.textedit.paste) alignleft_button = QtWidgets.QToolButton(self) icon = Icon('tango/16x16/actions/format-justify-left.png').getQIcon() alignleft_button.setIcon(icon) alignleft_button.setMaximumSize(QtCore.QSize(20, 20)) alignleft_button.setToolTip('Align left') alignleft_button.clicked.connect(self.set_alignleft) aligncenter_button = QtWidgets.QToolButton(self) icon = Icon('tango/16x16/actions/format-justify-center.png').getQIcon() aligncenter_button.setIcon(icon) aligncenter_button.setMaximumSize(QtCore.QSize(20, 20)) aligncenter_button.setToolTip('Align Center') aligncenter_button.clicked.connect(self.set_aligncenter) alignright_button = QtWidgets.QToolButton(self) icon = Icon('tango/16x16/actions/format-justify-right.png').getQIcon() alignright_button.setIcon(icon) alignright_button.setMaximumSize(QtCore.QSize(20, 20)) alignright_button.setToolTip('Align Right') alignright_button.clicked.connect(self.set_alignright) zoomin_button = QtWidgets.QToolButton(self) icon = Icon('tango/16x16/actions/list-add.png').getQIcon() zoomin_button.setIcon(icon) zoomin_button.setMaximumSize(QtCore.QSize(20, 20)) zoomin_button.setToolTip('Zoom in') zoomin_button.clicked.connect(self.zoomin) zoomout_button = QtWidgets.QToolButton(self) icon = Icon('tango/16x16/actions/list-remove.png').getQIcon() zoomout_button.setIcon(icon) zoomout_button.setMaximumSize(QtCore.QSize(20, 20)) zoomout_button.setToolTip('Zoom out') zoomout_button.clicked.connect(self.zoomout) color_button = QtWidgets.QToolButton(self) color_button.setMaximumSize(QtCore.QSize(20, 20)) color_button.setToolTip('Color') pixmap = QtGui.QPixmap(16, 16) pixmap.fill(QtGui.QColor('black')) color_button.setIcon(QtGui.QIcon(pixmap)) color_button.clicked.connect(self.set_color) toolbar.addWidget(copy_button) toolbar.addWidget(cut_button) toolbar.addWidget(paste_button) toolbar.addSeparator() toolbar.addWidget(bold_button) toolbar.addWidget(italic_button) toolbar.addWidget(underline_button) toolbar.addSeparator() toolbar.addWidget(alignleft_button) toolbar.addWidget(aligncenter_button) toolbar.addWidget(alignright_button) toolbar.addSeparator() toolbar.addWidget(color_button) toolbar.addSeparator() toolbar.addWidget(zoomin_button) toolbar.addWidget(zoomout_button) return toolbar # # Button methods # def set_bold(self): font = self.textedit.currentFont() if not font.bold(): self.textedit.setFontWeight(QtGui.QFont.Bold) else: self.textedit.setFontWeight(QtGui.QFont.Normal) def set_italic(self, bool): font = self.textedit.currentFont() self.textedit.setFontItalic(not font.italic()) def set_underline(self, bool): font = self.textedit.currentFont() self.textedit.setFontUnderline(not font.underline()) def zoomin( self ): self.textedit.zoomIn() def zoomout( self ): self.textedit.zoomOut() def set_alignleft(self, bool): self.textedit.setAlignment(Qt.AlignLeft) def set_aligncenter(self, bool): self.textedit.setAlignment(Qt.AlignCenter) def set_alignright(self, bool): self.textedit.setAlignment(Qt.AlignRight) def set_color(self): color = QtGui.QColorDialog.getColor(self.textedit.textColor()) if color.isValid(): self.textedit.setTextColor(color) def get_value(self): from xml.dom import minidom tree = minidom.parseString(six.text_type(self.textedit.toHtml()).encode('utf-8')) value = u''.join([node.toxml() for node in tree.getElementsByTagName('html')[0].getElementsByTagName('body')[0].childNodes]) return CustomEditor.get_value(self) or value def set_document( self, document ): """ :param document: a :class:`QtGui.QTextDocument` object. """ self.textedit.setDocument( document ) def set_value( self, value ): value = CustomEditor.set_value(self, value) if value!=None: if six.text_type(self.textedit.toHtml())!=value: self.textedit.setHtml(value) else: self.textedit.clear()