mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-13 08:08:42 +00:00
Added a day view to PlanningCalendarWindow (#4704)
* Reusing the existing configuration * Linked from days in month view to the more detailed view * Fixed color setting after construction
This commit is contained in:
committed by
GitHub
parent
069e2942df
commit
f84f9a410e
@@ -16,7 +16,6 @@
|
|||||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
#include "PlanningCalendarWindow.h"
|
#include "PlanningCalendarWindow.h"
|
||||||
|
|
||||||
#include <QComboBox>
|
#include <QComboBox>
|
||||||
@@ -109,13 +108,13 @@ PlanningCalendarWindow::PlanningCalendarWindow(Context *context)
|
|||||||
updateActivities();
|
updateActivities();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
connect(calendar, &Calendar::moveActivity, [=](CalendarEntry activity, const QDate &srcDay, const QDate &destDay) {
|
connect(calendar, &Calendar::moveActivity, [=](CalendarEntry activity, const QDate &srcDay, const QDate &destDay, const QTime &destTime) {
|
||||||
Q_UNUSED(srcDay)
|
Q_UNUSED(srcDay)
|
||||||
|
|
||||||
QApplication::setOverrideCursor(Qt::WaitCursor);
|
QApplication::setOverrideCursor(Qt::WaitCursor);
|
||||||
for (RideItem *rideItem : context->athlete->rideCache->rides()) {
|
for (RideItem *rideItem : context->athlete->rideCache->rides()) {
|
||||||
if (rideItem != nullptr && rideItem->fileName == activity.reference) {
|
if (rideItem != nullptr && rideItem->fileName == activity.reference) {
|
||||||
movePlannedActivity(rideItem, destDay);
|
movePlannedActivity(rideItem, destDay, destTime);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -147,9 +146,13 @@ PlanningCalendarWindow::PlanningCalendarWindow(Context *context)
|
|||||||
}
|
}
|
||||||
QApplication::restoreOverrideCursor();
|
QApplication::restoreOverrideCursor();
|
||||||
});
|
});
|
||||||
|
connect(calendar, &Calendar::dayChanged, this, &PlanningCalendarWindow::updateActivities);
|
||||||
connect(calendar, &Calendar::monthChanged, this, &PlanningCalendarWindow::updateActivities);
|
connect(calendar, &Calendar::monthChanged, this, &PlanningCalendarWindow::updateActivities);
|
||||||
|
connect(calendar, &Calendar::viewChanged, this, &PlanningCalendarWindow::updateActivities);
|
||||||
|
|
||||||
configChanged(CONFIG_APPEARANCE);
|
QTimer::singleShot(0, this, [this]() {
|
||||||
|
configChanged(CONFIG_APPEARANCE);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -276,7 +279,8 @@ PlanningCalendarWindow::setSummaryMetrics
|
|||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
PlanningCalendarWindow::configChanged(qint32 what)
|
PlanningCalendarWindow::configChanged
|
||||||
|
(qint32 what)
|
||||||
{
|
{
|
||||||
bool refreshActivities = false;
|
bool refreshActivities = false;
|
||||||
if ( (what & CONFIG_FIELDS)
|
if ( (what & CONFIG_FIELDS)
|
||||||
@@ -299,9 +303,20 @@ PlanningCalendarWindow::configChanged(qint32 what)
|
|||||||
|
|
||||||
QPalette palette;
|
QPalette palette;
|
||||||
|
|
||||||
|
palette.setColor(QPalette::Normal, QPalette::Window, activeBg);
|
||||||
|
palette.setColor(QPalette::Normal, QPalette::WindowText, activeText);
|
||||||
|
palette.setColor(QPalette::Normal, QPalette::Base, activeBg);
|
||||||
|
palette.setColor(QPalette::Normal, QPalette::AlternateBase, alternateBg);
|
||||||
|
palette.setColor(QPalette::Normal, QPalette::Text, activeText);
|
||||||
|
palette.setColor(QPalette::Normal, QPalette::Highlight, activeHl);
|
||||||
|
palette.setColor(QPalette::Normal, QPalette::HighlightedText, activeHlText);
|
||||||
|
palette.setColor(QPalette::Normal, QPalette::Button, activeBg);
|
||||||
|
palette.setColor(QPalette::Normal, QPalette::ButtonText, activeText);
|
||||||
|
|
||||||
palette.setColor(QPalette::Active, QPalette::Window, activeBg);
|
palette.setColor(QPalette::Active, QPalette::Window, activeBg);
|
||||||
palette.setColor(QPalette::Active, QPalette::WindowText, activeText);
|
palette.setColor(QPalette::Active, QPalette::WindowText, activeText);
|
||||||
palette.setColor(QPalette::Active, QPalette::Base, activeBg);
|
palette.setColor(QPalette::Active, QPalette::Base, activeBg);
|
||||||
|
palette.setColor(QPalette::Active, QPalette::AlternateBase, alternateBg);
|
||||||
palette.setColor(QPalette::Active, QPalette::Text, activeText);
|
palette.setColor(QPalette::Active, QPalette::Text, activeText);
|
||||||
palette.setColor(QPalette::Active, QPalette::Highlight, activeHl);
|
palette.setColor(QPalette::Active, QPalette::Highlight, activeHl);
|
||||||
palette.setColor(QPalette::Active, QPalette::HighlightedText, activeHlText);
|
palette.setColor(QPalette::Active, QPalette::HighlightedText, activeHlText);
|
||||||
@@ -311,12 +326,23 @@ PlanningCalendarWindow::configChanged(qint32 what)
|
|||||||
palette.setColor(QPalette::Disabled, QPalette::Window, alternateBg);
|
palette.setColor(QPalette::Disabled, QPalette::Window, alternateBg);
|
||||||
palette.setColor(QPalette::Disabled, QPalette::WindowText, alternateText);
|
palette.setColor(QPalette::Disabled, QPalette::WindowText, alternateText);
|
||||||
palette.setColor(QPalette::Disabled, QPalette::Base, alternateBg);
|
palette.setColor(QPalette::Disabled, QPalette::Base, alternateBg);
|
||||||
|
palette.setColor(QPalette::Disabled, QPalette::AlternateBase, alternateBg);
|
||||||
palette.setColor(QPalette::Disabled, QPalette::Text, alternateText);
|
palette.setColor(QPalette::Disabled, QPalette::Text, alternateText);
|
||||||
palette.setColor(QPalette::Disabled, QPalette::Highlight, activeHl);
|
palette.setColor(QPalette::Disabled, QPalette::Highlight, activeHl);
|
||||||
palette.setColor(QPalette::Disabled, QPalette::HighlightedText, activeHlText);
|
palette.setColor(QPalette::Disabled, QPalette::HighlightedText, activeHlText);
|
||||||
palette.setColor(QPalette::Disabled, QPalette::Button, alternateBg);
|
palette.setColor(QPalette::Disabled, QPalette::Button, alternateBg);
|
||||||
palette.setColor(QPalette::Disabled, QPalette::ButtonText, alternateText);
|
palette.setColor(QPalette::Disabled, QPalette::ButtonText, alternateText);
|
||||||
|
|
||||||
|
palette.setColor(QPalette::Inactive, QPalette::Window, activeBg);
|
||||||
|
palette.setColor(QPalette::Inactive, QPalette::WindowText, activeText);
|
||||||
|
palette.setColor(QPalette::Inactive, QPalette::Base, activeBg);
|
||||||
|
palette.setColor(QPalette::Inactive, QPalette::AlternateBase, alternateBg);
|
||||||
|
palette.setColor(QPalette::Inactive, QPalette::Text, activeText);
|
||||||
|
palette.setColor(QPalette::Inactive, QPalette::Highlight, activeHl);
|
||||||
|
palette.setColor(QPalette::Inactive, QPalette::HighlightedText, activeHlText);
|
||||||
|
palette.setColor(QPalette::Inactive, QPalette::Button, activeBg);
|
||||||
|
palette.setColor(QPalette::Inactive, QPalette::ButtonText, activeText);
|
||||||
|
|
||||||
PaletteApplier::setPaletteRecursively(this, palette, true);
|
PaletteApplier::setPaletteRecursively(this, palette, true);
|
||||||
|
|
||||||
calendar->applyNavIcons();
|
calendar->applyNavIcons();
|
||||||
@@ -505,27 +531,36 @@ PlanningCalendarWindow::getActivities
|
|||||||
activity.hasTrainMode = rideItem->planned && sport == "Bike" && ! buildWorkoutFilter(rideItem).isEmpty();
|
activity.hasTrainMode = rideItem->planned && sport == "Bike" && ! buildWorkoutFilter(rideItem).isEmpty();
|
||||||
activities[rideItem->dateTime.date()] << activity;
|
activities[rideItem->dateTime.date()] << activity;
|
||||||
}
|
}
|
||||||
|
for (auto dayIt = activities.begin(); dayIt != activities.end(); ++dayIt) {
|
||||||
|
std::sort(dayIt.value().begin(), dayIt.value().end(), [](const CalendarEntry &a, const CalendarEntry &b) {
|
||||||
|
if (a.start == b.start) {
|
||||||
|
return a.primary < b.primary;
|
||||||
|
} else {
|
||||||
|
return a.start < b.start;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
return activities;
|
return activities;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
QList<CalendarSummary>
|
QList<CalendarSummary>
|
||||||
PlanningCalendarWindow::getWeeklySummaries
|
PlanningCalendarWindow::getSummaries
|
||||||
(const QDate &firstDay, const QDate &lastDay) const
|
(const QDate &firstDay, const QDate &lastDay, int timeBucketSize) const
|
||||||
{
|
{
|
||||||
QStringList symbols = getSummaryMetricsList();
|
QStringList symbols = getSummaryMetricsList();
|
||||||
QList<CalendarSummary> summaries;
|
QList<CalendarSummary> summaries;
|
||||||
int numWeeks = firstDay.daysTo(lastDay) / 7 + 1;
|
int numTimeBuckets = firstDay.daysTo(lastDay) / timeBucketSize + 1;
|
||||||
bool useMetricUnits = GlobalContext::context()->useMetricUnits;
|
bool useMetricUnits = GlobalContext::context()->useMetricUnits;
|
||||||
|
|
||||||
const RideMetricFactory &factory = RideMetricFactory::instance();
|
const RideMetricFactory &factory = RideMetricFactory::instance();
|
||||||
FilterSet filterSet(context->isfiltered, context->filters);
|
FilterSet filterSet(context->isfiltered, context->filters);
|
||||||
Specification spec;
|
Specification spec;
|
||||||
spec.setFilterSet(filterSet);
|
spec.setFilterSet(filterSet);
|
||||||
for (int week = 0; week < numWeeks; ++week) {
|
for (int timeBucket = 0; timeBucket < numTimeBuckets; ++timeBucket) {
|
||||||
QDate firstDayOfWeek = firstDay.addDays(week * 7);
|
QDate firstDayOfTimeBucket = firstDay.addDays(timeBucket * timeBucketSize);
|
||||||
QDate lastDayOfWeek = firstDayOfWeek.addDays(6);
|
QDate lastDayOfTimeBucket = firstDayOfTimeBucket.addDays(timeBucketSize - 1);
|
||||||
spec.setDateRange(DateRange(firstDayOfWeek, lastDayOfWeek));
|
spec.setDateRange(DateRange(firstDayOfTimeBucket, lastDayOfTimeBucket));
|
||||||
CalendarSummary summary;
|
CalendarSummary summary;
|
||||||
summary.keyValues.clear();
|
summary.keyValues.clear();
|
||||||
for (const QString &symbol : symbols) {
|
for (const QString &symbol : symbols) {
|
||||||
@@ -588,7 +623,7 @@ PlanningCalendarWindow::getPhasesEvents
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
QList<Season> tmpSeasons = context->athlete->seasons->seasons;
|
QList<Season> tmpSeasons = context->athlete->seasons->seasons;
|
||||||
std::sort(tmpSeasons.begin(),tmpSeasons.end(),Season::LessThanForStarts);
|
std::sort(tmpSeasons.begin(), tmpSeasons.end(), Season::LessThanForStarts);
|
||||||
for (const Season &s : tmpSeasons) {
|
for (const Season &s : tmpSeasons) {
|
||||||
for (const SeasonEvent &event : s.events) {
|
for (const SeasonEvent &event : s.events) {
|
||||||
if ( ( ( firstDay.isValid()
|
if ( ( ( firstDay.isValid()
|
||||||
@@ -633,9 +668,15 @@ PlanningCalendarWindow::updateActivities
|
|||||||
Season const *season = context->currentSeason();
|
Season const *season = context->currentSeason();
|
||||||
if (!season) return; // avoid crash if no season selected
|
if (!season) return; // avoid crash if no season selected
|
||||||
|
|
||||||
|
QList<CalendarSummary> summaries;
|
||||||
QHash<QDate, QList<CalendarEntry>> activities = getActivities(calendar->firstVisibleDay(), calendar->lastVisibleDay());
|
QHash<QDate, QList<CalendarEntry>> activities = getActivities(calendar->firstVisibleDay(), calendar->lastVisibleDay());
|
||||||
QList<CalendarSummary> summaries = getWeeklySummaries(calendar->firstVisibleDay(), calendar->lastVisibleDay());
|
|
||||||
QHash<QDate, QList<CalendarEntry>> phasesEvents = getPhasesEvents(*season, calendar->firstVisibleDay(), calendar->lastVisibleDay());
|
QHash<QDate, QList<CalendarEntry>> phasesEvents = getPhasesEvents(*season, calendar->firstVisibleDay(), calendar->lastVisibleDay());
|
||||||
|
if (calendar->currentView() == CalendarView::Day) {
|
||||||
|
QDate dayViewDate = calendar->selectedDate();
|
||||||
|
summaries = getSummaries(dayViewDate, dayViewDate, 1);
|
||||||
|
} else {
|
||||||
|
summaries = getSummaries(calendar->firstVisibleDay(), calendar->lastVisibleDay(), 7);
|
||||||
|
}
|
||||||
calendar->fillEntries(activities, summaries, phasesEvents);
|
calendar->fillEntries(activities, summaries, phasesEvents);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -644,9 +685,15 @@ void
|
|||||||
PlanningCalendarWindow::updateActivitiesIfInRange
|
PlanningCalendarWindow::updateActivitiesIfInRange
|
||||||
(RideItem *rideItem)
|
(RideItem *rideItem)
|
||||||
{
|
{
|
||||||
if ( rideItem->dateTime.date() >= calendar->firstVisibleDay()
|
if (calendar->currentView() == CalendarView::Day) {
|
||||||
&& rideItem->dateTime.date() <= calendar->lastVisibleDay()) {
|
if (rideItem->dateTime.date() == calendar->selectedDate()) {
|
||||||
updateActivities();
|
updateActivities();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ( rideItem->dateTime.date() >= calendar->firstVisibleDay()
|
||||||
|
&& rideItem->dateTime.date() <= calendar->lastVisibleDay()) {
|
||||||
|
updateActivities();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -667,12 +714,12 @@ PlanningCalendarWindow::updateSeason
|
|||||||
|
|
||||||
bool
|
bool
|
||||||
PlanningCalendarWindow::movePlannedActivity
|
PlanningCalendarWindow::movePlannedActivity
|
||||||
(RideItem *rideItem, const QDate &destDay, bool force)
|
(RideItem *rideItem, const QDate &destDay, const QTime &destTime)
|
||||||
{
|
{
|
||||||
bool ret = false;
|
bool ret = false;
|
||||||
RideFile *rideFile = rideItem->ride();
|
RideFile *rideFile = rideItem->ride();
|
||||||
|
|
||||||
QDateTime rideDateTime(destDay, rideFile->startTime().time());
|
QDateTime rideDateTime(destDay, destTime);
|
||||||
rideFile->setStartTime(rideDateTime);
|
rideFile->setStartTime(rideDateTime);
|
||||||
QString basename = rideDateTime.toString("yyyy_MM_dd_HH_mm_ss");
|
QString basename = rideDateTime.toString("yyyy_MM_dd_HH_mm_ss");
|
||||||
|
|
||||||
@@ -683,8 +730,7 @@ PlanningCalendarWindow::movePlannedActivity
|
|||||||
filename = context->athlete->home->activities().canonicalPath() + "/" + basename + ".json";
|
filename = context->athlete->home->activities().canonicalPath() + "/" + basename + ".json";
|
||||||
}
|
}
|
||||||
QFile out(filename);
|
QFile out(filename);
|
||||||
if ( ( force
|
if ( ! out.exists()
|
||||||
|| (! force && ! out.exists()))
|
|
||||||
&& RideFileFactory::instance().writeRideFile(context, rideFile, out, "json")) {
|
&& RideFileFactory::instance().writeRideFile(context, rideFile, out, "json")) {
|
||||||
context->tab->setNoSwitch(true);
|
context->tab->setNoSwitch(true);
|
||||||
context->athlete->rideCache->removeRide(rideItem->fileName);
|
context->athlete->rideCache->removeRide(rideItem->fileName);
|
||||||
|
|||||||
@@ -85,14 +85,14 @@ class PlanningCalendarWindow : public GcChartWindow
|
|||||||
void updatePrimaryConfigCombos();
|
void updatePrimaryConfigCombos();
|
||||||
void updateSecondaryConfigCombo();
|
void updateSecondaryConfigCombo();
|
||||||
QHash<QDate, QList<CalendarEntry>> getActivities(const QDate &firstDay, const QDate &lastDay) const;
|
QHash<QDate, QList<CalendarEntry>> getActivities(const QDate &firstDay, const QDate &lastDay) const;
|
||||||
QList<CalendarSummary> getWeeklySummaries(const QDate &firstDay, const QDate &lastDay) const;
|
QList<CalendarSummary> getSummaries(const QDate &firstDay, const QDate &lastDay, int timeBucketSize = 7) const;
|
||||||
QHash<QDate, QList<CalendarEntry>> getPhasesEvents(const Season &season, const QDate &firstDay, const QDate &lastDay) const;
|
QHash<QDate, QList<CalendarEntry>> getPhasesEvents(const Season &season, const QDate &firstDay, const QDate &lastDay) const;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void updateActivities();
|
void updateActivities();
|
||||||
void updateActivitiesIfInRange(RideItem *rideItem);
|
void updateActivitiesIfInRange(RideItem *rideItem);
|
||||||
void updateSeason(Season const *season, bool allowKeepMonth = false);
|
void updateSeason(Season const *season, bool allowKeepMonth = false);
|
||||||
bool movePlannedActivity(RideItem *rideItem, const QDate &destDay, bool force = false);
|
bool movePlannedActivity(RideItem *rideItem, const QDate &destDay, const QTime &destTime = QTime());
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
1056
src/Gui/Calendar.cpp
1056
src/Gui/Calendar.cpp
File diff suppressed because it is too large
Load Diff
@@ -20,7 +20,9 @@
|
|||||||
#define CALENDAR_H
|
#define CALENDAR_H
|
||||||
|
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
#include <QStackedWidget>
|
||||||
#include <QTableWidget>
|
#include <QTableWidget>
|
||||||
|
#include <QCalendarWidget>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QComboBox>
|
#include <QComboBox>
|
||||||
@@ -29,10 +31,92 @@
|
|||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
|
||||||
|
#include "CalendarItemDelegates.h"
|
||||||
#include "CalendarData.h"
|
#include "CalendarData.h"
|
||||||
#include "TimeUtils.h"
|
#include "TimeUtils.h"
|
||||||
|
|
||||||
|
|
||||||
|
class CalendarOverview : public QCalendarWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit CalendarOverview(QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
QDate firstVisibleDay() const;
|
||||||
|
QDate lastVisibleDay() const;
|
||||||
|
void limitDateRange(const DateRange &dr);
|
||||||
|
void fillEntries(const QHash<QDate, QList<CalendarEntry>> &activityEntries, const QHash<QDate, QList<CalendarEntry>> &headlineEntries);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
#if QT_VERSION < 0x060000
|
||||||
|
void paintCell(QPainter *painter, const QRect &rect, const QDate &date) const override;
|
||||||
|
#else
|
||||||
|
void paintCell(QPainter *painter, const QRect &rect, QDate date) const override;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private:
|
||||||
|
QHash<QDate, QList<CalendarEntry>> activityEntries;
|
||||||
|
QHash<QDate, QList<CalendarEntry>> headlineEntries;
|
||||||
|
|
||||||
|
void drawEntries(QPainter *painter, const QList<CalendarEntry> &entries, QPolygon polygon, int maxEntries, int shiftX) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class CalendarDayTable : public QTableWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit CalendarDayTable(const QDate &date, QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
bool setDay(const QDate &date);
|
||||||
|
QDate selectedDate() const;
|
||||||
|
bool isInDateRange(const QDate &date) const;
|
||||||
|
void fillEntries(const QList<CalendarEntry> &activityEntries, const CalendarSummary &summary, const QList<CalendarEntry> &headlineEntries);
|
||||||
|
void limitDateRange(const DateRange &dr);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void dayClicked(const CalendarDay &day, const QTime &time);
|
||||||
|
void dayRightClicked(const CalendarDay &day, const QTime &time);
|
||||||
|
void entryClicked(const CalendarDay &day, int entryIdx);
|
||||||
|
void entryRightClicked(const CalendarDay &day, int entryIdx);
|
||||||
|
void entryMoved(const CalendarEntry &activity, const QDate &srcDay, const QDate &destDay, const QTime &destTime);
|
||||||
|
void dayChanged(const QDate &date);
|
||||||
|
void showInTrainMode(const CalendarEntry &activity);
|
||||||
|
void viewActivity(const CalendarEntry &activity);
|
||||||
|
void addActivity(bool plan, const QDate &day, const QTime &time);
|
||||||
|
void delActivity(const CalendarEntry &activity);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void changeEvent(QEvent *event) override;
|
||||||
|
void mouseDoubleClickEvent(QMouseEvent *event) override;
|
||||||
|
void mousePressEvent(QMouseEvent *event) override;
|
||||||
|
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||||
|
void mouseMoveEvent(QMouseEvent *event) override;
|
||||||
|
void dragEnterEvent(QDragEnterEvent *event) override;
|
||||||
|
void dragMoveEvent(QDragMoveEvent *event) override;
|
||||||
|
void dragLeaveEvent(QDragLeaveEvent *event) override;
|
||||||
|
void dropEvent(QDropEvent *event) override;
|
||||||
|
#if QT_VERSION < 0x060000
|
||||||
|
QAbstractItemDelegate *itemDelegateForIndex(const QModelIndex &index) const;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void showContextMenu(const QPoint &pos);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QDate date;
|
||||||
|
DateRange dr;
|
||||||
|
|
||||||
|
QTimer dragTimer;
|
||||||
|
QPoint pressedPos;
|
||||||
|
QModelIndex pressedIndex;
|
||||||
|
bool isDraggable = false;
|
||||||
|
TimeScaleData timeScaleData;
|
||||||
|
|
||||||
|
void setDropIndicator(int y, BlockIndicator block);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
class CalendarMonthTable : public QTableWidget {
|
class CalendarMonthTable : public QTableWidget {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
@@ -42,11 +126,6 @@ public:
|
|||||||
|
|
||||||
bool selectDay(const QDate &day);
|
bool selectDay(const QDate &day);
|
||||||
bool setMonth(const QDate &dateInMonth, bool allowKeepMonth = false);
|
bool setMonth(const QDate &dateInMonth, bool allowKeepMonth = false);
|
||||||
bool addMonths(int months);
|
|
||||||
bool addYears(int years);
|
|
||||||
QDate fitDate(const QDate &date) const;
|
|
||||||
bool canAddMonths(int months) const;
|
|
||||||
bool canAddYears(int years) const;
|
|
||||||
bool isInDateRange(const QDate &date) const;
|
bool isInDateRange(const QDate &date) const;
|
||||||
void fillEntries(const QHash<QDate, QList<CalendarEntry>> &activityEntries, const QList<CalendarSummary> &summaries, const QHash<QDate, QList<CalendarEntry>> &headlineEntries);
|
void fillEntries(const QHash<QDate, QList<CalendarEntry>> &activityEntries, const QList<CalendarSummary> &summaries, const QHash<QDate, QList<CalendarEntry>> &headlineEntries);
|
||||||
QDate firstOfCurrentMonth() const;
|
QDate firstOfCurrentMonth() const;
|
||||||
@@ -57,6 +136,7 @@ public:
|
|||||||
void setFirstDayOfWeek(Qt::DayOfWeek firstDayOfWeek);
|
void setFirstDayOfWeek(Qt::DayOfWeek firstDayOfWeek);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
void daySelected(const CalendarDay &day);
|
||||||
void dayClicked(const CalendarDay &day);
|
void dayClicked(const CalendarDay &day);
|
||||||
void dayDblClicked(const CalendarDay &day);
|
void dayDblClicked(const CalendarDay &day);
|
||||||
void moreClicked(const CalendarDay &day);
|
void moreClicked(const CalendarDay &day);
|
||||||
@@ -65,7 +145,7 @@ signals:
|
|||||||
void entryClicked(const CalendarDay &day, int entryIdx);
|
void entryClicked(const CalendarDay &day, int entryIdx);
|
||||||
void entryDblClicked(const CalendarDay &day, int entryIdx);
|
void entryDblClicked(const CalendarDay &day, int entryIdx);
|
||||||
void entryRightClicked(const CalendarDay &day, int entryIdx);
|
void entryRightClicked(const CalendarDay &day, int entryIdx);
|
||||||
void entryMoved(const CalendarEntry &activity, const QDate &srcDay, const QDate &destDay);
|
void entryMoved(const CalendarEntry &activity, const QDate &srcDay, const QDate &destDay, const QTime &destTime);
|
||||||
void summaryClicked(const QModelIndex &index);
|
void summaryClicked(const QModelIndex &index);
|
||||||
void summaryDblClicked(const QModelIndex &index);
|
void summaryDblClicked(const QModelIndex &index);
|
||||||
void summaryRightClicked(const QModelIndex &index);
|
void summaryRightClicked(const QModelIndex &index);
|
||||||
@@ -96,12 +176,11 @@ private slots:
|
|||||||
void showContextMenu(const QPoint &pos);
|
void showContextMenu(const QPoint &pos);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int findEntry(const QModelIndex &index, const QPoint &pos) const;
|
|
||||||
|
|
||||||
Qt::DayOfWeek firstDayOfWeek = Qt::Monday;
|
Qt::DayOfWeek firstDayOfWeek = Qt::Monday;
|
||||||
QDate firstOfMonth;
|
QDate firstOfMonth;
|
||||||
QDate startDate; // first visible date
|
QDate startDate; // first visible date
|
||||||
QDate endDate; // last visible date
|
QDate endDate; // last visible date
|
||||||
|
QDate currentDate; // currently selected date
|
||||||
DateRange dr;
|
DateRange dr;
|
||||||
|
|
||||||
QTimer dragTimer;
|
QTimer dragTimer;
|
||||||
@@ -111,6 +190,12 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
enum class CalendarView {
|
||||||
|
Day = 0,
|
||||||
|
Month = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
class Calendar : public QWidget {
|
class Calendar : public QWidget {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
@@ -118,22 +203,34 @@ public:
|
|||||||
explicit Calendar(Qt::DayOfWeek firstDayOfWeek = Qt::Monday, QWidget *parent = nullptr);
|
explicit Calendar(Qt::DayOfWeek firstDayOfWeek = Qt::Monday, QWidget *parent = nullptr);
|
||||||
explicit Calendar(const QDate &dateInMonth, Qt::DayOfWeek firstDayOfWeek = Qt::Monday, QWidget *parent = nullptr);
|
explicit Calendar(const QDate &dateInMonth, Qt::DayOfWeek firstDayOfWeek = Qt::Monday, QWidget *parent = nullptr);
|
||||||
|
|
||||||
void setMonth(const QDate &dateInMonth, bool allowKeepMonth = false);
|
void setDate(const QDate &dateInMonth, bool allowKeepMonth = false);
|
||||||
void fillEntries(const QHash<QDate, QList<CalendarEntry>> &activityEntries, const QList<CalendarSummary> &summaries, const QHash<QDate, QList<CalendarEntry>> &headlineEntries);
|
void fillEntries(const QHash<QDate, QList<CalendarEntry>> &activityEntries, const QList<CalendarSummary> &summaries, const QHash<QDate, QList<CalendarEntry>> &headlineEntries);
|
||||||
QDate firstOfCurrentMonth() const;
|
QDate firstOfCurrentMonth() const;
|
||||||
QDate firstVisibleDay() const;
|
QDate firstVisibleDay() const;
|
||||||
QDate lastVisibleDay() const;
|
QDate lastVisibleDay() const;
|
||||||
QDate selectedDate() const;
|
QDate selectedDate() const;
|
||||||
|
CalendarView currentView() const;
|
||||||
|
|
||||||
|
bool addMonths(int months);
|
||||||
|
bool addYears(int years);
|
||||||
|
QDate fitDate(const QDate &date, bool preferToday = true) const;
|
||||||
|
bool canAddMonths(int months) const;
|
||||||
|
bool canAddYears(int years) const;
|
||||||
|
bool isInDateRange(const QDate &date) const;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
void setView(CalendarView view);
|
||||||
void activateDateRange(const DateRange &dr, bool allowKeepMonth = false);
|
void activateDateRange(const DateRange &dr, bool allowKeepMonth = false);
|
||||||
void setFirstDayOfWeek(Qt::DayOfWeek firstDayOfWeek);
|
void setFirstDayOfWeek(Qt::DayOfWeek firstDayOfWeek);
|
||||||
void setSummaryMonthVisible(bool visible);
|
void setSummaryMonthVisible(bool visible);
|
||||||
void applyNavIcons();
|
void applyNavIcons();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
void viewChanged(CalendarView newView, CalendarView oldView);
|
||||||
|
void daySelected(const QDate &date);
|
||||||
void dayClicked(const QDate &date);
|
void dayClicked(const QDate &date);
|
||||||
void summaryClicked(const QDate &date);
|
void summaryClicked(const QDate &date);
|
||||||
|
void dayChanged(const QDate &date);
|
||||||
void monthChanged(const QDate &month, const QDate &firstVisible, const QDate &lastVisible);
|
void monthChanged(const QDate &month, const QDate &firstVisible, const QDate &lastVisible);
|
||||||
void dateRangeActivated(const QString &name);
|
void dateRangeActivated(const QString &name);
|
||||||
void showInTrainMode(const CalendarEntry &activity);
|
void showInTrainMode(const CalendarEntry &activity);
|
||||||
@@ -141,22 +238,29 @@ signals:
|
|||||||
void addActivity(bool plan, const QDate &day, const QTime &time);
|
void addActivity(bool plan, const QDate &day, const QTime &time);
|
||||||
void delActivity(const CalendarEntry &activity);
|
void delActivity(const CalendarEntry &activity);
|
||||||
void repeatSchedule(const QDate &day);
|
void repeatSchedule(const QDate &day);
|
||||||
void moveActivity(const CalendarEntry &activity, const QDate &srcDay, const QDate &destDay);
|
void moveActivity(const CalendarEntry &activity, const QDate &srcDay, const QDate &destDay, const QTime &destTime);
|
||||||
void insertRestday(const QDate &day);
|
void insertRestday(const QDate &day);
|
||||||
void delRestday(const QDate &day);
|
void delRestday(const QDate &day);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QPushButton *prevYButton;
|
QAction *prevYAction;
|
||||||
QPushButton *prevMButton;
|
QAction *prevMAction;
|
||||||
QPushButton *nextMButton;
|
QAction *nextMAction;
|
||||||
QPushButton *nextYButton;
|
QAction *nextYAction;
|
||||||
QPushButton *todayButton;
|
QAction *todayAction;
|
||||||
|
QAction *dayAction;
|
||||||
|
QAction *monthAction;
|
||||||
QLabel *dateLabel;
|
QLabel *dateLabel;
|
||||||
|
QStackedWidget *tableStack;
|
||||||
|
QWidget *dayCalendar;
|
||||||
|
CalendarOverview *dayDateSelector;
|
||||||
|
QLabel *dayPhaseLabel;
|
||||||
|
QLabel *dayEventLabel;
|
||||||
|
CalendarDayTable *dayTable;
|
||||||
CalendarMonthTable *monthTable;
|
CalendarMonthTable *monthTable;
|
||||||
QHash<QDate, QList<CalendarEntry>> headlineEntries;
|
DateRange dateRange;
|
||||||
|
|
||||||
void setNavButtonState();
|
void setNavButtonState();
|
||||||
bool isDark() const;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
112
src/Gui/CalendarData.cpp
Normal file
112
src/Gui/CalendarData.cpp
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Joachim Kohlhammer (joachim.kohlhammer@gmx.de)
|
||||||
|
*
|
||||||
|
* 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 2 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, write to the Free Software Foundation, Inc., 51
|
||||||
|
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <CalendarData.h>
|
||||||
|
|
||||||
|
|
||||||
|
CalendarEntryLayouter::CalendarEntryLayouter
|
||||||
|
()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QList<CalendarEntryLayout>
|
||||||
|
CalendarEntryLayouter::layout
|
||||||
|
(const QList<CalendarEntry> &entries)
|
||||||
|
{
|
||||||
|
QList<QList<int>> clusters = groupOverlapping(entries);
|
||||||
|
QList<CalendarEntryLayout> layouted;
|
||||||
|
for (const auto &cluster : clusters) {
|
||||||
|
layouted += assignColumns(cluster, entries);
|
||||||
|
}
|
||||||
|
return layouted;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QList<QList<int>>
|
||||||
|
CalendarEntryLayouter::groupOverlapping
|
||||||
|
(const QList<CalendarEntry> &entries)
|
||||||
|
{
|
||||||
|
QList<QList<int>> clusters;
|
||||||
|
QList<int> currentCluster;
|
||||||
|
QTime clusterEnd;
|
||||||
|
|
||||||
|
for (int entryIdx = 0; entryIdx < entries.count(); ++entryIdx) {
|
||||||
|
const CalendarEntry &entry = entries[entryIdx];
|
||||||
|
QTime end = entry.start.addSecs(entry.durationSecs);
|
||||||
|
|
||||||
|
if (currentCluster.isEmpty()) {
|
||||||
|
currentCluster.append(entryIdx);
|
||||||
|
clusterEnd = end;
|
||||||
|
} else {
|
||||||
|
if (entry.start < clusterEnd) {
|
||||||
|
currentCluster.append(entryIdx);
|
||||||
|
if (end > clusterEnd) {
|
||||||
|
clusterEnd = end;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
clusters.append(currentCluster);
|
||||||
|
currentCluster = { entryIdx };
|
||||||
|
clusterEnd = end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (! currentCluster.isEmpty()) {
|
||||||
|
clusters.append(currentCluster);
|
||||||
|
}
|
||||||
|
|
||||||
|
return clusters;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QList<CalendarEntryLayout>
|
||||||
|
CalendarEntryLayouter::assignColumns
|
||||||
|
(const QList<int> &cluster, const QList<CalendarEntry> &entries)
|
||||||
|
{
|
||||||
|
QList<QTime> columns;
|
||||||
|
QMap<int, int> entryColumns;
|
||||||
|
|
||||||
|
for (int entryIdx : cluster) {
|
||||||
|
const CalendarEntry &entry = entries[entryIdx];
|
||||||
|
QTime start = entry.start;
|
||||||
|
QTime end = start.addSecs(entry.durationSecs);
|
||||||
|
bool found = false;
|
||||||
|
for (int colIdx = 0; colIdx < columns.count(); ++colIdx) {
|
||||||
|
if (columns[colIdx] <= start) {
|
||||||
|
columns[colIdx] = end;
|
||||||
|
entryColumns[entryIdx] = colIdx;
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (! found) {
|
||||||
|
columns << end;
|
||||||
|
entryColumns[entryIdx] = columns.count() - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<CalendarEntryLayout> result;
|
||||||
|
for (int entryIdx : cluster) {
|
||||||
|
result << CalendarEntryLayout({
|
||||||
|
entryIdx,
|
||||||
|
entryColumns[entryIdx],
|
||||||
|
static_cast<int>(columns.count())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@@ -1,10 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Joachim Kohlhammer (joachim.kohlhammer@gmx.de)
|
||||||
|
*
|
||||||
|
* 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 2 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, write to the Free Software Foundation, Inc., 51
|
||||||
|
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
#ifndef CALENDARDATA_H
|
#ifndef CALENDARDATA_H
|
||||||
#define CALENDARDATA_H
|
#define CALENDARDATA_H
|
||||||
|
|
||||||
|
#include <QMetaType>
|
||||||
#include <QDate>
|
#include <QDate>
|
||||||
#include <QTime>
|
#include <QTime>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
|
#include <QMap>
|
||||||
|
#include <QColor>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#define ENTRY_TYPE_ACTIVITY 0
|
#define ENTRY_TYPE_ACTIVITY 0
|
||||||
#define ENTRY_TYPE_PLANNED_ACTIVITY 1
|
#define ENTRY_TYPE_PLANNED_ACTIVITY 1
|
||||||
@@ -13,17 +35,6 @@
|
|||||||
#define ENTRY_TYPE_OTHER 99
|
#define ENTRY_TYPE_OTHER 99
|
||||||
|
|
||||||
|
|
||||||
struct CalendarEvent {
|
|
||||||
QString name;
|
|
||||||
QDate date;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CalendarPhase {
|
|
||||||
QString name;
|
|
||||||
QDate start;
|
|
||||||
QDate end;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CalendarEntry {
|
struct CalendarEntry {
|
||||||
QString primary;
|
QString primary;
|
||||||
QString secondary;
|
QString secondary;
|
||||||
@@ -40,6 +51,12 @@ struct CalendarEntry {
|
|||||||
QDate spanEnd = QDate();
|
QDate spanEnd = QDate();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct CalendarEntryLayout {
|
||||||
|
int entryIdx;
|
||||||
|
int columnIndex;
|
||||||
|
int columnCount;
|
||||||
|
};
|
||||||
|
|
||||||
struct CalendarDay {
|
struct CalendarDay {
|
||||||
QDate date;
|
QDate date;
|
||||||
bool isDimmed;
|
bool isDimmed;
|
||||||
@@ -52,7 +69,20 @@ struct CalendarSummary {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(CalendarEntry)
|
Q_DECLARE_METATYPE(CalendarEntry)
|
||||||
|
Q_DECLARE_METATYPE(CalendarEntryLayout)
|
||||||
Q_DECLARE_METATYPE(CalendarDay)
|
Q_DECLARE_METATYPE(CalendarDay)
|
||||||
Q_DECLARE_METATYPE(CalendarSummary)
|
Q_DECLARE_METATYPE(CalendarSummary)
|
||||||
|
|
||||||
|
|
||||||
|
class CalendarEntryLayouter {
|
||||||
|
public:
|
||||||
|
explicit CalendarEntryLayouter();
|
||||||
|
|
||||||
|
QList<CalendarEntryLayout> layout(const QList<CalendarEntry> &entries);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QList<QList<int>> groupOverlapping(const QList<CalendarEntry> &entries);
|
||||||
|
QList<CalendarEntryLayout> assignColumns(const QList<int> &cluster, const QList<CalendarEntry> &entries);
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Joachim Kohlhammer (joachim.kohlhammer@gmx.de)
|
||||||
|
*
|
||||||
|
* 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 2 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, write to the Free Software Foundation, Inc., 51
|
||||||
|
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
#include "CalendarItemDelegates.h"
|
#include "CalendarItemDelegates.h"
|
||||||
|
|
||||||
#include <QDate>
|
#include <QDate>
|
||||||
@@ -13,6 +31,561 @@
|
|||||||
#include "Colors.h"
|
#include "Colors.h"
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// TimeScaleData
|
||||||
|
|
||||||
|
TimeScaleData::TimeScaleData
|
||||||
|
()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TimeScaleData::setFirstMinute
|
||||||
|
(int minute)
|
||||||
|
{
|
||||||
|
_firstMinute = minute;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int
|
||||||
|
TimeScaleData::firstMinute
|
||||||
|
() const
|
||||||
|
{
|
||||||
|
return _firstMinute;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
TimeScaleData::setLastMinute
|
||||||
|
(int minute)
|
||||||
|
{
|
||||||
|
_lastMinute = minute;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int
|
||||||
|
TimeScaleData::lastMinute
|
||||||
|
() const
|
||||||
|
{
|
||||||
|
return _lastMinute;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
TimeScaleData::setStepSize
|
||||||
|
(int step)
|
||||||
|
{
|
||||||
|
_stepSize = step;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int
|
||||||
|
TimeScaleData::stepSize
|
||||||
|
() const
|
||||||
|
{
|
||||||
|
return _stepSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
double
|
||||||
|
TimeScaleData::pixelsPerMinute
|
||||||
|
(int availableHeight) const
|
||||||
|
{
|
||||||
|
int totalMinutes = _lastMinute - _firstMinute;
|
||||||
|
if (totalMinutes <= 0) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
return static_cast<double>(availableHeight) / totalMinutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
double
|
||||||
|
TimeScaleData::pixelsPerMinute
|
||||||
|
(const QRect& rect) const
|
||||||
|
{
|
||||||
|
return pixelsPerMinute(rect.height());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int
|
||||||
|
TimeScaleData::minuteToY
|
||||||
|
(int minute, int rectTop, int rectHeight) const
|
||||||
|
{
|
||||||
|
if (minute < _firstMinute) {
|
||||||
|
return rectTop;
|
||||||
|
}
|
||||||
|
if (minute > _lastMinute) {
|
||||||
|
return rectTop + rectHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
int totalMinutes = _lastMinute - _firstMinute;
|
||||||
|
double pixelsPerMinute = static_cast<double>(rectHeight) / totalMinutes;
|
||||||
|
return std::min(rectHeight, rectTop + static_cast<int>((minute - _firstMinute) * pixelsPerMinute));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int
|
||||||
|
TimeScaleData::minuteToY
|
||||||
|
(int minute, const QRect &rect) const
|
||||||
|
{
|
||||||
|
return minuteToY(minute, rect.top(), rect.height());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int
|
||||||
|
TimeScaleData::minuteToY
|
||||||
|
(const QTime &time, const QRect& rect) const
|
||||||
|
{
|
||||||
|
return minuteToY(time.hour() * 60 + time.minute(), rect.top(), rect.height());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QTime
|
||||||
|
TimeScaleData::timeFromY
|
||||||
|
(int y, const QRect &rect, int snap) const
|
||||||
|
{
|
||||||
|
int s = std::max(1, snap);
|
||||||
|
int minute = trunc((_firstMinute + y / pixelsPerMinute(rect)) / s) * s;
|
||||||
|
return QTime(0, 0).addSecs(60 * minute);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// CalendarDayViewDayDelegate
|
||||||
|
|
||||||
|
ColumnDelegatingItemDelegate::ColumnDelegatingItemDelegate
|
||||||
|
(QList<QStyledItemDelegate*> delegates, QObject *parent)
|
||||||
|
: QStyledItemDelegate(parent), delegates(delegates)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QStyledItemDelegate*
|
||||||
|
ColumnDelegatingItemDelegate::getDelegate
|
||||||
|
(int idx) const
|
||||||
|
{
|
||||||
|
return delegates.value(idx, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QWidget*
|
||||||
|
ColumnDelegatingItemDelegate::createEditor
|
||||||
|
(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
QStyledItemDelegate *delegate = delegates.value(index.column(), nullptr);
|
||||||
|
if (delegate != nullptr) {
|
||||||
|
return delegate->createEditor(parent, option, index);
|
||||||
|
} else {
|
||||||
|
return QStyledItemDelegate::createEditor(parent, option, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
ColumnDelegatingItemDelegate::paint
|
||||||
|
(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
QStyledItemDelegate *delegate = delegates.value(index.column(), nullptr);
|
||||||
|
if (delegate != nullptr) {
|
||||||
|
delegate->paint(painter, option, index);
|
||||||
|
} else {
|
||||||
|
QStyledItemDelegate::paint(painter, option, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool
|
||||||
|
ColumnDelegatingItemDelegate::helpEvent
|
||||||
|
(QHelpEvent *event, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index)
|
||||||
|
{
|
||||||
|
QStyledItemDelegate *delegate = delegates.value(index.column(), nullptr);
|
||||||
|
if (delegate != nullptr) {
|
||||||
|
return delegate->helpEvent(event, view, option, index);
|
||||||
|
} else {
|
||||||
|
return QStyledItemDelegate::helpEvent(event, view, option, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
ColumnDelegatingItemDelegate::setEditorData
|
||||||
|
(QWidget *editor, const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
QStyledItemDelegate *delegate = delegates.value(index.column(), nullptr);
|
||||||
|
if (delegate != nullptr) {
|
||||||
|
return delegate->setEditorData(editor, index);
|
||||||
|
} else {
|
||||||
|
return QStyledItemDelegate::setEditorData(editor, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
ColumnDelegatingItemDelegate::setModelData
|
||||||
|
(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
QStyledItemDelegate *delegate = delegates.value(index.column(), nullptr);
|
||||||
|
if (delegate != nullptr) {
|
||||||
|
delegate->setModelData(editor, model, index);
|
||||||
|
} else {
|
||||||
|
QStyledItemDelegate::setModelData(editor, model, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QSize
|
||||||
|
ColumnDelegatingItemDelegate::sizeHint
|
||||||
|
(const QStyleOptionViewItem &option, const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
QStyledItemDelegate *delegate = delegates.value(index.column(), nullptr);
|
||||||
|
if (delegate != nullptr) {
|
||||||
|
return delegate->sizeHint(option, index);
|
||||||
|
} else {
|
||||||
|
return QStyledItemDelegate::sizeHint(option, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
ColumnDelegatingItemDelegate::updateEditorGeometry
|
||||||
|
(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
QStyledItemDelegate *delegate = delegates.value(index.column(), nullptr);
|
||||||
|
if (delegate != nullptr) {
|
||||||
|
delegate->updateEditorGeometry(editor, option, index);
|
||||||
|
} else {
|
||||||
|
QStyledItemDelegate::updateEditorGeometry(editor, option, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// CalendarDayViewDayDelegate
|
||||||
|
|
||||||
|
CalendarDayViewDayDelegate::CalendarDayViewDayDelegate
|
||||||
|
(TimeScaleData const * const timeScaleData, QObject *parent)
|
||||||
|
: QStyledItemDelegate(parent), timeScaleData(timeScaleData)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
CalendarDayViewDayDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
if (index.row() != 0) {
|
||||||
|
QStyledItemDelegate::paint(painter, option, index);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
painter->save();
|
||||||
|
painter->setRenderHint(QPainter::Antialiasing, true);
|
||||||
|
|
||||||
|
QStyleOptionViewItem opt = option;
|
||||||
|
initStyleOption(&opt, index);
|
||||||
|
|
||||||
|
bool ok;
|
||||||
|
int pressedEntryIdx = index.data(Qt::UserRole + 2).toInt(&ok);
|
||||||
|
if (! ok) {
|
||||||
|
pressedEntryIdx = -2;
|
||||||
|
}
|
||||||
|
QDate date = index.data(Qt::UserRole).toDate();
|
||||||
|
CalendarDay calendarDay = index.data(Qt::UserRole + 1).value<CalendarDay>();
|
||||||
|
QList<CalendarEntryLayout> layout = index.data(Qt::UserRole + 3).value<QList<CalendarEntryLayout>>();
|
||||||
|
#if QT_VERSION < 0x060000
|
||||||
|
entryRects[index].clear();
|
||||||
|
for (int i = 0; i < layout.count(); ++i) {
|
||||||
|
entryRects[index] << QRect();
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
entryRects[index].resize(layout.count());
|
||||||
|
#endif
|
||||||
|
bool isToday = (date == QDate::currentDate());
|
||||||
|
|
||||||
|
// Horizontal Lines
|
||||||
|
painter->save();
|
||||||
|
QPen linePen(Qt::lightGray, 1, Qt::DotLine);
|
||||||
|
painter->setPen(linePen);
|
||||||
|
for (int minute = timeScaleData->firstMinute(); minute <= timeScaleData->lastMinute(); minute += timeScaleData->stepSize()) {
|
||||||
|
int y = timeScaleData->minuteToY(minute, option.rect);
|
||||||
|
painter->drawLine(option.rect.left(), y, option.rect.right(), y);
|
||||||
|
}
|
||||||
|
painter->restore();
|
||||||
|
|
||||||
|
// Activities
|
||||||
|
const int columnSpacing = 10 * dpiXFactor;
|
||||||
|
const int lineSpacing = 2 * dpiYFactor;
|
||||||
|
const int iconSpacing = 2 * dpiXFactor;
|
||||||
|
const int radius = 4 * dpiXFactor;
|
||||||
|
const int horPadding = 4 * dpiXFactor;
|
||||||
|
QFont entryFont = painter->font();
|
||||||
|
entryFont.setPointSize(entryFont.pointSize() * 0.95);
|
||||||
|
QFontMetrics entryFM(entryFont);
|
||||||
|
int lineHeight = entryFM.height();
|
||||||
|
painter->setFont(entryFont);
|
||||||
|
|
||||||
|
int rectLeft = option.rect.left() + columnSpacing;
|
||||||
|
int rectWidth = option.rect.width() - 2 * columnSpacing;
|
||||||
|
for (const CalendarEntryLayout &l : layout) {
|
||||||
|
if (calendarDay.entries.count() < l.entryIdx) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
bool pressed = (l.entryIdx == pressedEntryIdx);
|
||||||
|
const CalendarEntry &entry = calendarDay.entries[l.entryIdx];
|
||||||
|
int startMinute = entry.start.hour() * 60 + entry.start.minute();
|
||||||
|
int endMinute = startMinute + entry.durationSecs / 60;
|
||||||
|
int columnWidth = (rectWidth - (l.columnCount - 1) * columnSpacing) / std::max(1, l.columnCount);
|
||||||
|
int left = rectLeft + l.columnIndex * (columnWidth + columnSpacing);
|
||||||
|
int top = timeScaleData->minuteToY(startMinute, option.rect);
|
||||||
|
int bottom = timeScaleData->minuteToY(endMinute, option.rect);
|
||||||
|
int height = std::max(1, bottom - top);
|
||||||
|
int numLines = (height + lineSpacing) / (lineHeight + lineSpacing);
|
||||||
|
|
||||||
|
painter->save();
|
||||||
|
QRect entryRect(left, top, columnWidth, height);
|
||||||
|
entryRects[index][l.entryIdx] = entryRect;
|
||||||
|
QColor entryBG = option.palette.base().color();
|
||||||
|
QColor entryFrame = entry.color;
|
||||||
|
if (pressed) {
|
||||||
|
entryBG = option.palette.highlight().color();
|
||||||
|
entryFrame = entryBG;
|
||||||
|
} else if (entry.type == ENTRY_TYPE_ACTIVITY) {
|
||||||
|
QColor overlayBG = entry.color;
|
||||||
|
overlayBG = entry.color;
|
||||||
|
if (GCColor::isPaletteDark(option.palette)) {
|
||||||
|
overlayBG.setAlpha(150);
|
||||||
|
} else {
|
||||||
|
overlayBG.setAlpha(60);
|
||||||
|
}
|
||||||
|
entryBG = GCColor::blendedColor(overlayBG, entryBG);
|
||||||
|
entryFrame = entryBG;
|
||||||
|
}
|
||||||
|
painter->save();
|
||||||
|
painter->setPen(entryFrame);
|
||||||
|
painter->setBrush(entryBG);
|
||||||
|
painter->drawRoundedRect(entryRect, radius, radius);
|
||||||
|
painter->restore();
|
||||||
|
|
||||||
|
int iconWidth = 0;
|
||||||
|
if (height >= lineHeight && columnWidth >= lineHeight) {
|
||||||
|
QColor pixmapColor(entry.color);
|
||||||
|
int headlineOffset = 0;
|
||||||
|
if (height >= 2 * lineHeight + lineSpacing && columnWidth >= 2 * lineHeight + lineSpacing) {
|
||||||
|
iconWidth = 2 * lineHeight + lineSpacing;
|
||||||
|
} else {
|
||||||
|
iconWidth = lineHeight;
|
||||||
|
}
|
||||||
|
QSize pixmapSize(iconWidth, iconWidth);
|
||||||
|
QPixmap pixmap;
|
||||||
|
if (entry.type == ENTRY_TYPE_PLANNED_ACTIVITY) {
|
||||||
|
if (pressed) {
|
||||||
|
pixmapColor = GCColor::invertColor(entryBG);
|
||||||
|
}
|
||||||
|
pixmap = svgAsColoredPixmap(entry.iconFile, pixmapSize, lineSpacing, pixmapColor);
|
||||||
|
} else {
|
||||||
|
pixmap = svgOnBackground(entry.iconFile, pixmapSize, lineSpacing, pixmapColor, radius);
|
||||||
|
}
|
||||||
|
painter->drawPixmap(left, top, pixmap);
|
||||||
|
}
|
||||||
|
if (numLines > 0) {
|
||||||
|
painter->save();
|
||||||
|
QRect textRect(left + iconWidth + horPadding, top, columnWidth - iconWidth - 2 * horPadding, lineHeight);
|
||||||
|
painter->setPen(GCColor::invertColor(entryBG));
|
||||||
|
painter->drawText(textRect, Qt::AlignLeft | Qt::AlignTop, entry.primary);
|
||||||
|
--numLines;
|
||||||
|
if (numLines > 0) {
|
||||||
|
textRect.translate(0, lineHeight + lineSpacing);
|
||||||
|
painter->drawText(textRect, Qt::AlignLeft | Qt::AlignTop, entry.secondary + " (" + entry.secondaryMetric + ")");
|
||||||
|
}
|
||||||
|
painter->restore();
|
||||||
|
}
|
||||||
|
painter->restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
painter->restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool
|
||||||
|
CalendarDayViewDayDelegate::helpEvent
|
||||||
|
(QHelpEvent *event, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index)
|
||||||
|
{
|
||||||
|
if (! event || ! view) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CalendarDay day = index.data(Qt::UserRole + 1).value<CalendarDay>();
|
||||||
|
int entryIdx = hitTestEntry(index, event->pos());
|
||||||
|
if (entryIdx >= 0 && entryIdx < day.entries.size()) {
|
||||||
|
CalendarEntry calEntry = day.entries[entryIdx];
|
||||||
|
QString tooltip = "<center><b>" + calEntry.primary + "</b>";
|
||||||
|
tooltip += "<br/>";
|
||||||
|
switch (calEntry.type) {
|
||||||
|
case ENTRY_TYPE_ACTIVITY:
|
||||||
|
tooltip += "[completed]";
|
||||||
|
break;
|
||||||
|
case ENTRY_TYPE_PLANNED_ACTIVITY:
|
||||||
|
tooltip += "[planned]";
|
||||||
|
break;
|
||||||
|
case ENTRY_TYPE_OTHER:
|
||||||
|
default:
|
||||||
|
tooltip += "[other]";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tooltip += "<br/>";
|
||||||
|
if (! calEntry.secondary.isEmpty()) {
|
||||||
|
tooltip += calEntry.secondaryMetric + ": " + calEntry.secondary;
|
||||||
|
tooltip += "<br/>";
|
||||||
|
}
|
||||||
|
if (calEntry.start.isValid()) {
|
||||||
|
tooltip += calEntry.start.toString();
|
||||||
|
if (calEntry.durationSecs > 0) {
|
||||||
|
tooltip += " - " + calEntry.start.addSecs(calEntry.durationSecs).toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tooltip += "</center>";
|
||||||
|
QToolTip::showText(event->globalPos(), tooltip, view);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return QStyledItemDelegate::helpEvent(event, view, option, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int
|
||||||
|
CalendarDayViewDayDelegate::hitTestEntry
|
||||||
|
(const QModelIndex &index, const QPoint &pos) const
|
||||||
|
{
|
||||||
|
if (! entryRects.contains(index)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
const QList<QRect> &rects = entryRects[index];
|
||||||
|
for (int i = 0; i < rects.size(); ++i) {
|
||||||
|
if (rects[i].contains(pos)) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// CalendarTimeScaleDelegate
|
||||||
|
|
||||||
|
CalendarTimeScaleDelegate::CalendarTimeScaleDelegate
|
||||||
|
(TimeScaleData *timeScaleData, QObject *parent)
|
||||||
|
: QStyledItemDelegate(parent), timeScaleData(timeScaleData)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
CalendarTimeScaleDelegate::paint
|
||||||
|
(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
if (index.row() != 0) {
|
||||||
|
QStyledItemDelegate::paint(painter, option, index);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int totalRangeMinutes = timeScaleData->lastMinute() - timeScaleData->firstMinute();
|
||||||
|
const int cellHeight = option.rect.height();
|
||||||
|
const double pixelsPerMinute = static_cast<double>(cellHeight) / totalRangeMinutes;
|
||||||
|
|
||||||
|
painter->save();
|
||||||
|
painter->setRenderHint(QPainter::Antialiasing, true);
|
||||||
|
QPen linePen(Qt::lightGray, 1, Qt::DotLine);
|
||||||
|
QPen textPen(Qt::darkGray);
|
||||||
|
painter->setPen(textPen);
|
||||||
|
|
||||||
|
QFont font = painter->font();
|
||||||
|
font.setPointSizeF(font.pointSizeF() * 0.9);
|
||||||
|
painter->setFont(font);
|
||||||
|
QFontMetrics fm(font);
|
||||||
|
|
||||||
|
int labelHeight = fm.height();
|
||||||
|
int minPixelSpacing = labelHeight + 4;
|
||||||
|
int calculatedMinInterval = std::ceil(minPixelSpacing / pixelsPerMinute);
|
||||||
|
int effectiveInterval = selectNiceInterval(calculatedMinInterval);
|
||||||
|
timeScaleData->setStepSize(effectiveInterval);
|
||||||
|
|
||||||
|
int previousYBottom = -labelHeight;
|
||||||
|
|
||||||
|
for (int minute = timeScaleData->firstMinute(); minute <= timeScaleData->lastMinute(); minute += timeScaleData->stepSize()) {
|
||||||
|
int y = timeScaleData->minuteToY(minute, option.rect);
|
||||||
|
QString timeLabel = QTime(0, 0).addSecs(minute * 60).toString("hh:mm");
|
||||||
|
|
||||||
|
QRect textRect;
|
||||||
|
if (minute == timeScaleData->firstMinute()) {
|
||||||
|
textRect = QRect(option.rect.left() + 4, y, option.rect.width() - 8, labelHeight);
|
||||||
|
} else if (minute + effectiveInterval > timeScaleData->lastMinute()) {
|
||||||
|
int bottomY = option.rect.bottom();
|
||||||
|
textRect = QRect(option.rect.left() + 4, bottomY - labelHeight, option.rect.width() - 8, labelHeight);
|
||||||
|
y = bottomY;
|
||||||
|
} else {
|
||||||
|
textRect = QRect(option.rect.left() + 4, y - labelHeight / 2, option.rect.width() - 8, labelHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (textRect.top() < previousYBottom + 2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
painter->setPen(linePen);
|
||||||
|
painter->drawLine(textRect.right(), y, option.rect.right(), y);
|
||||||
|
|
||||||
|
painter->setPen(textPen);
|
||||||
|
painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, timeLabel);
|
||||||
|
|
||||||
|
previousYBottom = textRect.bottom();
|
||||||
|
}
|
||||||
|
int blockY = -1;
|
||||||
|
QModelIndex scaleIndex = index.sibling(0, 0);
|
||||||
|
BlockIndicator blockIndicator = static_cast<BlockIndicator>(scaleIndex.data(Qt::UserRole + 1).toInt());
|
||||||
|
if (blockIndicator == BlockIndicator::AllBlock) {
|
||||||
|
blockY = option.rect.height();
|
||||||
|
} else if (blockIndicator == BlockIndicator::BlockBeforeNow) {
|
||||||
|
blockY = timeScaleData->minuteToY(QTime::currentTime(), option.rect);
|
||||||
|
}
|
||||||
|
if (blockY >= 0) {
|
||||||
|
QRect rect = option.rect;
|
||||||
|
rect.setHeight(blockY);
|
||||||
|
QColor blockColor = option.palette.color(QPalette::Disabled, QPalette::Base);
|
||||||
|
blockColor.setAlpha(180);
|
||||||
|
painter->fillRect(rect, blockColor);
|
||||||
|
}
|
||||||
|
int currentY = scaleIndex.data(Qt::UserRole).toInt();
|
||||||
|
if (currentY > blockY) {
|
||||||
|
painter->save();
|
||||||
|
painter->setPen(textPen);
|
||||||
|
painter->drawLine(option.rect.left(), currentY, option.rect.width(), currentY);
|
||||||
|
painter->restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
painter->restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QSize
|
||||||
|
CalendarTimeScaleDelegate::sizeHint
|
||||||
|
(const QStyleOptionViewItem& option, const QModelIndex& index) const
|
||||||
|
{
|
||||||
|
return QStyledItemDelegate::sizeHint(option, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int
|
||||||
|
CalendarTimeScaleDelegate::selectNiceInterval
|
||||||
|
(int minInterval) const
|
||||||
|
{
|
||||||
|
const QList<int> allowed = { 60, 120, 180, 240, 360, 720 };
|
||||||
|
for (int interval : allowed) {
|
||||||
|
if (interval >= minInterval) {
|
||||||
|
return interval;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1440;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
// CalendarDayDelegate
|
// CalendarDayDelegate
|
||||||
|
|
||||||
@@ -61,13 +634,13 @@ CalendarDayDelegate::paint
|
|||||||
|
|
||||||
painter->fillRect(opt.rect, bgColor);
|
painter->fillRect(opt.rect, bgColor);
|
||||||
|
|
||||||
int leftMargin = 4;
|
const int leftMargin = 4 * dpiXFactor;
|
||||||
int rightMargin = 4;
|
const int rightMargin = 4 * dpiXFactor;
|
||||||
int topMargin = 2;
|
const int topMargin = 2 * dpiYFactor;
|
||||||
int bottomMargin = 4;
|
const int bottomMargin = 4 * dpiYFactor;
|
||||||
int lineSpacing = 2;
|
const int lineSpacing = 2 * dpiYFactor;
|
||||||
int iconSpacing = 2;
|
const int iconSpacing = 2 * dpiXFactor;
|
||||||
int radius = 4;
|
const int radius = 4 * dpiXFactor;
|
||||||
|
|
||||||
// Day number / Headline
|
// Day number / Headline
|
||||||
painter->save();
|
painter->save();
|
||||||
@@ -336,8 +909,8 @@ CalendarDayDelegate::hitTestMore
|
|||||||
// CalendarSummaryDelegate
|
// CalendarSummaryDelegate
|
||||||
|
|
||||||
CalendarSummaryDelegate::CalendarSummaryDelegate
|
CalendarSummaryDelegate::CalendarSummaryDelegate
|
||||||
(QObject *parent)
|
(int horMargin, QObject *parent)
|
||||||
: QStyledItemDelegate(parent)
|
: QStyledItemDelegate(parent), horMargin(horMargin)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -357,9 +930,6 @@ CalendarSummaryDelegate::paint
|
|||||||
keyFont.setWeight(QFont::DemiBold);
|
keyFont.setWeight(QFont::DemiBold);
|
||||||
const QFontMetrics valueFontMetrics(valueFont);
|
const QFontMetrics valueFontMetrics(valueFont);
|
||||||
const QFontMetrics keyFontMetrics(keyFont);
|
const QFontMetrics keyFontMetrics(keyFont);
|
||||||
const int lineSpacing = 2;
|
|
||||||
const int horMargin = 4;
|
|
||||||
const int vertMargin = 4;
|
|
||||||
const int lineHeight = keyFontMetrics.height();
|
const int lineHeight = keyFontMetrics.height();
|
||||||
const int cellWidth = option.rect.width();
|
const int cellWidth = option.rect.width();
|
||||||
const int cellHeight = option.rect.height();
|
const int cellHeight = option.rect.height();
|
||||||
@@ -438,3 +1008,17 @@ CalendarSummaryDelegate::helpEvent
|
|||||||
QToolTip::showText(event->globalPos(), tooltip, view);
|
QToolTip::showText(event->globalPos(), tooltip, view);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QSize
|
||||||
|
CalendarSummaryDelegate::sizeHint
|
||||||
|
(const QStyleOptionViewItem &option, const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
const CalendarSummary summary = index.data(Qt::UserRole).value<CalendarSummary>();
|
||||||
|
QFont font;
|
||||||
|
font.setWeight(QFont::DemiBold);
|
||||||
|
const QFontMetrics fontMetrics(font);
|
||||||
|
const int lineHeight = fontMetrics.height();
|
||||||
|
|
||||||
|
return QSize(10, vertMargin + (lineHeight + lineSpacing) * summary.keyValues.count() - lineSpacing);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,111 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Joachim Kohlhammer (joachim.kohlhammer@gmx.de)
|
||||||
|
*
|
||||||
|
* 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 2 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, write to the Free Software Foundation, Inc., 51
|
||||||
|
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
#ifndef CALENDARITEMDELEGATES_H
|
#ifndef CALENDARITEMDELEGATES_H
|
||||||
#define CALENDARITEMDELEGATES_H
|
#define CALENDARITEMDELEGATES_H
|
||||||
|
|
||||||
#include <QStyledItemDelegate>
|
#include <QStyledItemDelegate>
|
||||||
|
|
||||||
|
|
||||||
|
enum class BlockIndicator {
|
||||||
|
NoBlock = 0,
|
||||||
|
AllBlock = 1,
|
||||||
|
BlockBeforeNow = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class TimeScaleData {
|
||||||
|
public:
|
||||||
|
explicit TimeScaleData();
|
||||||
|
|
||||||
|
void setFirstMinute(int minute);
|
||||||
|
int firstMinute() const;
|
||||||
|
|
||||||
|
void setLastMinute(int minute);
|
||||||
|
int lastMinute() const;
|
||||||
|
|
||||||
|
void setStepSize(int step);
|
||||||
|
int stepSize() const;
|
||||||
|
|
||||||
|
double pixelsPerMinute(int availableHeight) const;
|
||||||
|
double pixelsPerMinute(const QRect& rect) const;
|
||||||
|
|
||||||
|
int minuteToY(int minute, int rectTop, int rectHeight) const;
|
||||||
|
int minuteToY(int minute, const QRect& rect) const;
|
||||||
|
int minuteToY(const QTime &time, const QRect& rect) const;
|
||||||
|
|
||||||
|
QTime timeFromY(int y, const QRect &rect, int snap = 15) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
int _firstMinute = 8 * 60;
|
||||||
|
int _lastMinute = 21 * 60;
|
||||||
|
int _stepSize = 60;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Workaround for Qt5 not having QAbstractItemView::itemDelegateForIndex (Qt6 only)
|
||||||
|
class ColumnDelegatingItemDelegate : public QStyledItemDelegate {
|
||||||
|
public:
|
||||||
|
explicit ColumnDelegatingItemDelegate(QList<QStyledItemDelegate*> delegates, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
QStyledItemDelegate *getDelegate(int idx) const;
|
||||||
|
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
||||||
|
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
||||||
|
bool helpEvent(QHelpEvent *event, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index) override;
|
||||||
|
void setEditorData(QWidget *editor, const QModelIndex &index) const override;
|
||||||
|
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
|
||||||
|
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
||||||
|
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QList<QStyledItemDelegate*> delegates;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class CalendarDayViewDayDelegate : public QStyledItemDelegate {
|
||||||
|
public:
|
||||||
|
explicit CalendarDayViewDayDelegate(TimeScaleData const * const timeScaleData, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
||||||
|
bool helpEvent(QHelpEvent *event, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index) override;
|
||||||
|
|
||||||
|
int hitTestEntry(const QModelIndex &index, const QPoint &pos) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
TimeScaleData const * const timeScaleData;
|
||||||
|
mutable QHash<QModelIndex, QList<QRect>> entryRects;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class CalendarTimeScaleDelegate : public QStyledItemDelegate {
|
||||||
|
public:
|
||||||
|
explicit CalendarTimeScaleDelegate(TimeScaleData *timeScaleData, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
||||||
|
QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
TimeScaleData *timeScaleData;
|
||||||
|
|
||||||
|
int selectNiceInterval(int minInterval) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
class CalendarDayDelegate : public QStyledItemDelegate {
|
class CalendarDayDelegate : public QStyledItemDelegate {
|
||||||
public:
|
public:
|
||||||
explicit CalendarDayDelegate(QObject *parent = nullptr);
|
explicit CalendarDayDelegate(QObject *parent = nullptr);
|
||||||
@@ -24,13 +126,17 @@ private:
|
|||||||
|
|
||||||
class CalendarSummaryDelegate : public QStyledItemDelegate {
|
class CalendarSummaryDelegate : public QStyledItemDelegate {
|
||||||
public:
|
public:
|
||||||
explicit CalendarSummaryDelegate(QObject *parent = nullptr);
|
explicit CalendarSummaryDelegate(int horMargin = 4, QObject *parent = nullptr);
|
||||||
|
|
||||||
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
||||||
bool helpEvent(QHelpEvent *event, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index) override;
|
bool helpEvent(QHelpEvent *event, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index) override;
|
||||||
|
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
mutable QHash<QModelIndex, bool> indexHasToolTip;
|
mutable QHash<QModelIndex, bool> indexHasToolTip;
|
||||||
|
const int horMargin;
|
||||||
|
const int vertMargin = 4;
|
||||||
|
const int lineSpacing = 2;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QSvgRenderer>
|
#include <QSvgRenderer>
|
||||||
|
#include <cmath>
|
||||||
#include "Settings.h"
|
#include "Settings.h"
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
@@ -417,6 +418,25 @@ QColor GCColor::selectedColor(QColor bgColor)
|
|||||||
return bg_select;
|
return bg_select;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QColor GCColor::blendedColor(const QColor &fg, const QColor &bg)
|
||||||
|
{
|
||||||
|
double alpha = fg.alphaF();
|
||||||
|
int r = static_cast<int>(std::round(fg.red() * alpha + bg.red() * (1.0 - alpha)));
|
||||||
|
int g = static_cast<int>(std::round(fg.green() * alpha + bg.green() * (1.0 - alpha)));
|
||||||
|
int b = static_cast<int>(std::round(fg.blue() * alpha + bg.blue() * (1.0 - alpha)));
|
||||||
|
return QColor(r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GCColor::isDark(const QColor &color)
|
||||||
|
{
|
||||||
|
return color.lightness() < 127;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GCColor::isPaletteDark(const QPalette &palette)
|
||||||
|
{
|
||||||
|
return isDark(palette.color(QPalette::Active, QPalette::Base));
|
||||||
|
}
|
||||||
|
|
||||||
const Colors * GCColor::colorSet()
|
const Colors * GCColor::colorSet()
|
||||||
{
|
{
|
||||||
return ColorList;
|
return ColorList;
|
||||||
|
|||||||
@@ -140,6 +140,9 @@ class GCColor : public QObject
|
|||||||
static QColor alternateColor(QColor); // return the alternate background
|
static QColor alternateColor(QColor); // return the alternate background
|
||||||
static QColor inactiveColor(QColor baseColor, double factor = 0.2); // return a dimmed variant
|
static QColor inactiveColor(QColor baseColor, double factor = 0.2); // return a dimmed variant
|
||||||
static QColor selectedColor(QColor); // return the on select background color
|
static QColor selectedColor(QColor); // return the on select background color
|
||||||
|
static QColor blendedColor(const QColor &fg, const QColor &bg); // return a color with alpha blended on a background
|
||||||
|
static bool isDark(const QColor &color);
|
||||||
|
static bool isPaletteDark(const QPalette &palette);
|
||||||
static QColor htmlCode(QColor x) { return x.name(); } // return the alternate background
|
static QColor htmlCode(QColor x) { return x.name(); } // return the alternate background
|
||||||
static Themes &themes();
|
static Themes &themes();
|
||||||
static void applyTheme(int index);
|
static void applyTheme(int index);
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ ManualActivityWizard::ManualActivityWizard
|
|||||||
if (plan) {
|
if (plan) {
|
||||||
setWindowTitle(tr("Add a Planned Activity"));
|
setWindowTitle(tr("Add a Planned Activity"));
|
||||||
} else {
|
} else {
|
||||||
setWindowTitle(tr("Create a Completed Activity"));
|
setWindowTitle(tr("Add a Completed Activity"));
|
||||||
}
|
}
|
||||||
setMinimumSize(800 * dpiXFactor, 650 * dpiYFactor);
|
setMinimumSize(800 * dpiXFactor, 650 * dpiYFactor);
|
||||||
setModal(true);
|
setModal(true);
|
||||||
|
|||||||
@@ -764,7 +764,7 @@ SOURCES += Gui/AboutDialog.cpp Gui/AddIntervalDialog.cpp Gui/AnalysisSidebar.cpp
|
|||||||
Gui/AddTileWizard.cpp Gui/NavigationModel.cpp Gui/AthleteView.cpp Gui/AthleteConfigDialog.cpp Gui/AthletePages.cpp Gui/Perspective.cpp \
|
Gui/AddTileWizard.cpp Gui/NavigationModel.cpp Gui/AthleteView.cpp Gui/AthleteConfigDialog.cpp Gui/AthletePages.cpp Gui/Perspective.cpp \
|
||||||
Gui/PerspectiveDialog.cpp Gui/SplashScreen.cpp Gui/StyledItemDelegates.cpp Gui/MetadataDialog.cpp Gui/ActionButtonBox.cpp \
|
Gui/PerspectiveDialog.cpp Gui/SplashScreen.cpp Gui/StyledItemDelegates.cpp Gui/MetadataDialog.cpp Gui/ActionButtonBox.cpp \
|
||||||
Gui/MetricOverrideDialog.cpp Gui/RepeatScheduleWizard.cpp \
|
Gui/MetricOverrideDialog.cpp Gui/RepeatScheduleWizard.cpp \
|
||||||
Gui/Calendar.cpp Gui/CalendarItemDelegates.cpp \
|
Gui/Calendar.cpp Gui/CalendarData.cpp Gui/CalendarItemDelegates.cpp \
|
||||||
Gui/IconManager.cpp
|
Gui/IconManager.cpp
|
||||||
|
|
||||||
## Models and Metrics
|
## Models and Metrics
|
||||||
|
|||||||
6
unittests/Gui/calendarData/calendarData.pro
Normal file
6
unittests/Gui/calendarData/calendarData.pro
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
QT += testlib core
|
||||||
|
|
||||||
|
SOURCES = testCalendarData.cpp
|
||||||
|
GC_OBJS = CalendarData
|
||||||
|
|
||||||
|
include(../../unittests.pri)
|
||||||
43
unittests/Gui/calendarData/testCalendarData.cpp
Normal file
43
unittests/Gui/calendarData/testCalendarData.cpp
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
#include "Gui/CalendarData.h"
|
||||||
|
|
||||||
|
#include <QTest>
|
||||||
|
|
||||||
|
|
||||||
|
class TestCalendarData: public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
|
||||||
|
void layoutCalendarEntry() {
|
||||||
|
#if QT_VERSION < 0x060000
|
||||||
|
QSKIP("Skipping test with Qt5");
|
||||||
|
#else
|
||||||
|
QList<CalendarEntry> entries = {
|
||||||
|
{ "", "", "", "", Qt::red, "", QTime(9, 0), 3600 },
|
||||||
|
{ "", "", "", "", Qt::red, "", QTime(9, 30), 3600 },
|
||||||
|
{ "", "", "", "", Qt::red, "", QTime(10, 30), 1800 },
|
||||||
|
{ "", "", "", "", Qt::red, "", QTime(11, 0), 1800 },
|
||||||
|
{ "", "", "", "", Qt::red, "", QTime(11, 15), 3600 },
|
||||||
|
};
|
||||||
|
CalendarEntryLayouter layouter;
|
||||||
|
QList<CalendarEntryLayout> layout = layouter.layout(entries);
|
||||||
|
|
||||||
|
QCOMPARE(layout.count(), entries.count());
|
||||||
|
QCOMPARE(layout[0].columnIndex, 0);
|
||||||
|
QCOMPARE(layout[0].columnCount, 2);
|
||||||
|
QCOMPARE(layout[1].columnIndex, 1);
|
||||||
|
QCOMPARE(layout[1].columnCount, 2);
|
||||||
|
QCOMPARE(layout[2].columnIndex, 0);
|
||||||
|
QCOMPARE(layout[2].columnCount, 1);
|
||||||
|
QCOMPARE(layout[3].columnIndex, 0);
|
||||||
|
QCOMPARE(layout[3].columnCount, 2);
|
||||||
|
QCOMPARE(layout[4].columnIndex, 1);
|
||||||
|
QCOMPARE(layout[4].columnCount, 2);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
QTEST_MAIN(TestCalendarData)
|
||||||
|
#include "testCalendarData.moc"
|
||||||
@@ -8,7 +8,8 @@ equals(GC_UNITTESTS, active) {
|
|||||||
SUBDIRS += Core/seasonOffset \
|
SUBDIRS += Core/seasonOffset \
|
||||||
Core/season \
|
Core/season \
|
||||||
Core/seasonParser \
|
Core/seasonParser \
|
||||||
Core/units
|
Core/units \
|
||||||
|
Gui/calendarData
|
||||||
CONFIG += ordered
|
CONFIG += ordered
|
||||||
} else {
|
} else {
|
||||||
message("Unittests are disabled; to enable copy unittests/unittests.pri.in to unittests/unittests.pri")
|
message("Unittests are disabled; to enable copy unittests/unittests.pri.in to unittests/unittests.pri")
|
||||||
|
|||||||
Reference in New Issue
Block a user