main.qml 19.3 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

12
import "dateutils.js" as DateUtils
13
import org.kde.kalendar 1.0
Carl Schwan's avatar
Carl Schwan committed
14
15
16
17

Kirigami.ApplicationWindow {
    id: root

Felipe Kinoshita's avatar
Felipe Kinoshita committed
18
19
    width: Kirigami.Units.gridUnit * 65

20
21
22
    property date currentDate: new Date()
    property date selectedDate: currentDate

23
    property var openOccurrence
Carl Schwan's avatar
Carl Schwan committed
24
25
26
27

    readonly property var monthViewAction: KalendarApplication.action("open_month_view")
    readonly property var scheduleViewAction: KalendarApplication.action("open_schedule_view")
    readonly property var todoViewAction: KalendarApplication.action("open_todo_view")
28
    readonly property var aboutPageAction: KalendarApplication.action("open_about_page")
Carl Schwan's avatar
Carl Schwan committed
29
30
31
32
33
34
35
    readonly property var createEventAction: KalendarApplication.action("create_event")
    readonly property var createTodoAction: KalendarApplication.action("create_todo")
    readonly property var configureAction: KalendarApplication.action("options_configure")
    readonly property var quitAction: KalendarApplication.action("file_quit")
    readonly property var undoAction: KalendarApplication.action("edit_undo")
    readonly property var redoAction: KalendarApplication.action("edit_redo")

36
37
38
39
40
41
42
    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")

43
    pageStack.globalToolBar.canContainHandles: true
44
    pageStack.globalToolBar.style: Kirigami.ApplicationHeaderStyle.ToolBar
45

Felipe Kinoshita's avatar
Felipe Kinoshita committed
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
    onClosing: {
        rememberLastOpenedView();
    }

    function rememberLastOpenedView() {
        switch (pageStack.currentItem.objectName) {
            case "monthView":
                Config.lastOpenedView = 0;
                break;
            case "scheduleView":
                Config.lastOpenedView = 1;
                break;
            case "todoView":
                Config.lastOpenedView = 2;
                break;
        }
        Config.save();
    }

    Component.onCompleted: {
        switch (Config.lastOpenedView) {
            case 0:
                monthViewAction.trigger();
                break;
            case 1:
                scheduleViewAction.trigger();
                break;
            case 2:
                todoViewAction.trigger();
                break;
            default:
                monthViewAction.trigger();
                break;
        }
Carl Schwan's avatar
Carl Schwan committed
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
    }

    Connections {
        target: KalendarApplication
        function onOpenMonthView() {
            pageStack.pop(null);
            pageStack.replace(monthViewComponent);
        }

        function onOpenScheduleView() {
            pageStack.pop(null);
            pageStack.replace(scheduleViewComponent);
        }

        function onOpenTodoView() {
            pageStack.pop(null);
Felipe Kinoshita's avatar
Felipe Kinoshita committed
96
            pageStack.replace(todoPageComponent);
Carl Schwan's avatar
Carl Schwan committed
97
98
        }

99
100
101
102
        function onOpenAboutPage() {
            pageStack.layers.push("AboutPage.qml")
        }

Carl Schwan's avatar
Carl Schwan committed
103
104
105
106
107
108
109
110
        function onCreateNewEvent() {
            root.setUpAdd(IncidenceWrapper.TypeEvent);
        }

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

111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
        function onUndo() {
            CalendarManager.undoAction();
        }

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

        function onTodoViewSortAlphabetically() {
            pageStack.currentItem.sortBy = TodoSortFilterProxyModel.SummaryColumn;
        }

        function onTodoViewSortByDueDate() {
            pageStack.currentItem.sortBy = TodoSortFilterProxyModel.EndTimeColumn;
        }

        function onTodoViewSortByPriority() {
            pageStack.currentItem.sortBy = TodoSortFilterProxyModel.PriorityIntColumn;
        }

        function onTodoViewOrderAscending() {
            pageStack.currentItem.ascendingOrder = true;
        }

        function onTodoViewOrderDescending() {
            pageStack.currentItem.ascendingOrder = false;
        }

        function onTodoViewShowCompleted() {
            pageStack.currentItem.completedSheet.open();
        }

Carl Schwan's avatar
Carl Schwan committed
143
144
145
146
147
148
149
150
151
152
153
154
155
        function onQuit() {
             Qt.quit();
        }

        function onOpenSettings() {
            pageStack.pushDialogLayer("qrc:/SettingsPage.qml", {
                width: root.width
            }, {
                title: i18n("Settings"),
                width: root.width - (Kirigami.Units.gridUnit * 4),
                height: root.height - (Kirigami.Units.gridUnit * 3)
            })
        }
Claudio Cambra's avatar
Claudio Cambra committed
156
157
158
159
160
161
162
163
164

        function onOpenTagManager() {
            pageStack.pushDialogLayer("qrc:/TagManagerPage.qml", {
                width: root.width
            }, {
                width: Kirigami.Units.gridUnit * 30,
                height: Kirigami.Units.gridUnit * 30
            })
        }
Carl Schwan's avatar
Carl Schwan committed
165
166
    }

167
168
169
170
171
172
173
174
175
176
177
    Connections {
        target: CalendarManager

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

    property Kirigami.Action createAction: Kirigami.Action {
        text: i18n("Create")
178
179
180
        icon.name: "list-add"

        Kirigami.Action {
Carl Schwan's avatar
Carl Schwan committed
181
            id: newEventAction
182
            text: i18n("New Event…")
183
            icon.name: "resource-calendar-insert"
Carl Schwan's avatar
Carl Schwan committed
184
            onTriggered: createEventAction.trigger()
185
186
        }
        Kirigami.Action {
Carl Schwan's avatar
Carl Schwan committed
187
            id: newTodoAction
188
            text: i18n("New Task…")
189
            icon.name: "view-task-add"
Carl Schwan's avatar
Carl Schwan committed
190
            onTriggered: createTodoAction.trigger()
191
192
193
        }
    }

194
    title: if(pageStack.currentItem) {
195
196
197
198
199
200
201
202
        switch (pageStack.currentItem.objectName) {
            case "monthView":
                return i18n("Month View");
                break;
            case "scheduleView":
                return i18n("Schedule View");
                break;
            case "todoView":
203
                return i18n("Tasks View");
204
205
206
207
208
                break;
            default:
                return i18n("Calendar");
        }
    }
Carl Schwan's avatar
Carl Schwan committed
209

210
    pageStack.initialPage: Kirigami.Settings.isMobile ? scheduleViewComponent : monthViewComponent
Carl Schwan's avatar
Carl Schwan committed
211

212
213
214
215
216
217
218
219
220
221
222
223
224
    menuBar: Loader {
        id: menuLoader
        active: Kirigami.Settings.hasPlatformMenuBar != undefined ?
                !Kirigami.Settings.hasPlatformMenuBar && !Kirigami.Settings.isMobile :
                !Kirigami.Settings.isMobile

        sourceComponent: WindowMenu {
            parentWindow: root
            todoMode: pageStack.currentItem.objectName == "todoView"
            Kirigami.Theme.colorSet: Kirigami.Theme.Header
        }
    }

225
226
227
228
229
230
231
232
    footer: Loader {
        id: bottomLoader
        active: Kirigami.Settings.isMobile
        visible: pageStack.layers.currentItem.objectName != "settingsPage"

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

Felipe Kinoshita's avatar
Felipe Kinoshita committed
233
    globalDrawer: Sidebar {
234
        bottomPadding: menuLoader.active ? menuLoader.height : 0
235
        todoMode: pageStack.currentItem ? pageStack.currentItem.objectName === "todoView" : false
236
237
238
239
240
241
242
243
        onCalendarClicked: if(todoMode) {
            pageStack.currentItem.filterCollectionId = collectionId;
            pageStack.currentItem.filterCollectionDetails = CalendarManager.getCollectionDetails(pageStack.currentItem.filterCollectionId);
        }
        onCalendarCheckChanged: 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
        }
244
        onTagClicked: if(todoMode) pageStack.currentItem.filterCategoryString = tagName
Felipe Kinoshita's avatar
Felipe Kinoshita committed
245
        onViewAllTodosClicked: if(todoMode) pageStack.currentItem.filterCollectionId = -1
246
247
    }

248
249
    contextDrawer: IncidenceInfo {
        id: incidenceInfo
250

251
        bottomPadding: menuLoader.active ? menuLoader.height : 0
252
253
254
255
        contentItem.implicitWidth: Kirigami.Units.gridUnit * 25
        modal: !root.wideScreen || !enabled
        onEnabledChanged: drawerOpen = enabled && !modal
        onModalChanged: drawerOpen = !modal
256
        enabled: incidenceData != undefined && pageStack.layers.depth < 2 && pageStack.depth < 3
257
        handleVisible: enabled && pageStack.layers.depth < 2 && pageStack.depth < 3
258
        interactive: Kirigami.Settings.isMobile // Otherwise get weird bug where drawer gets dragged around despite no click
259

260
261
262
263
264
265
266
267
268
        onIncidenceDataChanged: root.openOccurrence = incidenceData;
        onVisibleChanged: {
            if(visible) {
                root.openOccurrence = incidenceData;
            } else {
                root.openOccurrence = null;
            }
        }

269
270
271
272
        onAddSubTodo: {
            setUpAddSubTodo(parentWrapper);
            if (modal) { incidenceInfo.close() }
        }
273
274
275
        onEditIncidence: {
            setUpEdit(incidencePtr, collectionId);
            if (modal) { incidenceInfo.close() }
276
        }
277
278
279
        onDeleteIncidence: {
            setUpDelete(incidencePtr, deleteDate)
            if (modal) { incidenceInfo.close() }
280
281
282
        }
    }

283
284
285
286
287
288
289
290
    DateChanger {
        id: dateChangeDrawer
        y: pageStack.globalToolBar.height - 1
        showDays: pageStack.currentItem.objectName !== "monthView"
        date: root.selectedDate
        onDateSelected: if(visible) pageStack.currentItem.setToDate(date)
    }

291
292
293
294
    IncidenceEditor {
        id: incidenceEditor
        onAdded: CalendarManager.addIncidence(incidenceWrapper)
        onEdited: CalendarManager.editIncidence(incidenceWrapper)
295
        onCancel: pageStack.pop(monthViewComponent)
296
297
    }

Carl Schwan's avatar
Carl Schwan committed
298
299
    Loader {
        active: !Kirigami.Settings.isMobile
300
301
302
        sourceComponent: GlobalMenu {
            todoMode: pageStack.currentItem.filterCollectionId !== undefined
        }
Carl Schwan's avatar
Carl Schwan committed
303
304
305
        onLoaded: item.parentWindow = root;
    }

306
307
308
309
310
311
312
    Loader {
        id: editorWindowedLoader
        active: false
        sourceComponent: Kirigami.ApplicationWindow {
            id: root

            width: Kirigami.Units.gridUnit * 40
313
            height: Kirigami.Units.gridUnit * 32
314

Carl Schwan's avatar
Carl Schwan committed
315
316
            flags: Qt.Dialog | Qt.WindowCloseButtonHint

317
            // Probably a more elegant way of accessing the editor from outside than this.
318
            property var incidenceEditor: incidenceEditorInLoader
319

320
            pageStack.initialPage: incidenceEditorInLoader
321

Carl Schwan's avatar
Carl Schwan committed
322
323
324
325
326
327
            Loader {
                active: !Kirigami.Settings.isMobile
                source: Qt.resolvedUrl("qrc:/GlobalMenu.qml")
                onLoaded: item.parentWindow = root
            }

328
329
330
331
            IncidenceEditor {
                id: incidenceEditorInLoader
                onAdded: CalendarManager.addIncidence(incidenceWrapper)
                onEdited: CalendarManager.editIncidence(incidenceWrapper)
332
333
334
335
336
337
338
339
340
                onCancel: root.close()
            }

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

    function editorToUse() {
341
        if (!Kirigami.Settings.isMobile) {
342
            editorWindowedLoader.active = true
343
            return editorWindowedLoader.item.incidenceEditor
344
        } else {
345
346
            pageStack.push(incidenceEditor);
            return incidenceEditor;
347
        }
348
349
    }

350
    function setUpAdd(type, addDate, collectionId) {
351
        let editorToUse = root.editorToUse();
352
353
        if (editorToUse.editMode || !editorToUse.incidenceWrapper) {
            editorToUse.incidenceWrapper = Qt.createQmlObject('import org.kde.kalendar 1.0; IncidenceWrapper {id: incidence}',
354
                editorToUse, "incidence");
355
356
        }
        editorToUse.editMode = false;
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373

        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;

            if(type === IncidenceWrapper.TypeEvent) {
                editorToUse.incidenceWrapper.incidenceStart = new Date(addDate.setHours(existingStart.getHours(), existingStart.getMinutes()));
                editorToUse.incidenceWrapper.incidenceEnd = new Date(addDate.setHours(existingStart.getHours() + 1, existingStart.getMinutes()));
            } else if (type === IncidenceWrapper.TypeTodo) {
                editorToUse.incidenceWrapper.incidenceEnd = new Date(addDate.setHours(existingEnd.getHours() + 1, existingEnd.getMinutes()));
            }
374
        }
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394

        if(collectionId && collectionId >= 0) {
            editorToUse.incidenceWrapper.collectionId = collectionId;
        } 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;
395
396
397
    }

    function setUpView(modelData, collectionData) {
398
399
400
        incidenceInfo.incidenceData = modelData
        incidenceInfo.collectionData = collectionData
        incidenceInfo.open()
401
402
    }

403
    function setUpEdit(incidencePtr, collectionId) {
404
        let editorToUse = root.editorToUse();
405
        editorToUse.incidenceWrapper = Qt.createQmlObject('import org.kde.kalendar 1.0; IncidenceWrapper {id: incidence}',
406
            editorToUse, "incidence");
407
408
        editorToUse.incidenceWrapper.incidencePtr = incidencePtr;
        editorToUse.incidenceWrapper.collectionId = collectionId;
409
410
411
        editorToUse.editMode = true;
    }

412
413
414
415
416
417
418
419
420
421
422
    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}',
423
            this, "incidence");
424
425
426
427
428
429
430

        todo.incidencePtr = incidencePtr;

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

433
434
    DeleteIncidenceSheet {
        id: deleteIncidenceSheet
435
        onAddException: {
436
437
438
            incidenceWrapper.recurrenceExceptionsModel.addExceptionDateTime(exceptionDate);
            CalendarManager.editIncidence(incidenceWrapper);
            deleteIncidenceSheet.close();
439
440
        }
        onAddRecurrenceEndDate: {
441
            incidenceWrapper.setRecurrenceDataItem("endDateTime", endDate);
442
443
            CalendarManager.editIncidence(incidenceWrapper);
            deleteIncidenceSheet.close();
444
        }
445
446
447
        onDeleteIncidence: {
            CalendarManager.deleteIncidence(incidencePtr);
            deleteIncidenceSheet.close();
448
        }
449
450
    }

451
    Component {
Carl Schwan's avatar
Carl Schwan committed
452
        id: monthViewComponent
453
454

        MonthView {
455
            id: monthView
456
            objectName: "monthView"
457

458
459
460
461
            titleDelegate: TitleDateButton {
                date: monthView.firstDayOfMonth
                onClicked: dateChangeDrawer.open()
            }
462
            currentDate: root.currentDate
463
            openOccurrence: root.openOccurrence
464

465
466
467
468
469
470
            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)
471

472
473
            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)
474

475
            Component.onCompleted: setToDate(root.selectedDate, true)
476

477
            actions.contextualActions: createAction
478
479
480
481
482
483
484
485
        }
    }

    Component {
        id: scheduleViewComponent

        ScheduleView {
            id: scheduleView
486
            objectName: "scheduleView"
487

488
489
490
491
492
            titleDelegate: TitleDateButton {
                date: scheduleView.startDate
                onClicked: dateChangeDrawer.open()
            }
            selectedDate: root.selectedDate
493
            openOccurrence: root.openOccurrence
494

495
496
497
            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)
498

499
            Component.onCompleted: setToDate(root.selectedDate, true)
500

501
502
            onAddIncidence: root.setUpAdd(type, addDate)
            onViewIncidence: root.setUpView(modelData, collectionData)
503
            onEditIncidence: root.setUpEdit(incidencePtr, collectionId)
504
505
            onDeleteIncidence: root.setUpDelete(incidencePtr, deleteDate)
            onCompleteTodo: root.completeTodo(incidencePtr)
506
            onAddSubTodo: root.setUpAddSubTodo(parentWrapper)
507

508
            actions.contextualActions: createAction
Carl Schwan's avatar
Carl Schwan committed
509
510
        }
    }
511
512

    Component {
Felipe Kinoshita's avatar
Felipe Kinoshita committed
513
514
515
516
        id: todoPageComponent

        TodoPage {
            id: todoPage
517
            objectName: "todoView"
518

519
            onAddTodo: root.setUpAdd(IncidenceWrapper.TypeTodo, new Date(), collectionId)
520
521
522
523
            onViewTodo: root.setUpView(todoData, collectionData)
            onEditTodo: root.setUpEdit(todoPtr, collectionId)
            onDeleteTodo: root.setUpDelete(todoPtr, deleteDate)
            onCompleteTodo: root.completeTodo(todoPtr)
524
            onAddSubTodo: root.setUpAddSubTodo(parentWrapper)
525
526
        }
    }
Carl Schwan's avatar
Carl Schwan committed
527
}