diff --git a/applets/digital-clock/package/contents/ui/CalendarView.qml b/applets/digital-clock/package/contents/ui/CalendarView.qml --- a/applets/digital-clock/package/contents/ui/CalendarView.qml +++ b/applets/digital-clock/package/contents/ui/CalendarView.qml @@ -25,25 +25,20 @@ Item { id: calendar + // The "sensible" values + property int _minimumWidth: rootLayout.childrenRect.width + (rootLayout.anchors.margins * 2) + property int _minimumHeight: rootLayout.childrenRect.height + (rootLayout.anchors.margins * 2) + Layout.minimumWidth: _minimumWidth Layout.minimumHeight: _minimumHeight - - // The "sensible" values - property int _minimumWidth: (showAgenda ? agendaViewWidth : 0) + monthViewWidth - property int _minimumHeight: units.gridUnit * 14 Layout.preferredWidth: _minimumWidth - Layout.preferredHeight: _minimumHeight * 1.5 - Layout.maximumWidth: Layout.preferredWidth - Layout.maximumHeight: Layout.preferredHeight + Layout.preferredHeight: _minimumHeight + Layout.maximumWidth: _minimumWidth + Layout.maximumHeight: _minimumHeight readonly property bool showAgenda: PlasmaCalendar.EventPluginsManager.enabledPlugins.length > 0 + readonly property bool showClocks: plasmoid.configuration.selectedTimeZones.length > 1 - readonly property int agendaViewWidth: _minimumHeight * 1.5 - readonly property int monthViewWidth: monthView.showWeekNumbers ? Math.round(_minimumHeight * 1.75) : Math.round(_minimumHeight * 1.5) - - property int boxWidth: (agendaViewWidth + monthViewWidth - ((showAgenda ? 3 : 4) * spacing)) / 2 - - property int spacing: units.largeSpacing property alias borderWidth: monthView.borderWidth property alias monthView: monthView @@ -56,312 +51,416 @@ monthView.resetToToday(); } - Item { - id: agenda - visible: calendar.showAgenda + // Top-level layout containing: + // - Left column with current date header, calendar, and agenda view + // - Right column with world clocks + RowLayout { + id: rootLayout - width: boxWidth anchors { top: parent.top left: parent.left - bottom: parent.bottom - leftMargin: spacing - topMargin: spacing - bottomMargin: spacing + margins: units.largeSpacing } - function dateString(format) { - return Qt.formatDate(monthView.currentDate, format); - } + // Left column containing header, calendar, and event view + ColumnLayout { + id: leftColumnLayout - 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); - } + Layout.minimumWidth: units.gridUnit * 18 - 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); + function dateString(format) { + return Qt.formatDate(monthView.currentDate, format); } - } - Connections { - target: monthView.daysModel + // Header for the calendar: Current day, month, and year + RowLayout { + id: currentDateHeaderLayout + + PlasmaComponents.Label { + height: dayAndMonthLayout.height + width: paintedWidth + font.pixelSize: dayAndMonthLayout.height + font.weight: Font.Light + text: leftColumnLayout.dateString("dd") + opacity: 0.6 + } - onAgendaUpdated: { - // Checks if the dates are the same, comparing the date objects - // directly won't work and this does a simple integer subtracting - // so should be fastest. One of the JS weirdness. - if (updatedDate - monthView.currentDate === 0) { - holidaysList.model = null; - holidaysList.model = monthView.daysModel.eventsForDate(monthView.currentDate); + ColumnLayout { + id: dayAndMonthLayout + PlasmaExtras.Heading { + level: 1 + elide: Text.ElideRight + text: leftColumnLayout.dateString("dddd") + } + PlasmaComponents.Label { + elide: Text.ElideRight + text: Qt.locale().standaloneMonthName(monthView.currentDate.getMonth()) + + leftColumnLayout.dateString(" yyyy") + } } } - } - Connections { - target: plasmoid.configuration - onEnabledCalendarPluginsChanged: { - PlasmaCalendar.EventPluginsManager.enabledPlugins = plasmoid.configuration.enabledCalendarPlugins; + // Horizontal separator line + PlasmaCore.SvgItem { + Layout.fillWidth: true + Layout.preferredHeight: naturalSize.height + elementId: "horizontal-line" + svg: PlasmaCore.Svg { + imagePath: "widgets/line" + } } - } - Binding { - target: plasmoid - property: "hideOnWindowDeactivate" - value: !plasmoid.configuration.pin - } - - PlasmaComponents.Label { - id: dayLabel - anchors.left: parent.left - height: dayHeading.height + dateHeading.height - width: paintedWidth - font.pixelSize: height - font.weight: Font.Light - text: agenda.dateString("dd") - opacity: 0.6 - } - PlasmaExtras.Heading { - id: dayHeading - anchors { - top: parent.top - left: dayLabel.right - right: parent.right - leftMargin: spacing / 2 - } - level: 1 - elide: Text.ElideRight - text: agenda.dateString("dddd") - } - PlasmaComponents.Label { - id: dateHeading - anchors { - top: dayHeading.bottom - left: dayLabel.right - right: parent.right - leftMargin: spacing / 2 + // 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 { + id: monthViewContainer + Layout.fillWidth: true + Layout.minimumHeight: units.gridUnit * 12 + + PlasmaCalendar.MonthView { + id: monthView + borderOpacity: 0.25 + today: root.tzDate + showWeekNumbers: plasmoid.configuration.showWeekNumbers + } } - elide: Text.ElideRight - text: Qt.locale().standaloneMonthName(monthView.currentDate.getMonth()) - + agenda.dateString(" yyyy") - } - TextMetrics { - id: dateLabelMetrics - // 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)) + // Agenda view + Item { + id: agenda + visible: calendar.showAgenda - font: theme.defaultFont - text: timeString.length > dateString.length ? timeString : dateString - } + Layout.fillWidth: true + Layout.minimumHeight: units.gridUnit * 12 - PlasmaExtras.ScrollArea { - id: holidaysView - anchors { - top: dateHeading.bottom - left: parent.left - right: parent.right - bottom: parent.bottom - } - flickableItem.boundsBehavior: Flickable.StopAtBounds + 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); + } - ListView { - id: holidaysList + 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); + } + } - delegate: PlasmaComponents.ListItem { - 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; + Connections { + target: monthView.daysModel + + onAgendaUpdated: { + // Checks if the dates are the same, comparing the date objects + // directly won't work and this does a simple integer subtracting + // so should be fastest. One of the JS weirdness. + if (updatedDate - monthView.currentDate === 0) { + holidaysList.model = null; + holidaysList.model = monthView.daysModel.eventsForDate(monthView.currentDate); } + } + } + + Connections { + target: plasmoid.configuration + + onEnabledCalendarPluginsChanged: { + PlasmaCalendar.EventPluginsManager.enabledPlugins = plasmoid.configuration.enabledCalendarPlugins; + } + } - // Non-explicit all-day event - var startIsMidnight = modelData.startDateTime.getHours() === 0 - && modelData.startDateTime.getMinutes() === 0; + Binding { + target: plasmoid + property: "hideOnWindowDeactivate" + value: !plasmoid.configuration.pin + } + + TextMetrics { + id: dateLabelMetrics - var endIsMidnight = modelData.endDateTime.getHours() === 0 - && modelData.endDateTime.getMinutes() === 0; + // 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)) - var sameDay = modelData.startDateTime.getDate() === modelData.endDateTime.getDate() - && modelData.startDateTime.getDay() === modelData.endDateTime.getDay() + font: theme.defaultFont + text: timeString.length > dateString.length ? timeString : dateString + } + + PlasmaExtras.ScrollArea { + id: holidaysView + anchors.fill: parent + + ListView { + id: holidaysList + + delegate: PlasmaComponents.ListItem { + 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; + } - if (startIsMidnight && endIsMidnight && sameDay) { - return false + 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 !== "" + } + + PlasmaComponents.Label { + 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 + } + + PlasmaComponents.Label { + 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 + enabled: false + + visible: eventItem.hasTime + } + + PlasmaComponents.Label { + id: eventTitle + + readonly property bool wrap: eventDescription.text === "" + + Layout.row: 0 + Layout.rowSpan: wrap ? 2 : 1 + Layout.column: 2 + Layout.fillWidth: true + + font.weight: Font.Bold + elide: Text.ElideRight + text: modelData.title + verticalAlignment: Text.AlignVCenter + maximumLineCount: 2 + wrapMode: wrap ? Text.Wrap : Text.NoWrap + } + + PlasmaComponents.Label { + id: eventDescription + + Layout.row: 1 + Layout.column: 2 + Layout.fillWidth: true + + elide: Text.ElideRight + text: modelData.description + verticalAlignment: Text.AlignVCenter + enabled: false + + visible: text !== "" + } + } + } } - return true; + section.property: "modelData.eventType" + section.delegate: PlasmaExtras.Heading { + level: 3 + elide: Text.ElideRight + text: section + } } + } - 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 !== "" - } + 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 + } + } + } - PlasmaComponents.Label { - id: startTimeLabel - readonly property bool startsToday: modelData.startDateTime - monthView.currentDate >= 0 - readonly property bool startedYesterdayLessThan12HoursAgo: modelData.startDateTime - monthView.currentDate >= -43200000 //12hrs in ms + // Vertical separator line between columns + PlasmaCore.SvgItem { + visible: calendar.showClocks + Layout.preferredWidth: naturalSize.width + Layout.leftMargin: units.largeSpacing + Layout.rightMargin: units.largeSpacing + Layout.fillHeight: true + elementId: "vertical-line" + svg: PlasmaCore.Svg { + imagePath: "widgets/line" + } + } - 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 - } + // List of world clocks + ColumnLayout { + id: worldClocks - PlasmaComponents.Label { - id: endTimeLabel + visible: calendar.showClocks - 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.minimumWidth: units.gridUnit * 12 + Layout.fillHeight: true - Layout.row: 1 - Layout.column: 1 - Layout.minimumWidth: dateLabelMetrics.width + // Header text + PlasmaExtras.Heading { + Layout.minimumHeight: currentDateHeaderLayout.height + verticalAlignment: Text.AlignBottom + text: i18n("Time Zones") + maximumLineCount: 1 + elide: Text.ElideRight + } - text: endsToday || endsTomorrowInLessThan12Hours - ? Qt.formatTime(modelData.endDateTime) - : agenda.formatDateWithoutYear(modelData.endDateTime) - horizontalAlignment: Qt.AlignRight - enabled: false + // Horizontal separator line + PlasmaCore.SvgItem { + Layout.fillWidth: true + Layout.preferredHeight: naturalSize.height + elementId: "horizontal-line" + svg: PlasmaCore.Svg { + imagePath: "widgets/line" + } + } - visible: eventItem.hasTime - } + // Clocks list + PlasmaExtras.ScrollArea { + Layout.fillWidth: true + Layout.fillHeight: true - PlasmaComponents.Label { - id: eventTitle + ListView { + id: clocksList - readonly property bool wrap: eventDescription.text === "" + model: { + var timezones = []; + for (var i = 0; i < plasmoid.configuration.selectedTimeZones.length; i++) { + timezones.push(plasmoid.configuration.selectedTimeZones[i]); + } - Layout.row: 0 - Layout.rowSpan: wrap ? 2 : 1 - Layout.column: 2 - Layout.fillWidth: true + return timezones; + } - font.weight: Font.Bold - elide: Text.ElideRight - text: modelData.title - verticalAlignment: Text.AlignVCenter - maximumLineCount: 2 - wrapMode: wrap ? Text.Wrap : Text.NoWrap - } + delegate: PlasmaComponents.ListItem { + id: listItem + readonly property bool isCurrentTimeZone: modelData === plasmoid.configuration.lastSelectedTimezone - PlasmaComponents.Label { - id: eventDescription + ColumnLayout { - Layout.row: 1 - Layout.column: 2 - Layout.fillWidth: true + width: clocksList.width + spacing: 0 + PlasmaExtras.Heading { + Layout.fillWidth: true + level: 3 + text: root.nameForZone(modelData) + font.weight: listItem.isCurrentTimeZone ? Font.Bold : Font.Normal + maximumLineCount: 1 elide: Text.ElideRight - text: modelData.description - verticalAlignment: Text.AlignVCenter - enabled: false + } - visible: text !== "" + PlasmaExtras.Heading { + Layout.fillWidth: true + level: 5 + text: root.timeForZone(modelData) + font.weight: listItem.isCurrentTimeZone ? Font.Bold : Font.Normal + maximumLineCount: 2 + elide: Text.ElideRight + opacity: 0.6 } } } } - - section.property: "modelData.eventType" - section.delegate: PlasmaExtras.Heading { - level: 3 - elide: Text.ElideRight - text: section - } } } - - 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 - } - } - Item { - id: cal - width: boxWidth - anchors { - top: parent.top - right: parent.right - bottom: parent.bottom - margins: spacing - } - - PlasmaCalendar.MonthView { - id: monthView - borderOpacity: 0.25 - today: root.tzDate - showWeekNumbers: plasmoid.configuration.showWeekNumbers - anchors.fill: parent - } - } // Allows the user to keep the calendar open for reference PlasmaComponents.ToolButton { diff --git a/applets/digital-clock/package/contents/ui/Tooltip.qml b/applets/digital-clock/package/contents/ui/Tooltip.qml --- a/applets/digital-clock/package/contents/ui/Tooltip.qml +++ b/applets/digital-clock/package/contents/ui/Tooltip.qml @@ -22,7 +22,6 @@ import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.plasma.extras 2.0 as PlasmaExtras -import org.kde.plasma.private.digitalclock 1.0 Item { id: tooltipContentItem @@ -37,36 +36,6 @@ PlasmaCore.ColorScope.colorGroup: PlasmaCore.Theme.NormalColorGroup PlasmaCore.ColorScope.inherit: false - function timeForZone(zone) { - var compactRepresentationItem = plasmoid.compactRepresentationItem; - if (!compactRepresentationItem) { - return ""; - } - - // get the time for the given timezone from the dataengine - var now = dataSource.data[zone]["DateTime"]; - // get current UTC time - var msUTC = now.getTime() + (now.getTimezoneOffset() * 60000); - // add the dataengine TZ offset to it - var dateTime = new Date(msUTC + (dataSource.data[zone]["Offset"] * 1000)); - - var formattedTime = Qt.formatTime(dateTime, compactRepresentationItem.timeFormat); - - if (dateTime.getDay() !== dataSource.data["Local"]["DateTime"].getDay()) { - formattedTime += " (" + Qt.formatDate(dateTime, compactRepresentationItem.dateFormat) + ")"; - } - - return formattedTime; - } - - function nameForZone(zone) { - // add the timezone string to the clock - var timezoneString = plasmoid.configuration.displayTimezoneAsCode ? dataSource.data[zone]["Timezone Abbreviation"] - : TimezonesI18n.i18nCity(dataSource.data[zone]["Timezone City"]); - - return timezoneString; - } - RowLayout { anchors { left: parent.left diff --git a/applets/digital-clock/package/contents/ui/main.qml b/applets/digital-clock/package/contents/ui/main.qml --- a/applets/digital-clock/package/contents/ui/main.qml +++ b/applets/digital-clock/package/contents/ui/main.qml @@ -50,6 +50,36 @@ root.allTimezones = tz.concat(plasmoid.configuration.selectedTimeZones); } + function timeForZone(zone) { + var compactRepresentationItem = plasmoid.compactRepresentationItem; + if (!compactRepresentationItem) { + return ""; + } + + // get the time for the given timezone from the dataengine + var now = dataSource.data[zone]["DateTime"]; + // get current UTC time + var msUTC = now.getTime() + (now.getTimezoneOffset() * 60000); + // add the dataengine TZ offset to it + var dateTime = new Date(msUTC + (dataSource.data[zone]["Offset"] * 1000)); + + var formattedTime = Qt.formatTime(dateTime, compactRepresentationItem.timeFormat); + + if (dateTime.getDay() !== dataSource.data["Local"]["DateTime"].getDay()) { + formattedTime += " (" + Qt.formatDate(dateTime, compactRepresentationItem.dateFormat) + ")"; + } + + return formattedTime; + } + + function nameForZone(zone) { + // add the timezone string to the clock + var timezoneString = plasmoid.configuration.displayTimezoneAsCode ? dataSource.data[zone]["Timezone Abbreviation"] + : TimezonesI18n.i18nCity(dataSource.data[zone]["Timezone City"]); + + return timezoneString; + } + Plasmoid.preferredRepresentation: Plasmoid.compactRepresentation Plasmoid.compactRepresentation: DigitalClock { } Plasmoid.fullRepresentation: CalendarView { }