1 | /* -*- C++ -*- |
2 | This file is part of the KDE libraries |
3 | SPDX-FileCopyrightText: 1997 Tim D. Gilman <tdgilman@best.org> |
4 | SPDX-FileCopyrightText: 1998-2001 Mirko Boehm <mirko@kde.org> |
5 | SPDX-FileCopyrightText: 2007 John Layt <john@layt.net> |
6 | |
7 | SPDX-License-Identifier: LGPL-2.0-or-later |
8 | */ |
9 | |
10 | #include "kdatetable_p.h" |
11 | |
12 | #include <QAction> |
13 | #include <QActionEvent> |
14 | #include <QApplication> |
15 | #include <QDate> |
16 | #include <QFontDatabase> |
17 | #include <QMenu> |
18 | #include <QPainter> |
19 | #include <QStyle> |
20 | #include <QStyleOptionViewItem> |
21 | |
22 | #include <cmath> |
23 | |
24 | #include "kdaterangecontrol_p.h" |
25 | |
26 | class KDateTable::KDateTablePrivate : public KDateRangeControlPrivate |
27 | { |
28 | public: |
29 | KDateTablePrivate(KDateTable *qq) |
30 | : q(qq) |
31 | { |
32 | m_popupMenuEnabled = false; |
33 | m_useCustomColors = false; |
34 | m_hoveredPos = -1; |
35 | setDate(QDate::currentDate()); |
36 | } |
37 | |
38 | ~KDateTablePrivate() |
39 | { |
40 | } |
41 | |
42 | void setDate(const QDate &date); |
43 | void nextMonth(); |
44 | void previousMonth(); |
45 | void beginningOfMonth(); |
46 | void endOfMonth(); |
47 | void beginningOfWeek(); |
48 | void endOfWeek(); |
49 | |
50 | KDateTable *q; |
51 | |
52 | /* |
53 | * The currently selected date. |
54 | */ |
55 | QDate m_date; |
56 | |
57 | /* |
58 | * The weekday number of the first day in the month [1..daysInWeek()]. |
59 | */ |
60 | int m_weekDayFirstOfMonth; |
61 | |
62 | /* |
63 | * The number of days in the current month. |
64 | */ |
65 | int m_numDaysThisMonth; |
66 | |
67 | /* |
68 | * Save the size of the largest used cell content. |
69 | */ |
70 | QRectF m_maxCell; |
71 | |
72 | /* |
73 | * How many week rows we are to draw. |
74 | */ |
75 | int m_numWeekRows; |
76 | |
77 | /* |
78 | * How many day columns we are to draw, i.e. days in a week. |
79 | */ |
80 | int m_numDayColumns; |
81 | |
82 | /* |
83 | * The font size of the displayed text. |
84 | */ |
85 | int fontsize; |
86 | |
87 | bool ; |
88 | bool m_useCustomColors; |
89 | |
90 | struct DatePaintingMode { |
91 | QColor fgColor; |
92 | QColor bgColor; |
93 | BackgroundMode bgMode; |
94 | }; |
95 | QHash<int, DatePaintingMode> m_customPaintingModes; |
96 | |
97 | int m_hoveredPos; |
98 | }; |
99 | |
100 | KDateTable::KDateTable(const QDate &date, QWidget *parent) |
101 | : QWidget(parent) |
102 | , d(new KDateTablePrivate(this)) |
103 | { |
104 | initWidget(date); |
105 | } |
106 | |
107 | KDateTable::KDateTable(QWidget *parent) |
108 | : QWidget(parent) |
109 | , d(std::make_unique<KDateTablePrivate>(args: this)) |
110 | { |
111 | initWidget(date: QDate::currentDate()); |
112 | } |
113 | |
114 | KDateTable::~KDateTable() |
115 | { |
116 | } |
117 | |
118 | void KDateTable::initWidget(const QDate &date) |
119 | { |
120 | d->m_numWeekRows = 7; |
121 | |
122 | setFontSize(10); |
123 | setFocusPolicy(Qt::StrongFocus); |
124 | setBackgroundRole(QPalette::Base); |
125 | setAutoFillBackground(true); |
126 | initAccels(); |
127 | setAttribute(Qt::WA_Hover, on: true); |
128 | |
129 | setDate(date); |
130 | } |
131 | |
132 | void KDateTable::initAccels() |
133 | { |
134 | QAction *next = new QAction(this); |
135 | next->setObjectName(QStringLiteral("next" )); |
136 | next->setShortcuts(QKeySequence::keyBindings(key: QKeySequence::Forward)); |
137 | next->setShortcutContext(Qt::WidgetWithChildrenShortcut); |
138 | connect(sender: next, signal: &QAction::triggered, context: this, slot: [this]() { |
139 | d->nextMonth(); |
140 | }); |
141 | |
142 | QAction *prior = new QAction(this); |
143 | prior->setObjectName(QStringLiteral("prior" )); |
144 | prior->setShortcuts(QKeySequence::keyBindings(key: QKeySequence::Back)); |
145 | prior->setShortcutContext(Qt::WidgetWithChildrenShortcut); |
146 | connect(sender: prior, signal: &QAction::triggered, context: this, slot: [this]() { |
147 | d->previousMonth(); |
148 | }); |
149 | |
150 | QAction *beginMonth = new QAction(this); |
151 | beginMonth->setObjectName(QStringLiteral("beginMonth" )); |
152 | beginMonth->setShortcuts(QKeySequence::keyBindings(key: QKeySequence::MoveToStartOfDocument)); |
153 | beginMonth->setShortcutContext(Qt::WidgetWithChildrenShortcut); |
154 | connect(sender: beginMonth, signal: &QAction::triggered, context: this, slot: [this]() { |
155 | d->beginningOfMonth(); |
156 | }); |
157 | |
158 | QAction *endMonth = new QAction(this); |
159 | endMonth->setObjectName(QStringLiteral("endMonth" )); |
160 | endMonth->setShortcuts(QKeySequence::keyBindings(key: QKeySequence::MoveToEndOfDocument)); |
161 | endMonth->setShortcutContext(Qt::WidgetWithChildrenShortcut); |
162 | connect(sender: endMonth, signal: &QAction::triggered, context: this, slot: [this]() { |
163 | d->endOfMonth(); |
164 | }); |
165 | |
166 | QAction *beginWeek = new QAction(this); |
167 | beginWeek->setObjectName(QStringLiteral("beginWeek" )); |
168 | beginWeek->setShortcuts(QKeySequence::keyBindings(key: QKeySequence::MoveToStartOfLine)); |
169 | beginWeek->setShortcutContext(Qt::WidgetWithChildrenShortcut); |
170 | connect(sender: beginWeek, signal: &QAction::triggered, context: this, slot: [this]() { |
171 | d->beginningOfWeek(); |
172 | }); |
173 | |
174 | QAction *endWeek = new QAction(this); |
175 | endWeek->setObjectName(QStringLiteral("endWeek" )); |
176 | endWeek->setShortcuts(QKeySequence::keyBindings(key: QKeySequence::MoveToEndOfLine)); |
177 | endWeek->setShortcutContext(Qt::WidgetWithChildrenShortcut); |
178 | connect(sender: endWeek, signal: &QAction::triggered, context: this, slot: [this]() { |
179 | d->endOfWeek(); |
180 | }); |
181 | } |
182 | |
183 | int KDateTable::posFromDate(const QDate &date) |
184 | { |
185 | int initialPosition = date.day(); |
186 | int offset = (d->m_weekDayFirstOfMonth - locale().firstDayOfWeek() + d->m_numDayColumns) % d->m_numDayColumns; |
187 | |
188 | // make sure at least one day of the previous month is visible. |
189 | // adjust this < 1 if more days should be forced visible: |
190 | if (offset < 1) { |
191 | offset += d->m_numDayColumns; |
192 | } |
193 | |
194 | return initialPosition + offset; |
195 | } |
196 | |
197 | QDate KDateTable::dateFromPos(int position) |
198 | { |
199 | int offset = (d->m_weekDayFirstOfMonth - locale().firstDayOfWeek() + d->m_numDayColumns) % d->m_numDayColumns; |
200 | |
201 | // make sure at least one day of the previous month is visible. |
202 | // adjust this < 1 if more days should be forced visible: |
203 | if (offset < 1) { |
204 | offset += d->m_numDayColumns; |
205 | } |
206 | |
207 | return QDate(d->m_date.year(), d->m_date.month(), 1).addDays(days: position - offset); |
208 | } |
209 | |
210 | void KDateTable::paintEvent(QPaintEvent *e) |
211 | { |
212 | QPainter p(this); |
213 | const QRect &rectToUpdate = e->rect(); |
214 | double cellWidth = width() / (double)d->m_numDayColumns; |
215 | double cellHeight = height() / (double)d->m_numWeekRows; |
216 | int leftCol = (int)std::floor(x: rectToUpdate.left() / cellWidth); |
217 | int topRow = (int)std::floor(x: rectToUpdate.top() / cellHeight); |
218 | int rightCol = (int)std::ceil(x: rectToUpdate.right() / cellWidth); |
219 | int bottomRow = (int)std::ceil(x: rectToUpdate.bottom() / cellHeight); |
220 | bottomRow = qMin(a: bottomRow, b: d->m_numWeekRows - 1); |
221 | rightCol = qMin(a: rightCol, b: d->m_numDayColumns - 1); |
222 | if (layoutDirection() == Qt::RightToLeft) { |
223 | p.translate(dx: (d->m_numDayColumns - leftCol - 1) * cellWidth, dy: topRow * cellHeight); |
224 | } else { |
225 | p.translate(dx: leftCol * cellWidth, dy: topRow * cellHeight); |
226 | } |
227 | for (int i = leftCol; i <= rightCol; ++i) { |
228 | for (int j = topRow; j <= bottomRow; ++j) { |
229 | paintCell(painter: &p, row: j, col: i); |
230 | p.translate(dx: 0, dy: cellHeight); |
231 | } |
232 | if (layoutDirection() == Qt::RightToLeft) { |
233 | p.translate(dx: -cellWidth, dy: 0); |
234 | } else { |
235 | p.translate(dx: cellWidth, dy: 0); |
236 | } |
237 | p.translate(dx: 0, dy: -cellHeight * (bottomRow - topRow + 1)); |
238 | } |
239 | } |
240 | |
241 | void KDateTable::paintCell(QPainter *painter, int row, int col) |
242 | { |
243 | double w = (width() / (double)d->m_numDayColumns) - 1; |
244 | double h = (height() / (double)d->m_numWeekRows) - 1; |
245 | QRectF cell = QRectF(0, 0, w, h); |
246 | QString cellText; |
247 | QColor cellBackgroundColor; |
248 | QColor cellTextColor; |
249 | QFont cellFont = QFontDatabase::systemFont(type: QFontDatabase::GeneralFont); |
250 | bool workingDay = false; |
251 | int cellWeekDay; |
252 | int pos; |
253 | |
254 | // Calculate the position of the cell in the grid |
255 | pos = d->m_numDayColumns * (row - 1) + col; |
256 | |
257 | // Calculate what day of the week the cell is |
258 | if (col + locale().firstDayOfWeek() <= d->m_numDayColumns) { |
259 | cellWeekDay = col + locale().firstDayOfWeek(); |
260 | } else { |
261 | cellWeekDay = col + locale().firstDayOfWeek() - d->m_numDayColumns; |
262 | } |
263 | |
264 | // FIXME This is wrong if the widget is not using the global! |
265 | // See if cell day is normally a working day |
266 | if (locale().weekdays().first() <= locale().weekdays().last()) { |
267 | if (cellWeekDay >= locale().weekdays().first() && cellWeekDay <= locale().weekdays().last()) { |
268 | workingDay = true; |
269 | } |
270 | } else { |
271 | if (cellWeekDay >= locale().weekdays().first() // |
272 | || cellWeekDay <= locale().weekdays().last()) { |
273 | workingDay = true; |
274 | } |
275 | } |
276 | |
277 | if (row == 0) { |
278 | // We are drawing a header cell |
279 | |
280 | // If not a normal working day, then use "do not work today" color |
281 | if (workingDay) { |
282 | cellTextColor = palette().color(cr: QPalette::WindowText); |
283 | } else { |
284 | cellTextColor = Qt::darkRed; |
285 | } |
286 | cellBackgroundColor = palette().color(cr: QPalette::Window); |
287 | |
288 | // Set the text to the short day name and bold it |
289 | cellFont.setBold(true); |
290 | cellText = locale().dayName(cellWeekDay, format: QLocale::ShortFormat); |
291 | |
292 | } else { |
293 | // We are drawing a day cell |
294 | |
295 | // Calculate the date the cell represents |
296 | QDate cellDate = dateFromPos(position: pos); |
297 | |
298 | bool validDay = cellDate.isValid(); |
299 | |
300 | // Draw the day number in the cell, if the date is not valid then we don't want to show it |
301 | if (validDay) { |
302 | cellText = locale().toString(i: cellDate.day()); |
303 | } else { |
304 | cellText = QString(); |
305 | } |
306 | |
307 | if (!validDay || cellDate.month() != d->m_date.month()) { |
308 | // we are either |
309 | // ° painting an invalid day |
310 | // ° painting a day of the previous month or |
311 | // ° painting a day of the following month or |
312 | cellBackgroundColor = palette().color(cr: backgroundRole()); |
313 | cellTextColor = palette().color(cg: QPalette::Disabled, cr: QPalette::Text); |
314 | } else { |
315 | // Paint a day of the current month |
316 | |
317 | // Background Colour priorities will be (high-to-low): |
318 | // * Selected Day Background Colour |
319 | // * Customized Day Background Colour |
320 | // * Normal Day Background Colour |
321 | |
322 | // Background Shape priorities will be (high-to-low): |
323 | // * Customized Day Shape |
324 | // * Normal Day Shape |
325 | |
326 | // Text Colour priorities will be (high-to-low): |
327 | // * Customized Day Colour |
328 | // * Day of Pray Colour (Red letter) |
329 | // * Selected Day Colour |
330 | // * Normal Day Colour |
331 | |
332 | // Determine various characteristics of the cell date |
333 | bool selectedDay = (cellDate == date()); |
334 | bool currentDay = (cellDate == QDate::currentDate()); |
335 | bool dayOfPray = (cellDate.dayOfWeek() == Qt::Sunday); |
336 | // TODO: Uncomment if QLocale ever gets the feature... |
337 | // bool dayOfPray = ( cellDate.dayOfWeek() == locale().dayOfPray() ); |
338 | bool customDay = (d->m_useCustomColors && d->m_customPaintingModes.contains(key: cellDate.toJulianDay())); |
339 | |
340 | // Default values for a normal cell |
341 | cellBackgroundColor = palette().color(cr: backgroundRole()); |
342 | cellTextColor = palette().color(cr: foregroundRole()); |
343 | |
344 | // If we are drawing the current date, then draw it bold and active |
345 | if (currentDay) { |
346 | cellFont.setBold(true); |
347 | cellTextColor = palette().color(cr: QPalette::LinkVisited); |
348 | } |
349 | |
350 | // if we are drawing the day cell currently selected in the table |
351 | if (selectedDay) { |
352 | // set the background to highlighted |
353 | cellBackgroundColor = palette().color(cr: QPalette::Highlight); |
354 | cellTextColor = palette().color(cr: QPalette::HighlightedText); |
355 | } |
356 | |
357 | // If custom colors or shape are required for this date |
358 | if (customDay) { |
359 | KDateTablePrivate::DatePaintingMode mode = d->m_customPaintingModes[cellDate.toJulianDay()]; |
360 | if (mode.bgMode != NoBgMode) { |
361 | if (!selectedDay) { |
362 | cellBackgroundColor = mode.bgColor; |
363 | } |
364 | } |
365 | cellTextColor = mode.fgColor; |
366 | } |
367 | |
368 | // If the cell day is the day of religious observance, then always color text red unless Custom overrides |
369 | if (!customDay && dayOfPray) { |
370 | cellTextColor = Qt::darkRed; |
371 | } |
372 | } |
373 | |
374 | // If the cell day is out of the allowed range, paint it as disabled |
375 | if (!d->isInDateRange(date: cellDate)) { |
376 | cellBackgroundColor = palette().color(cg: QPalette::Disabled, cr: backgroundRole()); |
377 | } |
378 | } |
379 | |
380 | // Draw the background |
381 | if (row == 0) { |
382 | painter->setPen(cellBackgroundColor); |
383 | painter->setBrush(cellBackgroundColor); |
384 | painter->drawRect(rect: cell); |
385 | } else if (cellBackgroundColor != palette().color(cr: backgroundRole()) || pos == d->m_hoveredPos) { |
386 | QStyleOptionViewItem opt; |
387 | opt.initFrom(w: this); |
388 | opt.rect = cell.toRect(); |
389 | if (cellBackgroundColor != palette().color(cr: backgroundRole())) { |
390 | opt.palette.setBrush(acr: QPalette::Highlight, abrush: cellBackgroundColor); |
391 | opt.state |= QStyle::State_Selected; |
392 | } |
393 | if (pos == d->m_hoveredPos && opt.state & QStyle::State_Enabled) { |
394 | opt.state |= QStyle::State_MouseOver; |
395 | } else { |
396 | opt.state &= ~QStyle::State_MouseOver; |
397 | } |
398 | opt.showDecorationSelected = true; |
399 | opt.viewItemPosition = QStyleOptionViewItem::OnlyOne; |
400 | style()->drawPrimitive(pe: QStyle::PE_PanelItemViewItem, opt: &opt, p: painter, w: this); |
401 | } |
402 | |
403 | // Draw the text |
404 | painter->setPen(cellTextColor); |
405 | painter->setFont(cellFont); |
406 | painter->drawText(r: cell, flags: Qt::AlignCenter, text: cellText, br: &cell); |
407 | |
408 | // Draw the base line |
409 | if (row == 0) { |
410 | painter->setPen(palette().color(cr: foregroundRole())); |
411 | painter->drawLine(p1: QPointF(0, h), p2: QPointF(w, h)); |
412 | } |
413 | |
414 | // If the day cell we just drew is bigger than the current max cell sizes, |
415 | // then adjust the max to the current cell |
416 | if (cell.width() > d->m_maxCell.width()) { |
417 | d->m_maxCell.setWidth(cell.width()); |
418 | } |
419 | if (cell.height() > d->m_maxCell.height()) { |
420 | d->m_maxCell.setHeight(cell.height()); |
421 | } |
422 | } |
423 | |
424 | void KDateTable::KDateTablePrivate::nextMonth() |
425 | { |
426 | // setDate does validity checking for us |
427 | q->setDate(m_date.addMonths(months: 1)); |
428 | } |
429 | |
430 | void KDateTable::KDateTablePrivate::previousMonth() |
431 | { |
432 | // setDate does validity checking for us |
433 | q->setDate(m_date.addMonths(months: -1)); |
434 | } |
435 | |
436 | void KDateTable::KDateTablePrivate::beginningOfMonth() |
437 | { |
438 | // setDate does validity checking for us |
439 | q->setDate(QDate(m_date.year(), m_date.month(), 1)); |
440 | } |
441 | |
442 | void KDateTable::KDateTablePrivate::endOfMonth() |
443 | { |
444 | // setDate does validity checking for us |
445 | q->setDate(QDate(m_date.year(), m_date.month() + 1, 0)); |
446 | } |
447 | |
448 | // JPL Do these make the assumption that first day of week is weekday 1? As it may not be. |
449 | void KDateTable::KDateTablePrivate::beginningOfWeek() |
450 | { |
451 | // setDate does validity checking for us |
452 | q->setDate(m_date.addDays(days: 1 - m_date.dayOfWeek())); |
453 | } |
454 | |
455 | // JPL Do these make the assumption that first day of week is weekday 1? As it may not be. |
456 | void KDateTable::KDateTablePrivate::endOfWeek() |
457 | { |
458 | // setDate does validity checking for us |
459 | q->setDate(m_date.addDays(days: 7 - m_date.dayOfWeek())); |
460 | } |
461 | |
462 | void KDateTable::keyPressEvent(QKeyEvent *e) |
463 | { |
464 | switch (e->key()) { |
465 | case Qt::Key_Up: |
466 | // setDate does validity checking for us |
467 | setDate(d->m_date.addDays(days: -d->m_numDayColumns)); |
468 | break; |
469 | case Qt::Key_Down: |
470 | // setDate does validity checking for us |
471 | setDate(d->m_date.addDays(days: d->m_numDayColumns)); |
472 | break; |
473 | case Qt::Key_Left: |
474 | // setDate does validity checking for us |
475 | setDate(d->m_date.addDays(days: -1)); |
476 | break; |
477 | case Qt::Key_Right: |
478 | // setDate does validity checking for us |
479 | setDate(d->m_date.addDays(days: 1)); |
480 | break; |
481 | case Qt::Key_Minus: |
482 | // setDate does validity checking for us |
483 | setDate(d->m_date.addDays(days: -1)); |
484 | break; |
485 | case Qt::Key_Plus: |
486 | // setDate does validity checking for us |
487 | setDate(d->m_date.addDays(days: 1)); |
488 | break; |
489 | case Qt::Key_N: |
490 | // setDate does validity checking for us |
491 | setDate(QDate::currentDate()); |
492 | break; |
493 | case Qt::Key_Return: |
494 | case Qt::Key_Enter: |
495 | Q_EMIT tableClicked(); |
496 | break; |
497 | case Qt::Key_Control: |
498 | case Qt::Key_Alt: |
499 | case Qt::Key_Meta: |
500 | case Qt::Key_Shift: |
501 | // Don't beep for modifiers |
502 | break; |
503 | default: |
504 | if (!e->modifiers()) { // hm |
505 | QApplication::beep(); |
506 | } |
507 | } |
508 | } |
509 | |
510 | void KDateTable::setFontSize(int size) |
511 | { |
512 | QFontMetricsF metrics(fontMetrics()); |
513 | QRectF rect; |
514 | // ----- store rectangles: |
515 | d->fontsize = size; |
516 | // ----- find largest day name: |
517 | d->m_maxCell.setWidth(0); |
518 | d->m_maxCell.setHeight(0); |
519 | for (int weekday = 1; weekday <= 7; ++weekday) { |
520 | rect = metrics.boundingRect(string: locale().dayName(weekday, format: QLocale::ShortFormat)); |
521 | d->m_maxCell.setWidth(qMax(a: d->m_maxCell.width(), b: rect.width())); |
522 | d->m_maxCell.setHeight(qMax(a: d->m_maxCell.height(), b: rect.height())); |
523 | } |
524 | // ----- compare with a real wide number and add some space: |
525 | rect = metrics.boundingRect(QStringLiteral("88" )); |
526 | d->m_maxCell.setWidth(qMax(a: d->m_maxCell.width() + 2, b: rect.width())); |
527 | d->m_maxCell.setHeight(qMax(a: d->m_maxCell.height() + 4, b: rect.height())); |
528 | } |
529 | |
530 | void KDateTable::wheelEvent(QWheelEvent *e) |
531 | { |
532 | setDate(d->m_date.addMonths(months: -(int)(e->angleDelta().y() / 120))); |
533 | e->accept(); |
534 | } |
535 | |
536 | bool KDateTable::event(QEvent *ev) |
537 | { |
538 | switch (ev->type()) { |
539 | case QEvent::HoverMove: { |
540 | QHoverEvent *e = static_cast<QHoverEvent *>(ev); |
541 | const int row = e->position().y() * d->m_numWeekRows / height(); |
542 | int col; |
543 | if (layoutDirection() == Qt::RightToLeft) { |
544 | col = d->m_numDayColumns - (e->position().x() * d->m_numDayColumns / width()) - 1; |
545 | } else { |
546 | col = e->position().x() * d->m_numDayColumns / width(); |
547 | } |
548 | |
549 | const int pos = row < 1 ? -1 : (d->m_numDayColumns * (row - 1)) + col; |
550 | |
551 | if (pos != d->m_hoveredPos) { |
552 | d->m_hoveredPos = pos; |
553 | update(); |
554 | } |
555 | break; |
556 | } |
557 | case QEvent::HoverLeave: |
558 | if (d->m_hoveredPos != -1) { |
559 | d->m_hoveredPos = -1; |
560 | update(); |
561 | } |
562 | break; |
563 | default: |
564 | break; |
565 | } |
566 | return QWidget::event(event: ev); |
567 | } |
568 | |
569 | void KDateTable::mousePressEvent(QMouseEvent *e) |
570 | { |
571 | if (e->type() != QEvent::MouseButtonPress) { // the KDatePicker only reacts on mouse press events: |
572 | return; |
573 | } |
574 | |
575 | if (!isEnabled()) { |
576 | QApplication::beep(); |
577 | return; |
578 | } |
579 | |
580 | int row; |
581 | int col; |
582 | int pos; |
583 | |
584 | QPoint mouseCoord = e->pos(); |
585 | row = mouseCoord.y() * d->m_numWeekRows / height(); |
586 | if (layoutDirection() == Qt::RightToLeft) { |
587 | col = d->m_numDayColumns - (mouseCoord.x() * d->m_numDayColumns / width()) - 1; |
588 | } else { |
589 | col = mouseCoord.x() * d->m_numDayColumns / width(); |
590 | } |
591 | |
592 | if (row < 1 || col < 0) { // the user clicked on the frame of the table |
593 | return; |
594 | } |
595 | |
596 | // Rows and columns are zero indexed. The (row - 1) below is to avoid counting |
597 | // the row with the days of the week in the calculation. |
598 | |
599 | // new position and date |
600 | pos = (d->m_numDayColumns * (row - 1)) + col; |
601 | QDate clickedDate = dateFromPos(position: pos); |
602 | |
603 | if (!d->isInDateRange(date: clickedDate)) { |
604 | return; |
605 | } |
606 | |
607 | // set the new date. If it is in the previous or next month, the month will |
608 | // automatically be changed, no need to do that manually... |
609 | // validity checking done inside setDate |
610 | setDate(clickedDate); |
611 | |
612 | // This could be optimized to only call update over the regions |
613 | // of old and new cell, but 99% of times there is also a call to |
614 | // setDate that already calls update() so no need to optimize that |
615 | // much here |
616 | update(); |
617 | |
618 | Q_EMIT tableClicked(); |
619 | |
620 | if (e->button() == Qt::RightButton && d->m_popupMenuEnabled) { |
621 | QMenu * = new QMenu(); |
622 | menu->addSection(text: locale().toString(date: d->m_date)); |
623 | Q_EMIT aboutToShowContextMenu(menu, date: clickedDate); |
624 | menu->popup(pos: e->globalPosition().toPoint()); |
625 | } |
626 | } |
627 | |
628 | void KDateTable::KDateTablePrivate::setDate(const QDate &date) |
629 | { |
630 | m_date = date; |
631 | m_weekDayFirstOfMonth = QDate(date.year(), date.month(), 1).dayOfWeek(); |
632 | m_numDaysThisMonth = m_date.daysInMonth(); |
633 | m_numDayColumns = 7; |
634 | } |
635 | |
636 | bool KDateTable::setDate(const QDate &toDate) |
637 | { |
638 | if (!toDate.isValid()) { |
639 | return false; |
640 | } |
641 | |
642 | if (toDate == date()) { |
643 | return true; |
644 | } |
645 | |
646 | if (!d->isInDateRange(date: toDate)) { |
647 | return false; |
648 | } |
649 | |
650 | d->setDate(toDate); |
651 | Q_EMIT dateChanged(date: date()); |
652 | update(); |
653 | |
654 | return true; |
655 | } |
656 | |
657 | const QDate &KDateTable::date() const |
658 | { |
659 | return d->m_date; |
660 | } |
661 | |
662 | void KDateTable::focusInEvent(QFocusEvent *e) |
663 | { |
664 | QWidget::focusInEvent(event: e); |
665 | } |
666 | |
667 | void KDateTable::focusOutEvent(QFocusEvent *e) |
668 | { |
669 | QWidget::focusOutEvent(event: e); |
670 | } |
671 | |
672 | QSize KDateTable::sizeHint() const |
673 | { |
674 | if (d->m_maxCell.height() > 0 && d->m_maxCell.width() > 0) { |
675 | return QSize(qRound(d: d->m_maxCell.width() * d->m_numDayColumns), (qRound(d: d->m_maxCell.height() + 2) * d->m_numWeekRows)); |
676 | } else { |
677 | // qCDebug(KWidgetsAddonsLog) << "KDateTable::sizeHint: obscure failure - " << endl; |
678 | return QSize(-1, -1); |
679 | } |
680 | } |
681 | |
682 | void KDateTable::(bool enable) |
683 | { |
684 | d->m_popupMenuEnabled = enable; |
685 | } |
686 | |
687 | bool KDateTable::() const |
688 | { |
689 | return d->m_popupMenuEnabled; |
690 | } |
691 | |
692 | void KDateTable::setCustomDatePainting(const QDate &date, const QColor &fgColor, BackgroundMode bgMode, const QColor &bgColor) |
693 | { |
694 | if (!fgColor.isValid()) { |
695 | unsetCustomDatePainting(date); |
696 | return; |
697 | } |
698 | |
699 | KDateTablePrivate::DatePaintingMode mode; |
700 | mode.bgMode = bgMode; |
701 | mode.fgColor = fgColor; |
702 | mode.bgColor = bgColor; |
703 | |
704 | d->m_customPaintingModes.insert(key: date.toJulianDay(), value: mode); |
705 | d->m_useCustomColors = true; |
706 | update(); |
707 | } |
708 | |
709 | void KDateTable::unsetCustomDatePainting(const QDate &date) |
710 | { |
711 | d->m_customPaintingModes.remove(key: date.toJulianDay()); |
712 | if (d->m_customPaintingModes.isEmpty()) { |
713 | d->m_useCustomColors = false; |
714 | } |
715 | update(); |
716 | } |
717 | |
718 | void KDateTable::setDateRange(const QDate &minDate, const QDate &maxDate) |
719 | { |
720 | d->setDateRange(minDate, maxDate); |
721 | } |
722 | |
723 | #include "moc_kdatetable_p.cpp" |
724 | |