WeekView.qml 52.8 KB
Newer Older
Claudio Cambra's avatar
Claudio Cambra committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// SPDX-FileCopyrightText: 2021 Claudio Cambra <claudio.cambra@gmail.com>
// SPDX-License-Identifier: GPL-2.0-or-later

import QtQuick 2.15
import QtQuick.Layouts 1.1
import QtQuick.Controls 2.15 as QQC2
import org.kde.kirigami 2.14 as Kirigami
import QtGraphicalEffects 1.12

import org.kde.kalendar 1.0 as Kalendar
import "dateutils.js" as DateUtils
import "labelutils.js" as LabelUtils

Kirigami.Page {
    id: root

    signal addIncidence(int type, date addDate, bool includeTime)
    signal viewIncidence(var modelData, var collectionData)
    signal editIncidence(var incidencePtr, var collectionId)
    signal deleteIncidence(var incidencePtr, date deleteDate)
    signal completeTodo(var incidencePtr)
    signal addSubTodo(var parentWrapper)
23
    signal deselect()
Claudio Cambra's avatar
Claudio Cambra committed
24
25

    property var openOccurrence: {}
26
27
    property var model

Claudio Cambra's avatar
Claudio Cambra committed
28
29
30
31
32
33
34
35
36
37
38
    property date selectedDate: new Date()
    property date startDate: DateUtils.getFirstDayOfMonth(selectedDate)
    property date currentDate: new Date() // Needs to get updated for marker to move, done from main.qml
    readonly property int currentDay: currentDate ? currentDate.getDate() : null
    readonly property int currentMonth: currentDate ? currentDate.getMonth() : null
    readonly property int currentYear: currentDate ? currentDate.getFullYear() : null
    property int day: selectedDate.getDate()
    property int month: selectedDate.getMonth()
    property int year: selectedDate.getFullYear()
    property bool initialWeek: true
    property int daysToShow: 7
39
    readonly property int minutesFromStartOfDay: (root.currentDate.getHours() * 60) + root.currentDate.getMinutes()
Claudio Cambra's avatar
Claudio Cambra committed
40
41
42
43
44
45
    readonly property bool isDark: LabelUtils.isDarkColor(Kirigami.Theme.backgroundColor)

    property real scrollbarWidth: 0
    readonly property real dayWidth: ((root.width - hourLabelWidth - leftPadding - scrollbarWidth) / daysToShow) - gridLineWidth
    readonly property real incidenceSpacing: Kirigami.Units.smallSpacing / 2
    readonly property real gridLineWidth: 1.0
46
47
    readonly property real hourLabelWidth: hourLabelMetrics.boundingRect(new Date(0,0,0,0,0,0,0).toLocaleTimeString(Qt.locale(), Locale.NarrowFormat)).width +
        Kirigami.Units.largeSpacing * 2.5
48
    readonly property real periodHeight: Kirigami.Units.gridUnit / 2
Claudio Cambra's avatar
Claudio Cambra committed
49
50
51
52

    Kirigami.Theme.inherit: false
    Kirigami.Theme.colorSet: Kirigami.Theme.View

53
54
55
56
57
    FontMetrics {
        id: hourLabelMetrics
        font.bold: true
    }

Claudio Cambra's avatar
Claudio Cambra committed
58
59
60
61
62
63
    background: Rectangle {
        color: Kirigami.Theme.backgroundColor
    }

    function setToDate(date, isInitialWeek = false) {
        root.initialWeek = isInitialWeek;
64

Claudio Cambra's avatar
Claudio Cambra committed
65
66
67
68
        date = DateUtils.getFirstDayOfWeek(date);
        const weekDiff = Math.round((date - pathView.currentItem.startDate) / (root.daysToShow * 24 * 60 * 60 * 1000));

        let newIndex = pathView.currentIndex + weekDiff;
69
70
        let firstItemDate = pathView.model.data(pathView.model.index(1,0), Kalendar.InfiniteCalendarViewModel.StartDateRole);
        let lastItemDate = pathView.model.data(pathView.model.index(pathView.model.rowCount() - 1,0), Kalendar.InfiniteCalendarViewModel.StartDateRole);
Claudio Cambra's avatar
Claudio Cambra committed
71
72
73

        while(firstItemDate >= date) {
            pathView.model.addDates(false)
74
            firstItemDate = pathView.model.data(pathView.model.index(1,0), Kalendar.InfiniteCalendarViewModel.StartDateRole);
Claudio Cambra's avatar
Claudio Cambra committed
75
76
77
78
79
80
81
82
            newIndex = 0;
        }
        if(firstItemDate < date && newIndex === 0) {
            newIndex = Math.round((date - firstItemDate) / (root.daysToShow * 24 * 60 * 60 * 1000)) + 1
        }

        while(lastItemDate <= date) {
            pathView.model.addDates(true)
83
            lastItemDate = pathView.model.data(pathView.model.index(pathView.model.rowCount() - 1,0), Kalendar.InfiniteCalendarViewModel.StartDateRole);
Claudio Cambra's avatar
Claudio Cambra committed
84
85
86
        }
        pathView.currentIndex = newIndex;
        selectedDate = date;
87
88
89
90

        if(isInitialWeek) {
            pathView.currentItem.item.hourScrollView.setToCurrentTime();
        }
Claudio Cambra's avatar
Claudio Cambra committed
91
    }
92
93
94
95
96
97
98
99
100
101
102
103
104
105
    readonly property Kirigami.Action previousAction: Kirigami.Action {
        icon.name: "go-previous"
        text: i18n("Previous Week")
        shortcut: "Left"
        onTriggered: setToDate(DateUtils.addDaysToDate(pathView.currentItem.startDate, -root.daysToShow))
        displayHint: Kirigami.DisplayHint.IconOnly
    }
    readonly property Kirigami.Action nextAction: Kirigami.Action {
        icon.name: "go-next"
        text: i18n("Next Week")
        shortcut: "Right"
        onTriggered: setToDate(DateUtils.addDaysToDate(pathView.currentItem.startDate, root.daysToShow))
        displayHint: Kirigami.DisplayHint.IconOnly
    }
106
107
108
    readonly property Kirigami.Action todayAction: Kirigami.Action {
        icon.name: "go-jump-today"
        text: i18n("Today")
109
        onTriggered: setToDate(new Date(), true);
110
    }
Claudio Cambra's avatar
Claudio Cambra committed
111
112

    actions {
113
114
        left: Qt.application.layoutDirection === Qt.RightToLeft ? nextAction : previousAction
        right: Qt.application.layoutDirection === Qt.RightToLeft ? previousAction : nextAction
115
        main: todayAction
Claudio Cambra's avatar
Claudio Cambra committed
116
117
118
119
    }

    padding: 0

120
121
122
123
    FontMetrics {
        id: fontMetrics
    }

Claudio Cambra's avatar
Claudio Cambra committed
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
    PathView {
        id: pathView

        anchors.fill: parent
        flickDeceleration: Kirigami.Units.longDuration
        preferredHighlightBegin: 0.5
        preferredHighlightEnd: 0.5
        snapMode: PathView.SnapToItem
        focus: true
        interactive: Kirigami.Settings.tabletMode

        path: Path {
            startX: - pathView.width * pathView.count / 2 + pathView.width / 2
            startY: pathView.height / 2
            PathLine {
                x: pathView.width * pathView.count / 2 + pathView.width / 2
                y: pathView.height / 2
            }
        }

144
        model: root.model
Claudio Cambra's avatar
Claudio Cambra committed
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167

        property date dateToUse
        property int startIndex
        Component.onCompleted: {
            startIndex = count / 2;
            currentIndex = startIndex;
        }
        onCurrentIndexChanged: {
            root.startDate = currentItem.startDate;
            root.month = currentItem.month;
            root.year = currentItem.year;

            if(currentIndex >= count - 2) {
                model.addDates(true);
            } else if (currentIndex <= 1) {
                model.addDates(false);
                startIndex += model.weeksToAdd;
            }
        }

        delegate: Loader {
            id: viewLoader

168
169
170
            readonly property date startDate: model.startDate
            readonly property int month: model.selectedMonth - 1 // Convert QDateTime month to JS month
            readonly property int year: model.selectedYear
Claudio Cambra's avatar
Claudio Cambra committed
171

172
173
174
            readonly property int index: model.index
            readonly property bool isCurrentItem: PathView.isCurrentItem
            readonly property bool isNextOrCurrentItem: index >= pathView.currentIndex -1 && index <= pathView.currentIndex + 1
175
            property int multiDayLinesShown: 0
Claudio Cambra's avatar
Claudio Cambra committed
176

177
            readonly property int daysFromWeekStart: DateUtils.fullDaysBetweenDates(startDate, root.currentDate) - 1
178
            // As long as the date is even slightly larger, it will return 1; since we start from the startDate at 00:00, adjust
179

Claudio Cambra's avatar
Claudio Cambra committed
180
            active: isNextOrCurrentItem
181
182
            asynchronous: !isCurrentItem
            visible: status === Loader.Ready
183
184
            sourceComponent: Column {
                id: viewColumn
Claudio Cambra's avatar
Claudio Cambra committed
185
186
187
188
                width: pathView.width
                height: pathView.height
                spacing: 0

189
190
                readonly property alias hourScrollView: hourlyView

Claudio Cambra's avatar
Claudio Cambra committed
191
                Row {
192
193
                    id: headingRow
                    width: pathView.width
Claudio Cambra's avatar
Claudio Cambra committed
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
                    spacing: root.gridLineWidth

                    Kirigami.Heading {
                        id: weekNumberHeading

                        width: root.hourLabelWidth - root.gridLineWidth
                        horizontalAlignment: Text.AlignRight
                        padding: Kirigami.Units.smallSpacing
                        level: 2
                        text: DateUtils.getWeek(viewLoader.startDate, Qt.locale().firstDayOfWeek)
                        color: Kirigami.Theme.disabledTextColor
                        background: Rectangle {
                            color: Kirigami.Theme.backgroundColor
                        }
                    }

                    Repeater {
                        id: dayHeadings

213
                        model: weekViewModel.rowCount()
Claudio Cambra's avatar
Claudio Cambra committed
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
                        delegate: Kirigami.Heading {
                            id: dayHeading

                            property date headingDate: DateUtils.addDaysToDate(viewLoader.startDate, index)
                            property bool isToday: headingDate.getDate() === root.currentDay &&
                                                   headingDate.getMonth() === root.currentMonth &&
                                                   headingDate.getFullYear() === root.currentYear
                            width: root.dayWidth
                            horizontalAlignment: Text.AlignRight
                            padding: Kirigami.Units.smallSpacing
                            level: 2
                            color: isToday ? Kirigami.Theme.highlightColor : Kirigami.Theme.textColor
                            text: {
                                const longText = headingDate.toLocaleDateString(Qt.locale(), "dddd <b>dd</b>");
                                const mediumText = headingDate.toLocaleDateString(Qt.locale(), "ddd <b>dd</b>");
                                const shortText = mediumText.slice(0,1) + " " + headingDate.toLocaleDateString(Qt.locale(), "<b>dd</b>");


232
                                if(fontMetrics.boundingRect(longText).width < width) {
Claudio Cambra's avatar
Claudio Cambra committed
233
                                    return longText;
234
                                } else if(fontMetrics.boundingRect(mediumText).width < width) {
Claudio Cambra's avatar
Claudio Cambra committed
235
236
237
238
239
240
241
242
243
244
                                    return mediumText;
                                } else {
                                    return shortText;
                                }
                            }
                            background: Rectangle {
                                color: dayHeading.isToday ? Kirigami.Theme.activeBackgroundColor : Kirigami.Theme.backgroundColor
                            }
                        }
                    }
245
246
247
248
249
                    Rectangle { // Cover up the shadow of headerTopSeparator above the scrollbar
                        color: Kirigami.Theme.backgroundColor
                        height: parent.height
                        width: root.scrollbarWidth
                    }
Claudio Cambra's avatar
Claudio Cambra committed
250
251
252
253
                }

                Kirigami.Separator {
                    id: headerTopSeparator
254
                    width: pathView.width
Claudio Cambra's avatar
Claudio Cambra committed
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
                    height: root.gridLineWidth
                    z: -1

                    RectangularGlow {
                        anchors.fill: parent
                        z: -1
                        glowRadius: 5
                        spread: 0.3
                        color: Qt.rgba(0.0, 0.0, 0.0, 0.15)
                        visible: !allDayViewLoader.active
                    }
                }

                Item {
                    id: allDayHeader
270
271
                    width: pathView.width
                    height: actualHeight
Claudio Cambra's avatar
Claudio Cambra committed
272
                    visible: allDayViewLoader.active
273
274
275
276
277
278
279
280
281
282
283
284

                    readonly property int minHeight: Kirigami.Units.gridUnit *2
                    readonly property int maxHeight: pathView.height / 3
                    readonly property int lineHeight: viewLoader.multiDayLinesShown * (Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing + root.incidenceSpacing) + Kirigami.Units.smallSpacing
                    readonly property int defaultHeight: Math.min(lineHeight, maxHeight)
                    property int actualHeight: {
                        if (Kalendar.Config.weekViewAllDayHeaderHeight === -1) {
                            return defaultHeight;
                        } else {
                            return Kalendar.Config.weekViewAllDayHeaderHeight;
                        }
                    }
Claudio Cambra's avatar
Claudio Cambra committed
285

286
287
288
289
290
291
292
293
294
295
296
297
298
299
                    NumberAnimation {
                        id: resetAnimation
                        target: allDayHeader
                        property: "height"
                        to: allDayHeader.defaultHeight
                        duration: Kirigami.Units.longDuration
                        easing.type: Easing.InOutQuad
                        onFinished: {
                            Kalendar.Config.weekViewAllDayHeaderHeight = -1;
                            Kalendar.Config.save();
                            allDayHeader.actualHeight = allDayHeader.defaultHeight;
                        }
                    }

Claudio Cambra's avatar
Claudio Cambra committed
300
301
302
303
304
305
                    Rectangle {
                        id: headerBackground
                        anchors.fill: parent
                        color: Kirigami.Theme.backgroundColor
                    }

306
307
308
309
                    Kirigami.ShadowedRectangle {
                        anchors.left: parent.left
                        anchors.top: parent.bottom
                        width: root.hourLabelWidth
310
311
312
                        height: Kalendar.Config.weekViewAllDayHeaderHeight !== -1 ?
                            resetHeaderHeightButton.height :
                            0
313
314
315
316
317
318
319
320
321
322
                        z: -1
                        corners.bottomRightRadius: Kirigami.Units.smallSpacing
                        shadow.size: Kirigami.Units.largeSpacing
                        shadow.color: Qt.rgba(0.0, 0.0, 0.0, 0.2)
                        shadow.yOffset: 2
                        shadow.xOffset: 2
                        color: Kirigami.Theme.backgroundColor
                        border.width: root.gridLineWidth
                        border.color: headerBottomSeparator.color

323
324
325
326
327
328
                        Behavior on height { NumberAnimation {
                            duration: Kirigami.Units.shortDuration
                            easing.type: Easing.InOutQuad
                        } }

                        Item {
329
                            width: root.hourLabelWidth
330
331
332
333
334
335
336
337
                            height: parent.height
                            clip: true

                            QQC2.ToolButton {
                                id: resetHeaderHeightButton
                                width: root.hourLabelWidth
                                text: i18nc("@action:button", "Reset")
                                onClicked: resetAnimation.start()
338
339
340
341
                            }
                        }
                    }

Claudio Cambra's avatar
Claudio Cambra committed
342
343
                    QQC2.Label {
                        width: root.hourLabelWidth
344
                        height: parent.height
Claudio Cambra's avatar
Claudio Cambra committed
345
346
347
348
                        padding: Kirigami.Units.smallSpacing
                        leftPadding: Kirigami.Units.largeSpacing
                        verticalAlignment: Text.AlignTop
                        horizontalAlignment: Text.AlignRight
349
                        text: i18n("All day or Multi day")
Claudio Cambra's avatar
Claudio Cambra committed
350
                        wrapMode: Text.Wrap
351
352
                        elide: Text.ElideRight
                        font: Kirigami.Theme.smallFont
Claudio Cambra's avatar
Claudio Cambra committed
353
354
355
356
357
358
359
                        color: Kirigami.Theme.disabledTextColor
                    }

                    Loader {
                        id: allDayViewLoader
                        anchors.fill: parent
                        anchors.leftMargin: root.hourLabelWidth
360
                        active: weekViewMultiDayViewModel.incidenceCount > 0
Claudio Cambra's avatar
Claudio Cambra committed
361
                        sourceComponent: Item {
362
363
                            id: allDayViewItem
                            implicitHeight: allDayHeader.actualHeight
Claudio Cambra's avatar
Claudio Cambra committed
364
365
366
                            clip: true

                            Repeater {
367
                                model: weekViewMultiDayViewModel // from root.model
Claudio Cambra's avatar
Claudio Cambra committed
368
369
370
                                Layout.topMargin: Kirigami.Units.largeSpacing
                                //One row => one week
                                Item {
371
                                    id: weekItem
Claudio Cambra's avatar
Claudio Cambra committed
372
                                    width: parent.width
373
                                    implicitHeight: allDayHeader.actualHeight
Claudio Cambra's avatar
Claudio Cambra committed
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
                                    clip: true
                                    RowLayout {
                                        width: parent.width
                                        height: parent.height
                                        spacing: root.gridLineWidth
                                        Item {
                                            id: dayDelegate
                                            Layout.fillWidth: true
                                            Layout.fillHeight: true
                                            readonly property date startDate: periodStartDate

                                            QQC2.ScrollView {
                                                id: linesListViewScrollView
                                                anchors {
                                                    fill: parent
                                                }

                                                QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff

                                                ListView {
                                                    id: linesRepeater
                                                    Layout.fillWidth: true
396
                                                    Layout.rightMargin: spacing
Claudio Cambra's avatar
Claudio Cambra committed
397
398
399
400
401
402
403
404
405
406
407
408
409

                                                    clip: true
                                                    spacing: root.incidenceSpacing

                                                    ListView {
                                                        id: allDayIncidencesBackgroundView
                                                        anchors.fill: parent
                                                        spacing: root.gridLineWidth
                                                        orientation: Qt.Horizontal
                                                        z: -1

                                                        Kirigami.Separator {
                                                            anchors.fill: parent
410
                                                            anchors.rightMargin: root.scrollbarWidth
Claudio Cambra's avatar
Claudio Cambra committed
411
412
413
414
415
416
417
418
419
420
421
422
423
424
                                                            z: -1
                                                        }

                                                        model: root.daysToShow
                                                        delegate: Rectangle {
                                                            id: multiDayViewBackground

                                                            readonly property date date: DateUtils.addDaysToDate(viewLoader.startDate, index)
                                                            readonly property bool isToday: date.getDate() === root.currentDay &&
                                                                date.getMonth() === root.currentMonth &&
                                                                date.getFullYear() === root.currentYear

                                                            width: root.dayWidth
                                                            height: linesListViewScrollView.height
425
426
                                                            color: incidenceDropArea.containsDrag ?  Kirigami.Theme.positiveBackgroundColor :
                                                                isToday ? Kirigami.Theme.activeBackgroundColor : Kirigami.Theme.backgroundColor
Claudio Cambra's avatar
Claudio Cambra committed
427
428
429
430
431
432
433

                                                            DayMouseArea {
                                                                id: listViewMenu
                                                                anchors.fill: parent

                                                                addDate: parent.date
                                                                onAddNewIncidence: root.addIncidence(type, addDate, false)
434
                                                                onDeselect: root.deselect()
435
436
437
438
439

                                                                DropArea {
                                                                    id: incidenceDropArea
                                                                    anchors.fill: parent
                                                                    z: 9999
440
441
442
443
444
445
                                                                    onDropped: if(viewLoader.isCurrentItem) {
                                                                        const pos = mapToItem(root, x, y);
                                                                        drop.source.caughtX = pos.x + root.incidenceSpacing;
                                                                        drop.source.caughtY = pos.y;
                                                                        drop.source.caught = true;

446
447
448
449
450
451
452
                                                                        const incidenceWrapper = Qt.createQmlObject('import org.kde.kalendar 1.0; IncidenceWrapper {id: incidence}', incidenceDropArea, "incidence");
                                                                        incidenceWrapper.incidencePtr = drop.source.incidencePtr;
                                                                        incidenceWrapper.collectionId = drop.source.collectionId;
                                                                        incidenceWrapper.setIncidenceStartDate(listViewMenu.addDate.getDate(), listViewMenu.addDate.getMonth() + 1, listViewMenu.addDate.getFullYear());
                                                                        Kalendar.CalendarManager.editIncidence(incidenceWrapper);
                                                                    }
                                                                }
Claudio Cambra's avatar
Claudio Cambra committed
453
454
455
456
457
                                                            }
                                                        }
                                                    }

                                                    model: incidences
458
459
460
                                                    onCountChanged: {
                                                        viewLoader.multiDayLinesShown = count
                                                    }
Claudio Cambra's avatar
Claudio Cambra committed
461
462
463
464
465
466
467
468
469

                                                    delegate: Item {
                                                        id: line
                                                        height: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing

                                                        //Incidences
                                                        Repeater {
                                                            id: incidencesRepeater
                                                            model: modelData
470
                                                            MultiDayViewIncidenceDelegate {
471
                                                                id: incidenceDelegate
472
                                                                dayWidth: root.dayWidth
473
                                                                height: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing
474
475
476
477
478
                                                                parentViewSpacing: root.gridLineWidth
                                                                horizontalSpacing: linesRepeater.spacing
                                                                openOccurrenceId: root.openOccurrence ? root.openOccurrence.incidenceId : ""
                                                                isDark: root.isDark
                                                                reactToCurrentMonth: false
479
480
481

                                                                Drag.active: mouseArea.drag.active

482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
                                                                states: [
                                                                    State {
                                                                        when: incidenceDelegate.mouseArea.drag.active
                                                                        ParentChange { target: incidenceDelegate; parent: root }
                                                                        PropertyChanges { target: incidenceDelegate; isOpenOccurrence: true }
                                                                    },
                                                                    State {
                                                                        when: incidenceDelegate.caught
                                                                        ParentChange { target: incidenceDelegate; parent: root }
                                                                        PropertyChanges {
                                                                            target: incidenceDelegate
                                                                            repositionAnimationEnabled: true
                                                                            x: caughtX
                                                                            y: caughtY
                                                                        }
497
                                                                    }
498
                                                                ]
Claudio Cambra's avatar
Claudio Cambra committed
499
500
501
502
503
504
505
506
507
508
509
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541

                    MouseArea {
                        anchors.left: parent.left
                        anchors.bottom: parent.bottom
                        anchors.right: parent.right
                        height: 5
                        z: Infinity
                        cursorShape: !Kirigami.Settings.isMobile ? Qt.SplitVCursor : undefined
                        preventStealing: true
                        enabled: true
                        visible: true
                        onPressed: {
                            _lastY = mapToGlobal(mouseX, mouseY).y;
                            if(Kalendar.Config.weekViewAllDayHeaderHeight === -1) {
                                // Stops shrink on first drag
                                Kalendar.Config.weekViewAllDayHeaderHeight = allDayHeader.defaultHeight;
                            }
                        }
                        onReleased: {
                            Kalendar.Config.weekViewAllDayHeaderHeight = allDayHeader.actualHeight;
                            Kalendar.Config.save();
                        }
                        property real _lastY: -1

                        onPositionChanged: {
                            if (_lastY === -1) {
                                return;
                            } else {
                                allDayHeader.actualHeight = Math.min(allDayHeader.maxHeight, Math.max(allDayHeader.minHeight, Kalendar.Config.weekViewAllDayHeaderHeight - _lastY + mapToGlobal(mouseX, mouseY).y))
                            }
                        }
                    }
Claudio Cambra's avatar
Claudio Cambra committed
542
543
544
545
                }

                Kirigami.Separator {
                    id: headerBottomSeparator
546
                    width: pathView.width
Claudio Cambra's avatar
Claudio Cambra committed
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
                    height: root.gridLineWidth
                    z: -1
                    visible: allDayViewLoader.active

                    RectangularGlow {
                        anchors.fill: parent
                        z: -1
                        glowRadius: 5
                        spread: 0.3
                        color: Qt.rgba(0.0, 0.0, 0.0, 0.15)
                    }
                }

                QQC2.ScrollView {
                    id: hourlyView
562
563
                    width: viewColumn.width
                    height: viewColumn.height - headerBottomSeparator.height - allDayHeader.height - headerTopSeparator.height - headingRow.height
Claudio Cambra's avatar
Claudio Cambra committed
564
565
566
567
                    contentWidth: availableWidth
                    z: -2
                    QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff

568
569
570
571
                    readonly property real periodsPerHour: 60 / weekViewModel.periodLength
                    readonly property real daySections: (60 * 24) / weekViewModel.periodLength
                    readonly property real dayHeight: (daySections * root.periodHeight) + (root.gridLineWidth * 23)
                    readonly property real hourHeight: periodsPerHour * root.periodHeight
Claudio Cambra's avatar
Claudio Cambra committed
572
                    readonly property real minuteHeight: hourHeight / 60
573
                    readonly property Item vScrollBar: QQC2.ScrollBar.vertical
Claudio Cambra's avatar
Claudio Cambra committed
574

575
576
577
578
579
580
581
                    function setToCurrentTime() {
                        if(currentTimeMarkerLoader.active) {
                            const viewHeight = (applicationWindow().height - applicationWindow().pageStack.globalToolBar.height - headerBottomSeparator.height - allDayHeader.height - headerTopSeparator.height - headingRow.height - Kirigami.Units.gridUnit);
                            // Since we position with anchors, height is 0 -- must calc manually

                            let yPos = (currentTimeMarkerLoader.item.y / dayHeight) - ((viewHeight / 2) / dayHeight)
                            yPos = Math.max(0.0, yPos);
582
                            yPos = vScrollBar.size ? Math.min(vScrollBar.size, yPos) : Math.min(1.0, yPos);
583
584
585
586
587

                            vScrollBar.position = yPos;
                        }
                    }

Claudio Cambra's avatar
Claudio Cambra committed
588
589
590
                    Connections {
                        target: hourlyView.QQC2.ScrollBar.vertical
                        function onWidthChanged() {
591
                            if(!Kirigami.Settings.isMobile) root.scrollbarWidth = hourlyView.QQC2.ScrollBar.vertical.width;
Claudio Cambra's avatar
Claudio Cambra committed
592
593
                        }
                    }
594
                    Component.onCompleted: {
595
                        if(!Kirigami.Settings.isMobile) root.scrollbarWidth = hourlyView.QQC2.ScrollBar.vertical.width;
596
597
                        if(currentTimeMarkerLoader.active && root.initialWeek) {
                            setToCurrentTime();
598
                        }
599
                    }
Claudio Cambra's avatar
Claudio Cambra committed
600
601
602

                    Item {
                        id: hourlyViewContents
603
604
                        width: parent.width
                        implicitHeight: hourlyView.dayHeight
Claudio Cambra's avatar
Claudio Cambra committed
605
606
607

                        clip: true

608
                        Item {
Claudio Cambra's avatar
Claudio Cambra committed
609
                            id: hourLabelsColumn
610

611
612
613
614
615
616
                            property real currentTimeLabelTop: currentTimeLabelLoader.active ?
                                currentTimeLabelLoader.item.y
                                : 0
                            property real currentTimeLabelBottom: currentTimeLabelLoader.active ?
                                currentTimeLabelLoader.item.y + fontMetrics.height
                                : 0
617

Claudio Cambra's avatar
Claudio Cambra committed
618
619
620
621
622
                            anchors.left: parent.left
                            anchors.top: parent.top
                            anchors.bottom: parent.bottom
                            width: root.hourLabelWidth

623
624
625
                            Loader {
                                id: currentTimeLabelLoader

626
                                active: currentTimeMarkerLoader.active
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
                                sourceComponent: QQC2.Label {
                                    id: currentTimeLabel

                                    width: root.hourLabelWidth
                                    color: Kirigami.Theme.highlightColor
                                    font.weight: Font.DemiBold
                                    horizontalAlignment: Text.AlignRight
                                    rightPadding: Kirigami.Units.smallSpacing
                                    y: Math.max(0, (root.currentDate.getHours() * root.gridLineWidth) + (hourlyView.minuteHeight * root.minutesFromStartOfDay) - (implicitHeight / 2)) - (root.gridLineWidth / 2)
                                    z: 100

                                    text: root.currentDate.toLocaleTimeString(Qt.locale(), Locale.NarrowFormat)

                                }
                            }

643
644
                            Repeater {
                                model: pathView.model.weekViewLocalisedHourLabels // Not a model role but instead one of the model object's properties
645

646
                                delegate: QQC2.Label {
647
648
                                    property real textYTop: y
                                    property real textYBottom: y + fontMetrics.height
649
                                    property bool overlapWithCurrentTimeLabel: currentTimeLabelLoader.active &&
650
651
652
                                        ((hourLabelsColumn.currentTimeLabelTop <= textYTop && hourLabelsColumn.currentTimeLabelBottom >= textYTop) ||
                                        (hourLabelsColumn.currentTimeLabelTop < textYBottom && hourLabelsColumn.currentTimeLabelBottom > textYBottom) ||
                                        (hourLabelsColumn.currentTimeLabelTop >= textYTop && hourLabelsColumn.currentTimeLabelBottom <= textYBottom))
653

654
                                    y: ((root.periodHeight * hourlyView.periodsPerHour) * (index + 1)) + (root.gridLineWidth * (index + 1)) -
655
656
657
658
659
660
661
                                        (fontMetrics.height / 2) - (root.gridLineWidth / 2)
                                    width: root.hourLabelWidth
                                    rightPadding: Kirigami.Units.smallSpacing
                                    verticalAlignment: Text.AlignBottom
                                    horizontalAlignment: Text.AlignRight
                                    text: modelData
                                    color: Kirigami.Theme.disabledTextColor
662
                                    visible: !overlapWithCurrentTimeLabel
663
                                }
Claudio Cambra's avatar
Claudio Cambra committed
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
                            }
                        }

                        Item {
                            id: innerWeekView
                            anchors {
                                left: hourLabelsColumn.right
                                top: parent.top
                                bottom: parent.bottom
                                right: parent.right
                            }
                            clip: true

                            Kirigami.Separator {
                                anchors.fill: parent
                            }

                            ListView {
                                anchors.fill: parent
                                spacing: root.gridLineWidth
                                orientation: Qt.Horizontal
685
                                model: weekViewModel // From root.model
Claudio Cambra's avatar
Claudio Cambra committed
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704

                                boundsBehavior: Flickable.StopAtBounds

                                delegate: Item {
                                    id: dayColumn

                                    readonly property int index: model.index
                                    readonly property date columnDate: DateUtils.addDaysToDate(viewLoader.startDate, index)
                                    readonly property bool isToday: columnDate.getDate() === root.currentDay &&
                                        columnDate.getMonth() === root.currentMonth &&
                                        columnDate.getFullYear() === root.currentYear

                                    width: root.dayWidth
                                    height: hourlyView.dayHeight
                                    clip: true

                                    ListView {
                                        anchors.fill: parent
                                        spacing: root.gridLineWidth
705
                                        boundsBehavior: Flickable.StopAtBounds
706
                                        interactive: false
Claudio Cambra's avatar
Claudio Cambra committed
707
708
709

                                        model: 24
                                        delegate: Rectangle {
710
                                            id: backgroundRectangle
Claudio Cambra's avatar
Claudio Cambra committed
711
712
713
714
                                            width: parent.width
                                            height: hourlyView.hourHeight
                                            color: dayColumn.isToday ? Kirigami.Theme.activeBackgroundColor : Kirigami.Theme.backgroundColor

715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
                                            property int index: model.index

                                            ColumnLayout {
                                                anchors.fill: parent
                                                spacing: 0
                                                z: 9999
                                                Repeater {
                                                    id: dropAreaRepeater
                                                    model: 4

                                                    readonly property int minutes: 60 / model

                                                    DropArea {
                                                        id: incidenceDropArea
                                                        Layout.fillWidth: true
                                                        Layout.fillHeight: true
                                                        z: 9999
732
733
734
735
736
737
                                                        onDropped: if(viewLoader.isCurrentItem) {
                                                            const pos = mapToItem(root, dropAreaHighlightRectangle.x, dropAreaHighlightRectangle.y);
                                                            drop.source.caughtX = pos.x + incidenceSpacing;
                                                            drop.source.caughtY = pos.y + incidenceSpacing;
                                                            drop.source.caught = true;

738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
                                                            let incidenceWrapper = Qt.createQmlObject('import org.kde.kalendar 1.0; IncidenceWrapper {id: incidence}', incidenceDropArea, "incidence");
                                                            incidenceWrapper.incidencePtr = drop.source.incidencePtr;
                                                            incidenceWrapper.collectionId = drop.source.collectionId;
                                                            incidenceWrapper.setIncidenceStartDate(backgroundDayMouseArea.addDate.getDate(), backgroundDayMouseArea.addDate.getMonth() + 1, backgroundDayMouseArea.addDate.getFullYear());
                                                            incidenceWrapper.setIncidenceStartTime(backgroundRectangle.index, dropAreaRepeater.minutes * index)
                                                            Kalendar.CalendarManager.editIncidence(incidenceWrapper);
                                                        }

                                                        Rectangle {
                                                            id: dropAreaHighlightRectangle
                                                            anchors.fill: parent
                                                            visible: incidenceDropArea.containsDrag
                                                            color: Kirigami.Theme.positiveBackgroundColor
                                                        }
                                                    }
                                                }
                                            }

Claudio Cambra's avatar
Claudio Cambra committed
756
                                            DayMouseArea {
757
                                                id: backgroundDayMouseArea
Claudio Cambra's avatar
Claudio Cambra committed
758
759
760
                                                anchors.fill: parent
                                                addDate: new Date(DateUtils.addDaysToDate(viewLoader.startDate, dayColumn.index).setHours(index))
                                                onAddNewIncidence: addIncidence(type, addDate, true)
761
                                                onDeselect: root.deselect()
Claudio Cambra's avatar
Claudio Cambra committed
762
763
764
765
766
767
768
                                            }
                                        }
                                    }

                                    Repeater {
                                        id: incidencesRepeater
                                        model: incidences
769

Claudio Cambra's avatar
Claudio Cambra committed
770
                                        delegate: Rectangle {
771
772
                                            id: incidenceDelegate

Claudio Cambra's avatar
Claudio Cambra committed
773
774
                                            readonly property real gridLineYCompensation: (modelData.starts / hourlyView.periodsPerHour) * root.gridLineWidth
                                            readonly property real gridLineHeightCompensation: (modelData.duration / hourlyView.periodsPerHour) * root.gridLineWidth
775
                                            property bool isOpenOccurrence: root.openOccurrence ?
Claudio Cambra's avatar
Claudio Cambra committed
776
777
778
                                                root.openOccurrence.incidenceId === modelData.incidenceId : false

                                            x: root.incidenceSpacing + (modelData.priorTakenWidthShare * root.dayWidth)
779
                                            y: (modelData.starts * root.periodHeight) + root.incidenceSpacing + gridLineYCompensation
Claudio Cambra's avatar
Claudio Cambra committed
780
                                            width: (root.dayWidth * modelData.widthShare) - (root.incidenceSpacing * 2)
781
                                            height: (modelData.duration * root.periodHeight) - (root.incidenceSpacing * 2) + gridLineHeightCompensation - root.gridLineWidth
782
                                            radius: Kirigami.Units.smallSpacing
Claudio Cambra's avatar
Claudio Cambra committed
783
784
785
                                            color: Qt.rgba(0,0,0,0)
                                            visible: !modelData.allDay

786
787
788
789
                                            property alias mouseArea: mouseArea
                                            property var incidencePtr: modelData.incidencePtr
                                            property var collectionId: modelData.collectionId
                                            property bool repositionAnimationEnabled: false
790
791
792
                                            property bool caught: false
                                            property real caughtX: 0
                                            property real caughtY: 0
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810

                                            // Drag reposition animations -- when the incidence goes to the correct cell of the monthgrid
                                            Behavior on x {
                                                enabled: repositionAnimationEnabled
                                                NumberAnimation {
                                                    duration: Kirigami.Units.shortDuration
                                                    easing.type: Easing.OutCubic
                                                }
                                            }

                                            Behavior on y {
                                                enabled: repositionAnimationEnabled
                                                NumberAnimation {
                                                    duration: Kirigami.Units.shortDuration
                                                    easing.type: Easing.OutCubic
                                                }
                                            }

811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
                                            states: [
                                                State {
                                                    when: incidenceDelegate.mouseArea.drag.active
                                                    ParentChange { target: incidenceDelegate; parent: root }
                                                    PropertyChanges { target: incidenceDelegate; isOpenOccurrence: true }
                                                },
                                                State {
                                                    when: incidenceDelegate.caught
                                                    ParentChange { target: incidenceDelegate; parent: root }
                                                    PropertyChanges {
                                                        target: incidenceDelegate
                                                        repositionAnimationEnabled: true
                                                        x: caughtX
                                                        y: caughtY
                                                    }
                                                }
                                            ]

829
                                            IncidenceBackground {
Claudio Cambra's avatar
Claudio Cambra committed
830
                                                id: incidenceBackground
831
832
                                                isOpenOccurrence: parent.isOpenOccurrence
                                                isDark: root.isDark
Claudio Cambra's avatar
Claudio Cambra committed
833
834
835
836
837
838
                                            }

                                            ColumnLayout {
                                                id: incidenceContents

                                                readonly property color textColor: LabelUtils.getIncidenceLabelColor(modelData.color, root.isDark)
839
                                                readonly property bool isTinyHeight: parent.height <= Kirigami.Units.gridUnit
Claudio Cambra's avatar
Claudio Cambra committed
840

841
842
                                                clip: true

Claudio Cambra's avatar
Claudio Cambra committed
843
844
845
846
                                                anchors {
                                                    fill: parent
                                                    leftMargin: Kirigami.Units.smallSpacing
                                                    rightMargin: Kirigami.Units.smallSpacing
847
848
                                                    topMargin: !isTinyHeight ? Kirigami.Units.smallSpacing : 0
                                                    bottomMargin: !isTinyHeight ? Kirigami.Units.smallSpacing : 0
Claudio Cambra's avatar
Claudio Cambra committed
849
850
851
852
853
854
855
856
857
858
                                                }

                                                QQC2.Label {
                                                    Layout.fillWidth: true
                                                    Layout.fillHeight: true
                                                    text: modelData.text
                                                    horizontalAlignment: Text.AlignLeft
                                                    verticalAlignment: Text.AlignTop
                                                    wrapMode: Text.Wrap
                                                    elide: Text.ElideRight
859
860
                                                    font.pointSize: parent.isTinyHeight ? Kirigami.Theme.smallFont.pointSize :
                                                        Kirigami.Theme.defaultFont.pointSize
Claudio Cambra's avatar
Claudio Cambra committed
861
                                                    font.weight: Font.Medium
862
                                                    font.strikeout: modelData.todoCompleted
863
                                                    renderType: Text.QtRendering
Claudio Cambra's avatar
Claudio Cambra committed
864
                                                    color: isOpenOccurrence ? (LabelUtils.isDarkColor(modelData.color) ? "white" : "black") :
865
                                                        incidenceContents.textColor
866
                                                    Behavior on color { ColorAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic } }
Claudio Cambra's avatar
Claudio Cambra committed
867
868
869
870
871
872
873
874
875
876
877
878
879
                                                }

                                                RowLayout {
                                                    width: parent.width
                                                    visible: parent.height > Kirigami.Units.gridUnit * 3
                                                    Kirigami.Icon {
                                                        id: incidenceIcon
                                                        implicitWidth: Kirigami.Units.iconSizes.smallMedium
                                                        implicitHeight: Kirigami.Units.iconSizes.smallMedium
                                                        source: modelData.incidenceTypeIcon
                                                        isMask: true
                                                        color: isOpenOccurrence ? (LabelUtils.isDarkColor(modelData.color) ? "white" : "black") :
                                                            incidenceContents.textColor
880
                                                        Behavior on color { ColorAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic } }
Claudio Cambra's avatar
Claudio Cambra committed
881
882
883
884
885
886
887
888
                                                        visible: parent.width > Kirigami.Units.gridUnit * 4
                                                    }
                                                    QQC2.Label {
                                                        id: timeLabel
                                                        Layout.fillWidth: true
                                                        horizontalAlignment: Text.AlignRight
                                                        text: modelData.startTime.toLocaleTimeString(Qt.locale(), Locale.NarrowFormat) + " - " + modelData.endTime.toLocaleTimeString(Qt.locale(), Locale.NarrowFormat)
                                                        wrapMode: Text.Wrap
889
                                                        renderType: Text.QtRendering
Claudio Cambra's avatar
Claudio Cambra committed
890
891
                                                        color: isOpenOccurrence ? (LabelUtils.isDarkColor(modelData.color) ? "white" : "black") :
                                                            incidenceContents.textColor
892
                                                        Behavior on color { ColorAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic } }
Claudio Cambra's avatar
Claudio Cambra committed
893
894
895
896
897
898
                                                        visible: parent.width > Kirigami.Units.gridUnit * 3
                                                    }
                                                }
                                            }

                                            IncidenceMouseArea {
899
                                                id: mouseArea
Claudio Cambra's avatar
Claudio Cambra committed
900
901
902
                                                incidenceData: modelData
                                                collectionId: modelData.collectionId

903
904
905
                                                drag.target: !Kirigami.Settings.isMobile && !modelData.isReadOnly ? parent : undefined
                                                onReleased: parent.Drag.drop()

Claudio Cambra's avatar
Claudio Cambra committed
906
907
908
909
910
911
                                                onViewClicked: viewIncidence(modelData, collectionData)
                                                onEditClicked: editIncidence(incidencePtr, collectionId)
                                                onDeleteClicked: deleteIncidence(incidencePtr, deleteDate)
                                                onTodoCompletedClicked: completeTodo(incidencePtr)
                                                onAddSubTodoClicked: root.addSubTodo(parentWrapper)
                                            }
912
913

                                            Drag.active: mouseArea.drag.active
Claudio Cambra's avatar
Claudio Cambra committed
914
915
916
917
918
                                        }
                                    }
                                }
                            }

919
920
                            Loader {
                                id: currentTimeMarkerLoader
Claudio Cambra's avatar
Claudio Cambra committed
921

922
923
924
925
926
927
                                active: root.currentDate >= viewLoader.startDate && viewLoader.daysFromWeekStart < root.daysToShow
                                sourceComponent: Rectangle {
                                    id: currentTimeMarker

                                    width: root.dayWidth
                                    height: root.gridLineWidth * 2
Claudio Cambra's avatar
Claudio Cambra committed
928
                                    color: Kirigami.Theme.highlightColor
929
                                    x: (viewLoader.daysFromWeekStart * root.dayWidth) + (viewLoader.daysFromWeekStart * root.gridLineWidth)
930
931
                                    y: (root.currentDate.getHours() * root.gridLineWidth) + (hourlyView.minuteHeight * root.minutesFromStartOfDay) -
                                        (height / 2) - (root.gridLineWidth / 2)
932
933
934
935
936
937
938
939
940
941
942
                                    z: 100

                                    Rectangle {
                                        anchors.left: parent.left
                                        anchors.top: parent.top
                                        anchors.topMargin: -(height / 2) + (parent.height / 2)
                                        width: height
                                        height: parent.height * 5
                                        radius: 100
                                        color: Kirigami.Theme.highlightColor
                                    }
Claudio Cambra's avatar
Claudio Cambra committed
943
944
945
946
947
948
949
950
951
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}