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.
Related
I am getting these two errors when initializing vc below.
#include <iostream>
#include <complex>
#include <math>
#include <vector>
#include <limits>
#include <list>
#include <string>
class Vector {
private:
double* elem; // elem points to an array of sz doubles
int sz;
public:
Vector(int s) :elem{ new double [s] }, sz{ s } // constructor: acquire resources
{
for (int i = 0; i != s; ++i) elem[i] = 0; // initialize elements
~Vector() { delete[] elem; } // destructor: release resources
double& operator[](int i);
int size() const;
void push_back(double);
};
double& Vector::operator[](int i)
{
// TODO: insert return statement here
// added below since the funtion needs to return a double and
return elem[i];
}
int Vector::size() const
{
return sz;
}
void Vector::push_back(double)
{
}
class Container {
public:
virtual double& operator[](int) = 0; // pure virtual function
virtual int size() const = 0; // const member function (§3.2.1.1)
virtual ~Container() {} // destructor (§3.2.1.2)
};
// use function uses Container interface.
void use(Container& c)
{
const int sz = c.size();
for (int i=0; i!=sz; ++i)
cout << c[i] << '\n';
}
class Vector_container : public Container { // List_container implements Container
Vector v;
public:
Vector_container(int s) : v(s) {} // Vector of s elements
void ˜Vector_container() {}
double& operator[](int i) { return v[i]; }
int size() const { return v.size(); }
};
void main()
{
Vector_container vc = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
use(vc)
}
I receive both errors pointing at this line Vector_container vc = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }
Error E0289 is - no instance of constructor "Vector_container::Vector_container" matches the argument list
Error C2440 'initializing': cannot convert from 'initializer list' to 'Vector_container'
I was able to solve the issue by modifying the Vector_container and initializing using Initializer-list constructor below:
class Vector_container : public Container {
std::list<double> ld;
public:
Vector_container() { }
Vector_container(initializer_list<double> il) : ld{ il } {}
~Vector_container() {}
double& operator[](int i);
int size() const { return ld.size(); }
};
double& Vector_container::operator[](int i)
{
for (auto& x : ld) {
if (i == 0) return x;
--i;
}
}
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);
}
}
}
}
I'm trying to call a function in an external script while passing a QObject as a parameter.
My QObject is defined as this:
#ifndef INSERTVALUES_H
#define INSERTVALUES_H
#include <QObject>
struct insertValueDef
{
QString name;
QString xmlCode;
QString value;
bool key;
bool insert;
};
typedef insertValueDef TinsertValueDef;
class insertValues : public QObject
{
Q_OBJECT
public:
explicit insertValues(QObject *parent = 0);
~insertValues();
void insertValue(TinsertValueDef value);
int count();
void setItemName(int index, QString name);
void setItemXMLCode(int index, QString xmlCode);
void setItemValue(int index, QString value);
void setItemIsKey(int index, bool isKey);
void setItemToInsert(int index, bool toInsert);
QString itemName(int index);
QString itemXMLCode(int index);
QString itemValue(int index);
bool itemIsKey(int index);
bool itemToInsert(int index);
bool valueIsNumber(int index);
int getIndexByColumnName(QString name);
private:
QList<TinsertValueDef> m_insertList;
};
#endif // INSERTVALUES_H
My JS Script function is this:
function beforeInsert(table,data)
{
if (table == "tmpTable")
{
var index = data.getIndexByColumnName("tmpfield");
if (index >= 0)
{
data.setItemValue(index,"Carlos Quiros");
}
}
}
The code that runs runs the script is the following:
QFile scriptFile(javaScript);
if (!scriptFile.open(QIODevice::ReadOnly))
{
log("Error: Script file defined but cannot be opened");
return 1;
}
JSEngine.evaluate(scriptFile.readAll(), javaScript);
scriptFile.close();
insertValues insertObject;
TinsertValueDef tfield;
tfield.key = false;
tfield.name = "tmpfield";
tfield.xmlCode = "tmpCode";
tfield.value = "tmpValue";
tfield.insert = true;
insertObject.insertValue(tfield);
QString error;
beforeInsertFunction = JSEngine.evaluate("beforeInsert",error);
if (!beforeInsertFunction.isError())
{
QJSValue insertListObj = JSEngine.newQObject(&insertObject);
QJSValue result = beforeInsertFunction.call(QJSValueList() << "tmpTable" << insertListObj);
if (result.isError())
{
log("Error calling BeforInsert JS function.");
return 1;
}
else
{
log("JS function seems to be ok");
for (int pos = 0; pos < insertObject.count(); pos++)
{
log(insertObject.itemName(pos) + "-" + insertObject.itemValue(pos));
}
return 1;
}
}
else
{
log("Error evaluating BeforInsert JS function. [" + error + "]");
return 1;
}
I can see that the parameter "table" is passing properly but the rest of the code is not working. I guess I cannot do:
var index = data.getIndexByColumnName("tmpfield");
Any idea what am I doing wrong? and what else should I do to make it work?
Thanks,
In order to access properties or invoke methods of QObjects passed to QJSEngine (or to QML), you need to declare them using Q_PROPERTY and Q_INVOKABLE macros in your QObject-derived class declaration.
Please see the Qt documentation for more details: Exposing Attributes of C++ Types to QML
It is not possible to capture an argument that has been passed as reference with a QSignalSpy:
QSignalSpy spy( myObject, SIGNAL(foo(int&)));
...
int& i=spy.at(0).at(0).value<int&>();
Since a QVariant can not contain a reference member. Plain logic.
But are there other solutions to check the passed-in argument?
Since Qt 5, we can simply connect to a lambda function, which makes the use of the QSignalSpy unnecessary:
std::vector<Value> values;
QObject::connect(myObject, &MyObject::foo,
[&](const auto &value)
{ values.emplace_back(value); });
myObject.somethingCausingFoo();
ASSERT_EQ(1u, values.size());
EXPECT_EQ(expectedValue, values.at(0));
An "ugly solution" would be to hack the fairly simple QSignalSpy code in order to handle the reference passed arguments. I provide a minimal working example for int reference arguments. The only changes were made to initArgs and appendArgs functions.
Notice that with this approach you will only be able to check the value of the passed argument by reference. You will not be able to change it's value.
In the initArgs function we check if we have references by argument and we populate the shouldreinterpret list.
void initArgs(const QMetaMethod &member)
{
QList<QByteArray> params = member.parameterTypes();
for (int i = 0; i < params.count(); ++i) {
int tp = QMetaType::type(params.at(i).constData());
if (tp == QMetaType::Void)
{
qWarning("Don't know how to handle '%s', use qRegisterMetaType to register it.",
params.at(i).constData());
// Check if we have a reference by removing the & from the parameter name
QString argString(params.at(i).constData());
argString.remove("&");
tp = QMetaType::type(argString.toStdString().c_str());
if (tp != QMetaType::Void)
shouldReinterpret << true;
}
else
shouldReinterpret << false;
args << tp;
}
}
and the appendArgs function, where we reinterpret the passed by reference arguments:
void appendArgs(void **a)
{
QList<QVariant> list;
for (int i = 0; i < args.count(); ++i) {
QMetaType::Type type = static_cast<QMetaType::Type>(args.at(i));
if (shouldReinterpret.at(i))
{
switch (type)
{
case QMetaType::Int:
list << QVariant(type, &(*reinterpret_cast<int*>(a[i + 1])));
break;
// Do the same for other types
}
}
else
list << QVariant(type, a[i + 1]);
}
append(list);
}
Complete code for reference:
class MySignalSpy: public QObject, public QList<QList<QVariant> >
{
public:
MySignalSpy(QObject *obj, const char *aSignal)
{
#ifdef Q_CC_BOR
const int memberOffset = QObject::staticMetaObject.methodCount();
#else
static const int memberOffset = QObject::staticMetaObject.methodCount();
#endif
Q_ASSERT(obj);
Q_ASSERT(aSignal);
if (((aSignal[0] - '0') & 0x03) != QSIGNAL_CODE) {
qWarning("QSignalSpy: Not a valid signal, use the SIGNAL macro");
return;
}
QByteArray ba = QMetaObject::normalizedSignature(aSignal + 1);
const QMetaObject *mo = obj->metaObject();
int sigIndex = mo->indexOfMethod(ba.constData());
if (sigIndex < 0) {
qWarning("QSignalSpy: No such signal: '%s'", ba.constData());
return;
}
if (!QMetaObject::connect(obj, sigIndex, this, memberOffset,
Qt::DirectConnection, 0)) {
qWarning("QSignalSpy: QMetaObject::connect returned false. Unable to connect.");
return;
}
sig = ba;
initArgs(mo->method(sigIndex));
}
inline bool isValid() const { return !sig.isEmpty(); }
inline QByteArray signal() const { return sig; }
int qt_metacall(QMetaObject::Call call, int methodId, void **a)
{
methodId = QObject::qt_metacall(call, methodId, a);
if (methodId < 0)
return methodId;
if (call == QMetaObject::InvokeMetaMethod) {
if (methodId == 0) {
appendArgs(a);
}
--methodId;
}
return methodId;
}
private:
void initArgs(const QMetaMethod &member)
{
QList<QByteArray> params = member.parameterTypes();
for (int i = 0; i < params.count(); ++i) {
int tp = QMetaType::type(params.at(i).constData());
if (tp == QMetaType::Void)
{
qWarning("Don't know how to handle '%s', use qRegisterMetaType to register it.",
params.at(i).constData());
QString argString(params.at(i).constData());
argString.remove("&");
tp = QMetaType::type(argString.toStdString().c_str());
if (tp != QMetaType::Void)
shouldReinterpret << true;
}
else
shouldReinterpret << false;
args << tp;
}
}
void appendArgs(void **a)
{
QList<QVariant> list;
for (int i = 0; i < args.count(); ++i) {
QMetaType::Type type = static_cast<QMetaType::Type>(args.at(i));
if (shouldReinterpret.at(i))
{
switch (type)
{
case QMetaType::Int:
int k = (*reinterpret_cast<int*>(a[i + 1]));
list << QVariant(type, &k);
break;
}
}
else
list << QVariant(type, a[i + 1]);
}
append(list);
}
// the full, normalized signal name
QByteArray sig;
// holds the QMetaType types for the argument list of the signal
QList<int> args;
// Holds the indexes of the arguments that
QList<bool> shouldReinterpret;
};
I'm studying WidgetMarqueeLabel class:
#include "WidgetMarqueeLabel.h"
#include <QPainter>
#include <QWidget>
WidgetMarqueeLabel::WidgetMarqueeLabel(QWidget *parent)//*parent)
{
px = 0;
py = 10;
speed = 1;
direction = RightToLeft;
connect(&timer3, SIGNAL(timeout()), this, SLOT(refreshLabel()));
timer3.start(10);
}
void WidgetMarqueeLabel::refreshLabel()
{
repaint();
}
WidgetMarqueeLabel::~WidgetMarqueeLabel()
{}
void WidgetMarqueeLabel::show()
{
QLabel::show();
}
void WidgetMarqueeLabel::setAlignment(Qt::Alignment al)
{
m_align = al;
updateCoordinates();
QLabel::setAlignment(al);
}
void WidgetMarqueeLabel::paintEvent(QPaintEvent *evt)
{
QPainter p(this);
if(direction==RightToLeft)
{
px -= speed;
if(px <= (-textLength))
px = width();
}
else
{
px += speed;
if(px >= width())
px = - textLength;
}
p.drawText(px, py+fontPointSize, text());
p.translate(px,0);
}
void WidgetMarqueeLabel::resizeEvent(QResizeEvent *evt)
{
updateCoordinates();
QLabel::resizeEvent(evt);
}
void WidgetMarqueeLabel::updateCoordinates()
{
switch(m_align)
{
case Qt::AlignTop:
py = 10;
break;
case Qt::AlignBottom:
py = height()-10;
break;
case Qt::AlignVCenter:
py = height()/2;
break;
}
fontPointSize = font().pointSize()/2;
textLength = fontMetrics().width(text());
}
void WidgetMarqueeLabel::setSpeed(int s)
{
speed = s;
}
int WidgetMarqueeLabel::getSpeed()
{
return speed;
}
void WidgetMarqueeLabel::setDirection(int d)
{
direction = d;
if (direction==RightToLeft)
px = width() - textLength;
else
px = 0;
refreshLabel();
}
void WidgetMarqueeLabel::close()
{
QLabel::close();
}
I was wondering if it was possible to make the text reappear before the text that reaches the end of the last letter on the right. I want something like this: for example (white space are 25):
WidgetMarqueeLabel
tMarqueeLabel Widge
eLabel WidgetMarque
el WidgetMarqueeLa
WidgetMarqueeLabel
WidgetMarqueeLabel
WidgetMarqueeLabel
WidgetMarqueeLabel
Is this possible?
For this purpose, I once wrote a class.
Example screenshot showing the text "This is an example text. It will be scrolled horizontally.". Note the alpha blending at both sides.
The code:
scrolltext.h:
#ifndef SCROLLTEXT_H
#define SCROLLTEXT_H
#include <QWidget>
#include <QStaticText>
#include <QTimer>
class ScrollText : public QWidget
{
Q_OBJECT
Q_PROPERTY(QString text READ text WRITE setText)
Q_PROPERTY(QString separator READ separator WRITE setSeparator)
public:
explicit ScrollText(QWidget *parent = 0);
public slots:
QString text() const;
void setText(QString text);
QString separator() const;
void setSeparator(QString separator);
protected:
virtual void paintEvent(QPaintEvent *);
virtual void resizeEvent(QResizeEvent *);
private:
void updateText();
QString _text;
QString _separator;
QStaticText staticText;
int singleTextWidth;
QSize wholeTextSize;
int leftMargin;
bool scrollEnabled;
int scrollPos;
QImage alphaChannel;
QImage buffer;
QTimer timer;
private slots:
virtual void timer_timeout();
};
#endif // SCROLLTEXT_H
scrolltext.cpp:
#include "scrolltext.h"
#include <QPainter>
ScrollText::ScrollText(QWidget *parent) :
QWidget(parent), scrollPos(0)
{
staticText.setTextFormat(Qt::PlainText);
setFixedHeight(fontMetrics().height());
leftMargin = height() / 3;
setSeparator(" --- ");
connect(&timer, SIGNAL(timeout()), this, SLOT(timer_timeout()));
timer.setInterval(50);
}
QString ScrollText::text() const
{
return _text;
}
void ScrollText::setText(QString text)
{
_text = text;
updateText();
update();
}
QString ScrollText::separator() const
{
return _separator;
}
void ScrollText::setSeparator(QString separator)
{
_separator = separator;
updateText();
update();
}
void ScrollText::updateText()
{
timer.stop();
singleTextWidth = fontMetrics().width(_text);
scrollEnabled = (singleTextWidth > width() - leftMargin);
if(scrollEnabled)
{
scrollPos = -64;
staticText.setText(_text + _separator);
timer.start();
}
else
staticText.setText(_text);
staticText.prepare(QTransform(), font());
wholeTextSize = QSize(fontMetrics().width(staticText.text()), fontMetrics().height());
}
void ScrollText::paintEvent(QPaintEvent*)
{
QPainter p(this);
if(scrollEnabled)
{
buffer.fill(qRgba(0, 0, 0, 0));
QPainter pb(&buffer);
pb.setPen(p.pen());
pb.setFont(p.font());
int x = qMin(-scrollPos, 0) + leftMargin;
while(x < width())
{
pb.drawStaticText(QPointF(x, (height() - wholeTextSize.height()) / 2) + QPoint(2, 2), staticText);
x += wholeTextSize.width();
}
//Apply Alpha Channel
pb.setCompositionMode(QPainter::CompositionMode_DestinationIn);
pb.setClipRect(width() - 15, 0, 15, height());
pb.drawImage(0, 0, alphaChannel);
pb.setClipRect(0, 0, 15, height());
//initial situation: don't apply alpha channel in the left half of the image at all; apply it more and more until scrollPos gets positive
if(scrollPos < 0)
pb.setOpacity((qreal)(qMax(-8, scrollPos) + 8) / 8.0);
pb.drawImage(0, 0, alphaChannel);
//pb.end();
p.drawImage(0, 0, buffer);
}
else
{
p.drawStaticText(QPointF(leftMargin, (height() - wholeTextSize.height()) / 2), staticText);
}
}
void ScrollText::resizeEvent(QResizeEvent*)
{
//When the widget is resized, we need to update the alpha channel.
alphaChannel = QImage(size(), QImage::Format_ARGB32_Premultiplied);
buffer = QImage(size(), QImage::Format_ARGB32_Premultiplied);
//Create Alpha Channel:
if(width() > 64)
{
//create first scanline
QRgb* scanline1 = (QRgb*)alphaChannel.scanLine(0);
for(int x = 1; x < 16; ++x)
scanline1[x - 1] = scanline1[width() - x] = qRgba(0, 0, 0, x << 4);
for(int x = 15; x < width() - 15; ++x)
scanline1[x] = qRgb(0, 0, 0);
//copy scanline to the other ones
for(int y = 1; y < height(); ++y)
memcpy(alphaChannel.scanLine(y), (uchar*)scanline1, width() * 4);
}
else
alphaChannel.fill(qRgb(0, 0, 0));
//Update scrolling state
bool newScrollEnabled = (singleTextWidth > width() - leftMargin);
if(newScrollEnabled != scrollEnabled)
updateText();
}
void ScrollText::timer_timeout()
{
scrollPos = (scrollPos + 2)
% wholeTextSize.width();
update();
}
Really easy. Simply repaint the text displaced by the width of the control:
void WidgetMarqueeLabel::paintEvent(QPaintEvent *evt)
{
QPainter p(this);
if(direction==RightToLeft)
{
px -= speed;
if(px <= (-textLength))
px = width();
}
else
{
px += speed;
if(px >= width())
px = - textLength;
}
p.drawText(px, py+fontPointSize, text());
__p.drawText(px-width(), py+fontPointSize, text());
p.drawText(px+width(), py+fontPointSize, text());
p.translate(px,0);
}
Something like the following should work. The padding is hard-coded at 25, which is what is sounded like you wanted. If you wanted the label to always be a certain size, you could use something like QString::leftJustified.
class MarqueeLabel : public QLabel {
public:
explicit MarqueeLabel(const QString &text) : QLabel(text), pos_(0) {
QString pad(25, ' ');
actual_text_ = text + pad;
startTimer(100);
}
protected:
void timerEvent(QTimerEvent *) {
pos_ = ++pos_ % actual_text_.length();
setText(actual_text_.mid(pos_).append(actual_text_.left(pos_)));
}
private:
QString actual_text_;
int pos_;
};
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
MarqueeLabel lbl("WidgetMarqueeLabel");
lbl.show();
return a.exec();
}