QSortFilterProxyModel: crash sometimes when adding rows from the source model - qt

I am writing a Qt application that includes a table view which displays the tasks performed by different persons. There is a column per person, and a row per day. The view is initialized with 26 rows from today minus 10 days to today +15 days. App overview When the vertical scrollbar is at min or max and that the user makes an action to move up/down, 10 new rows (i.e. days) are added on top/bottom. The problem is that sometimes it crashes at that time because of some internal QPersistentModelIndex issues, but only since I added a QSortFilterProxyModel between the source model and the view to display only the days where someone has a task to do.
The function calls propagate as follows:
the view (of type DashboardTableView) emits the signal "wantToGoMoreUp" or "wantToGoMoreDown"
-> DashboardWidget::slotAskDataUp or slotAskDataDown
-> DashboardTableModel::addDaysOnTop or addDaysOnBottom
I tried:
with a custom model instead of a QSortFilterProxyModel (by reimplementing "filterAcceptsRow")
to add "emit layoutAboutToBeChanged()" and "emit layoutChanged()" around rows additions/removals
... but with no success, it still crashes.
Sometimes the crash occurs after many manipulations (I was not able to understand what is the succession of events that makes it crash). The possible manipulations are:
scrolling of course (mouse wheel, mouse click on scroll bar buttons or page up/down keys)
jump to a given date (right click + select a date in the calendar pop-up)
enable/disable filtering using the toolbar button
I made a full working example on my GitHub account:
https://github.com/3noix/ForDebug
I am currently using Qt 5.11.0 with MinGW 5.3.0 32 bits.
So do you have any idea of what I am doing wrong?
Thanks by advance.
//DashboardWidget.h
#include <QWidget>
#include <QVector>
#include "../Task.h"
class QVBoxLayout;
class DashboardTableView;
class DashboardTableModel;
class DashboardFilterModel;
class QSortFilterProxyModel;
class DashboardTableDelegate;
class DashboardWidget : public QWidget
{
Q_OBJECT
public:
explicit DashboardWidget(QWidget *parent = nullptr);
DashboardWidget(const DashboardWidget &other) = delete;
DashboardWidget(DashboardWidget &&other) = delete;
DashboardWidget& operator=(const DashboardWidget &other) = delete;
DashboardWidget& operator=(DashboardWidget &&other) = delete;
virtual ~DashboardWidget() = default;
void toggleDaysFiltering();
void updateTasks(const QVector<Task> &tasks, const QDateTime &refreshDateTime);
public slots:
void slotAskDataUp();
void slotAskDataDown();
void slotShowDate(const QDate &date);
private slots:
void slotNewTimeRange(const QDate &from, const QDate &to);
private:
QVBoxLayout *m_layout;
DashboardTableView *m_view;
DashboardTableModel *m_model;
#ifdef CUSTOM_FILTER_MODEL
DashboardFilterModel *m_filter;
#else
QSortFilterProxyModel *m_filter;
#endif
DashboardTableDelegate *m_delegate;
};
//DashboardWidget.cpp
#include "DashboardWidget.h"
#include "DashboardTableView.h"
#include "DashboardTableModel.h"
#include "DashboardFilterModel.h"
#include <QSortFilterProxyModel>
#include "DashboardTableDelegate.h"
#include "DashboardGeometry.h"
#include "../DataInterface.h"
#include <QVBoxLayout>
///////////////////////////////////////////////////////////////////////////////
// RESUME :
//
// CONSTRUCTEUR
//
// TOGGLE DAYS FILTERING
// SLOT ASK DATA UP
// SLOT ASK DATA DOWN
// SLOT NEW TIME RANGE
// SLOT SHOW DATE
// UPDATE TASKS
///////////////////////////////////////////////////////////////////////////////
// CONSTRUCTEUR ///////////////////////////////////////////////////////////////
DashboardWidget::DashboardWidget(QWidget *parent) : QWidget{parent}
{
m_layout = new QVBoxLayout{this};
this->setLayout(m_layout);
m_view = new DashboardTableView{this};
m_model = new DashboardTableModel{this};
m_delegate = new DashboardTableDelegate{this};
#ifdef CUSTOM_FILTER_MODEL
m_filter = new DashboardFilterModel{this};
#else
m_filter = new QSortFilterProxyModel{this};
m_filter->setFilterKeyColumn(0);
m_filter->setFilterRole(Qt::UserRole+1);
m_filter->setFilterRegExp(QRegExp{});
#endif
m_filter->setSourceModel(m_model);
m_view->setModel(m_filter);
m_view->setItemDelegate(m_delegate);
m_view->setupView();
m_layout->addWidget(m_view);
m_layout->setContentsMargins(0,0,0,0);
QObject::connect(m_view, SIGNAL(wantToGoMoreUp()), this, SLOT(slotAskDataUp()));
QObject::connect(m_view, SIGNAL(wantToGoMoreDown()), this, SLOT(slotAskDataDown()));
QObject::connect(m_view, SIGNAL(dateClicked(QDate)), this, SLOT(slotShowDate(QDate)));
QObject::connect(m_model, SIGNAL(newTimeRange(QDate,QDate)), this, SLOT(slotNewTimeRange(QDate,QDate)));
}
// TOGGLE DAYS FILTERING //////////////////////////////////////////////////////
void DashboardWidget::toggleDaysFiltering()
{
#ifdef CUSTOM_FILTER_MODEL
bool b = m_filter->filtersDays();
m_filter->setFilterOnDays(!b);
#else
if (m_filter->filterRegExp().isEmpty()) {m_filter->setFilterRegExp("Keep");}
else {m_filter->setFilterRegExp(QRegExp{});}
#endif
}
// SLOT ASK DATA UP ///////////////////////////////////////////////////////////
void DashboardWidget::slotAskDataUp()
{
int nbDays = 10;
QDate from = m_model->from();
m_model->addDaysOnTop(nbDays);
//if (m_model->daysVisible() > 100) {m_model->removeDaysOnBottom(nbDays);}
this->slotNewTimeRange(from.addDays(-nbDays),from);
}
// SLOT ASK DATA DOWN /////////////////////////////////////////////////////////
void DashboardWidget::slotAskDataDown()
{
int nbDays = 10;
QDate to = m_model->to();
m_model->addDaysOnBottom(nbDays);
//if (m_model->daysVisible() > 100) {m_model->removeDaysOnTop(nbDays);}
this->slotNewTimeRange(to,to.addDays(nbDays));
}
// SLOT SHOW DATE /////////////////////////////////////////////////////////////
void DashboardWidget::slotShowDate(const QDate &date)
{
int row = m_model->showDate(date);
QModelIndex indexSource = m_model->index(row,0);
QModelIndex indexView = m_filter->mapFromSource(indexSource);
m_view->scrollTo(indexView,QAbstractItemView::PositionAtTop);
}
// SLOT NEW TIME RANGE ////////////////////////////////////////////////////////
void DashboardWidget::slotNewTimeRange(const QDate &from, const QDate &to)
{
// list the tasks in this interval
QString errorMessage;
QVector<Task> tasks = DataInterface::instance().getTasks(from,to,&errorMessage);
if (errorMessage != "") {return;}
// update the table
this->updateTasks(tasks,QDateTime{});
}
// UPDATE TASKS ///////////////////////////////////////////////////////////////
void DashboardWidget::updateTasks(const QVector<Task> &tasks, const QDateTime &refreshDateTime)
{
m_model->updateTasks(tasks);
if (refreshDateTime.isValid()) {m_view->setUpdateTime(refreshDateTime);}
}
//DashboardTableModel.h
#include <QAbstractTableModel>
#include "../User.h"
#include "../Task.h"
#include <QDate>
class DashboardTableModel : public QAbstractTableModel
{
Q_OBJECT
public:
explicit DashboardTableModel(QObject *parent = nullptr);
DashboardTableModel(const DashboardTableModel &other) = delete;
DashboardTableModel(DashboardTableModel &&other) = delete;
DashboardTableModel& operator=(const DashboardTableModel &other) = delete;
DashboardTableModel& operator=(DashboardTableModel &&other) = delete;
virtual ~DashboardTableModel() = default;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override final;
Qt::ItemFlags flags(const QModelIndex &index) const override final;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override final;
int columnCount(const QModelIndex &parent = QModelIndex{}) const override final;
int rowCount(const QModelIndex &parent = QModelIndex{}) const override final;
void updateTasks(const QVector<Task> &tasks);
int dateToRow(const QDate &date);
QDate from() const;
QDate to() const;
int daysVisible() const;
int showDate(const QDate &date);
void addDaysOnTop(int nbDays = 20);
void addDaysOnBottom(int nbDays = 20);
bool removeDaysOnTop(int nbDays = 20);
bool removeDaysOnBottom(int nbDays = 20);
void clear();
signals:
void newTimeRange(const QDate &from, const QDate &to);
private:
void initLines(const QDate &from, const QDate &to);
using Location = std::pair<QDate,int>;
struct DataLine
{
QDate date;
QVector<TaskGroup> data;
};
static int searchTask(const TaskGroup &g, int taskId);
int userIdToColumn(int userId) const;
QVector<User> m_users;
QList<DataLine> m_data;
QMap<int,Location> m_index; // key=taskId
};
//DashboardTableModel.cpp
#include "DashboardTableModel.h"
#include <QBrush>
const QStringList daysOfWeek{"Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"};
const QColor greenToday{200,255,200};
const QColor grayWeekEnd{240,240,240};
const int initNbDaysBefore = -10;
const int initNbDaysAfter = 15;
const bool emitLayoutChange = false;
///////////////////////////////////////////////////////////////////////////////
// RESUME :
//
// CONSTRUCTEUR
// INIT LINES
//
// FROM
// TO
// DAYS VISIBLE
//
// DATE TO ROW
// SEARCH TASK
//
// SHOW DATE
// ADD DAYS ON TOP
// ADD DAYS ON BOTTOM
// REMOVE DAYS ON TOP
// REMOVE DAYS ON BOTTOM
// CLEAR
//
// DATA
// FLAGS
// HEADER DATA
// COLUMN COUNT
// ROW COUNT
//
// UPDATE TASKS
///////////////////////////////////////////////////////////////////////////////
// CONSTRUCTEUR ///////////////////////////////////////////////////////////////
DashboardTableModel::DashboardTableModel(QObject *parent) : QAbstractTableModel{parent}
{
m_users << User{0,"Serge","Karamazov","SKZ"};
m_users << User{1,"Patrick","Biales","PBL"};
m_users << User{2,"Odile","Deray","ODR"};
m_users << User{3,"Mevatlaver","Kraspek","MLK"};
QDate from = QDate::currentDate().addDays(initNbDaysBefore);
QDate to = QDate::currentDate().addDays(initNbDaysAfter);
this->initLines(from,to);
}
// INIT LINES /////////////////////////////////////////////////////////////////
void DashboardTableModel::initLines(const QDate &from, const QDate &to)
{
this->clear();
int nbCols = this->columnCount();
int nbRowsInit = from.daysTo(to) + 1;
QDate date = from;
if (emitLayoutChange) emit layoutAboutToBeChanged();
this->beginInsertRows(QModelIndex{},0,nbRowsInit-1);
for (int row=0; row<=nbRowsInit; ++row)
{
date = date.addDays(1);
m_data << DataLine{date,QVector<TaskGroup>(nbCols)};
}
this->endInsertRows();
if (emitLayoutChange) emit layoutChanged();
}
// FROM ///////////////////////////////////////////////////////////////////////
QDate DashboardTableModel::from() const
{
if (m_data.size() == 0) {return {};}
return m_data[0].date;
}
// TO /////////////////////////////////////////////////////////////////////////
QDate DashboardTableModel::to() const
{
if (m_data.size() == 0) {return {};}
return m_data.last().date;
}
// DAYS VISIBLE ///////////////////////////////////////////////////////////////
int DashboardTableModel::daysVisible() const
{
if (m_data.size() == 0) {return 0;}
QDate from = m_data[0].date;
QDate to = m_data.last().date;
return from.daysTo(to)+1;
}
// DATE TO ROW ////////////////////////////////////////////////////////////////
int DashboardTableModel::dateToRow(const QDate &date)
{
if (m_data.size() == 0) {return -1;}
QDate date1 = m_data[0].date;
QDate date2 = m_data.last().date;
if (date < date1 || date > date2) {return -1;}
return date1.daysTo(date);
}
// USER ID TO COLUMN //////////////////////////////////////////////////////////
int DashboardTableModel::userIdToColumn(int userId) const
{
return userId;
}
// SEARCH TASK ////////////////////////////////////////////////////////////////
int DashboardTableModel::searchTask(const TaskGroup &g, int taskId)
{
for (int i=0; i<g.size(); ++i)
{
if (g[i].id == taskId)
return i;
}
return -1;
}
// SHOW DATE //////////////////////////////////////////////////////////////////
int DashboardTableModel::showDate(const QDate &date)
{
if (m_data.size() == 0) {return -1;}
QDate from = this->from();
QDate to = this->to();
if (from <= date && date <= to) {return this->dateToRow(date);}
int fromToDate = from.daysTo(date);
int toToDate = to.daysTo(date);
if (fromToDate >= -15 && fromToDate < 0)
{
this->addDaysOnTop(15);
emit newTimeRange(date,from);
return this->dateToRow(date);
}
else if (toToDate > 0 && toToDate <= 15)
{
this->addDaysOnBottom(15);
emit newTimeRange(to,date);
return this->dateToRow(date);
}
// we discard the existing data and restart from nothing
this->clear();
QDate newFrom = date.addDays(initNbDaysBefore);
QDate newTo = date.addDays(initNbDaysAfter);
this->initLines(newFrom,newTo);
emit newTimeRange(newFrom,newTo);
return this->dateToRow(date);
}
// ADD DAYS ON TOP ////////////////////////////////////////////////////////////
void DashboardTableModel::addDaysOnTop(int nbDays)
{
if (emitLayoutChange) emit layoutAboutToBeChanged();
this->beginInsertRows(QModelIndex{},0,nbDays-1);
QDate date = this->from();
int nbCol = this->columnCount();
for (int row=0; row<nbDays; ++row)
{
date = date.addDays(-1);
m_data.push_front(DataLine{date,QVector<TaskGroup>(nbCol)});
}
this->endInsertRows();
if (emitLayoutChange) emit layoutChanged();
}
// ADD DAYS ON BOTTOM /////////////////////////////////////////////////////////
void DashboardTableModel::addDaysOnBottom(int nbDays)
{
int nOld = m_data.size();
if (emitLayoutChange) emit layoutAboutToBeChanged();
this->beginInsertRows(QModelIndex{},nOld,nOld+nbDays-1);
QDate date = this->to();
int nbCol = this->columnCount();
for (int row=0; row<nbDays; ++row)
{
date = date.addDays(1);
m_data << DataLine{date,QVector<TaskGroup>(nbCol)};
}
this->endInsertRows();
if (emitLayoutChange) emit layoutChanged();
}
// REMOVE DAYS ON TOP /////////////////////////////////////////////////////////
bool DashboardTableModel::removeDaysOnTop(int nbDays)
{
if (m_data.size() < nbDays) {return false;}
// remove tasks ids from index
for (int row=0; row<nbDays; ++row)
{
for (const TaskGroup &g : m_data[row].data)
{
for (const Task &t : g)
{m_index.remove(t.id);}
}
}
// removal
if (emitLayoutChange) emit layoutAboutToBeChanged();
this->beginRemoveRows(QModelIndex{},0,nbDays-1);
for (int i=0; i<nbDays; ++i) {m_data.removeFirst();}
this->endRemoveRows();
if (emitLayoutChange) emit layoutChanged();
return true;
}
// REMOVE DAYS ON BOTTOM //////////////////////////////////////////////////////
bool DashboardTableModel::removeDaysOnBottom(int nbDays)
{
if (m_data.size() < nbDays) {return false;}
int start = m_data.size() - nbDays;
int end = m_data.size() - 1;
// remove tasks ids from index
for (int row=start; row<end; ++row)
{
for (const TaskGroup &g : m_data[row].data)
{
for (const Task &t : g)
{m_index.remove(t.id);}
}
}
// removal
if (emitLayoutChange) emit layoutAboutToBeChanged();
this->beginRemoveRows(QModelIndex{},start,end);
for (int i=0; i<nbDays; ++i) {m_data.removeLast();}
this->endRemoveRows();
if (emitLayoutChange) emit layoutChanged();
return true;
}
// CLEAR //////////////////////////////////////////////////////////////////////
void DashboardTableModel::clear()
{
if (m_data.size() == 0) {return;}
// this->beginResetModel();
if (emitLayoutChange) emit layoutAboutToBeChanged();
this->beginRemoveRows(QModelIndex{},0,m_data.size()-1);
m_data.clear();
m_index.clear();
this->endRemoveRows();
if (emitLayoutChange) emit layoutChanged();
// this->endResetModel();
}
// DATA ///////////////////////////////////////////////////////////////////////
QVariant DashboardTableModel::data(const QModelIndex &index, int role) const
{
int row = index.row();
int col = index.column();
if (!index.isValid() || row < 0 || row >= this->rowCount(QModelIndex{})) {return {};}
if (role == Qt::DisplayRole || role == Qt::EditRole)
{
const TaskGroup &g = m_data[row].data[col];
return QVariant::fromValue(g);
}
else if (role == Qt::BackgroundRole)
{
QDate date = m_data[row].date;
if (!date.isValid()) {return {};}
if (date == QDate::currentDate()) {return QBrush{greenToday};}
int d = date.dayOfWeek();
if (d == 6 || d == 7) {return QBrush{grayWeekEnd};}
return {};
}
else if (role == Qt::UserRole)
{
int nbPostTasks = 0;
for (const TaskGroup &g : m_data[row].data) {nbPostTasks += g.size();}
return nbPostTasks;
}
else if (role == Qt::UserRole+1)
{
int nbPostTasks = 0;
for (const TaskGroup &g : m_data[row].data) {nbPostTasks += g.size();}
return (nbPostTasks > 0 ? "Keep" : "Skip");
}
return {};
}
// FLAGS //////////////////////////////////////////////////////////////////////
Qt::ItemFlags DashboardTableModel::flags(const QModelIndex &index) const
{
Q_UNUSED(index)
return (Qt::ItemIsEnabled | Qt::ItemIsSelectable);
}
// HEADER DATA ////////////////////////////////////////////////////////////////
QVariant DashboardTableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
{
if (section < 0 || section >= m_users.size()) {return {};}
//return m_users[section].trigramme;
const User &u = m_users[section];
return u.firstName + " " + u.lastName + "\n" + u.trigramme;
}
else if (orientation == Qt::Vertical && role == Qt::DisplayRole)
{
if (section >= m_data.size()) {return {};}
QDate date = m_data[section].date;
if (!date.isValid()) {return {};}
if (date == QDate::currentDate()) {return "Today";}
QString str = daysOfWeek[date.dayOfWeek()-1] + "\n" + date.toString("dd/MM/yy");
if (date.dayOfWeek() == 1) {str += "\nW" + QString::number(date.weekNumber());}
return str;
}
else if (orientation == Qt::Horizontal && role == Qt::TextAlignmentRole)
{
return Qt::AlignCenter;
}
else if (orientation == Qt::Vertical && role == Qt::TextAlignmentRole)
{
return Qt::AlignCenter;
}
else if (orientation == Qt::Vertical && role == Qt::BackgroundRole)
{
if (section >= m_data.size()) {return {};}
QDate date = m_data[section].date;
if (!date.isValid()) {return {};}
if (date == QDate::currentDate()) {return QBrush{greenToday};}
int d = date.dayOfWeek();
if (d == 6 || d == 7) {return QBrush{grayWeekEnd};}
return {};
}
return QAbstractTableModel::headerData(section, orientation, role);
}
// COLUMN COUNT ///////////////////////////////////////////////////////////////
int DashboardTableModel::columnCount(const QModelIndex &parent) const
{
if (parent.isValid()) {return 0;}
return m_users.size();
}
// ROW COUNT //////////////////////////////////////////////////////////////////
int DashboardTableModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid()) {return 0;}
return m_data.size();
}
// UPDATE TASKS ///////////////////////////////////////////////////////////////
void DashboardTableModel::updateTasks(const QVector<Task> &tasks)
{
for (const Task &t : tasks)
{
int newRow = this->dateToRow(t.target);
int newCol = this->userIdToColumn(t.user_id);
bool bDisplayTask = (newRow != -1 && !t.deleted);
if (m_index.contains(t.id)) // the task is already displayed
{
Location loc = m_index[t.id];
int oldRow = this->dateToRow(loc.first);
int oldCol = loc.second;
if (bDisplayTask)
{
if (newRow != oldRow || newCol != oldCol)
{
// we move the post-it (and maybe update it)
TaskGroup &gOld = m_data[oldRow].data[oldCol];
int i = DashboardTableModel::searchTask(gOld,t.id);
if (i != -1) {gOld.removeAt(i);}
m_index.remove(t.id);
m_data[newRow].data[newCol] << t;
m_index.insert(t.id,std::make_pair(t.target,newCol));
QModelIndex oldIndex = this->index(oldRow,oldCol,{});
QModelIndex oldLineIndex = this->index(oldRow,0,{});
QModelIndex newLineIndex = this->index(newRow,0,{});
emit dataChanged(oldIndex,oldIndex);
emit dataChanged(oldLineIndex,oldLineIndex);
emit dataChanged(newLineIndex,newLineIndex);
}
else
{
// we only update the post-it
TaskGroup &g = m_data[newRow].data[newCol];
int i = DashboardTableModel::searchTask(g,t.id);
if (i != -1) {g[i] = t;}
}
QModelIndex newIndex = this->index(newRow,newCol,{});
emit dataChanged(newIndex,newIndex);
}
else
{
// we delete the post-it
TaskGroup &g = m_data[oldRow].data[oldCol];
int i = DashboardTableModel::searchTask(g,t.id);
if (i != -1) {g.removeAt(i);}
m_index.remove(t.id);
QModelIndex index = this->index(oldRow,oldCol,{});
QModelIndex oldLineIndex = this->index(oldRow,0,{});
emit dataChanged(index,index);
emit dataChanged(oldLineIndex,oldLineIndex);
}
}
else // the task is not displayed yet
{
if (bDisplayTask)
{
// we add the post-it
m_data[newRow].data[newCol] << t;
m_index.insert(t.id,std::make_pair(t.target,newCol));
QModelIndex index = this->index(newRow,newCol,{});
QModelIndex newLineIndex = this->index(newRow,0,{});
emit dataChanged(index,index);
emit dataChanged(newLineIndex,newLineIndex);
}
}
}
}

Related

Testing Qt application with Qt Test

I have looked at the 5 Qt testing examples including the one about GUI events, but these examples are way too simple.
I want to test my program by launching it, simulating some clicks, and checking the value of instance variables that have been changed by those clicks.
I assume that this test below is illogical: a.exec() blocks the thread until the program is closed, and when the program is closed w has been deleted I think (or will be deleted later?).
So how to write system/GUI tests?
My test:
void LaunchProgramTest::LaunchProgramTestFunction() {
QApplication a(argc, argv);
MainWindow *w = new MainWindow();
w->show();
a.exec();
int testResult = w->myTestFunction();
qDebug() << testResult; //Prints big numbers like "-17891602" or "1753770528" as if testResult was not initialized
QVERIFY2(testResult == 3, "Incorrectly changed");
}
In mainWindow.h I declared a variable:
int testValue;
Mainwindow.cpp is the class for the main GUI of the program. In the constructor I added
testValue = 2;
Then in a function that is executed upon events I wrote
void MainWindow::on_actionTest_clicked() {
testValue = 3;
}
enter code hereSo)) you need to add QTest, add .pro
QT += testlib
and
#include <QTest>
I will show an example of my implementation for MousePress, the rest you can do yourself))
struct EventMK
{
int type;
QString m_widPath;
int _timer;
int width;
int height;
QPoint p;
QPoint g;
int button;
int buttons;
int modifiers;
int _key;
QString text;
void print(){
qDebug()<<"{ \n"
<<"type "<< type<<","<<"\n"
<<"Widget_Path "<< m_widPath<<","<<"\n"
<<"Timer "<< _timer<<","<<"\n"
<<"Width "<< width<<","<<"\n"
<<"Height "<< height<<","<<"\n"
<<"Pos_x "<< p.x()<<","<<"\n"
<<"Pos_y "<< p.y()<<","<<"\n"
<<"Global_x "<< g.x()<<","<<"\n"
<<"Global_y "<< g.y()<<","<<"\n"
<<"Button "<< button<<","<<"\n"
<<"Buttons "<< buttons<<","<<"\n"
<<"Modifiers "<< modifiers<<","<<"\n"
<<"Key "<< _key<<","<<"\n"
<<"Text "<< text<<"\n"
<<"}\n";
}
};
QWidget * _getWidget(EventMK ev)
{
QString wname = ev.m_widPath;
QStringList wpath = wname.split ( "/" );
return QWidgetUtils::getAWidget(&wpath);
}
void _postExecution(EventMK ev, QWidget *widget)
{
if (widget){
//set focus
QWidgetUtils::setFocusOnWidget(widget);
//end simulation
widget->setUpdatesEnabled ( true );
widget->update();
}
}
QPoint adaptedPosition(EventMK ev, QWidget *w)
{
if (w == nullptr)
return QPoint(ev.p.x(), ev.p.y());
int orig_w = ev.width;
int orig_h = ev.height;
int curr_w = w->width();
int curr_h = w->height();
int new_x = ev.p.x() * curr_w / orig_w;
int new_y = ev.p.y() * curr_h / orig_h;
return QPoint(new_x, new_y);
}
and function implementation
void executeMousePressEvent(EventMK ev)
{
QWidget* widget = _getWidget(ev);
if ( widget == nullptr )
{
qDebug()<<"error: "<<__LINE__<<__FUNCTION__;
return;
}
// _preExecutionWithMouseMove(ev, widget);
if (widget){
QTest::mousePress ( widget, (Qt::MouseButton)ev.button ,
(Qt::KeyboardModifier) ev.modifiers,
adaptedPosition(ev,widget));
}
_postExecution(ev, widget);
}
now left to fill struct EventMK , you need to populate it from MouseButtonPress events.
Here is my example
bool eventFilter(QObject *obj, QEvent *event)
{
///
/// process control
///
//window events
if (event->type() == QEvent::KeyPress)
{
handleKeyPressEvent(obj, event);
}
//mouse events
else if (event->type() == QEvent::MouseButtonPress)
{
handleMousePressEvent(obj, event);
}
else if (event->type() == QEvent::MouseButtonRelease)
{
handleMouseReleaseEvent(obj, event);
}
else if (event->type() == QEvent::MouseButtonDblClick)
{
handleMouseDoubleEvent(obj, event);
}
else if (event->type() == QEvent::Wheel)
{
handleWheelEvent(obj, event);
}
//keyboard events
else if (event->type() == QEvent::Close)
{
handleCloseEvent(obj, event);
}
///the event should continue to reach its goal...
return false;
}
and
void handleMousePressEvent(QObject *obj, QEvent *event)
{
QWidget *widget = isValidWidget(obj);
if (!widget){
return;
}
QMouseEvent *me = dynamic_cast< QMouseEvent*> ( event );
//create the event
if (widget != nullptr){
EventMK evkm;
evkm.type = QOE_MOUSE_PRESS; // set your type
evkm._timer = _timer.restart(); // add your QElapsedTimer
evkm.m_widPath = QWidgetUtils::getWidgetPath(widget);
evkm. width = widget->width();
evkm. height = widget->height();
QPoint p ( me->pos() );
QPoint g = widget->mapToGlobal ( p );
evkm. p = p;
evkm. g = g;
evkm. button = me->button();
evkm. buttons = me->buttons();
evkm. modifiers = me->modifiers();
evkm.print();
}
//send event if EventMK is valid
}
so, it turns out we can write a scenario and run what you wanted, thanks

when deleting Tree item destructor not being called

when i am deleting Treeitem from the the tree model the destructor for the tree item is not being called.
This is my code for deleting the tree item from the model.
void TreeModel::removeItem(TreeItem *node)
{
const int row = node->row();
QModelIndex idx = createIndex(row, 0, node);
TreeItem* itm = getItem(idx);
beginRemoveRows(idx.parent(), row, row);
node->parent()->removeChild(row);
endRemoveRows();
}
The code for Treeitem RemoveChild.
void TreeItem::removeChild(int row)
{
childItems.removeAt(row);
}
The code for tree item header file.
#include <QList>
#include <QVariant>
#include <QVector>
#include "Container.h"
class TreeItem
{
public:
explicit TreeItem( 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 ;
Container* GetContainer();
bool setData(Container* data , QVariant value);
void setContainer( Container* data);
bool insertChildren(int position, int count );
bool removeChildren( int position , int count );
void removeChild(int row);
void removeChild(TreeItem* itm);
std::string getChildName(int row);
std::string getName();
int row() const;
void insertChild(int pos, TreeItem *child);
private:
QList<TreeItem*> childItems;
Container* itemData;
TreeItem* parentItem;
};
The code for the tree item Cpp file.
/////////////////////////////////////////////
//////////////////////////////////////////////////////
TreeItem::TreeItem( Container *data, TreeItem *parent )
{
parentItem = parent;
itemData = new Container;
*itemData = *data;
}
TreeItem::~TreeItem()
{
qDebug() << itemData->GetName().c_str();
if (itemData != nullptr)
{
delete itemData;
qDebug() << "deleting Item Data";
}
qDeleteAll(childItems);
}
TreeItem *TreeItem::parent()
{
return parentItem;
}
TreeItem *TreeItem::child(int iNumber)
{
return childItems.value(iNumber);
}
int TreeItem::childCount() const
{
return childItems.count();
}
int TreeItem::childNumber() const
{
if (parentItem)
return parentItem->childItems.indexOf(const_cast<TreeItem*> (this));
return 0;
}
Container TreeItem::data() const
{
return *itemData;
}
bool TreeItem::setData( Container* data , QVariant value )
{
//*itemData = *data; // Do Not !!!! uncomment this as it will set the
value of default container constructor.
itemData->SetName(value.toString().toStdString() );
return true;
}
bool TreeItem::insertChildren(int position, int count)
{
if (position < 0 || position > childItems.count())
return false;
Container cont;
TreeItem *item = new TreeItem(&cont, this);
childItems.insert(position, item);
return true;
}
bool TreeItem::removeChildren(int position, int count)
{
if (position < 0 || position > childItems.count())
return false;
for (int row = 0; row < count; ++row)
{
delete childItems.takeAt(position);
}
return true;
}
void TreeItem::setContainer( Container* cont)
{
*itemData = *cont;
}
void TreeItem::appendChild(TreeItem *node)
{
childItems.append( node );
}
int TreeItem::row() const
{
if (parentItem)
return parentItem->childItems.indexOf( const_cast<TreeItem*>(this) );
return 0;
}
void TreeItem::removeChild(int row)
{
childItems.removeAt(row);
}
void TreeItem::insertChild(int pos, TreeItem *child)
{
childItems.insert(pos, child);
child->parentItem = this;
}
void TreeItem::removeChild(TreeItem* itm)
{
childItems.removeOne(itm);
}
std::string TreeItem::getChildName(int row)
{
return childItems.value(row)->getName();
}
std::string TreeItem::getName()
{
return itemData->GetName();
}
Container* TreeItem::GetContainer()
{
return itemData;
}
The Header file for the TreeModel Class///////////////////////////////////
#pragma once
#include <QAbstractItemModel>
#include <QString>
#include <QMimedata.h>
#include <Qdatastream.h>
class TreeItem;
class Container;
class TreeModel : public QAbstractItemModel
{
Q_OBJECT
public:
TreeModel(const QString &header, Container *data, QObject *parent = 0);
~TreeModel();
QVariant data(const QModelIndex &index, int role) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
bool setHeaderData(int section, Qt::Orientation orientation,
const QVariant &value, int role = Qt::EditRole) override;
QModelIndex index(int row, int column, const QModelIndex &parent) const override;
QModelIndex parent(const QModelIndex &parent) const override;
bool insertRows(int position, int rows, const QModelIndex &parent);
// bool removeRows(int position, int rows, const QModelIndex &parent = QModelIndex()) override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
Container* GetContainer(const QModelIndex &index);
void SetContainer(const QModelIndex &index, Container* cont);
////////////////////// Drag And Drop Actions ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Qt::DropActions supportedDropActions() const override;
Qt::DropActions supportedDragActions() const override;
QStringList mimeTypes() const override;
QMimeData *mimeData(const QModelIndexList &indexes) const override;
bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex &parent);
void setupModelData(const QStringList &lines, TreeItem *parent);
void removeItem(TreeItem *item);
bool FindChild(std::string stdstrChildName);
TreeItem *getItem(const QModelIndex &index) const;
TreeItem *getRoot();
private:
//void setupModelData(const Container &cont, TreeItem *parent);
TreeItem *rootItem;
};
The Cpp file for the TreeModel
#include "TreeModel.h"
#include "TreeItem.h"
#include <qcoreapplication.h>
#include <qdebug.h>
#include "Container.h"
TreeItem *TreeModel::getItem(const QModelIndex &index) const
{
if (index.isValid()) {
TreeItem *item = static_cast<TreeItem*>(index.internalPointer());
if (item)
return item;
}
return rootItem;
}
TreeModel::TreeModel(const QString &header, Container *data, QObject
*parent) : QAbstractItemModel(parent)
{
qDebug() << "First level done";
rootItem = new TreeItem( data);
}
TreeModel::~TreeModel()
{
delete rootItem;
}
QVariant TreeModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (role != Qt::DisplayRole && role != Qt::EditRole)
return QVariant();
TreeItem *item = getItem(index);
return QString::fromStdString(item->data().GetName());
//return QVariant::fromValue(item->data());
}
QVariant TreeModel::headerData(int section, Qt::Orientation orientation,
int role) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
return QVariant::fromValue(rootItem->data());
return QVariant();
}
bool TreeModel::setData(const QModelIndex &index, const QVariant &val, int
role)
{
if (role != Qt::EditRole)
return false;
Container c = val.value<Container>();
TreeItem *item = getItem(index);
bool result = true;
item->setData(&c, val);
if (result)
emit dataChanged(index, index, { role });
return result;
}
QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent)
const
{
if (parent.isValid() && parent.column() != 0)
return QModelIndex();
TreeItem *parentItem = getItem(parent);
TreeItem *childItem = parentItem->child(row);
if (childItem)
return createIndex(row, column, childItem);
else
return QModelIndex();
}
QModelIndex TreeModel::parent(const QModelIndex &index) const
{
if (!index.isValid())
return QModelIndex();
TreeItem *childItem = getItem(index);
TreeItem *parentItem = childItem->parent();
if (parentItem == rootItem)
return QModelIndex();
return createIndex(parentItem->row(), 0, parentItem);
}
bool TreeModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &val, int role)
{
if (role != Qt::EditRole || orientation != Qt::Horizontal)
return false;
Container c = val.value<Container>();
bool result = rootItem->setData(&c, val);
if (result)
emit headerDataChanged(orientation, section, section);
return result;
}
bool TreeModel::insertRows(int position, int rows, const QModelIndex &parent)
{
TreeItem *parentItem = getItem(parent);
bool success;
beginInsertRows(parent, position, position + rows - 1);
success = parentItem->insertChildren(position, rows);
endInsertRows();
return success;
}
Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::ItemIsDropEnabled;
return QAbstractItemModel::flags(index) | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEditable;
}
int TreeModel::rowCount(const QModelIndex &parent) const
{
if (parent.column() > 0)
return 1;
TreeItem *parentItem = getItem(parent);
//qDebug() << "the child count = " << parentItem->childCount() << parentItem->data().GetName().c_str();
return parentItem->childCount();
}
int TreeModel::columnCount(const QModelIndex & /* parent */) const
{
return 1;
}
Container* TreeModel::GetContainer(const QModelIndex &index)
{
TreeItem *item = getItem(index);
return item->GetContainer();
}
void TreeModel::SetContainer(const QModelIndex &index, Container* Cont)
{
TreeItem *item = getItem(index);
item->setContainer(Cont);
}
static const char s_treeNodeMimeType[] = "application/x-treenode";
QStringList TreeModel::mimeTypes() const
{
return QStringList() << s_treeNodeMimeType;
}
QMimeData *TreeModel::mimeData(const QModelIndexList &indexes) const
{
QMimeData *mimeData = new QMimeData;
QByteArray data; //a kind of RAW format for datas
//QDataStream is independant on the OS or proc architecture
//serialization of C++'s basic data types, like char, short, int, char *, etc.
//Serialization of more complex data is accomplished
//by breaking up the data into primitive units.
QDataStream stream(&data, QIODevice::WriteOnly);
QList<TreeItem *> nodes;
//
foreach(const QModelIndex &index, indexes) {
TreeItem *node = getItem(index);
if (!nodes.contains(node))
nodes << node;
}
stream << QCoreApplication::applicationPid();
stream << nodes.count();
foreach(TreeItem *node, nodes) {
stream << reinterpret_cast<qlonglong>(node);
}
mimeData->setData(s_treeNodeMimeType, data);
return mimeData;
}
bool TreeModel::dropMimeData(const QMimeData *mimeData, Qt::DropAction
action, int row, int column, const QModelIndex &parent)
{
//Q_ASSERT(action == Qt::MoveAction);
//Q_UNUSED(column);
//test if the data type is the good one
if (!mimeData->hasFormat(s_treeNodeMimeType)) {
return false;
}
QByteArray data = mimeData->data(s_treeNodeMimeType);
QDataStream stream(&data, QIODevice::ReadOnly);
qint64 senderPid;
stream >> senderPid;
if (senderPid != QCoreApplication::applicationPid()) {
// Let's not cast pointers that come from another process...
return false;
}
TreeItem *parentNode = getItem(parent);
// Q_ASSERT(parentNode);
int count;
stream >> count;
if (row == -1) {
// valid index means: drop onto item. I chose that this should insert
// a child item, because this is the only way to create the first child
of an item...
// This explains why Qt calls it parent: unless you just support
replacing, this
// is really the future parent of the dropped items.
if (parent.isValid())
row = 0;
else
// invalid index means: append at bottom, after last toplevel
row = rowCount(parent);
}
//qDebug() << "The row" << row << parentNode->data().GetName().c_str() ;
for (int i = 0; i < count; ++i) {
// Decode data from the QMimeData
qlonglong nodePtr;
stream >> nodePtr;
TreeItem *node = reinterpret_cast<TreeItem *>(nodePtr);
// Adjust destination row for the case of moving an item
// within the same parent, to a position further down.
// Its own removal will reduce the final row number by one.
if (node->row() < row && parentNode == node->parent())
--row;
// Remove from old position
// qDebug() << "The remove item " << node->data().GetName().c_str();
removeItem(node);
// Insert at new position
//qDebug() << "Inserting into" << parent << row;
beginInsertRows(parent, row, row);
parentNode->insertChild(row, node);
endInsertRows();
++row;
}
return true;
}
void TreeModel::removeItem(TreeItem *node)
{
const int row = node->row();
QModelIndex idx = createIndex(row, 0, node);
TreeItem* itm = getItem(idx);
beginRemoveRows(idx.parent(), row, row);
node->parent()->removeChild(row);
endRemoveRows();
}
Qt::DropActions TreeModel::supportedDropActions() const
{
return Qt::MoveAction;
}
Qt::DropActions TreeModel::supportedDragActions() const
{
return Qt::MoveAction;
}
void TreeModel::setupModelData(const QStringList &lines, TreeItem *parent)
{
QList<TreeItem*> parents;
QList<int> indentations;
parents << parent;
indentations << 0;
int number = 0;
while (number < lines.count()) {
int position = 0;
while (position < lines[number].length()) {
if (lines[number].mid(position, 1) != " ")
break;
position++;
}
QString lineData = lines[number].mid(position).trimmed();
if (!lineData.isEmpty()) {
// Read the column data from the rest of the line.
QStringList columnStrings = lineData.split("\t", QString::SkipEmptyParts);
QList<QVariant> columnData;
for (int column = 0; column < columnStrings.count(); ++column)
columnData << columnStrings[column];
if (position > indentations.last()) {
// The last child of the current parent is now the new parent
// unless the current parent has no children.
if (parents.last()->childCount() > 0) {
parents << parents.last()->child(parents.last()->childCount() - 1);
indentations << position;
}
}
else {
while (position < indentations.last() && parents.count() > 0) {
parents.pop_back();
indentations.pop_back();
}
}
Container c;
// Append a new node to the current parent's list of children.
parents.last()->appendChild(new TreeItem(&c, parents.last()));
}
++number;
}
}
TreeItem *TreeModel::getRoot()
{
return rootItem;
}
childItems.removeOne(itm); and childItems.removeAt(row); just remove TreeItem * from your QList<TreeItem*> childItems; so memory is not freed.
You should explicitly call delete, once they are removed as you did with delete childItems.takeAt(position); for example.

why are different treeItems linked to the same data

I have been trying to copy a treeitem to another location in the tree view along with its sub tree.
Finally i have been able to to move all of them successfully.
Header file for treeItem
class TreeItem
{
public:
explicit TreeItem( Container *data , TreeItem *parent = 0 );
~TreeItem();
TreeItem *parent();
void appendChild(TreeItem *child);
TreeItem& operator = (const TreeItem &item);
TreeItem *child(int iNumber);
int childCount() const;
int childNumber() const;
Container data() const ;
Container* GetContainer();
bool setData(Container* data , QVariant value);
void setContainer( Container* data);
bool insertChildren(int position, int count );
bool removeChildren( int position , int count );
void removeChild(int row);
void removeChild(TreeItem* itm);
QList<TreeItem*> children();
std::string getChildName(int row);
std::string getName();
int row() const;
void insertChild(int pos, TreeItem *child);
private:
QList<TreeItem*> childItems;
Container* itemData;
TreeItem* parentItem;
};
Cpp file for treeItem
TreeItem::TreeItem( Container *data, TreeItem *parent )
{
parentItem = parent;
itemData = new Container;
*itemData = *data;
}
TreeItem::~TreeItem()
{
if (itemData != nullptr)
{
delete itemData;
}
qDeleteAll(childItems);
}
TreeItem& TreeItem::operator = (const TreeItem &item)
{
qDebug() << "TreeItem operator called";
// if( this->itemData == nullptr)
// this->itemData = new Container;
*this->itemData = *item.itemData;
this->childItems = item.childItems;
this->parentItem = item.parentItem;
return *this;
}
TreeItem *TreeItem::parent()
{
return parentItem;
}
TreeItem *TreeItem::child(int iNumber)
{
return childItems.value(iNumber);
}
int TreeItem::childCount() const
{
return childItems.count();
}
int TreeItem::childNumber() const
{
if (parentItem)
return parentItem->childItems.indexOf(const_cast<TreeItem*> (this));
return 0;
}
Container TreeItem::data() const
{
return *itemData;
}
bool TreeItem::setData( Container* data , QVariant value )
{
//*itemData = *data; // Do Not !!!! uncomment this ///////////////////////////as it will set the value of default container constructor.
itemData->SetName(value.toString().toStdString() );
return true;
}
bool TreeItem::insertChildren(int position, int count)
{
if (position < 0 || position > childItems.count())
return false;
Container cont;
TreeItem *item = new TreeItem(&cont, this);
childItems.insert(position, item);
return true;
}
bool TreeItem::removeChildren(int position, int count)
{
if (position < 0 || position > childItems.count())
return false;
for (int row = 0; row < count; ++row)
{
delete childItems.takeAt(position);
}
return true;
}
void TreeItem::setContainer( Container* cont)
{
*itemData = *cont;
}
void TreeItem::appendChild(TreeItem *node)
{
childItems.append( node );
}
int TreeItem::row() const
{
// qDebug() << "The child count = " << parentItem->childItems.indexOf(const_cast<TreeItem*>(this));
if (parentItem)
return parentItem->childItems.indexOf( const_cast<TreeItem*>(this) );
return 0;
}
void TreeItem::removeChild(int row)
{
// qDebug() << "The Row Number" << row;
// childItems.removeAt(row);
delete childItems.takeAt(row);
}
void TreeItem::insertChild(int pos, TreeItem *child)
{
childItems.insert(pos, child);
child->parentItem = this;
}
void TreeItem::removeChild(TreeItem* itm)
{
childItems.removeOne(itm);
}
std::string TreeItem::getChildName(int row)
{
return childItems.value(row)->getName();
}
std::string TreeItem::getName()
{
return itemData->GetName();
}
Container* TreeItem::GetContainer()
{
return itemData;
}
QList<TreeItem*> TreeItem::children()
{
return childItems;
}
This is my function to build the tree for subcontainers of the tree item in the tree structure.
void TreeModel::buildTree(TreeItem * pItem, QDataStream & ds) const
{
if (pItem == NULL)
return;
ds << reinterpret_cast<qlonglong>(pItem);
ds << pItem->childCount();
foreach(TreeItem* childItem, pItem->children())
{
buildTree(childItem, ds);
}
}
This is in the Drop mime data function where i restore the sub tree of the container.
// count is the number of child containers of the tree item
TreeItem *node;
//qDebug() << "The row" << row << parentNode->data().GetName().c_str() ;
for (int i = 0; i < count; ++i) {
// Decode data from the QMimeData
qlonglong nodePtr;
int childCount;
stream >> nodePtr;
stream >> childCount;
node = reinterpret_cast<TreeItem *>(nodePtr);
// Adjust destination row for the case of moving an item
// within the same parent, to a position further down.
// Its own removal will reduce the final row number by one.
if (node->row() < row && parentNode == node->parent())
--row;
TreeItem *nodeNew = new TreeItem(node->GetContainer(), node->parent());
nodeNew->setContainer(node->GetContainer());
// Insert at new position
//qDebug() << "Inserting into" << parent << row;
beginInsertRows(parent, row, row);
parentNode->insertChild(row, nodeNew);
endInsertRows();
for (int k = 0; k < childCount; k++)
{
restoreTree(stream, nodeNew);
}
++row;
}
This is the function to restore the sub tree.
TreeItem * TreeModel::restoreTree(QDataStream & ds , TreeItem* parent) const
{
Container cont;
// Restore the info from the stream
int childCount;
qlonglong nodePtr;
ds >> nodePtr;
ds >> childCount;
TreeItem *currentItem = reinterpret_cast<TreeItem *>(nodePtr);
if (currentItem == nullptr)
return nullptr;
TreeItem *thisItem = new TreeItem(currentItem->GetContainer(), currentItem->parent() );
thisItem->setContainer(currentItem->GetContainer());
//*thisItem = *currentItem;
parent->appendChild(thisItem);
for (auto nChild = 0; nChild < childCount; ++nChild)
{
TreeItem* oldChild = restoreTree(ds, thisItem);
if (oldChild != nullptr)
{
TreeItem *pChild = new TreeItem( oldChild->GetContainer() , oldChild->parent() );
pChild->setContainer(oldChild->GetContainer());
thisItem->appendChild(pChild);
qDebug() << "Appending child";
}
}
return thisItem;
}
The tree gets restored in the correct manner.
But now i have a issue, the treeitem in the old position and the new position are linked together , if i make any changes to the tree item in the old position than it also affects the TreeItem in the new position.
Changing parent->appendChild(thisItem); to parent->insertChild(row, thisItem); solved the problem now the BuildTree function that works.
TreeItem * TreeModel::restoreTree(QDataStream & ds , TreeItem* parent , int row) const
{
Container cont;
// Restore the info from the stream
int childCount;
qlonglong nodePtr;
QModelIndex parentIndex;
ds >> nodePtr;
ds >> childCount;
TreeItem *currentItem = reinterpret_cast<TreeItem *>(nodePtr);
if (currentItem == nullptr)
return nullptr;
TreeItem *thisItem = new TreeItem(currentItem->GetContainer(), currentItem->parent() );
thisItem->setContainer(currentItem->GetContainer());
//*thisItem = *currentItem;
// parent->appendChild(thisItem);
parent->insertChild(row, thisItem);
for (auto nChild = 0; nChild < childCount; ++nChild)
{
restoreTree(ds, thisItem , nChild);
}
return thisItem;
}
It was more of hit and try to find the solution that worked , if someone can give a explanation on the difference between parent->appendChild(thisItem); to parent->insertChild(row, thisItem) that would be great.
Or Any alternative solution.

change c++ QAbstractListModel values from Qml

this is my code
devicemodel.h
class Device
{
public:
Device(const int &nodeId ,const QString &type, const int &lampVoltage);
//![0]
QString type() const;
int lampVoltage() const;
int nodeId() const;
public:
QString m_type;
int m_lampVoltage;
int m_nodeId;
//![1]
};
class DeviceModel : public QAbstractListModel
{
Q_OBJECT
public:
enum DeviceRoles {
NodeIdRole = Qt::UserRole + 1,
TypeRole ,
LampVoltageRole
};
DeviceModel(QObject *parent = 0);
//![1]
void addDevice(const Device &Device);
int rowCount(const QModelIndex & parent = QModelIndex()) const;
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
protected:
QHash<int, QByteArray> roleNames() const;
private:
QList<Device> m_Devices;
//![2]
public slots :
void callFromQml(int index);
};
devicemodel.cpp
Device::Device(const int &nodeId ,const QString &type, const int &lampVoltage)
: m_nodeId(nodeId) , m_type(type), m_lampVoltage(lampVoltage)
{
}
QString Device::type() const
{
return m_type;
}
int Device::nodeId() const
{
return m_nodeId;
}
int Device::lampVoltage() const
{
return m_lampVoltage;
}
DeviceModel::DeviceModel(QObject *parent)
: QAbstractListModel(parent)
{
}
void DeviceModel::callFromQml(int index){
Device k= m_Devices.at(index);
qDebug() << k.type() << k.lampVoltage();
k.m_lampVoltage = 5;
emit dataChanged(createIndex(index,0), createIndex(index,0), {0,1,2} );
}
void DeviceModel::addDevice(const Device &Device)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_Devices << Device;
endInsertRows();
}
int DeviceModel::rowCount(const QModelIndex & parent) const {
Q_UNUSED(parent);
return m_Devices.count();
}
QVariant DeviceModel::data(const QModelIndex & index, int role) const {
if (index.row() < 0 || index.row() >= m_Devices.count())
return QVariant();
const Device &Device = m_Devices[index.row()];
if (role == NodeIdRole)
return Device.nodeId();
else if (role == TypeRole)
return Device.type();
else if (role == LampVoltageRole)
return Device.lampVoltage();
return QVariant();
}
//![0]
QHash<int, QByteArray> DeviceModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[NodeIdRole] = "nodeId";
roles[TypeRole] = "type";
roles[LampVoltageRole] = "lampVoltage";
return roles;
}
main.qml
ListView {
width: 200; height: 250
spacing: 10
model: myModel
delegate:
Text { text: "Animal: " + type + ", " + lampVoltage
MouseArea {
anchors.fill: parent
onClicked: {
console.log("egwegweg");
myModel.callFromQml(index);
//lampVoltage = 10;
// myModel.setData(index , 10 , 2);
}
}
}
}
main.cpp
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
DeviceModel model;
model.addDevice(Device(1, "Medium" , 200));
model.addDevice(Device(1, "Medium" , 200));
model.addDevice(Device(1, "Medium" , 200));
QQmlContext *ctxt = engine.rootContext();
ctxt->setContextProperty("myModel", &model);
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
model.addDevice(Device(1, "Medium" , 200));
model.addDevice(Device(1, "Medium" , 200));
model.addDevice(Device(1, "Medium" , 200));
return app.exec();
i want modify my item values from Qml
i wrote a slot named callFromQml and pass index of item from qml to c++ code and want update the values from it
but can not do it
i don not know emit signal works or not and do not know pass index to it correctly or not and don not
Thanks for #MarkCh for pointing out, that you have not reimplemented the setData()-method.
The signature is:
bool QAbstractItemModel::setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole)
It returns true if some data was set successfully, and false if not.
Further, uppon success, before returning, it is necessary to emit dataChanged(...) so any views will be aware of the change.
Let's do it:
Add the appropriate declaration to the header file
Add the implementation to the .cpp-file. This might look like this:
bool QAbstractItemModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.row() < 0 || index.row() >= m_Devices.count())
return false;
const Device &Device = m_Devices[index.row()];
if (role == NodeIdRole) {
Device.m_nodeId = value.toInt();
emit dataChanged(index, index);
return true;
}
else if (role == TypeRole) {
Device.m_type = value.toString();
emit dataChanged(index, index);
return true;
}
else if (role == LampVoltageRole) {
Device.m_lampVoltage = value.toInt();
emit dataChanged(index, index);
return true;
}
return false;
}
Make DeviceModel a friend of Device or change the access of the private fields to usage of getters. As you like.
Now you should be able to set the roles in the delegate as if they were properties... Like:
onClicked: model.nodeId = 100;
The code above is not tested, but should include the most relevant details.

QDoubleSpinBox with leading zeros (always 4 digits)

I have a QDoubleSpinBox in range 0-7000, but want the value always displayed as 4 digits
(0-> 0000, 1 -> 0001 , 30 -> 0030, 3333 -> 3333).
I understand I can add a prefix, but a prefix is always added.
What are my options?
If you use integers, then QSpinBox will be enough.
You can simply inherit from QSpinBox and re-implement the textFromValue function:
class MySpinBox: public QSpinBox
{
Q_OBJECT
public:
MySpinBox( QWidget * parent = 0) :
QSpinBox(parent)
{
}
virtual QString textFromValue ( int value ) const
{
/* 4 - number of digits, 10 - base of number, '0' - pad character*/
return QString("%1").arg(value, 4 , 10, QChar('0'));
}
};
Filling QString this way does the trick.
Since prefix is not an option solution if you consider negative values, in my opinion the best and most elegant solution is defining your own custom spin box by deriving QAbstractSpinBox. Here is a small example:
Note that it is far from perfection and it serves just as an example on what could be done:
q4digitspinbox.h:
#ifndef Q4DIGITSPINBOX_H
#define Q4DIGITSPINBOX_H
#include <QAbstractSpinBox>
#include <QLineEdit>
class Q4DigitSpinBox : public QAbstractSpinBox
{
Q_OBJECT
public:
explicit Q4DigitSpinBox(QWidget *parent = 0);
StepEnabled stepEnabled() const;
double maximum() const;
double minimum() const;
void setMaximum(double max);
void setMinimum(double min);
void setRange(double minimum, double maximum);
double value() const;
public slots:
virtual void stepBy(int steps);
void setValue(double val);
signals:
void valueChanged(double i);
void valueChanged(const QString & text);
private:
double m_value;
double m_minimum;
double m_maximum;
QLineEdit m_lineEdit;
};
#endif // Q4DIGITSPINBOX_H
q4digitspinbox.h:
#include "q4digitspinbox.h"
Q4DigitSpinBox::Q4DigitSpinBox(QWidget *parent) :
QAbstractSpinBox(parent),
m_value(0),
m_minimum(-99),
m_maximum(99)
{
setLineEdit(&m_lineEdit);
setValue(0.0);
}
QAbstractSpinBox::StepEnabled Q4DigitSpinBox::stepEnabled() const
{
return StepUpEnabled | StepDownEnabled;
}
double Q4DigitSpinBox::maximum() const
{
return m_maximum;
}
double Q4DigitSpinBox::minimum() const
{
return m_minimum;
}
void Q4DigitSpinBox::setMaximum(double max)
{
m_maximum = max;
}
void Q4DigitSpinBox::setMinimum(double min)
{
m_minimum = min;
}
void Q4DigitSpinBox::setRange(double minimum, double maximum)
{
m_minimum = minimum;
m_maximum = maximum;
}
double Q4DigitSpinBox::value() const
{
return m_value;
}
void Q4DigitSpinBox::stepBy(int steps)
{
m_value += (double)steps / 10;
if (fabs(m_value - 0) < 0.00001)
{
m_value = 0;
}
if(m_value < m_minimum || m_value > m_maximum)
{
return;
}
int prefixNumberOfDigits = 4;
QString valueAsString = QString("%1").arg((int)m_value);
int numberOfDigits = valueAsString.length();
QString prefix;
prefixNumberOfDigits -= numberOfDigits;
if(prefixNumberOfDigits > 0)
{
while(prefixNumberOfDigits--)
{
prefix += "0";
}
}
QString value;
if(m_value < 0)
{
value = QString("-%1%2").arg(prefix).arg(-m_value);
}
else
{
value = QString("%1%2").arg(prefix).arg(m_value);
}
m_lineEdit.setText(value);
emit valueChanged(m_value);
emit valueChanged(value);
}
void Q4DigitSpinBox::setValue(double val)
{
if(val < m_minimum || val > m_maximum)
{
return;
}
int prefixNumberOfDigits = 4;
QString valueAsString = QString("%1").arg((int)val);
int numberOfDigits = valueAsString.length();
QString prefix;
prefixNumberOfDigits -= numberOfDigits;
if(prefixNumberOfDigits > 0)
{
while(prefixNumberOfDigits--)
{
prefix += "0";
}
}
QString value;
if(val < 0)
{
value = QString("-%1%2").arg(prefix).arg(-val);
}
else
{
value = QString("%1%2").arg(prefix).arg(val);
}
m_lineEdit.setText(value);
emit valueChanged(val);
emit valueChanged(value);
}
I didn't provide any commentary since I considered it pretty straight forward, but if needed I can add a few more explanations.
I hope this helps.

Resources