How to know the QTreeView item decoration is clicked - c++

I'm trying to know when the user selects the decoration of an item, because I was trying to implement a single click expand/collapse QTreeview and the decoration now does nothing. It doesn't expand or collapse the item where as if I click on the item it works properly.
void MyTreeView::mousePressEvent(QMouseEvent *event)
{
QTreeView::mousePressEvent(event);
if (event->button() == Qt::LeftButton)
{
QModelIndex index = indexAt(event->pos());
isExpanded(index) ? collapse(index) : expand(index);
}
}
The problem is that when the decoration is selected, it enters the if condition. If it was not, everything works fine.
I don't know if I have to block the decoration action or have a condition in the if statement.
How do I know the decoration is selected and not the item itself or how do I block decoration action ?

Try this:
void MyTreeView::mousePressEvent( QMouseEvent* aEvent )
{
QModelIndex index = indexAt( aEvent->pos() );
if ( index.isValid() )
{
const bool wasExpanded = isExpanded( index );
QTreeView::mousePressEvent( aEvent );
if ( aEvent->button() == Qt::LeftButton )
{
const bool expanded = isExpanded( index );
// QTreeView did not change the item's state ... but you want.
if ( wasExpanded == expanded )
{
expanded ? collapse( index ) : expand( index );
}
}
}
else
{
QTreeView::mousePressEvent( aEvent );
}
}

Related

Qt5: Drag and Drop when using QSortFilterProxyModel

I have a model that is sub-classed from QAbstractListModel that has different Listviews that are filtered using a sub-classed QSortFilterProxyModel for each view. The data in the view can be sorted when the user clicks a sort button.
I implemented the drag and drop inside the QSortFilterProxyModel, to change the state of the data when it is dropped into a new Listview. This works fine, however, manually sorting the items within a listview causes all other listview displaying the same data to be sorted as well, which is not what I want.
Example, View 1 shows all participants, view 2 shows participants that are active. When dragged from view 1 to view 2, the participant becomes active. If I manually sort the participants, the index of participants that are active also get sorted. This does however not happen if I automatically sort them using proxyModel->sort() method.
How can I manually rearrange the data in the proxy model without changing the index in the source model?
Example code:
MySortFilterProxyModel::MySortFilterProxyModel(bool active, QObject *parent ) :
QSortFilterProxyModel( parent ),
m_filter( "" ),
m_active(active)
{
setDynamicSortFilter( false );
}
void MySortFilterProxyModel::setFilter( QString filter )
{
m_filter = filter;
invalidateFilter();
}
Qt::ItemFlags MySortFilterProxyModel::flags( const QModelIndex &index ) const
{
if( index.isValid() )
{
return ( QSortFilterProxyModel::flags( index ) | Qt::ItemIsDragEnabled | Qt::ItemIsEditable );
}
return Qt::ItemIsDropEnabled | QSortFilterProxyModel::flags( index );
}
bool MySortFilterProxyModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent )
{
if( !data->hasFormat( dataModel::dataMimeType() ) )
{
return false;
}
if( action == Qt::IgnoreAction )
{
return true;
}
if( column > 0 )
{
return false;
}
QByteArray encodeData = data->data(dataModel::dataMimeType());
QDataStream stream( &encodeData, QIODevice::ReadOnly );
while( !stream.atEnd() )
{
DataRecord *dr = new DataRecord();
stream >> dr;
dr->setActive( m_active );
// AddData method in the dataModel removes duplicate rows and inserts the data into the correct row.
qobject_cast< DataModel * >( sourceModel() )->addData( fdr, parent.row() );
}
return true;
}
Edit addData() There is propably a better way to do this:
void DataModel::addData( DataRecord *dr, int row )
{
int i =0;
for( auto const& itr : m_dataRecords )
{
if( itr->getUniqueID() == dr->getUniqueID() )
{
break;
}
++i;
}
removeRows( i, 1, QModelIndex() );
beginInsertRows( QModelIndex(), row, row );
m_dataRecords.insert( row, dr );
endInsertRows();
}
In the DataModel I also implement the following methods:
QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const override;
bool setData( const QModelIndex & index, const QVariant & value, int role=Qt::EditRole );
bool removeRows( int row, int count, const QModelIndex &parent ) override;
QMimeData *mimeData( const QModelIndexList &indexes ) const override;
QStringList mimeTypes() const override;
int rowCount( const QModelIndex &parent ) const override;
Qt::DropActions supportedDropActions() const override;
Don't modify DataModel
The point of QSortFilterProxyModel is that you can provide a new ordering completely independently of any other view on the underlying data. Having that view move the underlying rows around breaks that.
You should instead write a proxy that provides the manual override of ordering, recording where each row should be positioned. This can use the QSortFilterProxyModel as its source, which in turn sources from DataModel

How to avoid edit mode when I advance with TAB key after edit a cell in a QTableView?

This is my question. When I edit a cell in a QTableView, I would like that if I press TAB key, the current cell is updated, but the next cell isn't in edit mode.
I have try to create a eventFilter in a table, but it doesn't work. Neither if I create the eventFilter in the editor widget of the delegate.
This is my try of a eventFilter in the QTableView. My idea is that if the current cell is in the last column and row and the current row is not empty, I insert another row (this works fine) and if I am editing any cell, when I press TAB I can place on the next cell but without Edit Mode
This is a snippet of the code:
bool MiTabla::eventFilter(QObject *watched, QEvent *e)
{
if (e->type() == QEvent::KeyPress)
{
QModelIndex indice = this->currentIndex();
QKeyEvent *ke =static_cast<QKeyEvent*>(e);
switch (ke->key())
{
case (Qt::Key_Delete):
{
if (this->selectionModel()->isRowSelected(indice.row(),QModelIndex()))
{
//borrarLineas();
}
else
{
this->model()->setData(this->currentIndex(),"",Qt::EditRole);
}
break;
}
case (Qt::Key_Tab):
{
if (indice.row() == this->model()->rowCount(QModelIndex())-1
&& indice.column() == this->model()->columnCount(QModelIndex())-1
&& !NombreVacio())
{
this->model()->insertRow(this->model()->rowCount(QModelIndex()));
QModelIndex ind = this->model()->index(indice.row()+1,0);
this->setCurrentIndex(ind);
}
else //this doesn't work
{
QModelIndex ind = this->model()->index(indice.row(),indice.column()+1);
this->setCurrentIndex(ind);
}
break;
Thank you. I have solved it as you said.
I have reimplement the eventFilter() function in the delegate class (subclassed from QStyledItemDelegate) as below:
bool DelegadoNombre::eventFilter(QObject *obj, QEvent* event)
{
if (event->type()==QEvent::KeyPress)
{
QKeyEvent* key = static_cast<QKeyEvent*>(event);
if (key->key()==Qt::Key_Tab || key->key()==Qt::Key_Enter || key->key()==Qt::Key_Return)
{
QLineEdit *editor=qobject_cast<QLineEdit*>(obj);
emit commitData(editor);
emit closeEditor(editor, QStyledItemDelegate::NoHint);
}
else
{
return QObject::eventFilter(obj, event);
}
return false;
}
else
{
return QObject::eventFilter(obj, event);
}
return false;
}
This function says to editor of the delegate that if Tab Key is pressed it must commit the data and close the editor but no open the next editor in Edit Mode QStyledItemDelegate::NoHint.
Also, return false for allow to the table use this event

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.

QGraphicsItem and tab order

For some GUI application I use QMainWindow with different controls on it: QGraphicsScene + QGraphicsView , QPushButtons, QWidget.
Inside QGraphicsScene located a lot of different items type:
QGraphicsPolygonItem
QGraphicsTextItem
QGraphicsRectItem
But for me is most important is Polygon item, this item has those flags:
setFlag(QGraphicsItem::ItemIsFocusable, true);
setFlag(QGraphicsItem::ItemIsMovable, true);
setFlag(QGraphicsItem::ItemIsSelectable, true);
I can select every item by mouse, but I need to change focus for those items by Tab.
How is it possible ?
SetTabOrder worked only with QGraphicsObjects.
I tried to solve this problem with QGraphicsView::focusNextPrevChild redefining
bool MyGraphicsView::focusNextPrevChild( bool next )
{
QGraphicsPolygonItem *target;
QGraphicsPolygonItem *current;
if( scene()->focusItem() )
{
target = qgraphicsitem_cast<QGraphicsPolygonItem*>( scene()->focusItem() );
bool is_focus_next=false;
foreach( QGraphicsItem *item, scene()->items() )
{
current = qgraphicsitem_cast<QGraphicsPolygonItem*>( item );
// set focus for next before selected
if( current && is_focus_next )
{
item->setFocus( Qt::MouseFocusReason );
// item->setSelected( true );
is_focus_next = false;
break;
}
// searching for selected item
if( current && current == target )
{
is_focus_next = true;
}
}
}
}
But only first Tab worked, When I pressed Tab again focus has moved to other QWidget outside the QGraphicsView control.
Please, could you help me with Tab order focus for QGraphicsItem.
Thank you
EDIT: The final version, thanks to Steffen.
bool MyGraphicsView::focusNextPrevChild( bool next )
{
QGraphicsPolygonItem *target;
QGraphicsPolygonItem *current;
if( scene()->focusItem() )
{
target = qgraphicsitem_cast<QGraphicsPolygonItem*>( scene()->focusItem() );
bool is_focus_next=false;
foreach( QGraphicsItem *item, scene()->items() )
{
current = qgraphicsitem_cast<QGraphicsPolygonItem*>( item );
// set focus for next before selected
if( current && is_focus_next )
{
item->setFocus( Qt::MouseFocusReason );
return true;
}
// searching for selected item
if( current && current == target )
{
is_focus_next = true;
}
}
}
return QGraphicsView::focusNextPrevChild(next);
}
You should return true to indicate that you actually found a widget. As you have no return statement at all at the moment, the behaviour is undefined. You can also add some logic to return false on the last element to allow the user to leave your QGraphicsView again.
See also the documentation for QWidget::focusNextPrevChild().

Custom text color for certain indexes in QTreeView

I would like to draw texts in one of the columns in a QTreeView widget using a custom color (depending on the data related to each row). I tried to overload the drawRow() protected method and change the style option parameter like this (a stripped-down example):
virtual void drawRow(QPainter* p_painter, const QStyleOptionViewItem& option,
const QModelIndex& index) const
{
QStyleOptionViewItem optionCustom = option;
if (index.column() == 2)
{
optionCustom.palette.setColor(QPalette::Text, Qt::red);
}
QTreeView::drawRow(p_painter, optionCustom, index);
}
But obviously I am missing something because this does not seem to work (I tried to change also the QPalette::WindowText color role).
In your model, extend the data() function to return a given color as the Qt::ForegroundRole role.
For example:
virtual QVariant MyModel::data( const QModelIndex &index, int role ) const
{
if ( index.isValid() && role == Qt::ForegroundRole )
{
if ( index.column() == 2 )
{
return QVariant( QColor( Qt::red ) );
}
return QVariant( QColor( Qt::black ) );
}
return QAbstractItemModel::data( index, role );
}