Qt5 QStyledItemDelegate on a QListView removes all the default style - c++

I have some QIcon and QString pairs displayed in a QListview. The whole thing has been set up using the Qt Model/View Programming.
I am displaying labeled icons in this QListView. Items are displayed using the IconMode, Snap and TopToBottom flags. Thus, these are organised into a grid.
I would like to layout all the QListView items vertically and centered. In order to do this, I subclassed the QStyledItemDelegate object, and overloaded the paint method. However, I have three main problems:
Icon labels have been moved (in the QStyledItemDelegate subclasses) and a dotted square appears at its original place.
All the default styles are gone (hover, selection). I know how I can add some again, but I would like to use the default one (Windows style).
Everything is rendered into a grid, even if setGridSize is not called. I would like to use only one "column".
Here is a piece of code:
An extract of the constructor of my custom QListView:
setViewMode(QListView::IconMode);
setMovement(QListView::Snap);
setFlow(QListView::TopToBottom);
setSpacing(5);
setIconSize(QSize(iconSize, iconSize));
setGridSize(QSize(iconSize + 10, iconSize + 10));
setDragEnabled(true);
setAcceptDrops(true);
setDropIndicatorShown(true);
The paint method of the QStyledItemDelegate:
void FramesStyledItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
QStyleOptionViewItemV4 opt = option;
//initStyleOption(&opt, index);
opt.icon = QIcon();
opt.text = QString();
QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter);
const QRect r = option.rect;
QIcon icon = qvariant_cast<QIcon>(index.data(Qt::DecorationRole));
QString string = qvariant_cast<QString>(index.data(Qt::DisplayRole));
QPixmap pix = icon.pixmap(r.size());
const QPoint p = QPoint((r.width() - pix.width())/2, (r.height() - pix.height())/2);
painter->drawPixmap(r.topLeft() + p, pix);
painter->drawText(r.center() + p + QPoint(-(string.count() / 2), r.height() / 2), string);
}
If I do not use the initStyleOption shown above, I can remove the
dotted square, but I lose all the default styles.
If I uncomment the initStyleOption, The dotted square appears. I also lose all the default styles.
Here are some screenshots:
The cursor is on item 0 (No hover decoration, no selection decoration).
Item 0 has been selected. A small dotted square appears (initStyleOption has been uncommented).
I have switched to the ListMode. Selection decoration is working but not hover. Again, a small dotted square appears at the original place of the label.
Does someone have an idea? Thanks for your answers.

Related

Is it possible to add a custom widget into a QListView?

I have a large log data (100, 1000, 100000, ... records) and I want to visualize it in the following manner:
Which widget (e.g. QListView, QListWidget) should I use and how, in order to stay away from performance and memory problems?
Is it possible to add a custom widget into a QListView?
Please, read about:
How to display a scrollable list with a substantial amount of widgets as items in a Qt C++ app?
I want to show every log message in the above format
Solution
To achieve the desired result and stay away from performance issues, even with a very long data log, use a QListView with a custom delegate:
Create a subclass of QStyledItemDelegate, say Delegate
Reimplement the QStyledItemDelegate::paint method to do the custom drawing
Reimplement the QStyledItemDelegate::sizeHint to report the correct size of the items in the list
Use the custom delegate in the view by calling QAbstractItemView::setItemDelegate
Example
I have prepared a working example for you in order to demonstrate how the proposed solution could be implemented and used in an application.
The essential part of the example is the way the delegate paints the items in the list view:
void Delegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
QStyleOptionViewItem opt(option);
initStyleOption(&opt, index);
const QPalette &palette(opt.palette);
const QRect &rect(opt.rect);
const QRect &contentRect(rect.adjusted(m_ptr->margins.left(),
m_ptr->margins.top(),
-m_ptr->margins.right(),
-m_ptr->margins.bottom()));
const bool lastIndex = (index.model()->rowCount() - 1) == index.row();
const bool hasIcon = !opt.icon.isNull();
const int bottomEdge = rect.bottom();
QFont f(opt.font);
f.setPointSize(m_ptr->timestampFontPointSize(opt.font));
painter->save();
painter->setClipping(true);
painter->setClipRect(rect);
painter->setFont(opt.font);
// Draw background
painter->fillRect(rect, opt.state & QStyle::State_Selected ?
palette.highlight().color() :
palette.light().color());
// Draw bottom line
painter->setPen(lastIndex ? palette.dark().color()
: palette.mid().color());
painter->drawLine(lastIndex ? rect.left() : m_ptr->margins.left(),
bottomEdge, rect.right(), bottomEdge);
// Draw message icon
if (hasIcon)
painter->drawPixmap(contentRect.left(), contentRect.top(),
opt.icon.pixmap(m_ptr->iconSize));
// Draw timestamp
QRect timeStampRect(m_ptr->timestampBox(opt, index));
timeStampRect.moveTo(m_ptr->margins.left() + m_ptr->iconSize.width()
+ m_ptr->spacingHorizontal, contentRect.top());
painter->setFont(f);
painter->setPen(palette.text().color());
painter->drawText(timeStampRect, Qt::TextSingleLine,
index.data(Qt::UserRole).toString());
// Draw message text
QRect messageRect(m_ptr->messageBox(opt));
messageRect.moveTo(timeStampRect.left(), timeStampRect.bottom()
+ m_ptr->spacingVertical);
painter->setFont(opt.font);
painter->setPen(palette.windowText().color());
painter->drawText(messageRect, Qt::TextSingleLine, opt.text);
painter->restore();
}
The complete code of the example is available on GitHub.
Result
As written, the given example produces the following result:

How to adjust QTextEdit to fit it's contents

I'm developing a Qt Application and I'm trying to find a way to use QTextEdit as a label with long text without the scroll bar. In my ui I have a QScrollArea and inside of it I want to place a couple off QTextEdit widgets and I only want use scrolling inside QScrollArea. Problem is that no matter how I try to resize the QTextEdit it seems it has a maximum height and cuts of text, even if I set the size manually and QTextEdit::size returns the correct value.
I did the same thing with QLabel and it works fine, but in this case I need some methods that are only provided in QTextEdit.
I found this post:
Resizing QT's QTextEdit to Match Text Height: maximumViewportSize()
And the answer given was the following:
I have solved this issue. There were 2 things that I had to do to get
it to work:
Walk up the widget hierarchy and make sure all the size policies made
sense to ensure that if any child widget wanted to be big/small, then
the parent widget would want to be the same thing.
This is the main
source of the fix. It turns out that since the QTextEdit is inside a
QFrame that is the main widget in a QScrollArea, the QScrollArea has a
constraint that it will not resize the internal widget unless the
"widgetResizable" property is true. The documentation for that is
here: http://doc.qt.io/qt-4.8/qscrollarea.html#widgetResizable-prop.
The documentation was not clear to me until I played around with this
setting and got it to work. From the docs, it seems that this property
only deals with times where the main scroll area wants to resize a
widget (i.e. from parent to child). It actually means that if the main
widget in the scroll area wants to ever resize (i.e. child to parent),
then this setting has to be set to true. So, the moral of the story is
that the QTextEdit code was correct in overriding sizeHint, but the
QScrollArea was ignoring the value returned from the main frame's
sizeHint.
The problem is that I have no idea how to access the QTextEdit's QScrollArea to enable widgetResizable. Can anyone explain how I can achieve this or suggest a different way of resizing QTextEdit to perfectly fit it's content?
This will allow the height of the text box to change as required. You can edit the code a little to handle the width as well.
connect( m_textField, SIGNAL( textChanged() ), this, SLOT( onTextChanged() ) );
void MyClass::onTextChanged()
{
QSize size = m_textField->document()->size().toSize();
m_textField->setFixedHeight( size.height() + 3 );
}
Try this one :
QTextEdit textEdit;
textEdit.setHtml("<p>test test test test test test</p><p>|||||||||</p>");
textEdit.show();
textEdit.setFixedWidth(textEdit.document()->idealWidth() +
textEdit.contentsMargins().left() +
textEdit.contentsMargins().right());
Without a concrete example it's difficult to judge, but... it sounds as if you simply want a QTextEdit whose sizeHint depends on the current document size.
class text_edit: public QTextEdit {
using super = QTextEdit;
public:
explicit text_edit (QWidget *parent = nullptr)
: super(parent)
{
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
}
virtual QSize sizeHint () const override
{
QSize s(document()->size().toSize());
/*
* Make sure width and height have `usable' values.
*/
s.rwidth() = std::max(100, s.width());
s.rheight() = std::max(100, s.height());
return(s);
}
protected:
virtual void resizeEvent (QResizeEvent *event) override
{
/*
* If the widget has been resized then the size hint will
* also have changed. Call updateGeometry to make sure
* any layouts are notified of the change.
*/
updateGeometry();
super::resizeEvent(event);
}
};
Then use as...
QScrollArea sa;
sa.setWidgetResizable(true);
text_edit te;
te.setPlainText(...);
sa.setWidget(&te);
sa.show();
It appears to work as expected in the few tests I've done.
In ui i defined QTextEdit *textEdit object. I write it as height scalable-content :
int count = 0;
QString str = "";
// set textEdit text
ui->textEdit->setText("hfdsf\ncsad\nfsc\dajkjkjkjhhkdkca\n925");
str = ui->textEdit->toPlainText();
for(int i = 0;i < str.length();i++)
if(str.at(i).cell() == '\n')
count++;
// resize textEdit (width and height)
ui->textEdit->resize(ui->textEdit->fontMetrics().width("this is the max-length line in qlabel")
, ui->textEdit->fontMetrics().height() * (count + 2));
Notice : this work if you change QTextEdit font face or size! just in height scalable (before every thing set your QTextEdit frameShape to BOX).
if you want do width scalable-content, you should do these steps :
read QTextEdit(textEdit object) text as line to line
calculate every line length
select maximum of line length
use of QTextEdit::fontMetrics().width(QString str) for investigate str size in width
I hope this can help you...

Reimplementing QStyledItemDelegate::paint - How to obtain subelement coordinates?

A QTreeView is rendered with the help of a custom QStyledItemDelegate::paint method. The intention is to add graphical elements to the nodes, e.g. to draw (and fill) a box around the item texts. The tree items may have check boxes, or not.
The Ruby code below achieves the goal, except that I cannot obtain the coordinates of the text element. An empirical offset (x=29; y=4) serves as a workaround. The super method draws the text on top of the box.
How can I obtain the coordinates of the text element?
Is this the right approach at all, or do I have to use drawText and drawControl instead of calling the superclass paint method? In that case, how do you control the layout of the sub elements?
(This question is not Ruby specific. Answers containing C++ are welcome.)
class ItemDelegate < Qt::StyledItemDelegate
def paint(painter, option, index)
text = index.data.toString
bg_color = Qt::Color.new(Qt::yellow)
fg_color = Qt::Color.new(Qt::black)
offset = Qt::Point.new(29,4)
painter.save
painter.translate(option.rect.topLeft + offset)
recti = Qt::Rect.new(0, 0, option.rect.width, option.rect.height)
rectf = Qt::RectF.new(recti)
margin = 4
bounding = painter.boundingRect(rectf, Qt::AlignLeft, text)
tbox = Qt::RectF.new(Qt::PointF.new(-margin,0), bounding.size)
tbox.width += 2*margin
painter.fillRect(tbox, bg_color)
painter.drawRect(tbox)
painter.restore
super
end
end
Edit: Please find a self-contained example here in this Gist.
I had the same problem in C++. Unfortunately, the workaround on option.rect.* properties seems to be the only way to find the text coords.
Here the paint method of my delegate:
void ThumbnailDelegate::paint(QPainter *p_painter, const QStyleOptionViewItem &p_option, const QModelIndex &p_index) const
{
if(p_index.isValid())
{
const QAbstractItemModel* l_model = p_index.model();
QPen l_text_pen(Qt::darkGray);
QBrush l_brush(Qt::black, Qt::SolidPattern);
/** background rect **/
QPen l_pen;
l_pen.setStyle(Qt::SolidLine);
l_pen.setWidth(4);
l_pen.setBrush(Qt::lightGray);
l_pen.setCapStyle(Qt::RoundCap);
l_pen.setJoinStyle(Qt::RoundJoin);
p_painter->setPen(l_pen);
QRect l_border_rect;
l_border_rect.setX(p_option.rect.x() + 5);
l_border_rect.setY(p_option.rect.y() + 5);
l_border_rect.setWidth(p_option.rect.width() - 16);
l_border_rect.setHeight(p_option.rect.height() - 16);
QPainterPath l_rounded_rect;
l_rounded_rect.addRect(QRectF(l_border_rect));
p_painter->setClipPath(l_rounded_rect);
/** background color for hovered items **/
p_painter->fillPath(l_rounded_rect, l_brush);
p_painter->drawPath(l_rounded_rect);
/** image **/
QPixmap l_pixmap = bytearrayToPixmap(l_model->data(p_index, ImageRole).toByteArray()).scaled(150, 150, Qt::KeepAspectRatio);
QRect l_img_rect = l_border_rect;
int l_img_x = (l_img_rect.width()/2 - l_pixmap.width()/2)+l_img_rect.x();
l_img_rect.setX(l_img_x);
l_img_rect.setY(l_img_rect.y() + 12);
l_img_rect.setWidth(l_pixmap.width());
l_img_rect.setHeight(l_pixmap.height());
p_painter->drawPixmap(l_img_rect, l_pixmap);
/** label **/
QRect l_txt_rect = p_option.rect;
l_txt_rect.setX(l_border_rect.x()+5);
l_txt_rect.setY(l_border_rect.y() + l_border_rect.height() -20);
l_txt_rect.setHeight(20);
l_txt_rect.setWidth(l_txt_rect.width()-20);
QFont l_font;
l_font.setBold(true);
l_font.setPixelSize(12);
p_painter->setFont(l_font);
p_painter->setPen(l_text_pen);
QString l_text = l_model->data(p_index, TextRole).toString();
p_painter->drawText(l_txt_rect, Qt::ElideRight|Qt::AlignHCenter, l_text);
}
else
{
qWarning() << "ThumbnailDelegate::paint() Invalid index!";
}
}
I am not skilled on Ruby but, as you can see, I am using drawPath, drawPixmap and drawText.
Here is the result:
I think it is better to avoid invoking paint from the superclass, since it should be done automatically by Qt and you may break something on the UI lifecycle.

Qt: QSS and drawComplexControl()

I have a QDialog containing a QTableView, along with a custom delegate showing a QComboBox for enum types.
When the row is not selected, I still want the QComboBox to be visible (I would like to avoid using QTableView::openPersistentEditor()).
To do so, the custom delegate forwards the paint event to the following method:
QStyleOptionViewItem &option) const
{
painter->save();
QStyleOptionComboBox comboBoxOption;
comboBoxOption.rect = option.rect;
comboBoxOption.state = option.state;
comboBoxOption.state |= QStyle::State_Enabled;
comboBoxOption.editable = false;
comboBoxOption.currentText = enumInfo.valueToKey(curValue);
// The cast is successful, and srcWidget is the QTableView
QWidget *srcWidget = qobject_cast<QWidget *>(option.styleObject);
// style->metaObject()->className() = QStyleSheetStyle
QStyle *style = srcWidget ? srcWidget->style() : QApplication::style();
// However, the QSS is ignored here (while srcWidget->styleSheet() correctly
// returns the style I've set in Qt Designer)
style->drawComplexControl(QStyle::CC_ComboBox, &comboBoxOption, painter, srcWidget);
style->drawControl(QStyle::CE_ComboBoxLabel, &comboBoxOption, painter, srcWidget);
painter->restore();
}
The problem is that I’ve styled the combo box control using QSS, but drawComplexControl() seems to ignore that, despite using the QTableView’s style. Here’s a screenshot:
Is it possible for drawComplexControl() to consider the style sheet?
Thanks
I think that the only way is to grab widget with QPixmap::grabWidget(). And to use this image in delegate.
Seems, that it is not possible to do because of QSS limitation
I believe that you need to use dirty hacks with casting style() to private QStyleSheetStyle

How can I set the line style of a specific cell in a QTableView?

I am working with a QT GUI. I am implementing a simple hex edit control using a QTableView. My initial idea is to use a table with seventeen columns. Each row of the table will have 16 hex bytes and then an ASCII representation of that data in the seventeenth column. Ideally, I would like to edit/set the style of the seventeenth column to have no lines on the top and bottom of each cell to give the text a free flowing appearance. What is the best way to approach this using the QTableView?
I could think about a couple of ways of doing what you need; both would include drawing custom grid as it looks like there is no straight forward way of hooking into the grid painting routine of QTableView class:
1.Switch off the standard grid for your treeview grid by calling setShowGrid(false) and draw grid lines for cells which need them using item delegate. Below is an example:
// custom item delegate to draw grid lines around cells
class CustomDelegate : public QStyledItemDelegate
{
public:
CustomDelegate(QTableView* tableView);
protected:
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
private:
QPen _gridPen;
};
CustomDelegate::CustomDelegate(QTableView* tableView)
{
// create grid pen
int gridHint = tableView->style()->styleHint(QStyle::SH_Table_GridLineColor, new QStyleOptionViewItemV4());
QColor gridColor = static_cast<QRgb>(gridHint);
_gridPen = QPen(gridColor, 0, tableView->gridStyle());
}
void CustomDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
QStyledItemDelegate::paint(painter, option, index);
QPen oldPen = painter->pen();
painter->setPen(_gridPen);
// paint vertical lines
painter->drawLine(option.rect.topRight(), option.rect.bottomRight());
// paint horizontal lines
if (index.column()!=1) //<-- check if column need horizontal grid lines
painter->drawLine(option.rect.bottomLeft(), option.rect.bottomRight());
painter->setPen(oldPen);
}
// set up for your tree view:
ui->tableView->setShowGrid(false);
ui->tableView->setItemDelegate(new CustomDelegate(ui->tableView));
2.Create a QTableView descendant and override the paintEvent method. There you could either draw your own grid or let base class to draw it and then paint horizontal lines on top of the grid with using tableview's background color.
hope this helps, regards