Algorithm for splitting large amount of text data - c++

I have a text area which i populate dynamically(to be specific I have a QPlainTextEdit in Qt, but its not important for algorithm suggestion).
Now problem is sometimes Large amounts of data comes and as more data comes in my application becomes heavy,since all the text data is in main memory.
So I thought of the following. We can use a file for storing all the text data and display only limited amount of data dynamically, but at the same time I have to illusion the user that the data size is that of the file, by creating scroll events that trigger when new lines comes.
Is there any standard algorithm for such problem?

Subclass QAbstractListModel implement cache there.
When cell value is read you are fetching data from cache and update it if value is not present in cache.
Tweak QTableView, by altering delegate to achieve needed visualization of cells. Note you have to use QTableView since other QAbstractItemViews have broken items recycling and they don't handle very large models well (QTableView doesn't have such issue).
Some time ego I've wrote hex viewer of large files and tested that with file size 2GB and it was working perfectly.
Ok, I found my old code which could be a good example:
#include <QAbstractTableModel>
class LargeFileCache;
class LageFileDataModel : public QAbstractTableModel
{
Q_OBJECT
public:
explicit LageFileDataModel(QObject *parent);
// QAbstractTableModel
int rowCount(const QModelIndex &parent) const;
int columnCount(const QModelIndex &parent) const;
QVariant data(const QModelIndex &index, int role) const;
signals:
public slots:
void setFileName(const QString &fileName);
private:
LargeFileCache *cachedData;
};
// ----- cpp file -----
#include "lagefiledatamodel.h"
#include "largefilecache.h"
#include <QSize>
static const int kBytesPerRow = 16;
LageFileDataModel::LageFileDataModel(QObject *parent)
: QAbstractTableModel(parent)
{
cachedData = new LargeFileCache(this);
}
int LageFileDataModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return (cachedData->FileSize() + kBytesPerRow - 1)/kBytesPerRow;
}
int LageFileDataModel::columnCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return kBytesPerRow;
}
QVariant LageFileDataModel::data(const QModelIndex &index, int role) const
{
if (index.parent().isValid())
return QVariant();
if (index.isValid()) {
if (role == Qt::DisplayRole) {
qint64 pos = index.row()*kBytesPerRow + index.column();
if (pos>=cachedData->FileSize())
return QString();
return QString("%1").arg((unsigned char)cachedData->geByte(pos), 2, 0x10, QChar('0'));
} else if (role == Qt::SizeHintRole) {
return QSize(30, 30);
}
}
return QVariant();
}
void LageFileDataModel::setFileName(const QString &fileName)
{
beginResetModel();
cachedData->SetFileName(fileName);
endResetModel();
}
Here is a cache implementation:
class LargeFileCache : public QObject
{
Q_OBJECT
public:
explicit LargeFileCache(QObject *parent = 0);
char geByte(qint64 pos);
qint64 FileSize() const;
signals:
public slots:
void SetFileName(const QString& filename);
private:
static const int kPageSize;
struct Page {
qint64 offset;
QByteArray data;
};
private:
int maxPageCount;
qint64 fileSize;
QFile file;
QQueue<Page> pages;
};
// ----- cpp file -----
#include "largefilecache.h"
const int LargeFileCache::kPageSize = 1024*4;
LargeFileCache::LargeFileCache(QObject *parent)
: QObject(parent)
, maxPageCount(1024)
, fileSize(0)
{
}
char LargeFileCache::geByte(qint64 pos)
{
// largefilecache
if (pos>=fileSize)
return 0;
for (int i=0, n=pages.size(); i<n; ++i) {
int k = pos - pages.at(i).offset;
if (k>=0 && k< pages.at(i).data.size()) {
pages.enqueue(pages.takeAt(i));
return pages.back().data.at(k);
}
}
Page newPage;
newPage.offset = (pos/kPageSize)*kPageSize;
file.seek(newPage.offset);
newPage.data = file.read(kPageSize);
pages.push_front(newPage);
while (pages.count()>maxPageCount)
pages.dequeue();
return newPage.data.at(pos - newPage.offset);
}
qint64 LargeFileCache::FileSize() const
{
return fileSize;
}
void LargeFileCache::SetFileName(const QString &filename)
{
file.close();
pages.clear();
file.setFileName(filename);
file.open(QFile::ReadOnly);
fileSize = file.size();
}
I wrote cache manually since I was handling a row data, but you can use QCache which should help you do a caching logic.

Using mmap only addresses how you may read the file while only having pieces of it in memory. It doesn't address how the edit control would only have pieces at a time.
I have to think that any such system would be fairly specific to the text editing widget involved. In this case, you would either need to figure out how to extend QPlainTextEdit with the desired functionality, or make a new text editing widget (possible forking an existing one). There are numerous text editing widgets available as open source that could be used as a starting point.
I've been assuming so far that you want to edit the text in this large file. If you are only using QPlainTextEdit as a read-only viewer, then that writing your own that is only a large text stream reading widget may be much easier than extending an existing editor widget.

It's my two cents,
When I googled the similar question, I've found the answer from Fast textfile reading in c++
In short, Memory mapped files in the boost Lib. might be helpful not only for performance but also for handling a large amount of data.
The sample in the link, I could check the number of lines and getting data from Lib.
Good Luck

Related

QAbstractTableModel and emit dataChanged for a single row

I derived a model from QAbstractTableModel and now I want to notify, that the data of a whole row has been changed. If for example the data of a row with index 5 is changed (4 columns), than using the following code works as expected.
emit dataChanged(index(5,0), index(5, 0));
emit dataChanged(index(5,1), index(5, 1));
emit dataChanged(index(5,2), index(5, 2));
emit dataChanged(index(5,3), index(5, 3));
But if I try to achieve the same with only one emit, ALL columns of ALL rows in the view are updated.
emit dataChanged(index(5, 0), index(5, 3));
What I am doing wrong here?
Minimal example (C++11, QTCreator 4.7.1, Windows 10 (1803), 64 Bit)
demo.h
#pragma once
#include <QAbstractTableModel>
#include <QTime>
#include <QTimer>
class Demo : public QAbstractTableModel
{
Q_OBJECT
QTimer * t;
public:
Demo()
{
t = new QTimer(this);
t->setInterval(1000);
connect(t, SIGNAL(timeout()) , this, SLOT(timerHit()));
t->start();
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
{
int c = index.column();
if (role == Qt::DisplayRole)
{
QString strTime = QTime::currentTime().toString();
if (c == 0) return "A" + strTime;
if (c == 1) return "B" + strTime;
if (c == 2) return "C" + strTime;
if (c == 3) return "D" + strTime;
}
return QVariant();
}
int rowCount(const QModelIndex &) const override { return 10; }
int columnCount(const QModelIndex &) const override { return 4; }
private slots:
void timerHit()
{
//Works
emit dataChanged(index(5,0), index(5, 0));
emit dataChanged(index(5,1), index(5, 1));
emit dataChanged(index(5,2), index(5, 2));
emit dataChanged(index(5,3), index(5, 3));
//emit dataChanged(index(5,0), index(5, 3)); // <-- Doesn't work
}
};
main.cpp
#include "demo.h"
#include <QApplication>
#include <QTreeView>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTreeView dataView;
Demo dataModel{};
dataView.setModel( &dataModel );
dataView.show();
return a.exec();
}
I think the problem lies with certain assumptions you're making with regard the behaviour of QTreeView when the QAbstractItemModel::dataChanged signal is emitted.
Specifically, you assume that the view will only invoke QAbstractItemModel::data on those indexes that are specified in the signal. That's not necessarily the case.
Looking at the source for QAbstractItemView::dataChanged (Qt 5.11.2) you'll see...
void QAbstractItemView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
{
Q_UNUSED(roles);
// Single item changed
Q_D(QAbstractItemView);
if (topLeft == bottomRight && topLeft.isValid()) {
const QEditorInfo &editorInfo = d->editorForIndex(topLeft);
//we don't update the edit data if it is static
if (!editorInfo.isStatic && editorInfo.widget) {
QAbstractItemDelegate *delegate = d->delegateForIndex(topLeft);
if (delegate) {
delegate->setEditorData(editorInfo.widget.data(), topLeft);
}
}
if (isVisible() && !d->delayedPendingLayout) {
// otherwise the items will be update later anyway
update(topLeft);
}
} else {
d->updateEditorData(topLeft, bottomRight);
if (isVisible() && !d->delayedPendingLayout)
d->viewport->update();
}
#ifndef QT_NO_ACCESSIBILITY
if (QAccessible::isActive()) {
QAccessibleTableModelChangeEvent accessibleEvent(this, QAccessibleTableModelChangeEvent::DataChanged);
accessibleEvent.setFirstRow(topLeft.row());
accessibleEvent.setFirstColumn(topLeft.column());
accessibleEvent.setLastRow(bottomRight.row());
accessibleEvent.setLastColumn(bottomRight.column());
QAccessible::updateAccessibility(&accessibleEvent);
}
#endif
d->updateGeometry();
}
The important point is that this code behaves differently depending on whether or not the signal specifies a single QModelIndex -- e.g. topLeft is the same as bottomRight. If they are the same then the view tries to ensure that only that model index is updated. However, if multiple model indexes are specified then it will invoke...
d->viewport->update();
which will, presumably, result in the data for all visible model indexes being queried.
Since your implementation of Demo::data always returns new data based on the current time you will see the entire visible part of the view update giving the impression that the dataChanged signal was emitted for all rows and columns.
So the fix is really to make your data model more ``stateful'' -- it needs to keep track of values rather than simply generating them on demand.
Not sure whether this is what you're looking for but I'll put it up anyways.
Even using emit dataChanged(...), you would still see that clicks/selection on rows will cause them to self-update (doing this from a Mac, so might be different).
Instead of using the QAbstractItemModel::dataChanged signal, I will be using the QAbstractItemModel::setData() function.
This is my implementation of demo.h
#pragma once
#include <QAbstractTableModel>
#include <QTime>
#include <QTimer>
class Demo : public QAbstractTableModel
{
Q_OBJECT
public:
Demo()
{
int cCount = columnCount(index(0, 0));
int rCount = rowCount(index(0, 0));
// populate model data with *static* values
QString strTime = QTime::currentTime().toString();
QStringList temp;
for (int j = 0; j < cCount; j++)
temp.append(strTime);
for (int i = 0; i < rCount; i++)
demoModelData.append(temp);
// nothing new here
t = new QTimer(this);
t->setInterval(1000);
connect(t, SIGNAL(timeout()) , this, SLOT(timerHit()));
t->start();
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
{
// tells the *view* what to display
// if this was dynamic (e.g. like your original strTime implementation)
// then the view (QTreeView in main.cpp) will constantly update
if (role == Qt::DisplayRole)
return demoModelData.at(index.row()).at(index.column()); // retrieve data from model
return QVariant();
}
// reimplemented from QAbstractTableModel
virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::DisplayRole) override
{
if (role == Qt::DisplayRole)
{
demoModelData[index.row()][index.column()] = value.toString(); // set the new data
emit dataChanged(index, index); // explicitly emit dataChanged signal, notifies TreeView to update by
// calling this->data(index, Qt::DisplayRole)
}
return true;
}
int rowCount(const QModelIndex &) const override { return 10; }
int columnCount(const QModelIndex &) const override { return 4; }
private slots:
void timerHit()
{
QString strTime = QTime::currentTime().toString();
setData(index(5, 0), QVariant(strTime), Qt::DisplayRole); // only changes index at (row = 5, col = 0)
}
private:
QTimer *t;
QList<QStringList> demoModelData; // stores the table model's data
};
Since the class is a "model", there should be some way of storing/retrieving data for display. Here, I've used a QList<QStringList>, but you can store data in other ways that suit you as well (e.g. a tree, QVector, QMap).
This is a genuine efficiency bug in Qt.
The Qt project is no longer accepting changes to Qt 5, so I made the change and pushed it to my fork on GitHub. You can see a fix for the issue you've encountered here.
If you want to build your own copy of 5.15.2, you may be interested in my other fixes.

QTableView: endMoveRows in model resets horizontal header sizes

I'm trying to implement drag'n'drop reordering of rows in a QTableView.
The operation needs to actually reorder the underlying data, therefore reordering the vertical header rows doesn't seem like a good solution.
I instead opted for the underlying model to handle this by providing the row indices in its mimeData function and accepting this list in dropMimeData. In the dropMimeData function, I use the beginMoveRows and endMoveRows to move individual rows visually, in addition to moving the underlying data. This approach seems to work, with only one shortcoming: as soon as I call endMoveRows, the QTableView's horizontal header resets all its section sizes to defaults.
Am I missing something I need to do, or is this a bug in Qt (or even intended behavior)? Is there any other way to handle drag'n'drop reordering?
Here is a simplified piece of code that shows the same behavior:
class Model: public QAbstractTableModel
{
public:
Model():
m_Items({"A", "B", "C", "D", "E"})
{
}
virtual QVariant data(const QModelIndex & a_Parent, int a_Role) const override
{
return QString("%1 - %2").arg(m_Items[a_Parent.row()]).arg(a_Parent.column());
}
virtual int rowCount(const QModelIndex & a_Parent) const override
{
return m_Items.count();
}
virtual int columnCount(const QModelIndex & a_Parent) const override
{
return 4;
}
void moveNow()
{
// Moves the last row as the 3rd row:
auto count = m_Items.count();
beginMoveRows(QModelIndex(), count, count, QModelIndex(), 2);
m_Items.insert(2, m_Items.last());
m_Items.pop_back();
endMoveRows();
}
protected:
QStringList m_Items;
};
static Model g_Model;
MainWindow::MainWindow(QWidget * parent):
QMainWindow(parent),
ui(new Ui::MainWindow)
{
// "ui" created by QtCreator, has a "tableView" and a "pushButton"
ui->setupUi(this);
ui->tableView->setModel(&g_Model);
ui->tableView->horizontalHeader()->resizeSection(0, 200);
ui->tableView->horizontalHeader()->resizeSection(1, 100);
ui->tableView->horizontalHeader()->resizeSection(2, 50);
ui->tableView->horizontalHeader()->resizeSection(3, 80);
connect(ui->pushButton, &QPushButton::clicked,
[this]()
{
// Outputs 200 on first click
qDebug() << ui->tableView->horizontalHeader()->sectionSize(0);
g_Model.moveNow();
// Outputs 100
qDebug() << ui->tableView->horizontalHeader()->sectionSize(0);
}
);
}
I found out that calling emit layoutAboutToBeChanged() just before the move operation fixes the issue:
void moveNow()
{
emit layoutAboutToBeChanged();
auto count = m_Items.count();
beginMoveRows(QModelIndex(), count, count, QModelIndex(), 2);
m_Items.insert(2, m_Items.last());
m_Items.pop_back();
endMoveRows();
}
This seems to contradict the documentation for beginMoveRows, which indicates that it is a replacement for calling this function. Also there must not be a matching emit layoutChanged() after endMoveRows, otherwise the columns are, again, reset.
This all together leads me to believe it's an actual bug in Qt.

C++/QML: ListView is not updated on dataChanged signal from QAbstractListModel

I am trying to write a QML Gui for a large dynamic C/Fortran simulation. The data I want to display is stored in Fortran Common blocks and updated on fixed time steps. My problem is that QML ListView does not refresh when the dataChanged signal is emitted after each time step, although the signal is received by the Gui (test is in the code below).
I am probably missing out something really obvious because when I flick my ListView down and up again, the displayed data is updated and correct (I guess because the QML engine re-renders elements when they get "out of sight" and back in again). So the only thing that does not work is that the ListView gets updated every time the dataChanged signal is received and not only when it is re-rendered. Below is a more detailed description of my approach and the relevant code parts.
Each simulation entity has several attributes (alive, position...), so I decided to create a ListModel containing a DataObject for each entity. This is the corresponding header file (the actual simulation data is declared as extern structs in "interface.h", so I can access it via pointer):
"acdata.h"
#include <QtCore>
#include <QObject>
#include <QtGui>
extern "C" {
#include "interface.h"
}
class AcDataObject : public QObject
{
Q_OBJECT
public:
explicit AcDataObject(int id_, int *pac_live, double *pac_pos_x, QObject *parent = 0) :
QObject(parent)
{
entity_id = id_;
ac_live = pac_live;
ac_pos_x = pac_pos_x;
}
int entity_id;
int *ac_live;
double *ac_pos_x;
};
class AcDataModel : public QAbstractListModel
{
Q_OBJECT
public:
enum RoleNames {
IdRole = Qt::UserRole,
LiveRole = Qt::UserRole + 1,
PosXRole = Qt::UserRole + 2
};
explicit AcDataModel(QObject *parent = 0);
virtual int rowCount(const QModelIndex &parent) const;
virtual QVariant data(const QModelIndex &index, int role) const;
Q_INVOKABLE Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE;
void do_update();
protected:
virtual QHash<int, QByteArray> roleNames() const;
private:
QList<AcDataObject*> data_list;
QHash<int, QByteArray> m_roleNames;
QModelIndex start_index;
QModelIndex end_index;
signals:
void dataChanged(const QModelIndex &start_index, const QModelIndex &end_index);
};
Like the header, the .cpp file is also adapted from what you can find in the Qt5 Cadaques Book here, except that my constructor iterates over all simulation entities to set the pointers. Additionally, there is the do_update function that emits the dataChanged signal for the whole list.
"acdata.cpp"
#include "acdata.h"
AcDataModel::AcDataModel(QObject *parent) :
QAbstractListModel(parent)
{
m_roleNames[IdRole] = "entity_id";
m_roleNames[LiveRole] = "ac_live";
m_roleNames[PosXRole] = "ac_pos_x";
for (int i = 0; i < MAX_ENTITIES; i++) // MAX_ENTITIES is defined in interface.h
{
AcDataObject *data_object = new AcDataObject( i,
&fdata_ac_.ac_live[i], // fdata_ac_ is the C struct/Fortran common block defined in interface.h
&fdata_ac_.ac_pos_x[i] );
data_list.append(data_object);
}
}
int AcDataModel::rowCount(const QModelIndex &parent) const {
Q_UNUSED(parent);
return data_list.count();
}
QVariant AcDataModel::data(const QModelIndex &index, int role) const
{
int row = index.row();
if(row < 0 || row >= data_list.count()) {
return QVariant();
}
const AcDataObject *data_object = data_list.at(row);
switch(role) {
case IdRole: return data_object->entity_id;
case LiveRole: return *(data_object->ac_live);
case PosXRole: return *(data_object->ac_pos_x);
}
return QVariant();
}
QHash<int, QByteArray> AcDataModel::roleNames() const
{
return m_roleNames;
}
void AcDataModel::do_update() {
start_index = createIndex(0, 0);
end_index = createIndex((data_list.count() - 1), 0);
dataChanged(start_index, end_index);
}
Qt::ItemFlags AcDataModel::flags(const QModelIndex &index) const
{
if (!index.isValid()) {return 0;}
return Qt::ItemIsEditable | QAbstractItemModel::flags(index);
}
When the simulation is running, do_update() is called every second. I have created a test Gui with a ListView and exposed my model to it with:
Excerpt from "threadcontrol.cpp"
acdata = new AcDataModel();
viewer = new QtQuick2ApplicationViewer();
viewer->rootContext()->setContextProperty("acdata", acdata);
viewer->setMainQmlFile(QStringLiteral("../lib/qml_gui/main.qml"));
viewer->showExpanded();
(This code is part of a larger file that controls the different threads. I am quite sure the rest is not relevant to the actual problem and this question is getting really long...)
So finally there is main.qml. It contains a list with MAX_ENTITIES elements and each elements holds text fields to display my data. I have also added a Connections element to check if the dataChanged signal is received by the Gui.
"main.qml"
ListView {
id: listviewer
model: acdata
delegate: Rectangle {
/* ... some formatting stuff like height etc ... */
Row {
anchors.fill: parent
Text {
/* ... formatting stuff ... */
text: model.entity_id
}
Text {
/* ... formatting stuff ... */
text: model.ac_live
}
Text {
/* ... formatting stuff ... */
text: model.ac_pos_x
}
}
}
Connections {
target: listviewer.model // EDIT: I drew the wrong conclusions here, see text below!
onDataChanged: {
console.log("DataChanged received")
}
}
}
When running the simulation, the "DataChanged received" message is printed every second.
Edit: I was connecting to the ListModel and not to the ListView here, although the ListView has to receive the dataChanged signal. As the console log does not work when connecting to listviewer, I am probably missing the connection between listView and dataChanged signal. However, I think this should work automatically when implementing the dataChanged signal?
Additional information: I have found a similar problem here with Qt Map and it actually seemed to be a bug that was fixed in Qt 5.6. However, running qmake with Qt 5.7 did not fix my problem.
You mustn't declare the dataChanged() signal in your class, because you want to emit the signal AbstractItemModel::dataChanged(). If you re-declare it you add a comleptely new and different Signal that is not connected anywhere. If you remove the declaration in acdata.h everything should work fine.

Sample code for dialog like Qt Creator Options tab

I would like to build up dialog same as Qt Creator "Options" tab that left side page titles with scroll bar and detailed page on the right side.
It would be really helpful if there were code samples or sample applications for reference.
Qt Creator Source Code
Qt Creator has it source code both in Gitorious and in GitHub. But because Qt Creator is such a large and complex project, it can be overwhelming finding its sub parts.
The Github version is very searchable. It ends up that the source related to the nice options page in Qt Creator uses the IOptionsPage as the base class for any of the pages that show up in the Options dialog.
https://github.com/qtproject/qt-creator/search?utf8=%E2%9C%93&q=ioptionspage&type=Code
The ioptionspage.cpp has all the comments explaining the purpose of the different slots.
https://github.com/qtproject/qt-creator/blob/9926fc2ab12ccaa02b7f03b416c54cd58ef30b31/src/plugins/coreplugin/dialogs/ioptionspage.cpp
Basically for Qt Creators options page, it has an interface that is used by several different sub modules.
https://github.com/qtproject/qt-creator/blob/9926fc2ab12ccaa02b7f03b416c54cd58ef30b31/src/plugins/coreplugin/dialogs/ioptionspage.h
#ifndef IOPTIONSPAGE_H
#define IOPTIONSPAGE_H
#include <coreplugin/id.h>
#include <QIcon>
#include <QObject>
#include <QStringList>
namespace Core {
class CORE_EXPORT IOptionsPage : public QObject
{
Q_OBJECT
public:
IOptionsPage(QObject *parent = 0);
virtual ~IOptionsPage();
Id id() const { return m_id; }
QString displayName() const { return m_displayName; }
Id category() const { return m_category; }
QString displayCategory() const { return m_displayCategory; }
QIcon categoryIcon() const { return QIcon(m_categoryIcon); }
virtual bool matches(const QString &searchKeyWord) const;
virtual QWidget *widget() = 0;
virtual void apply() = 0;
virtual void finish() = 0;
protected:
void setId(Id id) { m_id = id; }
void setDisplayName(const QString &displayName) { m_displayName = displayName; }
void setCategory(Id category) { m_category = category; }
void setDisplayCategory(const QString &displayCategory) { m_displayCategory = displayCategory; }
void setCategoryIcon(const QString &categoryIcon) { m_categoryIcon = categoryIcon; }
Id m_id;
Id m_category;
QString m_displayName;
QString m_displayCategory;
QString m_categoryIcon;
mutable bool m_keywordsInitialized;
mutable QStringList m_keywords;
};
/*
Alternative way for providing option pages instead of adding IOptionsPage
objects into the plugin manager pool. Should only be used if creation of the
actual option pages is not possible or too expensive at Qt Creator startup.
(Like the designer integration, which needs to initialize designer plugins
before the options pages get available.)
*/
class CORE_EXPORT IOptionsPageProvider : public QObject
{
Q_OBJECT
public:
IOptionsPageProvider(QObject *parent = 0) : QObject(parent) {}
Id category() const { return m_category; }
QString displayCategory() const { return m_displayCategory; }
QIcon categoryIcon() const { return QIcon(m_categoryIcon); }
virtual QList<IOptionsPage *> pages() const = 0;
virtual bool matches(const QString & /* searchKeyWord*/) const = 0;
protected:
void setCategory(Id category) { m_category = category; }
void setDisplayCategory(const QString &displayCategory) { m_displayCategory = displayCategory; }
void setCategoryIcon(const QString &categoryIcon) { m_categoryIcon = categoryIcon; }
Id m_category;
QString m_displayCategory;
QString m_categoryIcon;
};
} // namespace Core
#endif // IOPTIONSPAGE_H
The search box uses an index the all the titles/labels of the children of each options page that gets added.
bool Core::IOptionsPage::matches(const QString &searchKeyWord) const
{
if (!m_keywordsInitialized) {
IOptionsPage *that = const_cast<IOptionsPage *>(this);
QWidget *widget = that->widget();
if (!widget)
return false;
// find common subwidgets
foreach (const QLabel *label, widget->findChildren<QLabel *>())
m_keywords << label->text();
foreach (const QCheckBox *checkbox, widget->findChildren<QCheckBox *>())
m_keywords << checkbox->text();
foreach (const QPushButton *pushButton, widget->findChildren<QPushButton *>())
m_keywords << pushButton->text();
foreach (const QGroupBox *groupBox, widget->findChildren<QGroupBox *>())
m_keywords << groupBox->title();
// clean up accelerators
QMutableStringListIterator it(m_keywords);
while (it.hasNext())
it.next().remove(QLatin1Char('&'));
m_keywordsInitialized = true;
}
foreach (const QString &keyword, m_keywords)
if (keyword.contains(searchKeyWord, Qt::CaseInsensitive))
return true;
return false;
}
Finding the rest of the components of the original dialog may take some time, but it is doable.
Included Example
When in Qt Creator > Welcome (tab) > Examples, the best one for a complex settings dialog is probably:
Tab Dialog Example
http://doc.qt.io/qt-5/qtwidgets-dialogs-tabdialog-example.html
Persistent Settings
QSettings is probably the best bet for storing settings. Other options include XML, and JSON. Qt 5 has a great implementation of JSON.
Hope that helps.

QAbstractTableModel retrieve custom object on data changed

I have recently picked up Qt again, and started refreshing my memory.
Creating a custom data model for a table was easy enough.
Now I am trying to retrieve the selected data.
Take note that I use custom data objects.
Example of my custom model:
platform.h
class Platform
{
public:
Platform();
Platform(QString name);
QString getName();
void setName(QString name);
private:
QString m_name;
};
Very simple data structure for testing purposes.
I then implemented a QAbstractTableModel, the Data() method looks like this:
platformmodel.cpp
QVariant PlatformModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (index.row() >= m_platforms.size() || index.row() < 0)
return QVariant();
if (role == Qt::DisplayRole) {
Platform platform = m_platforms.at(index.row());
qDebug() << platform.getName();
return platform.getName();
}
return QVariant();
}
What I understand from this code is, that for the selectable items, a String is always returned, instead of a platform object.
For displaying, this works fine, I see the actual objects in the view.
Now I want to select the actual object from the model, and not just a QString.
So the method body would be something like:
void MainWindow::selectionChangedSlot(const QItemSelection &, const QItemSelection &)
{
//get the text of the selected item
const QModelIndex index = ui->lvPlatforms->selectionModel()->currentIndex();
Platform selectedPlatform = index.data();//This returns a QVariant and will fail at compile time, but I want to achieve something along this line.
setWindowTitle(selectedPlatform.getName());
}
P.s. Maybe I am trying to search on the wrong thing, I can find examples that use custom objects, but none talk about retrieving the selected item.
There has to be a better way then retreiving the string, then looping trough the list of platforms and comparing the name to the selected item.. If i have a big list, having to loop trough each item and do string comparison is not very efficient.
I hope my problem is clear enough. If something important lacks, let me know so I can edit my example.
EDIT
I tried Q_DECLARE_METATYPE(Platform);
And yes it works, it makes it possible to store it in a QVariant,
the problem is, since for displaying, a String is always expected, or 9/10 times anyway.
So far it seems impossible to have both text display AND get the full platform object from the selection model(i can do both individually.. pretty useless..)
You can create custom type compatible with QVariant using the macro Q_DECLARE_METATYPE.
If you declare your class as a metatype, you can store it in a QVariant and extract it with a cast.
Here an example that show how to create a custom delegate which can display data from a custom class using QVariant :
class Data {
private:
QString name;
int value;
public:
Data() : name(""), value(-1){}
Data( QString n, int v ) : name(n), value(v){}
QString text() {
return QString( "Test %1 - %2" ).arg( name ).arg( value );
}
};
Q_DECLARE_METATYPE( Data )
class Delegate : public QStyledItemDelegate {
protected:
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
Data d = index.data().value<Data>();
painter->drawText( option.rect, d.text() );
}
};
int main( int argc, char **argv) {
QApplication app(argc, argv, true);
QVariant var0, var1, var2;
var0.setValue(Data( "Item A", 0 ));
var1.setValue(Data( "Item B", 1 ));
var2.setValue(Data( "Item C", 2 ));
QListView *view = new QListView();
QStandardItemModel model(3, 1);
model.setData( model.index( 0, 0 ), var0 );
model.setData( model.index( 1, 0 ), var1 );
model.setData( model.index( 2, 0 ), var2 );
view->setModel( &model );
view->show();
view->setItemDelegate( new Delegate() );
return app.exec();
}