コード例 #1
0
class TextAnnotation(Annotation):
    """
    Text annotation item for the canvas scheme.

    Text interaction (if enabled) is started by double clicking the item.
    """
    #: Emitted when the editing is finished (i.e. the item loses edit focus).
    editingFinished = Signal()

    #: Emitted when the text content changes on user interaction.
    textEdited = Signal()

    #: Emitted when the text annotation's contents change
    #: (`content` or `contentType` changed)
    contentChanged = Signal()

    def __init__(self, parent=None, **kwargs):
        # type: (Optional[QGraphicsItem], Any) -> None
        super().__init__(None, **kwargs)
        self.setFlag(QGraphicsItem.ItemIsMovable)
        self.setFlag(QGraphicsItem.ItemIsSelectable)

        self.setFocusPolicy(Qt.ClickFocus)

        self.__contentType = "text/plain"
        self.__content = ""

        self.__textMargins = (2, 2, 2, 2)
        self.__textInteractionFlags = Qt.NoTextInteraction
        self.__defaultInteractionFlags = Qt.TextInteractionFlags(
            Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard)
        rect = self.geometry().translated(-self.pos())
        self.__framePen = QPen(Qt.NoPen)
        self.__framePathItem = QGraphicsPathItem(self)
        self.__framePathItem.setPen(self.__framePen)

        self.__textItem = GraphicsTextEdit(self)
        self.__textItem.setOpenExternalLinks(True)
        self.__textItem.setPlaceholderText(self.tr("Enter text here"))
        self.__textItem.setPos(2, 2)
        self.__textItem.setTextWidth(rect.width() - 4)
        self.__textItem.setTabChangesFocus(True)
        self.__textItem.setTextInteractionFlags(self.__defaultInteractionFlags)
        self.__textItem.setFont(self.font())
        self.__textItem.editingFinished.connect(self.__textEditingFinished)
        self.__textItem.setDefaultTextColor(self.palette().color(
            QPalette.Text))
        if self.__textItem.scene() is not None:
            self.__textItem.installSceneEventFilter(self)
        layout = self.__textItem.document().documentLayout()
        layout.documentSizeChanged.connect(self.__onDocumentSizeChanged)

        self.__updateFrame()
        # set parent item at the end in order to ensure
        # QGraphicsItem.ItemSceneHasChanged is delivered after initialization
        if parent is not None:
            self.setParentItem(parent)

    def itemChange(self, change, value):
        # type: (QGraphicsItem.GraphicsItemChange, Any) -> Any
        if change == QGraphicsItem.ItemSceneHasChanged:
            if self.__textItem.scene() is not None:
                self.__textItem.installSceneEventFilter(self)
        if change == QGraphicsItem.ItemSelectedHasChanged:
            self.__updateFrameStyle()
        return super().itemChange(change, value)

    def adjustSize(self):
        # type: () -> None
        """Resize to a reasonable size.
        """
        self.__textItem.setTextWidth(-1)
        self.__textItem.adjustSize()
        size = self.__textItem.boundingRect().size()
        left, top, right, bottom = self.textMargins()
        geom = QRectF(self.pos(), size + QSizeF(left + right, top + bottom))
        self.setGeometry(geom)

    def setFramePen(self, pen):
        # type: (QPen) -> None
        """Set the frame pen. By default Qt.NoPen is used (i.e. the frame
        is not shown).
        """
        if pen != self.__framePen:
            self.__framePen = QPen(pen)
            self.__updateFrameStyle()

    def framePen(self):
        # type: () -> QPen
        """Return the frame pen.
        """
        return QPen(self.__framePen)

    def setFrameBrush(self, brush):
        # type: (QBrush) -> None
        """Set the frame brush.
        """
        self.__framePathItem.setBrush(brush)

    def frameBrush(self):
        # type: () -> QBrush
        """Return the frame brush.
        """
        return self.__framePathItem.brush()

    def __updateFrameStyle(self):
        # type: () -> None
        if self.isSelected():
            pen = QPen(QColor(96, 158, 215), 1.25, Qt.DashDotLine)
        else:
            pen = self.__framePen

        self.__framePathItem.setPen(pen)

    def contentType(self):
        # type: () -> str
        return self.__contentType

    def setContent(self, content, contentType="text/plain"):
        # type: (str, str) -> None
        if self.__content != content or self.__contentType != contentType:
            self.__contentType = contentType
            self.__content = content
            self.__updateRenderedContent()
            self.contentChanged.emit()

    def content(self):
        # type: () -> str
        return self.__content

    def setPlainText(self, text):
        # type: (str) -> None
        """Set the annotation text as plain text.
        """
        self.setContent(text, "text/plain")

    def toPlainText(self):
        # type: () -> str
        return self.__textItem.toPlainText()

    def setHtml(self, text):
        # type: (str) -> None
        """Set the annotation text as html.
        """
        self.setContent(text, "text/html")

    def toHtml(self):
        # type: () -> str
        return self.__textItem.toHtml()

    def setDefaultTextColor(self, color):
        # type: (QColor) -> None
        """Set the default text color.
        """
        self.__textItem.setDefaultTextColor(color)

    def defaultTextColor(self):
        # type: () -> QColor
        return self.__textItem.defaultTextColor()

    def setTextMargins(self, left, top, right, bottom):
        # type: (int, int, int, int) -> None
        """Set the text margins.
        """
        margins = (left, top, right, bottom)
        if self.__textMargins != margins:
            self.__textMargins = margins
            self.__textItem.setPos(left, top)
            self.__textItem.setTextWidth(
                max(self.geometry().width() - left - right, 0))

    def textMargins(self):
        # type: () -> Tuple[int, int, int, int]
        """Return the text margins.
        """
        return self.__textMargins

    def document(self):
        # type: () -> QTextDocument
        """Return the QTextDocument instance used internally.
        """
        return self.__textItem.document()

    def setTextCursor(self, cursor):
        # type: (QTextCursor) -> None
        self.__textItem.setTextCursor(cursor)

    def textCursor(self):
        # type: () -> QTextCursor
        return self.__textItem.textCursor()

    def setTextInteractionFlags(self, flags):
        # type: (Union[Qt.TextInteractionFlags, Qt.TextInteractionFlag]) -> None
        self.__textInteractionFlags = Qt.TextInteractionFlags(flags)

    def textInteractionFlags(self):
        # type: () -> Qt.TextInteractionFlags
        return Qt.TextInteractionFlags(self.__textInteractionFlags)

    def setDefaultStyleSheet(self, stylesheet):
        # type: (str) -> None
        self.document().setDefaultStyleSheet(stylesheet)

    def mouseDoubleClickEvent(self, event):
        # type: (QGraphicsSceneMouseEvent) -> None
        super().mouseDoubleClickEvent(event)

        if event.buttons() == Qt.LeftButton and \
                self.__textInteractionFlags & Qt.TextEditable:
            self.startEdit()

    def startEdit(self):
        # type: () -> None
        """Start the annotation text edit process.
        """
        self.__textItem.setPlainText(self.__content)
        self.__textItem.setTextInteractionFlags(self.__textInteractionFlags)
        self.__textItem.setFocus(Qt.MouseFocusReason)
        self.__textItem.document().contentsChanged.connect(self.textEdited)

    def endEdit(self):
        # type: () -> None
        """End the annotation edit.
        """
        content = self.__textItem.toPlainText()

        self.__textItem.setTextInteractionFlags(self.__defaultInteractionFlags)
        self.__textItem.document().contentsChanged.disconnect(self.textEdited)
        cursor = self.__textItem.textCursor()
        cursor.clearSelection()
        self.__textItem.setTextCursor(cursor)
        self.__content = content

        self.editingFinished.emit()
        # Cannot change the textItem's html immediately, this method is
        # invoked from it.
        # TODO: Separate the editor from the view.
        QMetaObject.invokeMethod(self, "__updateRenderedContent",
                                 Qt.QueuedConnection)

    def __onDocumentSizeChanged(self, size):
        # type: (QSizeF) -> None
        # The size of the text document has changed. Expand the text
        # control rect's height if the text no longer fits inside.
        rect = self.geometry()
        _, top, _, bottom = self.textMargins()
        if rect.height() < (size.height() + bottom + top):
            rect.setHeight(size.height() + bottom + top)
            self.setGeometry(rect)

    def __updateFrame(self):
        # type: () -> None
        rect = self.geometry()
        rect.moveTo(0, 0)
        path = QPainterPath()
        path.addRect(rect)
        self.__framePathItem.setPath(path)

    def resizeEvent(self, event):
        # type: (QGraphicsSceneResizeEvent) -> None
        width = event.newSize().width()
        left, _, right, _ = self.textMargins()
        self.__textItem.setTextWidth(max(width - left - right, 0))
        self.__updateFrame()
        super().resizeEvent(event)

    def __textEditingFinished(self):
        # type: () -> None
        self.endEdit()

    def sceneEventFilter(self, obj, event):
        # type: (QGraphicsItem, QEvent) -> bool
        if obj is self.__textItem and \
                not (self.__textItem.hasFocus() and
                     self.__textItem.textInteractionFlags() & Qt.TextEditable) and \
                event.type() in {QEvent.GraphicsSceneContextMenu} and \
                event.modifiers() & Qt.AltModifier:
            # Handle Alt + context menu events here
            self.contextMenuEvent(event)
            event.accept()
            return True
        return super().sceneEventFilter(obj, event)

    def changeEvent(self, event):
        # type: (QEvent) -> None
        if event.type() == QEvent.FontChange:
            self.__textItem.setFont(self.font())
        elif event.type() == QEvent.PaletteChange:
            self.__textItem.setDefaultTextColor(self.palette().color(
                QPalette.Text))
        super().changeEvent(event)

    @Slot()
    def __updateRenderedContent(self):
        # type: () -> None
        self.__textItem.setHtml(
            markup.render_as_rich_text(self.__content, self.__contentType))

    def contextMenuEvent(self, event):
        # type: (QGraphicsSceneContextMenuEvent) -> None
        if event.modifiers() & Qt.AltModifier:
            menu = QMenu(event.widget())
            menu.setAttribute(Qt.WA_DeleteOnClose)
            formatmenu = menu.addMenu("Render as")
            group = QActionGroup(self)

            def makeaction(text, parent, data=None, **kwargs):
                # type: (str, QObject, Any, Any) -> QAction
                action = QAction(text, parent, **kwargs)
                if data is not None:
                    action.setData(data)
                return action

            formatactions = [
                makeaction("Plain Text",
                           group,
                           checkable=True,
                           toolTip=self.tr("Render contents as plain text"),
                           data="text/plain"),
                makeaction("HTML",
                           group,
                           checkable=True,
                           toolTip=self.tr("Render contents as HTML"),
                           data="text/html"),
                makeaction("RST",
                           group,
                           checkable=True,
                           toolTip=self.tr("Render contents as RST "
                                           "(reStructuredText)"),
                           data="text/rst"),
                makeaction("Markdown",
                           group,
                           checkable=True,
                           toolTip=self.tr("Render contents as Markdown"),
                           data="text/markdown")
            ]
            for action in formatactions:
                action.setChecked(action.data() == self.__contentType.lower())
                formatmenu.addAction(action)

            def ontriggered(action):
                # type: (QAction) -> None
                mimetype = action.data()
                content = self.content()
                self.setContent(content, mimetype)
                self.editingFinished.emit()

            menu.triggered.connect(ontriggered)
            menu.popup(event.screenPos())
            event.accept()
        else:
            event.ignore()
コード例 #2
0
class TextAnnotation(Annotation):
    """Text annotation item for the canvas scheme.

    """

    editingFinished = Signal()
    """Emitted when the editing is finished (i.e. the item loses focus)."""

    textEdited = Signal()
    """Emitted when the edited text changes."""

    def __init__(self, parent=None, **kwargs):
        Annotation.__init__(self, parent, **kwargs)
        self.setFlag(QGraphicsItem.ItemIsMovable)
        self.setFlag(QGraphicsItem.ItemIsSelectable)

        self.setFocusPolicy(Qt.ClickFocus)

        self.__textMargins = (2, 2, 2, 2)

        rect = self.geometry().translated(-self.pos())
        self.__framePen = QPen(Qt.NoPen)
        self.__framePathItem = QGraphicsPathItem(self)
        self.__framePathItem.setPen(self.__framePen)

        self.__textItem = GraphicsTextEdit(self)
        self.__textItem.setPlaceholderText(self.tr("Enter text here"))
        self.__textItem.setPos(2, 2)
        self.__textItem.setTextWidth(rect.width() - 4)
        self.__textItem.setTabChangesFocus(True)
        self.__textItem.setTextInteractionFlags(Qt.NoTextInteraction)
        self.__textItem.setFont(self.font())
        self.__textInteractionFlags = Qt.NoTextInteraction

        layout = self.__textItem.document().documentLayout()
        layout.documentSizeChanged.connect(self.__onDocumentSizeChanged)

        self.__updateFrame()

    def adjustSize(self):
        """Resize to a reasonable size.
        """
        self.__textItem.setTextWidth(-1)
        self.__textItem.adjustSize()
        size = self.__textItem.boundingRect().size()
        left, top, right, bottom = self.textMargins()
        geom = QRectF(self.pos(), size + QSizeF(left + right, top + bottom))
        self.setGeometry(geom)

    def setFramePen(self, pen):
        """Set the frame pen. By default Qt.NoPen is used (i.e. the frame
        is not shown).
        """
        if pen != self.__framePen:
            self.__framePen = QPen(pen)
            self.__updateFrameStyle()

    def framePen(self):
        """Return the frame pen.
        """
        return QPen(self.__framePen)

    def setFrameBrush(self, brush):
        """Set the frame brush.
        """
        self.__framePathItem.setBrush(brush)

    def frameBrush(self):
        """Return the frame brush.
        """
        return self.__framePathItem.brush()

    def __updateFrameStyle(self):
        if self.isSelected():
            pen = QPen(QColor(96, 158, 215), 1.25, Qt.DashDotLine)
        else:
            pen = self.__framePen

        self.__framePathItem.setPen(pen)

    def setPlainText(self, text):
        """Set the annotation plain text.
        """
        self.__textItem.setPlainText(text)

    def toPlainText(self):
        return self.__textItem.toPlainText()

    def setHtml(self, text):
        """Set the annotation rich text.
        """
        self.__textItem.setHtml(text)

    def toHtml(self):
        return self.__textItem.toHtml()

    def setDefaultTextColor(self, color):
        """Set the default text color.
        """
        self.__textItem.setDefaultTextColor(color)

    def defaultTextColor(self):
        return self.__textItem.defaultTextColor()

    def setTextMargins(self, left, top, right, bottom):
        """Set the text margins.
        """
        margins = (left, top, right, bottom)
        if self.__textMargins != margins:
            self.__textMargins = margins
            self.__textItem.setPos(left, top)
            self.__textItem.setTextWidth(max(self.geometry().width() - left - right, 0))

    def textMargins(self):
        """Return the text margins.
        """
        return self.__textMargins

    def document(self):
        """Return the QTextDocument instance used internally.
        """
        return self.__textItem.document()

    def setTextCursor(self, cursor):
        self.__textItem.setTextCursor(cursor)

    def textCursor(self):
        return self.__textItem.textCursor()

    def setTextInteractionFlags(self, flags):
        self.__textInteractionFlags = flags

    def textInteractionFlags(self):
        return self.__textInteractionFlags

    def setDefaultStyleSheet(self, stylesheet):
        self.document().setDefaultStyleSheet(stylesheet)

    def mouseDoubleClickEvent(self, event):
        Annotation.mouseDoubleClickEvent(self, event)

        if event.buttons() == Qt.LeftButton and self.__textInteractionFlags & Qt.TextEditable:
            self.startEdit()

    def startEdit(self):
        """Start the annotation text edit process.
        """
        self.__textItem.setTextInteractionFlags(self.__textInteractionFlags)
        self.__textItem.setFocus(Qt.MouseFocusReason)

        # Install event filter to find out when the text item loses focus.
        self.__textItem.installSceneEventFilter(self)
        self.__textItem.document().contentsChanged.connect(self.textEdited)

    def endEdit(self):
        """End the annotation edit.
        """
        self.__textItem.setTextInteractionFlags(Qt.NoTextInteraction)
        self.__textItem.removeSceneEventFilter(self)
        self.__textItem.document().contentsChanged.disconnect(self.textEdited)
        cursor = self.__textItem.textCursor()
        cursor.clearSelection()
        self.__textItem.setTextCursor(cursor)
        self.editingFinished.emit()

    def __onDocumentSizeChanged(self, size):
        # The size of the text document has changed. Expand the text
        # control rect's height if the text no longer fits inside.
        try:
            rect = self.geometry()
            _, top, _, bottom = self.textMargins()
            if rect.height() < (size.height() + bottom + top):
                rect.setHeight(size.height() + bottom + top)
                self.setGeometry(rect)
        except Exception:
            log.error("error in __onDocumentSizeChanged", exc_info=True)

    def __updateFrame(self):
        rect = self.geometry()
        rect.moveTo(0, 0)
        path = QPainterPath()
        path.addRect(rect)
        self.__framePathItem.setPath(path)

    def resizeEvent(self, event):
        width = event.newSize().width()
        left, _, right, _ = self.textMargins()
        self.__textItem.setTextWidth(max(width - left - right, 0))
        self.__updateFrame()
        QGraphicsWidget.resizeEvent(self, event)

    def sceneEventFilter(self, obj, event):
        if obj is self.__textItem and event.type() == QEvent.FocusOut:
            self.__textItem.focusOutEvent(event)
            self.endEdit()
            return True

        return Annotation.sceneEventFilter(self, obj, event)

    def itemChange(self, change, value):
        if change == QGraphicsItem.ItemSelectedHasChanged:
            self.__updateFrameStyle()

        return Annotation.itemChange(self, change, value)

    def changeEvent(self, event):
        if event.type() == QEvent.FontChange:
            self.__textItem.setFont(self.font())

        Annotation.changeEvent(self, event)
コード例 #3
0
ファイル: annotationitem.py プロジェクト: PrimozGodec/orange3
class TextAnnotation(Annotation):
    """
    Text annotation item for the canvas scheme.

    Text interaction (if enabled) is started by double clicking the item.
    """
    #: Emitted when the editing is finished (i.e. the item loses edit focus).
    editingFinished = Signal()

    #: Emitted when the text content changes on user interaction.
    textEdited = Signal()

    #: Emitted when the text annotation's contents change
    #: (`content` or `contentType` changed)
    contentChanged = Signal()

    #: Mapping of supported content types to corresponding
    #: content -> html transformer.
    ContentRenderer = OrderedDict([
        ("text/plain", render_plain),
        ("text/rst", render_rst),
        ("text/html", render_html),
    ])  # type: Dict[str, Callable[[str], [str]]]

    def __init__(self, parent=None, **kwargs):
        super().__init__(None, **kwargs)
        self.setFlag(QGraphicsItem.ItemIsMovable)
        self.setFlag(QGraphicsItem.ItemIsSelectable)

        self.setFocusPolicy(Qt.ClickFocus)

        self.__contentType = "text/plain"
        self.__content = ""
        self.__renderer = render_plain

        self.__textMargins = (2, 2, 2, 2)
        self.__textInteractionFlags = Qt.NoTextInteraction
        self.__defaultInteractionFlags = (
            Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard)

        rect = self.geometry().translated(-self.pos())
        self.__framePen = QPen(Qt.NoPen)
        self.__framePathItem = QGraphicsPathItem(self)
        self.__framePathItem.setPen(self.__framePen)

        self.__textItem = GraphicsTextEdit(self)
        self.__textItem.setOpenExternalLinks(True)
        self.__textItem.setPlaceholderText(self.tr("Enter text here"))
        self.__textItem.setPos(2, 2)
        self.__textItem.setTextWidth(rect.width() - 4)
        self.__textItem.setTabChangesFocus(True)
        self.__textItem.setTextInteractionFlags(self.__defaultInteractionFlags)
        self.__textItem.setFont(self.font())
        self.__textItem.editingFinished.connect(self.__textEditingFinished)
        self.__textItem.setDefaultTextColor(
            self.palette().color(QPalette.Text)
        )
        if self.__textItem.scene() is not None:
            self.__textItem.installSceneEventFilter(self)
        layout = self.__textItem.document().documentLayout()
        layout.documentSizeChanged.connect(self.__onDocumentSizeChanged)

        self.__updateFrame()
        # set parent item at the end in order to ensure
        # QGraphicsItem.ItemSceneHasChanged is delivered after initialization
        if parent is not None:
            self.setParentItem(parent)

    def itemChange(self, change, value):
        if change == QGraphicsItem.ItemSceneHasChanged:
            if self.__textItem.scene() is not None:
                self.__textItem.installSceneEventFilter(self)
        if change == QGraphicsItem.ItemSelectedHasChanged:
            self.__updateFrameStyle()
        return super().itemChange(change, value)

    def adjustSize(self):
        """Resize to a reasonable size.
        """
        self.__textItem.setTextWidth(-1)
        self.__textItem.adjustSize()
        size = self.__textItem.boundingRect().size()
        left, top, right, bottom = self.textMargins()
        geom = QRectF(self.pos(), size + QSizeF(left + right, top + bottom))
        self.setGeometry(geom)

    def setFramePen(self, pen):
        """Set the frame pen. By default Qt.NoPen is used (i.e. the frame
        is not shown).
        """
        if pen != self.__framePen:
            self.__framePen = QPen(pen)
            self.__updateFrameStyle()

    def framePen(self):
        """Return the frame pen.
        """
        return QPen(self.__framePen)

    def setFrameBrush(self, brush):
        """Set the frame brush.
        """
        self.__framePathItem.setBrush(brush)

    def frameBrush(self):
        """Return the frame brush.
        """
        return self.__framePathItem.brush()

    def __updateFrameStyle(self):
        if self.isSelected():
            pen = QPen(QColor(96, 158, 215), 1.25, Qt.DashDotLine)
        else:
            pen = self.__framePen

        self.__framePathItem.setPen(pen)

    def contentType(self):
        return self.__contentType

    def setContent(self, content, contentType="text/plain"):
        if self.__content != content or self.__contentType != contentType:
            self.__contentType = contentType
            self.__content = content
            self.__updateRenderedContent()
            self.contentChanged.emit()

    def content(self):
        return self.__content

    def setPlainText(self, text):
        """Set the annotation text as plain text.
        """
        self.setContent(text, "text/plain")

    def toPlainText(self):
        return self.__textItem.toPlainText()

    def setHtml(self, text):
        """Set the annotation text as html.
        """
        self.setContent(text, "text/html")

    def toHtml(self):
        return self.__textItem.toHtml()

    def setDefaultTextColor(self, color):
        """Set the default text color.
        """
        self.__textItem.setDefaultTextColor(color)

    def defaultTextColor(self):
        return self.__textItem.defaultTextColor()

    def setTextMargins(self, left, top, right, bottom):
        """Set the text margins.
        """
        margins = (left, top, right, bottom)
        if self.__textMargins != margins:
            self.__textMargins = margins
            self.__textItem.setPos(left, top)
            self.__textItem.setTextWidth(
                max(self.geometry().width() - left - right, 0)
            )

    def textMargins(self):
        """Return the text margins.
        """
        return self.__textMargins

    def document(self):
        """Return the QTextDocument instance used internally.
        """
        return self.__textItem.document()

    def setTextCursor(self, cursor):
        self.__textItem.setTextCursor(cursor)

    def textCursor(self):
        return self.__textItem.textCursor()

    def setTextInteractionFlags(self, flags):
        self.__textInteractionFlags = flags

    def textInteractionFlags(self):
        return self.__textInteractionFlags

    def setDefaultStyleSheet(self, stylesheet):
        self.document().setDefaultStyleSheet(stylesheet)

    def mouseDoubleClickEvent(self, event):
        Annotation.mouseDoubleClickEvent(self, event)

        if event.buttons() == Qt.LeftButton and \
                self.__textInteractionFlags & Qt.TextEditable:
            self.startEdit()

    def startEdit(self):
        """Start the annotation text edit process.
        """
        self.__textItem.setPlainText(self.__content)
        self.__textItem.setTextInteractionFlags(self.__textInteractionFlags)
        self.__textItem.setFocus(Qt.MouseFocusReason)
        self.__textItem.document().contentsChanged.connect(
            self.textEdited
        )

    def endEdit(self):
        """End the annotation edit.
        """
        content = self.__textItem.toPlainText()

        self.__textItem.setTextInteractionFlags(self.__defaultInteractionFlags)
        self.__textItem.document().contentsChanged.disconnect(
            self.textEdited
        )
        cursor = self.__textItem.textCursor()
        cursor.clearSelection()
        self.__textItem.setTextCursor(cursor)
        self.__content = content

        self.editingFinished.emit()
        # Cannot change the textItem's html immediately, this method is
        # invoked from it.
        # TODO: Separate the editor from the view.
        QMetaObject.invokeMethod(
            self, "__updateRenderedContent", Qt.QueuedConnection)

    def __onDocumentSizeChanged(self, size):
        # The size of the text document has changed. Expand the text
        # control rect's height if the text no longer fits inside.
        rect = self.geometry()
        _, top, _, bottom = self.textMargins()
        if rect.height() < (size.height() + bottom + top):
            rect.setHeight(size.height() + bottom + top)
            self.setGeometry(rect)

    def __updateFrame(self):
        rect = self.geometry()
        rect.moveTo(0, 0)
        path = QPainterPath()
        path.addRect(rect)
        self.__framePathItem.setPath(path)

    def resizeEvent(self, event):
        width = event.newSize().width()
        left, _, right, _ = self.textMargins()
        self.__textItem.setTextWidth(max(width - left - right, 0))
        self.__updateFrame()
        QGraphicsWidget.resizeEvent(self, event)

    def __textEditingFinished(self):
        self.endEdit()

    def sceneEventFilter(self, obj, event):
        if obj is self.__textItem and \
                not (self.__textItem.hasFocus() and
                     self.__textItem.textInteractionFlags() & Qt.TextEditable) and \
                event.type() in {QEvent.GraphicsSceneContextMenu} and \
                event.modifiers() & Qt.AltModifier:
            # Handle Alt + context menu events here
            self.contextMenuEvent(event)
            event.accept()
            return True
        return super().sceneEventFilter(obj, event)

    def changeEvent(self, event):
        if event.type() == QEvent.FontChange:
            self.__textItem.setFont(self.font())
        elif event.type() == QEvent.PaletteChange:
            self.__textItem.setDefaultTextColor(
                self.palette().color(QPalette.Text)
            )
        Annotation.changeEvent(self, event)

    @Slot()
    def __updateRenderedContent(self):
        try:
            renderer = TextAnnotation.ContentRenderer[self.__contentType]
        except KeyError:
            renderer = render_plain
        self.__textItem.setHtml(renderer(self.__content))

    def contextMenuEvent(self, event):
        if event.modifiers() & Qt.AltModifier:
            menu = QMenu(event.widget())
            menu.setAttribute(Qt.WA_DeleteOnClose)
            formatmenu = menu.addMenu("Render as")
            group = QActionGroup(self, exclusive=True)

            def makeaction(text, parent, data=None, **kwargs):
                action = QAction(text, parent, **kwargs)
                if data is not None:
                    action.setData(data)
                return action

            formatactions = [
                makeaction("Plain Text", group, checkable=True,
                           toolTip=self.tr("Render contents as plain text"),
                           data="text/plain"),
                makeaction("HTML", group, checkable=True,
                           toolTip=self.tr("Render contents as HTML"),
                           data="text/html"),
                makeaction("RST", group, checkable=True,
                           toolTip=self.tr("Render contents as RST "
                                           "(reStructuredText)"),
                           data="text/rst"),
                makeaction("Markdown", group, checkable=True,
                           toolTip=self.tr("Render contents as Markdown"),
                           data="text/markdown")
            ]
            for action in formatactions:
                action.setChecked(action.data() == self.__contentType.lower())
                formatmenu.addAction(action)

            def ontriggered(action):
                mimetype = action.data()
                content = self.content()
                self.setContent(content, mimetype)
                self.editingFinished.emit()

            menu.triggered.connect(ontriggered)
            menu.popup(event.screenPos())
            event.accept()
        else:
            event.ignore()
コード例 #4
0
ファイル: annotationitem.py プロジェクト: zaffnet/orange3
class TextAnnotation(Annotation):
    """Text annotation item for the canvas scheme.

    """
    editingFinished = Signal()
    """Emitted when the editing is finished (i.e. the item loses focus)."""

    textEdited = Signal()
    """Emitted when the edited text changes."""
    def __init__(self, parent=None, **kwargs):
        Annotation.__init__(self, parent, **kwargs)
        self.setFlag(QGraphicsItem.ItemIsMovable)
        self.setFlag(QGraphicsItem.ItemIsSelectable)

        self.setFocusPolicy(Qt.ClickFocus)

        self.__textMargins = (2, 2, 2, 2)

        rect = self.geometry().translated(-self.pos())
        self.__framePen = QPen(Qt.NoPen)
        self.__framePathItem = QGraphicsPathItem(self)
        self.__framePathItem.setPen(self.__framePen)

        self.__textItem = GraphicsTextEdit(self)
        self.__textItem.setPlaceholderText(self.tr("Enter text here"))
        self.__textItem.setPos(2, 2)
        self.__textItem.setTextWidth(rect.width() - 4)
        self.__textItem.setTabChangesFocus(True)
        self.__textItem.setTextInteractionFlags(Qt.NoTextInteraction)
        self.__textItem.setFont(self.font())
        self.__textInteractionFlags = Qt.NoTextInteraction

        layout = self.__textItem.document().documentLayout()
        layout.documentSizeChanged.connect(self.__onDocumentSizeChanged)

        self.__updateFrame()

    def adjustSize(self):
        """Resize to a reasonable size.
        """
        self.__textItem.setTextWidth(-1)
        self.__textItem.adjustSize()
        size = self.__textItem.boundingRect().size()
        left, top, right, bottom = self.textMargins()
        geom = QRectF(self.pos(), size + QSizeF(left + right, top + bottom))
        self.setGeometry(geom)

    def setFramePen(self, pen):
        """Set the frame pen. By default Qt.NoPen is used (i.e. the frame
        is not shown).
        """
        if pen != self.__framePen:
            self.__framePen = QPen(pen)
            self.__updateFrameStyle()

    def framePen(self):
        """Return the frame pen.
        """
        return QPen(self.__framePen)

    def setFrameBrush(self, brush):
        """Set the frame brush.
        """
        self.__framePathItem.setBrush(brush)

    def frameBrush(self):
        """Return the frame brush.
        """
        return self.__framePathItem.brush()

    def __updateFrameStyle(self):
        if self.isSelected():
            pen = QPen(QColor(96, 158, 215), 1.25, Qt.DashDotLine)
        else:
            pen = self.__framePen

        self.__framePathItem.setPen(pen)

    def setPlainText(self, text):
        """Set the annotation plain text.
        """
        self.__textItem.setPlainText(text)

    def toPlainText(self):
        return self.__textItem.toPlainText()

    def setHtml(self, text):
        """Set the annotation rich text.
        """
        self.__textItem.setHtml(text)

    def toHtml(self):
        return self.__textItem.toHtml()

    def setDefaultTextColor(self, color):
        """Set the default text color.
        """
        self.__textItem.setDefaultTextColor(color)

    def defaultTextColor(self):
        return self.__textItem.defaultTextColor()

    def setTextMargins(self, left, top, right, bottom):
        """Set the text margins.
        """
        margins = (left, top, right, bottom)
        if self.__textMargins != margins:
            self.__textMargins = margins
            self.__textItem.setPos(left, top)
            self.__textItem.setTextWidth(
                max(self.geometry().width() - left - right, 0))

    def textMargins(self):
        """Return the text margins.
        """
        return self.__textMargins

    def document(self):
        """Return the QTextDocument instance used internally.
        """
        return self.__textItem.document()

    def setTextCursor(self, cursor):
        self.__textItem.setTextCursor(cursor)

    def textCursor(self):
        return self.__textItem.textCursor()

    def setTextInteractionFlags(self, flags):
        self.__textInteractionFlags = flags

    def textInteractionFlags(self):
        return self.__textInteractionFlags

    def setDefaultStyleSheet(self, stylesheet):
        self.document().setDefaultStyleSheet(stylesheet)

    def mouseDoubleClickEvent(self, event):
        Annotation.mouseDoubleClickEvent(self, event)

        if event.buttons() == Qt.LeftButton and \
                self.__textInteractionFlags & Qt.TextEditable:
            self.startEdit()

    def startEdit(self):
        """Start the annotation text edit process.
        """
        self.__textItem.setTextInteractionFlags(self.__textInteractionFlags)
        self.__textItem.setFocus(Qt.MouseFocusReason)

        # Install event filter to find out when the text item loses focus.
        self.__textItem.installSceneEventFilter(self)
        self.__textItem.document().contentsChanged.connect(self.textEdited)

    def endEdit(self):
        """End the annotation edit.
        """
        self.__textItem.setTextInteractionFlags(Qt.NoTextInteraction)
        self.__textItem.removeSceneEventFilter(self)
        self.__textItem.document().contentsChanged.disconnect(self.textEdited)
        cursor = self.__textItem.textCursor()
        cursor.clearSelection()
        self.__textItem.setTextCursor(cursor)
        self.editingFinished.emit()

    def __onDocumentSizeChanged(self, size):
        # The size of the text document has changed. Expand the text
        # control rect's height if the text no longer fits inside.
        try:
            rect = self.geometry()
            _, top, _, bottom = self.textMargins()
            if rect.height() < (size.height() + bottom + top):
                rect.setHeight(size.height() + bottom + top)
                self.setGeometry(rect)
        except Exception:
            log.error("error in __onDocumentSizeChanged", exc_info=True)

    def __updateFrame(self):
        rect = self.geometry()
        rect.moveTo(0, 0)
        path = QPainterPath()
        path.addRect(rect)
        self.__framePathItem.setPath(path)

    def resizeEvent(self, event):
        width = event.newSize().width()
        left, _, right, _ = self.textMargins()
        self.__textItem.setTextWidth(max(width - left - right, 0))
        self.__updateFrame()
        QGraphicsWidget.resizeEvent(self, event)

    def sceneEventFilter(self, obj, event):
        if obj is self.__textItem and event.type() == QEvent.FocusOut:
            self.__textItem.focusOutEvent(event)
            self.endEdit()
            return True

        return Annotation.sceneEventFilter(self, obj, event)

    def itemChange(self, change, value):
        if change == QGraphicsItem.ItemSelectedHasChanged:
            self.__updateFrameStyle()

        return Annotation.itemChange(self, change, value)

    def changeEvent(self, event):
        if event.type() == QEvent.FontChange:
            self.__textItem.setFont(self.font())

        Annotation.changeEvent(self, event)