CalendarView.qml 20.2 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
                            id: eventItem
266
                            implicitHeight: eventGrid.height + PlasmaCore.Units.smallSpacing * 2
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 294
                            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;
                            }
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 324
                            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 !== ""
                                    }

325
                                    PlasmaComponents3.Label {
326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341
                                        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
                                    }

342
                                    PlasmaComponents3.Label {
343 344 345 346 347 348 349 350 351 352 353 354 355
                                        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
356
                                        opacity: 0.7
357 358 359 360

                                        visible: eventItem.hasTime
                                    }

361
                                    PlasmaComponents3.Label {
362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377
                                        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
                                    }

378
                                    PlasmaComponents3.Label {
379 380
                                        id: eventDescription

381 382
                                        opacity: 0.7

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

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

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

400 401 402 403 404 405 406 407 408 409 410 411 412
                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
                }
            }
413

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

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

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

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

433
                Layout.fillWidth: true
434

435
                level: 2
436

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

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

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

451 452
                ListView {
                    id: clocksList
453

454 455
                    width: parent.width

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

462 463
                        return timezones;
                    }
464

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

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

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

479 480
                        RowLayout {
                            anchors.fill: parent
481

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

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