PDA

View Full Version : HTML rich text delegate and text centering/aligning, [code & pictures]



DoTheEvo
22nd June 2015, 00:25
Simple example of the issue with a tableview:

http://i.imgur.com/r0TLqUU.png

heres the code of the example, python 3 + pyqt4



from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys

class My_Model_table(QAbstractTableModel):
def __init__(self, table_data=[], parent=None):
super().__init__()
self.table_data = table_data

def rowCount(self, parent):
return len(self.table_data)

def columnCount(self, parent):
return 2

def data(self, index, role):
if role == Qt.DisplayRole:
value = self.table_data[index.row()]
return value
if role == Qt.TextAlignmentRole:
return Qt.AlignCenter


class My_table(QTableView):
def __init__(self, parent=None):
super().__init__()
#rowHeight = self.fontMetrics().height()
self.verticalHeader().setDefaultSectionSize(50)

def resizeEvent(self, event):
width = event.size().width()
self.setColumnWidth(0, width * 0.80)

class HTMLDelegate(QStyledItemDelegate):
def __init__(self, parent=None):
super().__init__()
self.doc = QTextDocument(self)

def paint(self, painter, option, index):
painter.save()

options = QStyleOptionViewItemV4(option)
self.initStyleOption(options, index)

self.doc.setHtml(options.text)
#options.text = ""

style = QApplication.style() if options.widget is None \
else options.widget.style()
style.drawControl(QStyle.CE_ItemViewItem, options, painter)

ctx = QAbstractTextDocumentLayout.PaintContext()

if option.state & QStyle.State_Selected:
ctx.palette.setColor(QPalette.Text, option.palette.color(
QPalette.Active, QPalette.HighlightedText))

textRect = style.subElementRect(QStyle.SE_ItemViewItemText, options)
#textRect.adjust(30, 5, 0, 0)
painter.translate(textRect.topLeft())
self.doc.documentLayout().draw(painter, ctx)

painter.restore()

def sizeHint(self, option, index):
return QSize(self.doc.idealWidth(), self.doc.size().height())

if __name__ == '__main__':
app = QApplication(sys.argv)
data = ['1', '2', '<b>3</b>', '4', '5']
main_list = My_table()
main_list.setItemDelegate(HTMLDelegate())
main_list.setModel(My_Model_table(data))
main_list.show()
sys.exit(app.exec_())


The delegate is something put together from the stackoverflow questions and googling around, I commented out line #46 that would normally hide the plain text, leaving only the desired rich html tag aware text.
Also if you would remove aligning center at the lines #20 and #21 and custom row height at the line #27 and #28 it would look like this:

http://i.imgur.com/lX5aAl6.png

What I am trying to point out by this, is that theres something affecting the plaintext by default, even without calling align on it, while this mechanism that vertically centers plaintext ignores the rich text.

Now, I know I can move the text around by pixels with relative to its position by adjusting the text rectangle, commented out code at line #59 demonstrating this, but it needs to be done relative to the cell and text size to center correctly, and that I am not sure how I can do, where to get all the informations to do this correctly.
Also I personally, I am not actually looking for horizontal center align, what I am looking for is the vertical align that would be as good as normal plain text allowing me to control row height and text staying centered across various desktop environments. Playing around with align and row height showed the issue for what it is - a positioning issue, rather than some font issue or something else.

DoTheEvo
22nd June 2015, 12:55
as I said I am looking only for vertical align... well I dug in a little and after testing and trying around I found a way, though theres need to be use of a constant.

height of the table cell = option.rect.height()
height of the font = options.fontMetrics.height()

now normally this would be all we need.
We subtract the font height from the rows height and we got the empty space left, which when divided by two would give us the equal margins when text is centered.
so vertically interested only, 0 + height of one that margin gives us the position where the text should start so that empty space behind it, is also the same size
unfortunately theres some shift in text position at the start. top left is not really 0,0 or at least visually its not. So we need to adjust it bit by a a specific amount of pixel = 4


thefuckyourshitup_constant = 4
margin = (option.rect.height() - options.fontMetrics.height()) // 2
margin = margin - thefuckyourshitup_constant
textRect.setTop(textRect.top()+margin)

if the code above is pasted in the example code, between 59 and 60 lines, you can see it in action, and working when you change the height of the rows at the line #28

http://i.imgur.com/4XHZnI7.png

Its probably not the most ideal solution, maybe theres something more elegant as it can be seen its not a 100% center, but I tested it on gnome, kde, openbox and i3, and its close enough I guess

ChrisW67
22nd June 2015, 14:12
Can you call blockBoundingRect() or documentSize() to get the size of the text block you have created and then position it vertically at the correct position?

DoTheEvo
19th January 2016, 18:22
OP here after a long long time

heres the fix that I used after all, sorry its been a long time, dont remember all the details but it works mostly



from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

import sys

class My_Model_table(QAbstractTableModel):
def __init__(self, table_data=[], parent=None):
super().__init__()
self.table_data = table_data

def rowCount(self, parent):
return len(self.table_data)

def columnCount(self, parent):
return 2

def data(self, index, role):
if role == Qt.DisplayRole:
value = self.table_data[index.row()]
return value
if role == Qt.TextAlignmentRole:
return Qt.AlignCenter


class My_table(QTableView):
def __init__(self, parent=None):
super().__init__()
#rowHeight = self.fontMetrics().height()
self.verticalHeader().setDefaultSectionSize(50)

def resizeEvent(self, event):
width = event.size().width()
self.setColumnWidth(0, width * 0.80)

class HTMLDelegate(QStyledItemDelegate):
def __init__(self, parent=None):
super().__init__()
self.doc = QTextDocument(self)

def paint(self, painter, option, index):
painter.save()

options = QStyleOptionViewItem(option)

self.initStyleOption(options, index)
self.doc.setHtml(options.text)
#options.text = ""

style = QApplication.style() if options.widget is None \
else options.widget.style()
style.drawControl(QStyle.CE_ItemViewItem, options, painter)

ctx = QAbstractTextDocumentLayout.PaintContext()

if option.state & QStyle.State_Selected:
ctx.palette.setColor(QPalette.Text, option.palette.color(
QPalette.Active, QPalette.HighlightedText))
else:
ctx.palette.setColor(QPalette.Text, option.palette.color(
QPalette.Active, QPalette.Text))

textRect = style.subElementRect(
QStyle.SE_ItemViewItemText, options)

if index.column() != 0:
textRect.adjust(5, 0, 0, 0)

thefuckyourshitup_constant = 4
margin = (option.rect.height() - options.fontMetrics.height()) // 2
margin = margin - thefuckyourshitup_constant
textRect.setTop(textRect.top() + margin)

painter.translate(textRect.topLeft())
painter.setClipRect(textRect.translated(-textRect.topLeft()))
self.doc.documentLayout().draw(painter, ctx)

painter.restore()

def sizeHint(self, option, index):
return QSize(self.doc.idealWidth(), self.doc.size().height())

if __name__ == '__main__':
app = QApplication(sys.argv)
data = ['1', '2', '<b>3</b>', '4', '5']
main_list = My_table()
main_list.setItemDelegate(HTMLDelegate())
main_list.setModel(My_Model_table(data))
main_list.show()
sys.exit(app.exec_())