From ec8d3e99490e720e8d8a5efc992e17385ccc3e65 Mon Sep 17 00:00:00 2001 From: Berend De Schouwer Date: Sun, 30 Aug 2009 18:55:03 +0200 Subject: [PATCH] Add Calendar --- src/MainWindow.cpp | 98 ++++++++++++++++++++++++++++++++++++++++---- src/MainWindow.h | 5 +++ src/RideCalendar.cpp | 79 +++++++++++++++++++++++++++++++++++ src/RideCalendar.h | 25 +++++++++++ src/Settings.h | 1 + src/src.pro | 6 ++- 6 files changed, 203 insertions(+), 11 deletions(-) create mode 100644 src/RideCalendar.cpp create mode 100644 src/RideCalendar.h diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 3603cf7e0..98943030c 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -44,7 +44,7 @@ #include #include #include "DaysScaleDraw.h" - +#include "RideCalendar.h" #include "DatePickerDialog.h" #include "ToolsDialog.h" #include "MetricAggregator.h" @@ -136,6 +136,9 @@ MainWindow::MainWindow(const QDir &home) : setCentralWidget(splitter); splitter->setContentsMargins(10, 20, 10, 10); // attempting to follow some UI guides + calendar = new RideCalendar; + calendar->setFirstDayOfWeek(Qt::Monday); + treeWidget = new QTreeWidget; treeWidget->setColumnCount(3); treeWidget->setSelectionMode(QAbstractItemView::SingleSelection); @@ -143,7 +146,7 @@ MainWindow::MainWindow(const QDir &home) : treeWidget->header()->resizeSection(0,70); treeWidget->header()->resizeSection(1,95); treeWidget->header()->resizeSection(2,70); - treeWidget->setMaximumWidth(250); + //treeWidget->setMaximumWidth(250); treeWidget->header()->hide(); treeWidget->setAlternatingRowColors (true); treeWidget->setIndentation(5); @@ -151,7 +154,19 @@ MainWindow::MainWindow(const QDir &home) : allRides = new QTreeWidgetItem(treeWidget, FOLDER_TYPE); allRides->setText(0, tr("All Rides")); treeWidget->expandItem(allRides); - splitter->addWidget(treeWidget); + + leftLayout = new QSplitter; + leftLayout->setOrientation(Qt::Vertical); + leftLayout->addWidget(calendar); + leftLayout->setCollapsible(0, false); + leftLayout->addWidget(treeWidget); + leftLayout->setCollapsible(1, false); + splitter->addWidget(leftLayout); + splitter->setCollapsible(0, false); + QVariant calendarSizes = settings->value(GC_SETTINGS_CALENDAR_SIZES); + if (calendarSizes != QVariant()) { + leftLayout->restoreState(calendarSizes.toByteArray()); + } QTreeWidgetItem *last = NULL; QStringListIterator i(RideFileFactory::instance().listRideFiles(home)); @@ -162,6 +177,39 @@ MainWindow::MainWindow(const QDir &home) : last = new RideItem(RIDE_TYPE, home.path(), name, dt, &zones, notesFileName); allRides->addChild(last); + + /* + * We want to display these things inside the Calendar. + * Pick a colour (this should really be configurable) + * - red for races + * - yellow for sick days + * - green for rides + */ + QString notesPath = home.absolutePath() + "/" + notesFileName; + QFile notesFile(notesPath); + QColor color(Qt::green); + QString line("Ride"); + if (notesFile.exists()) { + if (notesFile.open(QFile::ReadOnly | QFile::Text)) { + QTextStream in(¬esFile); + line = in.readLine(); + notesFile.close(); + if (line.contains("race", Qt::CaseInsensitive)) { + color = QColor(Qt::red); + line = "RACE"; + } + if (line.contains("sick", Qt::CaseInsensitive)) { + color = QColor(Qt::red); + } + if (line.contains("swim", Qt::CaseInsensitive)) { + color = QColor(Qt::blue); + } + if (line.contains("gym", Qt::CaseInsensitive)) { + color = QColor(Qt::gray); + } + } + } + calendar->addEvent(dt.date(), line, color); } } @@ -260,6 +308,7 @@ MainWindow::MainWindow(const QDir &home) : tabWidget->addTab(window, "Ride Plot"); splitter->addWidget(tabWidget); + splitter->setCollapsible(1, false); QVariant splitterSizes = settings->value(GC_SETTINGS_SPLITTER_SIZES); if (splitterSizes != QVariant()) @@ -505,6 +554,10 @@ MainWindow::MainWindow(const QDir &home) : ////////////////////////////// Signals ////////////////////////////// + connect(calendar, SIGNAL(clicked(const QDate &)), + this, SLOT(dateChanged(const QDate &))); + connect(leftLayout, SIGNAL(splitterMoved(int,int)), + this, SLOT(leftLayoutMoved())); connect(treeWidget, SIGNAL(itemSelectionChanged()), this, SLOT(rideSelected())); connect(splitter, SIGNAL(splitterMoved(int,int)), @@ -1130,6 +1183,7 @@ MainWindow::rideSelected() } ride = (RideItem*) which; + calendar->setSelectedDate(ride->dateTime.date()); rideSummary->setHtml(ride->htmlSummary()); rideSummary->setAlignment(Qt::AlignCenter); if (ride) { @@ -1186,7 +1240,7 @@ void MainWindow::getBSFactors(float &timeBS, float &distanceBS) BSdays.setValue(30); // by default look back no more than 30 days // if there are rides, find most recent ride so we count back from there: - if (allRides->childCount() > 0) + if (allRides->childCount() > 0) lastRideItem = (RideItem*) allRides->child(allRides->childCount() - 1); else lastRideItem = ride; // not enough rides, use current ride @@ -1553,7 +1607,7 @@ void MainWindow::generateWeeklySummary() // Now open any notes associated with the new ride. rideNotes->setPlainText(""); - QString notesPath = home.absolutePath() + "/" + ride->notesFileName; + QString notesPath = home.absolutePath() + "/" + ride->notesFileName; QFile notesFile(notesPath); if (notesFile.exists()) { @@ -1561,7 +1615,7 @@ void MainWindow::generateWeeklySummary() QTextStream in(¬esFile); rideNotes->setPlainText(in.readAll()); notesFile.close(); - } + } else { QMessageBox::critical( this, tr("Read Error"), @@ -1569,7 +1623,7 @@ void MainWindow::generateWeeklySummary() } } - currentNotesFile = ride->notesFileName; + currentNotesFile = ride->notesFileName; currentNotesChanged = false; } @@ -1591,7 +1645,7 @@ void MainWindow::saveNotes() tr("Can't rename %1 to %2") .arg(tmpPath).arg(notesPath)); } - } + } else { QMessageBox::critical( this, tr("Write Error"), @@ -1647,7 +1701,13 @@ MainWindow::closeEvent(QCloseEvent*) saveNotes(); } -void +void +MainWindow::leftLayoutMoved() +{ + settings->setValue(GC_SETTINGS_CALENDAR_SIZES, leftLayout->saveState()); +} + +void MainWindow::splitterMoved() { settings->setValue(GC_SETTINGS_SPLITTER_SIZES, splitter->saveState()); @@ -2112,3 +2172,23 @@ void MainWindow::setHistWidgets(RideItem *rideItem) withZerosCheckBox->setEnabled(false); lnYHistCheckBox->setEnabled(false); } + +/* + * This slot gets called when the user picks a new date, using the mouse, + * in the calendar. We have to adjust TreeView to match. + */ +void MainWindow::dateChanged(const QDate &date) +{ + for (int i = 0; i < allRides->childCount(); i++) + { + ride = (RideItem*) allRides->child(i); + if (ride->dateTime.date() == date) { + treeWidget->scrollToItem(allRides->child(i), + QAbstractItemView::EnsureVisible); + treeWidget->setCurrentItem(allRides->child(i)); + i = allRides->childCount(); + } + } +} + + diff --git a/src/MainWindow.h b/src/MainWindow.h index bca21c4d5..46dfd234f 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -34,6 +34,7 @@ class QwtPlotPicker; class QwtPlotZoomer; class RideFile; class Zones; +class RideCalendar; class MainWindow : public QMainWindow { @@ -54,6 +55,7 @@ class MainWindow : public QMainWindow private slots: void rideSelected(); + void leftLayoutMoved(); void splitterMoved(); void newCyclist(); void openCyclist(); @@ -93,6 +95,7 @@ class MainWindow : public QMainWindow void importRideToDB(); void scanForMissing(); void generateWeeklySummary(); + void dateChanged(const QDate &); protected: @@ -105,6 +108,7 @@ class MainWindow : public QMainWindow QSettings *settings; + RideCalendar *calendar; QSplitter *splitter; QTreeWidget *treeWidget; QTabWidget *tabWidget; @@ -140,6 +144,7 @@ class MainWindow : public QMainWindow QwtPlotCurve *weeklyBaselineCurve; QwtPlotCurve *weeklyBSBaselineCurve; QwtPlot *weeklyBSPlot; + QSplitter *leftLayout; QwtPlotCurve *weeklyBSCurve; QwtPlotCurve *weeklyRICurve; diff --git a/src/RideCalendar.cpp b/src/RideCalendar.cpp new file mode 100644 index 000000000..fb13e0b74 --- /dev/null +++ b/src/RideCalendar.cpp @@ -0,0 +1,79 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "RideCalendar.h" + +RideCalendar::RideCalendar(QWidget *parent) + : QCalendarWidget(parent) +{ +}; + +void RideCalendar::addEvent(QDate date, QString string, QColor color) +{ + _text[date] = string; + _color[date] = color; + update(); +} + +void RideCalendar::paintCell(QPainter *painter, const QRect &rect, const QDate &date) const +{ + if (_text.contains(date)) { + painter->save(); + + /* + * Draw a rectangle in the color specified. If this is the + * currently selected date, draw a black outline. + */ + QPen pen(Qt::SolidLine); + pen.setCapStyle(Qt::SquareCap); + painter->setBrush(_color[date]); + if (date == selectedDate()) { + pen.setColor(Qt::black); + pen.setWidth(1); + } else { + pen.setColor(_color[date]); + } + painter->setPen(pen); + /* + * We have to draw to height-1 and width-1 because Qt draws outlines + * outside the box by default. + */ + painter->drawRect(rect.x(), rect.y(), rect.width() - 1, rect.height() - 1); + + /* + * Display the text. + */ + pen.setColor(Qt::black); + painter->setPen(pen); + QString text = QString::number(date.day()); + text = text + "\n" + _text[date]; + QFont font = painter->font(); + font.setPointSize(font.pointSize() - 2); + painter->setFont(font); + painter->drawText(rect, Qt::AlignHCenter | Qt::TextWordWrap, text); + + painter->restore(); + } else { + QCalendarWidget::paintCell(painter, rect, date); + } +} + +/* + * We extend QT's QCalendarWidget's sizeHint() so we claim a little bit of + * extra space. + */ +QSize RideCalendar::sizeHint() const +{ + QSize hint = QCalendarWidget::sizeHint(); + hint.setHeight(hint.height() * 2); + hint.setWidth(hint.width() * 2); + return hint; +} + diff --git a/src/RideCalendar.h b/src/RideCalendar.h new file mode 100644 index 000000000..8eb8f94a9 --- /dev/null +++ b/src/RideCalendar.h @@ -0,0 +1,25 @@ +#ifndef EVENT_CALENDAR_WIDGET_H +#define EVENT_CALENDAR_WIDGET_H + +#include +#include + +class RideCalendar : public QCalendarWidget +{ + Q_OBJECT + +public: + RideCalendar(QWidget *parent = 0); + + void addEvent(QDate, QString, QColor); + QSize sizeHint() const; + +protected: + void paintCell(QPainter *, const QRect &, const QDate &) const; + +private: + QMap _text; + QMap _color; +}; + +#endif diff --git a/src/Settings.h b/src/Settings.h index 7eb9014d2..8f7b52f65 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -28,6 +28,7 @@ #define GC_SETTINGS_MAIN_Y "mainwindow/y" #define GC_SETTINGS_MAIN_GEOM "mainwindow/geometry" #define GC_SETTINGS_SPLITTER_SIZES "mainwindow/splitterSizes" +#define GC_SETTINGS_CALENDAR_SIZES "mainwindow/calendarSizes" #define GC_DATETIME_FORMAT "ddd MMM dd, yyyy, hh:mm AP" #define GC_UNIT "unit" #define GC_SETTINGS_LAST_IMPORT_PATH "mainwindow/lastImportPath" diff --git a/src/src.pro b/src/src.pro index e6f50cd9f..3792bbc55 100644 --- a/src/src.pro +++ b/src/src.pro @@ -75,7 +75,8 @@ HEADERS += \ Settings.h \ XmlRideFile.h \ ManualRideFile.h \ - ManualRideDialog.h + ManualRideDialog.h \ + RideCalendar.h SOURCES += \ AllPlot.cpp \ @@ -121,7 +122,8 @@ SOURCES += \ SplitRideDialog.cpp \ XmlRideFile.cpp \ ManualRideFile.cpp \ - ManualRideDialog.cpp + ManualRideDialog.cpp \ + RideCalendar.cpp # win32 is after SOURCES and HEADERS so we can remove Serial.h/.cpp win32 {