How to highlight some items in a QTreeView (without inheriting) - qt

I am looking for a way to highlight rows of a QTreeView without reimplementing a QTreeView subclass.
I have seen similar questions:
highlight-specific-substrings-in-a-qtreeview
how-to-make-item-view-render-rich-html-text-in-qt
set-bold-rows-in-a-qtreeview
But they all use delegate. The reason is that I am creating a search text in widgets tool which browse all widgets and find and highlights text in them. Hence, I cannot use delegate.
Is there any possible solution?
Painting a semi-transparent item above it?
Here is the code of the tool which stores the widget and the way to search for text in them.
class GUI_EXPORT QgsSearchHighlightOptionWidget : public QObject
{
Q_OBJECT
public:
/**
* Constructor
* \param widget the widget used to search text into
*/
explicit QgsSearchHighlightOptionWidget( QWidget *widget = nullptr );
/**
* Returns if it valid: if the widget type is handled and if the widget is not still available
*/
bool isValid() { return mWidget && mValid; }
/**
* search for a text pattern and highlight the widget if the text is found
* \returns true if the text pattern is found
*/
bool searchHighlight( const QString &searchText );
/**
* reset the style to the original state
*/
void reset();
/**
* return the widget
*/
QWidget *widget() { return mWidget; }
bool eventFilter( QObject *obj, QEvent *event ) override;
private slots:
void widgetDestroyed();
private:
QPointer< QWidget > mWidget;
QString mStyleSheet;
bool mValid = true;
bool mChangedStyle = false;
std::function < bool( QString )> mTextFound = []( QString searchText ) {Q_UNUSED( searchText ); return false;};
bool mInstalledFilter = false;
};
QgsSearchHighlightOptionWidget::QgsSearchHighlightOptionWidget( QWidget *widget )
: QObject( widget )
, mWidget( widget )
{
if ( qobject_cast<QLabel *>( widget ) )
{
mStyleSheet = QStringLiteral( "QLabel { background-color: yellow; color: blue;}" );
mTextFound = [ = ]( QString searchText ) {return qobject_cast<QLabel *>( mWidget )->text().contains( searchText, Qt::CaseInsensitive );};
}
else if ( qobject_cast<QCheckBox *>( widget ) )
{
mStyleSheet = QStringLiteral( "QCheckBox { background-color: yellow; color: blue;}" );
mTextFound = [ = ]( QString searchText ) {return qobject_cast<QCheckBox *>( mWidget )->text().contains( searchText, Qt::CaseInsensitive );};
}
else if ( qobject_cast<QAbstractButton *>( widget ) )
{
mStyleSheet = QStringLiteral( "QAbstractButton { background-color: yellow; color: blue;}" );
mTextFound = [ = ]( QString searchText ) {return qobject_cast<QAbstractButton *>( mWidget )->text().contains( searchText, Qt::CaseInsensitive );};
}
else if ( qobject_cast<QGroupBox *>( widget ) )
{
mStyleSheet = QStringLiteral( "QGroupBox::title { background-color: yellow; color: blue;}" );
mTextFound = [ = ]( QString searchText ) {return qobject_cast<QGroupBox *>( mWidget )->title().contains( searchText, Qt::CaseInsensitive );};
}
else if ( qobject_cast<QTreeView *>( widget ) )
{
// TODO - style individual matching items
mTextFound = [ = ]( QString searchText )
{
QTreeView *tree = qobject_cast<QTreeView *>( mWidget );
if ( !tree )
return false;
QModelIndexList hits = tree->model()->match( tree->model()->index( 0, 0 ), Qt::DisplayRole, searchText, 1, Qt::MatchContains | Qt::MatchRecursive );
return !hits.isEmpty();
};
}
else
{
mValid = false;
}
if ( mValid )
{
mStyleSheet.prepend( "/*!search!*/" ).append( "/*!search!*/" );
QgsDebugMsgLevel( mStyleSheet, 4 );
connect( mWidget, &QWidget::destroyed, this, &QgsSearchHighlightOptionWidget::widgetDestroyed );
}
}
bool QgsSearchHighlightOptionWidget::searchHighlight( const QString &searchText )
{
bool found = false;
if ( !mWidget )
return found;
if ( !searchText.isEmpty() )
{
found = mTextFound( searchText );
}
if ( found && !mChangedStyle )
{
if ( !mWidget->isVisible() )
{
// show the widget to get initial stylesheet in case it's modified
QgsDebugMsg( QString( "installing event filter on: %1 (%2)" )
.arg( mWidget->objectName() )
.arg( qobject_cast<QLabel *>( mWidget ) ? qobject_cast<QLabel *>( mWidget )->text() : QString() ) );
mWidget->installEventFilter( this );
mInstalledFilter = true;
}
else
{
mWidget->setStyleSheet( mWidget->styleSheet() + mStyleSheet );
mChangedStyle = true;
}
}
return found;
}
bool QgsSearchHighlightOptionWidget::eventFilter( QObject *obj, QEvent *event )
{
if ( mInstalledFilter && event->type() == QEvent::Show && obj == mWidget )
{
mWidget->removeEventFilter( this );
mInstalledFilter = false;
// instead of catching the event and calling show again
// it might be better to use a timer to change the style
// after the widget is shown
#if 1
mWidget->show();
mWidget->setStyleSheet( mWidget->styleSheet() + mStyleSheet );
return true;
#else
QTimer::singleShot( 500, this, [ = ]
{
mWidget->setStyleSheet( mWidget->styleSheet() + mStyleSheet );
mChangedStyle = true;
} );
#endif
}
return QObject::eventFilter( obj, event );
}
void QgsSearchHighlightOptionWidget::reset()
{
if ( mWidget && mValid )
{
if ( mChangedStyle )
{
QString ss = mWidget->styleSheet();
ss.remove( mStyleSheet );
mWidget->setStyleSheet( ss );
mChangedStyle = false;
}
else if ( mInstalledFilter )
{
mWidget->removeEventFilter( this );
mInstalledFilter = false;
}
}
}
void QgsSearchHighlightOptionWidget::widgetDestroyed()
{
mWidget = nullptr;
mValid = false;
}
And here is the code to actually register the widgets from the dialog:
void QgsOptionsDialogBase::registerTextSearchWidgets()
{
mRegisteredSearchWidgets.clear();
for ( int i = 0; i < mOptStackedWidget->count(); i++ )
{
Q_FOREACH ( QWidget *w, mOptStackedWidget->widget( i )->findChildren<QWidget *>() )
{
QgsSearchHighlightOptionWidget *shw = new QgsSearchHighlightOptionWidget( w );
if ( shw->isValid() )
{
QgsDebugMsgLevel( QString( "Registering: %1" ).arg( w->objectName() ), 4 );
mRegisteredSearchWidgets.append( qMakePair( shw, i ) );
}
else
{
delete shw;
}
}
}
}

Reading the docs, I don't know how to do it directly.
Here are some suggestions. Hopefully one works for you:
1) Could you use QTreeWidget instead of QTreeView ?
With this, it should be easy.
Use the item functions and e.g. setBackground() on the item.
2) How about you filter tree instead of highlighting? Using setRowHidden()?
3) If you haven't already, you could also try using QTreeView::keyboardSearch() and see what this does.
4) you could use select* and add a next/previous button to your search box. I.e. you hop through the tree selecting the current search result.

Related

How to arrange items according to groups using QSortFilterProxyModel?

I have a table like this
In this table, Abbreviation and Meaning are headers. When we click onto that, the items should be arranged. But the order I need when we click onto that is:
|Last Used or |Last Used
|--FBI |--PIG
|--PIG |--FBI
|Something |Something
|--ADIDAS |--TEAM
|--DIET |--DIET
|--TEAM |--ADIDAS
|Lorem Ipsum |Lorem Ipsum
|--CLASS |--PwC
|--PMS |--PMS
|--PwC |--CLASS
which means, I arrange the items only inside each group (Last Used, Something, and Lorem Ipsum), the order of the groups should be remained.
These are my daten:
CompleterSourceModel.h
#include <QStandardItemModel>
#include <CompleterData.h>
class CompleterSourceModel : public QStandardItemModel
{
public:
CompleterSourceModel( QObject *p_parent = nullptr );
Qt::ItemFlags flags( const QModelIndex &index ) const override;
void setCompleterData( const CompleterData &p_completerData );
private:
CompleterData m_completerData;
};
CompleterSourceModel.cpp
#include "CompleterSourceModel.h"
CompleterSourceModel::CompleterSourceModel( QObject *p_parent ) : QStandardItemModel( p_parent )
{
}
Qt::ItemFlags CompleterSourceModel::flags( const QModelIndex &p_index ) const
{
if ( !p_index.isValid() ) {
return Qt::NoItemFlags;
}
CompleterDataRow::Type type = m_completerData.data().at( p_index.row() ).type();
if ( type == CompleterDataRow::Type::Data || type == CompleterDataRow::Type::LastUsed ) {
return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
}
return Qt::NoItemFlags;
}
void CompleterSourceModel::setCompleterData( const CompleterData &p_completerData )
{
m_completerData = p_completerData;
setColumnCount( m_completerData.headers().size() + 1 );
setRowCount( m_completerData.data().size() );
for ( int col = 0; col <= m_completerData.headers().size(); col++ ) {
col < m_completerData.headers().size() ? setHeaderData( col, Qt::Horizontal, m_completerData.headers().at( col ) ) : setHeaderData( col, Qt::Horizontal, {} );
}
for ( int row = 0; row < m_completerData.data().size(); row++ ) {
for ( int col = 0; col <= m_completerData.headers().size(); col++ ) {
if ( m_completerData.data().at( row ).type() == CompleterDataRow::Type::Header || m_completerData.data().at( row ).type() == CompleterDataRow::Type::SecondHeader ) {
col == 0 ? setData( index( row, col ), m_completerData.data().at( row ).rowData().at( col ).first, Qt::EditRole ) : setData( index( row, col ), {}, Qt::EditRole );
}
else {
col == m_completerData.headers().size() ? setData( index( row, col ), {}, Qt::EditRole ) : setData( index( row, col ), m_completerData.data().at( row ).rowData().at( col ).first, Qt::EditRole );
}
setData( index( row, col ), QVariant( static_cast<int>( m_completerData.data().at( row ).type() ) ), Qt::UserRole );
}
}
}
CompleterData.h
#include <QList>
#include <QPair>
#include <QVariant>
#include <QVector>
class CompleterDataRow
{
public:
enum class Type
{
Header,
SecondHeader,
Data,
LastUsed
};
CompleterDataRow() = default;
CompleterDataRow( const CompleterDataRow::Type p_rowType, const
QList<QPair<QString, QVariant>> &p_rowData );
void setType( const CompleterDataRow::Type p_type );
CompleterDataRow::Type type() const;
QList<QPair<QString, QVariant>> rowData() const;
void setRowData( const QList<QPair<QString, QVariant>> &p_rowData );
private:
QList<QPair<QString, QVariant>> m_rowData;
Type m_type;
};
class CompleterData
{
public:
CompleterData() = default;
QVector<CompleterDataRow> data() const;
void setData( const QVector<CompleterDataRow> &p_data );
void addData( const CompleterDataRow &p_rowData );
void removeData( int p_row );
void setHeaders( const QStringList &p_headers );
void setTitle( const QString &p_label );
const QStringList &headers() const;
const QString &title() const;
private:
QVector<CompleterDataRow> m_completerData;
QString m_title;
QStringList m_headers;
};
CompleterData.cpp
#include "CompleterData.h"
CompleterDataRow::CompleterDataRow( const CompleterDataRow::Type p_rowType, const QList<QPair<QString, QVariant>> &p_rowData )
{
m_type = p_rowType;
m_rowData = p_rowData;
}
QList<QPair<QString, QVariant>> CompleterDataRow::rowData() const
{
return m_rowData;
}
void CompleterDataRow::setRowData( const QList<QPair<QString, QVariant>> &p_rowData )
{
m_rowData = p_rowData;
}
CompleterDataRow::Type CompleterDataRow::type() const
{
return m_type;
}
void CompleterDataRow::setType( const Type p_type )
{
m_type = p_type;
}
QVector<CompleterDataRow> CompleterData::data() const
{
return m_completerData;
}
void CompleterData::addData( const CompleterDataRow &p_rowData )
{
m_completerData.append( p_rowData );
}
void CompleterData::removeData( int p_row )
{
m_completerData.remove( p_row );
}
void CompleterData::setData( const QVector<CompleterDataRow> &p_data )
{
m_completerData = p_data;
}
void CompleterData::setTitle( const QString &p_title )
{
m_title = p_title;
}
const QString &CompleterData::title() const
{
return m_title;
}
void CompleterData::setHeaders( const QStringList &p_headers )
{
m_headers = p_headers;
}
const QStringList &CompleterData::headers() const
{
return m_headers;
}
MyComboBox.h
#include <QComboBox>
#include <QTreeView>
#include "CompleterData.h"
#include "CompleterSourceModel.h"
#include "CompleterProxyModel.h"
class MyComboBox : public QComboBox
{
public:
MyComboBox( QWidget *p_parent = nullptr );
CompleterData createTestData();
void setDataForCompleter(const CompleterData &p_data); // this function should be set in main.cpp in Qt Project so that problem can be reproduced
private:
QTreeView *m_view = nullptr;
CompleterSourceModel *m_sourceModel = nullptr;
CompleterProxyModel *m_proxyModel =nullptr;
};
MyComboBox.cpp
#include "MyComboBox.h"
MyComboBox::MyComboBox( QWidget *p_parent ) : QComboBox( p_parent )
{
setEditable( true );
m_view = new QTreeView();
m_sourceModel = new CompleterSourceModel( this );
m_proxyModel = new CompleterProxyModel( this );
m_proxyModel->setSourceModel(m_sourceModel);
setModel( m_proxyModel );
setView( m_view );
}
void MyComboBox::setDataForCompleter(const CompleterData &p_data)
{
m_sourceModel->setCompleterData( p_data );
}
CompleterData MyComboBox::createTestData()
{
CompleterData data;
data.addData( CompleterDataRow( CompleterDataRow::Type::Header, { { "Last Used", {} } } ) );
data.addData( CompleterDataRow( CompleterDataRow::Type::Data, { { "FBI", {} }, { "Female Body Inspector", {} } } ) );
data.addData( CompleterDataRow( CompleterDataRow::Type::Data, { { "PIG", {} }, { "Pretty Insensitive Guy", {} } } ) );
data.addData( CompleterDataRow( CompleterDataRow::Type::Header, { { "Something", {}} } ) );
data.addData( CompleterDataRow( CompleterDataRow::Type::LastUsed, { { "ADIDAS", {} }, {"All Day I Dream About Soccer", {} } } ) );
data.addData( CompleterDataRow( CompleterDataRow::Type::LastUsed, { { "DIET", {}}, {"Do I eat today?", {}} } ) );
data.addData( CompleterDataRow( CompleterDataRow::Type::Data, { { "TEAM", {} }, { "Together Everyone Achieves More", {} } } ) );
data.addData( CompleterDataRow( CompleterDataRow::Type::SecondHeader, { { "Lorem Ipsum", {} } } ) );
data.addData( CompleterDataRow( CompleterDataRow::Type::LastUsed, { { "CLASS", {}}, {"Come late and start sleeping", {}} } ) );
data.addData( CompleterDataRow( CompleterDataRow::Type::LastUsed, { { "PMS", {}}, {"Purchase More Shoes", {}} } ) );
data.addData( CompleterDataRow( CompleterDataRow::Type::LastUsed, { { "PwC", {}}, {"Partner want Cash", {}} } ) );
data.addData( CompleterDataRow( CompleterDataRow::Type::Header, { { "Some Countries", {} } } ) );
data.addData( CompleterDataRow( CompleterDataRow::Type::SecondHeader, { { "Some Cities", {} } } ) );
data.setTitle( "Proposal List" );
data.setHeaders( { "Abbreviation", "Meaning" } );
return data;
}
main.cpp
#include "mycombobox.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MyComboBox combo;
combo.setDataForCompleter(combo.createTestData());
combo.show();
return a.exec();
}
In order to satisfy the requirement I think I should use Proxymodel. But I have something wrong with my proxy model, so the result I become is like this, when I load the model (I did not click onto headers to sort items yet)
As you can see, the Lorem Ipsum is moved to the end of the list, the order right at the beginning is wrong. So I think I have mistakes in my proxy model. Could you show me where exactly in my proxy model? Or any other solutions are also welcomed.
This is my proxy model:
CompleterProxyModel.h
#include <QSortFilterProxyModel>
#include <CompleterData.h>
class CompleterProxyModel : public QSortFilterProxyModel
{
public:
CompleterProxyModel( QObject *p_parent = nullptr );
protected:
bool lessThan( const QModelIndex &p_left, const QModelIndex &p_right ) const override;
};
CompleterProxyModel.cpp
#include "CompleterProxyModel.h"
CompleterProxyModel::CompleterProxyModel( QObject *p_parent ) : QSortFilterProxyModel( p_parent )
{
}
bool CompleterProxyModel::lessThan( const QModelIndex &p_left, const QModelIndex &p_right ) const
{
CompleterDataRow::Type leftType = static_cast<CompleterDataRow::Type>( p_left.data( Qt::UserRole ).toInt() );
CompleterDataRow::Type rightType = static_cast<CompleterDataRow::Type>( p_right.data( Qt::UserRole ).toInt() );
if ( ( leftType == CompleterDataRow::Type::Data && rightType == CompleterDataRow::Type::Data ) ||
( leftType == CompleterDataRow::Type::LastUsed && rightType == CompleterDataRow::Type::LastUsed ) )
{
QString leftString = p_left.data( Qt::EditRole ).toString();
QString rightString = p_right.data( Qt::EditRole ).toString();
qDebug() << leftString << rightString << QString::localeAwareCompare( leftString, rightString );
return QString::localeAwareCompare( leftString, rightString ) < 0;
}
return false;
}
You have two rules for your sorting proxy:
An item is a category. So it's a root item with no parent and you have to sort on the row number to keep the original order.
Otherwise, it's a child item and you can sort it by its value.
Your proxy is quite simple: if an item has a parent, sort it regarding to the row. Otherwise use the original sorting rules (or another one).
The only tricky part is the order (ascending or descending). So, you have to deal with it (or your category will be inverted).
For example:
class SortProxyModel: public QSortFilterProxyModel
{
public:
SortProxyModel(): QSortFilterProxyModel() {}
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override
{
if (!left.parent().isValid())
{
if (sortOrder() == Qt::DescendingOrder) // Don't care about the order
return left.row() > right.row();
return left.row() < right.row();
}
return QSortFilterProxyModel::lessThan(left, right);
}
};
The tests:
QTreeView* view = new QTreeView();
QStandardItemModel* model = new QStandardItemModel();
QSortFilterProxyModel* proxy = new SortProxyModel();
proxy->setSourceModel(model);
model->setHorizontalHeaderLabels(QStringList() << "Col 1");
QStandardItem* item1 = new QStandardItem("Last Used");
item1->appendRows(QList<QStandardItem*>() << new QStandardItem("A") << new QStandardItem("C") << new QStandardItem("B"));
QStandardItem* item2 = new QStandardItem("Something");
item2->appendRows(QList<QStandardItem*>() << new QStandardItem("F") << new QStandardItem("E") << new QStandardItem("D"));
QStandardItem* item3 = new QStandardItem("Lorem");
item3->appendRows(QList<QStandardItem*>() << new QStandardItem("I") << new QStandardItem("G") << new QStandardItem("H"));
model->appendRow(item1);
model->appendRow(item2);
model->appendRow(item3);
view->setModel(proxy);
view->setSortingEnabled(true);
view->show();
In this case I have just found a solution. I think the model should be updated as I import data into it. So I put beginResetModel() and endResetModel() into function setCompleterData and it works now.
void CompleterSourceModel::setCompleterData( const CompleterData &p_completerData )
{
beginResetModel();
m_completerData = p_completerData;
setColumnCount( m_completerData.headers().size() + 1 );
setRowCount( m_completerData.data().size() );
for ( int col = 0; col <= m_completerData.headers().size(); col++ ) {
col < m_completerData.headers().size() ? setHeaderData( col, Qt::Horizontal, m_completerData.headers().at( col ) ) : setHeaderData( col, Qt::Horizontal, {} );
}
for ( int row = 0; row < m_completerData.data().size(); row++ ) {
for ( int col = 0; col <= m_completerData.headers().size(); col++ ) {
if ( m_completerData.data().at( row ).type() == CompleterDataRow::Type::Header || m_completerData.data().at( row ).type() == CompleterDataRow::Type::SecondHeader ) {
col == 0 ? setData( index( row, col ), m_completerData.data().at( row ).rowData().at( col ).first, Qt::EditRole ) : setData( index( row, col ), {}, Qt::EditRole );
}
else {
col == m_completerData.headers().size() ? setData( index( row, col ), {}, Qt::EditRole ) : setData( index( row, col ), m_completerData.data().at( row ).rowData().at( col ).first, Qt::EditRole );
}
setData( index( row, col ), QVariant( static_cast<int>( m_completerData.data().at( row ).type() ) ), Qt::UserRole );
}
endResetModel();
}

How to setBold for matching characters with QSortFilterProxyModel?

This is an example on Qt Website
In this case the matching characters are not bold, and other characters are bold. I want to do the other way around: the matching characters should be bold and others are not bold. How can I do it?
MyLineEdit.cpp
MyLineEdit::MyLineEdit( QWidget *p_parent ) : QWidget( p_parent )
{
............
m_completer = new Completer();
m_lineEdit = new QLineEdit;
m_lineEdit->setObjectName( "lineEdit" );
connect( m_lineEdit, &LineEdit::textChanged, this, &MyLineEdit::setTextForFilter );
}
void MyLineEdit::setTextForFilter( const QString &p_text )
{
m_completer->setTextForFilter( p_text );
}
Completer.cpp
Completer::Completer( QWidget *p_parent ) : QWidget( p_parent )
{
.........
QGridLayout *tableViewLayout = new QGridLayout( this );
m_caption = new Label;
tableViewLayout->addWidget( m_caption );
m_table = new QTreeView();
m_table->setItemDelegate( new CompleterDelegate() );
m_sourceModel = new CompleterModel( this );
m_proxyModel = new CompleterProxyModel( this );
m_proxyModel->setSourceModel( m_sourceModel );
m_table->setModel( m_proxyModel );
tableViewLayout->addWidget( m_table );
}
void Completer::setTextForFilter( const QString &p_text )
{
QRegExp regExp( p_text, Qt::CaseInsensitive, QRegExp::RegExp );
m_proxyModel->setFilterRegExp( regExp );
}
CompleterModel.cpp
QVariant CompleterModel::data( const QModelIndex &p_index, int p_role ) const
{
int row = p_index.row();
int col = p_index.column();
switch ( p_role )
{
case Qt::DisplayRole:
if ( col < m_completerData.rowData().at( row ).columnCount() )
{
return m_completerData.rowData().at( row ).columnData( col ).first;
}
break;
case Qt::TextAlignmentRole:
return Qt::AlignLeft;
case Qt::UserRole:
return QVariant( static_cast<int>( m_completerData.rowData().at( row ).type() ) );
}
return QVariant();
}
CompleterProxyModel.cpp
bool CompleterProxyModel::filterAcceptsRow( int p_sourceRow, const QModelIndex &p_sourceParent ) const
{
QModelIndex index0 = sourceModel()->index( p_sourceRow, 0, p_sourceParent );
QModelIndex index1 = sourceModel()->index( p_sourceRow, 1, p_sourceParent );
return ( sourceModel()->data( index0 ).toString().contains( filterRegExp() ) || sourceModel()->data( index1 ).toString().contains( filterRegExp() ) );
}
You are going to need a custom delegate and do your own rendering. Using rich text is unfortunately not supported out of the box.

QItemDelegate: painting an unwanted QCheckBox

I have an editable model, which inherits QAbstractTableModel. Also have a custom delegate to go with it. This is my first editable model, and I think I'm missing something. I'm pretty much following the examples found at Nokia. My model tells the delegate that the data is editable via flags(). When I do this, it draws a QSpinBox in the cell.
Underlying model is a simple std::map. The key is days, the value is a rate.
Generally, what is painted in any editable cell, is a QCheckBox, but is ghosted out, then the data. If I double-click on the value, I am shown the editor, which happens to be a custom widget based on QDoubleSpinbox.
Qt::ItemFlags my_model_t::flags( const QModelIndex& index ) const
{
if ( !index.isValid() ) {
return Qt::NoItemFlags;
}
if ( index.column() == col_rates ) {
return QAbstractItemModel::flags( index ) | Qt::ItemIsEditable;
}
return QAbstractItemModel::flags( index );
}
QVariant my_model_t::data( const QModelIndex& index, int role ) const
{
if ( !index.isValid() ) {
return QVariant();
}
if ( role == Qt::DisplayRole || Qt::EditRole ) {
if ( static_cast<int>( rates.size() ) <= index.row() ) {
return QVariant();
}
int day = vec[index.row()];
if ( index.column() == col_days ) {
return day;
} else if ( index.column() == col_rates ) {
std::map<int, double>::const_iterator it = rates.find( day );
if ( it != rates.end() ) {
return (*it).second;
}
}
}
return QVariant();
}
QWidget* my_delegate_t::createEditor( QWidget* parent, const QStyleOptionViewItem& /*option*/, const QModelIndex& index ) const
{
gui_spinbox* editor = new gui_spinbox( parent );
if ( index.column() == col_rate ) {
const my_model_t* model = static_cast<my_model_t*>( index.model() );
}
return editor;
}
void my_delegate_t::setEditorData( QWidget* editor, const QModelIndex& index ) const
{
double value = index.model()->data( index, Qt::EditRole ).toDouble();
gui_spinbox* spin_box = static_cast<gui_spinbox*>( editor );
if ( spin_box ) {
spin_box->setValue( value );
}
}
void my_delegate_t::setModelData( QWidget* editor, QAbstractItemModel* model, const QModelIndex& index ) const
{
gui_spinbox* spin_box = static_cast<gui_spinbox*>( editor );
if ( spin_box ) {
double value = spin_box->value();
model->setData( index, value, Qt::EditRole );
}
}
void my_delegate_t::updateEditorGemoetry( QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& /*index*/ ) const
{
editor->setGeometry( option.rect );
}
I'm not pretty sure about your problem. Do you want to paint a QCheckBox?
In your createEditor method, you created an editor use QSpinbox but in the Title you said it was unwanted.
If you want to be shown an editor based on an QCheckBox when you double clicked an item you just create a checkBox in the createEditor method and use checkBox in setEditorData and setModelData.

An IP address widget for Qt, Similar to MFC's IP address control

I am looking for a widget in Qt which is similar to MFC's IP address control. Does anyone know of such a widget, or perhaps how I can create one?
I have no idea what's an MFC IP Widget, but looks like it is a Widget to enter an IP address.
You need to use a QLineEdit with a inputMask "000.000.000.000;_"
QLineEdit *ipEdit = new QLineEdit();
ipEdit->setInputMask("000.000.000.000;_");
ipEdit->show();
A little improvement of jpo38's code...
#include <QFrame>
#include <QLineEdit>
#include <QIntValidator>
#include "stdint.h"
#include <QHBoxLayout>
#include <QFont>
#include <QLabel>
#include <QKeyEvent>
class IPCtrl : public QFrame
{
Q_OBJECT
public:
IPCtrl(QWidget *parent = 0);
~IPCtrl();
virtual bool eventFilter( QObject *obj, QEvent *event );
public slots:
void slotTextChanged( QLineEdit* pEdit );
signals:
void signalTextChanged( QLineEdit* pEdit );
private:
enum
{
QTUTL_IP_SIZE = 4,// число октетов IP адресе
MAX_DIGITS = 3 // число символов в LineEdit
};
QLineEdit *(m_pLineEdit[QTUTL_IP_SIZE]);
void MoveNextLineEdit (int i);
void MovePrevLineEdit (int i);
};
IPCtrl::IPCtrl(QWidget *parent) : QFrame(parent)
{
setFrameShape( QFrame::StyledPanel );
setFrameShadow( QFrame::Sunken );
QHBoxLayout* pLayout = new QHBoxLayout( this );
setLayout( pLayout );
pLayout->setContentsMargins( 0, 0, 0, 0 );
pLayout->setSpacing( 0 );
for ( int i = 0; i != QTUTL_IP_SIZE; ++i )
{
if ( i != 0 )
{
QLabel* pDot = new QLabel( ".", this );
pDot->setStyleSheet( "background: white" );
pLayout->addWidget( pDot );
pLayout->setStretch( pLayout->count(), 0 );
}
m_pLineEdit[i] = new QLineEdit( this );
QLineEdit* pEdit = m_pLineEdit[i];
pEdit->installEventFilter( this );
pLayout->addWidget( pEdit );
pLayout->setStretch( pLayout->count(), 1 );
pEdit->setFrame( false );
pEdit->setAlignment( Qt::AlignCenter );
QFont font = pEdit->font();
font.setStyleHint( QFont::Monospace );
font.setFixedPitch( true );
pEdit->setFont( font );
QRegExp rx ( "^(0|[1-9]|[1-9][0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))$" );
QValidator *validator = new QRegExpValidator(rx, pEdit);
pEdit->setValidator( validator );
}
setMaximumWidth( 30 * QTUTL_IP_SIZE );
connect( this, SIGNAL(signalTextChanged(QLineEdit*)),
this, SLOT(slotTextChanged(QLineEdit*)),
Qt::QueuedConnection );
}
IPCtrl::~IPCtrl()
{
}
void IPCtrl::slotTextChanged( QLineEdit* pEdit )
{
for ( unsigned int i = 0; i != QTUTL_IP_SIZE; ++i )
{
if ( pEdit == m_pLineEdit[i] )
{
if ( ( pEdit->text().size() == MAX_DIGITS && pEdit->text().size() == pEdit->cursorPosition() ) || ( pEdit->text() == "0") )
{
// auto-move to next item
if ( i+1 != QTUTL_IP_SIZE )
{
m_pLineEdit[i+1]->setFocus();
m_pLineEdit[i+1]->selectAll();
}
}
}
}
}
bool IPCtrl::eventFilter(QObject *obj, QEvent *event)
{
bool bRes = QFrame::eventFilter(obj, event);
if ( event->type() == QEvent::KeyPress )
{
QKeyEvent* pEvent = dynamic_cast<QKeyEvent*>( event );
if ( pEvent )
{
for ( unsigned int i = 0; i != QTUTL_IP_SIZE; ++i )
{
QLineEdit* pEdit = m_pLineEdit[i];
if ( pEdit == obj )
{
switch ( pEvent->key() )
{
case Qt::Key_Left:
if ( pEdit->cursorPosition() == 0 )
{
// user wants to move to previous item
MovePrevLineEdit(i);
}
break;
case Qt::Key_Right:
if ( pEdit->text().isEmpty() || (pEdit->text().size() == pEdit->cursorPosition()) )
{
// user wants to move to next item
MoveNextLineEdit(i);
}
break;
case Qt::Key_0:
if ( pEdit->text().isEmpty() || pEdit->text() == "0" )
{
pEdit->setText("0");
// user wants to move to next item
MoveNextLineEdit(i);
}
emit signalTextChanged( pEdit );
break;
case Qt::Key_Backspace:
if ( pEdit->text().isEmpty() || pEdit->cursorPosition() == 0)
{
// user wants to move to previous item
MovePrevLineEdit(i);
}
break;
case Qt::Key_Comma:
case Qt::Key_Period:
MoveNextLineEdit(i);
break;
default:
emit signalTextChanged( pEdit );
break;
}
}
}
}
}
return bRes;
}
void IPCtrl::MoveNextLineEdit(int i)
{
if ( i+1 != QTUTL_IP_SIZE )
{
m_pLineEdit[i+1]->setFocus();
m_pLineEdit[i+1]->setCursorPosition( 0 );
m_pLineEdit[i+1]->selectAll();
}
}
void IPCtrl::MovePrevLineEdit(int i)
{
if ( i != 0 )
{
m_pLineEdit[i-1]->setFocus();
m_pLineEdit[i-1]->setCursorPosition( m_pLineEdit[i-1]->text().size() );
//m_pLineEdit[i-1]->selectAll();
}
}
I agree with little_su: QLineEdit with input mask does not look and behaves as nice as the standard Windows IP control. I worked out a complete QWidget-based IP control embedding 4 QLineEdit and 3 QLabel (for dots). It just looks and behaves perfectly as MFC/Windows IP controls.
Here is the code:
class IPCtrl : public QFrame
{
typedef QFrame baseClass;
Q_OBJECT
public:
IPCtrl(QWidget *parent);
~IPCtrl();
#define QTUTL_IP_SIZE 4
virtual bool eventFilter( QObject *obj, QEvent *event );
public slots:
void slotTextChanged( QLineEdit* pEdit );
signals:
void signalTextChanged( QLineEdit* pEdit );
private:
QLineEdit *(m_pLineEdit[QTUTL_IP_SIZE]);
static std::string getIPItemStr( unsigned char item );
};
class IPItemValidator : public QIntValidator
{
public:
IPItemValidator( QObject* parent ) : QIntValidator( parent )
{
setRange( 0, UCHAR_MAX );
}
~IPItemValidator() {}
virtual void fixup( QString & input ) const
{
if ( input.isEmpty() )
input = "0";
}
};
IPCtrl::IPCtrl(QWidget *parent) : baseClass(parent)
{
setFrameShape( QFrame::StyledPanel );
setFrameShadow( QFrame::Sunken );
QHBoxLayout* pLayout = new QHBoxLayout( this );
setLayout( pLayout );
pLayout->setContentsMargins( 0, 0, 0, 0 );
pLayout->setSpacing( 0 );
for ( int i = 0; i != QTUTL_IP_SIZE; ++i )
{
if ( i != 0 )
{
QLabel* pDot = new QLabel( ".", this );
pDot->setStyleSheet( "background: white" );
pLayout->addWidget( pDot );
pLayout->setStretch( pLayout->count(), 0 );
}
m_pLineEdit[i] = new QLineEdit( this );
QLineEdit* pEdit = m_pLineEdit[i];
pEdit->installEventFilter( this );
pLayout->addWidget( pEdit );
pLayout->setStretch( pLayout->count(), 1 );
pEdit->setFrame( false );
pEdit->setAlignment( Qt::AlignCenter );
QFont font = pEdit->font();
font.setStyleHint( QFont::Monospace );
font.setFixedPitch( true );
pEdit->setFont( font );
pEdit->setValidator( new IPItemValidator( pEdit ) );
}
setMaximumWidth( 30 * QTUTL_IP_SIZE );
connect( this, SIGNAL(signalTextChanged(QLineEdit*)),
this, SLOT(slotTextChanged(QLineEdit*)),
Qt::QueuedConnection );
}
IPCtrl::~IPCtrl()
{
}
std::string IPCtrl::getIPItemStr( unsigned char item )
{
std::strstream str;
str << (int) item;
str << std::ends;
return str.str();
}
void IPCtrl::slotTextChanged( QLineEdit* pEdit )
{
for ( unsigned int i = 0; i != QTUTL_IP_SIZE; ++i )
{
if ( pEdit == m_pLineEdit[i] )
{
if ( pEdit->text().size() == getIPItemStr( UCHAR_MAX ).size() &&
pEdit->text().size() == pEdit->cursorPosition() )
{
// auto-move to next item
if ( i+1 != QTUTL_IP_SIZE )
{
m_pLineEdit[i+1]->setFocus();
m_pLineEdit[i+1]->selectAll();
}
}
}
}
}
bool IPCtrl::eventFilter(QObject *obj, QEvent *event)
{
bool bRes = baseClass::eventFilter(obj, event);
if ( event->type() == QEvent::KeyPress )
{
QKeyEvent* pEvent = dynamic_cast<QKeyEvent*>( event );
if ( pEvent )
{
for ( unsigned int i = 0; i != QTUTL_IP_SIZE; ++i )
{
QLineEdit* pEdit = m_pLineEdit[i];
if ( pEdit == obj )
{
switch ( pEvent->key() )
{
case Qt::Key_Left:
{
if ( pEdit->cursorPosition() == 0 )
{
// user wants to move to previous item
if ( i != 0 )
{
m_pLineEdit[i-1]->setFocus();
m_pLineEdit[i-1]->setCursorPosition( m_pLineEdit[i-1]->text().size() );
}
}
break;
}
case Qt::Key_Right:
{
if ( pEdit->text().isEmpty() ||
(pEdit->text().size() == pEdit->cursorPosition()) )
{
// user wants to move to next item
if ( i+1 != QTUTL_IP_SIZE )
{
m_pLineEdit[i+1]->setFocus();
m_pLineEdit[i+1]->setCursorPosition( 0 );
}
}
break;
}
default:
{
emit signalTextChanged( pEdit );
}
}
break;
}
}
}
}
return bRes;
}
A little improvement of Tugo's code... I cannot comment so...
classic array to std::array
setter and getter
remove styleSheet, u can use your own
ipctrl.h :
#pragma once
#include <QFrame>
#include <array>
/// Thanx to https://stackoverflow.com/a/11358560/8524139
class QLineEdit;
class IPCtrl : public QFrame
{
Q_OBJECT
enum
{
QTUTL_IP_SIZE = 4, // число октетов IP адресе
MAX_DIGITS = 3 // число символов в LineEdit
};
public:
IPCtrl(QWidget *parent = 0);
~IPCtrl();
virtual bool eventFilter(QObject *obj, QEvent *event);
std::array<quint8, QTUTL_IP_SIZE> getIP() const;
void setIP(std::array<quint8, QTUTL_IP_SIZE> ipAddr);
signals:
void signalTextChanged(QLineEdit *pEdit);
private:
std::array<QLineEdit *, QTUTL_IP_SIZE> m_pLineEdit;
void slotTextChanged(QLineEdit *pEdit);
void moveNextLineEdit(int i);
void movePrevLineEdit(int i);
};
ipctrl.cpp :
#include "ipctrl.h"
#include <QHBoxLayout>
#include <QIntValidator>
#include <QKeyEvent>
#include <QLabel>
#include <QLineEdit>
IPCtrl::IPCtrl(QWidget *parent) : QFrame(parent)
{
setFrameShape(QFrame::StyledPanel);
setFrameShadow(QFrame::Sunken);
QHBoxLayout *pLayout = new QHBoxLayout(this);
setLayout(pLayout);
pLayout->setContentsMargins(0, 0, 1, 0);
pLayout->setSpacing(0);
for (int i = 0; i != QTUTL_IP_SIZE; ++i)
{
if (i != 0)
{
QLabel *pDot = new QLabel(".", this);
pLayout->addWidget(pDot);
pLayout->setStretch(pLayout->count(), 0);
}
m_pLineEdit.at(i) = new QLineEdit(this);
QLineEdit *pEdit = m_pLineEdit.at(i);
pEdit->installEventFilter(this);
pLayout->addWidget(pEdit);
pLayout->setStretch(pLayout->count(), 1);
pEdit->setFrame(false);
pEdit->setAlignment(Qt::AlignCenter);
QFont font = pEdit->font();
font.setStyleHint(QFont::Monospace);
font.setFixedPitch(true);
pEdit->setFont(font);
QRegExp rx("^(0|[1-9]|[1-9][0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))$");
QValidator *validator = new QRegExpValidator(rx, pEdit);
pEdit->setValidator(validator);
}
setMaximumWidth(30 * QTUTL_IP_SIZE);
connect(this, &IPCtrl::signalTextChanged, this, &IPCtrl::slotTextChanged, Qt::QueuedConnection);
}
IPCtrl::~IPCtrl()
{
}
void IPCtrl::slotTextChanged(QLineEdit *pEdit)
{
for (unsigned int i = 0; i != QTUTL_IP_SIZE; ++i)
{
if (pEdit == m_pLineEdit.at(i))
{
if ((pEdit->text().size() == MAX_DIGITS && pEdit->text().size() == pEdit->cursorPosition())
|| (pEdit->text() == "0"))
{
// auto-move to next item
if (i + 1 != QTUTL_IP_SIZE)
{
m_pLineEdit.at(i + 1)->setFocus();
m_pLineEdit.at(i + 1)->selectAll();
}
}
}
}
}
bool IPCtrl::eventFilter(QObject *obj, QEvent *event)
{
bool bRes = QFrame::eventFilter(obj, event);
if (event->type() == QEvent::KeyPress)
{
QKeyEvent *pEvent = dynamic_cast<QKeyEvent *>(event);
if (pEvent)
{
for (unsigned int i = 0; i != QTUTL_IP_SIZE; ++i)
{
QLineEdit *pEdit = m_pLineEdit[i];
if (pEdit == obj)
{
switch (pEvent->key())
{
case Qt::Key_Left:
if (pEdit->cursorPosition() == 0)
{
// user wants to move to previous item
movePrevLineEdit(i);
}
break;
case Qt::Key_Right:
if (pEdit->text().isEmpty() || (pEdit->text().size() == pEdit->cursorPosition()))
{
// user wants to move to next item
moveNextLineEdit(i);
}
break;
case Qt::Key_0:
if (pEdit->text().isEmpty() || pEdit->text() == "0")
{
pEdit->setText("0");
// user wants to move to next item
moveNextLineEdit(i);
}
emit signalTextChanged(pEdit);
break;
case Qt::Key_Backspace:
if (pEdit->text().isEmpty() || pEdit->cursorPosition() == 0)
{
// user wants to move to previous item
movePrevLineEdit(i);
}
break;
case Qt::Key_Comma:
case Qt::Key_Period:
moveNextLineEdit(i);
break;
default:
emit signalTextChanged(pEdit);
break;
}
}
}
}
}
return bRes;
}
std::array<quint8, IPCtrl::QTUTL_IP_SIZE> IPCtrl::getIP() const
{
std::array<quint8, QTUTL_IP_SIZE> ipAddr;
std::transform(m_pLineEdit.cbegin(), m_pLineEdit.cend(), ipAddr.begin(),
[](const QLineEdit *lineEdit) -> quint8 { return lineEdit->text().toUInt(); });
return ipAddr;
}
void IPCtrl::setIP(std::array<quint8, IPCtrl::QTUTL_IP_SIZE> ipAddr)
{
for (auto i = 0; i != QTUTL_IP_SIZE; ++i)
{
m_pLineEdit.at(i)->setText(QString::number(ipAddr.at(i)));
}
}
void IPCtrl::moveNextLineEdit(int i)
{
if (i + 1 != QTUTL_IP_SIZE)
{
m_pLineEdit.at(i + 1)->setFocus();
m_pLineEdit.at(i + 1)->setCursorPosition(0);
m_pLineEdit.at(i + 1)->selectAll();
}
}
void IPCtrl::movePrevLineEdit(int i)
{
if (i != 0)
{
m_pLineEdit.at(i - 1)->setFocus();
m_pLineEdit.at(i - 1)->setCursorPosition(m_pLineEdit[i - 1]->text().size());
// m_pLineEdit[i-1]->selectAll();
}
}

Need example about nodes in QAbstractItemModel for QTreeView?

Problem: I'm looking example about creating model ( based on QAbstractItemModel ) to QTreeView, but can't find sane codes. Qt examples are based on QStandardModel, which is not very useful and complex, Internet examples are based on python?! codes... Other information can't gave me the right directions to go. So, here is what I have:
std::map
typedef std::map< CompanyData, std::vector< ContractorData >, LessData< CompanyData > > Companies;
Here it's data example (CompanyName + ContractorsNames):
[Microsoft]*
[Bill Gates]
[Steve Balmer]
[...]
[Apple]*
[Steve Jobs - R.I.P.]
[Wozniak]
[OtherStuff]*
...
where * means - expandable item (parent)
And all that I need it to create QTreeView with this data above!
Can any one help?
Many thanks!
So, as no posts was done here, I post here my own solution (also used text filtering):
void ContractorsFilter::onCustomFilterChanged( const QString& text )
{
try
{
struct MatchFilter
{
// data
QString filter_;
Companies& filtered_recipients_;
// methods
MatchFilter( const QString& _filter, Companies& _recipients )
: filter_( _filter )
, filtered_recipients_( _recipients )
{
filtered_recipients_.clear();
}
void operator()( const Companies::value_type& val ) const
{
bool isFound = false;
std::vector< ContractorData >::const_iterator con_i( val.second.begin() ), con_e( val.second.end() );
for( ; con_i != con_e; ++con_i )
{
const QString contractorName = (*con_i).name;
if( contractorName.contains( filter_, Qt::CaseInsensitive ) )
{
filtered_recipients_[ val.first ].push_back( (*con_i) );
isFound = true;
}
}
const QString companyName = val.first.name;
if( companyName.contains( filter_, Qt::CaseInsensitive ) )
{
filtered_recipients_[ val.first ].push_back( ContractorData() );
}
}
};
struct FillView
{
// data
QFont boldFont;
QBrush whiteBrush;
QStandardItemModel* model_;
// methods
FillView( QStandardItemModel* model )
: model_( model )
{
model_->clear();
}
void operator ()( const Companies::value_type& val ) const
{
struct AppendContractors
{
// data
QStandardItem* parent_;
// methods
AppendContractors( QStandardItem* _parent = 0 )
: parent_( _parent )
{}
bool isEmpty( const ContractorData& contractor ) const
{
return contractor.id.isEmpty();
}
void operator()( const std::vector< ContractorData >::value_type& contractor ) const
{
if( !isEmpty( contractor ) )
{
QStandardItem *item = 0;
QList< QStandardItem* > line;
line << ( item = new QStandardItem( QIcon( ACCOUNT_ITEM_ICON ), contractor.name ) );
item->setSizeHint( QSize( 0, 25 ) );
parent_->appendRow( line );
}
}
};
QStandardItem *parentItem = model_->invisibleRootItem();
// добавляем новую компанию + контрагента
QList< QStandardItem* > line;
line << ( parentItem = new QStandardItem( QIcon( COMPANY_ITEM_ICON ), val.first.name ) );
parentItem->setSizeHint( QSize( 0, 25 ) );
model_->appendRow( line );
std::for_each( val.second.begin(), val.second.end(), AppendContractors( parentItem ) );
}
};
// удаляем символ(ы), которые не фильтруются
// формируется новая таблица, которая и будет использоваться моделью для отображения
std::for_each( data_.begin(), data_.end(),
MatchFilter( text, filter_data_ ) );
// вывод отфильтрованных контрагентов
std::for_each( filter_data_.begin(), filter_data_.end(),
FillView( model_ ) );
ui_.treeView->expandAll();
}
catch( const std::exception& e )
{
Core::errLog( "ContractorsFilter::onCustomFilterChanged", e.what() );
throw;
}
}
PS: type Companies is a
typedef std::map< CompanyData, ContractorsData, LessData< CompanyData > > Companies;
where CompanyData, ContractorsData are simple structures...
Have a nice day!

Resources