| 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 | |