Qt delegate setting mouseover state for checkbox - c++

I have a paint method in a delegate used in a QTableview where I add a checkbox indicator to a cell. Its very easy to set the mouseover state of the checkbox when I enter the cell by checking the option.state for the QStyle::State_MouseOver flag but what I ideally need to do is only set the mouseover state for the checkbox indicator when the mouse pointer is over the indicator itself and not just hovering around the cell. Unfortunately the paint method is only triggered when moving from one cell to the other at present so I need some pointers on how to do this.
Code is as follows (where mouse_pointer_ is the last stored mouse coordinates):
void
CheckBoxDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option,
const QModelIndex& index) const
{
// Draw our checkbox indicator
bool value = index.data(Qt::DisplayRole).toBool();
QStyleOptionButton checkbox_indicator;
// Set our button state to enabled
checkbox_indicator.state |= QStyle::State_Enabled;
checkbox_indicator.state |= (value) ? QStyle::State_On : QStyle::State_Off;
// Get our dimensions
checkbox_indicator.rect = QApplication::style()->subElementRect(QStyle::SE_CheckBoxIndicator, &checkbox_indicator, NULL );
// Position our indicator
const int x = option.rect.center().x() - checkbox_indicator.rect.width() / 2;
const int y = option.rect.center().y() - checkbox_indicator.rect.height() / 2;
checkbox_indicator.rect.moveTo(x, y);
if (checkbox_indicator.rect.contains(mouse_position_)) {
checkbox_indicator.state |= QStyle::State_MouseOver;
}
QApplication::style()->drawControl(QStyle::CE_CheckBox, &checkbox_indicator, painter);
}
Any help would be much appreciated.

After some investigation I found that the issue was that my editorEvent() method needed to return true (indicating that the cell contents have changed) to force the repaint of the cell and thus set the selected state. Code is below:
bool CheckBoxDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index)
{
bool repaint_cell = false;
QMouseEvent *e = static_cast<QMouseEvent *> (event);
mouse_position_ = e->pos();
// We need to check if the mouse pointer is hovering within
// the checkbox indicator area of the table cell
QStyleOptionButton checkbox_indicator;
checkbox_indicator.rect = QApplication::style()->subElementRect( QStyle::SE_CheckBoxIndicator, &checkbox_indicator, NULL );
const int x = option.rect.center().x() - checkbox_indicator.rect.width() / 2;
const int y = option.rect.center().y() - checkbox_indicator.rect.height() / 2;
checkbox_indicator.rect.moveTo(x, y );
if (checkbox_indicator.rect.contains(mouse_position_)) {
repaint_cell = true;
// Check if the user has clicked in this area
if (e->button() == Qt::LeftButton) {
switch(event->type()) {
case QEvent::MouseButtonRelease:
// Update model data in here
break;
default:
break;
}
}
}
return repaint_cell;
}

more simple
bool hover = option.state & QStyle::State_MouseOver;
if (hover) {
painter->fillRect(r, QColor(0,0,0,10));
}

Related

Set tab close button position on vertical tabs issue

I want to change the default tab widget close button and set my icon instead. The problem is that it draws the icon on the text. I want to draw the X to the right.
Code:
void AppTabBar::paintEvent(QPaintEvent *event)
{
QStylePainter painter(this);
QStyleOptionTab opt;
for (int i = 0; i < this->count(); i++) {
initStyleOption(&opt, i);
opt.text = painter.fontMetrics().elidedText(opt.text, Qt::ElideRight, 70);
painter.drawControl(QStyle::CE_TabBarTabShape, opt);
painter.save();
QSize s = opt.rect.size();
if (tabPos != AppTabPosition::Top && tabPos != AppTabPosition::Bottom) {
s.transpose();
}
QRect r(QPoint(), s);
r.moveCenter(opt.rect.center());
opt.rect = r;
QPoint c = tabRect(i).center();
painter.translate(c);
if (tabPos == AppTabPosition::Left) {
painter.rotate(90);
} else if (tabPos == AppTabPosition::Right) {
painter.rotate(270); //90 - left pos, 270 - right pos
}
painter.translate(-c);
painter.drawControl(QStyle::CE_TabBarTabLabel, opt);
painter.restore();
}
QWidget::paintEvent(event);
}
void AppTabStyle::drawPrimitive(QStyle::PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const
{
if (element == PE_IndicatorTabClose) {
int size = proxy()->pixelMetric(QStyle::PM_SmallIconSize);
QIcon::Mode mode = option->state & State_Enabled ? (option->state & State_Raised ? QIcon::Active : QIcon::Normal) : QIcon::Disabled;
if (!(option->state & State_Raised) && !(option->state & State_Sunken) && !(option->state & QStyle::State_Selected)) {
mode = QIcon::Disabled;
}
QIcon::State state = option->state & State_Sunken ? QIcon::On : QIcon::Off;
QPixmap pixmap = QIcon(":/Icons/cross_icon.png").pixmap(size, mode, state);
proxy()->drawItemPixmap(painter, option->rect, Qt::AlignRight, pixmap);
} else {
QProxyStyle::drawPrimitive(element, option, painter, widget);
}
}
Screenshot:
This issue only appears on the vertical tabs (AppTabPosition::Left or AppTabPosition::Right). Any ideas how to draw it to the right?
Edited (15.05.2021):
I have set setTabsClosable(false);, then created the TabBarLabel class and set it to setTabButton method. It closes the tabs but the issue with overlapping the tab text still exists:
Screenshot:
It is a lot of code, so I have created and uploaded to Mega the test example to illustrate this issue. Here is my test example: TabExample.zip (6kb)
Any ideas how to change position of tab bar close button? Thank you.
Thank you.
Finally! I have fixed the issue with vertical tab close button position.
Code:
if (tabPos != AppTabPosition::Top && tabPos != AppTabPosition::Bottom) {
s.transpose();
if (this->tabsClosable()) { // check if tab is closable
QRect optRect = opt.rect;
optRect.setX(90); // set X pos of close button
optRect.setY(optRect.y() + 8); // calcs the Y pos of close button
optRect.setSize(QSize(12, 12));
this->tabButton(i, QTabBar::RightSide)->setGeometry(optRect);
}
}
Here I do not change the default opt.rect but instead I copy it to new QRect. Then I change the size and position of optRect and finally set geometry to tabButtton .
Screenshot:
The issue is resolved.

Qt QTableView implementing toggle button and checkbox delegates

I'm currently trying to build a Qt table by subclassing QTableView, QAbstractTableModel and QStyledItemDelegate to maintian some semblance of MVC archiectecture.
I'm using this simple example as a base to build on as I haven't gone near Qt table classes before:
http://qt-project.org/doc/qt-4.8/modelview.html
Anyway the table is mostly text columns but it also needs a single toggle button column and a single checkbox column.
I've noticed that the data method of the model can be used to implement a checkbox but I'm going to need a custom delegate for the button so I was going to use it for the checkbox also.
Anyway I'm unable to find any decent examples on the internet that create tables by using the QTableView object with a mixture of text, checkboxes and buttons. Can any of you good sirs point me in the right direction?
You don't need a custom delegate for having checkbox and toggle button in your tableview. You can simply make your item checkable and set it to your model like:
QStandardItem *item = new QStandardItem( true );
item->setCheckable(true);
item->setCheckState(Qt::Unchecked);
QStandardItemModel * model = new QStandardItemModel( 0, 2 );
model->setRowCount(1);
model->setItem(0, 0, item);
For a toggle button you can do like:
QPushButton * but = new QPushButton(this);
but->setCheckable(true);
but->setText("Toggle");
ui->tableView->setIndexWidget(model->item(0,1)->index(),but);
Based on the information Dmitry provided above I've implemented the following paint method in my delegate for rendering my button and checkbox. Obviously this needs an editorEvent() to do anything I can add this also if it proves useful.
void DDUTableDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option,
const QModelIndex& index) const
{
int col = index.column();
if (col == VIEW_COLUMN) {
// Draw our checkbox indicator
bool value = index.data(Qt::EditRole).toBool();
QStyleOptionButton checkbox_indicator;
// Set our button state to enabled
checkbox_indicator.state |= QStyle::State_Enabled;
checkbox_indicator.state |= (value) ? QStyle::State_On : QStyle::State_Off;
// Get our deimensions
checkbox_indicator.rect = QApplication::style()->subElementRect( QStyle::SE_CheckBoxIndicator, &checkbox_indicator, NULL );
// Position our indicator
const int x = option.rect.center().x() - checkbox_indicator.rect.width() / 2;
const int y = option.rect.center().y() - checkbox_indicator.rect.height() / 2;
checkbox_indicator.rect.moveTo( x, y );
if (option.state & QStyle::State_Selected) {
painter->fillRect(option.rect, option.palette.highlight());
}
QApplication::style()->drawControl( QStyle::CE_CheckBox, &checkbox_indicator, painter );
}
else if (col == TEST_COLUMN) {
bool value = index.data(Qt::EditRole).toBool();
QStyleOptionButton button;
// Set our button to fill the entire cell contents
button.rect = option.rect;
// Set our button state to enabled
button.state |= QStyle::State_Enabled;
if (value) {
button.state |= QStyle::State_Sunken;
button.text = STOP_TEST;
}
else {
button.text = START_TEST;
}
if (option.state & QStyle::State_Selected) {
painter->fillRect(option.rect, option.palette.highlight());
}
QApplication::style()->drawControl(QStyle::CE_PushButton, &button, painter);
}
}

Cannot draw checkbox in QStyledItemDelegate

I have a QStyledItemDelegate derived object for a QTableView derived view. I further delegate the painting and editor creation depending on the model index data type. For bools I wanted to represent the state via a checkbox - but the check box never appears.
Here is the base delegate paint function:
void Sy_QtPropertyDelegate::paint( QPainter* painter,
const QStyleOptionViewItem& option,
const QModelIndex& index ) const
{
painter->save();
if ( index.column() == 0 ) {
...
} else {
QVariant var = index.data();
bool modified = index.data( Sy_QtPropertyModel::ModifiedRole ).toBool();
// If the data type is one of our delegates, then push the work onto
// that.
auto it = delegateMap_.find( var.type() );
if ( it != delegateMap_.end() ) {
( *it )->paint( painter, option, index );
} else if ( var.type() != QVariant::UserType ) {
...
} else {
...
}
}
painter->restore();
}
And the bool sub delegate paint function:
void Sy_boolPD::paint( QPainter* painter,
const QStyleOptionViewItem& option,
const QModelIndex& index ) const
{
painter->save();
bool checked = index.data().toBool();
bool modified = index.data( Sy_QtPropertyModel::ModifiedRole ).toBool();
QStyle* style = Sy_application::style();
if ( modified ) {
QStyleOptionViewItemV4 bgOpt( option );
bgOpt.backgroundBrush = QBrush( Sy_QtPropertyDelegate::ModifiedColour );
style->drawControl( QStyle::CE_ItemViewItem, &bgOpt, painter );
}
QStyleOption butOpt( option );
butOpt.state = QStyle::State_Enabled;
butOpt.state |= checked ? QStyle::State_On : QStyle::State_Off;
style->drawControl( QStyle::CE_CheckBox, &butOpt, painter );
painter->restore();
}
If I force modified to be true, the background is colour of the table is appropriately changed, and couting butOpt's rect and state members show that they are correct - but no check box is shown! Setting QStyle::CE_CheckBox to any other type also causes nothing to render.
I've worked with Qt's MVC framework a lot, but I cannot see where I have gone wrong here.
All I had to do was look in the source code...
case CE_CheckBox:
if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(option)) {
...
}
The QStyleOption I pass to the method is cast to the type specific for drawing the control, CE_CheckBox requires a QStyleOptionButton, and if the cast fails the drawing operation silently skips.

Qt Mouse Cursor reset

i have item delegate that when the mouse event is over icon i change its Cursor to Qt::PointingHandCursor
when its off i set it back to Qt::ArrowCursor . its working fine .
the problem is that besides when it over the icon . it allways stack on Qt::ArrowCursor
even in situation when the icon needs to changes natively like when resizing the windows or when over native push button . it always Qt::ArrowCursor.
how can i force the Cursor act normally when its is no over the icon?
here is what i do :
bool MiniItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
const QStyleOptionViewItem &option,
const QModelIndex &index)
{
// Emit a signal when the icon is clicked
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
if(!index.parent().isValid() &&
event->type() == QEvent::MouseMove)
{
QSize iconsize = m_iconAdd.actualSize(option.decorationSize);
QRect closeButtonRect = m_iconAdd.pixmap(iconsize.width(),iconsize.height()).
rect().translated(AddIconPos(option));
QSize iconRemoveSize = m_iconRemove.actualSize(option.decorationSize);
QRect iconRemoveRect = m_iconRemove.pixmap(iconRemoveSize.width(),iconRemoveSize.height()).
rect().translated(RemoveIconPos(option));
if(closeButtonRect.contains(mouseEvent->pos()))
{
QApplication::setOverrideCursor(QCursor(Qt::PointingHandCursor));
}
else if(iconRemoveRect.contains(mouseEvent->pos()))
{
QApplication::setOverrideCursor(QCursor(Qt::PointingHandCursor));
}
else
{
Qt::CursorShape shape = Qt::ArrowCursor;
QApplication::setOverrideCursor(QCursor(shape));
}
}
if(!index.parent().isValid() &&
event->type() == QEvent::MouseButtonRelease)
{
QSize iconsize = m_iconAdd.actualSize(option.decorationSize);
QRect closeButtonRect = m_iconAdd.pixmap(iconsize.width(),iconsize.height()).
rect().translated(AddIconPos(option));
QSize iconRemoveSize = m_iconRemove.actualSize(option.decorationSize);
QRect iconRemoveRect = m_iconRemove.pixmap(iconRemoveSize.width(),iconRemoveSize.height()).
rect().translated(RemoveIconPos(option));
if(closeButtonRect.contains(mouseEvent->pos()))
{
;
}
else if(iconRemoveRect.contains(mouseEvent->pos()))
{
;
}
}
return false;
}
You need to use restoreOverrideCursor() to undo each call to setOverrideCursor(). From the documentation:
http://doc.qt.io/archives/qt-4.7/qapplication.html#setOverrideCursor
Application cursors are stored on an internal stack.
setOverrideCursor() pushes the cursor onto the stack, and
restoreOverrideCursor() pops the active cursor off the stack.
changeOverrideCursor() changes the curently active application
override cursor. Every setOverrideCursor() must eventually be followed
by a corresponding restoreOverrideCursor(), otherwise the stack will
never be emptied.
You'll have to figure out exactly how to make this work in your code (it's not exactly clear what behavior you want), but you could start by replacing that else clause
{
Qt::CursorShape shape = Qt::ArrowCursor;
QApplication::setOverrideCursor(QCursor(shape));
}
with
{
QApplication::restoreOverrideCursor();
}
NOTE: In my applications, I've created an RAII-style class that is initialized with a Qt::CursorShape, calls setOverrideCursor(...) in its constructor, and calls restoreOverrideCursor() in its destructor. In this way, you can set the cursor shape within a function and be certain that this change will be un-done automatically by the time that function ends. This may not work in all cases, but when it does, it makes things easy.

How does one paint the entire row's background in a QStyledItemDelegate?

I have a QTableView which I am setting a custom QStyledItemDelegate on.
In addition to the custom item painting, I want to style the row's background color for the selection/hovered states. The look I am going for is something like this KGet screenshot:
KGet's Row Background http://www.binaryelysium.com/images/kget_background.jpeg
Here is my code:
void MyDelegate::paint( QPainter* painter, const QStyleOptionViewItem& opt, const QModelIndex& index ) const
{
QBrush backBrush;
QColor foreColor;
bool hover = false;
if ( opt.state & QStyle::State_MouseOver )
{
backBrush = opt.palette.color( QPalette::Highlight ).light( 115 );
foreColor = opt.palette.color( QPalette::HighlightedText );
hover = true;
}
QStyleOptionViewItemV4 option(opt);
initStyleOption(&option, index);
painter->save();
const QStyle *style = option.widget ? option.widget->style() : QApplication::style();
const QWidget* widget = option.widget;
if( hover )
{
option.backgroundBrush = backBrush;
}
painter->save();
style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, widget);
painter->restore();
switch( index.column() )
{
case 0: // we want default behavior
style->drawControl(QStyle::CE_ItemViewItem, &option, painter, widget);
break;
case 1:
// some custom drawText
break;
case 2:
// draw a QStyleOptionProgressBar
break;
}
painter->restore();
}
The result is that each individual cell receives the mousedover background only when the mouse is over it, and not the entire row. It is hard to describe so here is a screenshot:
The result of the above code http://www.binaryelysium.com/images/loader_bg.jpeg
In that picture the mouse was over the left most cell, hence the highlighted background.. but I want the background to be drawn over the entire row.
How can I achieve this?
Edit: With some more thought I've realized that the QStyle::State_MouseOver state is only being passed for actual cell which the mouse is over, and when the paint method is called for the other cells in the row QStyle::State_MouseOver is not set.
So the question becomes is there a QStyle::State_MouseOver_Row state (answer: no), so how do I go about achieving that?
You need to be telling the view to update its cells when the mouse is over a row, so I would suggest tracking that in your model. Then in the paint event, you can ask for that data from the model index using a custom data role.
void TrackDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyleOptionViewItem viewOption(option);
if (viewOption.state & QStyle::State_HasFocus)
viewOption.state = viewOption.state ^ QStyle::State_HasFocus;
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;
}