Personal noctalia plugins collection
1import QtQuick
2import QtQuick.Controls
3import QtQuick.Layouts
4import Quickshell
5import qs.Commons
6import qs.Services.Location
7import qs.Services.UI
8import qs.Widgets
9
10Item {
11 id: root
12 property var pluginApi: null
13 readonly property var mainInstance: pluginApi?.mainInstance
14 readonly property var geometryPlaceholder: panelContainer
15 property real contentPreferredWidth: 950 * Style.uiScaleRatio
16 property real contentPreferredHeight: 700 * Style.uiScaleRatio
17 property real topHeaderHeight: 60 * Style.uiScaleRatio
18 readonly property bool allowAttach: mainInstance ? mainInstance.panelModeSetting === "attached" : false
19 readonly property bool panelAnchorHorizontalCenter: mainInstance ? mainInstance.panelModeSetting === "centered" : false
20 readonly property bool panelAnchorVerticalCenter: mainInstance ? mainInstance.panelModeSetting === "centered" : false
21 anchors.fill: parent
22
23 property bool showCreateDialog: false
24
25 property real hourHeight: 50 * Style.uiScaleRatio
26 property real timeColumnWidth: 65 * Style.uiScaleRatio
27 property real daySpacing: 1 * Style.uiScaleRatio
28
29 // Panel doesn't need its own CalendarService connection - Main.qml handles it.
30 // When panel opens, trigger a fresh load if needed.
31 Component.onCompleted: mainInstance?.initializePlugin()
32 onVisibleChanged: if (visible && mainInstance) {
33 mainInstance.refreshView()
34 mainInstance.goToToday()
35 Qt.callLater(root.scrollToCurrentTime)
36 }
37
38 // Scroll to time indicator position
39 function scrollToCurrentTime() {
40 if (!mainInstance || !calendarFlickable) return
41 var now = new Date(), today = new Date(now.getFullYear(), now.getMonth(), now.getDate())
42 var weekStart = new Date(mainInstance.weekStart)
43 var weekEnd = new Date(weekStart.getFullYear(), weekStart.getMonth(), weekStart.getDate() + 7)
44
45 if (today >= weekStart && today < weekEnd) {
46 var currentHour = now.getHours() + now.getMinutes() / 60
47 var scrollPos = (currentHour * hourHeight) - (calendarFlickable.height / 2)
48 var maxScroll = Math.max(0, (24 * hourHeight) - calendarFlickable.height)
49 scrollAnim.targetY = Math.max(0, Math.min(scrollPos, maxScroll))
50 scrollAnim.start()
51 }
52 }
53
54 // Event creation dialog
55 Rectangle {
56 id: createEventOverlay
57 anchors.fill: parent
58 color: Qt.rgba(0, 0, 0, 0.5)
59 visible: showCreateDialog
60 z: 2000
61
62 MouseArea { anchors.fill: parent; onClicked: showCreateDialog = false }
63
64 Rectangle {
65 anchors.centerIn: parent
66 width: 400 * Style.uiScaleRatio
67 height: createDialogColumn.implicitHeight + 2 * Style.marginM
68 color: Color.mSurface
69 radius: Style.radiusM
70
71 MouseArea { anchors.fill: parent } // block clicks through
72
73 ColumnLayout {
74 id: createDialogColumn
75 anchors.fill: parent
76 anchors.margins: Style.marginM
77 spacing: Style.marginS
78
79 NText {
80 text: pluginApi.tr("panel.add_event")
81 font.pointSize: Style.fontSizeL; font.weight: Font.Bold
82 color: Color.mOnSurface
83 }
84
85 NText { text: pluginApi.tr("panel.summary"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS }
86 TextField {
87 id: createEventSummary
88 Layout.fillWidth: true
89 placeholderText: pluginApi.tr("panel.summary")
90 color: Color.mOnSurface
91 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS }
92 }
93
94 NText { text: pluginApi.tr("panel.date"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS }
95 TextField {
96 id: createEventDate
97 Layout.fillWidth: true
98 placeholderText: "YYYY-MM-DD"
99 color: Color.mOnSurface
100 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS }
101 }
102
103 RowLayout {
104 spacing: Style.marginS
105 ColumnLayout {
106 Layout.fillWidth: true
107 NText { text: pluginApi.tr("panel.start_time"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS }
108 TextField {
109 id: createEventStartTime
110 Layout.fillWidth: true
111 placeholderText: "HH:MM"
112 color: Color.mOnSurface
113 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS }
114 }
115 }
116 ColumnLayout {
117 Layout.fillWidth: true
118 NText { text: pluginApi.tr("panel.end_time"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS }
119 TextField {
120 id: createEventEndTime
121 Layout.fillWidth: true
122 placeholderText: "HH:MM"
123 color: Color.mOnSurface
124 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS }
125 }
126 }
127 }
128
129 NText { text: pluginApi.tr("panel.location"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS }
130 TextField {
131 id: createEventLocation
132 Layout.fillWidth: true
133 placeholderText: pluginApi.tr("panel.location")
134 color: Color.mOnSurface
135 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS }
136 }
137
138 NText { text: pluginApi.tr("panel.description"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS }
139 TextField {
140 id: createEventDescription
141 Layout.fillWidth: true
142 placeholderText: pluginApi.tr("panel.description")
143 color: Color.mOnSurface
144 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS }
145 }
146
147 NText { text: pluginApi.tr("panel.calendar_select"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS }
148 ComboBox {
149 id: calendarSelector
150 Layout.fillWidth: true
151 model: CalendarService.calendars || []
152 textRole: "name"
153 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS }
154 }
155
156 RowLayout {
157 Layout.fillWidth: true
158 spacing: Style.marginS
159
160 Item { Layout.fillWidth: true }
161
162 Rectangle {
163 Layout.preferredWidth: cancelBtn.implicitWidth + 2 * Style.marginM
164 Layout.preferredHeight: cancelBtn.implicitHeight + Style.marginS
165 color: Color.mSurfaceVariant; radius: Style.radiusS
166 NText {
167 id: cancelBtn; anchors.centerIn: parent
168 text: pluginApi.tr("panel.cancel"); color: Color.mOnSurfaceVariant
169 }
170 MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: showCreateDialog = false }
171 }
172
173 Rectangle {
174 Layout.preferredWidth: createBtn.implicitWidth + 2 * Style.marginM
175 Layout.preferredHeight: createBtn.implicitHeight + Style.marginS
176 color: Color.mPrimary; radius: Style.radiusS
177 opacity: createEventSummary.text.trim() !== "" ? 1.0 : 0.5
178 NText {
179 id: createBtn; anchors.centerIn: parent
180 text: pluginApi.tr("panel.create"); color: Color.mOnPrimary; font.weight: Font.Bold
181 }
182 MouseArea {
183 anchors.fill: parent; cursorShape: Qt.PointingHandCursor
184 onClicked: {
185 if (createEventSummary.text.trim() === "") return
186 var cal = CalendarService.calendars?.[calendarSelector.currentIndex]
187 var calUid = cal?.uid || ""
188 var dateParts = createEventDate.text.split("-")
189 var startParts = createEventStartTime.text.split(":")
190 var endParts = createEventEndTime.text.split(":")
191 var startDate = new Date(parseInt(dateParts[0]), parseInt(dateParts[1])-1, parseInt(dateParts[2]),
192 parseInt(startParts[0]), parseInt(startParts[1]), 0)
193 var endDate = new Date(parseInt(dateParts[0]), parseInt(dateParts[1])-1, parseInt(dateParts[2]),
194 parseInt(endParts[0]), parseInt(endParts[1]), 0)
195 mainInstance?.createEvent(calUid, createEventSummary.text.trim(),
196 Math.floor(startDate.getTime()/1000), Math.floor(endDate.getTime()/1000),
197 createEventLocation.text.trim(), createEventDescription.text.trim())
198 showCreateDialog = false
199 }
200 }
201 }
202 }
203 }
204 }
205 }
206
207 // UI
208 Rectangle {
209 id: panelContainer
210 anchors.fill: parent
211 color: "transparent"
212
213 ColumnLayout {
214 anchors.fill: parent
215 anchors.margins: Style.marginM
216 spacing: Style.marginM
217
218 //Header Section
219 Rectangle {
220 id: header
221 Layout.fillWidth: true
222 Layout.preferredHeight: topHeaderHeight
223 color: Color.mSurfaceVariant
224 radius: Style.radiusM
225
226 RowLayout {
227 anchors.margins: Style.marginM
228 anchors.fill: parent
229
230 NIcon { icon: "calendar-week"; pointSize: Style.fontSizeXXL; color: Color.mPrimary }
231
232 ColumnLayout {
233 Layout.fillHeight: true
234 spacing: 0
235 NText {
236 text: pluginApi.tr("panel.header")
237 font.pointSize: Style.fontSizeL; font.weight: Font.Bold; color: Color.mOnSurface
238 }
239 RowLayout {
240 spacing: Style.marginS
241 NText {
242 text: mainInstance?.monthRangeText || ""
243 font.pointSize: Style.fontSizeS; font.weight: Font.Medium; color: Color.mOnSurfaceVariant
244 }
245 Rectangle {
246 Layout.preferredWidth: 8; Layout.preferredHeight: 8; radius: 4
247 color: mainInstance?.isLoading ? Color.mError :
248 mainInstance?.syncStatus?.includes("No") ? Color.mError : Color.mOnSurfaceVariant
249 }
250 NText {
251 text: mainInstance?.syncStatus || ""
252 font.pointSize: Style.fontSizeS; color: Color.mOnSurfaceVariant
253 }
254 }
255 }
256
257 Item { Layout.fillWidth: true }
258
259 RowLayout {
260 spacing: Style.marginS
261 NIconButton {
262 icon: "plus"; tooltipText: pluginApi.tr("panel.add_event")
263 onClicked: {
264 createEventSummary.text = ""
265 createEventLocation.text = ""
266 createEventDescription.text = ""
267 var now = new Date()
268 var startH = now.getHours() + 1
269 createEventDate.text = now.getFullYear() + "-" + String(now.getMonth()+1).padStart(2,'0') + "-" + String(now.getDate()).padStart(2,'0')
270 createEventStartTime.text = String(startH).padStart(2,'0') + ":00"
271 createEventEndTime.text = String(startH+1).padStart(2,'0') + ":00"
272 showCreateDialog = true
273 }
274 }
275 NIconButton {
276 icon: "chevron-left"
277 onClicked: mainInstance?.navigateWeek(-7)
278 }
279 NIconButton {
280 icon: "calendar"; tooltipText: pluginApi.tr("panel.today")
281 onClicked: { mainInstance?.goToToday(); Qt.callLater(root.scrollToCurrentTime) }
282 }
283 NIconButton {
284 icon: "chevron-right"
285 onClicked: mainInstance?.navigateWeek(7)
286 }
287 NIconButton {
288 icon: "refresh"; tooltipText: I18n.tr("common.refresh")
289 onClicked: mainInstance?.loadEvents()
290 enabled: mainInstance ? !mainInstance.isLoading : false
291 }
292 NIconButton {
293 icon: "close"; tooltipText: I18n.tr("common.close")
294 onClicked: pluginApi.closePanel(pluginApi.panelOpenScreen)
295 }
296 }
297 }
298 }
299
300 // Calendar
301 Rectangle {
302 Layout.fillWidth: true
303 Layout.fillHeight: true
304 color: Color.mSurfaceVariant
305 radius: Style.radiusM
306 clip: true
307
308 Column {
309 anchors.fill: parent
310 spacing: 0
311
312 //Day Headers
313 Rectangle {
314 id: dayHeaders
315 width: parent.width
316 height: 56
317 color: Color.mSurfaceVariant
318 radius: Style.radiusM
319
320 Row {
321 anchors.fill: parent
322 anchors.leftMargin: root.timeColumnWidth
323 spacing: root.daySpacing
324
325 Repeater {
326 model: 7
327 Rectangle {
328 width: mainInstance?.dayColumnWidth
329 height: parent.height
330 color: "transparent"
331 property date dayDate: mainInstance?.weekDates?.[index] || new Date()
332 property bool isToday: {
333 var today = new Date()
334 return dayDate.getDate() === today.getDate() &&
335 dayDate.getMonth() === today.getMonth() &&
336 dayDate.getFullYear() === today.getFullYear()
337 }
338 Rectangle {
339 anchors.fill: parent
340 anchors.margins: 4
341 color: Color.mSurfaceVariant
342 border.color: isToday ? Color.mPrimary : "transparent"
343 border.width: 2
344 radius: Style.radiusM
345 Column {
346 anchors.centerIn: parent
347 spacing: 2
348 NText {
349 anchors.horizontalCenter: parent.horizontalCenter
350 text: dayDate ? I18n.locale.dayName(dayDate.getDay(), Locale.ShortFormat).toUpperCase() : ""
351 color: isToday ? Color.mPrimary : Color.mOnSurface
352 font.pointSize: Style.fontSizeS; font.weight: Font.Medium
353 }
354 NText {
355 anchors.horizontalCenter: parent.horizontalCenter
356 text: dayDate ? ((dayDate.getDate() < 10 ? "0" : "") + dayDate.getDate()) : ""
357 color: isToday ? Color.mPrimary : Color.mOnSurface
358 font.pointSize: Style.fontSizeM; font.weight: Font.Bold
359 }
360 }
361 }
362 }
363 }
364 }
365 }
366 // All-day row
367 Rectangle {
368 id: allDayEventsSection
369 width: parent.width
370 height: mainInstance ? Math.round(mainInstance.allDaySectionHeight * Style.uiScaleRatio) : 0
371 color: Color.mSurfaceVariant
372 visible: height > 0
373
374 Item {
375 id: allDayEventsContainer
376 anchors.fill: parent
377 anchors.leftMargin: root.timeColumnWidth
378
379 Repeater {
380 model: 6
381 delegate: Rectangle {
382 width: 1; height: parent.height
383 x: (index + 1) * ((mainInstance?.dayColumnWidth) + (root.daySpacing)) - ((root.daySpacing) / 2)
384 color: Qt.alpha(mainInstance?.lineColor || Color.mOutline, mainInstance?.dayLineOpacitySetting || 0.9)
385 }
386 }
387
388 Repeater {
389 model: mainInstance?.allDayEventsWithLayout || []
390 delegate: Item {
391 property var eventData: modelData
392 x: eventData.startDay * ((mainInstance?.dayColumnWidth) + (root.daySpacing))
393 y: eventData.lane * 25
394 width: (eventData.spanDays * ((mainInstance?.dayColumnWidth) + (root.daySpacing))) - (root.daySpacing)
395 height: 24
396
397 Rectangle {
398 anchors.fill: parent
399 color: Color.mTertiary
400 radius: Style.radiusS
401 NText {
402 anchors.fill: parent; anchors.margins: 4
403 text: eventData.title
404 color: Color.mOnTertiary
405 font.pointSize: Style.fontSizeXXS; font.weight: Font.Medium
406 elide: Text.ElideRight; verticalAlignment: Text.AlignVCenter
407 }
408 }
409 MouseArea {
410 anchors.fill: parent
411 hoverEnabled: true
412 cursorShape: Qt.PointingHandCursor
413 onEntered: {
414 var tip = mainInstance?.getEventTooltip(eventData) || ""
415 TooltipService.show(parent, tip, "auto", Style.tooltipDelay, Settings.data.ui.fontFixed)
416 }
417 onClicked: mainInstance?.handleEventClick(eventData)
418 onExited: TooltipService.hide()
419 }
420 }
421 }
422 }
423 }
424 // Calendar flickable
425 Rectangle {
426 width: parent.width
427 height: parent.height - dayHeaders.height - (allDayEventsSection.visible ? allDayEventsSection.height : 0)
428 color: Color.mSurfaceVariant
429 radius: Style.radiusM
430 clip: true
431
432 Flickable {
433 id: calendarFlickable
434 anchors.fill: parent
435 clip: true
436 contentHeight: 24 * (root.hourHeight)
437 boundsBehavior: Flickable.DragOverBounds
438
439 Component.onCompleted: {
440 calendarFlickable.forceActiveFocus()
441 }
442
443 // Keyboard interaction
444 Keys.onPressed: function(event) {
445 if (event.key === Qt.Key_Up || event.key === Qt.Key_Down) {
446 var step = root.hourHeight
447 var targetY = event.key === Qt.Key_Up ? Math.max(0, contentY - step) :
448 Math.min(Math.max(0, contentHeight - height), contentY + step)
449 scrollAnim.targetY = targetY
450 scrollAnim.start()
451 event.accepted = true
452 } else if (event.key === Qt.Key_Left || event.key === Qt.Key_Right) {
453 if (mainInstance) {
454 mainInstance.navigateWeek(event.key === Qt.Key_Left ? -7 : 7)
455 }
456 event.accepted = true
457 }
458 }
459
460 NumberAnimation {
461 id: scrollAnim
462 target: calendarFlickable; property: "contentY"; duration: 100
463 easing.type: Easing.OutCubic; property real targetY: 0; to: targetY
464 }
465
466 ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded }
467
468 Row {
469 width: parent.width
470 height: parent.height
471
472 // Time Column
473 Column {
474 width: root.timeColumnWidth
475 height: parent.height
476 Repeater {
477 model: 23
478 Rectangle {
479 width: root.timeColumnWidth
480 height: root.hourHeight
481 color: "transparent"
482 NText {
483 text: {
484 var hour = index + 1
485 if (mainInstance?.use12hourFormat) {
486 var d = new Date(); d.setHours(hour, 0, 0, 0)
487 return mainInstance.formatTime(d)
488 }
489 return (hour < 10 ? "0" : "") + hour + ':00'
490 }
491 anchors.right: parent.right
492 anchors.rightMargin: Style.marginS
493 anchors.verticalCenter: parent.top
494 anchors.verticalCenterOffset: root.hourHeight
495 font.pointSize: Style.fontSizeXS; color: Color.mOnSurfaceVariant
496 }
497 }
498 }
499 }
500
501 // Hour Rectangles
502 Item {
503 width: 7 * ((mainInstance?.dayColumnWidth) + (root.daySpacing))
504 height: parent.height
505
506 Row {
507 anchors.fill: parent
508 spacing: root.daySpacing
509 Repeater {
510 model: 7
511 Column {
512 width: mainInstance?.dayColumnWidth
513 height: parent.height
514 Repeater {
515 model: 24
516 Rectangle { width: parent.width; height: 1; color: Color.mSurfaceVariant }
517 }
518 }
519 }
520 }
521 // Hour Lines
522 Repeater {
523 model: 24
524 Rectangle {
525 width: parent.width; height: 1
526 y: index * (root.hourHeight)
527 color: Qt.alpha(mainInstance?.lineColor || Color.mOutline, mainInstance?.hourLineOpacitySetting || 0.5)
528 }
529 }
530 // Day Lines
531 Repeater {
532 model: 6
533 Rectangle {
534 width: 1; height: parent.height
535 x: (index + 1) * ((mainInstance?.dayColumnWidth) + (root.daySpacing)) - ((root.daySpacing) / 2)
536 color: Qt.alpha(mainInstance?.lineColor || Color.mOutline, mainInstance?.dayLineOpacitySetting || 0.9)
537 }
538 }
539
540 // Event positioning
541 Repeater {
542 model: mainInstance?.eventsModel
543 delegate: Item {
544 property var eventData: model
545 property int dayIndex: mainInstance?.getDisplayDayIndexForDate(model.startTime) ?? -1
546 property real startHour: model.startTime.getHours() + model.startTime.getMinutes() / 60
547 property real endHour: model.endTime.getHours() + model.endTime.getMinutes() / 60
548 property real duration: Math.max(0, (model.endTime - model.startTime) / 3600000)
549
550 property real exactHeight: Math.max(1, duration * (mainInstance?.hourHeight || 50) - 1)
551 property bool isCompact: exactHeight < 40
552 property var overlapInfo: mainInstance?.overlappingEventsData?.[index] ?? {
553 xOffset: 0, width: (mainInstance?.dayColumnWidth) - 8, lane: 0, totalLanes: 1
554 }
555 property real eventWidth: overlapInfo.width - 1
556 property real eventXOffset: overlapInfo.xOffset
557
558 visible: dayIndex >= 0 && dayIndex < 7 && duration > 0
559 width: eventWidth
560 height: exactHeight
561 x: dayIndex * ((mainInstance?.dayColumnWidth) + (root.daySpacing)) + eventXOffset
562 y: startHour * (mainInstance?.hourHeight || 50)
563 z: 100 + overlapInfo.lane
564
565 Rectangle {
566 anchors.fill: parent
567 color: Color.mPrimary
568 radius: Style.radiusS
569 opacity: 0.9
570 clip: true
571 Rectangle {
572 visible: exactHeight < 5 && overlapInfo.lane > 0
573 anchors.fill: parent
574 color: "transparent"
575 radius: parent.radius
576 border.width: 1
577 border.color: Color.mPrimary
578 }
579 Loader {
580 anchors.fill: parent
581 anchors.margins: exactHeight < 10 ? 1 : Style.marginS
582 anchors.leftMargin: exactHeight < 10 ? 1 : Style.marginS + 3
583 sourceComponent: isCompact ? compactLayout : normalLayout
584 }
585 }
586
587 Component {
588 id: normalLayout
589 Column {
590 spacing: 2
591 width: parent.width - 3
592 NText {
593 visible: exactHeight >= 20
594 text: model.title
595 color: Color.mOnPrimary
596 font.pointSize: Style.fontSizeXS; font.weight: Font.Medium
597 elide: Text.ElideRight; width: parent.width
598 }
599 NText {
600 visible: exactHeight >= 30
601 text: mainInstance?.formatTimeRangeForDisplay(model) || ""
602 color: Color.mOnPrimary
603 font.pointSize: Style.fontSizeXXS; opacity: 0.9
604 elide: Text.ElideRight; width: parent.width
605 }
606 NText {
607 visible: exactHeight >= 45 && model.location && model.location !== ""
608 text: "\u26B2 " + (model.location || "")
609 color: Color.mOnPrimary
610 font.pointSize: Style.fontSizeXXS; opacity: 0.8
611 elide: Text.ElideRight; width: parent.width
612 }
613 }
614 }
615
616 Component {
617 id: compactLayout
618 NText {
619 text: exactHeight < 15 ? model.title :
620 model.title + " • " + (mainInstance?.formatTimeRangeForDisplay(model) || "")
621 color: Color.mOnPrimary
622 font.pointSize: exactHeight < 15 ? Style.fontSizeXXS : Style.fontSizeXS
623 font.weight: Font.Medium
624 elide: Text.ElideRight; verticalAlignment: Text.AlignVCenter
625 width: parent.width - 3
626 }
627 }
628
629 MouseArea {
630 anchors.fill: parent
631 hoverEnabled: true
632 cursorShape: Qt.PointingHandCursor
633 onEntered: {
634 var tip = mainInstance?.getEventTooltip(model) || ""
635 TooltipService.show(parent, tip, "auto", Style.tooltipDelay, Settings.data.ui.fontFixed)
636 }
637 onClicked: mainInstance?.handleEventClick(eventData)
638 onExited: TooltipService.hide()
639 }
640 }
641 }
642
643 // Time Indicator
644 Rectangle {
645 property var now: new Date()
646 property date today: new Date(now.getFullYear(), now.getMonth(), now.getDate())
647 property date weekStartDate: mainInstance?.weekStart ?? new Date()
648 property date weekEndDate: mainInstance ?
649 new Date(mainInstance.weekStart.getFullYear(), mainInstance.weekStart.getMonth(), mainInstance.weekStart.getDate() + 7) : new Date()
650 property bool inCurrentWeek: today >= weekStartDate && today < weekEndDate
651 property int currentDay: mainInstance?.getDayIndexForDate(now) ?? -1
652 property real currentHour: now.getHours() + now.getMinutes() / 60
653
654 visible: inCurrentWeek && currentDay >= 0
655 width: mainInstance?.dayColumnWidth
656 height: 2
657 x: currentDay * ((mainInstance?.dayColumnWidth) + (root.daySpacing))
658 y: currentHour * (root.hourHeight)
659 color: Color.mError
660 radius: 1
661 z: 1000
662 Rectangle {
663 width: 8; height: 8; radius: 4; color: Color.mError
664 anchors.verticalCenter: parent.verticalCenter; x: -4
665 }
666 Timer {
667 interval: 60000; running: true; repeat: true
668 onTriggered: parent.now = new Date()
669 }
670 }
671 }
672 }
673 }
674 }
675 }
676 }
677 }
678 }
679}