Exemple #1
0
 def __render_fullname(self, width, index):
     fullname = index.data(self.FullnameRole).toPyObject()
     doc = QTextDocument()
     doc.setHtml("<b>%s</b>" % fullname)
     doc.setDefaultFont(FULLNAME_FONT)
     doc.setTextWidth(self.__calculate_text_width(width))
     return doc
Exemple #2
0
    def drawContents(self, painter):
        """
        Reimplementation of drawContents to limit the drawing
        inside `textRext`.

        """
        painter.setPen(self.__color)
        painter.setFont(self.font())

        if self.__textRect:
            rect = self.__textRect
        else:
            rect = self.rect().adjusted(5, 5, -5, -5)
        if Qt.mightBeRichText(self.__message):
            doc = QTextDocument()
            doc.setHtml(self.__message)
            doc.setTextWidth(rect.width())
            cursor = QTextCursor(doc)
            cursor.select(QTextCursor.Document)
            fmt = QTextBlockFormat()
            fmt.setAlignment(self.__alignment)
            cursor.mergeBlockFormat(fmt)
            painter.save()
            painter.translate(rect.topLeft())
            doc.drawContents(painter)
            painter.restore()
        else:
            painter.drawText(rect, self.__alignment, self.__message)
Exemple #3
0
    def drawContents(self, painter):
        """
        Reimplementation of drawContents to limit the drawing
        inside `textRext`.

        """
        painter.setPen(self.__color)
        painter.setFont(self.font())

        if self.__textRect:
            rect = self.__textRect
        else:
            rect = self.rect().adjusted(5, 5, -5, -5)
        if Qt.mightBeRichText(self.__message):
            doc = QTextDocument()
            doc.setHtml(self.__message)
            doc.setTextWidth(rect.width())
            cursor = QTextCursor(doc)
            cursor.select(QTextCursor.Document)
            fmt = QTextBlockFormat()
            fmt.setAlignment(self.__alignment)
            cursor.mergeBlockFormat(fmt)
            painter.save()
            painter.translate(rect.topLeft())
            doc.drawContents(painter)
            painter.restore()
        else:
            painter.drawText(rect, self.__alignment, self.__message)
Exemple #4
0
 def __render_fullname(self, width, index):
     fullname = index.data(self.FullnameRole).toPyObject()
     doc = QTextDocument()
     doc.setHtml("<b>%s</b>" % fullname)
     doc.setDefaultFont(FULLNAME_FONT)
     doc.setTextWidth(self.__calculate_text_width(width))
     return doc
Exemple #5
0
def stamp_image(image, expression_str, position, feature):
    painter = QPainter(image)
    data = QgsExpression.replaceExpressionText(expression_str, feature, None)
    if not data:
        return image

    data = data.replace(r"\n", "<br>")
    style = """
    body {
        color: yellow;
    }
    """
    doc = QTextDocument()
    doc.setDefaultStyleSheet(style)
    data = "<body>{}</body>".format(data)
    doc.setHtml(data)
    point = QPointF(20, 20)

    # Wrap the text so we don't go crazy
    if doc.size().width() > 300:
        doc.setTextWidth(300)
    if position == "top-left":
        point = QPointF(20, 20)
    elif position == "top-right":
        x = image.width() - 20 - doc.size().width()
        point = QPointF(x, 20)
    elif position == "bottom-left":
        point = QPointF(20, image.height() - 20 - doc.size().height())
    elif position == "bottom-right":
        x = image.width() - 20 - doc.size().width()
        y = image.height() - 20 - doc.size().height()
        point = QPointF(x, y)
    painter.translate(point)
    doc.drawContents(painter)
    return image
Exemple #6
0
	def sizeHint(self, option, index):
		options = QStyleOptionViewItemV4(option)
		self.initStyleOption(options,index)

		doc = QTextDocument()
		doc.setHtml(options.text)
		doc.setTextWidth(options.rect.width())
		return QtCore.QSize(doc.idealWidth(), doc.size().height())
Exemple #7
0
    def sizeHint(self, option, index):
        options = QStyleOptionViewItemV4(option)
        self.initStyleOption(options, index)

        doc = QTextDocument()
        doc.setHtml(options.text)
        doc.setTextWidth(options.rect.width())
        return QtCore.QSize(doc.idealWidth(), doc.size().height())
Exemple #8
0
    def sizeHint(self, option, index):
        """Calculate the needed size."""
        options = QStyleOptionViewItemV4(option)
        self.initStyleOption(options, index)

        doc = QTextDocument()
        doc.setHtml(options.text)
        doc.setTextWidth(options.rect.width())
        return QSize(doc.idealWidth(), doc.size().height())
Exemple #9
0
    def sizeHint(self, option, index):
        optionV4 = QStyleOptionViewItemV4(option)
        self.initStyleOption(optionV4, index)

        doc = QTextDocument()
        doc.setHtml(optionV4.text)
        doc.setTextWidth(optionV4.rect.width())

        return QSize(doc.idealWidth(), max(doc.size().height(), optionV4.decorationSize.height()))
Exemple #10
0
    def sizeHint(self, option, index):
        optionV4 = QStyleOptionViewItemV4(option)
        self.initStyleOption(optionV4, index)

        doc = QTextDocument()
        doc.setHtml(optionV4.text)
        doc.setTextWidth(optionV4.rect.width())

        return QSize(doc.idealWidth(), max(doc.size().height(), optionV4.decorationSize.height()))
Exemple #11
0
    def sizeHint(self, option, index):
        """Calculate the needed size."""
        options = QStyleOptionViewItemV4(option)
        self.initStyleOption(options, index)

        doc = QTextDocument()
        doc.setHtml(options.text)
        doc.setTextWidth(options.rect.width())
        return QSize(doc.idealWidth(), doc.size().height())
Exemple #12
0
 def __render_username(self, width, index):
     username_string = index.data(self.UsernameRole).toPyObject()
     username = QTextDocument()
     if username_string != '':
         username.setHtml("<span style='color: #666;'>@%s</span>" % username_string)
     else:
         username.setHtml("<span style='color: #666;'></span>" % username_string)
     username.setDefaultFont(USERNAME_FONT)
     username.setTextWidth(self.__calculate_text_width(width))
     return username
Exemple #13
0
 def __render_status_message(self, width, index):
     message = unicode(index.data(self.MessageRole).toPyObject())
     urls = index.data(self.URLsEntitiesRole).toPyObject()
     for url in urls:
         pretty_url = "<a href='%s'>%s</a>" % (url.url, url.display_text)
         message = message.replace(url.search_for, pretty_url)
     doc = QTextDocument()
     doc.setHtml(message)
     doc.setTextWidth(self.__calculate_text_width(width))
     return doc
Exemple #14
0
 def __render_status_message(self, width, index):
     message = unicode(index.data(self.MessageRole).toPyObject())
     urls = index.data(self.URLsEntitiesRole).toPyObject()
     for url in urls:
         pretty_url = "<a href='%s'>%s</a>" % (url.url, url.display_text)
         message = message.replace(url.search_for, pretty_url)
     doc = QTextDocument()
     doc.setHtml(message)
     doc.setTextWidth(self.__calculate_text_width(width))
     return doc
Exemple #15
0
 def __render_username(self, width, index):
     username_string = index.data(self.UsernameRole).toPyObject()
     username = QTextDocument()
     if username_string != '':
         username.setHtml("<span style='color: #666;'>@%s</span>" %
                          username_string)
     else:
         username.setHtml("<span style='color: #666;'></span>" %
                          username_string)
     username.setDefaultFont(USERNAME_FONT)
     username.setTextWidth(self.__calculate_text_width(width))
     return username
 def sizeHint(self, option1, index):
     # option.rect is a zero rect
     width = self.parent().columnWidth(index.column())
     
     if index.data(Qt.DisplayRole).type() == QMetaType.Double:
         return QSize(width, 18)
     
     option = QStyleOptionViewItemV4(option1)
     self.initStyleOption(option, index)
     
     if not option.text:
         iconSize = option.icon.actualSize(QSize(32, 32))
         return QSize(32, max(32, iconSize.height() + 4))
     
     doc = QTextDocument()
     doc.setHtml(option.text)
     doc.setTextWidth(self._preferredMessageWidth(width))
     
     return QSize(doc.idealWidth(), max(32, doc.size().height() + 4))
Exemple #17
0
    def paint(self, painter, option, index):
        painter.save()

        cell_width = self.size.width()

        #if option.state & QStyle.State_Selected:
        #    painter.fillRect(option.rect, option.palette.highlight())
        #painter.drawRect(option.rect)


        # Draw marks before translating painter
        # =====================================

        # Draw avatar
        if not self.avatar:
            avatar_filepath = index.data(self.AvatarRole).toPyObject()
            self.avatar = QPixmap(avatar_filepath)
        x = option.rect.left() + (self.BOX_MARGIN * 2)
        y = option.rect.top() + (self.BOX_MARGIN * 2)
        rect = QRect(x, y, self.AVATAR_SIZE, self.AVATAR_SIZE)
        painter.drawPixmap(rect, self.avatar)

        # Draw verified account icon
        if index.data(self.VerifiedRole).toPyObject():
            rect2 = QRect(rect.right() - 11, rect.bottom() - 10, 16, 16)
            painter.drawPixmap(rect2, self.verified_icon)

        marks_margin = 0
        # Favorite mark
        if index.data(self.FavoritedRole).toPyObject():
            x = cell_width - 16 - self.BOX_MARGIN
            y = option.rect.top() + self.BOX_MARGIN
            rect = QRect(x, y, 16, 16)
            painter.drawPixmap(rect, self.favorite_icon)
            marks_margin = 16

        # Draw reposted icon
        if index.data(self.RepeatedRole).toPyObject():
            x = cell_width - 16 - self.BOX_MARGIN - marks_margin
            y = option.rect.top() + self.BOX_MARGIN
            rect = QRect(x, y, 16, 16)
            painter.drawPixmap(rect, self.repeated_icon)

        # Draw protected account icon
        protected_icon_margin = 0
        if index.data(self.ProtectedRole).toPyObject():
            x = option.rect.left() + self.BOX_MARGIN + self.AVATAR_SIZE + self.LEFT_MESSAGE_MARGIN
            y = option.rect.top() + self.BOX_MARGIN
            rect = QRect(x, y, 16, 16)
            painter.drawPixmap(rect, self.protected_icon)
            protected_icon_margin = 16

        # ==== End of pixmap drawing ====

        accumulated_height = 0

        # Draw fullname
        fullname = self.__render_fullname(cell_width, index)
        x = option.rect.left() + self.BOX_MARGIN + self.AVATAR_SIZE
        x += self.LEFT_MESSAGE_MARGIN + protected_icon_margin
        y = option.rect.top()
        painter.translate(x, y)
        fullname.drawContents(painter)

        # Draw username
        username = self.__render_username(cell_width, index)
        painter.translate(fullname.idealWidth(), 0)
        username.drawContents(painter)

        # Draw status message
        x = -fullname.idealWidth() - protected_icon_margin
        y = fullname.size().height() + self.TOP_MESSAGE_MARGIN
        painter.translate(x, y)
        message = self.__render_status_message(cell_width, index)
        message.drawContents(painter)
        accumulated_height += y + message.size().height()

        # Draw reposted by
        x = self.BOX_MARGIN + 16 - (self.LEFT_MESSAGE_MARGIN + self.AVATAR_SIZE)
        y = message.size().height() + self.BOTTOM_MESSAGE_MARGIN
        if accumulated_height < self.AVATAR_SIZE:
            y += (self.AVATAR_SIZE - accumulated_height) + self.COMPLEMENT_HEIGHT
        painter.translate(x, y)

        reposted_by = index.data(self.RepostedRole).toPyObject()
        if reposted_by:
            reposted = QTextDocument()
            reposted.setHtml("<span style='color: #999;'>%s</span>" % reposted_by)
            reposted.setDefaultFont(FOOTER_FONT)
            reposted.setTextWidth(self.__calculate_text_width(cell_width))
            reposted.drawContents(painter)

            # Draw reposted icon
            rect2 = QRect(-16, 3, 16, 16)
            painter.drawPixmap(rect2, self.reposted_icon)

        # Draw datetime
        datetime = index.data(self.DateRole).toPyObject()
        timestamp = QTextDocument()
        timestamp.setHtml("<span style='color: #999;'>%s</span>" % datetime)
        timestamp.setDefaultFont(FOOTER_FONT)
        timestamp.setTextWidth(self.__calculate_text_width(cell_width))
        x = self.size.width() - timestamp.idealWidth() - 20 - self.BOX_MARGIN
        painter.translate(x, 0)
        timestamp.drawContents(painter)

        painter.resetTransform()
        painter.translate(0, option.rect.bottom())
        line = QLine(0, 0, option.rect.width(), 0)
        painter.setPen(QColor(230, 230, 230))
        painter.drawLine(line)

        painter.restore()
class MessageItemDelegate(QStyledItemDelegate):
    def __init__(self, parentView, logger, column=None, margin=50):
        super(MessageItemDelegate, self).__init__(parentView)

        self.logger = logger
        # We need that to receive mouse move events in editorEvent
        parentView.setMouseTracking(True)

        # Revert the mouse cursor when the mouse isn't over 
        # an item but still on the view widget
        parentView.viewportEntered.connect(self.unsetParentCursor)

        self.document = QTextDocument()
        self.mouseOverDocument = self.document
        self.mouseOverDocumentRow = -1
        self.mouseOverOption = None
        self.lastTextPos = QPoint(0, 0)
        self._editIndex = None
        self._editor = None
        self._column = column
        self._margin = margin
        
        ownGradient = QLinearGradient(0, 0, 0, 10)
        ownGradient.setColorAt(0, QColor(229, 239, 254))
        ownGradient.setColorAt(1, QColor(182, 208, 251))
        self._ownBrush = QBrush(ownGradient)
        self._ownPenColor = QColor(104, 126, 164)
        
        otherGradient = QLinearGradient(0, 0, 0, 10)
        otherGradient.setColorAt(0, QColor(248, 248, 248))
        otherGradient.setColorAt(1, QColor(200, 200, 200))
        self._otherBrush = QBrush(otherGradient)
        self._otherPenColor = QColor(153, 153, 153)
        
        self._timeFont = QFont("default", 12, QFont.Bold)
        
        self.closeEditor.connect(self.editorClosing)
        
        self._rowHeights = {}
        
    @loggingSlot()
    def unsetParentCursor(self):
        self.parent().unsetCursor()
        
    def setEditIndex(self, modelIndex):
        self._editIndex = modelIndex
        
    def getEditIndex(self):
        return self._editIndex
        
    @loggingSlot(QWidget, QAbstractItemDelegate.EndEditHint)
    def editorClosing(self, _editor, _hint):
        self._editor = None
        self.setEditIndex(None)
        
    def getEditor(self):
        return self._editor
    
    def createEditor(self, parent, option_, modelIndex):
        self.setEditIndex(modelIndex)
        
        option = QStyleOptionViewItemV4(option_)
        self.initStyleOption(option, modelIndex)
        
        text = QString(option.text)
    
        editorWidget = EditorWidget(parent)
        editor = ItemEditor(text, self._preferredMessageWidth(option.rect.width()), editorWidget)
        editorWidget.setItemEditor(editor)

        messageRect = self._getMessageRect(option, editor.document(), modelIndex, relativeToItem=True)
        pos = messageRect.topLeft()
        editor.move(pos)
        editor.resize(messageRect.size())
        
        self._editor = editorWidget
        return editorWidget
    
    def setModelData(self, *_args, **_kwargs):
        pass
    
    def _preferredMessageWidth(self, textRectWidth):
        return textRectWidth - self._margin
    
    def _getMessageRect(self, option, doc, modelIndex, relativeToItem=False):
        rightAligned = modelIndex.data(ChatMessagesModel.OWN_MESSAGE_ROLE).toBool()
        statusIcon = modelIndex.data(ChatMessagesModel.STATUS_ICON_ROLE)
        hasStatusIcon = statusIcon != None and not statusIcon.isNull()
        textRect = option.rect
        
        documentWidth = doc.idealWidth()
        if rightAligned:
            xOffset = textRect.width() - documentWidth - 3
            if hasStatusIcon:
                xOffset -= 20
        else:
            xOffset = 3
            if hasStatusIcon:
                xOffset += 20
        
        height = doc.size().height()
        if modelIndex.row() not in self._rowHeights:
            self._rowHeights[modelIndex.row()] = height
        elif self._rowHeights[modelIndex.row()] != height:
            self._rowHeights[modelIndex.row()] = height
            self.sizeHintChanged.emit(modelIndex)
            
        if height < 32:
            # vertically center
            yOffset = (32. - height) / 2 + 1
        else:
            yOffset = 0
        
        textPos = QPoint(0,0) if relativeToItem else textRect.topLeft()
        textPos += QPoint(xOffset, yOffset)
        return QRect(textPos, QSize(documentWidth, height))
    
    def _paintTime(self, painter, option, modelIndex):
        if modelIndex.column() != 1:
            return
        # total rect for us to paint in
        textRect = option.rect
        
        rtime, _ok = modelIndex.data(Qt.DisplayRole).toDouble()
        timeString = formatTime(localtime(rtime))
        
        painter.save()
        painter.setRenderHint(QPainter.Antialiasing)
        painter.translate(textRect.topLeft())
        painter.setFont(self._timeFont)
        textWidth = painter.fontMetrics().width(timeString)
        painter.drawText((textRect.size().width() - textWidth) / 2, 13, timeString)
        painter.restore()
    
    def paint(self, painter, option1, modelIndex):
        if self._column is not None and modelIndex.column() != self._column:
            return super(MessageItemDelegate, self).paint(painter, option1, modelIndex)
        
        option = QStyleOptionViewItemV4(option1)
        self.initStyleOption(option, modelIndex)
        
        if modelIndex.data(Qt.DisplayRole).type() == QMetaType.Double:
            # this is a time item
            self._paintTime(painter, option, modelIndex)
            return

        text = QString(option.text)
        if not text:
            option1.decorationAlignment = Qt.AlignLeft
            return super(MessageItemDelegate, self).paint(painter, option1, modelIndex)
        
        rightAligned = modelIndex.data(ChatMessagesModel.OWN_MESSAGE_ROLE).toBool()
        selected = (int(option.state) & int(QStyle.State_Selected)) != 0
        editing = self._editIndex == modelIndex
    
        self.document.setHtml(text)
        self.document.setTextWidth(self._preferredMessageWidth(option.rect.width()))
        
        ctx = QAbstractTextDocumentLayout.PaintContext()
    
        # Highlighting text if item is selected
        if selected:
            ctx.palette.setColor(QPalette.Text, option.palette.color(QPalette.Active, QPalette.HighlightedText))
    
        # total rect for us to paint in
        textRect = option.rect
        # final rect to paint message in
        messageRect = self._getMessageRect(option, self.document, modelIndex)
        
        painter.save()
        
        mouseOver = (int(option.state) & int(QStyle.State_MouseOver)) != 0
        if mouseOver:
            self.mouseOverDocument = QTextDocument()
            self.mouseOverDocument.setHtml(text)
            self.mouseOverDocument.setTextWidth(self._preferredMessageWidth(option.rect.width()))
            self.mouseOverDocumentRow = modelIndex.row()
            self.lastTextPos = textRect.topLeft()
            self.mouseOverOption = option
        
        # draw decoration
        painter.translate(textRect.topLeft())
        statusIcon = modelIndex.data(ChatMessagesModel.STATUS_ICON_ROLE)
        if statusIcon != None and not statusIcon.isNull():
            statusIcon = QIcon(statusIcon)
            if rightAligned:
                statusIcon.paint(painter, textRect.size().width() - 19, 8, 16, 16, Qt.AlignCenter)
            else:
                statusIcon.paint(painter, 3, 8, 16, 16, Qt.AlignCenter)
                
        # draw message
        painter.restore()
        painter.save()
        painter.setRenderHint(QPainter.Antialiasing)
        painter.translate(messageRect.topLeft())
        if not editing:
            painter.setBrush(self._ownBrush if rightAligned else self._otherBrush)
        painter.setPen(self._ownPenColor if rightAligned else self._otherPenColor)
        painter.drawRoundedRect(QRectF(QPointF(0, 0.5),
                                       QSizeF(self.document.idealWidth(),
                                              self.document.size().height() - 1.)),
                                7, 7)
        painter.setClipRect(textRect.translated(-textRect.topLeft()))
        if not editing:
            self.document.documentLayout().draw(painter, ctx)
        painter.restore()

    startEditing = pyqtSignal(QModelIndex)

    def shouldStartEditAt(self, eventPos, modelIndex):
        option = QStyleOptionViewItemV4()
        option.initFrom(self.parent())
        option.rect.setHeight(32)
        self.initStyleOption(option, modelIndex)
        
        if modelIndex.row() != self.mouseOverDocumentRow:
            # TODO reset document
            self.logger.warning("shouldStartEditAt(): wrong mouse over document")
            return False
        messageRect = self._getMessageRect(self.mouseOverOption, self.mouseOverDocument, modelIndex)
        anchorPos = QPointF(eventPos) - QPointF(messageRect.topLeft())
        anchor = self.mouseOverDocument.documentLayout().anchorAt(anchorPos)
        if anchor != "":
            return False
        
        return messageRect.contains(eventPos)

    def editorEvent(self, event, _model, option_, modelIndex):
        if self._column and modelIndex.column() != self._column:
            return False
        option = QStyleOptionViewItemV4(option_)
        self.initStyleOption(option, modelIndex)
        text = QString(option.text)
        if not text:
            self.parent().unsetCursor()
            return False
        
        if event.type() not in (QEvent.MouseMove, QEvent.MouseButtonRelease, QEvent.MouseButtonPress) \
            or not (option.state & QStyle.State_Enabled):
            return False
        
        if modelIndex.row() != self.mouseOverDocumentRow:
            return False
        
        # Get the link at the mouse position
        pos = event.pos()
        messageRect = self._getMessageRect(option, self.mouseOverDocument, modelIndex)
        anchor = convert_string(self.mouseOverDocument.documentLayout().anchorAt(QPointF(pos) - QPointF(messageRect.topLeft())))
        if anchor == "":
            if messageRect.contains(pos):
                self.parent().setCursor(Qt.IBeamCursor)
            else:
                self.parent().unsetCursor()
        else:
            self.parent().setCursor(Qt.PointingHandCursor)               
            if event.type() == QEvent.MouseButtonRelease:
                if anchor.startswith(u"www."):
                    anchor = u"http://" + anchor
                webbrowser.open(anchor)
                return True 
        return False

    def sizeHint(self, option1, index):
        # option.rect is a zero rect
        width = self.parent().columnWidth(index.column())
        
        if index.data(Qt.DisplayRole).type() == QMetaType.Double:
            return QSize(width, 18)
        
        option = QStyleOptionViewItemV4(option1)
        self.initStyleOption(option, index)
        
        if not option.text:
            iconSize = option.icon.actualSize(QSize(32, 32))
            return QSize(32, max(32, iconSize.height() + 4))
        
        doc = QTextDocument()
        doc.setHtml(option.text)
        doc.setTextWidth(self._preferredMessageWidth(width))
        
        return QSize(doc.idealWidth(), max(32, doc.size().height() + 4))
Exemple #19
0
    def paint(self, painter, option, index):
        painter.save()

        cell_width = self.size.width()

        #if option.state & QStyle.State_Selected:
        #    painter.fillRect(option.rect, option.palette.highlight())
        #painter.drawRect(option.rect)

        # Draw marks before translating painter
        # =====================================

        # Draw avatar
        if not self.avatar:
            avatar_filepath = index.data(self.AvatarRole).toPyObject()
            self.avatar = QPixmap(avatar_filepath)
        x = option.rect.left() + (self.BOX_MARGIN * 2)
        y = option.rect.top() + (self.BOX_MARGIN * 2)
        rect = QRect(x, y, self.AVATAR_SIZE, self.AVATAR_SIZE)
        painter.drawPixmap(rect, self.avatar)

        # Draw verified account icon
        if index.data(self.VerifiedRole).toPyObject():
            rect2 = QRect(rect.right() - 11, rect.bottom() - 10, 16, 16)
            painter.drawPixmap(rect2, self.verified_icon)

        marks_margin = 0
        # Favorite mark
        if index.data(self.FavoritedRole).toPyObject():
            x = cell_width - 16 - self.BOX_MARGIN
            y = option.rect.top() + self.BOX_MARGIN
            rect = QRect(x, y, 16, 16)
            painter.drawPixmap(rect, self.favorite_icon)
            marks_margin = 16

        # Draw reposted icon
        if index.data(self.RepeatedRole).toPyObject():
            x = cell_width - 16 - self.BOX_MARGIN - marks_margin
            y = option.rect.top() + self.BOX_MARGIN
            rect = QRect(x, y, 16, 16)
            painter.drawPixmap(rect, self.repeated_icon)

        # Draw protected account icon
        protected_icon_margin = 0
        if index.data(self.ProtectedRole).toPyObject():
            x = option.rect.left(
            ) + self.BOX_MARGIN + self.AVATAR_SIZE + self.LEFT_MESSAGE_MARGIN
            y = option.rect.top() + self.BOX_MARGIN
            rect = QRect(x, y, 16, 16)
            painter.drawPixmap(rect, self.protected_icon)
            protected_icon_margin = 16

        # ==== End of pixmap drawing ====

        accumulated_height = 0

        # Draw fullname
        fullname = self.__render_fullname(cell_width, index)
        x = option.rect.left() + self.BOX_MARGIN + self.AVATAR_SIZE
        x += self.LEFT_MESSAGE_MARGIN + protected_icon_margin
        y = option.rect.top()
        painter.translate(x, y)
        fullname.drawContents(painter)

        # Draw username
        username = self.__render_username(cell_width, index)
        painter.translate(fullname.idealWidth(), 0)
        username.drawContents(painter)

        # Draw status message
        x = -fullname.idealWidth() - protected_icon_margin
        y = fullname.size().height() + self.TOP_MESSAGE_MARGIN
        painter.translate(x, y)
        message = self.__render_status_message(cell_width, index)
        message.drawContents(painter)
        accumulated_height += y + message.size().height()

        # Draw reposted by
        x = self.BOX_MARGIN + 16 - (self.LEFT_MESSAGE_MARGIN +
                                    self.AVATAR_SIZE)
        y = message.size().height() + self.BOTTOM_MESSAGE_MARGIN
        if accumulated_height < self.AVATAR_SIZE:
            y += (self.AVATAR_SIZE -
                  accumulated_height) + self.COMPLEMENT_HEIGHT
        painter.translate(x, y)

        reposted_by = index.data(self.RepostedRole).toPyObject()
        if reposted_by:
            reposted = QTextDocument()
            reposted.setHtml("<span style='color: #999;'>%s</span>" %
                             reposted_by)
            reposted.setDefaultFont(FOOTER_FONT)
            reposted.setTextWidth(self.__calculate_text_width(cell_width))
            reposted.drawContents(painter)

            # Draw reposted icon
            rect2 = QRect(-16, 3, 16, 16)
            painter.drawPixmap(rect2, self.reposted_icon)

        # Draw datetime
        datetime = index.data(self.DateRole).toPyObject()
        timestamp = QTextDocument()
        timestamp.setHtml("<span style='color: #999;'>%s</span>" % datetime)
        timestamp.setDefaultFont(FOOTER_FONT)
        timestamp.setTextWidth(self.__calculate_text_width(cell_width))
        x = self.size.width() - timestamp.idealWidth() - 20 - self.BOX_MARGIN
        painter.translate(x, 0)
        timestamp.drawContents(painter)

        painter.resetTransform()
        painter.translate(0, option.rect.bottom())
        line = QLine(0, 0, option.rect.width(), 0)
        painter.setPen(QColor(230, 230, 230))
        painter.drawLine(line)

        painter.restore()