1/**************************************************************************** 2** 3** Copyright (C) 2016 The Qt Company Ltd. 4** Contact: https://www.qt.io/licensing/ 5** 6** This file is part of the Qt Quick Controls module of the Qt Toolkit. 7** 8** $QT_BEGIN_LICENSE:LGPL$ 9** Commercial License Usage 10** Licensees holding valid commercial Qt licenses may use this file in 11** accordance with the commercial license agreement provided with the 12** Software or, alternatively, in accordance with the terms contained in 13** a written agreement between you and The Qt Company. For licensing terms 14** and conditions see https://www.qt.io/terms-conditions. For further 15** information use the contact form at https://www.qt.io/contact-us. 16** 17** GNU Lesser General Public License Usage 18** Alternatively, this file may be used under the terms of the GNU Lesser 19** General Public License version 3 as published by the Free Software 20** Foundation and appearing in the file LICENSE.LGPL3 included in the 21** packaging of this file. Please review the following information to 22** ensure the GNU Lesser General Public License version 3 requirements 23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. 24** 25** GNU General Public License Usage 26** Alternatively, this file may be used under the terms of the GNU 27** General Public License version 2.0 or (at your option) the GNU General 28** Public license version 3 or any later version approved by the KDE Free 29** Qt Foundation. The licenses are as published by the Free Software 30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 31** included in the packaging of this file. Please review the following 32** information to ensure the GNU General Public License requirements will 33** be met: https://www.gnu.org/licenses/gpl-2.0.html and 34** https://www.gnu.org/licenses/gpl-3.0.html. 35** 36** $QT_END_LICENSE$ 37** 38****************************************************************************/ 39 40import QtQuick 2.2 41import QtQuick.Controls 1.2 42import QtQuick.Controls.Private 1.0 43 44/*! 45 \qmltype CalendarStyle 46 \inqmlmodule QtQuick.Controls.Styles 47 \since 5.3 48 \ingroup controlsstyling 49 \brief Provides custom styling for \l Calendar. 50 51 \section2 Component Map 52 53 \image calendarstyle-components-week-numbers.png 54 55 The calendar has the following styleable components: 56 57 \table 58 \row \li \image square-white.png 59 \li \l background 60 \li Fills the entire control. 61 \row \li \image square-yellow.png 62 \li \l navigationBar 63 \li 64 \row \li \image square-green.png 65 \li \l dayOfWeekDelegate 66 \li One instance per day of week. 67 \row \li \image square-red.png 68 \li \l weekNumberDelegate 69 \li One instance per week. 70 \row \li \image square-blue.png 71 \li \l dayDelegate 72 \li One instance per day of month. 73 \endtable 74 75 \section2 Custom Style Example 76 \qml 77 Calendar { 78 anchors.centerIn: parent 79 80 style: CalendarStyle { 81 gridVisible: false 82 dayDelegate: Rectangle { 83 gradient: Gradient { 84 GradientStop { 85 position: 0.00 86 color: styleData.selected ? "#111" : (styleData.visibleMonth && styleData.valid ? "#444" : "#666"); 87 } 88 GradientStop { 89 position: 1.00 90 color: styleData.selected ? "#444" : (styleData.visibleMonth && styleData.valid ? "#111" : "#666"); 91 } 92 GradientStop { 93 position: 1.00 94 color: styleData.selected ? "#777" : (styleData.visibleMonth && styleData.valid ? "#111" : "#666"); 95 } 96 } 97 98 Label { 99 text: styleData.date.getDate() 100 anchors.centerIn: parent 101 color: styleData.valid ? "white" : "grey" 102 } 103 104 Rectangle { 105 width: parent.width 106 height: 1 107 color: "#555" 108 anchors.bottom: parent.bottom 109 } 110 111 Rectangle { 112 width: 1 113 height: parent.height 114 color: "#555" 115 anchors.right: parent.right 116 } 117 } 118 } 119 } 120 \endqml 121*/ 122 123Style { 124 id: calendarStyle 125 126 /*! 127 The Calendar this style is attached to. 128 */ 129 readonly property Calendar control: __control 130 131 /*! 132 The color of the grid lines. 133 */ 134 property color gridColor: "#d3d3d3" 135 136 /*! 137 This property determines the visibility of the grid. 138 139 The default value is \c true. 140 */ 141 property bool gridVisible: true 142 143 /*! 144 \internal 145 146 The width of each grid line. 147 */ 148 property real __gridLineWidth: 1 149 150 /*! \internal */ 151 property color __horizontalSeparatorColor: gridColor 152 153 /*! \internal */ 154 property color __verticalSeparatorColor: gridColor 155 156 function __cellRectAt(index) { 157 return CalendarUtils.cellRectAt(index, control.__panel.columns, control.__panel.rows, 158 control.__panel.availableWidth, control.__panel.availableHeight, gridVisible ? __gridLineWidth : 0); 159 } 160 161 function __isValidDate(date) { 162 return date !== undefined 163 && date.getTime() >= control.minimumDate.getTime() 164 && date.getTime() <= control.maximumDate.getTime(); 165 } 166 167 /*! 168 The background of the calendar. 169 170 The implicit size of the calendar is calculated based on the implicit size of the background delegate. 171 */ 172 property Component background: Rectangle { 173 color: "#fff" 174 implicitWidth: Math.max(250, Math.round(TextSingleton.implicitHeight * 14)) 175 implicitHeight: Math.max(250, Math.round(TextSingleton.implicitHeight * 14)) 176 } 177 178 /*! 179 The navigation bar of the calendar. 180 181 Styles the bar at the top of the calendar that contains the 182 next month/previous month buttons and the selected date label. 183 184 The properties provided to the delegate are: 185 \table 186 \row \li readonly property string \b styleData.title 187 \li The title of the calendar. 188 \endtable 189 */ 190 property Component navigationBar: Rectangle { 191 height: Math.round(TextSingleton.implicitHeight * 2.73) 192 color: "#f9f9f9" 193 194 Rectangle { 195 color: Qt.rgba(1,1,1,0.6) 196 height: 1 197 width: parent.width 198 } 199 200 Rectangle { 201 anchors.bottom: parent.bottom 202 height: 1 203 width: parent.width 204 color: "#ddd" 205 } 206 HoverButton { 207 id: previousMonth 208 width: parent.height 209 height: width 210 anchors.verticalCenter: parent.verticalCenter 211 anchors.left: parent.left 212 source: "images/leftanglearrow.png" 213 onClicked: control.showPreviousMonth() 214 } 215 Label { 216 id: dateText 217 text: styleData.title 218 elide: Text.ElideRight 219 horizontalAlignment: Text.AlignHCenter 220 font.pixelSize: TextSingleton.implicitHeight * 1.25 221 anchors.verticalCenter: parent.verticalCenter 222 anchors.left: previousMonth.right 223 anchors.leftMargin: 2 224 anchors.right: nextMonth.left 225 anchors.rightMargin: 2 226 } 227 HoverButton { 228 id: nextMonth 229 width: parent.height 230 height: width 231 anchors.verticalCenter: parent.verticalCenter 232 anchors.right: parent.right 233 source: "images/rightanglearrow.png" 234 onClicked: control.showNextMonth() 235 } 236 } 237 238 /*! 239 The delegate that styles each date in the calendar. 240 241 The properties provided to each delegate are: 242 \table 243 \row \li readonly property date \b styleData.date 244 \li The date this delegate represents. 245 \row \li readonly property bool \b styleData.selected 246 \li \c true if this is the selected date. 247 \row \li readonly property int \b styleData.index 248 \li The index of this delegate. 249 \row \li readonly property bool \b styleData.valid 250 \li \c true if this date is greater than or equal to than \l {Calendar::minimumDate}{minimumDate} and 251 less than or equal to \l {Calendar::maximumDate}{maximumDate}. 252 \row \li readonly property bool \b styleData.today 253 \li \c true if this date is equal to today's date. 254 \row \li readonly property bool \b styleData.visibleMonth 255 \li \c true if the month in this date is the visible month. 256 \row \li readonly property bool \b styleData.hovered 257 \li \c true if the mouse is over this cell. 258 \note This property is \c true even when the mouse is hovered over an invalid date. 259 \row \li readonly property bool \b styleData.pressed 260 \li \c true if the mouse is pressed on this cell. 261 \note This property is \c true even when the mouse is pressed on an invalid date. 262 \endtable 263 */ 264 property Component dayDelegate: Rectangle { 265 anchors.fill: parent 266 anchors.leftMargin: (!addExtraMargin || control.weekNumbersVisible) && styleData.index % CalendarUtils.daysInAWeek === 0 ? 0 : -1 267 anchors.rightMargin: !addExtraMargin && styleData.index % CalendarUtils.daysInAWeek === CalendarUtils.daysInAWeek - 1 ? 0 : -1 268 anchors.bottomMargin: !addExtraMargin && styleData.index >= CalendarUtils.daysInAWeek * (CalendarUtils.weeksOnACalendarMonth - 1) ? 0 : -1 269 anchors.topMargin: styleData.selected ? -1 : 0 270 color: styleData.date !== undefined && styleData.selected ? selectedDateColor : "transparent" 271 272 readonly property bool addExtraMargin: control.frameVisible && styleData.selected 273 readonly property color sameMonthDateTextColor: "#444" 274 readonly property color selectedDateColor: Qt.platform.os === "osx" ? "#3778d0" : SystemPaletteSingleton.highlight(control.enabled) 275 readonly property color selectedDateTextColor: "white" 276 readonly property color differentMonthDateTextColor: "#bbb" 277 readonly property color invalidDateColor: "#dddddd" 278 Label { 279 id: dayDelegateText 280 text: styleData.date.getDate() 281 anchors.centerIn: parent 282 horizontalAlignment: Text.AlignRight 283 font.pixelSize: Math.min(parent.height/3, parent.width/3) 284 color: { 285 var theColor = invalidDateColor; 286 if (styleData.valid) { 287 // Date is within the valid range. 288 theColor = styleData.visibleMonth ? sameMonthDateTextColor : differentMonthDateTextColor; 289 if (styleData.selected) 290 theColor = selectedDateTextColor; 291 } 292 theColor; 293 } 294 } 295 } 296 297 /*! 298 The delegate that styles each weekday. 299 300 The height of the weekday row is calculated based on the maximum implicit height of the delegates. 301 302 The properties provided to each delegate are: 303 \table 304 \row \li readonly property int \b styleData.index 305 \li The index (0-6) of the delegate. 306 \row \li readonly property int \b styleData.dayOfWeek 307 \li The day of the week this delegate represents. Possible values: 308 \list 309 \li \c Locale.Sunday 310 \li \c Locale.Monday 311 \li \c Locale.Tuesday 312 \li \c Locale.Wednesday 313 \li \c Locale.Thursday 314 \li \c Locale.Friday 315 \li \c Locale.Saturday 316 \endlist 317 \endtable 318 */ 319 property Component dayOfWeekDelegate: Rectangle { 320 color: gridVisible ? "#fcfcfc" : "transparent" 321 implicitHeight: Math.round(TextSingleton.implicitHeight * 2.25) 322 Label { 323 text: control.locale.dayName(styleData.dayOfWeek, control.dayOfWeekFormat) 324 anchors.centerIn: parent 325 } 326 } 327 328 /*! 329 The delegate that styles each week number. 330 331 The width of the week number column is calculated based on the maximum implicit width of the delegates. 332 333 The properties provided to each delegate are: 334 \table 335 \row \li readonly property int \b styleData.index 336 \li The index (0-5) of the delegate. 337 \row \li readonly property int \b styleData.weekNumber 338 \li The number of the week this delegate represents. 339 \endtable 340 */ 341 property Component weekNumberDelegate: Rectangle { 342 implicitWidth: Math.round(TextSingleton.implicitHeight * 2) 343 Label { 344 text: styleData.weekNumber 345 anchors.centerIn: parent 346 color: "#444" 347 } 348 } 349 350 /*! \internal */ 351 property Component panel: Item { 352 id: panelItem 353 354 implicitWidth: backgroundLoader.implicitWidth 355 implicitHeight: backgroundLoader.implicitHeight 356 357 property alias navigationBarItem: navigationBarLoader.item 358 359 property alias dayOfWeekHeaderRow: dayOfWeekHeaderRow 360 361 readonly property int weeksToShow: 6 362 readonly property int rows: weeksToShow 363 readonly property int columns: CalendarUtils.daysInAWeek 364 365 // The combined available width and height to be shared amongst each cell. 366 readonly property real availableWidth: viewContainer.width 367 readonly property real availableHeight: viewContainer.height 368 369 property int hoveredCellIndex: -1 370 property int pressedCellIndex: -1 371 property int pressCellIndex: -1 372 property var pressDate: null 373 374 Rectangle { 375 anchors.fill: parent 376 color: "transparent" 377 border.color: gridColor 378 visible: control.frameVisible 379 } 380 381 Item { 382 id: container 383 anchors.fill: parent 384 anchors.margins: control.frameVisible ? 1 : 0 385 386 Loader { 387 id: backgroundLoader 388 anchors.fill: parent 389 sourceComponent: background 390 } 391 392 Loader { 393 id: navigationBarLoader 394 anchors.left: parent.left 395 anchors.right: parent.right 396 anchors.top: parent.top 397 sourceComponent: navigationBar 398 active: control.navigationBarVisible 399 400 property QtObject styleData: QtObject { 401 readonly property string title: control.locale.standaloneMonthName(control.visibleMonth) 402 + new Date(control.visibleYear, control.visibleMonth, 1).toLocaleDateString(control.locale, " yyyy") 403 } 404 } 405 406 Row { 407 id: dayOfWeekHeaderRow 408 anchors.top: navigationBarLoader.bottom 409 anchors.left: parent.left 410 anchors.leftMargin: (control.weekNumbersVisible ? weekNumbersItem.width : 0) 411 anchors.right: parent.right 412 spacing: gridVisible ? __gridLineWidth : 0 413 property alias __repeater: repeater 414 415 Repeater { 416 id: repeater 417 model: CalendarHeaderModel { 418 locale: control.locale 419 } 420 Loader { 421 id: dayOfWeekDelegateLoader 422 sourceComponent: dayOfWeekDelegate 423 width: __cellRectAt(index).width 424 425 readonly property int __index: index 426 readonly property var __dayOfWeek: dayOfWeek 427 428 property QtObject styleData: QtObject { 429 readonly property alias index: dayOfWeekDelegateLoader.__index 430 readonly property alias dayOfWeek: dayOfWeekDelegateLoader.__dayOfWeek 431 } 432 } 433 } 434 } 435 436 Rectangle { 437 id: topGridLine 438 color: __horizontalSeparatorColor 439 width: parent.width 440 height: __gridLineWidth 441 visible: gridVisible 442 anchors.top: dayOfWeekHeaderRow.bottom 443 } 444 445 Row { 446 id: gridRow 447 width: weekNumbersItem.width + viewContainer.width 448 height: viewContainer.height 449 anchors.top: topGridLine.bottom 450 451 Column { 452 id: weekNumbersItem 453 visible: control.weekNumbersVisible 454 height: viewContainer.height 455 spacing: gridVisible ? __gridLineWidth : 0 456 Repeater { 457 id: weekNumberRepeater 458 model: panelItem.weeksToShow 459 460 Loader { 461 id: weekNumberDelegateLoader 462 height: __cellRectAt(index * panelItem.columns).height 463 sourceComponent: weekNumberDelegate 464 465 readonly property int __index: index 466 property int __weekNumber: control.__model.weekNumberAt(index) 467 468 Connections { 469 target: control 470 471 function onVisibleMonthChanged() { 472 __weekNumber = control.__model.weekNumberAt(index) 473 } 474 475 function onVisibleYearChanged() { 476 __weekNumber = control.__model.weekNumberAt(index) 477 } 478 } 479 480 Connections { 481 target: control.__model 482 function onCountChanged() { 483 __weekNumber = control.__model.weekNumberAt(index) 484 } 485 } 486 487 property QtObject styleData: QtObject { 488 readonly property alias index: weekNumberDelegateLoader.__index 489 readonly property int weekNumber: weekNumberDelegateLoader.__weekNumber 490 } 491 } 492 } 493 } 494 495 Rectangle { 496 id: separator 497 anchors.topMargin: - dayOfWeekHeaderRow.height - 1 498 anchors.top: weekNumbersItem.top 499 anchors.bottom: weekNumbersItem.bottom 500 501 width: __gridLineWidth 502 color: __verticalSeparatorColor 503 visible: control.weekNumbersVisible 504 } 505 506 // Contains the grid lines and the grid itself. 507 Item { 508 id: viewContainer 509 width: container.width - (control.weekNumbersVisible ? weekNumbersItem.width + separator.width : 0) 510 height: container.height - navigationBarLoader.height - dayOfWeekHeaderRow.height - topGridLine.height 511 512 Repeater { 513 id: verticalGridLineRepeater 514 model: panelItem.columns - 1 515 delegate: Rectangle { 516 x: __cellRectAt(index + 1).x - __gridLineWidth 517 y: 0 518 width: __gridLineWidth 519 height: viewContainer.height 520 color: gridColor 521 visible: gridVisible 522 } 523 } 524 525 Repeater { 526 id: horizontalGridLineRepeater 527 model: panelItem.rows - 1 528 delegate: Rectangle { 529 x: 0 530 y: __cellRectAt((index + 1) * panelItem.columns).y - __gridLineWidth 531 width: viewContainer.width 532 height: __gridLineWidth 533 color: gridColor 534 visible: gridVisible 535 } 536 } 537 538 MouseArea { 539 id: mouseArea 540 anchors.fill: parent 541 542 hoverEnabled: Settings.hoverEnabled 543 544 function cellIndexAt(mouseX, mouseY) { 545 var viewContainerPos = viewContainer.mapFromItem(mouseArea, mouseX, mouseY); 546 var child = viewContainer.childAt(viewContainerPos.x, viewContainerPos.y); 547 // In the tests, the mouseArea sometimes gets picked instead of the cells, 548 // probably because stuff is still loading. To be safe, we check for that here. 549 return child && child !== mouseArea ? child.__index : -1; 550 } 551 552 onEntered: { 553 hoveredCellIndex = cellIndexAt(mouseX, mouseY); 554 if (hoveredCellIndex === undefined) { 555 hoveredCellIndex = cellIndexAt(mouseX, mouseY); 556 } 557 558 var date = view.model.dateAt(hoveredCellIndex); 559 if (__isValidDate(date)) { 560 control.hovered(date); 561 } 562 } 563 564 onExited: { 565 hoveredCellIndex = -1; 566 } 567 568 onPositionChanged: { 569 var indexOfCell = cellIndexAt(mouse.x, mouse.y); 570 var previousHoveredCellIndex = hoveredCellIndex; 571 hoveredCellIndex = indexOfCell; 572 if (indexOfCell !== -1) { 573 var date = view.model.dateAt(indexOfCell); 574 if (__isValidDate(date)) { 575 if (hoveredCellIndex !== previousHoveredCellIndex) 576 control.hovered(date); 577 578 // The date must be different for the pressed signal to be emitted. 579 if (pressed && date.getTime() !== control.selectedDate.getTime()) { 580 control.pressed(date); 581 582 // You can't select dates in a different month while dragging. 583 if (date.getMonth() === control.selectedDate.getMonth()) { 584 control.selectedDate = date; 585 pressedCellIndex = indexOfCell; 586 } 587 } 588 } 589 } 590 } 591 592 onPressed: { 593 pressCellIndex = cellIndexAt(mouse.x, mouse.y); 594 pressDate = null; 595 if (pressCellIndex !== -1) { 596 var date = view.model.dateAt(pressCellIndex); 597 pressedCellIndex = pressCellIndex; 598 pressDate = date; 599 if (__isValidDate(date)) { 600 control.selectedDate = date; 601 control.pressed(date); 602 } 603 } 604 } 605 606 onReleased: { 607 var indexOfCell = cellIndexAt(mouse.x, mouse.y); 608 if (indexOfCell !== -1) { 609 // The cell index might be valid, but the date has to be too. We could let the 610 // selected date validation take care of this, but then the selected date would 611 // change to the earliest day if a day before the minimum date is clicked, for example. 612 var date = view.model.dateAt(indexOfCell); 613 if (__isValidDate(date)) { 614 control.released(date); 615 } 616 } 617 pressedCellIndex = -1; 618 } 619 620 onClicked: { 621 var indexOfCell = cellIndexAt(mouse.x, mouse.y); 622 if (indexOfCell !== -1 && indexOfCell === pressCellIndex) { 623 if (__isValidDate(pressDate)) 624 control.clicked(pressDate); 625 } 626 } 627 628 onDoubleClicked: { 629 var indexOfCell = cellIndexAt(mouse.x, mouse.y); 630 if (indexOfCell !== -1) { 631 var date = view.model.dateAt(indexOfCell); 632 if (__isValidDate(date)) 633 control.doubleClicked(date); 634 } 635 } 636 637 onPressAndHold: { 638 var indexOfCell = cellIndexAt(mouse.x, mouse.y); 639 if (indexOfCell !== -1 && indexOfCell === pressCellIndex) { 640 var date = view.model.dateAt(indexOfCell); 641 if (__isValidDate(date)) 642 control.pressAndHold(date); 643 } 644 } 645 } 646 647 Connections { 648 target: control 649 function onSelectedDateChanged() { view.selectedDateChanged() } 650 } 651 652 Repeater { 653 id: view 654 655 property int currentIndex: -1 656 657 model: control.__model 658 659 Component.onCompleted: selectedDateChanged() 660 661 function selectedDateChanged() { 662 if (model !== undefined && model.locale !== undefined) { 663 currentIndex = model.indexAt(control.selectedDate); 664 } 665 } 666 667 delegate: Loader { 668 id: delegateLoader 669 670 x: __cellRectAt(index).x 671 y: __cellRectAt(index).y 672 width: __cellRectAt(index).width 673 height: __cellRectAt(index).height 674 sourceComponent: dayDelegate 675 676 readonly property int __index: index 677 readonly property date __date: date 678 // We rely on the fact that an invalid QDate will be converted to a Date 679 // whose year is -4713, which is always an invalid date since our 680 // earliest minimum date is the year 1. 681 readonly property bool valid: __isValidDate(date) 682 683 property QtObject styleData: QtObject { 684 readonly property alias index: delegateLoader.__index 685 readonly property bool selected: control.selectedDate.getFullYear() === date.getFullYear() && 686 control.selectedDate.getMonth() === date.getMonth() && 687 control.selectedDate.getDate() === date.getDate() 688 readonly property alias date: delegateLoader.__date 689 readonly property bool valid: delegateLoader.valid 690 // TODO: this will not be correct if the app is running when a new day begins. 691 readonly property bool today: date.getTime() === new Date().setHours(0, 0, 0, 0) 692 readonly property bool visibleMonth: date.getMonth() === control.visibleMonth 693 readonly property bool hovered: panelItem.hoveredCellIndex == index 694 readonly property bool pressed: panelItem.pressedCellIndex == index 695 // todo: pressed property here, clicked and doubleClicked in the control itself 696 } 697 } 698 } 699 } 700 } 701 } 702 } 703} 704