#include #include #include #include #include #include #include #include #include #include "RideItem.h" #include "RideMetadata.h" #include "RideCalendar.h" #include "MainWindow.h" #include "Settings.h" #include RideCalendar::RideCalendar(MainWindow *parent) : QCalendarWidget(parent), mainWindow(parent) { defaultColor = Qt::white; // make font 2 point sizes smaller that the default QFont font; font.setPointSize(font.pointSize()-2); setFont(font); this->setFirstDayOfWeek(Qt::Monday); // setup workout codes configUpdate(); connect(mainWindow, SIGNAL(configChanged()), this, SLOT(configUpdate())); }; void RideCalendar::configUpdate() { // clear existing workoutCodes.clear(); // setup the keyword/color combinations from config settings foreach (KeywordDefinition keyword, mainWindow->rideMetadata()->getKeywords()) { if (keyword.name == "Default") defaultColor = keyword.color; else { addWorkoutCode(keyword.name, keyword.color); // alternative texts in notes foreach (QString token, keyword.tokens) { addWorkoutCode(token, keyword.color); } } } // force a repaint -- NASTY HACK TO WORKAROUND BUG QAbstractItemView *view = qFindChild(this); if (view) view->viewport()->update(); else update(); // fallback } struct RideIter { typedef std::random_access_iterator_tag iterator_category; typedef const RideItem *value_type; typedef int difference_type; typedef value_type& reference; typedef value_type* pointer; MainWindow *mainWindow; int i; RideIter() : mainWindow(NULL), i(0) {} RideIter(MainWindow *mainWindow, int i = 0) : mainWindow(mainWindow), i(i) {} const RideItem *operator*() { assert(mainWindow); assert(i < mainWindow->allRideItems()->childCount()); return dynamic_cast(mainWindow->allRideItems()->child(i)); } RideIter operator++() { /* preincrement */ ++i; return *this; } RideIter operator++(int) { RideIter result(mainWindow, i); ++i; return result; } const RideIter &operator+=(int j) { i += j; return *this; } }; bool operator==(const RideIter &a, const RideIter &b) { return a.i == b.i; } bool operator!=(const RideIter &a, const RideIter &b) { return a.i != b.i; } int operator-(const RideIter &a, const RideIter &b) { return a.i - b.i; } struct RideItemDateLessThan { bool operator()(const RideItem *a, const RideItem *b) { return a->dateTime < b->dateTime; } }; struct RideItemDateGreaterThan { bool operator()(const RideItem *a, const RideItem *b) { return a->dateTime > b->dateTime; } }; void RideCalendar::paintCell(QPainter *painter, const QRect &rect, const QDate &date) const { QMultiMap ridesToday; RideIter begin(mainWindow, 0); RideIter end(mainWindow, mainWindow->allRideItems()->childCount()); bool ascending = appsettings->value(this, GC_ALLRIDES_ASCENDING, Qt::Checked).toInt() > 0; RideIter i; if (ascending) { RideItemDateLessThan comp; RideItem search(0, "", "", QDateTime(date), NULL, NULL, "", NULL); i = std::lower_bound(begin, end, &search, comp); } else { RideItemDateGreaterThan comp; RideItem search(0, "", "", QDateTime(date.addDays(1)), NULL, NULL, "", NULL); i = std::upper_bound(begin, end, &search, comp); } while (i != end) { const RideItem *ride = *i; if (ride->dateTime.date() != date) break; ridesToday.insert(ride->dateTime, ride); ++i; } if (ridesToday.isEmpty()) { QCalendarWidget::paintCell(painter, rect, date); return; } painter->save(); /* * Fill in the text and color to some default */ QRect textRect; int number = ridesToday.size(); float count = 1; int startY, stopY; QPen pen(Qt::SolidLine); painter->setPen(pen); pen.setCapStyle(Qt::SquareCap); QFont font = painter->font(); font.setPointSize(font.pointSize() - 2); painter->setFont(font); /* * Loop over all the rides for today, and record colour and text. */ foreach (const RideItem *ride, ridesToday.values()) { QString notesPath = home.absolutePath() + "/" + ride->notesFileName; QFile notesFile(notesPath); QColor color = defaultColor; QString line("Ride"); QString code; if (notesFile.exists()) { if (notesFile.open(QFile::ReadOnly | QFile::Text)) { QTextStream in(¬esFile); line = in.readLine(); notesFile.close(); foreach(code, workoutCodes.keys()) { if (line.contains(code, Qt::CaseInsensitive)) { color = workoutCodes[code]; } } } } startY = rect.y() + ((count - 1) * rect.height()) / number; stopY = rect.height() / number; /* fillRect() doesn't need width-1, height-1. drawRect() does */ painter->fillRect(rect.x(), startY, rect.width(), stopY, color); textRect = QRect(rect.x(), startY, rect.width(), stopY); pen.setColor(Qt::black); pen.setStyle(Qt::SolidLine); painter->setPen(pen); painter->drawText(textRect, Qt::AlignHCenter | Qt::TextWordWrap, line); pen.setColor(QColor(0, 0, 0, 63)); pen.setStyle(Qt::SolidLine); painter->setPen(pen); painter->drawRoundRect(textRect, 10, 10); ++count; } /* * Draw a rectangle in the color specified. If this is the * currently selected date, draw a black outline. */ if (date == selectedDate()) { pen.setColor(Qt::black); pen.setStyle(Qt::SolidLine); pen.setWidth(2); painter->setPen(pen); /* drawRect() draws an outline, so needs width-1, height-1 */ /* We set +1, +1, -2, -2 because the width is 2. */ painter->drawRect(rect.x() + 1, rect.y() + 1, rect.width() - 2, rect.height() - 2); } /* * Display the date. */ pen.setColor(QColor(0, 0, 0, 63)); pen.setStyle(Qt::SolidLine); pen.setWidth(1); painter->setPen(pen); QString textDate = QString::number(date.day()); painter->drawText(rect, Qt::AlignHCenter | Qt::TextWordWrap | Qt::AlignVCenter, textDate); /* We're done, restore */ painter->restore(); } void RideCalendar::setHome(const QDir &homeDir) { home = homeDir; } void RideCalendar::addWorkoutCode(QString string, QColor color) { workoutCodes[string] = color; } /* * 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; }