New tDateTimePicker widget

This commit is contained in:
Victor Tran 2019-06-30 21:58:10 +10:00
parent 8975be9b41
commit ffb17e0f10
7 changed files with 746 additions and 0 deletions

View file

@ -65,6 +65,9 @@ SOURCES += tvariantanimation.cpp \
tcsdtools/csdbuttonbox.cpp \
tcsdtools/csdsizegrip.cpp \
private/nativeeventfilter.cpp \
tdatetimepicker.cpp \
tdatetimepicker/datetimepart.cpp \
tdatetimepicker/datetimepartbutton.cpp \
terrorflash.cpp \
tpropertyanimation.cpp \
thelibsglobal.cpp \
@ -85,6 +88,9 @@ HEADERS += tvariantanimation.h\
tcsdtools/csdbuttonbox.h \
tcsdtools/csdsizegrip.h \
private/nativeeventfilter.h \
tdatetimepicker.h \
tdatetimepicker/datetimepart.h \
tdatetimepicker/datetimepartbutton.h \
terrorflash.h \
the-libs_global.h \
tpropertyanimation.h \

185
lib/tdatetimepicker.cpp Normal file
View file

@ -0,0 +1,185 @@
/****************************************
*
* INSERT-PROJECT-NAME-HERE - INSERT-GENERIC-NAME-HERE
* Copyright (C) 2019 Victor Tran
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* *************************************/
#include "tdatetimepicker.h"
#include <QDateTime>
#include <QBoxLayout>
#include "tdatetimepicker/datetimepart.h"
struct tDateTimePickerPrivate {
QDateTime dateTime;
QWidget* datePart;
QWidget* timePart;
QList<DateTimePart*> dateTimeParts;
QLocale locale;
int valueForPart(QChar part) {
if (part == 'd') return dateTime.date().day();
if (part == 'M') return dateTime.date().month();
if (part == 'y') return dateTime.date().year();
if (part == 'H') return dateTime.time().hour();
if (part == 'm') return dateTime.time().minute();
if (part == 's') return dateTime.time().second();
if (part == 'a') return dateTime.time().hour() >= 12 ? 1 : 0;
if (part == 'h') {
int hour = dateTime.time().hour() % 12;
if (hour == 0) hour = 12;
return hour;
}
return 0;
}
};
tDateTimePicker::tDateTimePicker(QWidget *parent) : QWidget(parent)
{
d = new tDateTimePickerPrivate();
d->datePart = new QWidget(this);
d->timePart = new QWidget(this);
QBoxLayout* layout = new QBoxLayout(QBoxLayout::LeftToRight, this);
layout->addStretch(0);
layout->addWidget(d->datePart);
layout->addWidget(d->timePart);
layout->addStretch(0);
this->setLayout(layout);
QString dateFormat = d->locale.dateFormat(QLocale::ShortFormat);
QString foundChars;
for (QChar c : dateFormat) {
if (!foundChars.contains(c) && QStringLiteral("dMy").contains(c)) {
foundChars.append(c);
}
}
foundChars.append("h:m:s a");
QBoxLayout* dateLayout = new QBoxLayout(QBoxLayout::LeftToRight, d->datePart);
QBoxLayout* timeLayout = new QBoxLayout(QBoxLayout::LeftToRight, d->timePart);
for (QChar c : foundChars) {
QLayout* layout;
if (QStringLiteral("dMy").contains(c)) {
layout = dateLayout;
} else {
layout = timeLayout;
}
QWidget* widgetToAdd;
if (QStringLiteral("dMyhHmsa").contains(c)) {
DateTimePart* part = new DateTimePart(this);
part->setValueType(c);
connect(this, &tDateTimePicker::dateTimeChanged, part, [=](QDateTime dateTime) {
part->blockSignals(true);
part->setValue(d->valueForPart(c));
part->blockSignals(false);
if (c == 'd') {
part->setMaxValue(dateTime.date().daysInMonth());
}
});
connect(part, &DateTimePart::valueChanged, this, [=](int value) {
QDate newDate = d->dateTime.date();
QTime newTime = d->dateTime.time();
switch (c.unicode()) {
case 'd':
newDate.setDate(newDate.year(), newDate.month(), value);
break;
case 'M': {
int day = newDate.day();
int daysInMonth = QDate(newDate.year(), value, 1).daysInMonth();
if (daysInMonth < day) day = daysInMonth;
newDate.setDate(newDate.year(), value, day);
break;
}
case 'y': {
int day = newDate.day();
int daysInMonth = QDate(value, newDate.month(), 1).daysInMonth();
if (daysInMonth < day) day = daysInMonth;
newDate.setDate(value, newDate.month(), day);
break;
}
case 'H':
newTime.setHMS(value, newTime.minute(), newTime.second());
break;
case 'm':
newTime.setHMS(newTime.hour(), value, newTime.second());
break;
case 's':
newTime.setHMS(newTime.hour(), newTime.minute(), value);
break;
case 'a': {
int baseHour = newTime.hour() % 12;
if (value == 1) {
//PM
newTime.setHMS(baseHour + 12, newTime.minute(), newTime.second());
} else {
//AM
newTime.setHMS(baseHour, newTime.minute(), newTime.second());
}
break;
}
case 'h':
if (newTime.hour() / 12 == 1) {
//PM
newTime.setHMS(value + 12, newTime.minute(), newTime.second());
} else {
//AM
newTime.setHMS(value, newTime.minute(), newTime.second());
}
break;
}
this->setDateTime(QDateTime(newDate, newTime));
});
d->dateTimeParts.append(part);
widgetToAdd = part;
} else {
QLabel* label = new QLabel(parent);
label->setText(c);
widgetToAdd = label;
}
layout->addWidget(widgetToAdd);
}
d->datePart->setLayout(dateLayout);
d->timePart->setLayout(timeLayout);
this->setDateTime(QDateTime::currentDateTime());
}
tDateTimePicker::~tDateTimePicker() {
delete d;
}
void tDateTimePicker::setDateTime(QDateTime dateTime) {
d->dateTime = dateTime;
emit dateTimeChanged(d->dateTime);
}
QDateTime tDateTimePicker::currentDateTime() {
return d->dateTime;
}
void tDateTimePicker::setPickOptions(PickOptions options) {
d->datePart->setVisible(options & PickDate);
d->timePart->setVisible(options & PickTime);
}

54
lib/tdatetimepicker.h Normal file
View file

@ -0,0 +1,54 @@
/****************************************
*
* INSERT-PROJECT-NAME-HERE - INSERT-GENERIC-NAME-HERE
* Copyright (C) 2019 Victor Tran
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* *************************************/
#ifndef TDATETIMEPICKER_H
#define TDATETIMEPICKER_H
#include <QWidget>
#include <QDateTime>
#include "the-libs_global.h"
struct tDateTimePickerPrivate;
class THELIBSSHARED_EXPORT tDateTimePicker : public QWidget
{
Q_OBJECT
public:
explicit tDateTimePicker(QWidget *parent = nullptr);
~tDateTimePicker();
enum PickOption {
PickDate = 0x1,
PickTime = 0x2
};
typedef QFlags<PickOption> PickOptions;
QDateTime currentDateTime();
void setDateTime(QDateTime dateTime);
void setPickOptions(PickOptions options);
signals:
void dateTimeChanged(QDateTime dateTime);
public slots:
private:
tDateTimePickerPrivate* d;
};
#endif // TDATETIMEPICKER_H

View file

@ -0,0 +1,347 @@
/****************************************
*
* INSERT-PROJECT-NAME-HERE - INSERT-GENERIC-NAME-HERE
* Copyright (C) 2019 Victor Tran
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* *************************************/
#include "datetimepart.h"
#include <QPainter>
#include <QLocale>
#include <QPushButton>
#include <QWheelEvent>
#include "tvariantanimation.h"
#include "datetimepartbutton.h"
struct DateTimePartPrivate {
int value;
int drawOffset = 0;
QChar valueType = 'm';
int sizeWidth;
int currentWheelDelta = 0;
int maxValueUser = -1;
DateTimePartButton *upButton, *downButton;
QLocale locale;
QString textForValue(int value) {
switch (valueType.unicode()) {
case 'y':
return QString::number(value);
case 'd':
case 'h':
case 'H':
case 'm':
case 's':
return locale.toString(value).rightJustified(2, locale.zeroDigit());
case 'M':
return locale.monthName(value, QLocale::ShortFormat);
case 'a':
if (value == 0) {
return locale.amText();
} else {
return locale.pmText();
}
}
return "(invalid)";
}
int minValue() {
switch (valueType.unicode()) {
case 'y':
return 1980;
case 'd':
case 'M':
case 'h':
return 1;
case 'H':
case 'm':
case 's':
case 'a':
return 0;
}
return 0;
}
int maxValue() {
if (maxValueUser != -1) return maxValueUser;
switch (valueType.unicode()) {
case 'y':
return 2099;
case 'd':
return 30; //TODO: set depending on month
case 'h':
return 12;
case 'H':
return 23;
case 'm':
case 's':
return 59;
case 'M':
return 12;
case 'a':
return 1;
}
return 0;
}
bool wrapAroundValue() {
switch (valueType.unicode()) {
case 'h':
case 'H':
case 'm':
case 's':
case 'a':
return true;
}
return false;
}
QString altText;
bool performOppositeAnimation = false;
};
DateTimePart::DateTimePart(QWidget *parent) : QLabel(parent)
{
d = new DateTimePartPrivate();
this->setMouseTracking(true);
this->setMargin(3);
this->setFocusPolicy(Qt::StrongFocus);
d->upButton = new DateTimePartButton(parent->parentWidget());
d->upButton->setIsTopSide(true);
d->upButton->setIcon(QIcon::fromTheme("go-up"));
d->upButton->setFlat(true);
d->upButton->setVisible(false);
d->upButton->setAutoRepeat(true);
d->upButton->installEventFilter(this);
d->upButton->setFocusProxy(this);
connect(d->upButton, &QPushButton::clicked, this, &DateTimePart::increment);
d->downButton = new DateTimePartButton(parent->parentWidget());
d->downButton->setIsTopSide(false);
d->downButton->setIcon(QIcon::fromTheme("go-down"));
d->downButton->setFlat(true);
d->downButton->setVisible(false);
d->downButton->setAutoRepeat(true);
d->downButton->installEventFilter(this);
d->downButton->setFocusProxy(this);
connect(d->downButton, &QPushButton::clicked, this, &DateTimePart::decrement);
}
DateTimePart::~DateTimePart() {
d->upButton->deleteLater();
d->downButton->deleteLater();
delete d;
}
void DateTimePart::paintEvent(QPaintEvent *event) {
QPainter painter(this);
painter.setFont(this->font());
painter.setPen(this->palette().color(QPalette::WindowText));
painter.drawText(QRect(0, d->drawOffset - this->height(), this->width(), this->height()), Qt::AlignCenter, d->altText);
painter.drawText(QRect(0, d->drawOffset, this->width(), this->height()), Qt::AlignCenter, this->text());
painter.drawText(QRect(0, d->drawOffset + this->height(), this->width(), this->height()), Qt::AlignCenter, d->altText);
if (d->upButton->isVisible()) {
//Draw borders
painter.drawLine(0, 0, 0, this->height());
painter.drawLine(this->width() - 1, 0, this->width() - 1, this->height());
}
}
void DateTimePart::setValue(int value, bool animate) {
int oldValue = d->value;
d->altText = this->text();
d->value = value;
emit valueChanged(value);
if (animate) {
if (oldValue < value) {
if (d->performOppositeAnimation) {
decrementAnimation();
} else {
incrementAnimation();
}
} else if (oldValue > value) {
if (d->performOppositeAnimation) {
incrementAnimation();
} else {
decrementAnimation();
}
}
d->performOppositeAnimation = false;
}
this->setText(d->textForValue(value));
}
int DateTimePart::value() {
return d->value;
}
void DateTimePart::setValueType(QChar valueType) {
d->valueType = valueType;
//Find the correct width
int maxWidth = 0;
for (int i = d->minValue(); i <= d->maxValue(); i++) {
maxWidth = qMax(maxWidth, this->fontMetrics().width(d->textForValue(i)));
}
d->sizeWidth = maxWidth + 6; //margin
this->updateGeometry();
}
void DateTimePart::enterEvent(QEvent *event) {
int buttonHeight = SC_DPI(32);
QSize buttonSize = QSize(this->width(), buttonHeight);
QPoint topLeft = d->upButton->parentWidget()->mapFromGlobal(this->mapToGlobal(QPoint(0, 0)));
d->upButton->setGeometry(QRect(topLeft - QPoint(0, buttonHeight), buttonSize));
d->downButton->setGeometry(QRect(topLeft + QPoint(0, this->height()), buttonSize));
d->upButton->setVisible(true);
d->downButton->setVisible(true);
d->upButton->raise();
d->downButton->raise();
this->update();
}
void DateTimePart::leaveEvent(QEvent *event) {
calculateLeave();
}
bool DateTimePart::eventFilter(QObject *watched, QEvent *event) {
if (event->type() == QEvent::Leave) {
calculateLeave();
}
return false;
}
void DateTimePart::calculateLeave() {
QPoint pos = d->upButton->parentWidget()->mapFromGlobal(QCursor::pos());
if (!d->upButton->geometry().adjusted(0, 0, 0, 3).contains(pos) &&
!d->downButton->geometry().adjusted(0, -3, 0, 0).contains(pos) &&
!this->geometry().contains(this->parentWidget()->mapFromGlobal(QCursor::pos()))) {
d->upButton->setVisible(false);
d->downButton->setVisible(false);
this->update();
}
}
void DateTimePart::increment() {
int val = d->value + 1;
if (d->maxValue() < val) {
if (d->wrapAroundValue()) {
val = d->minValue();
d->performOppositeAnimation = true;
} else {
return; //Don't do anything
}
}
this->setValue(val);
}
void DateTimePart::decrement() {
int val = d->value - 1;
if (d->minValue() > val) {
if (d->wrapAroundValue()) {
val = d->maxValue();
d->performOppositeAnimation = true;
} else {
return; //Don't do anything
}
}
this->setValue(val);
}
void DateTimePart::incrementAnimation() {
tVariantAnimation* anim = new tVariantAnimation();
anim->setStartValue(this->height());
anim->setEndValue(0);
anim->setDuration(250);
anim->setEasingCurve(QEasingCurve::OutCubic);
connect(anim, &tVariantAnimation::valueChanged, this, [=](QVariant value) {
d->drawOffset = value.toInt();
this->update();
});
connect(anim, &tVariantAnimation::finished, this, [=] {
anim->deleteLater();
});
anim->start();
}
void DateTimePart::decrementAnimation() {
tVariantAnimation* anim = new tVariantAnimation();
anim->setStartValue(-this->height());
anim->setEndValue(0);
anim->setDuration(250);
anim->setEasingCurve(QEasingCurve::OutCubic);
connect(anim, &tVariantAnimation::valueChanged, this, [=](QVariant value) {
d->drawOffset = value.toInt();
this->update();
});
connect(anim, &tVariantAnimation::finished, this, [=] {
anim->deleteLater();
});
anim->start();
}
QSize DateTimePart::sizeHint() const {
QSize sz = QLabel::sizeHint();
sz.setWidth(d->sizeWidth);
return sz;
}
void DateTimePart::wheelEvent(QWheelEvent *event) {
event->accept();
d->currentWheelDelta += event->angleDelta().y();
while (d->currentWheelDelta > 60) {
increment();
d->currentWheelDelta -= 120;
}
while (d->currentWheelDelta < -60) {
decrement();
d->currentWheelDelta += 120;
}
}
void DateTimePart::keyPressEvent(QKeyEvent *event) {
if (event->key() == Qt::Key_Up) {
increment();
} else if (event->key() == Qt::Key_Down) {
decrement();
}
}
void DateTimePart::setMaxValue(int maxValue) {
d->maxValueUser = maxValue;
if (maxValue < d->value) {
setValue(maxValue);
}
}

View file

@ -0,0 +1,65 @@
/****************************************
*
* INSERT-PROJECT-NAME-HERE - INSERT-GENERIC-NAME-HERE
* Copyright (C) 2019 Victor Tran
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* *************************************/
#ifndef DATETIMEPART_H
#define DATETIMEPART_H
#include <QLabel>
struct DateTimePartPrivate;
class DateTimePart : public QLabel
{
Q_OBJECT
public:
explicit DateTimePart(QWidget *parent);
~DateTimePart();
QSize sizeHint() const;
void setValue(int value, bool animate = true);
int value();
void setMaxValue(int maxValue);
void setValueType(QChar valueType);
signals:
void valueChanged(int value);
public slots:
void increment();
void decrement();
private:
DateTimePartPrivate* d;
void paintEvent(QPaintEvent *event);
void enterEvent(QEvent* event);
void leaveEvent(QEvent* event);
void wheelEvent(QWheelEvent* event);
void keyPressEvent(QKeyEvent *event);
bool eventFilter(QObject* watched, QEvent* event);
void calculateLeave();
void incrementAnimation();
void decrementAnimation();
};
#endif // DATETIMEPART_H

View file

@ -0,0 +1,47 @@
/****************************************
*
* INSERT-PROJECT-NAME-HERE - INSERT-GENERIC-NAME-HERE
* Copyright (C) 2019 Victor Tran
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* *************************************/
#include "datetimepartbutton.h"
#include <QPainter>
DateTimePartButton::DateTimePartButton(QWidget *parent) : QPushButton(parent)
{
}
void DateTimePartButton::paintEvent(QPaintEvent *event) {
QPushButton::paintEvent(event);
//Draw borders
QPainter painter(this);
painter.setPen(this->palette().color(QPalette::WindowText));
painter.drawLine(0, 0, 0, this->height());
painter.drawLine(this->width() - 1, 0, this->width() - 1, this->height());
if (isTopSide) {
painter.drawLine(0, 0, this->width(), 0);
} else {
painter.drawLine(0, this->height() - 1, this->width(), this->height() - 1);
}
}
void DateTimePartButton::setIsTopSide(bool isTopSide) {
this->isTopSide = isTopSide;
}

View file

@ -0,0 +1,42 @@
/****************************************
*
* INSERT-PROJECT-NAME-HERE - INSERT-GENERIC-NAME-HERE
* Copyright (C) 2019 Victor Tran
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* *************************************/
#ifndef DATETIMEPARTBUTTON_H
#define DATETIMEPARTBUTTON_H
#include <QPushButton>
class DateTimePartButton : public QPushButton
{
Q_OBJECT
public:
explicit DateTimePartButton(QWidget *parent = nullptr);
void setIsTopSide(bool isTopSide);
signals:
public slots:
private:
void paintEvent(QPaintEvent *);
bool isTopSide = false;
};
#endif // DATETIMEPARTBUTTON_H