2
* Copyright 2008,2010 Davide Bettio <davide.bettio@kdemail.net>
3
* Copyright 2009 John Layt <john@layt.net>
5
* This program is free software; you can redistribute it and/or modify
6
* it under the terms of the GNU Library General Public License as
7
* published by the Free Software Foundation; either version 2, or
8
* (at your option) any later version.
10
* This program is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
* GNU General Public License for more details
15
* You should have received a copy of the GNU Library General Public
16
* License along with this program; if not, write to the
17
* Free Software Foundation, Inc.,
18
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21
#include "calendartable.h"
22
#include "config-calendartable.h"
25
#include <QtCore/QDate>
26
#include <QtCore/QListIterator>
27
#include <QtCore/QTimer>
28
#include <QtGui/QPainter>
29
#include <QtGui/QWidget>
30
#include <QtGui/QGraphicsSceneWheelEvent>
31
#include <QtGui/QStyleOptionGraphicsItem>
37
#include <KConfigDialog>
38
#include <KConfigGroup>
42
#include <Plasma/Theme>
43
#include <Plasma/DataEngine>
44
#include <Plasma/DataEngineManager>
46
#ifdef HAVE_KDEPIMLIBS
47
#include "ui_calendarHolidaysConfig.h"
49
#include "ui_calendarConfig.h"
57
static const int DISPLAYED_WEEKS = 6;
59
class CalendarCellBorder
62
CalendarCellBorder(int c, int w, int d, CalendarTable::CellTypes t, QDate dt)
74
CalendarTable::CellTypes type;
78
class CalendarTablePrivate
81
enum Populations { NoPendingPopulation = 0, PopulateHolidays = 1, PopulateEvents = 2 };
83
CalendarTablePrivate(CalendarTable *calTable, const QDate &initialDate = QDate::currentDate())
85
calendarType("locale"),
86
calendar(KGlobal::locale()->calendar()),
88
displayHolidays(false),
89
calendarDataEngine(0),
90
automaticUpdates(true),
92
pendingPopulations(NoPendingPopulation),
93
delayedPopulationTimer(new QTimer())
95
KGlobal::locale()->insertCatalog("libkholidays");
98
svg->setImagePath("widgets/calendar");
99
svg->setContainsMultipleImages(true);
101
delayedPopulationTimer->setInterval(0);
102
delayedPopulationTimer->setSingleShot(true);
103
QObject::connect(delayedPopulationTimer, SIGNAL(timeout()), q, SLOT(populateCalendar()));
105
setDate(initialDate);
108
~CalendarTablePrivate()
110
// Delete the old calendar first if it's not the global calendar
111
if (calendar != KGlobal::locale()->calendar()) {
115
if (calendarDataEngine) {
116
Plasma::DataEngineManager::self()->unloadEngine("calendar");
120
delete delayedPopulationTimer;
123
void setCalendar(const KCalendarSystem *newCalendar)
125
// If not the global calendar, delete the old calendar first
126
if (calendar != KGlobal::locale()->calendar()) {
130
calendar = newCalendar;
132
if (calendar == KGlobal::locale()->calendar()) {
133
calendarType = "locale";
135
calendarType = calendar->calendarType();
138
// Force date update to refresh cached date componants then update display
139
setDate(selectedDate);
140
updateHoveredPainting(QPointF());
146
void setDate(const QDate &setDate)
148
selectedDate = setDate;
149
selectedMonth = calendar->month(setDate);
150
selectedYear = calendar->year(setDate);
151
weekDayFirstOfSelectedMonth = weekDayFirstOfMonth(setDate);
152
daysInWeek = calendar->daysInWeek(setDate);
153
daysInSelectedMonth = calendar->daysInMonth(setDate);
154
daysShownInPrevMonth = (weekDayFirstOfSelectedMonth - calendar->weekStartDay() + daysInWeek) % daysInWeek;
155
// make sure at least one day of the previous month is visible.
156
// 1 = minimum number of days to show, increase if more days should be forced visible:
157
if (daysShownInPrevMonth < 1) {
158
daysShownInPrevMonth += daysInWeek;
160
viewStartDate = dateFromRowColumn(0, 0);
161
viewEndDate = dateFromRowColumn(DISPLAYED_WEEKS - 1, daysInWeek - 1);
164
//Returns the x co-ordinate of a given column to LTR order, column is 0 to (daysInWeek-1)
165
//This version does not adjust for RTL, so should not be used directly for drawing
166
int columnToX(int column)
168
return q->boundingRect().x() +
172
((cellW + cellSpace) * column);
175
//Returns the y co-ordinate for given row, row is 0 to (DISPLAYED_WEEKS - 1)
178
return (int) q->boundingRect().y() +
181
((cellH + cellSpace) * row);
184
//Returns the absolute LTR column for a given x co-ordinate, -1 if outside table
185
int xToColumn(qreal x)
187
if (x >= columnToX(0) && x < columnToX(daysInWeek)) {
188
return ((x - centeringSpace) / (cellW + cellSpace)) - 1;
193
//Returns the absolute row for a given y co-ordinate, -1 if outside table
196
if (y >= rowToY(0) && y < rowToY(DISPLAYED_WEEKS)) {
197
return (y - headerHeight - headerSpace) / (cellH + cellSpace);
202
//Convert between column and weekdayColumn depending on LTR or RTL mode
203
//Note the same calculation used in both directions
204
int adjustColumn(int column)
206
if (column >= 0 && column < daysInWeek) {
207
if (q->layoutDirection() == Qt::RightToLeft) {
208
return daysInWeek - column - 1;
216
//Given an x y point in the table return the cell date.
217
//Note can be an invalid date in the calendar system
218
QDate dateFromPoint(QPointF point)
220
if (point.isNull()) {
224
int column = xToColumn(point.x());
225
int row = yToRow(point.y());
227
if (column < 0 || column >= daysInWeek || row < 0 || row >= DISPLAYED_WEEKS) {
231
return dateFromRowColumn(row, adjustColumn(column));
234
//Given a date in the currently selected month, return the position in the table as a
235
//row and column. Note no direction is assumed
236
void rowColumnFromDate(const QDate &cellDate, int &weekRow, int &weekdayColumn)
238
int offset = calendar->day(cellDate) + daysShownInPrevMonth - 1;
239
weekRow = offset / daysInWeek;
240
weekdayColumn = offset % daysInWeek;
243
//Given a position in the table as a 0-indexed row and column, return the cell date. Makes
244
//no assumption about direction. Date returned can be an invalid date in the calendar
245
//system, or simply invalid.
246
QDate dateFromRowColumn(int weekRow, int weekdayColumn)
250
//starting from the first of the month, which is known to always be valid, add/subtract
251
//number of days to get to the required cell
252
if (calendar->setYMD(cellDate, selectedYear, selectedMonth, 1)) {
253
cellDate = calendar->addDays(cellDate, (weekRow * daysInWeek) + weekdayColumn - daysShownInPrevMonth);
259
void updateHoveredPainting(const QPointF &hoverPoint)
261
QRectF oldHoverRect = hoverRect;
262
hoverRect = QRectF();
263
hoverWeekdayColumn = -1;
266
if (!hoverPoint.isNull()) {
267
int column = xToColumn(hoverPoint.x());
268
int row = yToRow(hoverPoint.y());
270
if (column >= 0 && column < daysInWeek && row >= 0 && row < DISPLAYED_WEEKS) {
271
hoverRect = QRectF(columnToX(column) - glowRadius,
272
rowToY(row) - glowRadius,
273
cellW + glowRadius * 2,
274
cellH + glowRadius * 2).adjusted(-2,-2,2,2);
275
hoverWeekdayColumn = adjustColumn(column);
280
// now update what is needed, and only what is needed!
281
if (hoverRect != oldHoverRect) {
282
//FIXME: update only of a piece seems to paint over the old stuff
283
/*if (oldHoverRect.isValid()) {
284
q->update(oldHoverRect);
286
if (hoverRect.isValid()) {
287
q->update(hoverRect);
289
emit q->dateHovered(dateFromRowColumn(hoverWeekRow, hoverWeekdayColumn));
294
// calculate weekday number of first day of this month, this is the anchor for all calculations
295
int weekDayFirstOfMonth(const QDate &cellDate)
298
QDate firstDayOfMonth;
300
if ( calendar->setYMD(firstDayOfMonth, selectedYear, selectedMonth, 1)) {
301
weekday = calendar->dayOfWeek(firstDayOfMonth);
306
QString defaultHolidaysRegion()
308
//FIXME: get rid of the query; turn it into a proper async request
309
return calendarEngine()->query("holidaysDefaultRegion").value("holidaysDefaultRegion").toString();
312
bool isValidHolidaysRegion(const QString &holidayRegion)
314
//FIXME: get rid of the query; turn it into a proper async request
315
QString queryString = "holidaysIsValidRegion" + QString(':') + holidayRegion;
316
return calendarEngine()->query(queryString).value(queryString).toBool();
319
bool addHolidaysRegion(const QString &holidayRegion, bool daysOff)
321
//kDebug() << holidayRegion << holidaysRegions.contains(holidayRegion) << isValidHolidaysRegion(holidayRegion);
322
if (!holidaysRegions.contains(holidayRegion) && isValidHolidaysRegion(holidayRegion)) {
323
QString queryString = "holidaysRegion" + QString(':') + holidayRegion;
324
Plasma::DataEngine::Data regions = calendarEngine()->query(queryString);
325
Plasma::DataEngine::DataIterator it(regions);
326
while (it.hasNext()) {
328
Plasma::DataEngine::Data region = it.value().toHash();
329
region.insert("UseForDaysOff", daysOff);
330
holidaysRegions.insert(it.key(), region);
339
bool holidayIsDayOff(Plasma::DataEngine::Data holidayData)
341
return (holidayData.value("ObservanceType").toString() == "PublicHoliday" &&
342
holidaysRegions.value(holidayData.value("RegionCode").toString()).value("UseForDaysOff").toBool());
346
void insertPimOccurence(const QString &type, const QDate &date, Plasma::DataEngine::Data occurrence)
348
if (date >= viewStartDate && date <= viewEndDate) {
349
int julian = date.toJulianDay();
350
if (type == "Event" && !events.contains(julian, occurrence)) {
351
events.insert(julian, occurrence);
352
} else if (type == "Todo" && !todos.contains(julian, occurrence)) {
353
todos.insert(julian, occurrence);
354
} else if (type == "Journal" && !journals.contains(julian, occurrence)) {
355
journals.insert(julian, occurrence);
360
Plasma::DataEngine *calendarEngine();
361
void checkIfCalendarEngineNeeded();
362
void populateHolidays();
363
void populateEvents();
364
void populateCalendar();
367
QString calendarType;
368
const KCalendarSystem *calendar;
374
int weekDayFirstOfSelectedMonth;
376
int daysInSelectedMonth;
377
int daysShownInPrevMonth;
382
bool displayHolidays;
383
Plasma::DataEngine *calendarDataEngine;
384
// List of Holiday Regions to display
385
// Hash key: QString = Holiday Region Code, Data = details of Holiday Region
386
QHash<QString, Plasma::DataEngine::Data> holidaysRegions;
387
// Index to Holidays that occur on a given date.
388
// Hash key: int = Julian Day number of holiday observance, int = key of holiday event
389
QMultiHash<int, int> holidays;
390
// Holiday details. A holiday may be observed on multiple dates.
391
// Hash key: int = key of holiday event, Data = details of holiday
392
QHash<int, Plasma::DataEngine::Data> holidayEvents;
393
// Index to Events/Todos/Journals that occur on a given date.
394
// Hash key: int = Julian Day number of event/todo occurrence, Data = occurence details including start date, end date and Akonadi incidence UID
395
QMultiHash<int, Plasma::DataEngine::Data> events;
396
QMultiHash<int, Plasma::DataEngine::Data> todos;
397
QMultiHash<int, Plasma::DataEngine::Data> journals;
398
// Event/Todo/Journal details. An event may recur on multiple dates.
399
// Hash key: QString = Akonadi UID of event/todo/journal, Data = details of event/todo/journal
400
QHash<QString, Plasma::DataEngine::Data> pimEvents;
403
bool automaticUpdates;
405
QPointF lastSeenMousePos;
407
#ifdef HAVE_KDEPIMLIBS
408
Ui::calendarHolidaysConfig calendarConfigUi;
410
Ui::calendarConfig calendarConfigUi;
414
float opacity; //transparency for the inactive text
417
int hoverWeekdayColumn;
427
int pendingPopulations;
428
QTimer *delayedPopulationTimer;
431
CalendarTable::CalendarTable(const QDate &date, QGraphicsWidget *parent)
432
: QGraphicsWidget(parent), d(new CalendarTablePrivate(this, date))
434
setAcceptHoverEvents(true);
435
setCacheMode(QGraphicsItem::DeviceCoordinateCache);
438
CalendarTable::CalendarTable(QGraphicsWidget *parent)
439
: QGraphicsWidget(parent), d(new CalendarTablePrivate(this))
441
setAcceptHoverEvents(true);
442
setCacheMode(QGraphicsItem::DeviceCoordinateCache);
445
CalendarTable::~CalendarTable()
450
void CalendarTable::setCalendar(const QString &newCalendarType)
452
if (newCalendarType == d->calendarType) {
456
if (newCalendarType == "locale") {
457
d->setCalendar(KGlobal::locale()->calendar());
459
d->setCalendar(KCalendarSystem::create(newCalendarType));
462
// Signal out date change so any dependents will update as well
463
emit dateChanged(date(), date());
464
emit dateChanged(date());
467
void CalendarTable::setCalendar(const KCalendarSystem *newCalendar)
469
if (newCalendar == d->calendar) {
473
d->setCalendar(newCalendar);
475
// Signal out date change so any dependents will update as well
476
emit dateChanged(date(), date());
477
emit dateChanged(date());
480
const KCalendarSystem *CalendarTable::calendar() const
485
void CalendarTable::setDate(const QDate &newDate)
487
// New date must be valid in the current calendar system
488
if (!calendar()->isValid(newDate)) {
492
// If new date is the same as old date don't actually need to do anything
493
if (newDate == date()) {
497
int oldYear = d->selectedYear;
498
int oldMonth = d->selectedMonth;
499
QDate oldDate = date();
501
// now change the date
504
d->updateHoveredPainting(d->lastSeenMousePos);
505
update(); //FIXME: we shouldn't need this update here, but something in Qt is broken (but with plasmoidviewer everything work)
507
if (oldYear != d->selectedYear || oldMonth != d->selectedMonth) {
508
d->populateHolidays();
511
// only update the old and the new areas
513
d->rowColumnFromDate(oldDate, row, column);
514
update(cellX(column) - d->glowRadius, cellY(row) - d->glowRadius,
515
d->cellW + d->glowRadius * 2, d->cellH + d->glowRadius * 2);
517
d->rowColumnFromDate(newDate, row, column);
518
update(cellX(column) - d->glowRadius, cellY(row) - d->glowRadius,
519
d->cellW + d->glowRadius * 2, d->cellH + d->glowRadius * 2);
522
emit dateChanged(newDate, oldDate);
523
emit dateChanged(newDate);
526
const QDate& CalendarTable::date() const
528
return d->selectedDate;
531
void CalendarTable::setDisplayHolidays(bool showHolidays)
534
if (d->holidaysRegions.isEmpty()) {
535
d->addHolidaysRegion(d->defaultHolidaysRegion(), true);
538
QMutableHashIterator<QString, Plasma::DataEngine::Data> it(d->holidaysRegions);
539
while (it.hasNext()) {
541
if (!d->isValidHolidaysRegion(it.key())) {
547
d->checkIfCalendarEngineNeeded();
550
if (d->displayHolidays != showHolidays) {
551
d->displayHolidays = showHolidays;
552
d->populateHolidays();
556
bool CalendarTable::displayHolidays()
558
return d->displayHolidays && !d->holidaysRegions.isEmpty();
561
bool CalendarTable::displayEvents()
563
return d->displayEvents;
566
void CalendarTable::setDisplayEvents(bool display)
568
if (d->displayEvents == display) {
572
d->displayEvents = display;
576
if (d->calendarDataEngine) {
577
d->calendarDataEngine->disconnectSource(d->eventsQuery, this);
582
d->pimEvents.clear();
583
d->checkIfCalendarEngineNeeded();
587
void CalendarTable::clearHolidaysRegions()
589
d->holidaysRegions.clear();
593
void CalendarTable::addHolidaysRegion(const QString ®ion, bool daysOff)
595
if (d->displayHolidays && d->addHolidaysRegion(region, daysOff)) {
596
d->populateHolidays();
600
QStringList CalendarTable::holidaysRegions() const
602
return d->holidaysRegions.keys();
605
QStringList CalendarTable::holidaysRegionsDaysOff() const
608
QHashIterator<QString, Plasma::DataEngine::Data> it(d->holidaysRegions);
609
while (it.hasNext()) {
611
if (it.value().value("UseForDaysOff").toBool()) {
612
regions.append(it.key());
618
void CalendarTable::clearHolidays()
620
d->holidayEvents.clear();
625
void CalendarTable::addHoliday(Plasma::DataEngine::Data holidayData)
627
QDate holidayStartDate = QDate::fromString(holidayData.value("ObservanceStartDate").toString(), Qt::ISODate);
628
int holidayDuration = holidayData.value("ObservanceDuration").toInt();
629
int uid = d->holidayEvents.count();
630
d->holidayEvents.insert(uid, holidayData);
631
for (int i = 0; i < holidayDuration; ++i) {
632
d->holidays.insertMulti(holidayStartDate.addDays(i).toJulianDay(), uid);
636
bool CalendarTable::dateHasDetails(const QDate &date) const
638
int julian = date.toJulianDay();
639
return d->holidays.contains(julian) ||
640
d->events.contains(julian) ||
641
d->todos.contains(julian) ||
642
d->journals.contains(julian);
645
QStringList CalendarTable::dateDetails(const QDate &date) const
648
const int julian = date.toJulianDay();
650
if (d->holidays.contains(julian)) {
651
foreach (int holidayUid, d->holidays.values(julian)) {
652
Plasma::DataEngine::Data holidayData = d->holidayEvents.value(holidayUid);
653
QString region = holidayData.value("RegionCode").toString();
655
if (d->holidayIsDayOff(holidayData)) {
656
details << i18nc("Day off: Holiday name (holiday region)",
657
"<i>Holiday</i>: %1 (%2)",
658
holidayData.value("Name").toString(),
659
d->holidaysRegions.value(region).value("Name").toString());
661
QString region = holidayData.value("RegionCode").toString();
662
details << i18nc("Not day off: Holiday name (holiday region)",
664
holidayData.value("Name").toString(),
665
d->holidaysRegions.value(region).value("Name").toString());
670
if (d->events.contains(julian)) {
671
foreach (const Plasma::DataEngine::Data &occurrence, d->events.values(julian)) {
672
details << i18n("<i>Event</i>: %1", buildOccurrenceDescription(occurrence));
676
if (d->todos.contains(julian)) {
677
foreach (const Plasma::DataEngine::Data &occurrence, d->todos.values(julian)) {
678
//TODO add Status and Percentage Complete when strings not frozen
679
details << i18n("<i>Todo</i>: %1", buildOccurrenceDescription(occurrence));
686
QString CalendarTable::buildOccurrenceDescription(const Plasma::DataEngine::Data &occurrence) const
688
const QString uid = occurrence.value("OccurrenceUid").toString();
689
const KDateTime occStartDate = occurrence.value("OccurrenceStartDate").value<KDateTime>();
690
const KDateTime occEndDate = occurrence.value("OccurrenceEndDate").value<KDateTime>();
691
const Plasma::DataEngine::Data details = d->pimEvents.value(uid);
693
if (details.value("AllDay").toBool()) {
694
return i18nc("All-day calendar event summary", "<br>%1", details.value("Summary").toString());
695
} else if (!occEndDate.isValid() || occStartDate == occEndDate) {
696
return i18nc("Time and summary for a calendarevent", "%1<br>%2",
697
KGlobal::locale()->formatTime(occStartDate.time()),
698
details.value("Summary").toString());
701
return i18nc("Start and end time and summary for a calendar event", "%1 - %2<br>%3",
702
KGlobal::locale()->formatTime(occStartDate.time()),
703
KGlobal::locale()->formatTime(occEndDate.time()),
704
details.value("Summary").toString());
707
void CalendarTable::setAutomaticUpdateEnabled(bool enabled)
709
d->automaticUpdates = enabled;
712
bool CalendarTable::isAutomaticUpdateEnabled() const
714
return d->automaticUpdates;
717
void CalendarTable::setCurrentDate(const QDate &date)
719
d->currentDate = date;
722
const QDate& CalendarTable::currentDate() const
724
return d->currentDate;
727
QDate CalendarTable::startDate() const
729
return d->viewStartDate;
732
QDate CalendarTable::endDate() const
734
return d->viewEndDate;
737
Plasma::DataEngine *CalendarTablePrivate::calendarEngine()
739
if (!calendarDataEngine) {
740
calendarDataEngine = Plasma::DataEngineManager::self()->loadEngine("calendar");
743
return calendarDataEngine;
746
void CalendarTablePrivate::checkIfCalendarEngineNeeded()
748
if (calendarDataEngine && !displayHolidays && !displayEvents) {
749
calendarDataEngine = Plasma::DataEngineManager::self()->loadEngine("calendar");
753
void CalendarTablePrivate::populateHolidays()
755
pendingPopulations |= PopulateHolidays;
756
delayedPopulationTimer->start();
759
void CalendarTablePrivate::populateCalendar()
761
if (pendingPopulations & PopulateHolidays) {
762
holidayEvents.clear();
765
if (q->displayHolidays()) {
766
// Just fetch the days displayed in the grid
767
//FIXME: queries are bad, use an async method
768
QString queryString = "holidays" + QString(':') +
769
q->holidaysRegions().join(QString(',')) +
770
QString(':') + viewStartDate.toString(Qt::ISODate) +
771
QString(':') + viewEndDate.toString(Qt::ISODate);
772
QList<QVariant> holidays = calendarEngine()->query(queryString).value(queryString).toList();
774
QMutableListIterator<QVariant> i(holidays);
775
while (i.hasNext()) {
776
q->addHoliday(i.next().toHash());
781
kDebug() << "repainting due to holiday population";
784
if (pendingPopulations & PopulateEvents) {
790
if (calendarDataEngine && !eventsQuery.isEmpty()) {
791
calendarDataEngine->disconnectSource(eventsQuery, q);
795
// Just fetch the days displayed in the grid
796
eventsQuery = "events" + QString(':') + viewStartDate.toString(Qt::ISODate) +
797
QString(':') + viewEndDate.toString(Qt::ISODate);
798
kDebug() << "connecting to .. " << eventsQuery;
799
calendarEngine()->connectSource(eventsQuery, q);
807
pendingPopulations = NoPendingPopulation;
808
emit q->eventsChanged();
811
void CalendarTablePrivate::populateEvents()
813
pendingPopulations |= PopulateEvents;
814
delayedPopulationTimer->start();
817
void CalendarTable::dataUpdated(const QString &source, const Plasma::DataEngine::Data &data)
823
d->pimEvents.clear();
825
foreach (const QVariant &v, data) {
826
Plasma::DataEngine::Data pimData = v.toHash();
827
QString type = pimData.value("Type").toString();
828
QString uid = pimData.value("UID").toString();
829
QDate startDate = pimData.value("StartDate").value<KDateTime>().date();
831
d->pimEvents.insert(uid, pimData);
833
QList<QVariant> occurrenceList = pimData.value("Occurrences").toList();
834
foreach (const QVariant &occurrence, occurrenceList) {
835
QDate occStartDate = occurrence.toHash().value("OccurrenceStartDate").value<KDateTime>().date();
836
if (pimData.value("EventMultiDay").toBool() == true) {
837
QDate occEndDate = occurrence.toHash().value("OccurrenceEndDate").value<KDateTime>().date();
838
QDate multiDate = occStartDate;
839
while (multiDate <= occEndDate) {
840
d->insertPimOccurence(type, multiDate, occurrence.toHash());
841
multiDate = multiDate.addDays(1);
844
d->insertPimOccurence(type, occStartDate, occurrence.toHash());
849
emit eventsChanged();
853
void CalendarTable::applyConfiguration(KConfigGroup cg)
855
setCalendar(cg.readEntry("calendarType", "locale"));
856
const bool holidays = cg.readEntry("displayHolidays", true);
857
clearHolidaysRegions();
859
QStringList regions = cg.readEntry("holidaysRegions", QStringList(d->defaultHolidaysRegion()));
860
QStringList daysOff = cg.readEntry("holidaysRegionsDaysOff", QStringList(d->defaultHolidaysRegion()));
861
clearHolidaysRegions();
862
foreach (const QString ®ion, regions) {
863
d->addHolidaysRegion(region, daysOff.contains(region));
866
d->populateHolidays();
869
setDisplayHolidays(holidays);
870
setDisplayEvents(cg.readEntry("displayEvents", true));
873
void CalendarTable::writeConfiguration(KConfigGroup cg)
875
cg.writeEntry("calendarType", d->calendarType);
876
cg.writeEntry("holidaysRegions", holidaysRegions());
877
cg.writeEntry("holidaysRegionsDaysOff", holidaysRegionsDaysOff());
878
cg.writeEntry("displayHolidays", d->displayHolidays);
879
cg.writeEntry("displayEvents", d->displayEvents);
882
void CalendarTable::createConfigurationInterface(KConfigDialog *parent)
884
QWidget *calendarConfigWidget = new QWidget();
885
d->calendarConfigUi.setupUi(calendarConfigWidget);
886
parent->addPage(calendarConfigWidget, i18n("Calendar"), "view-pim-calendar");
888
QStringList calendars = KCalendarSystem::calendarSystems();
889
d->calendarConfigUi.calendarComboBox->addItem( i18n("Local"), QVariant( "locale" ) );
890
foreach ( const QString &cal, calendars ) {
891
d->calendarConfigUi.calendarComboBox->addItem( KCalendarSystem::calendarLabel( cal ), QVariant( cal ) );
893
d->calendarConfigUi.calendarComboBox->setCurrentIndex( d->calendarConfigUi.calendarComboBox->findData( QVariant( d->calendarType ) ) );
895
d->calendarConfigUi.displayEvents->setChecked(d->displayEvents);
897
#ifdef HAVE_KDEPIMLIBS
898
QHashIterator<QString, Plasma::DataEngine::Data> it(d->holidaysRegions);
899
while (it.hasNext()) {
901
if (it.value().value("UseForDaysOff").toBool()) {
902
d->calendarConfigUi.holidayRegionWidget->setRegionUseFlags(it.key(), KHolidays::HolidayRegionSelector::UseDaysOff);
904
d->calendarConfigUi.holidayRegionWidget->setRegionUseFlags(it.key(), KHolidays::HolidayRegionSelector::UseInformationOnly);
907
d->calendarConfigUi.holidayRegionWidget->setDescriptionHidden(true);
909
connect(d->calendarConfigUi.holidayRegionWidget, SIGNAL(selectionChanged()), parent, SLOT(settingsModified()));
912
connect(d->calendarConfigUi.calendarComboBox, SIGNAL(activated(int)), parent, SLOT(settingsModified()));
913
connect(d->calendarConfigUi.displayEvents, SIGNAL(stateChanged(int)), parent, SLOT(settingsModified()));
916
void CalendarTable::configAccepted(KConfigGroup cg)
918
setCalendar(d->calendarConfigUi.calendarComboBox->itemData(d->calendarConfigUi.calendarComboBox->currentIndex()).toString());
919
setDisplayEvents(d->calendarConfigUi.displayEvents->isChecked());
921
#ifdef HAVE_KDEPIMLIBS
922
clearHolidaysRegions();
923
QHash<QString, KHolidays::HolidayRegionSelector::RegionUseFlags> regions = d->calendarConfigUi.holidayRegionWidget->regionUseFlags();
924
QHashIterator<QString, KHolidays::HolidayRegionSelector::RegionUseFlags> it(regions);
925
bool displayHolidays = false;
926
while (it.hasNext()) {
928
if (it.value() == KHolidays::HolidayRegionSelector::UseDaysOff) {
929
addHolidaysRegion(it.key(), true);
930
displayHolidays = true;
931
} else if (it.value() == KHolidays::HolidayRegionSelector::UseInformationOnly) {
932
addHolidaysRegion(it.key(), false);
933
displayHolidays = true;
936
setDisplayHolidays(displayHolidays);
939
writeConfiguration(cg);
942
//Returns the x co-ordinate for drawing the day cell on the widget given the weekday column
943
//Note weekdayColumn is 0 to (daysInWeek -1) and is not a real weekDay number (i.e. NOT Monday=1).
944
//Adjusts automatically for RTL mode, so don't use to calculate absolute positions
945
int CalendarTable::cellX(int weekdayColumn)
947
return d->columnToX(d->adjustColumn(weekdayColumn));
950
//Returns the y co-ordinate for drawing the day cell on the widget given the weekRow
951
//weekRow is 0 to (DISPLAYED_WEEKS - 1)
952
int CalendarTable::cellY(int weekRow)
954
return d->rowToY(weekRow);
957
void CalendarTable::wheelEvent(QGraphicsSceneWheelEvent * event)
959
if (event->delta() < 0) {
960
setDate(calendar()->addMonths(date(), 1));
961
} else if (event->delta() > 0) {
962
setDate(calendar()->addMonths(date(), -1));
966
void CalendarTable::mousePressEvent(QGraphicsSceneMouseEvent *event)
968
d->lastSeenMousePos = event->pos();
971
QDate date = d->dateFromPoint(event->pos());
973
emit dateSelected(date);
976
void CalendarTable::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
978
mousePressEvent(event);
981
void CalendarTable::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
985
d->lastSeenMousePos = event->pos();
990
void CalendarTable::hoverMoveEvent(QGraphicsSceneHoverEvent *event)
992
d->lastSeenMousePos = event->pos();
994
d->updateHoveredPainting(event->pos());
997
void CalendarTable::resizeEvent(QGraphicsSceneResizeEvent * event)
1001
QRectF r = contentsRect();
1002
int numCols = d->daysInWeek + 1;
1003
int rectSizeH = int(r.height() / (DISPLAYED_WEEKS + 1));
1004
int rectSizeW = int(r.width() / numCols);
1006
//Using integers to help to keep things aligned to the grid
1007
//kDebug() << r.width() << rectSize;
1008
d->cellSpace = qMax(1, qMin(4, qMin(rectSizeH, rectSizeW) / 20));
1009
d->headerSpace = d->cellSpace * 2;
1010
d->weekBarSpace = d->cellSpace * 2 + 1;
1011
d->cellH = rectSizeH - d->cellSpace;
1012
d->cellW = rectSizeW - d->cellSpace;
1013
d->glowRadius = d->cellW * .1;
1014
d->headerHeight = (int) (d->cellH / 1.5);
1015
d->centeringSpace = qMax(0, int((r.width() - (rectSizeW * numCols) - (d->cellSpace * (numCols -1))) / 2));
1018
void CalendarTable::paintCell(QPainter *p, int cell, int weekRow, int weekdayColumn, CellTypes type, const QDate &cellDate)
1022
QString cellSuffix = type & NotInCurrentMonth ? "inactive" : "active";
1023
QRectF cellArea = QRectF(cellX(weekdayColumn), cellY(weekRow), d->cellW, d->cellH);
1025
d->svg->paint(p, cellArea, cellSuffix); // draw background
1027
QColor numberColor = Theme::defaultTheme()->color(Plasma::Theme::TextColor);
1028
if (type & NotInCurrentMonth || type & InvalidDate) {
1029
p->setOpacity(d->opacity);
1032
p->setPen(numberColor);
1033
QFont font = Theme::defaultTheme()->font(Plasma::Theme::DefaultFont);
1037
font.setPixelSize(cellArea.height() * 0.7);
1039
if (!(type & InvalidDate)) {
1040
p->drawText(cellArea, Qt::AlignCenter, calendar()->dayString(cellDate, KCalendarSystem::ShortFormat), &cellArea); //draw number
1045
void CalendarTable::paintBorder(QPainter *p, int cell, int weekRow, int weekdayColumn, CellTypes type, const QDate &cellDate)
1050
if (type & Hovered) {
1051
d->svg->paint(p, QRect(cellX(weekdayColumn), cellY(weekRow), d->cellW, d->cellH), "hoverHighlight");
1057
elementId = "today";
1058
} else if (type & Selected) {
1059
elementId = "selected";
1060
} else if (type & PublicHoliday) {
1062
} else if (type & Holiday) {
1063
elementId = "green";
1068
d->svg->paint(p, QRectF(cellX(weekdayColumn) - 1, cellY(weekRow) - 1, d->cellW + 1, d->cellH + 2), elementId);
1071
void CalendarTable::paint(QPainter *p, const QStyleOptionGraphicsItem *option, QWidget *widget)
1075
// Draw weeks numbers column and day header
1076
QRectF r = boundingRect();
1077
d->svg->paint(p, QRectF(r.x() + d->centeringSpace, cellY(0), d->cellW,
1078
cellY(DISPLAYED_WEEKS) - cellY(0) - d->cellSpace), "weeksColumn");
1079
d->svg->paint(p, QRectF(r.x() + d->centeringSpace, r.y(),
1080
cellX(d->daysInWeek) - r.x() - d->cellSpace - d->centeringSpace, d->headerHeight), "weekDayHeader");
1082
QList<CalendarCellBorder> borders;
1083
QList<CalendarCellBorder> hovers;
1084
if (d->automaticUpdates) {
1085
d->currentDate = QDate::currentDate();
1088
//weekRow and weekDaycolumn of table are 0 indexed and are not equivalent to weekday or week
1089
//numbers. In LTR mode we count/paint row and column from top-left corner, in RTL mode we
1090
//count/paint from top-right corner, but we don't need to know as cellX() calculates the actual
1091
//painting position for us depending on the mode.
1092
for (int weekRow = 0; weekRow < DISPLAYED_WEEKS; weekRow++) {
1093
for (int weekdayColumn = 0; weekdayColumn < d->daysInWeek; weekdayColumn++) {
1095
int x = cellX(weekdayColumn);
1096
int y = cellY(weekRow);
1098
QRectF cellRect(x, y, d->cellW, d->cellH);
1099
if (!cellRect.intersects(option->exposedRect)) {
1103
QDate cellDate = d->dateFromRowColumn(weekRow, weekdayColumn);
1104
CalendarTable::CellTypes type(CalendarTable::NoType);
1106
const int cellDay = calendar()->day(cellDate);
1107
const int julian = cellDate.toJulianDay();
1109
// check what kind of cell we are
1110
if (calendar()->month(cellDate) != d->selectedMonth) {
1111
type |= CalendarTable::NotInCurrentMonth;
1114
if (!calendar()->isValid(cellDate)) {
1115
type |= CalendarTable::InvalidDate;
1118
if (cellDate == d->currentDate) {
1119
type |= CalendarTable::Today;
1122
if (cellDate == date()) {
1123
type |= CalendarTable::Selected;
1126
foreach (int holidayUid, d->holidays.values(julian)) {
1127
if (d->holidayIsDayOff(d->holidayEvents.value(holidayUid))) {
1128
type |= CalendarTable::PublicHoliday;
1130
type |= CalendarTable::Holiday;
1134
if (d->events.contains(julian) || d->todos.contains(julian)) {
1135
type |= CalendarTable::Event;
1138
if (type != CalendarTable::NoType && type != CalendarTable::NotInCurrentMonth) {
1139
borders.append(CalendarCellBorder(cellDay, weekRow, weekdayColumn, type, cellDate));
1142
if (weekRow == d->hoverWeekRow && weekdayColumn == d->hoverWeekdayColumn) {
1143
type |= CalendarTable::Hovered;
1144
hovers.append(CalendarCellBorder(cellDay, weekRow, weekdayColumn, type, cellDate));
1147
paintCell(p, cellDay, weekRow, weekdayColumn, type, cellDate);
1149
// FIXME: modify svg to allow for a wider week number cell
1150
// a temporary workaround is to paint only one week number (weekString) when the cell is small
1151
// and both week numbers (accurateWeekString) when there is enough room
1152
if (weekdayColumn == 0) {
1153
QRectF cellRect(r.x() + d->centeringSpace, y, d->cellW, d->cellH);
1154
p->setPen(Theme::defaultTheme()->color(Plasma::Theme::TextColor));
1155
QFont font = Theme::defaultTheme()->font(Plasma::Theme::DefaultFont);
1156
font.setPixelSize(cellRect.height() * 0.7);
1158
p->setOpacity(d->opacity);
1160
QString accurateWeekString;
1161
if (calendar()->isValid(cellDate)) {
1162
weekString = calendar()->weekNumberString(cellDate);
1163
accurateWeekString = weekString;
1164
if (calendar()->dayOfWeek(cellDate) != Qt::Monday) {
1165
QDate nextWeekDate = calendar()->addDays(cellDate, d->daysInWeek);
1166
if (calendar()->isValid(nextWeekDate)) {
1167
if (layoutDirection() == Qt::RightToLeft) {
1168
accurateWeekString.prepend("/").prepend(calendar()->weekNumberString(nextWeekDate));
1170
accurateWeekString.append("/").append(calendar()->weekNumberString(nextWeekDate));
1173
// ensure that weekString is the week number that has the most amout of days in the row
1174
QDate middleWeekDate = calendar()->addDays(cellDate, floor(static_cast<float>(d->daysInWeek / 2)));
1175
if (calendar()->isValid(middleWeekDate)) {
1176
QString middleWeekString = calendar()->weekNumberString(middleWeekDate);
1177
if (weekString != middleWeekString) {
1178
weekString = middleWeekString;
1183
QFontMetrics fontMetrics(font);
1184
if (fontMetrics.width(accurateWeekString) > d->cellW) {
1185
p->drawText(cellRect, Qt::AlignCenter, weekString); //draw number
1187
p->drawText(cellRect, Qt::AlignCenter, accurateWeekString); //draw number
1195
if (option->exposedRect.intersects(QRect(r.x(), r.y(), r.width(), d->headerHeight))) {
1196
p->setPen(Plasma::Theme::defaultTheme()->color(Plasma::Theme::TextColor));
1197
int weekStartDay = calendar()->weekStartDay();
1198
for (int i = 0; i < d->daysInWeek; i++){
1199
int weekDay = ((i + weekStartDay - 1) % d->daysInWeek) + 1;
1200
QString dayName = calendar()->weekDayName(weekDay, KCalendarSystem::ShortDayName);
1201
QFont font = Theme::defaultTheme()->font(Plasma::Theme::DefaultFont);
1202
font.setPixelSize(d->headerHeight * 0.9);
1204
p->drawText(QRectF(cellX(i), r.y(), d->cellW, d->headerHeight),
1205
Qt::AlignCenter | Qt::AlignVCenter, dayName);
1210
foreach (const CalendarCellBorder &border, hovers) {
1212
paintBorder(p, border.cell, border.week, border.weekDay, border.type, border.date);
1217
foreach (const CalendarCellBorder &border, borders) {
1219
paintBorder(p, border.cell, border.week, border.weekDay, border.type, border.date);
1224
} //namespace Plasma
1226
#include "calendartable.moc"