CalendarView.qml 20.1 KB
Newer Older
1
2
/*
 * Copyright 2013 Sebastian Kügler <sebas@kde.org>
3
 * Copyright 2015 Martin Klapetek <mklapetek@kde.org>
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
18
import QtQuick 2.4
19
20
21
import QtQuick.Layouts 1.1
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.calendar 2.0 as PlasmaCalendar
22
import org.kde.plasma.components 3.0 as PlasmaComponents3
23
24
import org.kde.plasma.extras 2.0 as PlasmaExtras

25
PlasmaComponents3.Page {
26
27
    id: calendar

28
    // The "sensible" values
29
30
    property int _minimumWidth: rootLayout.childrenRect.width + (calendar.paddings * 2)
    property int _minimumHeight: rootLayout.childrenRect.height + (calendar.paddings * 2) + headerArea.height
31

32
33
34
    Layout.minimumWidth: _minimumWidth
    Layout.minimumHeight: _minimumHeight
    Layout.preferredWidth: _minimumWidth
35
36
37
    Layout.preferredHeight: _minimumHeight
    Layout.maximumWidth: _minimumWidth
    Layout.maximumHeight: _minimumHeight
38

39
    readonly property int paddings: units.smallSpacing
40
    readonly property bool showAgenda: PlasmaCalendar.EventPluginsManager.enabledPlugins.length > 0
41
    readonly property bool showClocks: plasmoid.configuration.selectedTimeZones.length > 1
42

43
44
45
46
47
    property alias borderWidth: monthView.borderWidth
    property alias monthView: monthView

    property bool debug: false

48
49
50
    property bool isExpanded: plasmoid.expanded

    onIsExpandedChanged: {
51
52
        // clear all the selections when the plasmoid is showing/hiding
        monthView.resetToToday();
53
54
    }

55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
    // Header containing date and pin button
    header: PlasmaExtras.PlasmoidHeading {
        id: headerArea

        RowLayout {
            width: parent.width

            PlasmaExtras.Heading {
                Layout.fillWidth: true
                Layout.leftMargin: calendar.paddings // Match calendar title

                level: 1

                text: monthView.currentDate.toLocaleDateString(Qt.locale(), Locale.LongFormat)
            }
70
71
72
73
74
75
76
77
78
79

            PlasmaComponents3.ToolButton {
                visible: plasmoid.action("configure").enabled
                icon.name: "configure"
                onClicked: plasmoid.action("configure").trigger()
                PlasmaComponents3.ToolTip {
                    text: plasmoid.action("configure").text
                }
            }

80
81
82
83
84
85
86
87
88
89
90
91
            // Allows the user to keep the calendar open for reference
            PlasmaComponents3.ToolButton {
                checkable: true
                checked: plasmoid.configuration.pin
                onToggled: plasmoid.configuration.pin = checked
                icon.name: "window-pin"
                PlasmaComponents3.ToolTip {
                    text: i18n("Keep Open")
                }
            }
        }
    }
92
93
94
95
96
    // Top-level layout containing:
    // - Left column with current date header, calendar, and agenda view
    // - Right column with world clocks
    RowLayout {
        id: rootLayout
97
98
99
100

        anchors {
            top: parent.top
            left: parent.left
101
            margins: calendar.paddings
102
103
        }

104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
        spacing: calendar.paddings

        // Left column containing calendar
        // ===============================
        // TODO KF6: remove the `Item` wrapper, which this is only needed since
        // PlasmaCalendar.MonthView internally has `anchors.fill:parent` set on
        // it, erroneously expecting to never be in a Layout
        Item {
            Layout.fillWidth: true
            Layout.minimumHeight: units.gridUnit * 22
            Layout.minimumWidth: units.gridUnit * 22

            PlasmaCalendar.MonthView {
                id: monthView
                borderOpacity: 0.25
                today: root.tzDate
                showWeekNumbers: plasmoid.configuration.showWeekNumbers
            }
        }
123

124

125
126
127
128
        // Vertical separator line between columns
        // =======================================
        PlasmaCore.SvgItem {
            visible: rightColumn.visible
129

130
131
132
133
134
            Layout.preferredWidth: naturalSize.width
            Layout.fillHeight: true
            // Unify margins because the calendar includes its own
            Layout.topMargin: calendar.paddings
            Layout.rightMargin: calendar.paddings
135

136
137
138
            elementId: "vertical-line"
            svg: PlasmaCore.Svg {
                imagePath: "widgets/line"
139
            }
140
        }
141
142


143
144
145
146
        // Right column containing agenda view and time zones
        // ==================================================
        ColumnLayout {
            id: rightColumn
147

148
149
150
151
152
153
154
155
156
            visible: agenda.visible || worldClocks.visible

            Layout.minimumWidth: units.gridUnit * 14

            // Agenda view stuff
            // -----------------
            // Header text
            PlasmaExtras.Heading {
                visible: agenda.visible
157

158
159
                Layout.fillWidth: true

160
                level: 2
161

162
163
164
165
                text: i18n("Events")
                maximumLineCount: 1
                elide: Text.ElideRight
            }
166

167
            // Agenda view itself
168
169
170
            Item {
                id: agenda
                visible: calendar.showAgenda
171

172
                Layout.fillWidth: true
173
174
175
                Layout.fillHeight: true
                Layout.minimumHeight: units.gridUnit * 4
                Layout.leftMargin: -units.smallSpacing
176

177
178
179
180
181
182
183
184
185
186
                function formatDateWithoutYear(date) {
                    // Unfortunatelly Qt overrides ECMA's Date.toLocaleDateString(),
                    // which is able to return locale-specific date-and-month-only date
                    // formats, with its dumb version that only supports Qt::DateFormat
                    // enum subset. So to get a day-and-month-only date format string we
                    // must resort to this magic and hope there are no locales that use
                    // other separators...
                    var format = Qt.locale().dateFormat(Locale.ShortFormat).replace(/[./ ]*Y{2,4}[./ ]*/i, '');
                    return Qt.formatDate(date, format);
                }
187

188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
                function dateEquals(date1, date2) {
                    var values1 = [
                        date1.getFullYear(),
                        date1.getMonth(),
                        date1.getDate()
                    ];

                    var values2 = [
                        date2.getFullYear(),
                        date2.getMonth(),
                        date2.getDate()
                    ];

                    return values1.every((value, index) => {
                        return (value === values2[index]);
                    }, false)
                }

206
207
208
209
210
211
212
213
214
215
216
217
218
219
                Connections {
                    target: monthView

                    onCurrentDateChanged: {
                        // Apparently this is needed because this is a simple QList being
                        // returned and if the list for the current day has 1 event and the
                        // user clicks some other date which also has 1 event, QML sees the
                        // sizes match and does not update the labels with the content.
                        // Resetting the model to null first clears it and then correct data
                        // are displayed.
                        holidaysList.model = null;
                        holidaysList.model = monthView.daysModel.eventsForDate(monthView.currentDate);
                    }
                }
220

221
222
223
224
                Connections {
                    target: monthView.daysModel

                    onAgendaUpdated: {
225
                        if (agenda.dateEquals(updatedDate, monthView.currentDate)) {
226
227
                            holidaysList.model = null;
                            holidaysList.model = monthView.daysModel.eventsForDate(monthView.currentDate);
228
                        }
229
230
231
232
233
234
235
236
237
238
                    }
                }

                Connections {
                    target: plasmoid.configuration

                    onEnabledCalendarPluginsChanged: {
                        PlasmaCalendar.EventPluginsManager.enabledPlugins = plasmoid.configuration.enabledCalendarPlugins;
                    }
                }
239

240
241
242
243
244
245
246
247
                Binding {
                    target: plasmoid
                    property: "hideOnWindowDeactivate"
                    value: !plasmoid.configuration.pin
                }

                TextMetrics {
                    id: dateLabelMetrics
248

249
250
251
                    // Date/time are arbitrary values with all parts being two-digit
                    readonly property string timeString: Qt.formatTime(new Date(2000, 12, 12, 12, 12, 12, 12))
                    readonly property string dateString: agenda.formatDateWithoutYear(new Date(2000, 12, 12, 12, 12, 12))
252

253
254
255
256
257
258
259
260
261
262
263
                    font: theme.defaultFont
                    text: timeString.length > dateString.length ? timeString : dateString
                }

                PlasmaExtras.ScrollArea {
                    id: holidaysView
                    anchors.fill: parent

                    ListView {
                        id: holidaysList

264
                        delegate: PlasmaExtras.ListItem {
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
                            id: eventItem
                            property bool hasTime: {
                                // Explicitly all-day event
                                if (modelData.isAllDay) {
                                    return false;
                                }
                                // Multi-day event which does not start or end today (so
                                // is all-day from today's point of view)
                                if (modelData.startDateTime - monthView.currentDate < 0 &&
                                    modelData.endDateTime - monthView.currentDate > 86400000) { // 24hrs in ms
                                    return false;
                                }

                                // Non-explicit all-day event
                                var startIsMidnight = modelData.startDateTime.getHours() === 0
                                                && modelData.startDateTime.getMinutes() === 0;

                                var endIsMidnight = modelData.endDateTime.getHours() === 0
                                                && modelData.endDateTime.getMinutes() === 0;

                                var sameDay = modelData.startDateTime.getDate() === modelData.endDateTime.getDate()
                                        && modelData.startDateTime.getDay() === modelData.endDateTime.getDay()

                                if (startIsMidnight && endIsMidnight && sameDay) {
                                    return false
                                }

                                return true;
                            }
294

295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
                            PlasmaCore.ToolTipArea {
                                width: parent.width
                                height: eventGrid.height
                                active: eventTitle.truncated || eventDescription.truncated
                                mainText: active ? eventTitle.text : ""
                                subText: active ? eventDescription.text : ""

                                GridLayout {
                                    id: eventGrid
                                    columns: 3
                                    rows: 2
                                    rowSpacing: 0
                                    columnSpacing: 2 * units.smallSpacing

                                    width: parent.width

                                    Rectangle {
                                        id: eventColor

                                        Layout.row: 0
                                        Layout.column: 0
                                        Layout.rowSpan: 2
                                        Layout.fillHeight: true

                                        color: modelData.eventColor
                                        width: 5 * units.devicePixelRatio
                                        visible: modelData.eventColor !== ""
                                    }

324
                                    PlasmaComponents3.Label {
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
                                        id: startTimeLabel

                                        readonly property bool startsToday: modelData.startDateTime - monthView.currentDate >= 0
                                        readonly property bool startedYesterdayLessThan12HoursAgo: modelData.startDateTime - monthView.currentDate >= -43200000 //12hrs in ms

                                        Layout.row: 0
                                        Layout.column: 1
                                        Layout.minimumWidth: dateLabelMetrics.width

                                        text: startsToday || startedYesterdayLessThan12HoursAgo
                                                ? Qt.formatTime(modelData.startDateTime)
                                                : agenda.formatDateWithoutYear(modelData.startDateTime)
                                        horizontalAlignment: Qt.AlignRight
                                        visible: eventItem.hasTime
                                    }

341
                                    PlasmaComponents3.Label {
342
343
344
345
346
347
348
349
350
351
352
353
354
                                        id: endTimeLabel

                                        readonly property bool endsToday: modelData.endDateTime - monthView.currentDate <= 86400000 // 24hrs in ms
                                        readonly property bool endsTomorrowInLessThan12Hours: modelData.endDateTime - monthView.currentDate <= 86400000 + 43200000 // 36hrs in ms

                                        Layout.row: 1
                                        Layout.column: 1
                                        Layout.minimumWidth: dateLabelMetrics.width

                                        text: endsToday || endsTomorrowInLessThan12Hours
                                                ? Qt.formatTime(modelData.endDateTime)
                                                : agenda.formatDateWithoutYear(modelData.endDateTime)
                                        horizontalAlignment: Qt.AlignRight
355
                                        opacity: 0.7
356
357
358
359

                                        visible: eventItem.hasTime
                                    }

360
                                    PlasmaComponents3.Label {
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
                                        id: eventTitle

                                        readonly property bool wrap: eventDescription.text === ""

                                        Layout.row: 0
                                        Layout.rowSpan: wrap ? 2 : 1
                                        Layout.column: 2
                                        Layout.fillWidth: true

                                        elide: Text.ElideRight
                                        text: modelData.title
                                        verticalAlignment: Text.AlignVCenter
                                        maximumLineCount: 2
                                        wrapMode: wrap ? Text.Wrap : Text.NoWrap
                                    }

377
                                    PlasmaComponents3.Label {
378
379
                                        id: eventDescription

380
381
                                        opacity: 0.7

382
383
384
385
386
387
388
389
390
391
392
393
394
                                        Layout.row: 1
                                        Layout.column: 2
                                        Layout.fillWidth: true

                                        elide: Text.ElideRight
                                        text: modelData.description
                                        verticalAlignment: Text.AlignVCenter
                                        enabled: false

                                        visible: text !== ""
                                    }
                                }
                            }
395
396
                        }
                    }
397
                }
398

399
400
401
402
403
404
405
406
407
408
409
410
411
                PlasmaExtras.Heading {
                    anchors.fill: holidaysView
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                    anchors.leftMargin: units.largeSpacing
                    anchors.rightMargin: units.largeSpacing
                    text: monthView.isToday(monthView.currentDate) ? i18n("No events for today")
                                                                : i18n("No events for this day");
                    level: 3
                    enabled: false
                    visible: holidaysList.count == 0
                }
            }
412

413
414
415
            // Horizontal separator line between events and time zones
            PlasmaCore.SvgItem {
                visible: worldClocks.visible && agenda.visible
416

417
418
                Layout.fillWidth: true
                Layout.preferredHeight: naturalSize.height
419

420
421
422
423
424
                elementId: "horizontal-line"
                svg: PlasmaCore.Svg {
                    imagePath: "widgets/line"
                }
            }
425

426
427
428
429
430
            // Clocks stuff
            // ------------
            // Header text
            PlasmaExtras.Heading {
                visible: worldClocks.visible
431

432
                Layout.fillWidth: true
433

434
                level: 2
435

436
437
438
439
                text: i18n("Time Zones")
                maximumLineCount: 1
                elide: Text.ElideRight
            }
440

441
            // Clocks view itself
442
            PlasmaExtras.ScrollArea {
443
444
445
                id: worldClocks
                visible: calendar.showClocks

446
                Layout.fillWidth: true
447
448
                Layout.fillHeight: !agenda.visible
                Layout.leftMargin: -units.smallSpacing
449

450
451
                ListView {
                    id: clocksList
452

453
454
                    width: parent.width

455
456
457
458
459
                    model: {
                        var timezones = [];
                        for (var i = 0; i < plasmoid.configuration.selectedTimeZones.length; i++) {
                            timezones.push(plasmoid.configuration.selectedTimeZones[i]);
                        }
460

461
462
                        return timezones;
                    }
463

464
                    delegate: PlasmaExtras.ListItem {
465
466
                        id: listItem
                        readonly property bool isCurrentTimeZone: modelData === plasmoid.configuration.lastSelectedTimezone
467
468
469
470
                        separatorVisible: false

                        width: clocksList.width
                        height: units.gridUnit + units.smallSpacing
471

472
473
474
475
476
477
                        MouseArea {
                            anchors.fill: parent
                            cursorShape: Qt.PointingHandCursor
                            onClicked: plasmoid.configuration.lastSelectedTimezone = modelData
                        }

478
479
                        RowLayout {
                            anchors.fill: parent
480

481
                            PlasmaComponents3.Label {
482
483
484
                                text: root.nameForZone(modelData)
                                font.weight: listItem.isCurrentTimeZone ? Font.Bold : Font.Normal
                                maximumLineCount: 1
485
                                elide: Text.ElideRight
486
                            }
487

488
                            PlasmaComponents3.Label {
489
                                Layout.fillWidth: true
490
                                horizontalAlignment: Qt.AlignRight
491
492
493
                                text: root.timeForZone(modelData)
                                font.weight: listItem.isCurrentTimeZone ? Font.Bold : Font.Normal
                                elide: Text.ElideRight
494
                                maximumLineCount: 1
495
                            }
496
497
498
499
500
501
                        }
                    }
                }
            }
        }
    }
502
}