main.qml 38.4 KB
Newer Older
Carl Schwan's avatar
Carl Schwan committed
1
// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
2
3
// SPDX-FileCopyrightText: 2021 Claudio Cambra <claudio.cambra@gmail.com>

Carl Schwan's avatar
Carl Schwan committed
4
5
// SPDX-License-Identifier: GPL-3.0-or-later

Carl Schwan's avatar
Carl Schwan committed
6
import QtQuick 2.15
Carl Schwan's avatar
Carl Schwan committed
7
import org.kde.kirigami 2.14 as Kirigami
8
import QtQuick.Controls 2.15 as QQC2
9
import QtQuick.Layouts 1.15
Carl Schwan's avatar
Carl Schwan committed
10
import QtQml.Models 2.15
11
import QtGraphicalEffects 1.12
Claudio Cambra's avatar
Claudio Cambra committed
12
import QtQuick.Dialogs 1.0
13

14
import "dateutils.js" as DateUtils
15
import "labelutils.js" as LabelUtils
16
import org.kde.kalendar 1.0
Carl Schwan's avatar
Carl Schwan committed
17
18
19
20

Kirigami.ApplicationWindow {
    id: root

Felipe Kinoshita's avatar
Felipe Kinoshita committed
21
22
    width: Kirigami.Units.gridUnit * 65

23
24
25
26
    minimumWidth: Kirigami.Units.gridUnit * 15
    minimumHeight: Kirigami.Units.gridUnit * 20
    onClosing: KalendarApplication.saveWindowGeometry(root)

27
    property date currentDate: new Date()
Claudio Cambra's avatar
Claudio Cambra committed
28
29
30
31
32
33
    Timer {
        interval: 5000;
        running: true
        repeat: true
        onTriggered: currentDate = new Date()
    }
34
    property date selectedDate: new Date()
35
    property var openOccurrence: {}
36
37
38
39
40
41
    property var filter: {
        "collectionId": -1,
        "tags": [],
        "name": ""
    }
    onFilterChanged: if(pageStack.currentItem.objectName === "todoView") pageStack.currentItem.filter = filter
42
    readonly property bool isDark: LabelUtils.isDarkColor(Kirigami.Theme.backgroundColor)
Carl Schwan's avatar
Carl Schwan committed
43
44

    readonly property var monthViewAction: KalendarApplication.action("open_month_view")
Claudio Cambra's avatar
Claudio Cambra committed
45
    readonly property var weekViewAction: KalendarApplication.action("open_week_view")
Carl Schwan's avatar
Carl Schwan committed
46
47
    readonly property var scheduleViewAction: KalendarApplication.action("open_schedule_view")
    readonly property var todoViewAction: KalendarApplication.action("open_todo_view")
48
49
50
51
    readonly property var moveViewForwardsAction: KalendarApplication.action("move_view_forwards")
    readonly property var moveViewBackwardsAction: KalendarApplication.action("move_view_backwards")
    readonly property var moveViewToTodayAction: KalendarApplication.action("move_view_to_today")
    readonly property var openDateChangerAction: KalendarApplication.action("open_date_changer")
52
    readonly property var aboutPageAction: KalendarApplication.action("open_about_page")
Felipe Kinoshita's avatar
Felipe Kinoshita committed
53
    readonly property var toggleMenubarAction: KalendarApplication.action("toggle_menubar")
Carl Schwan's avatar
Carl Schwan committed
54
55
56
    readonly property var createEventAction: KalendarApplication.action("create_event")
    readonly property var createTodoAction: KalendarApplication.action("create_todo")
    readonly property var configureAction: KalendarApplication.action("options_configure")
Claudio Cambra's avatar
Claudio Cambra committed
57
    readonly property var importAction: KalendarApplication.action("import_calendar")
Carl Schwan's avatar
Carl Schwan committed
58
59
60
61
    readonly property var quitAction: KalendarApplication.action("file_quit")
    readonly property var undoAction: KalendarApplication.action("edit_undo")
    readonly property var redoAction: KalendarApplication.action("edit_redo")

62
63
64
65
66
67
    readonly property var todoViewSortAlphabeticallyAction: KalendarApplication.action("todoview_sort_alphabetically")
    readonly property var todoViewSortByDueDateAction: KalendarApplication.action("todoview_sort_by_due_date")
    readonly property var todoViewSortByPriorityAction: KalendarApplication.action("todoview_sort_by_priority")
    readonly property var todoViewOrderAscendingAction: KalendarApplication.action("todoview_order_ascending")
    readonly property var todoViewOrderDescendingAction: KalendarApplication.action("todoview_order_descending")
    readonly property var todoViewShowCompletedAction: KalendarApplication.action("todoview_show_completed")
Carl Schwan's avatar
Carl Schwan committed
68
    readonly property var openKCommandBarAction: KalendarApplication.action("open_kcommand_bar")
69
    readonly property var tagManagerAction: KalendarApplication.action("open_tag_manager")
70

71
    pageStack.globalToolBar.canContainHandles: true
72
    pageStack.globalToolBar.style: Kirigami.ApplicationHeaderStyle.ToolBar
73
74
    pageStack.initialPage: Kirigami.Settings.isMobile ? scheduleViewComponent : monthViewComponent

Felipe Kinoshita's avatar
Felipe Kinoshita committed
75
76
    Component.onCompleted: {
        switch (Config.lastOpenedView) {
Carl Schwan's avatar
Carl Schwan committed
77
            case Config.MonthView:
Felipe Kinoshita's avatar
Felipe Kinoshita committed
78
79
                monthViewAction.trigger();
                break;
Carl Schwan's avatar
Carl Schwan committed
80
            case Config.WeekView:
Claudio Cambra's avatar
Claudio Cambra committed
81
                weekViewAction.trigger();
Felipe Kinoshita's avatar
Felipe Kinoshita committed
82
                break;
Carl Schwan's avatar
Carl Schwan committed
83
            case Config.ScheduleView:
Claudio Cambra's avatar
Claudio Cambra committed
84
85
                scheduleViewAction.trigger();
                break;
Carl Schwan's avatar
Carl Schwan committed
86
            case Config.TodoView:
Felipe Kinoshita's avatar
Felipe Kinoshita committed
87
88
89
                todoViewAction.trigger();
                break;
            default:
90
                Kirigami.Settings.isMobile ? scheduleViewAction.trigger() : monthViewAction.trigger();
Felipe Kinoshita's avatar
Felipe Kinoshita committed
91
92
                break;
        }
Carl Schwan's avatar
Carl Schwan committed
93
94
    }

95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
    QQC2.Action {
        id: closeOverlayAction
        shortcut: "Escape"
        onTriggered: {
            if(applicationWindow().overlay.children[0].visible) {
                applicationWindow().overlay.children[0].visible = false;
                return;
            }
            if(pageStack.layers.depth > 1) {
                pageStack.layers.pop();
                return;
            }
            if(contextDrawer.visible) {
                contextDrawer.close();
                return;
            }
        }
    }

114
    function switchView(newViewComponent) {
115
116
117
        if(pageStack.layers.depth > 1) {
            pageStack.layers.pop(pageStack.layers.initialItem);
        }
118
119
120
121
122
123
124
        pageStack.replace(newViewComponent);

        if(filterHeader.active) {
            pageStack.currentItem.header = filterHeader.item;
        }
    }

Carl Schwan's avatar
Carl Schwan committed
125
126
127
    Connections {
        target: KalendarApplication
        function onOpenMonthView() {
128
            monthScaleModelLoader.active = true;
129
            root.switchView(monthViewComponent);
Carl Schwan's avatar
Carl Schwan committed
130
131
        }

Claudio Cambra's avatar
Claudio Cambra committed
132
        function onOpenWeekView() {
133
            weekScaleModelLoader.active = true;
134
            root.switchView(weekViewComponent);
Claudio Cambra's avatar
Claudio Cambra committed
135
136
        }

Carl Schwan's avatar
Carl Schwan committed
137
        function onOpenScheduleView() {
138
            monthScaleModelLoader.active = true;
139
            root.switchView(scheduleViewComponent);
Carl Schwan's avatar
Carl Schwan committed
140
141
142
        }

        function onOpenTodoView() {
143
            filterHeader.active = true;
144
            root.switchView(todoPageComponent);
Carl Schwan's avatar
Carl Schwan committed
145
146
        }

147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
        function onMoveViewForwards() {
            pageStack.currentItem.nextAction.trigger();
        }

        function onMoveViewBackwards() {
            pageStack.currentItem.previousAction.trigger();
        }

        function onMoveViewToToday() {
            pageStack.currentItem.todayAction.trigger();
        }

        function onOpenDateChanger() {
            dateChangeDrawer.open()
        }

163
164
165
166
        function onOpenAboutPage() {
            pageStack.layers.push("AboutPage.qml")
        }

Felipe Kinoshita's avatar
Felipe Kinoshita committed
167
168
169
170
171
        function onToggleMenubar() {
            Config.showMenubar = !Config.showMenubar;
            Config.save();
        }

Carl Schwan's avatar
Carl Schwan committed
172
173
174
175
176
177
178
179
        function onCreateNewEvent() {
            root.setUpAdd(IncidenceWrapper.TypeEvent);
        }

        function onCreateNewTodo() {
            root.setUpAdd(IncidenceWrapper.TypeTodo);
        }

180
181
182
183
184
185
186
187
188
        function onUndo() {
            CalendarManager.undoAction();
        }

        function onRedo() {
            CalendarManager.redoAction();
        }

        function onTodoViewSortAlphabetically() {
Claudio Cambra's avatar
Claudio Cambra committed
189
190
            Config.sort = Config.Alphabetically;
            Config.save();
191
192
193
        }

        function onTodoViewSortByDueDate() {
Claudio Cambra's avatar
Claudio Cambra committed
194
195
            Config.sort = Config.DueTime;
            Config.save();
196
197
198
        }

        function onTodoViewSortByPriority() {
Claudio Cambra's avatar
Claudio Cambra committed
199
200
            Config.sort = Config.Priority;
            Config.save();
201
202
203
        }

        function onTodoViewOrderAscending() {
Claudio Cambra's avatar
Claudio Cambra committed
204
205
            Config.ascendingOrder = true;
            Config.save();
206
207
208
        }

        function onTodoViewOrderDescending() {
Claudio Cambra's avatar
Claudio Cambra committed
209
210
            Config.ascendingOrder = false;
            Config.save();
211
212
213
        }

        function onTodoViewShowCompleted() {
214
            pageStack.pushDialogLayer(pageStack.currentItem.completedSheetComponent)
215
216
        }

Claudio Cambra's avatar
Claudio Cambra committed
217
218
219
220
221
        function onImportCalendar() {
            filterHeader.active = true;
            importFileDialog.open();
        }

222
223
224
225
226
227
228
229
230
231
232
        function onImportCalendarFromFile(file) {
            importFileDialog.selectedUrl = file // FIXME don't piggy-back on importFileDialog

            const openDialogWindow = pageStack.pushDialogLayer(importChoicePageComponent, {
                width: root.width
            }, {
                width: Kirigami.Units.gridUnit * 30,
                height: Kirigami.Units.gridUnit * 8
            });
        }

Claudio Cambra's avatar
Claudio Cambra committed
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
        function onImportIntoExistingFinished(success, total) {
            filterHeader.active = true;
            pageStack.currentItem.header = filterHeader.item;

            if(success) {
                filterHeader.item.messageItem.type = Kirigami.MessageType.Positive;
                filterHeader.item.messageItem.text = i18nc("%1 is a number", "%1 incidences were imported successfully.", total);
            } else {
                filterHeader.item.messageItem.type = Kirigami.MessageType.Error;
                filterHeader.item.messageItem.text = i18nc("%1 is the error message", "An error occurred importing incidences: %1", KalendarApplication.importErrorMessage);
            }

            filterHeader.item.messageItem.visible = true;
        }

        function onImportIntoNewFinished(success) {
            filterHeader.active = true;
            pageStack.currentItem.header = filterHeader.item;

            if(success) {
                filterHeader.item.messageItem.type = Kirigami.MessageType.Positive;
                filterHeader.item.messageItem.text = i18n("New calendar  created from imported file successfully.");
            } else {
                filterHeader.item.messageItem.type = Kirigami.MessageType.Error;
                filterHeader.item.messageItem.text = i18nc("%1 is the error message", "An error occurred importing incidences: %1", KalendarApplication.importErrorMessage);
            }

            filterHeader.item.messageItem.visible = true;
        }

Carl Schwan's avatar
Carl Schwan committed
263
264
265
266
267
        function onQuit() {
             Qt.quit();
        }

        function onOpenSettings() {
268
            const openDialogWindow = pageStack.pushDialogLayer("qrc:/SettingsPage.qml", {
Carl Schwan's avatar
Carl Schwan committed
269
270
                width: root.width
            }, {
271
                title: i18n("Configure"),
272
273
                width: Kirigami.Units.gridUnit * 45,
                height: Kirigami.Units.gridUnit * 35
274
275
276
277
278
            });

            if(!Kirigami.Settings.isMobile) {
                openDialogWindow.Keys.escapePressed.connect(function() { openDialogWindow.closeDialog() });
            }
Carl Schwan's avatar
Carl Schwan committed
279
        }
Claudio Cambra's avatar
Claudio Cambra committed
280
281

        function onOpenTagManager() {
282
            const openDialogWindow = pageStack.pushDialogLayer("qrc:/TagManagerPage.qml", {
Claudio Cambra's avatar
Claudio Cambra committed
283
284
285
286
                width: root.width
            }, {
                width: Kirigami.Units.gridUnit * 30,
                height: Kirigami.Units.gridUnit * 30
287
288
289
290
291
            });

            if(!Kirigami.Settings.isMobile) {
                openDialogWindow.Keys.escapePressed.connect(function() { openDialogWindow.closeDialog() });
            }
Claudio Cambra's avatar
Claudio Cambra committed
292
        }
Carl Schwan's avatar
Carl Schwan committed
293
294
295
296
297
298
299
300
301
302
303
304
305

        function onOpenKCommandBarAction() {
            kcommandbarLoader.active = true;
        }
    }

    Loader {
        id: kcommandbarLoader
        active: false
        source: 'qrc:/KQuickCommandbar.qml'
        onActiveChanged: if (active) {
            item.open()
        }
Carl Schwan's avatar
Carl Schwan committed
306
307
    }

308
309
310
311
312
313
314
315
316
317
318
    Connections {
        target: CalendarManager

        function onUndoRedoDataChanged() {
            undoAction.enabled = CalendarManager.undoRedoData.undoAvailable;
            redoAction.enabled = CalendarManager.undoRedoData.redoAvailable;
        }
    }

    property Kirigami.Action createAction: Kirigami.Action {
        text: i18n("Create")
319
320
321
        icon.name: "list-add"

        Kirigami.Action {
Carl Schwan's avatar
Carl Schwan committed
322
            id: newEventAction
323
            text: i18n("New Event…")
324
            icon.name: "resource-calendar-insert"
Carl Schwan's avatar
Carl Schwan committed
325
            onTriggered: createEventAction.trigger()
326
327
        }
        Kirigami.Action {
Carl Schwan's avatar
Carl Schwan committed
328
            id: newTodoAction
329
            text: i18n("New Task…")
330
            icon.name: "view-task-add"
Carl Schwan's avatar
Carl Schwan committed
331
            onTriggered: createTodoAction.trigger()
332
333
334
        }
    }

335
    title: if(pageStack.currentItem) {
336
337
338
339
340
341
342
343
        switch (pageStack.currentItem.objectName) {
            case "monthView":
                return i18n("Month View");
                break;
            case "scheduleView":
                return i18n("Schedule View");
                break;
            case "todoView":
344
                return i18n("Tasks View");
345
346
347
348
                break;
            default:
                return i18n("Calendar");
        }
349
350
    } else {
        return i18n("Calendar");
351
    }
Carl Schwan's avatar
Carl Schwan committed
352

353
354
    menuBar: Loader {
        id: menuLoader
355
356
        active: Kirigami.Settings.hasPlatformMenuBar != undefined ?
                !Kirigami.Settings.hasPlatformMenuBar && !Kirigami.Settings.isMobile : !Kirigami.Settings.isMobile && Config.showMenubar
Felipe Kinoshita's avatar
Felipe Kinoshita committed
357
358
359

        visible: Config.showMenubar
        height: visible ? implicitHeight : 0
360
361
362

        sourceComponent: WindowMenu {
            parentWindow: root
363
            todoMode: pageStack.currentItem.objectName === "todoView"
364
365
366
367
            Kirigami.Theme.colorSet: Kirigami.Theme.Header
        }
    }

368
369
370
    footer: Loader {
        id: bottomLoader
        active: Kirigami.Settings.isMobile
371
        visible: pageStack.currentItem.objectName !== "settingsPage"
372
373
374
375

        source: Qt.resolvedUrl("qrc:/BottomToolBar.qml")
    }

Felipe Kinoshita's avatar
Felipe Kinoshita committed
376
    globalDrawer: Sidebar {
377
        id: sidebar
378
        bottomPadding: menuLoader.active ? menuLoader.height : 0
379
        todoMode: pageStack.currentItem ? pageStack.currentItem.objectName === "todoView" : false
380
381
        activeTags: root.filter && root.filter.tags ?
                    root.filter.tags : []
Claudio Cambra's avatar
Claudio Cambra committed
382
        onSearchTextChanged: {
383
384
            if(root.filter) {
                root.filter.name = searchText;
Claudio Cambra's avatar
Claudio Cambra committed
385
            } else {
386
                root.filter = {name: searchText};
Claudio Cambra's avatar
Claudio Cambra committed
387
            }
388
            root.filterChanged();
Claudio Cambra's avatar
Claudio Cambra committed
389
        }
390
        onCalendarClicked: if(todoMode) {
391
392
393
394
            root.filter ?
                root.filter.collectionId = collectionId :
                root.filter = {"collectionId" : collectionId};
            root.filterChanged();
395
            pageStack.currentItem.filterCollectionDetails = CalendarManager.getCollectionDetails(collectionId);
396
        }
397
398
399
400
401
402
        onCalendarCheckChanged: {
            CalendarManager.save();
            if(todoMode && collectionId === pageStack.currentItem.filterCollectionId) {
                pageStack.currentItem.filterCollectionDetails = CalendarManager.getCollectionDetails(pageStack.currentItem.filterCollectionId);
                // HACK: The Todo View should be able to detect change in collection filtering independently
            }
403
        }
404
405
406
407
408
409
        onTagClicked: if(!root.filter || !root.filter.tags || !root.filter.tags.includes(tagName)) {
            root.filter ? root.filter.tags ?
                root.filter.tags.push(tagName) :
                root.filter.tags = [tagName] :
                root.filter = {"tags" : [tagName]};
            root.filterChanged();
410
411
            filterHeader.active = true;
            pageStack.currentItem.header = filterHeader.item;
412
413
414
        } else if (root.filter.tags.includes(tagName)) {
            root.filter.tags = root.filter.tags.filter((tag) => tag !== tagName);
            root.filterChanged();
415
416
        }
        onViewAllTodosClicked: if(todoMode) {
417
418
419
420
            root.filter.collectionId = -1;
            root.filter.tags = [];
            root.filter.name = "";
            root.filterChanged();
421
        }
422
423
    }

424
425
    contextDrawer: IncidenceInfo {
        id: incidenceInfo
426

427
        bottomPadding: menuLoader.active ? menuLoader.height : 0
428
        width: actualWidth
429
430
431
        modal: !root.wideScreen || !enabled
        onEnabledChanged: drawerOpen = enabled && !modal
        onModalChanged: drawerOpen = !modal
432
        enabled: incidenceData != undefined && pageStack.layers.depth < 2 && pageStack.depth < 3
433
        handleVisible: enabled && pageStack.layers.depth < 2 && pageStack.depth < 3
434
        interactive: Kirigami.Settings.isMobile // Otherwise get weird bug where drawer gets dragged around despite no click
435

436
437
438
439
440
441
442
443
444
        onIncidenceDataChanged: root.openOccurrence = incidenceData;
        onVisibleChanged: {
            if(visible) {
                root.openOccurrence = incidenceData;
            } else {
                root.openOccurrence = null;
            }
        }

445
446
447
448
        onAddSubTodo: {
            setUpAddSubTodo(parentWrapper);
            if (modal) { incidenceInfo.close() }
        }
449
450
451
        onEditIncidence: {
            setUpEdit(incidencePtr, collectionId);
            if (modal) { incidenceInfo.close() }
452
        }
453
454
455
        onDeleteIncidence: {
            setUpDelete(incidencePtr, deleteDate)
            if (modal) { incidenceInfo.close() }
456
        }
457
458
459
460
461
462

        readonly property int minWidth: Kirigami.Units.gridUnit * 15
        readonly property int maxWidth: Kirigami.Units.gridUnit * 25
        readonly property int defaultWidth: Kirigami.Units.gridUnit * 20
        property int actualWidth: {
            if (Config.incidenceInfoDrawerWidth === -1) {
463
                return defaultWidth;
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
            } else {
                return Config.incidenceInfoDrawerWidth;
            }
        }

        MouseArea {
            anchors.left: parent.left
            anchors.top: parent.top
            anchors.bottom: parent.bottom
            anchors.right: undefined
            width: 2
            z: 500
            cursorShape: !Kirigami.Settings.isMobile ? Qt.SplitHCursor : undefined
            enabled: true
            visible: true
            onPressed: _lastX = mapToGlobal(mouseX, mouseY).x
            onReleased: {
                Config.incidenceInfoDrawerWidth = incidenceInfo.actualWidth;
                Config.save();
            }
            property real _lastX: -1

            onPositionChanged: {
                if (_lastX === -1) {
                    return;
                }
                if (Qt.application.layoutDirection === Qt.RightToLeft) {
                    incidenceInfo.actualWidth = Math.min(incidenceInfo.maxWidth, Math.max(incidenceInfo.minWidth, Config.incidenceInfoDrawerWidth - _lastX + mapToGlobal(mouseX, mouseY).x))
                } else {
                    incidenceInfo.actualWidth = Math.min(incidenceInfo.maxWidth, Math.max(incidenceInfo.minWidth, Config.incidenceInfoDrawerWidth + _lastX - mapToGlobal(mouseX, mouseY).x))
                }
            }
        }
497
498
    }

499
500
501
    DateChanger {
        id: dateChangeDrawer
        y: pageStack.globalToolBar.height - 1
502
        showDays: pageStack.currentItem && pageStack.currentItem.objectName !== "monthView"
503
        date: root.selectedDate
504
505
506
507
        onDateSelected: if(visible) {
            pageStack.currentItem.setToDate(date);
            root.selectedDate = date;
        }
508
509
    }

510
511
512
513
    IncidenceEditor {
        id: incidenceEditor
        onAdded: CalendarManager.addIncidence(incidenceWrapper)
        onEdited: CalendarManager.editIncidence(incidenceWrapper)
514
        onCancel: pageStack.layers.pop()
515
516
    }

Carl Schwan's avatar
Carl Schwan committed
517
    Loader {
Felipe Kinoshita's avatar
Felipe Kinoshita committed
518
        id: globalMenuLoader
Carl Schwan's avatar
Carl Schwan committed
519
        active: !Kirigami.Settings.isMobile
520
        sourceComponent: GlobalMenu {
521
            todoMode: pageStack.currentItem && pageStack.currentItem.objectName === "todoView"
522
        }
Carl Schwan's avatar
Carl Schwan committed
523
524
525
        onLoaded: item.parentWindow = root;
    }

526
527
528
529
530
531
532
    Loader {
        id: editorWindowedLoader
        active: false
        sourceComponent: Kirigami.ApplicationWindow {
            id: root

            width: Kirigami.Units.gridUnit * 40
533
            height: Kirigami.Units.gridUnit * 32
534

Carl Schwan's avatar
Carl Schwan committed
535
536
            flags: Qt.Dialog | Qt.WindowCloseButtonHint

537
            // Probably a more elegant way of accessing the editor from outside than this.
538
            property var incidenceEditor: incidenceEditorInLoader
539

540
            pageStack.initialPage: incidenceEditorInLoader
541

Carl Schwan's avatar
Carl Schwan committed
542
543
544
545
546
547
            Loader {
                active: !Kirigami.Settings.isMobile
                source: Qt.resolvedUrl("qrc:/GlobalMenu.qml")
                onLoaded: item.parentWindow = root
            }

548
549
550
551
            IncidenceEditor {
                id: incidenceEditorInLoader
                onAdded: CalendarManager.addIncidence(incidenceWrapper)
                onEdited: CalendarManager.editIncidence(incidenceWrapper)
552
                onCancel: root.close()
553
                Keys.onEscapePressed: root.close()
554
555
556
557
558
559
560
            }

            visible: true
            onClosing: editorWindowedLoader.active = false
        }
    }

561
562
563
564
565
566
567
568
569
    Loader {
        id: filterHeader
        active: false
        sourceComponent: Item {
            anchors {
                top: parent.top
                left: parent.left
                right: parent.right
            }
570

Claudio Cambra's avatar
Claudio Cambra committed
571
572
            readonly property bool show: header.todoMode || header.filter.tags.length > 0 || notifyMessage.visible
            readonly property alias messageItem: notifyMessage
573

Claudio Cambra's avatar
Claudio Cambra committed
574
575
            height: show ? headerLayout.implicitHeight + headerSeparator.height : 0
            // Adjust for margins
576
577
578
579
580
581
            clip: height === 0

            Behavior on height { NumberAnimation {
                duration: Kirigami.Units.longDuration
                easing.type: Easing.InOutQuad
            } }
582
583

            Rectangle {
Claudio Cambra's avatar
Claudio Cambra committed
584
585
                width: headerLayout.width
                height: headerLayout.height
586
587
588
589
590
                Kirigami.Theme.inherit: false
                Kirigami.Theme.colorSet: Kirigami.Theme.View
                color: Kirigami.Theme.backgroundColor
            }

Claudio Cambra's avatar
Claudio Cambra committed
591
592
            ColumnLayout {
                id: headerLayout
593
                anchors.fill: parent
594
                clip: true
595

Claudio Cambra's avatar
Claudio Cambra committed
596
597
598
599
600
601
                Kirigami.InlineMessage {
                    id: notifyMessage
                    Layout.fillWidth: true
                    Layout.margins: Kirigami.Units.smallSpacing
                    showCloseButton: true
                    visible: false
602
                }
Claudio Cambra's avatar
Claudio Cambra committed
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622

                FilterHeader {
                    id: header
                    Layout.fillWidth: true
                    Layout.fillHeight: true
                    todoMode: pageStack.currentItem ? pageStack.currentItem.objectName === "todoView" : false
                    filter: root.filter ?
                        root.filter : {"tags": [], "collectionId": -1}
                    isDark: root.isDark
                    visible: todoMode || filter.tags.length > 0
                    clip: true

                    onRemoveFilterTag: {
                        root.filter.tags.splice(root.filter.tags.indexOf(tagName), 1);
                        root.filterChanged();
                    }
                    onResetFilterCollection: {
                        root.filter.collectionId = -1;
                        root.filterChanged();
                    }
623
                }
624
625
626
            }
            Kirigami.Separator {
                id: headerSeparator
Claudio Cambra's avatar
Claudio Cambra committed
627
                anchors.top: headerLayout.bottom
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
                width: parent.width
                height: 1
                z: -2

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

Claudio Cambra's avatar
Claudio Cambra committed
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
    FileDialog {
        id: importFileDialog

        property string selectedUrl: ""

        title: "Import a calendar"
        folder: shortcuts.home
        nameFilters: ["Calendar files (*.ics *.vcs)"]
        onAccepted: {
            selectedUrl = fileUrl;
            const openDialogWindow = pageStack.pushDialogLayer(importChoicePageComponent, {
                width: root.width
            }, {
                width: Kirigami.Units.gridUnit * 30,
                height: Kirigami.Units.gridUnit * 8
            });
        }
    }

    Component {
        id: importChoicePageComponent
        Kirigami.Page {
            title: i18n("Import Calendar")

            ColumnLayout {
                anchors.fill: parent
                QQC2.Label {
                    Layout.fillWidth: true
                    Layout.fillHeight: true
                    text: i18n("Would you like to merge this calendar file's events and tasks into one of your existing calendars, or would prefer to create a new calendar from this file?\n ")
                    wrapMode: Text.WordWrap
                }

                RowLayout {
                    QQC2.Button {
                        Layout.fillWidth: true
                        icon.name: "document-import"
                        text: i18n("Merge with existing calendar")
                        onClicked: {
                            closeDialog();
                            const openDialogWindow = pageStack.pushDialogLayer(importMergeCollectionPickerComponent, {
                                width: root.width
                            }, {
                                width: Kirigami.Units.gridUnit * 30,
                                height: Kirigami.Units.gridUnit * 30
                            });
                        }
                    }
                    QQC2.Button {
                        Layout.fillWidth: true
                        icon.name: "document-new"
                        text: i18n("Create new calendar")
                        onClicked: {
                            KalendarApplication.importCalendarFromUrl(importFileDialog.selectedUrl, false);
                            closeDialog();
                        }
                    }
                    QQC2.Button {
                        icon.name: "gtk-cancel"
                        text: i18n("Cancel")
                        onClicked: closeDialog();
                    }
                }
            }
        }
    }

    Component {
        id: importMergeCollectionPickerComponent
        CollectionPickerPage {
            onCollectionPicked: {
                KalendarApplication.importCalendarFromUrl(importFileDialog.selectedUrl, true, collectionId);
                closeDialog();
            }
            onCancel: closeDialog()
        }
    }

721
    function editorToUse() {
722
        if (!Kirigami.Settings.isMobile) {
723
            editorWindowedLoader.active = true
724
            return editorWindowedLoader.item.incidenceEditor
725
        } else {
726
            pageStack.layers.push(incidenceEditor);
727
            return incidenceEditor;
728
        }
729
730
    }

Claudio Cambra's avatar
Claudio Cambra committed
731
    function setUpAdd(type, addDate, collectionId, includeTime) {
732
        let editorToUse = root.editorToUse();
733
734
        if (editorToUse.editMode || !editorToUse.incidenceWrapper) {
            editorToUse.incidenceWrapper = Qt.createQmlObject('import org.kde.kalendar 1.0; IncidenceWrapper {id: incidence}',
735
                editorToUse, "incidence");
736
737
        }
        editorToUse.editMode = false;
738
739
740
741
742
743
744
745
746
747
748

        if(type === IncidenceWrapper.TypeEvent) {
            editorToUse.incidenceWrapper.setNewEvent();
        } else if (type === IncidenceWrapper.TypeTodo) {
            editorToUse.incidenceWrapper.setNewTodo();
        }

        if(addDate !== undefined && !isNaN(addDate.getTime())) {
            let existingStart = editorToUse.incidenceWrapper.incidenceStart;
            let existingEnd = editorToUse.incidenceWrapper.incidenceEnd;

Claudio Cambra's avatar
Claudio Cambra committed
749
750
751
752
753
754
755
756
            let newStart = addDate;
            let newEnd = new Date(newStart.getFullYear(), newStart.getMonth(), newStart.getDate(), newStart.getHours() + 1, newStart.getMinutes());

            if(!includeTime) {
                newStart = new Date(addDate.setHours(existingStart.getHours(), existingStart.getMinutes()));
                newEnd = new Date(addDate.setHours(existingStart.getHours() + 1, existingStart.getMinutes()));
            }

757
            if(type === IncidenceWrapper.TypeEvent) {
Claudio Cambra's avatar
Claudio Cambra committed
758
759
                editorToUse.incidenceWrapper.incidenceStart = newStart;
                editorToUse.incidenceWrapper.incidenceEnd = newEnd;
760
            } else if (type === IncidenceWrapper.TypeTodo) {
Claudio Cambra's avatar
Claudio Cambra committed
761
                editorToUse.incidenceWrapper.incidenceEnd = newStart;
762
            }
763
        }
764
765
766

        if(collectionId && collectionId >= 0) {
            editorToUse.incidenceWrapper.collectionId = collectionId;
767
768
769
770
        } else if(type === IncidenceWrapper.TypeEvent && Config.lastUsedEventCollection > -1) {
            editorToUse.incidenceWrapper.collectionId = Config.lastUsedEventCollection;
        } else if (type === IncidenceWrapper.TypeTodo && Config.lastUsedTodoCollection > -1) {
            editorToUse.incidenceWrapper.collectionId = Config.lastUsedTodoCollection;
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
        } else {
            editorToUse.incidenceWrapper.collectionId = CalendarManager.defaultCalendarId(editorToUse.incidenceWrapper);
        }
    }

    function setUpAddSubTodo(parentWrapper) {
        let editorToUse = root.editorToUse();
        if (editorToUse.editMode || !editorToUse.incidenceWrapper) {
            editorToUse.incidenceWrapper = Qt.createQmlObject('import org.kde.kalendar 1.0; IncidenceWrapper {id: incidence}',
                editorToUse, "incidence");
        }
        editorToUse.editMode = false;
        editorToUse.incidenceWrapper.setNewTodo();
        editorToUse.incidenceWrapper.parent = parentWrapper.uid;
        editorToUse.incidenceWrapper.collectionId = parentWrapper.collectionId;
        editorToUse.incidenceWrapper.incidenceStart = parentWrapper.incidenceStart;
        editorToUse.incidenceWrapper.incidenceEnd = parentWrapper.incidenceEnd;
788
789
790
    }

    function setUpView(modelData, collectionData) {
791
792
793
        incidenceInfo.incidenceData = modelData
        incidenceInfo.collectionData = collectionData
        incidenceInfo.open()
794
795
    }

796
    function setUpEdit(incidencePtr, collectionId) {
797
        let editorToUse = root.editorToUse();
798
        editorToUse.incidenceWrapper = Qt.createQmlObject('import org.kde.kalendar 1.0; IncidenceWrapper {id: incidence}',
799
            editorToUse, "incidence");
800
        editorToUse.incidenceWrapper.incidencePtr = incidencePtr;
801
        editorToUse.incidenceWrapper.triggerEditMode();
802
        editorToUse.incidenceWrapper.collectionId = collectionId;
803
804
805
        editorToUse.editMode = true;
    }

806
807
808
809
810
811
812
813
814
815
816
    function setUpDelete(incidencePtr, deleteDate) {
        deleteIncidenceSheet.incidenceWrapper = Qt.createQmlObject('import org.kde.kalendar 1.0; IncidenceWrapper {id: incidence}',
                                                                   deleteIncidenceSheet,
                                                                   "incidence");
        deleteIncidenceSheet.incidenceWrapper.incidencePtr = incidencePtr;
        deleteIncidenceSheet.deleteDate = deleteDate;
        deleteIncidenceSheet.open();
    }

    function completeTodo(incidencePtr) {
        let todo = Qt.createQmlObject('import org.kde.kalendar 1.0; IncidenceWrapper {id: incidence}',
817
            this, "incidence");
818
819

        todo.incidencePtr = incidencePtr;
820
        todo.collectionId = -1;
821
822
823
824
825

        if(todo.incidenceType === IncidenceWrapper.TypeTodo) {
            todo.todoCompleted = !todo.todoCompleted;
            CalendarManager.editIncidence(todo);
        }
826
827
    }

828
829
    DeleteIncidenceSheet {
        id: deleteIncidenceSheet
830
        onAddException: {
831
832
833
            incidenceWrapper.recurrenceExceptionsModel.addExceptionDateTime(exceptionDate);
            CalendarManager.editIncidence(incidenceWrapper);
            deleteIncidenceSheet.close();
834
835
        }
        onAddRecurrenceEndDate: {
836
            incidenceWrapper.setRecurrenceDataItem("endDateTime", endDate);
837
838
            CalendarManager.editIncidence(incidenceWrapper);
            deleteIncidenceSheet.close();
839
        }
840
841
842
        onDeleteIncidence: {
            CalendarManager.deleteIncidence(incidencePtr);
            deleteIncidenceSheet.close();
843
844
845
846
        }
        onDeleteIncidenceWithChildren: {
            CalendarManager.deleteIncidence(incidencePtr, true);
            deleteIncidenceSheet.close();
847
        }
848
849
    }

850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
    Loader {
        id: monthScaleModelLoader
        active: Config.lastOpenedView === Config.MonthView || Config.lastOpenedView === Config.ScheduleView
        onStatusChanged: if(status === Loader.Ready) asynchronous = true
        sourceComponent: InfiniteCalendarViewModel {
            scale: InfiniteCalendarViewModel.MonthScale
            calendar: CalendarManager.calendar
            filter: root.filter
        }
    }

    Loader {
        id: weekScaleModelLoader
        active: Config.lastOpenedView === Config.WeekView
        onStatusChanged: if(status === Loader.Ready) asynchronous = true
        sourceComponent: InfiniteCalendarViewModel {
            scale: InfiniteCalendarViewModel.WeekScale
            calendar: CalendarManager.calendar
            filter: root.filter
        }
    }

872
    Component {
Carl Schwan's avatar
Carl Schwan committed
873
        id: monthViewComponent
874
875

        MonthView {
876
            id: monthView
877
            objectName: "monthView"
878

879
880
881
            titleDelegate: ViewTitleDelegate {
                titleDateButton.date: monthView.firstDayOfMonth
                titleDateButton.onClicked: dateChangeDrawer.open()
882
            }
883
            currentDate: root.currentDate
884
            openOccurrence: root.openOccurrence
885
            model: monthScaleModelLoader.item
886

887
888
889
890
891
892
            onAddIncidence: root.setUpAdd(type, addDate)
            onViewIncidence: root.setUpView(modelData, collectionData)
            onEditIncidence: root.setUpEdit(incidencePtr, collectionId)
            onDeleteIncidence: root.setUpDelete(incidencePtr, deleteDate)
            onCompleteTodo: root.completeTodo(incidencePtr)
            onAddSubTodo: root.setUpAddSubTodo(parentWrapper)
893
            onDeselect: incidenceInfo.close()
894

895
896
            onMonthChanged: if(month !== root.selectedDate.getMonth() && !initialMonth) root.selectedDate = new Date (year, month, 1)
            onYearChanged: if(year !== root.selectedDate.getFullYear() && !initialMonth) root.selectedDate = new Date (year, month, 1)
897

898
            Component.onCompleted: setToDate(root.selectedDate, true)
899

900
            actions.contextualActions: createAction
901
902
903
904
905
906
907
908
        }
    }

    Component {
        id: scheduleViewComponent

        ScheduleView {
            id: scheduleView
909
            objectName: "scheduleView"
910

911
912
913
            titleDelegate: ViewTitleDelegate {
                titleDateButton.date: scheduleView.startDate
                titleDateButton.onClicked: dateChangeDrawer.open()
914
915
            }
            selectedDate: root.selectedDate
916
            openOccurrence: root.openOccurrence
917
            model: monthScaleModelLoader.item
918

919
920
921
            onDayChanged: if(day !== root.selectedDate.getDate() && !initialMonth) root.selectedDate = new Date (year, month, day)
            onMonthChanged: if(month !== root.selectedDate.getMonth() && !initialMonth) root.selectedDate = new Date (year, month, day)
            onYearChanged: if(year !== root.selectedDate.getFullYear() && !initialMonth) root.selectedDate = new Date (year, month, day)
922

923
            Component.onCompleted: setToDate(root.selectedDate, true)
924

925
926
            onAddIncidence: root.setUpAdd(type, addDate)
            onViewIncidence: root.setUpView(modelData, collectionData)
927
            onEditIncidence: root.setUpEdit(incidencePtr, collectionId)
928
929
            onDeleteIncidence: root.setUpDelete(incidencePtr, deleteDate)
            onCompleteTodo: root.completeTodo(incidencePtr)
930
            onAddSubTodo: root.setUpAddSubTodo(parentWrapper)
931
            onDeselect: incidenceInfo.close()
932

933
            actions.contextualActions: createAction
Carl Schwan's avatar
Carl Schwan committed
934
935
        }
    }
936

Claudio Cambra's avatar
Claudio Cambra committed
937
938
939
940
941
942
943
    Component {
        id: weekViewComponent

        WeekView {
            id: weekView
            objectName: "weekView"

944
945
946
947
948
            titleDelegate: ViewTitleDelegate {
                titleDateButton.range: true
                titleDateButton.date: weekView.startDate
                titleDateButton.lastDate: DateUtils.addDaysToDate(weekView.startDate, 6)
                titleDateButton.onClicked: dateChangeDrawer.open()
Claudio Cambra's avatar
Claudio Cambra committed
949
950
951
952
            }
            selectedDate: root.selectedDate
            currentDate: root.currentDate
            openOccurrence: root.openOccurrence
953
            model: weekScaleModelLoader.item
Claudio Cambra's avatar
Claudio Cambra committed
954
955
956
957
958

            onDayChanged: if(day !== root.selectedDate.getDate() && !initialWeek) root.selectedDate = new Date (year, month, day)
            onMonthChanged: if(month !== root.selectedDate.getMonth() && !initialWeek) root.selectedDate = new Date (year, month, day)
            onYearChanged: if(year !== root.selectedDate.getFullYear() && !initialWeek) root.selectedDate = new Date (year, month, day)

959
            Component.onCompleted: setToDate(root.selectedDate, true)
Claudio Cambra's avatar
Claudio Cambra committed
960
961
962
963
964
965
966

            onAddIncidence: root.setUpAdd(type, addDate, null, includeTime)
            onViewIncidence: root.setUpView(modelData, collectionData)
            onEditIncidence: root.setUpEdit(incidencePtr, collectionId)
            onDeleteIncidence: root.setUpDelete(incidencePtr, deleteDate)
            onCompleteTodo: root.completeTodo(incidencePtr)
            onAddSubTodo: root.setUpAddSubTodo(parentWrapper)
967
            onDeselect: incidenceInfo.close()
Claudio Cambra's avatar
Claudio Cambra committed
968
969
970
971
972

            actions.contextualActions: createAction
        }
    }

973
    Component {
Felipe Kinoshita's avatar
Felipe Kinoshita committed
974
975
976
977
        id: todoPageComponent

        TodoPage {
            id: todoPage
978
            objectName: "todoView"
979

980
981
982
983
984
            titleDelegate: RowLayout {
                spacing: 0
                QQC2.ToolButton {
                    visible: !Kirigami.Settings.isMobile
                    icon.name: sidebar.collapsed ? "sidebar-expand" : "sidebar-collapse"
985
                    onClicked: {
986
987
988
989
990
991
992
                        if(sidebar.collapsed && !wideScreen) { // Collapsed due to narrow window
                            // We don't want to write to config as when narrow the button will only open the modal drawer
                            sidebar.collapsed = !sidebar.collapsed;
                        } else {
                            Config.forceCollapsedSidebar = !Config.forceCollapsedSidebar;
                            Config.save()
                        }
993
                    }
994
995
996
997
998
999
1000
1001
1002
1003

                    QQC2.ToolTip.text: sidebar.collapsed ? i18n("Expand Sidebar") : i18n("Collapse Sidebar")
                    QQC2.ToolTip.visible: hovered
                    QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
                }
                Kirigami.Heading {
                    text: i18n("Tasks")
                }
            }

1004
1005
            filter: if(root.filter) root.filter

1006
            onAddTodo: root.setUpAdd(IncidenceWrapper.TypeTodo, new Date(), collectionId)
1007
1008
1009
1010
            onViewTodo: root.setUpView(todoData, collectionData)
            onEditTodo: root.setUpEdit(todoPtr, collectionId)
            onDeleteTodo: root.setUpDelete(todoPtr, deleteDate)
            onCompleteTodo: root.completeTodo(todoPtr)
1011
            onAddSubTodo: root.setUpAddSubTodo(parentWrapper)
1012
            onDeselect: incidenceInfo.close()
1013
1014
        }
    }
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031

    property Item hoverLinkIndicator: QQC2.Control {
        parent: overlay.parent
        property alias text: linkText.text
        opacity: text.length > 0 ? 1 : 0

        z: 99999
        x: 0
        y: parent.height - implicitHeight
        contentItem: QQC2.Label {
            id: linkText
        }
        Kirigami.Theme.colorSet: Kirigami.Theme.View
        background: Rectangle {
             color: Kirigami.Theme.backgroundColor
        }
    }
Carl Schwan's avatar
Carl Schwan committed
1032
}