Last active
April 22, 2018 22:55
-
-
Save jniemann66/40a89ac1e30a1e49f260e02689bf3054 to your computer and use it in GitHub Desktop.
Qt Appointment/Diary Widget
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef APPOINTMENTWIDGET_H | |
#define APPOINTMENTWIDGET_H | |
// AppointmentWidget.h : basic appointment (Diary) widget | |
// (implementation in header) | |
#include <QWidget> | |
#include <QStringList> | |
#include <QTime> | |
#include <QTimer> | |
#include <QPainter> | |
#include <QPaintEvent> | |
#include <QStyleOption> | |
#include <QDebug> | |
#include <QAbstractTextDocumentLayout> | |
#include <QTextDocument> | |
#include <cmath> | |
enum AppointmentWidgetTextFormat { | |
Plain, | |
Html | |
}; | |
struct AppointmentWidgetItem { | |
QTime startTime; | |
QTime finishTime; | |
QString text; | |
QColor color; | |
AppointmentWidgetItem() : color{152, 251, 152, 192}{} | |
}; | |
class AppointmentWidget : public QWidget | |
{ | |
Q_OBJECT | |
public: | |
explicit AppointmentWidget(QWidget *parent = nullptr) : QWidget(parent), | |
displayHoursStart(0), displayHoursEnd(24), busHoursStart(0.0), busHoursEnd(24.0), warpEffect(true), selectQuantizeTo(0.25), | |
vMargin(30), hMargin(10), showBusinessHours(true), | |
defaultDuration(0.5), textFormat(Plain), selectionStart(-1.0) | |
{ | |
textWidth = 60; // to-do: measure | |
nMinorLines = (1.0 / selectQuantizeTo) - 1; | |
displayHoursSpan = displayHoursEnd - displayHoursStart; | |
autopaintTimer.setInterval(15000); | |
connect(&autopaintTimer, &QTimer::timeout, this, &AppointmentWidget::onAutopaintTimeout); | |
} | |
QList<AppointmentWidgetItem> getAppointments() const | |
{ | |
return appointments; | |
} | |
void setAppointments(const QList<AppointmentWidgetItem>& value) | |
{ | |
appointments = value; | |
update(); | |
} | |
void addAppointment(const AppointmentWidgetItem& appointment) | |
{ | |
appointments.append(appointment); | |
update(); | |
} | |
void clearAppointments() | |
{ | |
appointments.clear(); | |
update(); | |
} | |
double convertTimeToYpos(const QTime& t) const | |
{ | |
double vSpacing = (geometry().height() - 2 * vMargin) / displayHoursSpan; | |
double h = std::min(std::max(0.0, (t.hour() + t.minute() / 60.0) - displayHoursStart), (double)displayHoursSpan); | |
return vSpacing * h + vMargin; | |
} | |
void setDisplayHoursStart(int value) | |
{ | |
displayHoursStart = value; | |
displayHoursSpan = displayHoursEnd - displayHoursStart; | |
} | |
void setDisplayHoursEnd(int value) | |
{ | |
displayHoursEnd = value; | |
displayHoursSpan = displayHoursEnd - displayHoursStart; | |
} | |
void setBusHoursStart(double value) | |
{ | |
busHoursStart = value; | |
} | |
void setBusHoursEnd(double value) | |
{ | |
busHoursEnd = value; | |
} | |
int getVMargin() const | |
{ | |
return vMargin; | |
} | |
void setVMargin(int numPixels) | |
{ | |
vMargin = numPixels; | |
} | |
int getHMargin() const | |
{ | |
return hMargin; | |
} | |
void setHMargin(int numPixels) | |
{ | |
hMargin = numPixels; | |
} | |
void setWarpEffect(bool value) | |
{ | |
warpEffect = value; | |
} | |
void setShowBusinessHours(bool value) | |
{ | |
showBusinessHours = value; | |
} | |
void setDefaultDuration(double numHours) | |
{ | |
defaultDuration = numHours; | |
} | |
QTime getSelectionStart() const | |
{ | |
int h = std::floor(selectionStart); | |
int m = (selectionStart - h) * 60.0; | |
return QTime(h, m); | |
} | |
void setTextFormat(const AppointmentWidgetTextFormat& value) | |
{ | |
textFormat = value; | |
} | |
QDate getDateShown() const | |
{ | |
return dateShown; | |
} | |
void setDateShown(const QDate& value) | |
{ | |
dateShown = value; | |
autopaintTimer.start(); | |
} | |
protected: | |
void paintEvent(QPaintEvent* event) override | |
{ | |
// apply stylesheet options | |
QStyleOption opt; | |
opt.init(this); | |
QPainter p(this); | |
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); | |
QTime currentTime(QTime::currentTime()); | |
int currentHour = currentTime.hour(); | |
int currentMinute = currentTime.minute(); | |
int hourLabelToHide = -1; | |
bool drawNowIndicator = (dateShown == QDate::currentDate()); | |
if(currentMinute < 10) { | |
hourLabelToHide = currentHour; | |
} else if (currentMinute > 50) { | |
hourLabelToHide = currentHour + 1; | |
} | |
auto rect = geometry(); | |
if(warpEffect) { | |
// warping effect (blend between widget height and paint area height) | |
double h = (3 * rect.height() + event->rect().height() * 2) / 4; | |
rect.setHeight(h); | |
} | |
// set drawing parameters | |
int hLineStart = hMargin + textWidth + hMargin; | |
double vSpacing = (rect.height() - 2* vMargin) / displayHoursSpan; | |
int hLineEnd = rect.width() - hMargin; | |
int hMinorLineStart = hLineStart + (hMargin >> 1); | |
int hMinorLineEnd = hLineEnd - (hMargin >> 1); | |
int vMinorIncrement = selectQuantizeTo * vSpacing; | |
// set up pens, brushes and painter | |
QPen gridPen(QColor(128,128,128)); | |
QPen minorGridPen(QColor(230,230,230)); | |
QPen blackPen(QColor(64,64,64,192)); | |
QPen shadePen(QColor(0,0,0,32)); | |
QBrush shadeBrush(QColor(0, 0, 0, 16)); | |
QPainter painter(this); | |
QFont f("Helvetica", 20); | |
painter.setFont(f); | |
// determine text height and offset | |
double tHeight = QFontMetricsF(f).height(); | |
double tOffset = tHeight / 2; | |
// draw hour lines and minor gridlines | |
for(int hour = displayHoursStart; hour < displayHoursEnd; hour++) { | |
int v = vMargin + vSpacing * (hour - displayHoursStart); | |
painter.setPen(gridPen); | |
if(!drawNowIndicator || (hour != hourLabelToHide)) { | |
painter.drawText(hMargin, v - tOffset, textWidth, tHeight, Qt::AlignVCenter | Qt::AlignRight, hourNames[hour]); | |
} | |
painter.drawLine(hLineStart, v, hLineEnd, v); | |
int vv = v; | |
painter.setPen(minorGridPen); | |
for(int m = 0; m < nMinorLines; m++) { | |
vv += vMinorIncrement; | |
painter.drawLine(hMinorLineStart, vv, hMinorLineEnd, vv); | |
} | |
} | |
// draw last hour line | |
int vLastLine = vMargin + vSpacing * displayHoursSpan; | |
painter.setPen(gridPen); | |
painter.drawText(hMargin, vLastLine - tOffset, textWidth, tHeight, Qt::AlignVCenter | Qt::AlignRight, hourNames[displayHoursEnd]); | |
painter.drawLine(hLineStart, vLastLine, hLineEnd, vLastLine); | |
int appointmentWidth = hLineEnd - hLineStart; | |
if(showBusinessHours) { // shade after-hours | |
painter.setPen(shadePen); | |
painter.setBrush(shadeBrush); | |
painter.drawRect(0, 0, rect.width(), vMargin + vSpacing * (busHoursStart - displayHoursStart)); | |
painter.drawRect(0, vMargin + vSpacing * (busHoursEnd - displayHoursStart), rect.width(), vMargin + vSpacing * displayHoursSpan); | |
} | |
// draw appointments | |
painter.setPen(blackPen); | |
for(auto& appointment : appointments) { | |
painter.setBrush(QColor(appointment.color)); | |
double h0 = (appointment.startTime.hour() + appointment.startTime.minute() / 60.0) - displayHoursStart; | |
double h1 = (appointment.finishTime.hour() + appointment.finishTime.minute() / 60.0) - displayHoursStart; | |
if (h1 >= h0) { | |
int v0 = vMargin + vSpacing * h0; | |
int v1 = vMargin + vSpacing * h1; | |
painter.drawRect(hLineStart, v0, appointmentWidth, v1 - v0); | |
// render text | |
if(textFormat == Html) { | |
QTextDocument td; | |
td.setHtml(appointment.text); | |
QAbstractTextDocumentLayout::PaintContext paintContext; | |
painter.save(); | |
painter.translate(hLineStart, v0); | |
paintContext.clip = QRectF(0, 0, appointmentWidth, v1 - v0); | |
td.documentLayout()->draw(&painter, paintContext); | |
painter.restore(); | |
} | |
else { | |
painter.drawText(hLineStart, v0, appointmentWidth, v1 - v0, Qt::AlignCenter, appointment.text); | |
} | |
} | |
} | |
if(selectionStart >= 0) { // draw selection | |
painter.setBrush(QColor(173,216,230,64)); | |
painter.setPen(QColor(64,64,64,64)); | |
int v0 = vMargin + vSpacing * (selectionStart - displayHoursStart); | |
int v1 = vMargin + vSpacing * (selectionEnd - displayHoursStart); | |
painter.drawRect(hLineStart, v0, appointmentWidth, v1 - v0); | |
} | |
if(drawNowIndicator) { // draw the 'now' line | |
if(currentHour >= busHoursStart && currentHour <= busHoursEnd) { | |
painter.setBrush(QColor(255,0,0,255)); | |
painter.setPen(QColor(255,0,0,255)); | |
int v0 = vMargin + vSpacing * (currentHour + currentMinute / 60.0 - displayHoursStart); | |
painter.drawLine(hLineStart, v0, hLineStart + appointmentWidth, v0); | |
painter.drawEllipse(QPoint(hLineStart, v0), 5, 5); | |
QFont f(this->font()); | |
f.setPointSize(12); | |
painter.setFont(f); | |
painter.drawText(hMargin, v0 - tOffset, textWidth, tHeight, Qt::AlignVCenter | Qt::AlignRight, currentTime.toString("hh:mm ap")); | |
} | |
} | |
QWidget::paintEvent(event); | |
} | |
void mousePressEvent(QMouseEvent *event) override | |
{ | |
auto rect = geometry(); | |
double vSpacing = (rect.height() - 2* vMargin) / displayHoursSpan; | |
double t = displayHoursStart + (event->pos().y()-vMargin) / vSpacing; // convert mouse y to hours | |
selectionStart = std::min(std::max((double)displayHoursStart, std::floor(t / selectQuantizeTo) * selectQuantizeTo), displayHoursEnd - selectQuantizeTo); // quantize and limit range | |
selectionEnd = std::min(defaultDuration + selectionStart, (double)displayHoursEnd); | |
QTime t0(0,0,0); | |
emit selected(t0.addSecs(selectionStart * 3600), t0.addSecs((selectionStart + defaultDuration) * 3600)); | |
update(); | |
QWidget::mousePressEvent(event); | |
} | |
void leaveEvent(QEvent *event) override | |
{ | |
selectionStart = -1.0; | |
update(); | |
QWidget::leaveEvent(event); | |
} | |
signals: | |
void selected(QTime startTime, QTime finishTime); | |
public slots: | |
private slots: | |
void onAutopaintTimeout() { | |
if(dateShown == QDate::currentDate()) { | |
update(); | |
} else { | |
autopaintTimer.stop(); | |
} | |
} | |
private: | |
// resources | |
QTimer autopaintTimer; | |
// properties | |
QDate dateShown; | |
int displayHoursStart; // first hour to be displayed 0..24 | |
int displayHoursEnd; // last hour to be displayed | |
double busHoursStart; // start of business hours 0.0..23.999 | |
double busHoursEnd; // end of business hours | |
bool warpEffect; | |
double selectQuantizeTo; // quantize 'snap' amount (fraction of hour) | |
int vMargin; | |
int hMargin; | |
int textWidth; | |
bool showBusinessHours; | |
double defaultDuration; | |
AppointmentWidgetTextFormat textFormat; | |
// calculated properties | |
int displayHoursSpan; | |
int nMinorLines; | |
// object state | |
double selectionStart; | |
double selectionEnd; | |
QList<AppointmentWidgetItem> appointments; | |
// constants | |
const QStringList hourNames { | |
"12 am", | |
"1 am", | |
"2 am", | |
"3 am", | |
"4 am", | |
"5 am", | |
"6 am", | |
"7 am", | |
"8 am", | |
"9 am", | |
"10 am", | |
"11 am", | |
"Noon", | |
"1 pm", | |
"2 pm", | |
"3 pm", | |
"4 pm", | |
"5 pm", | |
"6 pm", | |
"7 pm", | |
"8 pm", | |
"9 pm", | |
"10 pm", | |
"11 pm", | |
"12 am" | |
}; | |
}; | |
#endif // APPOINTMENTWIDGET_H |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment