Qt: QSS and drawComplexControl() - c++

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

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...

Qt5 QStyledItemDelegate on a QListView removes all the default style

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.

How to highlight the entire row on mouse hover in QTableWidget: Qt5

I want to highlight the row on mouse hover in my QTableWidget.
When I hover the mouse, only single cell highlighted.
I have tried this approach :
bool MyTabWidget::eventFilter(QObject *target, QEvent *event)
{
if( target == ui->MyTableWidget )
{
//Just to print the event type
qDebug() <<"EventType : "<<event->type();
}
}
Output : EventType : 13.
`(13 = QEvent::Move)`
I have done lost of googling. but not get any proper solution.
Is there any other approach to fulfill my requirment (to highlight entire row on mouse hover)?
Please help. Thank in advance.
EDIT:
Please refer below screen shot for more clear.
This is my QTableWidget
I want to change the background color of that red boarder(edited) row on mouse hover.
Here is my implementation,it works well.First you should subclass QTableView/QTabWidget ,emit a signal to QStyledItemDelegate in mouseMoveEvent/dragMoveEvent function .This signal will send the hovering index.
In QStyledItemDelegate ,use a member variable hover_row_(changed in a slot bind to above signal) to tell paint function which row is be hovered.
Here is the code examaple:
//1: Tableview :
void TableView::mouseMoveEvent(QMouseEvent *event)
{
QModelIndex index = indexAt(event->pos());
emit hoverIndexChanged(index);
...
}
//2.connect signal and slot
connect(this,SIGNAL(hoverIndexChanged(const QModelIndex&)),delegate_,SLOT(onHoverIndexChanged(const QModelIndex&)));
//3.onHoverIndexChanged
void TableViewDelegate::onHoverIndexChanged(const QModelIndex& index)
{
hoverrow_ = index.row();
}
//4.in Delegate paint():
void TableViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
...
if(index.row() == hoverrow_)
{
//HERE IS HOVER COLOR
painter->fillRect(option.rect, kHoverItemBackgroundcColor);
}
else
{
painter->fillRect(option.rect, kItemBackgroundColor);
}
...
}
I had similar task and answer from baixiangcpp helped me, but it worked only when mouse button was pressed, not on simple hovering. I resolved this issue with help from user mrjj on qt forum, who suggested I should set "mouseTracking" property in TableView (CustomView in my case) to "true".
CustomView::CustomView(QWidget *parent) : QTableView(parent)
{
this->setMouseTracking(true);
connect(this,SIGNAL(hoverIndexChanged(const QModelIndex&)),parent,SLOT(onHoverIndexChanged(const QModelIndex&)));
}
It's not the correct way to solve the problem, but if you would like to continue using QTableWidget you can just show the verticalheader and click above them to highlight its specific row.
I'm adding another answer cause it's too long:
Ok, you're right, this is happening on QTableView. Now the question is, why you need a QTableView? If you just need a resume like the one you posted there, you can use a QTreeView, that instead of the QTableView, supports hovering on the entire row, instead of a single cell.
If you absolutely need a QTableView, you need to disable your current hover effect and override the paint and mouseMoveEvent method. On your mouseMoveEvent method calculate the row under the mouse using QTableView::rowAt(y) (also remember to map your mouse coords to the Widget relative coords), and store an index, if it changes from the previous one, invalidate the entire table. On the paint event, just paint a rect around the row manually after calling the base class paint event.
Haven't played with QT5 still, but with QT4 this is super-easy using a style sheet:
QTableView::item:hover {
background-color: rgba(200,200,220,255);
}

How to put an image and a QProgressBar inside a QTableView?

I'm developing some kind of download manager and display the file name, it's size and the remaining bytes in a QTableView. Now I want to visualize the progress with a QProgressBar and display an image (to indicate whether it's an down- or upload). How can I add or display a QProgressBar and an image inside the QTableView?
If you are using QTableView, I presume you use a model linked to this view.
One solution would be to use delegates (see QItemDelegate) to paint the progress, In QItemDelegate::paint method you have to define, use QStyle of the widget (widget->style()) to paint the progress (use QStyle::drawControl with QStyle::CE_ProgressBarContents as control identifier).
Check the documentation from the example Star Delegate, to see how to define the delegate for the column you need.
Later edit: Example of defining the delegate paint method (code sketch, not really tested, take it as a principle, not fully working).
void MyDelegate::paint ( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const
{
QStyleOptionProgressBar progressStyle;
progressStyle.rect = option.rect; // Maybe some other initialization from option would be needed
// For the sake of the example, I assume that the index indicates the progress, and the next two siblings indicate the min and max of the progress.
QModelIndex minIndex = index.sibling( index.row(), index.column() + 1);
QModelIndex maxIndex = index.sibling( index.row(), index.column() + 2);
progressStyle.minimum = qvariant_cast< int>( minIndex.data( Qt::UserRole));
progressStyle.maximum = qvariant_cast< int>( maxIndex.data( Qt::UserRole));
progressStyle.progress = qvariant_cast< int>( index.data( Qt::UserRole));
progressStyle.textVisible = false;
qApp->style()->drawControl( QStyle::CE_ProgressBarContents, progressStyleOption, painter);
}
TrackDelegate::TrackDelegate(QObject *parent)
: QItemDelegate(parent)
--------------------------------------------------------------------------------
void TrackDelegate::paint( QPainter* painter,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
QStyleOptionViewItem viewOption(option);
QImage image(m_RowBackGroundImagePath);
QPixmap pixmap(m_RowBackGroundImagePath);
qDebug()<<"forward"<<pixmap.width()<<pixmap.height();
pixmap.scaled(option.rect.width(),option.rect.height());
qDebug()<<"back"<<pixmap.width()<<pixmap.height();
qDebug()<<option.rect.width()<<option.rect.height();
QBrush brush(pixmap);
painter->save();
painter->fillRect(option.rect, brush/*QColor(238, 233, 233, 255)*/);
painter->restore();
viewOption.rect = QRect(option.rect.x(), option.rect.y(), option.rect.width(), option.rect.height());
// viewOption.palette.setColor(QPalette::Text, QColor(Qt::red));
// viewOption.palette.setBrush ( QPalette::ButtonText, brush1);
QItemDelegate::paint(painter, viewOption,index);
int progress = index.model()->data(index,Qt::DisplayRole).toInt();
QStyleOptionProgressBar progressBarOption;
progressBarOption.rect = QRect(option.rect.x(), option.rect.y()+(SETHEIGHT - PROGRESSBARHEIGHT)/2, option.rect.width(), /*option.rect.height()*/PROGRESSBARHEIGHT);
//qDebug()<<progressBarOption.rect.x()<<progressBarOption.rect.y()<<progressBarOption.rect.height()<<progressBarOption.rect.width();
//qDebug()<<option.rect.x()<<option.rect.y()<<option.rect.height()<<option.rect.width();
progressBarOption.state |= QStyle::State_Enabled;
progressBarOption.direction = QApplication::layoutDirection();
progressBarOption.fontMetrics = QApplication::fontMetrics();
progressBarOption.minimum = 0;
progressBarOption.maximum = 100;
progressBarOption.textAlignment = Qt::AlignCenter;
progressBarOption.textVisible = true;
progressBarOption.progress = progress < 0 ? 0 : progress;
progressBarOption.text = QString().sprintf("%d%%", progressBarOption.progress);
QApplication::style()->drawControl(QStyle::CE_ProgressBar, &progressBarOption, painter);
break;
}
You probably want to use QTableWidget for this. It has a method which allows you to add widgets like a QProgressBar. It's the "setCellWidget" method.
There is a slot in QProgressBar called setValue(int),
You can update it sending a signal to this progress bar from Your file manager.
This one should be designed in a way it could check or monitor download state and sends those signals periodically.
Good approach to manage up/down/finish images would be to have additional column in table with the image item.
It would be quite easy to update your image if the socket/connection/file corrupt state would change.
Writing any example would be actually writing you a program, so i suggest to post parts of the problems (if any) with code performed by yourself.
QTableView is not for displaying widgets in a layout. Use QGridLayout or some other suitable layout and put the widgets in that layout.