Testing Qt application with Qt Test - qt

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

Related

QSortFilterProxyModel: crash sometimes when adding rows from the source model

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);
}
}
}
}

Qt mouse movement/action combination

How to manage combined mouse movement (for example left click + midle click + left click release)
I have the method mousePressEvent and mouseReleaseEvent but I did not find the way to combine them.
If you need to combine the information from mousePress and mouseRelease you need to somehow keep track which button is still pressed and which one has already been released again.
Below is a rather simply example which seems to do what you describe (actions indicated by printouts)
Let me know if this helps
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QMouseEvent>
#include <QtCore>
class MyGraphicsView: public QGraphicsView
{
public:
MyGraphicsView( QGraphicsScene *scene, QWidget *parent = 0) : QGraphicsView(scene,parent)
{
active[Qt::LeftButton] = false;
active[Qt::RightButton] = false;
active[Qt::MiddleButton] = false;
}
public slots:
virtual void mousePressEvent( QMouseEvent * event );
virtual void mouseReleaseEvent( QMouseEvent * event );
std::map< Qt::MouseButton, bool > active;
};
void MyGraphicsView::mousePressEvent( QMouseEvent * event )
{
active[event->button()] = true;
if( event->button() == Qt::LeftButton && !active[Qt::MiddleButton] && !active[Qt::RightButton]) {
qDebug() << "select";
} else if (event->button() == Qt::RightButton && !active[Qt::LeftButton] && !active[Qt::MiddleButton] ) {
qDebug() << "property";
} else if ( event->button() == Qt::LeftButton && active[Qt::MiddleButton] && !active[Qt::RightButton]) {
qDebug() << "zoom";
} else if ( event->button() == Qt::MiddleButton && !active[Qt::LeftButton] && !active[Qt::RightButton] ) {
qDebug() << "pan";
}
}
void MyGraphicsView::mouseReleaseEvent( QMouseEvent * event )
{
if( event->button() == Qt::LeftButton && active[Qt::LeftButton] && active[Qt::MiddleButton] ){
qDebug() << "move";
}
active[event->button()] = false;
}
int main( int argc, char **argv )
{
QApplication app(argc, argv);
QGraphicsScene scene;
scene.setSceneRect( -100.0, -100.0, 200.0, 200.0 );
MyGraphicsView view( &scene );
view.show();
return app.exec();
}

send Qevent from thread

Please can some-one suggest on this question..? I have to send data from my TX thread to mainwindow using class derived from QEvent.
I want my RX thread to throw an event whenever some data is received so hat i can display that data in the Mainwindow.
I start a main thread when button start is pressed. I save the pointer to my mainwindow inside the object of main thread. I will use this pointer to post events to the mainwindow object.
When first time i enter the Qthread function dowork_rx(). Event is thrown & i am able to catch it in customEvent() handler. But when dowork_rx() while loop starts when i throws the event it does not trigger the customEvent() function.
Please suggest how to resolve this problem.
My qevent class :---
//String event derived class
template <typename T> class StringEvent : public QEvent
{
QString m_str;
public:
explicit StringEvent(const QString val) : QEvent(staticType()), m_str(val)
{
}
void setvalue(QString val)
{
m_str = val;
}
QString value() const
{
return m_str;
}
static QEvent::Type staticType()
{
static int type = QEvent::registerEventType();
return static_cast<QEvent::Type>(type);
/*
static int type;
if(type == 0)
{
type = QEvent::registerEventType();
}
return static_cast<QEvent::Type>(type);*/
}
static bool is(const QEvent * ev)
{
return ev->type() == staticType();
}
};
class UpdateEvent : public StringEvent<UpdateEvent>
{
public:
explicit UpdateEvent(QString val): StringEvent(val)
{
//qDebug() << "hello";
}
};
class ClearEvent : public StringEvent<ClearEvent>
{
public:
explicit ClearEvent(QString val): StringEvent(val)
{
}
};
Dowork function of the thread :----
// Common slot for the rx - thread
void RxThreadObject::dowork_rx()
{
int i =0;
qDebug() << "\nrx start \n";
myUpdateEvent_rx = new UpdateEvent("UpdateEventObject - dowork_rx");
//myUpdateEvent_rx->setvalue("first");
QCoreApplication::postEvent(m_pMainThreadObj->ptrmainwindow, myUpdateEvent_rx);
qDebug() << "\nrx throw event - done \n";
while(!m_bQuitRx)
{
SleepTimerDelay::sleep(2);
if(i==0){
//qDebug() << "\nrx throw event - done 11 \n";
myUpdateEvent_rx = new UpdateEvent("first");
myUpdateEvent_rx->setvalue("first");
QCoreApplication::postEvent(m_pMainThreadObj->ptrmainwindow, myUpdateEvent_rx);
i++;
}else{
//qDebug() << "\nrx throw event - done 22 \n";
myUpdateEvent_rx = new UpdateEvent("second");
myUpdateEvent_rx->setvalue("second");
QCoreApplication::postEvent(m_pMainThreadObj->ptrmainwindow, myUpdateEvent_rx);
i=0;
}
}
qDebug() << "\nrx end \n";
}
Event handler :----
/*!
** Custom event handler
*/
void MainWindow::customEvent(QEvent *event)
{
qDebug() << "oo customEvent";
if (UpdateEvent::is(event)) {
UpdateEvent *tempUpdateEvent = static_cast<UpdateEvent *>(event);
qDebug() << tempUpdateEvent->value();
}
else if (ClearEvent::is(event)) {
ClearEvent *tempClearEvent = static_cast<ClearEvent *>(event);
qDebug() << tempClearEvent->value();
}
}
/*!
** event filter handler
*/
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
qDebug() << "oo eventFilter";
if (UpdateEvent::is(event)) {
UpdateEvent *tempUpdateEvent = static_cast<UpdateEvent *>(event);
qDebug() << tempUpdateEvent->value();
}
else if (ClearEvent::is(event)) {
ClearEvent *tempClearEvent = static_cast<ClearEvent *>(event);
qDebug() << tempClearEvent->value();
}
return true;
}

Can I get mouse events in a QGraphicsItem?

I have subclassed QGraphicsRectItem, and it's not receiving any mouse events. I've seen other questions similar to this say I need to enable mouse tracking, but setMouseTracking is in QWidget, and QGraphicsItem does not appear to be a QWidget.
I've implemented paint, and that's working. In my subclassed QGraphicsView I am getting mouse events.
The docs seem to think I should just override the mousePressEvent function (for example) and I should start getting the events. Whether I forward the mousePressEvent to the superclass of my QGraphicsView or not doesn't seem to make any difference.
In your subclassed QGraphicsView, you need to call the default implementations of overridden mouse event methods if you want them to propagate down to the items. For example:
CustomView::mousePressEvent(QMouseEvent *event)
{
// handle the event as you like
QGraphicsView::mousePressEvent(event); // then call default implementation
}
If you want to accept hover events, you need to call QGraphicsItem::setAcceptHoverEvents(true);. Otherwise you do not need to enable any particular mouse tracking.
EDIT: Here is a full working example:
#include <QtGui>
class CustomView : public QGraphicsView
{
protected:
void mousePressEvent(QMouseEvent *event)
{
qDebug() << "Custom view clicked.";
QGraphicsView::mousePressEvent(event);
}
};
class CustomItem : public QGraphicsRectItem
{
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event)
{
qDebug() << "Custom item clicked.";
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
CustomItem item;
item.setRect(20, 20, 60, 60);
QGraphicsScene scene(0, 0, 100, 100);
scene.addItem(&item);
CustomView view;
view.setScene(&scene);
view.show();
return a.exec();
}
I went through the same problems you have encountered and I wanted to add some insights on top of Anthony's really good answer. Here is an example I wrote showing some features that can be implemented using the mouse events and the keyboard events.
Note that the events do not propagate to QGraphicsItems in a QGraphicsItemGroup or in a QList<QGraphicsItem> (it took me a while to figure that out).
#include <QtGui>
#include <QGraphicsRectItem>
#include <QGraphicsView>
#include <QApplication>
#include <QGraphicsSceneMouseEvent>
class CustomItem : public QGraphicsEllipseItem
{
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if(event->button() == Qt::LeftButton) {
if(event->modifiers() == Qt::ShiftModifier) {
qDebug() << "Custom item left clicked with shift key.";
// add the item to the selection
setSelected(true);
} else if(event->modifiers() == Qt::AltModifier){
qDebug() << "Custom item left clicked with alt key.";
// resize the item
double radius = boundingRect().width() / 2.0;
_center = QPointF(boundingRect().topLeft().x() + pos().x() + radius, boundingRect().topLeft().y() + pos().y() + radius);
QPointF pos = event->scenePos();
qDebug() << boundingRect() << radius << this->pos() << pos << event->pos();
double dist = sqrt(pow(_center.x()-pos.x(), 2) + pow(_center.y()-pos.y(), 2));
if(dist / radius > 0.8) {
qDebug() << dist << radius << dist / radius;
_isResizing = true;
} else {
_isResizing = false;
}
} else {
qDebug() << "Custom item left clicked.";
QGraphicsItem::mousePressEvent(event);
event->accept();
}
} else if(event->button() == Qt::RightButton) {
qDebug() << "Custom item right clicked.";
event->ignore();
}
}
void mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if(event->modifiers() == Qt::AltModifier && _isResizing){
QPointF pos = event->scenePos();
double dist = sqrt(pow(_center.x()-pos.x(), 2) + pow(_center.y()-pos.y(), 2));
setRect(_center.x()-this->pos().x()-dist, _center.y()-this->pos().y()-dist, dist*2, dist*2);
} else if(event->modifiers() != Qt::AltModifier) {
qDebug() << "Custom item moved.";
QGraphicsItem::mouseMoveEvent(event);
qDebug()<<"moved"<<pos();
}
}
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if(event->modifiers() == Qt::AltModifier && _isResizing) {
_isResizing = false;
} else if(event->modifiers() != Qt::ShiftModifier) {
QGraphicsItem::mouseReleaseEvent(event);
}
}
int type() const
{
// Enable the use of qgraphicsitem_cast with this item.
return UserType+1;
}
private:
QPointF _center;
bool _isResizing;
};
class CustomScene : public QGraphicsScene
{
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event)
{
qDebug() << "Custom scene clicked.";
QGraphicsScene::mousePressEvent(event);
if(!event->isAccepted()) {
if(event->button() == Qt::LeftButton) {
// add a custom item to the scene
QPointF pt = event->scenePos();
CustomItem * item = new CustomItem();
item->setRect(pt.x()-25, pt.y()-25, 50, 50);
item->setFlags(QGraphicsItem::ItemIsSelectable|
QGraphicsItem::ItemIsMovable);
addItem(item);
} else if(event->button() == Qt::RightButton) {
// check whether there is an item under the cursor
QGraphicsItem * itemToRemove = NULL;
foreach(auto item, items(event->scenePos())) {
if(item->type() == QGraphicsItem::UserType+1) {
itemToRemove = item;
break;
}
}
if(itemToRemove) {
// remove the item from the graphicsScene
removeItem(itemToRemove);
}
}
}
}
void mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
qDebug() << "Custom scene moved.";
QGraphicsScene::mouseMoveEvent(event);
}
void keyPressEvent(QKeyEvent * event) {
if(event->key() == Qt::Key_Backspace) {
// remove all selected items
qDebug() << "selected items" << selectedItems().size();
while(!selectedItems().isEmpty()) {
removeItem(selectedItems().front());
}
} else {
QGraphicsScene::keyPressEvent(event);
}
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
CustomItem item;
item.setRect(20, 20, 60, 60);
item.setFlags(QGraphicsItem::ItemIsSelectable|
QGraphicsItem::ItemIsMovable);
CustomScene scene;
scene.setSceneRect(0, 0, 500, 500);
scene.addItem(&item);
QGraphicsView view;
view.setScene(&scene);
view.show();
return a.exec();
}
Hope it helps too!
I had a similar problem with a view not accepting mouse clicks. The problem was that I needed to enable the view ( ui->view->setEnabled(true) ).

How to select child's items checkBoxs in QTreeView when select their parent's checkbox

I want to select/unselect all child's items QCheckBoxs when I select/unselect their parent's item QCheckBox.
i inherit from QTreeView and detect when the QCheckBox is selected then i call function to do selecting/unselecting process.
here my code:
#ifndef MYQTREEVIEW_H
#define MYQTREEVIEW_H
#include <QTreeView>
#include <QMouseEvent>
#include <QDebug>
#include <QStandardItem>
class MyQTreeView: public QTreeView {
public:
MyQTreeView(QWidget* parent=0): QTreeView(parent){}
virtual ~MyQTreeView() {}
protected:
void resettingCheckBox (QModelIndex& parentIndex) {
if ( ! parentIndex.isValid() )
return;
QString text = parentIndex.data( Qt::DisplayRole ).value<QString>();
qDebug() << "parent is: " << text;
if ( model()->hasChildren(parentIndex) ) {
for( int i = 0; i < model()->rowCount(parentIndex) ; i++ ) {
QModelIndex childIndex = model()->index( i, 0, parentIndex );
if ( model()->hasChildren(childIndex) )
resettingCheckBox(childIndex);
else {
QString text = childIndex.data( Qt::DisplayRole ).value<QString>();
qDebug() << "child is: " << text;
QStandardItem *parentItem = static_cast<QStandardItem*> (parentIndex.internalPointer());
QStandardItem *childItem = static_cast<QStandardItem*> (childIndex.internalPointer());
if ( parentItem->checkState() == Qt::Checked ) {
qDebug() << "child item " << childItem->checkState();
childItem->setCheckState( Qt::Unchecked);
}
else {
qDebug() << "child item " << childItem->checkState();
childItem->setCheckState( Qt::Checked);
}
}
}
}
}
void mousePressEvent (QMouseEvent *event) {
QModelIndex index = indexAt(event->pos());
if(index.isValid()) {
QStyleOptionButton opt;
opt.QStyleOption::operator=(viewOptions());
opt.rect = visualRect(index);
QRect rect = style()->subElementRect(QStyle::SE_ViewItemCheckIndicator, &opt);
if (rect.contains(event->pos())) {
resettingCheckBox(index);
}
QTreeView::mousePressEvent(event);
}
}
};
#endif // MYQTREEVIEW_H
the code is not working probably when i select/unselect parent checkBox (subchilds is not selected/unselected).
Thanks in advance.
I believe the best way to manipulate treeview items is through the model. It looks like you're using QStandardItemModel; so you can override your model's setData method and reset child items values for the item index passed as a parameter to this method. Below is a small example:
class TestModel : public QStandardItemModel
{
public:
TestModel() {}
bool setData(const QModelIndex & index, const QVariant & value, int role = Qt::EditRole)
{
QStandardItem *item = itemFromIndex(index);
for( int i = 0; i < item->rowCount() ; i++ )
{
QStandardItem *childItem = item->child(i);
setData(childItem->index(), value, role);
}
return QStandardItemModel::setData(index, value, role);
}
};
here's how this model gets initialized:
QStandardItemModel* tableModel = new TestModel();
QStandardItem* parentItem = tableModel->invisibleRootItem();
for (int i = 0; i < 4; ++i)
{
QStandardItem *item = new QStandardItem(QString("item %0").arg(i));
item->setCheckable(true);
parentItem->appendRow(item);
parentItem = item;
}
treeView->setModel(tableModel);
hope this helps, regards
It seems to me that you should call QTreeView::mousePressEvent(event) before resettingCheckBox(index), to let QTreeView update the checkState.
override mouseReleaseEvent() instead of mousePressEvent()!
because the checkState changes when mouse Release not mouse - press!

Resources