change QCompleter result in QLineEdit - qt

The QLineEdit is for entering post code. User may also input city name, while QCompleter will display a list of names for user to select. The problem is, on selecting the name in completer, how could the post code be put in the QLineEdit?
I tried to connect QCompleter::activated(QModelIndex) to slot that change the QLineEdit text to post code. But later the text was again set to city name by QLineEdit.

Sorry, my previous answer was not correct, so I've edited it.
As the documentation says:
QString QCompleter::pathFromIndex ( const QModelIndex & index ) const
[virtual]
Returns the path for the given index. The completer object
uses this to obtain the completion text from the underlying model. The
default implementation returns the edit role of the item for list
models. It returns the absolute file path if the model is a QDirModel.
I've got what you need by subclassing QCompleter and reimplementing pathFromIndex:
class CodeCompleter : public QCompleter
{
Q_OBJECT
public:
explicit CodeCompleter(QObject *parent = 0);
static const int CompleteRole;
QString pathFromIndex(const QModelIndex &index) const;
};
const int CodeCompleter::CompleteRole = Qt::UserRole + 1;
CodeCompleter::CodeCompleter(QObject *parent) :
QCompleter(parent)
{
}
QString
CodeCompleter::pathFromIndex(const QModelIndex &index) const
{
QMap<int, QVariant> data = model()->itemData(index);
QString code = data.value(CompleteRole).toString();
return code;
}
And you can use it like this:
QStringList cities;
cities << "Moscow" << "London" << "Las Vegas" << "New York";
QStandardItemModel *model = new QStandardItemModel;
for (int i = 0; i < cities.count(); ++i)
{
QString city = cities.at(i);
QString code = city.at(0) + QString::number(city.length());///< just an example
QStandardItem *item = new QStandardItem;
item->setText(city);
item->setData(code, CodeCompleter::CompleteRole);
model->appendRow(item);
}
QLineEdit *lineEdit = new QLineEdit(this);
CodeCompleter *completer = new CodeCompleter(this);
completer->setModel(model);
completer->setCaseSensitivity(Qt::CaseInsensitive);
lineEdit->setCompleter(completer);

Related

Mime type for custom data in tree view

The items in the tree view hold a instance of class container.
I want to implement drag and drop functionality in the view.
According to the QT tutorial for the data to copy i need specify the mime type and than write the Mimedata and dropMimeData functions.
The QT Example is dealing with a simple string so i am totally clueless of how to implement these function in case of custom objects.
1) What should be the mime type in my case ?
2) How to implement the current mimedata function for Container object data?
3) How to implement the current dropmimedata function for Container object data?
/////////////////////////////////////////
class Container
{
private:
std::string stdstrContainerName;
std::string stdstrPluginType;
int iSegments;
float fRadius;
public:
Container();
Container(std::string , std::string , int , float);
Container(const Container& obj);
~Container();
std::string GetName();
std::string GetType();
void SetName(std::string stdstrName);
};
Q_DECLARE_METATYPE( Container )
////////////////////////////////////////////////////////////
QMimeData *DragDropListModel::mimeData(const QModelIndexList &indexes)
const
{
QMimeData *mimeData = new QMimeData();
QByteArray encodedData;
QDataStream stream(&encodedData, QIODevice::WriteOnly);
foreach (const QModelIndex &index, indexes) {
if (index.isValid()) {
QString text = data(index, Qt::DisplayRole).toString();
// I have a GetContainer function which returns the Container
//object and i can use the GetContainer instead of data function.
stream << text;
}
}
mimeData->setData("application/vnd.text.list", encodedData);
return mimeData;
}
//////////////////////////////////////////////////////////////////
bool DragDropListModel::dropMimeData(const QMimeData *data,
Qt::DropAction action, int row, int column, const QModelIndex
&parent)
{
if (action == Qt::IgnoreAction)
return true;
if (!data->hasFormat("application/vnd.text.list"))
return false;
if (column > 0)
return false;
int beginRow;
if (row != -1)
beginRow = row;
else if (parent.isValid())
beginRow = parent.row();
else
beginRow = rowCount(QModelIndex());
QByteArray encodedData = data->data("application/vnd.text.list");
QDataStream stream(&encodedData, QIODevice::ReadOnly);
QStringList newItems;
int rows = 0;
while (!stream.atEnd()) {
QString text;
stream >> text;
newItems << text;
++rows;
}
insertRows(beginRow, rows, QModelIndex());
foreach (const QString &text, newItems) {
QModelIndex idx = index(beginRow, 0, QModelIndex());
setData(idx, text);
beginRow++;
}
return true;
}
The header file for TreeItem.
class TreeItem
{
public:
explicit TreeItem( const Container &data , TreeItem *parent = 0 );
~TreeItem();
TreeItem *parent();
void appendChild(TreeItem *child);
TreeItem *child(int iNumber);
int childCount() const;
int childNumber() const;
Container data() const;
bool setData(const Container &data , QVariant value);
void setContainer(const Container &data);
bool insertChildren(int position, int count );
bool removeChildren( int position , int count );
private:
QList<TreeItem*> childItems;
Container itemData;
TreeItem* parentItem;
}
You can add your custom mime types to specify the type of container you want to drag/drop. See this post for details.
The QDrag object constructed by the source contains a list of MIME types that it uses to represent the data (ordered from most appropriate to least appropriate), and the drop target uses one of these to access the data.
First of all, try to find a compatible standard mime type. Those are the most common one assigned by the IANA.
If the one you are looking for is not in the list, then you can label your custom one and serialize your data into a QByteArray to share it.
QByteArray output;
// do whatever
mimeData->setData("my-awesome-mime-type", output);
Now, in your custom widget, don't forget to accept the drops of this mime type:
void Window::dragEnterEvent(QDragEnterEvent *event) {
if (event->mimeData()->hasFormat("my-awesome-mime-type"))
event->acceptProposedAction();
}
You can find a complete example in this project.

QFileSystemModel and Proxy sort differently in QTreeView

I want to show only mounted drives sorted by drive letter in windows using Qt 5.10. Here is my test code:
#include <QtWidgets>
#include <QDebug>
QStringList mountedDrives;
class FSFilter : public QSortFilterProxyModel
{
public:
FSFilter(QObject *parent) : QSortFilterProxyModel(parent) {}
protected:
bool filterAcceptsRow(int row, const QModelIndex &par) const override
{
QModelIndex idx = sourceModel()->index(row, 0, par);
QString path = idx.data(QFileSystemModel::FilePathRole).toString();
QString path2 = idx.data(Qt::DisplayRole).toString();
bool mounted = mountedDrives.contains(path);
if (mounted) {
qDebug() << "QFileSystemModel::FilePathRole =" << path
<< " Qt::DisplayRole =" << path2;
return true;
}
else return false;
}
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
// get mounted drives only
foreach (const QStorageInfo &storage, QStorageInfo::mountedVolumes()) {
if (storage.isValid() && storage.isReady()) {
if (!storage.isReadOnly()) {
mountedDrives << storage.rootPath();
}
}
}
QFileSystemModel *fsModel = new QFileSystemModel;
fsModel->setRootPath("");
FSFilter *fsFilter = new FSFilter(fsModel);
fsFilter->setSourceModel(fsModel);
fsFilter->setSortRole(QFileSystemModel::FilePathRole); // now it works
fsModel->fetchMore(fsModel->index(0,0));
QTreeView tree;
tree.setModel(fsFilter); // different sort if use
tree.setModel(fsFilter);
tree.setSortingEnabled(true);
tree.sortByColumn(0, Qt::AscendingOrder);
tree.resize(800, 400);
tree.setColumnWidth(0, 300);
tree.setWindowTitle(QObject::tr("File System Test"));
tree.show();
return app.exec();
}
This works fine, producing this output, except it is not sorted by drive letter:
Running the same code, but setting the treeview model as
tree.setModel(fsModel); // use QFileSystemModel, not QSortFilterProxyModel
results in the following, where the items are sorting the way I want:
I thought this might be a role issue. Here is the output from my qDebug statements:
QFileSystemModel::FilePathRole = "C:/" Qt::DisplayRole = "C:"
QFileSystemModel::FilePathRole = "D:/" Qt::DisplayRole = "Data
(D:)"
QFileSystemModel::FilePathRole = "E:/" Qt::DisplayRole = "Photos
(E:)"
QFileSystemModel::FilePathRole = "F:/" Qt::DisplayRole =
"Archive (F:)"
QFileSystemModel::FilePathRole = "H:/" Qt::DisplayRole = "Old D
(H:)"
QFileSystemModel::FilePathRole = "I:/" Qt::DisplayRole = "Old C
(I:)"
So I included this line
fsFilter->setFilterRole(QFileSystemModel::FilePathRole);
but this did not help.
Solution (corrected in code listing above):
fsFilter->setSortRole(QFileSystemModel::FilePathRole);
You should reimplement virtual function lessThan in FSFilter, by default it sort alphabetically, but in this case you need take into account first and last letters in drive name.

Change QSortFilterProxyModel behaviour for multiple column filtering

We have a QSortFilterProxyModel installed on a QTableView and two (or more) QLineEdit for filtering the view (based on the text of these QLineEdits)
In our view we have a slot that tells us the string of lineedits and the current column that we want. Something like this :
void onTextChange(int index, QString ntext) {
filter.setFilterKeyColumn(index);
filter.setFilterRegExp(QRegExp(ntext, Qt::CaseInsensitive));
}
On the first column we have names in the second we have year of birthday.
Now we enter a year for column 2 (for example 1985). Until now filtering is ok but when we switch to the first lineedit and enter a name (for example john) the previous filtering based on year will reset.
How could we change this behaviour for our custom QSortFilterProxyModel ?
(Actually when we change the filter keycolumn the filtermodel must filter existing view not reset it)
Update...
Based on #Mike's answer :
If you interacting with unknown column count using QMap<int, QRegExp> will help you
You can subclass QSortFilterProxyModel, to make it take two separate filters (one for the name and the other for the year), and override filterAcceptsRow to return true only when both filters are satisfied.
The Qt documentation's Custom Sort/Filter Model Example shows a subclassed QSortFilterProxyModel that can take filters for dates in addition to the main string filter used for searching.
Here is a fully working example on how to make a subclassed QSortFilterProxyModel apply two separate filters for one table:
#include <QApplication>
#include <QtWidgets>
class NameYearFilterProxyModel : public QSortFilterProxyModel{
Q_OBJECT
public:
explicit NameYearFilterProxyModel(QObject* parent= nullptr):
QSortFilterProxyModel(parent){
//general parameters for the custom model
nameRegExp.setCaseSensitivity(Qt::CaseInsensitive);
yearRegExp.setCaseSensitivity(Qt::CaseInsensitive);
yearRegExp.setPatternSyntax(QRegExp::RegExp);
nameRegExp.setPatternSyntax(QRegExp::RegExp);
}
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override{
QModelIndex nameIndex= sourceModel()->index(sourceRow, 0, sourceParent);
QModelIndex yearIndex= sourceModel()->index(sourceRow, 1, sourceParent);
QString name= sourceModel()->data(nameIndex).toString();
QString year= sourceModel()->data(yearIndex).toString();
return (name.contains(nameRegExp) && year.contains(yearRegExp));
}
public slots:
void setNameFilter(const QString& regExp){
nameRegExp.setPattern(regExp);
invalidateFilter();
}
void setYearFilter(const QString& regExp){
yearRegExp.setPattern(regExp);
invalidateFilter();
}
private:
QRegExp nameRegExp;
QRegExp yearRegExp;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//set up GUI
QWidget w;
QVBoxLayout layout(&w);
QHBoxLayout hLayout;
QLineEdit lineEditName;
QLineEdit lineEditYear;
lineEditName.setPlaceholderText("name filter");
lineEditYear.setPlaceholderText("year filter");
lineEditYear.setValidator(new QRegExpValidator(QRegExp("[0-9]*")));
lineEditYear.setMaxLength(4);
hLayout.addWidget(&lineEditName);
hLayout.addWidget(&lineEditYear);
QTableView tableView;
layout.addLayout(&hLayout);
layout.addWidget(&tableView);
//set up models
QStandardItemModel sourceModel;
NameYearFilterProxyModel filterModel;;
filterModel.setSourceModel(&sourceModel);
tableView.setModel(&filterModel);
QObject::connect(&lineEditName, &QLineEdit::textChanged,
&filterModel, &NameYearFilterProxyModel::setNameFilter);
QObject::connect(&lineEditYear, &QLineEdit::textChanged,
&filterModel, &NameYearFilterProxyModel::setYearFilter);
//fill with dummy data
QVector<QString> names{"Danny", "Christine", "Lars",
"Roberto", "Maria"};
for(int i=0; i<100; i++){
QList<QStandardItem*> row;
row.append(new QStandardItem(names[i%names.size()]));
row.append(new QStandardItem(QString::number((i%9)+1980)));
sourceModel.appendRow(row);
}
w.show();
return a.exec();
}
#include "main.moc"
Based on #Hayt's answer and comment. Since you want to have two separate filters on your model, You can have two chained QSortFilterProxyModel(one does the filtering based on the name, and the other does the filtering based on the year using the first filtering model as the source model).
Here is a fully working example on how to have two separate filters for one table:
#include <QApplication>
#include <QtWidgets>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//set up GUI
QWidget w;
QVBoxLayout layout(&w);
QHBoxLayout hLayout;
QLineEdit lineEditName;
QLineEdit lineEditYear;
lineEditName.setPlaceholderText("name filter");
lineEditYear.setPlaceholderText("year filter");
lineEditYear.setValidator(new QRegExpValidator(QRegExp("[0-9]*")));
lineEditYear.setMaxLength(4);
hLayout.addWidget(&lineEditName);
hLayout.addWidget(&lineEditYear);
QTableView tableView;
layout.addLayout(&hLayout);
layout.addWidget(&tableView);
//set up models
QStandardItemModel sourceModel;
QSortFilterProxyModel yearFilterModel;
yearFilterModel.setSourceModel(&sourceModel);
QSortFilterProxyModel nameFilterModel;
//nameFilterModel uses yearFilterModel as source
nameFilterModel.setSourceModel(&yearFilterModel);
//tableView displayes the last model in the chain nameFilterModel
tableView.setModel(&nameFilterModel);
nameFilterModel.setFilterKeyColumn(0);
yearFilterModel.setFilterKeyColumn(1);
nameFilterModel.setFilterCaseSensitivity(Qt::CaseInsensitive);
yearFilterModel.setFilterCaseSensitivity(Qt::CaseInsensitive);
QObject::connect(&lineEditName, &QLineEdit::textChanged, &nameFilterModel,
static_cast<void (QSortFilterProxyModel::*)(const QString&)>
(&QSortFilterProxyModel::setFilterRegExp));
QObject::connect(&lineEditYear, &QLineEdit::textChanged, &yearFilterModel,
static_cast<void (QSortFilterProxyModel::*)(const QString&)>
(&QSortFilterProxyModel::setFilterRegExp));
//fill with dummy data
QVector<QString> names{"Danny", "Christine", "Lars",
"Roberto", "Maria"};
for(int i=0; i<100; i++){
QList<QStandardItem*> row;
row.append(new QStandardItem(names[i%names.size()]));
row.append(new QStandardItem(QString::number((i%9)+1980)));
sourceModel.appendRow(row);
}
w.show();
return a.exec();
}
If you want to connect the 2 inputs with a "and" filter you can simply layer them.
Something like this should work.
QSortFilterProxyModel namefilter;
nameFilter.setFilterKeyColumn(nameColum);
QSortFilterProxyModel yearFilter;
yearFilter.setFilterKeyColumn(yearColumn);
yearFilter.setSourceModel(model);
nameFilter.setSourceModel(&yearFilter);
view.setSource(&nameFilter);
//....
void onTextChange(int index, QString ntext)
{
switch(index)
{
case yearColumn:
yearFilter.setFilterRegExp(QRegExp(ntext, Qt::CaseInsensitive));
break;
case nameColum:
namefilter.setFilterRegExp(QRegExp(ntext, Qt::CaseInsensitive));
break;
}
}
I know this is an old thread, but here is a more generic version of the QSortFilterProxyModel with a multicolumn filter implementation. I saw someone's comment (Mike) that eluded to a solution like this, but I didn't see any code examples for it.
This design allows you to indicate weather the model is multifilter when creating an SortFilterProxyModel object. The reason for this is so that you can add other custom behavior without having to create a separate QSortFilterProxyModel subclass. In other words, if you create other custom behaviors by overriding QSortFilterProxyModel functions in this manner, you can pick and choose which custom sort/filter behaviors you want, and which standard sort/filter behaviors you want, for a given object. Obviously if you don't need or want that kind of flexibility with your subclass you can make it your own with a few small adjustments.
Header:
class SortFilterProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
explicit SortFilterProxyModel(bool multiFilterModel, QObject *parent = nullptr);
void setMultiFilterRegularExpression(const int &column, const QString &pattern);
void clearMultiFilter();
protected:
virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
private:
QMap<int, QRegularExpression> m_multiFilterMap;
bool m_multiFilterModel = false;
signals:
};
Implementation:
#include "sortfilterproxymodel.h"
//The constructor takes one additional argument (multiFilterModel) that
//will dictate filtering behavior. If multiFilterModel is false the
//setMultiFilterRegularExpression and clearMultifilter will do nothing.
SortFilterProxyModel::SortFilterProxyModel(bool multiFilterModel, QObject *parent) : QSortFilterProxyModel(parent)
{
m_multiFilterModel = multiFilterModel;
}
//This loads the QMap with the column numbers and their corresponding filters.
//This member function that should be called from your main to filter model.
void SortFilterProxyModel::setMultiFilterRegularExpression(const int &column, const QString &pattern)
{
if(!m_multiFilterModel) //notifying that this does nothing and returning
{
qDebug() << "Object is not a multiFilterModel!";
return;
}
QRegularExpression filter;
filter.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
filter.setPattern(pattern);
m_multiFilterMap.insert(column, filter);
invalidateFilter(); //this causes filterAcceptsRow to run
}
//This will effectively unfilter the model by making the pattern for all
//existing regular expressions in the QMap to an empty string, and then invalidating the filter.
//This member function should be called from main to clear filter.
void SortFilterProxyModel::clearMultiFilter()
{
if(!m_multiFilterModel) //notifying that this does nothing and returning
{
qDebug() << "Object is not a multiFilterModel!";
return;
}
QMap<int, QRegularExpression>::const_iterator i = m_multiFilterMap.constBegin();
while(i != m_multiFilterMap.constEnd())
{
QRegularExpression blankExpression("");
m_multiFilterMap.insert(i.key(), blankExpression);
i++;
}
invalidateFilter(); //this causes filterAcceptsRow to run
}
//This checks to see if the model should be multifiltered, else it will
//work like the standard QSortFilterProxyModel.
bool SortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
if(m_multiFilterModel)
{
QMap<int, QRegularExpression>::const_iterator i = m_multiFilterMap.constBegin();
while(i != m_multiFilterMap.constEnd())
{
QModelIndex index = sourceModel()->index(source_row, i.key(), source_parent);
QString indexValue = sourceModel()->data(index).toString();
if(!indexValue.contains(i.value()))
{
return false; //if a value doesn't match returns false
}
i++;
}
return true; //if all column values match returns true
}
//This is standard QSortFilterProxyModel behavoir. It only runs if object is not multiFilterModel
QModelIndex index = sourceModel()->index(source_row, filterKeyColumn(), source_parent);
QString indexValue = sourceModel()->data(index).toString();
return indexValue.contains(filterRegularExpression());
}

How to add different types of delegates in QTreeView

I want to create same kind of QTreeView (not QTreeWidget) structure as shown in attached figure.. This is Property Editor of QT.
I am using QT-4.6
On 2nd column, depending on different condition, I can have either a spin box, or a drop down or a checkbox or text edit... and so on...
Please guide me on how to set different delegates in different cells of a particular column.
From docs, it is evident that there is no straight away API for setting delegate on a cell (rather is available for full widget or a row or a column).
All QAbstractItemDelegate methods, like createEditor or paint, have a model index as one of their parameters. You can access model data using that index and create an appropriate delegate widget. When you create your model you should set some value to every item that will be used to distinguish its type.
An example:
enum DelegateType
{
DT_Text,
DT_Checkbox,
DT_Combo
}
const int MyTypeRole = Qt::UserRole + 1;
QStandardItemModel* createModel()
{
QStandardItemModel *model = new QStandardItemModel;
QStandardItem *item = new QStandardItem;
item->setText("Hello!");
item->setData(DT_Checkbox, MyTypeRole);
model->appendRow(item);
return model;
}
QWidget* MyDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
int type = index.data(MyTypeRole).toInt();
// this is a simplified example
switch (type)
{
case DT_Text:
return new QLinedEdit;
case DT_Checkbox:
return new QCheckBox;
case DT_Combo:
return new QComboBox;
default:
return QItemDelegate::createEditor(parent, option, index);
}
}
#hank This is in response to your last comment... Do you see any flaw in it ?
MyItem* item2 = new MyItem(second);
item2->setData(delType, **MyTypeRole**);
if(delType == DT_Combo)
{
QString str1, str2, str3;
QStringList abc ;
abc << ("1" + str1.setNum(counter) ) << ("2" + str2.setNum(counter) )<< ( "3" + str3.setNum(counter) );
item2->setData(abc, MyTypeRole1);
}
QWidget* MyDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
int type = index.data(MyTypeRole).toInt();
// this is a simplified example
switch (type)
{
case DT_Text:
return new QLinedEdit;
case DT_Combo:
{
QComboBox* cb = new QComboBox(parent);
QStringList entries - index.data(MyTypeRole1).toStringList();
cb->addItems(entries)
return cb;
}
On different item2, I dynamically create entries with a counter variable that is different everytime it comes here...
Here, different combo boxes display different entries.
Does the approach looks fine to you ?

Drawing a check mark in Qt

I am working with simple QTreeView. Each row of tree is a specific class inherited from one class EditorRow.
EditorRow has these functions:
virtual QVariant data(ColumnIndexEnum index, int role = Qt::DisplayRole) const = 0;
virtual void setData(const QVariant& data, ColumnIndexEnum index, int role = Qt::UserRole);
virtual QWidget* getEditor(QWidget* parent) const;
Each row has its specific widget, which is shown in the right column when selecting that row. When the row is not selected data function returns appropriate value for each row(f.e. the value which was chosen in the QComboBox).
But in case of row, which's widget is QCheckBox, I need to draw a checked(or unchecked) checkbox when the row is not selected.
I have tried to use Decoration role like this:
if(Qt::DecorationRole == role)
{
if(ValueColumn == index)
{
QStyle* style = QApplication::style();
QStyleOptionButton opt;
opt.state |= QStyle::State_Enabled;
if(isChecked())
opt.state = QStyle::State_On;
else
opt.state = QStyle::State_Off;
const int indicatorWidth = style->pixelMetric(QStyle::PM_IndicatorWidth, &opt);
const int indicatorHeight = style->pixelMetric(QStyle::PM_IndicatorHeight, &opt);
const int listViewIconSize = indicatorWidth;
const int pixmapWidth = indicatorWidth;
const int pixmapHeight = qMax(indicatorHeight, listViewIconSize);
opt.rect = QRect(0, 0, indicatorWidth, indicatorHeight);
QPixmap pixmap = QPixmap(pixmapWidth, pixmapHeight);
pixmap.fill(Qt::transparent);
{
QPainter painter(&pixmap);
QCheckBox cb;
cb.setLayoutDirection(Qt::RightToLeft);
style->drawPrimitive(QStyle::PE_IndicatorCheckBox, &opt, &painter, &cb);
}
return QIcon(pixmap);
}
}
It actually works, but the icon is shown always, even when the row is selected.
I think it is because of DecorationRole.
Do You have any ideas how to handle this problem ?
Thank You.

Resources