I have a QTimeEdit widget on my dialog and I want to provide some kind of autochange - if the cursor is on minutes section and the time is 04:59, the next click on the arrow up would lead the time to change to 5:00.
How to do that?
I saw some mention of AutoAdvance property but I suppose it's obsolete because I cannot find it in Qt 4.7.
I noticed there is a signal called void timeChanged ( const QTime & time ). You can connect it to a slot and call function void QAbstractSpinBox::stepBy ( int steps ) in the slot function.
EDIT1:
Sorry for the misleading. In fact, we don't really need void timeChanged ( const QTime & time ).
See the code below:
class myTime : public QTimeEdit
{
Q_OBJECT
public:
virtual void stepBy(int steps)
{
if (this->time().minute()==59 && steps>0){
setTime(QTime(time().hour()+1,0,time().second(),time().msec()));
}else if(this->time().minute()==00 && steps<0){
setTime(QTime(time().hour()-1,59,time().second(),time().msec()));
}else{
QTimeEdit::stepBy(steps);
}
}
};
Keep in mind, you need to setWrapping(true) yourself.
I don't know whether it's still interesting, but I found another solution:
class myTime : public QTimeEdit {
Q_OBJECT public:
virtual void stepBy(int steps)
{
long lFactor=1;
if (currentSection()==QDateTimeEdit::MinuteSection)
lFactor=60;
else if (currentSection()==QDateTimeEdit::HourSection)
lFactor=3600;
long lDateTime = (dateTime().toMSecsSinceEpoch()/1000)+(steps*lFactor);
QDateTime dt = QDateTime::fromMSecsSinceEpoch(1000*(qint64)(lDateTime));
setDateTime(dt);
} };
If some one is wondering how to automatically change time (because I did), and needs easy, rather clean solution, here's what I came up with:
class TimeWidget : public QTimeEdit {
Q_OBJECT
public:
void stepBy(int steps) {
//Grab the current time
QTime t = time();
//So we edit the time as in raw milliseconds. Larger type would be better in this case.
//But 32bits is more than enough for day amount of milliseconds.
int raw = (t.hour()*360000 + t.minute()*60000 + t.second() * 1000 + t.msec());
//Let's find out what section time widget is changing
//And edit the raw millisecond data accordingly.
switch(currentSection()) {
case QDateTimeEdit::MSecSection:
raw += steps;
break;
case QDateTimeEdit::SecondSection:
raw += steps*1000;
break;
case QDateTimeEdit::MinuteSection:
raw+= steps*60000;
break;
case QDateTimeEdit::HourSection:
raw += steps*3600000;
break;
}
//Now we just split the "raw" milliseconds into
int hrs = (raw/ 36000000) % 24; //... hours
int mins = (raw/ 60000) % 60; //... minutes
int secs = (raw / 1000) % 60; //... seconds
int mills = raw % 1000; //... milliseconds
//Update & save the current time
t.setHMS(hrs, mins, secs, mills);
setTime(t);
}
};
Related
Is there a way to get a list of visible days for the current month view (including the ones from previous/next month that are also visible)
If we inspect the code of the Calendar here we can find a suspicious property __model. We find, it has always a count of 42 - which is the amount of days that are visible.
The problem is, that in QML we can't really access descendants of QAbstractItemModels, as we need a QModelIndex to fetch data, struggle with int instead of roleNames to identify the roles e.t.c.
So we need to introduce a little helper, that offers a get(index) function to us, as known from the ListModel. As base for this, we can use the QIdentityProxyModel that we'll extend by the missing, exposed method.
qmlproxymodel.h
#ifndef QMLPROXYMODEL_H
#define QMLPROXYMODEL_H
#include <QObject>
#include <QIdentityProxyModel>
#include <QMap>
class QMLProxyModel : public QIdentityProxyModel
{
Q_OBJECT
public:
QMLProxyModel(QObject* parent = nullptr);
public slots:
QMap<QString, QVariant> get(int row_index) const;
};
#endif // QMLPROXYMODEL_H
qmlproxymodel.cpp
#include "qmlproxymodel.h"
QMLProxyModel::QMLProxyModel(QObject* parent)
: QIdentityProxyModel(parent)
{
}
QMap<QString, QVariant> QMLProxyModel::get(int row_index) const
{
QMap<QString, QVariant> ret;
QModelIndex ind = index(row_index, 0);
QHash<int, QByteArray> roles = roleNames();
// for some reason the itemData-method always throw a index-out-of-range exception
for (int e : roles.keys()) {
ret.insert(roles.value(e), data(ind, e));
}
return ret;
}
Then we register this new type to QML in our main.cpp
qmlRegisterType<QMLProxyModel>("QMLProxyModel", 1, 0, "QMLProxyModel");
Finally we use it in QML:
Calendar {
id: cal
anchors.fill: parent
}
QMLProxyModel {
id: pm
sourceModel: cal.__model
onDataChanged: console.log("Some Data has changed", pm.get(0).date)
}
Here I output the date of the first displayed date whenever the data of the model changes - that is, when the next month is selected.
It should be easy for you, to write a JS function to iterate over all (42) elements of the QMLProxyModel to add the content to any data structure, you desire.
I found a quicker workaround if anyone is interested. The following code considers week starting on Monday or Sunday.
I check what day is the first day of the month, and get the Monday/Sunday of the first week, and then I check through day = firstDay + X where X goes from 0 to 42.
function checkMonthlyCases(currentYear, currentMonth){
var firstMonthDay = new Date(currentYear,currentMonth,1,0)
var firstVisibleDay = new Date(firstMonthDay.getTime() - (((firstMonthDay.getDay() === Locale.Sunday) ? Qt.Sunday : firstMonthDay.getDay()) - 1) * 86400000 )
if( startDay == Qt.Sunday){
firstVisibleDay = new Date(firstVisibleDay.getTime() - 86400000);
}
for(var i=0; i< 42; i++){ // 42: number of visible days
var checkDay = new Date(firstVisibleDay.getTime() + i*86400000)
var count = getDayCaseCount(checkDay)
}
}
EDIT: I have read Passing and argument to a slot it's helpful, but doesn't address my issue of passing multiple references to a function I called via a signal-slot.
I'm currently working on a Qt application that essentially is a unit converter. I'm implementing it using QDoubleSpinBoxes as the input and the output. I am running into an issue that i'm looking for help with. The implementation idea is that the user will input a value of whatever they want to convert and it will, upon the user losing focus on the spinbox or hitting enter, populate the other unit type boxes with the answer.
Here is how I currently do it:
// creates a custom spinbox for each option
modifiedSpinbox *FahrenheitDblSpinbox = new modifiedSpinbox();
modifiedSpinbox *CelciusDblSpinbox = new modifiedSpinbox();
modifiedSpinbox *KelvinDblSpinbox = new modifiedSpinbox();
modifiedSpinbox *RankineDblSpinbox = new modifiedSpinbox();
// Creates a signal mapper that allows passing of a parameter
// to the convert functions anytime a number is entered
QSignalMapper *tempMapper = new QSignalMapper(this);
// Connects the spinbox editing complete signal with the mapper
connect(tempMapper, SIGNAL(mapped(int)),
this,SLOT(on_userInput(int whoSignaled)));
// Connects the mapper with the function that calls the convert class
connect(FahrenheitDblSpinbox, SIGNAL(editingFinished()),
tempMapper, SLOT(map()));
tempMapper->setMapping(FahrenheitDblSpinbox, 1);
The way I would like to implement this conversion function is to, on user finishing their input into a spinbox, have the code send a signal (editingFinished()) to a slot function which calls a converttools class that has the functions needed to do the actual conversion. The problem that i'm running in to is that I cannot figure out how to pass references for these spinbox objects to my converttools class so that I can set the spinbox values directly.
The closest i've come is to use QSignalMapper (seen above) to pass a single int or Qwidget to my slot function, not the objects I want.
I would like some advice as to how to pass multiple references to my custom class after a signal is emitted. I've looked though numerous questions here and still cant seem to figure out how to do this or a better way i'm not seeing.
Thanks!
QSignalMapper is obsolete in C++11. It's only a band-aid against the cumbersomeness of declaring functors in C++98. If you're using a C++11 compiler, you never have to use QSignalMapper.
Lambdas make it trivial - and you really don't need to pass much. Side note: ideally, you should use a modern C++11 units library.
Here's a complete example:
// https://github.com/KubaO/stackoverflown/tree/master/questions/tempconvert-42648860
#include <QtWidgets>
const char kUnit[] = "unit";
class MyWidget : public QWidget {
QFormLayout m_layout{this};
QDoubleSpinBox m_fahrenheit, m_celsius, m_kelvin;
QList<QDoubleSpinBox*> const m_spinBoxes{&m_fahrenheit, &m_celsius, &m_kelvin};
enum Unit { Fahrenheit, Celsius, Kelvin };
static double Q_DECL_RELAXED_CONSTEXPR fromK(Unit to, double val) {
if (to == Fahrenheit) return (val-273.15)*1.8 + 32.0;
else if (to == Celsius) return val - 273.15;
else return val;
}
static double Q_DECL_RELAXED_CONSTEXPR toK(Unit from, double val) {
if (from == Fahrenheit) return (val-32.0)/1.8 + 273.15;
else if (from == Celsius) return val + 273.15;
else return val;
}
void setTemp(Unit unit, double temp, QDoubleSpinBox * skip = nullptr) {
for (auto spin : m_spinBoxes) if (spin != skip) {
QSignalBlocker b{spin};
spin->setValue(fromK(unitOf(spin), toK(unit, temp)));
}
}
static Unit unitOf(QDoubleSpinBox * spin) {
return static_cast<Unit>(spin->property(kUnit).toInt());
}
public:
MyWidget(QWidget * parent = nullptr) : QWidget{parent} {
m_layout.addRow("Fahreneheit", &m_fahrenheit);
m_layout.addRow("Celsius", &m_celsius);
m_layout.addRow("Kelvin", &m_kelvin);
m_fahrenheit.setProperty(kUnit, Fahrenheit);
m_celsius.setProperty(kUnit, Celsius);
m_kelvin.setProperty(kUnit, Kelvin);
for (auto const spin : m_spinBoxes) {
auto const unit = unitOf(spin);
spin->setRange(fromK(unit, 0.), fromK(unit, 1000.));
connect(spin, static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged),
[=]{ setTemp(unit, spin->value(), spin); });
}
setTemp(Celsius, 20.);
}
};
int main(int argc, char ** argv) {
QApplication app{argc, argv};
MyWidget ui;
ui.show();
return app.exec();
}
What you're looking for is:
Signals to carry the QDoubleSpinBox's assigned temperature
A conversion to a common temperature unit
A Signal to all other QDoubleSpinBoxs to update them
A conversion from common temperature unit to each QDoubleSpinBox's specific temperature unit
QSignalMapper is a bad choice here, because:
This class collects a set of parameterless signals, and re-emits them with integer, string or widget parameters corresponding to the object that sent the signal
So we cannot take in the assigned temperature. Instead lets start with a map<QDoubleSpinBox*, pair<function<double(double)>, function<double(double)>>> which will serve to map from a given QDoubleSpinBox to its "conversion to a common temperature unit" and "conversion from a common temperature unit", respectively.
We'll then build an object around this map looking something like this:
class SlotMapper : public QObject
{
Q_OBJECT
map<QDoubleSpinBox*, pair<function<double(double)>, function<double(double)>>> mapping;
public:
SlotMapper() = default;
SlotMapper(const map<QDoubleSpinBox*, pair<function<double(double)>, function<double(double)>>> mapping) : mapping(mapping) {};
AddMapping(QDoubleSpinBox* key, function<double(double)> valueFirst, function<double(double)> valueSecond) { mapping.insert_or_assign(key, make_pair(valueFirst, valueSecond)); }
void map(const double assignedTemperature) const {
const auto commonTemperatureUnit = mapping.at(QObject()::sender).first(assignedTemperature);
for(auto it = cbegin(mapping); it != cend(mapping); ++it) {
if(it->first != QObject()::sender) {
it->first->blockSignals(true);
it->first->setValue(it->second.second(commonTemperatureUnit));
it->first->blockSignals(false);
}
}
}
};
This object should be constructed with all necessary conversion functions. in your case that probably looks something like:
SlotMapper mySlotMapper(map<QDoubleSpinBox*, pair<function<double(double)>, function<double(double)>>>{ {FahrenheitDblSpinbox, make_pair([](const double param){ return (param - 32.0) * 5.0 / 9.0; }, [](const double param){ return param * 9.0 / 5.0 + 32.0; })},
{CelciusDblSpinbox, make_pair([](const double param){ return param; }, [](const double param){ return param; })},
{KelvinDblSpinbox, make_pair([](const double param){ return param - 273.15; }, [](const double param){ return param + 273.15; })},
{RankineDblSpinbox, make_pair([](const double param){ return (param - 491.67) * 5.0 / 9.0; }, [](const double param){ return (param + 273.15) * 9.0 / 5.0; })} });
As far as your connections, they'll look like:
connect(FahrenheitDblSpinbox, static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), &mySlotMapper, &SlotMapper::map);
connect(CelciusDblSpinbox, static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), &mySlotMapper, &SlotMapper::map);
connect(KelvinDblSpinbox, static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), &mySlotMapper, &SlotMapper::map);
connect(RankineDblSpinbox, static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), &mySlotMapper, &SlotMapper::map);
I have my custom list model where I put data which should be displayed on the QML view. But for some kind of reason the view in QML sometimes is updated normally, sometimes with previous data and sometimes updating is not performed.
Here is the function where I fill the model - this function is called from some other thread.
void MyScreen::fillListModel()
{
const QString SEPARATOR = " ";
myListModel->resetModel();
for (int i = 0; i < MAX_ROWS; ++i)
{
QString key = QString::fromUtf16(MyData::getParameterKey(i).c_str());
QString val = QString::fromUtf16(MyData::getParameterVal(i).c_str());
myListModel->addItem(key + SEPARATOR + val);
}
}
Implementation of model reset:
void BrowsingModelBase::resetModel()
{
beginResetModel();
m_items.clear();
endResetModel();
}
Implementation of addItem():
void BrowsingModelBase::addItem(const BrowsingItemModelBase &item)
{
int count = m_items.size();
beginInsertRows(QModelIndex(), count, count);
m_items.append(item);
endInsertRows();
}
Finally my QML file:
MyScreen {
Column {
id: myFlowList
y: 110
x: 220
ListView {
height:1000
spacing: 35;
model: myListModelRoot.myListModel
delegate: Text {
text: text1
}
}
}
}
The strange thing is that after loop with line
myListModel->addItem(key + SEPARATOR + val);
when I print logs with data from myListModel it is filled with proper data, but the view is usually updated with previous data. Is it possible that data change signal is stuck somewhere? Any idea what is the solution?
Assuming that you call your model's methods from another thread, with the model being fundamentally not thread-safe, you have two options:
Make certain methods of your model thread-safe, or
Explicitly invoke the methods in a thread-safe fashion.
But first, you'd gain a bit of performance by adding all the items at once, as a unit. That way, the model will emit only one signal for all of the rows, instead of one signal per row. The views will appreciate it very much.
class BrowsingModelBase {
...
};
Q_DECLARE_METATYPE(QList<BrowsingItemModelBase>)
void BrowsingModelBase::addItems(const QList<BrowsingItemModelBase> & items)
{
beginInsertRows(QModelIndex(), m_items.size(), m_items.size() + items.size() - 1);
m_items.append(items);
endInsertRows();
}
You should probably also have a method called clear instead of resetModel, since to reset a model has a much more general meaning: "change it so much that it's not worth emitting individual change signals". To reset a model does not mean "clear it"! Thus:
void BrowsingModelBase::clear()
{
beginResetModel();
m_items.clear();
endResetModel();
}
Finally, following the 2nd approach of safely invoking the model's methods, fillListModel becomes as follows. See this answer for discussion of postTo.
template <typename F>
void postTo(QObject * obj, F && fun) {
if (obj->thread() != QThread::currentThread()) {
QObject signalSource;
QObject::connect(&signalSource, &QObject::destroyed, obj, std::forward<F>(fun));
} else
fun();
}
void MyScreen::fillListModel()
{
auto separator = QStringLiteral(" ");
QList<BrowserItemModelBase> items;
for (int i = 0; i < MAX_ROWS; ++i) {
auto key = QString::fromUtf16(MyData::getParameterKey(i).c_str());
auto val = QString::fromUtf16(MyData::getParameterVal(i).c_str());
items << BrowserItemModelBase(key + separator + val);
}
postTo(myListModel, [this, items]{
myListModel->clear();
myListModel->addItems(items);
});
}
Alternatively, following the first approach, you can make the clear and addItems methods thread-safe:
/// This method is thread-safe.
void BrowsingModelBase::addItems(const QList<BrowsingItemModelBase> & items)
{
postTo(this, [this, items]{
beginInsertRows(QModelIndex(), m_items.size(), m_items.size() + items.size() - 1);
m_items.append(items);
endInsertRows();
});
}
/// This method is thread-safe.
void BrowsingModelBase::clear()
{
postTo(this, [this]{
beginResetModel();
m_items.clear();
endResetModel();
});
}
You then need no changes to fillListModel, except to make it use addItems:
void MyScreen::fillListModel()
{
auto separator = QStringLiteral(" ");
myListModel->clear();
QList<BrowserItemModelBase> items;
for (int i = 0; i < MAX_ROWS; ++i) {
auto key = QString::fromUtf16(MyData::getParameterKey(i).c_str());
auto val = QString::fromUtf16(MyData::getParameterVal(i).c_str());
items << BrowserItemModelBase(key + separator + val);
}
myListModel->addItems(items);
}
The problem is most likely the fact that you are calling the fillListModel() method not from the main GUI thread. You can update the model from other threads but the beginResetModel();, endResetModel();, beginInsertRows(QModelIndex(), count, count);... methods have to be called in the main GUI thread.
One way to call these method in GUI thread (maybe not the most efficient) is to :
Create signals for each method you want to call:
signals:
//these signals are emitted from worker thread
void requestBeginResetModel();
void requestEndResetModel();
Create slots that will actually call the methods:
private slots:
//these slots execute the model reset operations in main thread
void callBeginResetModel();
void callEndResetModel();
Connect the signals and the slots:
//connect the appropriate signals
connect(this, SIGNAL(requestBeginResetModel()),
this, SLOT(callBeginResetModel()));
connect(this, SIGNAL(requestEndResetModel()),
this, SLOT(callEndResetModel()));
Your reset model will then be:
void BrowsingModelBase::resetModel()
{
emit requestBeginResetModel();
m_items.clear();
emit requestEndResetModel();
}
Finally the slots are implemented as:
void ObjectModel::callBeginResetModel()
{
beginResetModel();
}
void ObjectModel::callEndResetModel()
{
endResetModel();
}
Note that you will have to do the same for row insert methods as well. Or alternatively you could fill you model in the resetModel() method in between emitted signals.
I'm developing an OpenGL benchmark for a Qt application using VBOs and not glBegin/glEnd stuff.
I call the the class GLWidget (that inherits from public QGLWidget and protected QGLFunctions) from the MainWindow and I want to wait for the rendering to finish and get the value of the variable that stores the time elapsed. Problem is I don't find a suitable SIGNAL / method of waiting for the finish of a QGLWidget, so I get wrong variable values from MainWindow even if I use glFinish() at the end of the rendering.
void GLWidget::initializeGL() {
// VBO, glGenBuffers, glBindBuffer, glBufferData stuff
}
void GLWidget::paintGL()
{
QGLFunctions::glEnableVertexAttribArray(0);
QGLFunctions::glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
QGLFunctions::glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,(void*)0);
// Start Time
startTime = clock();
glDrawArrays(GL_TRIANGLES, 0, numVertices);
// Wait for the rendering
glFinish();
// Finish Time
finishTime = clock();
relativeTime = finishTime - startTime;
totalTime = (float) relativeTime / CLOCKS_PER_SEC;
QGLFunctions::glDisableVertexAttribArray(0);
}
You can create your own two signals
void startPaint() and void endPaint()
Than catch them somwhere(MainWindow maybe) and void startPaint() will start timer, and void endPaint() will stop timer and store value somewhere. Qt class QElapsedTimer should do the job.
All developer could you show me how to create a count down time with c++ Qt? If you can, you should to show me a source code.
You could use something like that. It calls timeOutSlot every second.
#define TIMEOUT 60
...
QTimer * timer = new QTimer();
connect(timer,SIGNAL(timeout()),this,SLOT(timeOutSlot()));
timer->start(1000);
...
void timeOutSlot()
{
static int time = TIMEOUT;
time--; // decrement counter
if (time==0) // countdown has finished
{
// timeout
}
else // re-start counter
{
time->start(1000);
}
}