Change color of single QTabWidget tab - c++

I want to change the color of a single tab, please see screenshot below. The FOO_SUPtab should be red (right now only the button is), all others not.
For the text color there is bar->setTabTextColor(index, QColor(Qt::red)), but not the whole tab. Setting the tab stylesheet for the tab widget changes the color of all tabs.
I have found a stylesheet to change the tab color here: https://stackoverflow.com/a/21687821/356726 but not for a single tab, also I need to be able to decide at runtime if the tab is red or not.
Just to make clear, the widget below shall remain black, the tab only red.

One option would be to implement your own tab bar (as explained here).
Anyway, I find more useful and cleaner the use of a proxy style, since it allows you to partially override the painting without need to use inheritance for the tab bar. It will allow you also to easily apply the new style to existing controls.
It can be something like:
class TabBackgroundProxyStyle : public QProxyStyle {
public:
TabBackgroundProxyStyle(const QString& base_style_name, const QMap<QString, QBrush>& backgrounds)
: QProxyStyle(base_style_name),
m_backgrounds(backgrounds) {
}
void drawControl(ControlElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget) const override {
if (element == CE_TabBarTab) {
if (auto tab = qstyleoption_cast<const QStyleOptionTab*>(option)) {
if (m_backgrounds.contains(tab->text)) {
QStyleOptionTab opt(*tab);
opt.palette.setBrush(QPalette::Background, m_backgrounds[tab->text]);
return QProxyStyle::drawControl(element, &opt, painter, widget);
}
}
}
QProxyStyle::drawControl(element, option, painter, widget);
}
private:
const QMap<QString, QBrush> m_backgrounds;
};
To use it, just create the style with the appropriate tabs-color mapping (examples using C++11):
auto theTabWidget = new QTabWidget();
for (int ii = 0; ii < 10; ++ii) theTabWidget->addTab(new QWidget(), QString("Tab %1").arg(ii + 1));
const QMap<QString, QBrush> backgrounds = {
{"Tab 2", QBrush(Qt::red)},
{"Tab 3", QBrush("#c0b050")},
};
theTabWidget->tabBar()->setStyle(new TabBackgroundProxyStyle("", backgrounds));
If your user interface allows the tab's text to change on runtime (e.g., on-the-fly translations, or the text is a filename...) then you must modify the map accordingly.
The use of the tab's label for indexing is because the style option doesn't store any other direct information about the tab (not even the associated widget, because QTabBar is in charge of rendering only the tabs, it is not the container).
Another option would be to check the tab's rectangle, not much time-consuming for tab bars with just a few dozens of tabs, and more versatile if you don't want to deal with labels:
class TabBackgroundProxyStyle : public QProxyStyle {
public:
TabBackgroundProxyStyle(const QString& base_style_name, const QMap<int, QBrush>& backgrounds)
: QProxyStyle(base_style_name),
m_backgrounds(backgrounds) {
}
void drawControl(ControlElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget) const override {
if (element == CE_TabBarTab) {
if (auto tab = qstyleoption_cast<const QStyleOptionTab*>(option)) {
auto tabBar = qobject_cast<const QTabBar*>(widget);
for (auto index : m_backgrounds.keys()) {
if (tab->rect == tabBar->tabRect(index)) {
QStyleOptionTab opt(*tab);
opt.palette.setBrush(QPalette::Background, m_backgrounds[index]);
return QProxyStyle::drawControl(element, &opt, painter, widget);
}
}
}
}
QProxyStyle::drawControl(element, option, painter, widget);
}
private:
const QMap<int, QBrush> m_backgrounds;
};
Use:
auto theTabWidget = new QTabWidget();
for (int ii = 0; ii < 10; ++ii) theTabWidget->addTab(new QWidget(), QString("Tab %1").arg(ii + 1));
const QMap<int, QBrush> backgrounds = {
{1, QBrush(Qt::red)},
{4, QBrush("#c0b050")},
};
theTabWidget->tabBar()->setStyle(new TabBackgroundProxyStyle("", backgrounds));
Full source code can be downloaded from https://github.com/cbuchart/stackoverflow/tree/master/54070408-change-color-of-single-qtabwidget-tab
IMPORTANT: The main drawback of this solution is that it doesn't mix well with existing stylesheet for tabs: you have to disable/comment the stylesheets for QTabBar::tab in order to be able to apply the style.

Related

How to animate the color of a QTableview cell in time once it's value gets updated?

I want to animate the color (in time) of a QTableview cell once it's value is updated via the connected data model to attract the end-users attention that something has changed.
The idea is that the color changes in gradients of f.i. blue, starts off blue just after the value change and fades to white in about 1 ~ 2 seconds.
I guess one has to use the QStyledItemDelegate here since I use the model-view concept (http://doc.qt.io/qt-5/model-view-programming.html).
One needs a trigger for the cell's value-change for the animation to start, this can be achieved via the paint() method since it is called on a value change. One can figure out the row and column, from the index parameter that is passed to paint(), to get a hold of which cell to animate.
So far so good, you can set the color of that cell to let's say blue.
Here comes the problem; the color shall fade towards white in time steps. So I was thinking about a QTimer in the QStyledItemDelegate class and maintain a kinda bookkeeping for the cell's that are being animated (could be a simple list of countdown values that are used to calculate the blue-gradient color. The lower the value the more the gradient goes towards white, once 0 the result is white which is the default color of the cell. On each QTimer timeout() event all values not equal to 0 are lowered by 1. The QStyledItemDelegate is only connected to the row of the QTableview that I want to color-animate i.e. where the value items are displayed.
Problem I face is that:
paint() is a const method so you cannot change any class
parameters.
how to re-paint the cell color on a QTimer event
(re-paint the whole QTableview is not god-style)
I managed to get it to work but I consider it a dirty solution. What I did is maintain the bookkeeping of the color animation for each cell in the data-model. I consider it a dirty solution because the color-animation is only a visual aspect so imho it should not reside in the data-model. In this way it is also not a portable solution i.e. in another project you have to rework a lot to get it to work.
I stripped down my application to the core problem. Full code can be found here (a working application): https://github.com/fruitCoder123/animated_tableview_cell
void TableViewDelegateValueWritable::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
// Paint background
uint8_t red_gradient = calculate_color_gradient(RGB_RED_MAX, RGB_RED_MIN, red_gradient_step_size, m_step_value);
uint8_t green_gradient = calculate_color_gradient(RGB_GREEN_MAX, RGB_GREEN_MIN, green_gradient_step_size, m_step_value);
painter->fillRect(option.rect, QColor(red_gradient, green_gradient, 255));
// Paint text
QStyledItemDelegate::paint(painter, option, index);
}
uint8_t TableViewDelegateValueWritable::calculate_color_gradient(const uint8_t MAX_COLOR, const uint8_t MIN_COLOR, const uint8_t step_size, uint8_t step) const
{
uint16_t color = (step_size * (1 + MAX_COLOR_GRADIENT_STEP - step)) + MIN_COLOR;
// Handle overflow and rounding errors
if(color > MAX_COLOR || color > (MAX_COLOR-(step_size/2)))
color = MAX_COLOR;
return static_cast<uint8_t>(color);
}
void TableViewDelegateValueWritable::gradient_timer_elapsed()
{
if(m_step_value)
{
m_step_value--;
m_timer->start(GRADIENT_TIMEOUT_VALUE);
//this->paint(m_painter, m_option, m_model_index);
}
}
I spent a horrific amount of hours to find a nice solution. I started with Qt a month ago so maybe I lack knowledge. Hopefully someone can give a hint how to solve it in a nice way - encapsulated in the view and not entangled with the data-model.
For the stated problems :
paint() is declared as const, you can use a mutable variable member and modify it whenever you want. For example :
class TableViewDelegateValueWritable : public QStyledItemDelegate
{
Q_OBJECT
mutable QTableView * m_view;
public:
explicit TableViewDelegateValueWritable(QTableView * view, QObject *parent){
m_view = view;
//...
}
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
//...
int nFrame m_view->getFrameOfIndex(index);
drawFrame (painter, nFrame );
m_view->setFrameOfIndex(index, ++nFrame);
//...
}
//class body
}
Repaint the whole QTableView is not a god-style but repaint only the QTableView's viewport is a good option, in the MainWindow constructor :
m_db->insert_record(QString("my_val_1"), "0");
m_db->insert_record(QString("my_val_2"), "0");
m_db->insert_record(QString("my_val_3"), "0");
QTimer * timer = new QTimer( this );
connect( timer, &QTimer::timeout, this, [this](){
ui->tableView->viewport()->repaint();
});
timer->start( TIME_RESOLUTION ); //set to 1000ms
Here is a snippet to animate a cell when it was modified, the color will be changed each 1s, the method used is to subclass QSqlTableModel to track the modified cell (via dataChanged signal) :
enum MyDataRole {
ItemModifiedRole = Qt::UserRole + 1
};
class MySqlTableModel : public QSqlTableModel {
Q_OBJECT
QMap<int, QVariant > mapTimeout;
public:
MySqlTableModel( QObject *parent = Q_NULLPTR, QSqlDatabase db = QSqlDatabase() )
:QSqlTableModel( parent, db ) {
connect( this, &QSqlTableModel::dataChanged,
this, [this]( const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles )
{
for(int i = topLeft.row(); i <= bottomRight.row(); i ++ ){
mapTimeout.insert( i , QDateTime::currentDateTime() );
}
} );
}
//this data function will be called in the delegate paint() function.
QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE
{
if( role != ItemModifiedRole )
return QSqlTableModel::data( idx, role );
QMap<int, QVariant>::const_iterator it = mapTimeout.find( idx.row() );
return it == mapTimeout.end() ? QVariant() : it.value();
}
void clearEffects() {
mapTimeout.clear();
}
};
And the delegate paint() function:
// background color manipulation
void TableViewDelegateValueWritable::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QSqlTableModel * db_model = (QSqlTableModel *)index.model();
QVariant v = db_model->data( index, ItemModifiedRole );
if( !v.isNull() ){
QDateTime dt = v.toDateTime();
int nTimePassed = dt.secsTo( QDateTime::currentDateTime() );
int step_value = nTimePassed + 2;
uint8_t red_gradient = calculate_color_gradient( RGB_RED_MAX, RGB_RED_MIN, red_gradient_step_size, step_value );
uint8_t green_gradient = calculate_color_gradient( RGB_GREEN_MAX, RGB_GREEN_MIN, red_gradient_step_size, step_value );
painter->fillRect( option.rect, QColor( red_gradient, green_gradient, 255) );
}
// Paint text
QStyledItemDelegate::paint(painter, option, index);
}
You should use QSqlRecord way if possible instead of executing a sql statement. In fact, after each sql statement was executed, you call the select() function, this will reset the whole table model. Here is an example of insert/update record :
void dbase::insert_record(const QString &signal_name, const QString &value)
{
QSqlRecord r = db_model->record();
r.setValue( "signal_name", signal_name );
r.setValue( "signal_value", value );
db_model->insertRecord(-1, r );
}
void dbase::update_record(const QString &signal_name, const QString &new_value)
{
for(int row = 0; row < db_model->rowCount(); row ++ ){
QSqlRecord r = db_model->record( row );
if( r.value("signal_name").toString() == signal_name ){
r.setValue("signal_value", new_value );
db_model->setRecord( row, r );
break;
}
}
}

Remove arrow from disabled QComboBox while respecting style

In the picture below, the first QComboBox is disabled:
I want to emphasize the fact that the value cannot be changed by removing the arrow of disabled QComboBoxes.
I've tried modifying the stylesheet already being used to:
QComboBox::down-arrow:disabled {
border: 0;
background: transparent;
image: none;
height: 0;
width: 0;
}
But it doesn't solve the issue and conflicts with my current style (set using qApp->setStyle("fusion")):
How can I get it?
The trick can be done by using a QProxyStyle and returning a null QRect for the arrow subcontrol (QProxyStyle::subControlRect). A QProxyStyle allows you to vary specific behaviours of a style without the need of implementing a whole new one (it wraps the original style).
class MyProxyStyle : public QProxyStyle {
public:
MyProxyStyle(const QString& base_style_name) : QProxyStyle(base_style_name) {}
QRect MyProxyStyle::subControlRect(QStyle::ComplexControl cc,
const QStyleOptionComplex* option,
QStyle::SubControl sc,
const QWidget* widget) const override
{
if (cc == CC_ComboBox && sc == SC_ComboBoxArrow && !widget->isEnabled()) return QRect();
return QProxyStyle::subControlRect(cc, option, sc, widget);
}
};
// ...
qApp->setStyle(new MyProxyStyle("fusion"));
Result:

How to make a fast QTableView with HTML-formatted and clickable cells?

I'm making a dictionary program that displays word definitions in a 3-column QTableView subclass, as user types them, taking data from a QAbstractTableModel subclass. Something like that:
I want to add various formatting to the text, I'm using QAbstractItemView::setIndexWidget to add a QLabel to each cell as data comes in:
WordView.h
#include <QTableView>
class QLabel;
class WordView : public QTableView {
Q_OBJECT
public:
explicit WordView(QWidget *parent = 0);
void rowsInserted(const QModelIndex &parent, int start, int end);
private:
void insertLabels(int row);
void removeLabels(int row);
};
WordView.cpp
#include <QLabel>
#include "WordView.h"
WordView::WordView(QWidget *parent) :
QTableView(parent)
{}
void WordView::rowsInserted(const QModelIndex &parent, int start, int end) {
QTableView::rowsInserted(parent, start, end);
for (int row = start; row <= end; ++row) {
insertLabels(row);
}
}
void WordView::insertLabels(int row) {
for (int i = 0; i < 3; ++i) {
auto label = new QLabel(this);
label->setTextFormat(Qt::RichText);
label->setAutoFillBackground(true);
QModelIndex ix = model()->index(row, i);
label->setText(model()->data(ix, Qt::DisplayRole).toString()); // this has HTML
label->setWordWrap(true);
setIndexWidget(ix, label); // this calls QAbstractItemView::dataChanged
}
}
However, this is very slow - it takes around 1 second to refresh 100 rows (remove all, then add 100 new ones) like that. With original QTableView it worked fast, but I did not have formatting and ability to add links (cross-references in dictionary). How to make this much faster? Or what other widget can I use to display that data?
My requirements are:
Adding/removing around 1000 rows in ~0.2s, where around 30 will be visible at once
Clickable, multiple internal links (<a>?) in each cell (e.g. QLabel has that, QItemDelegate might have been fast, but I don't know how to get info which link I clicked there)
Formatting that allows different font sizes and colors, word wrap, different cell heights
I'm not really dead-set on QTableView, anything that looks like a scrollable table and looks consistent with Qt graphics is okay
Notes:
I tried making a single label with HTML <table> instead, but it wasn't much faster. Seems like QLabel isn't the way to go.
Data in the sample courtesy of the JMdict project.
I solved the problem by putting together few answers and looking at Qt's internals.
A solution which works very fast for static html content with links in QTableView is as folows:
Subclass QTableView and handle mouse events there;
Subclass QStyledItemDelegate and paint the html there (contrary to RazrFalcon's answer, it is very fast, as only a small amount of cells is visible at a time and only those have paint() method called);
In subclassed QStyledItemDelegate create a function that figures out which link was clicked by QAbstractTextDocumentLayout::anchorAt(). You cannot create QAbstractTextDocumentLayout yourself, but you can get it from QTextDocument::documentLayout() and, according to Qt source code, it's guaranteed to be non-null.
In subclassed QTableView modify QCursor pointer shape accordingly to whether it's hovering over a link
Below is a complete, working implementation of QTableView and QStyledItemDelegate subclasses that paint the HTML and send signals on link hover/activation. The delegate and model still have to be set outside, as follows:
wordTable->setModel(&myModel);
auto wordItemDelegate = new WordItemDelegate(this);
wordTable->setItemDelegate(wordItemDelegate); // or just choose specific columns/rows
WordView.h
class WordView : public QTableView {
Q_OBJECT
public:
explicit WordView(QWidget *parent = 0);
signals:
void linkActivated(QString link);
void linkHovered(QString link);
void linkUnhovered();
protected:
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
private:
QString anchorAt(const QPoint &pos) const;
private:
QString _mousePressAnchor;
QString _lastHoveredAnchor;
};
WordView.cpp
#include <QApplication>
#include <QCursor>
#include <QMouseEvent>
#include "WordItemDelegate.h"
#include "WordView.h"
WordView::WordView(QWidget *parent) :
QTableView(parent)
{
// needed for the hover functionality
setMouseTracking(true);
}
void WordView::mousePressEvent(QMouseEvent *event) {
QTableView::mousePressEvent(event);
auto anchor = anchorAt(event->pos());
_mousePressAnchor = anchor;
}
void WordView::mouseMoveEvent(QMouseEvent *event) {
auto anchor = anchorAt(event->pos());
if (_mousePressAnchor != anchor) {
_mousePressAnchor.clear();
}
if (_lastHoveredAnchor != anchor) {
_lastHoveredAnchor = anchor;
if (!_lastHoveredAnchor.isEmpty()) {
QApplication::setOverrideCursor(QCursor(Qt::PointingHandCursor));
emit linkHovered(_lastHoveredAnchor);
} else {
QApplication::restoreOverrideCursor();
emit linkUnhovered();
}
}
}
void WordView::mouseReleaseEvent(QMouseEvent *event) {
if (!_mousePressAnchor.isEmpty()) {
auto anchor = anchorAt(event->pos());
if (anchor == _mousePressAnchor) {
emit linkActivated(_mousePressAnchor);
}
_mousePressAnchor.clear();
}
QTableView::mouseReleaseEvent(event);
}
QString WordView::anchorAt(const QPoint &pos) const {
auto index = indexAt(pos);
if (index.isValid()) {
auto delegate = itemDelegate(index);
auto wordDelegate = qobject_cast<WordItemDelegate *>(delegate);
if (wordDelegate != 0) {
auto itemRect = visualRect(index);
auto relativeClickPosition = pos - itemRect.topLeft();
auto html = model()->data(index, Qt::DisplayRole).toString();
return wordDelegate->anchorAt(html, relativeClickPosition);
}
}
return QString();
}
WordItemDelegate.h
#include <QStyledItemDelegate>
class WordItemDelegate : public QStyledItemDelegate {
Q_OBJECT
public:
explicit WordItemDelegate(QObject *parent = 0);
QString anchorAt(QString html, const QPoint &point) const;
protected:
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const;
};
WordItemDelegate.cpp
#include <QPainter>
#include <QTextDocument>
#include <QAbstractTextDocumentLayout>
#include "WordItemDelegate.h"
WordItemDelegate::WordItemDelegate(QObject *parent) :
QStyledItemDelegate(parent)
{}
QString WordItemDelegate::anchorAt(QString html, const QPoint &point) const {
QTextDocument doc;
doc.setHtml(html);
auto textLayout = doc.documentLayout();
Q_ASSERT(textLayout != 0);
return textLayout->anchorAt(point);
}
void WordItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
auto options = option;
initStyleOption(&options, index);
painter->save();
QTextDocument doc;
doc.setHtml(options.text);
options.text = "";
options.widget->style()->drawControl(QStyle::CE_ItemViewItem, &option, painter);
painter->translate(options.rect.left(), options.rect.top());
QRect clip(0, 0, options.rect.width(), options.rect.height());
doc.drawContents(painter, clip);
painter->restore();
}
QSize WordItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const {
QStyleOptionViewItemV4 options = option;
initStyleOption(&options, index);
QTextDocument doc;
doc.setHtml(options.text);
doc.setTextWidth(options.rect.width());
return QSize(doc.idealWidth(), doc.size().height());
}
Note that this solution is fast only because a small subset of rows is rendered at once, and therefore not many QTextDocuments are rendered at once. Automatic adjusting all row heights or column widths at once will still be slow. If you need that functionality, you can make the delegate inform the view that it painted something and then making the view adjust the height/width if it hasn't before. Combine that with QAbstractItemView::rowsAboutToBeRemoved to remove cached information and you have a working solution. If you're picky about scrollbar size and position, you can compute average height based on a few sample elements in QAbstractItemView::rowsInserted and resize the rest accordingly without sizeHint.
References:
RazrFalcon's answer for pointing me to the right direction
Answer with code sample to render HTML in QTableView: How to make item view render rich (html) text in Qt
Answer with code sample on detecting links in QTreeView: Hyperlinks in QTreeView without QLabel
QLabel's and internal Qt's QWidgetTextControl's source code on how to handle mouse click/move/release for links
Many thanks for these code examples, it helped me implement similar functionalaity in my application. I'm working with Python 3 and QT5 and I would like to share my Python code, is case it may be helpful implementing this in Python.
Note that if you are using QT Designer for the UI design, you can use "promote" to change a regular "QTableView" widget to use your custom widget automatically when converting the XML to Python code with "pyuic5".
Code as follows:
from PyQt5 import QtCore, QtWidgets, QtGui
class CustomTableView(QtWidgets.QTableView):
link_activated = QtCore.pyqtSignal(str)
def __init__(self, parent=None):
self.parent = parent
super().__init__(parent)
self.setMouseTracking(True)
self._mousePressAnchor = ''
self._lastHoveredAnchor = ''
def mousePressEvent(self, event):
anchor = self.anchorAt(event.pos())
self._mousePressAnchor = anchor
def mouseMoveEvent(self, event):
anchor = self.anchorAt(event.pos())
if self._mousePressAnchor != anchor:
self._mousePressAnchor = ''
if self._lastHoveredAnchor != anchor:
self._lastHoveredAnchor = anchor
if self._lastHoveredAnchor:
QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
else:
QtWidgets.QApplication.restoreOverrideCursor()
def mouseReleaseEvent(self, event):
if self._mousePressAnchor:
anchor = self.anchorAt(event.pos())
if anchor == self._mousePressAnchor:
self.link_activated.emit(anchor)
self._mousePressAnchor = ''
def anchorAt(self, pos):
index = self.indexAt(pos)
if index.isValid():
delegate = self.itemDelegate(index)
if delegate:
itemRect = self.visualRect(index)
relativeClickPosition = pos - itemRect.topLeft()
html = self.model().data(index, QtCore.Qt.DisplayRole)
return delegate.anchorAt(html, relativeClickPosition)
return ''
class CustomDelegate(QtWidgets.QStyledItemDelegate):
def anchorAt(self, html, point):
doc = QtGui.QTextDocument()
doc.setHtml(html)
textLayout = doc.documentLayout()
return textLayout.anchorAt(point)
def paint(self, painter, option, index):
options = QtWidgets.QStyleOptionViewItem(option)
self.initStyleOption(options, index)
if options.widget:
style = options.widget.style()
else:
style = QtWidgets.QApplication.style()
doc = QtGui.QTextDocument()
doc.setHtml(options.text)
options.text = ''
style.drawControl(QtWidgets.QStyle.CE_ItemViewItem, options, painter)
ctx = QtGui.QAbstractTextDocumentLayout.PaintContext()
textRect = style.subElementRect(QtWidgets.QStyle.SE_ItemViewItemText, options)
painter.save()
painter.translate(textRect.topLeft())
painter.setClipRect(textRect.translated(-textRect.topLeft()))
painter.translate(0, 0.5*(options.rect.height() - doc.size().height()))
doc.documentLayout().draw(painter, ctx)
painter.restore()
def sizeHint(self, option, index):
options = QtWidgets.QStyleOptionViewItem(option)
self.initStyleOption(options, index)
doc = QtGui.QTextDocument()
doc.setHtml(options.text)
doc.setTextWidth(options.rect.width())
return QtCore.QSize(doc.idealWidth(), doc.size().height())
In your case QLabel (re)painting is slow, not QTableView.
On other hand, QTableView does not support formated text at all.
Probably, your only way, is to create your own delegate, QStyledItemDelegate, and make your own painting and click processing in it.
PS: yes, you can use QTextDocument for rendering html inside delegate, but it will be slow too.
I use a slighted improved solution based on Xilexio code. There is 3 fundamental differences:
Vertical alignment so if you put the text in a cell higher than the text it will be center aligned and not top aligned.
The text will be right shifted if the cell contains an icon so the icon will not be displayed above the text.
The widget style to highlighted cells will be followed, so you select this cell, the colors will behave similar to other cells without the delegate.
Here is my code of the paint() function (the rest of the code remains the same).
QStyleOptionViewItemV4 options = option;
initStyleOption(&options, index);
painter->save();
QTextDocument doc;
doc.setHtml(options.text);
options.text = "";
options.widget->style()->drawControl(QStyle::CE_ItemViewItem, &options, painter);
QSize iconSize = options.icon.actualSize(options.rect.size);
// right shit the icon
painter->translate(options.rect.left() + iconSize.width(), options.rect.top());
QRect clip(0, 0, options.rect.width() + iconSize.width(), options.rect.height());
painter->setClipRect(clip);
QAbstractTextDocumentLayout::PaintContext ctx;
// Adjust color palette if the cell is selected
if (option.state & QStyle::State_Selected)
ctx.palette.setColor(QPalette::Text, option.palette.color(QPalette::Active, QPalette::HighlightedText));
ctx.clip = clip;
// Vertical Center alignment instead of the default top alignment
painter->translate(0, 0.5*(options.rect.height() - doc.size().height()));
doc.documentLayout()->draw(painter, ctx);
painter->restore();

Setting text below the icon rather than on right in QTableView?

I'm adding images as items to a QTableView, I'm also adding a specific text to each images, problem is the text is shown beside the image or the icon, but I want QTableView to show it below the image or the icon. My code snippet is as below:
QStandardItemModel * model = new QStandardItemModel(NumOfRow, 3, this);
Then comes this part which is in the loop
//
QStandardItem * itm = new QStandardItem;
itm->setIcon(image);
itm->setText(text);
model->setItem(row, column, itm);
//
Then this part outside the loop
ui->listOfImages->setModel(model);
ui->listOfImages->setStyleSheet(QString("icon-size: 150px 150px"));
ui->listOfImages->verticalHeader()->setVisible(false);
ui->listOfImages->horizontalHeader()->setVisible(false);
for(int i=0; i<=rowPointer; i++)
{
ui->listOfImages->setRowHeight(i,150);
}
for(int j=0; j<3; j++)
{
ui->listOfImages->setColumnWidth(j,150);
}
Could you say me if there is any way to put the name below the icon rather than in the right side of the icon?
Thanks
Well, I would try to handle the text alignment with the custom QStandardItemModel sub class. Here is the sample model, that implements it:
class ItemModel : public QStandardItemModel
{
public:
ItemModel(int rows, int columns, QObject *parent = 0)
:
QStandardItemModel(rows, columns, parent)
{}
QVariant data(const QModelIndex &index, int role) const
{
if (role == Qt::TextAlignmentRole) {
return Qt::AlignBottom; // <- Make alignment look different, i.e.
// <- text at the bottom.
} else {
return QStandardItemModel::data(index, role);
}
}
};
So, instead of using the Qt class, you will need to create an instance of this custom class:
QStandardItemModel * model = new ItemModel(NumOfRow, 3, this);
The rest will remain the same.

Autofill to correct size in Qt Tables

I am trying to learn Qt by doing some project, and would like quick pointer on one part of my requirement.
I have database with multi-line passages, that I want to show in Qt using some view.
What I additionally want is that user does not have to re-size the window in order to read, so, if big passage comes in, then size shrinks, and will small passage, the font increase such that it takes the total space to display.
Kindly suggest :
what logic or functionality will suit to shrink and expand size, or is there a widget/view that already do so(by modifying the property) or suggestion on how to achieve it.
Same question again, to show shrink/expanded things, but using only using tree view. Then can I do this in tree view? And how?
Here is a way you could do it with a custom QItemDelegate.
Note that this solution isn't complete, you still need to do some work here.
First, some code to setup a QStandardItemModel and QTreeView.
The View uses a custom Delegate, which is described below.
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QStandardItemModel model(4, 4);
for (int row = 0; row < 4; ++row) {
for (int column = 0; column < 4; ++column) {
QStandardItem *item = new QStandardItem(QString("row %0, column %1").arg(row).arg(column));
model.setItem(row, column, item);
}
}
Delegate delegate;
QTreeView view;
view.setItemDelegate(&delegate);
view.setModel(&model);
view.setWindowTitle("QTreeView with custom delegate");
view.show();
return a.exec();
}
Here comes the code for the Delegate.
It looks how much space is available for the text, and then tries to find a font size that fits.
I'm currently only checking the width and ignoring height.
class Delegate : public QItemDelegate
{
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
};
void Delegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QRect availableSpace = option.rect;
// padding taking place, don't know how to find out how many pixels are padded.
// for me, subtracting 6 looked ok, but that's not a good solution
availableSpace.setWidth(availableSpace.width() - 6);
QString text = index.data().toString();
QStyleOptionViewItem newOption(option);
// initial font size guess
float size = 20;
int width;
// try to make font smaller until the text fits
do
{
newOption.font.setPointSizeF(size);
size -= .1;
width = QFontMetrics(newOption.font).width(text);
}
while (width > availableSpace.width());
newOption.textElideMode = Qt::ElideNone;
// call the parent paint method with the new font size
QItemDelegate::paint(painter, newOption, index);
}
This is what the result looks like: