Warning, /plasma/plasma-workspace/applets/digital-clock/package/contents/ui/CalendarView.qml is written in an unsupported language. File is not indexed.
0001 /* 0002 SPDX-FileCopyrightText: 2013 Sebastian Kügler <sebas@kde.org> 0003 SPDX-FileCopyrightText: 2015 Martin Klapetek <mklapetek@kde.org> 0004 SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org> 0005 SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk> 0006 0007 SPDX-License-Identifier: GPL-2.0-or-later 0008 */ 0009 import QtQuick 2.4 0010 import QtQuick.Layouts 1.1 0011 import QtQml 2.15 0012 0013 import org.kde.kquickcontrolsaddons 2.0 // For kcmshell 0014 import org.kde.plasma.plasmoid 2.0 0015 import org.kde.ksvg 1.0 as KSvg 0016 import org.kde.plasma.workspace.calendar 2.0 as PlasmaCalendar 0017 import org.kde.plasma.components 3.0 as PlasmaComponents3 0018 import org.kde.plasma.extras 2.0 as PlasmaExtras 0019 import org.kde.plasma.private.digitalclock 1.0 0020 import org.kde.config // KAuthorized 0021 import org.kde.kcmutils // KCMUtils 0022 import org.kde.kirigami 2.20 as Kirigami 0023 0024 // Top-level layout containing: 0025 // - Leading column with world clock and agenda view 0026 // - Trailing column with current date header and calendar 0027 // 0028 // Trailing column fills exactly half of the popup width, then there's 1 0029 // logical pixel wide separator, and the rest is left for the Leading. 0030 // Representation's header is intentionally zero-sized, because Calendar view 0031 // brings its own header, and there's currently no other way to stack them. 0032 PlasmaExtras.Representation { 0033 id: calendar 0034 0035 readonly property var appletInterface: root 0036 0037 Kirigami.Theme.colorSet: Kirigami.Theme.Window 0038 Kirigami.Theme.inherit: false 0039 0040 Layout.minimumWidth: (calendar.showAgenda || calendar.showClocks) ? Kirigami.Units.gridUnit * 45 : Kirigami.Units.gridUnit * 22 0041 Layout.maximumWidth: Kirigami.Units.gridUnit * 80 0042 0043 Layout.minimumHeight: Kirigami.Units.gridUnit * 25 0044 Layout.maximumHeight: Kirigami.Units.gridUnit * 40 0045 0046 collapseMarginsHint: true 0047 0048 readonly property int paddings: Kirigami.Units.largeSpacing 0049 readonly property bool showAgenda: eventPluginsManager.enabledPlugins.length > 0 0050 readonly property bool showClocks: Plasmoid.configuration.selectedTimeZones.length > 1 0051 0052 property alias borderWidth: monthView.borderWidth 0053 property alias monthView: monthView 0054 0055 property bool debug: false 0056 0057 Keys.onDownPressed: monthView.Keys.downPressed(event); 0058 0059 Connections { 0060 target: root 0061 0062 function onExpandedChanged() { 0063 // clear all the selections when the plasmoid is showing/hiding 0064 monthView.resetToToday(); 0065 } 0066 } 0067 0068 PlasmaCalendar.EventPluginsManager { 0069 id: eventPluginsManager 0070 enabledPlugins: Plasmoid.configuration.enabledCalendarPlugins 0071 } 0072 0073 // Having this in place helps preserving top margins for Pin and Configure 0074 // buttons somehow. Actual headers are spread across leading and trailing 0075 // columns. 0076 header: Item {} 0077 0078 // Leading column containing agenda view and time zones 0079 // ================================================== 0080 ColumnLayout { 0081 id: leadingColumn 0082 0083 visible: calendar.showAgenda || calendar.showClocks 0084 0085 anchors { 0086 top: parent.top 0087 left: parent.left 0088 right: mainSeparator.left 0089 bottom: parent.bottom 0090 } 0091 0092 spacing: 0 0093 0094 PlasmaExtras.PlasmoidHeading { 0095 Layout.fillWidth: true 0096 Layout.preferredHeight: monthView.viewHeader.height 0097 leftInset: 0 0098 rightInset: 0 0099 0100 // Agenda view header 0101 // ----------------- 0102 contentItem: ColumnLayout { 0103 spacing: 0 0104 0105 Kirigami.Heading { 0106 Layout.alignment: Qt.AlignTop 0107 // Match calendar title 0108 Layout.leftMargin: calendar.paddings 0109 Layout.rightMargin: calendar.paddings 0110 Layout.fillWidth: true 0111 0112 text: monthView.currentDate.toLocaleDateString(Qt.locale(), Locale.LongFormat) 0113 textFormat: Text.PlainText 0114 } 0115 0116 PlasmaComponents3.Label { 0117 visible: monthView.currentDateAuxilliaryText.length > 0 0118 0119 Layout.leftMargin: calendar.paddings 0120 Layout.rightMargin: calendar.paddings 0121 Layout.fillWidth: true 0122 0123 font.pixelSize: Kirigami.Theme.smallFont.pixelSize 0124 text: monthView.currentDateAuxilliaryText 0125 textFormat: Text.PlainText 0126 } 0127 0128 RowLayout { 0129 spacing: Kirigami.Units.smallSpacing 0130 0131 Layout.alignment: Qt.AlignBottom 0132 Layout.bottomMargin: Kirigami.Units.mediumSpacing 0133 0134 // Heading text 0135 Kirigami.Heading { 0136 visible: agenda.visible 0137 0138 Layout.fillWidth: true 0139 Layout.leftMargin: calendar.paddings 0140 Layout.rightMargin: calendar.paddings 0141 0142 level: 2 0143 0144 text: i18n("Events") 0145 textFormat: Text.PlainText 0146 maximumLineCount: 1 0147 elide: Text.ElideRight 0148 } 0149 PlasmaComponents3.ToolButton { 0150 id: addEventButton 0151 0152 visible: agenda.visible && ApplicationIntegration.calendarInstalled 0153 text: i18nc("@action:button Add event", "Add…") 0154 Layout.rightMargin: Kirigami.Units.smallSpacing 0155 icon.name: "list-add" 0156 0157 Accessible.description: i18nc("@info:tooltip", "Add a new event") 0158 KeyNavigation.down: KeyNavigation.tab 0159 KeyNavigation.right: monthView.viewHeader.tabBar 0160 0161 onClicked: ApplicationIntegration.launchCalendar() 0162 KeyNavigation.tab: calendar.showAgenda && holidaysList.count ? holidaysList : holidaysList.KeyNavigation.down 0163 } 0164 } 0165 } 0166 } 0167 0168 // Agenda view itself 0169 Item { 0170 id: agenda 0171 visible: calendar.showAgenda 0172 0173 Layout.fillWidth: true 0174 Layout.fillHeight: true 0175 Layout.minimumHeight: Kirigami.Units.gridUnit * 4 0176 0177 function formatDateWithoutYear(date) { 0178 // Unfortunatelly Qt overrides ECMA's Date.toLocaleDateString(), 0179 // which is able to return locale-specific date-and-month-only date 0180 // formats, with its dumb version that only supports Qt::DateFormat 0181 // enum subset. So to get a day-and-month-only date format string we 0182 // must resort to this magic and hope there are no locales that use 0183 // other separators... 0184 var format = Qt.locale().dateFormat(Locale.ShortFormat).replace(/[./ ]*Y{2,4}[./ ]*/i, ''); 0185 return Qt.formatDate(date, format); 0186 } 0187 0188 function dateEquals(date1, date2) { 0189 const values1 = [ 0190 date1.getFullYear(), 0191 date1.getMonth(), 0192 date1.getDate() 0193 ]; 0194 0195 const values2 = [ 0196 date2.getFullYear(), 0197 date2.getMonth(), 0198 date2.getDate() 0199 ]; 0200 0201 return values1.every((value, index) => { 0202 return (value === values2[index]); 0203 }, false) 0204 } 0205 0206 Connections { 0207 target: monthView 0208 0209 function onCurrentDateChanged() { 0210 // Apparently this is needed because this is a simple QList being 0211 // returned and if the list for the current day has 1 event and the 0212 // user clicks some other date which also has 1 event, QML sees the 0213 // sizes match and does not update the labels with the content. 0214 // Resetting the model to null first clears it and then correct data 0215 // are displayed. 0216 holidaysList.model = null; 0217 holidaysList.model = monthView.daysModel.eventsForDate(monthView.currentDate); 0218 } 0219 } 0220 0221 Connections { 0222 target: monthView.daysModel 0223 0224 function onAgendaUpdated(updatedDate) { 0225 if (agenda.dateEquals(updatedDate, monthView.currentDate)) { 0226 holidaysList.model = null; 0227 holidaysList.model = monthView.daysModel.eventsForDate(monthView.currentDate); 0228 } 0229 } 0230 } 0231 0232 TextMetrics { 0233 id: dateLabelMetrics 0234 0235 // Date/time are arbitrary values with all parts being two-digit 0236 readonly property string timeString: Qt.formatTime(new Date(2000, 12, 12, 12, 12, 12, 12)) 0237 readonly property string dateString: agenda.formatDateWithoutYear(new Date(2000, 12, 12, 12, 12, 12)) 0238 0239 font: Kirigami.Theme.defaultFont 0240 text: timeString.length > dateString.length ? timeString : dateString 0241 } 0242 0243 PlasmaComponents3.ScrollView { 0244 id: holidaysView 0245 anchors.fill: parent 0246 0247 ListView { 0248 id: holidaysList 0249 0250 focus: false 0251 activeFocusOnTab: true 0252 highlight: null 0253 currentIndex: -1 0254 0255 KeyNavigation.down: switchTimeZoneButton.visible ? switchTimeZoneButton : clocksList 0256 Keys.onRightPressed: switchTimeZoneButton.Keys.rightPressed(event); 0257 0258 onCurrentIndexChanged: if (!activeFocus) { 0259 currentIndex = -1; 0260 } 0261 0262 onActiveFocusChanged: if (activeFocus) { 0263 currentIndex = 0; 0264 } else { 0265 currentIndex = -1; 0266 } 0267 0268 delegate: PlasmaComponents3.ItemDelegate { 0269 id: eventItem 0270 width: holidaysList.width 0271 0272 leftPadding: calendar.paddings 0273 0274 text: eventTitle.text 0275 hoverEnabled: true 0276 highlighted: ListView.isCurrentItem 0277 Accessible.description: modelData.description 0278 property bool hasTime: { 0279 // Explicitly all-day event 0280 if (modelData.isAllDay) { 0281 return false; 0282 } 0283 // Multi-day event which does not start or end today (so 0284 // is all-day from today's point of view) 0285 if (modelData.startDateTime - monthView.currentDate < 0 && 0286 modelData.endDateTime - monthView.currentDate > 86400000) { // 24hrs in ms 0287 return false; 0288 } 0289 0290 // Non-explicit all-day event 0291 const startIsMidnight = modelData.startDateTime.getHours() === 0 0292 && modelData.startDateTime.getMinutes() === 0; 0293 0294 const endIsMidnight = modelData.endDateTime.getHours() === 0 0295 && modelData.endDateTime.getMinutes() === 0; 0296 0297 const sameDay = modelData.startDateTime.getDate() === modelData.endDateTime.getDate() 0298 && modelData.startDateTime.getDay() === modelData.endDateTime.getDay() 0299 0300 return !(startIsMidnight && endIsMidnight && sameDay); 0301 } 0302 0303 PlasmaComponents3.ToolTip { 0304 text: modelData.description 0305 visible: text !== "" && eventItem.hovered 0306 } 0307 0308 contentItem: GridLayout { 0309 id: eventGrid 0310 columns: 3 0311 rows: 2 0312 rowSpacing: 0 0313 columnSpacing: Kirigami.Units.largeSpacing 0314 0315 Rectangle { 0316 id: eventColor 0317 0318 Layout.row: 0 0319 Layout.column: 0 0320 Layout.rowSpan: 2 0321 Layout.fillHeight: true 0322 0323 color: modelData.eventColor 0324 width: 5 0325 visible: modelData.eventColor !== "" 0326 } 0327 0328 PlasmaComponents3.Label { 0329 id: startTimeLabel 0330 0331 readonly property bool startsToday: modelData.startDateTime - monthView.currentDate >= 0 0332 readonly property bool startedYesterdayLessThan12HoursAgo: modelData.startDateTime - monthView.currentDate >= -43200000 //12hrs in ms 0333 0334 Layout.row: 0 0335 Layout.column: 1 0336 Layout.minimumWidth: dateLabelMetrics.width 0337 0338 text: startsToday || startedYesterdayLessThan12HoursAgo 0339 ? Qt.formatTime(modelData.startDateTime) 0340 : agenda.formatDateWithoutYear(modelData.startDateTime) 0341 textFormat: Text.PlainText 0342 horizontalAlignment: Qt.AlignRight 0343 visible: eventItem.hasTime 0344 } 0345 0346 PlasmaComponents3.Label { 0347 id: endTimeLabel 0348 0349 readonly property bool endsToday: modelData.endDateTime - monthView.currentDate <= 86400000 // 24hrs in ms 0350 readonly property bool endsTomorrowInLessThan12Hours: modelData.endDateTime - monthView.currentDate <= 86400000 + 43200000 // 36hrs in ms 0351 0352 Layout.row: 1 0353 Layout.column: 1 0354 Layout.minimumWidth: dateLabelMetrics.width 0355 0356 text: endsToday || endsTomorrowInLessThan12Hours 0357 ? Qt.formatTime(modelData.endDateTime) 0358 : agenda.formatDateWithoutYear(modelData.endDateTime) 0359 textFormat: Text.PlainText 0360 horizontalAlignment: Qt.AlignRight 0361 opacity: 0.7 0362 0363 visible: eventItem.hasTime 0364 } 0365 0366 PlasmaComponents3.Label { 0367 id: eventTitle 0368 0369 Layout.row: 0 0370 Layout.column: 2 0371 Layout.fillWidth: true 0372 0373 elide: Text.ElideRight 0374 text: modelData.title 0375 textFormat: Text.PlainText 0376 verticalAlignment: Text.AlignVCenter 0377 maximumLineCount: 2 0378 wrapMode: Text.Wrap 0379 } 0380 } 0381 } 0382 } 0383 } 0384 0385 PlasmaExtras.PlaceholderMessage { 0386 anchors.centerIn: holidaysView 0387 width: holidaysView.width - (Kirigami.Units.gridUnit * 8) 0388 0389 visible: holidaysList.count == 0 0390 0391 iconName: "checkmark" 0392 text: monthView.isToday(monthView.currentDate) ? i18n("No events for today") 0393 : i18n("No events for this day"); 0394 } 0395 } 0396 0397 // Horizontal separator line between events and time zones 0398 KSvg.SvgItem { 0399 visible: worldClocks.visible && agenda.visible 0400 0401 Layout.fillWidth: true 0402 Layout.preferredHeight: naturalSize.height 0403 0404 imagePath: "widgets/line" 0405 elementId: "horizontal-line" 0406 } 0407 0408 // Clocks stuff 0409 // ------------ 0410 // Header text + button to change time & timezone 0411 PlasmaExtras.PlasmoidHeading { 0412 visible: worldClocks.visible 0413 0414 enabledBorders: Qt.TopEdge | Qt.BottomEdge 0415 // Normally gets some positive/negative values from base component. 0416 topInset: 0 0417 topPadding: Kirigami.Units.smallSpacing 0418 0419 leftInset: 0 0420 rightInset: 0 0421 leftPadding: mirrored ? Kirigami.Units.smallSpacing : calendar.paddings 0422 rightPadding: mirrored ? calendar.paddings : Kirigami.Units.smallSpacing 0423 0424 contentItem: RowLayout { 0425 spacing: Kirigami.Units.smallSpacing 0426 0427 Kirigami.Heading { 0428 Layout.fillWidth: true 0429 0430 level: 2 0431 0432 text: i18n("Time Zones") 0433 textFormat: Text.PlainText 0434 maximumLineCount: 1 0435 elide: Text.ElideRight 0436 Accessible.ignored: true 0437 } 0438 0439 PlasmaComponents3.ToolButton { 0440 id: switchTimeZoneButton 0441 0442 visible: KAuthorized.authorizeControlModule("kcm_clock.desktop") 0443 text: i18n("Switch…") 0444 Accessible.name: i18n("Switch to another timezone") 0445 icon.name: "preferences-system-time" 0446 0447 Accessible.description: i18n("Switch to another timezone") 0448 KeyNavigation.down: clocksList 0449 Keys.onRightPressed: monthView.Keys.downPressed(event) 0450 0451 onClicked: KCMLauncher.openSystemSettings("kcm_clock") 0452 0453 PlasmaComponents3.ToolTip { 0454 text: parent.Accessible.description 0455 } 0456 } 0457 } 0458 } 0459 0460 // Clocks view itself 0461 PlasmaComponents3.ScrollView { 0462 id: worldClocks 0463 visible: calendar.showClocks 0464 0465 Layout.fillWidth: true 0466 Layout.fillHeight: !agenda.visible 0467 Layout.minimumHeight: visible ? Kirigami.Units.gridUnit * 7 : 0 0468 Layout.maximumHeight: agenda.visible ? Kirigami.Units.gridUnit * 10 : -1 0469 0470 ListView { 0471 id: clocksList 0472 activeFocusOnTab: true 0473 0474 highlight: null 0475 currentIndex: -1 0476 onActiveFocusChanged: if (activeFocus) { 0477 currentIndex = 0; 0478 } else { 0479 currentIndex = -1; 0480 } 0481 0482 Keys.onRightPressed: switchTimeZoneButton.Keys.rightPressed(event); 0483 0484 // Can't use KeyNavigation.tab since the focus won't go to config button, instead it will be redirected to somewhere else because of 0485 // some existing code. Since now the header was in this file and this was not a problem. Now the header is also implicitly 0486 // inside the monthViewWrapper. 0487 Keys.onTabPressed: { 0488 monthView.viewHeader.configureButton.forceActiveFocus(Qt.BacktabFocusReason); 0489 } 0490 0491 model: { 0492 let timezones = []; 0493 for (let i = 0; i < Plasmoid.configuration.selectedTimeZones.length; i++) { 0494 let thisTzData = Plasmoid.configuration.selectedTimeZones[i]; 0495 0496 /* Don't add this item if it's the same as the local time zone, which 0497 * would indicate that the user has deliberately added a dedicated entry 0498 * for the city of their normal time zone. This is not an error condition 0499 * because the user may have done this on purpose so that their normal 0500 * local time zone shows up automatically while they're traveling and 0501 * they've switched the current local time zone to something else. But 0502 * with this use case, when they're back in their normal local time zone, 0503 * the clocks list would show two entries for the same city. To avoid 0504 * this, let's suppress the duplicate. 0505 */ 0506 if (!(thisTzData !== "Local" && root.nameForZone(thisTzData) === root.nameForZone("Local"))) { 0507 timezones.push(Plasmoid.configuration.selectedTimeZones[i]); 0508 } 0509 } 0510 return timezones; 0511 } 0512 0513 delegate: PlasmaComponents3.ItemDelegate { 0514 id: listItem 0515 readonly property bool isCurrentTimeZone: modelData === Plasmoid.configuration.lastSelectedTimezone 0516 width: ListView.view.width - ListView.view.leftMargin - ListView.view.rightMargin 0517 0518 leftPadding: calendar.paddings 0519 rightPadding: calendar.paddings 0520 0521 highlighted: ListView.isCurrentItem 0522 Accessible.name: root.nameForZone(modelData) 0523 Accessible.description: root.timeForZone(modelData, Plasmoid.configuration.showSeconds === 2) 0524 hoverEnabled: false 0525 0526 contentItem: RowLayout { 0527 PlasmaComponents3.Label { 0528 Layout.fillWidth: true 0529 text: root.nameForZone(modelData) 0530 textFormat: Text.PlainText 0531 font.weight: listItem.isCurrentTimeZone ? Font.Bold : Font.Normal 0532 maximumLineCount: 1 0533 elide: Text.ElideRight 0534 } 0535 0536 PlasmaComponents3.Label { 0537 horizontalAlignment: Qt.AlignRight 0538 text: root.timeForZone(modelData, Plasmoid.configuration.showSeconds === 2) 0539 textFormat: Text.PlainText 0540 font.weight: listItem.isCurrentTimeZone ? Font.Bold : Font.Normal 0541 elide: Text.ElideRight 0542 maximumLineCount: 1 0543 } 0544 } 0545 } 0546 } 0547 } 0548 } 0549 0550 // Vertical separator line between columns 0551 // ======================================= 0552 KSvg.SvgItem { 0553 id: mainSeparator 0554 0555 anchors { 0556 top: parent.top 0557 right: monthViewWrapper.left 0558 bottom: parent.bottom 0559 // Stretch all the way to the top of a dialog. This magic comes 0560 // from PlasmaCore.Dialog::margins and CompactApplet containment. 0561 topMargin: calendar.parent ? -calendar.parent.y : 0 0562 } 0563 0564 width: naturalSize.width 0565 visible: calendar.showAgenda || calendar.showClocks 0566 0567 imagePath: "widgets/line" 0568 elementId: "vertical-line" 0569 } 0570 0571 // Trailing column containing calendar 0572 // =============================== 0573 FocusScope { 0574 id: monthViewWrapper 0575 0576 anchors { 0577 top: parent.top 0578 right: parent.right 0579 bottom: parent.bottom 0580 } 0581 0582 // Not anchoring to horizontalCenter to avoid sub-pixel misalignments 0583 width: (calendar.showAgenda || calendar.showClocks) ? Math.round(parent.width / 2) : parent.width 0584 0585 onActiveFocusChanged: if (activeFocus) { 0586 monthViewWrapper.nextItemInFocusChain().forceActiveFocus(); 0587 } 0588 0589 PlasmaCalendar.MonthView { 0590 id: monthView 0591 0592 anchors { 0593 fill: parent 0594 leftMargin: Kirigami.Units.smallSpacing 0595 rightMargin: Kirigami.Units.smallSpacing 0596 bottomMargin: Kirigami.Units.smallSpacing 0597 } 0598 0599 borderOpacity: 0.25 0600 0601 eventPluginsManager: eventPluginsManager 0602 today: root.tzDate 0603 firstDayOfWeek: Plasmoid.configuration.firstDayOfWeek > -1 0604 ? Plasmoid.configuration.firstDayOfWeek 0605 : Qt.locale().firstDayOfWeek 0606 showWeekNumbers: Plasmoid.configuration.showWeekNumbers 0607 0608 showDigitalClockHeader: true 0609 digitalClock: Plasmoid 0610 eventButton: addEventButton 0611 0612 KeyNavigation.left: KeyNavigation.tab 0613 KeyNavigation.tab: addEventButton.visible ? addEventButton : addEventButton.KeyNavigation.down 0614 Keys.onUpPressed: viewHeader.tabBar.currentItem.forceActiveFocus(Qt.BacktabFocusReason); 0615 } 0616 } 0617 }