Commit 87f90472 authored by Nate Graham's avatar Nate Graham 🔩

[applets/digital-clock] Overhaul how to change between timezones

Right now we have a usability problem: the Digital Clock applet has two
ways to let you change the timezone displayed in the applet itself, but
has no easy link to the correct way to change the timezone systemwide. As
a result, users change the timezone in the clock and wonder why times are
wrong everywhere else throughout the OS.

This commit removes the feature of changing the timezone in just the
clock, and replaces it with an overhauled Time Zones page in the config
dialog and a new button in the popup that links you to the Date & Time
KCM where you can change the systemwide time zone in the correct way.

I anticipate that this may cause some grumbling from people who currently
use the "mouse wheel to cycle through timezones" behavior, but now that
all the timezones and their times are displayed prominently in the popup,
you can just click on the applet to show the popup instead of rolling the
mouse wheel over it to cycle through times; it's different, but no worse.

BUG: 428096
FIXED-IN: 5.21
parent bafd2ad7
......@@ -42,17 +42,13 @@
<default>default</default>
</entry>
<entry name="selectedTimeZones" type="StringList">
<label>A list of the time zones available on mouse wheel. Requires wheelChangesTimezone to be true. Format is "Europe/London". Special entry "Local" indicates system time zone.</label>
<label>A list of the configured time zones. Format is "Europe/London". Special entry "Local" indicates system time zone.</label>
<default>Local</default>
</entry>
<entry name="lastSelectedTimezone" type="String">
<label>When multiple time zones are configured, this is the one shown on widget restore.</label>
<label>When multiple time zones are configured, this is the one shown on widget restore. Typically the system's current timezone.</label>
<default>Local</default>
</entry>
<entry name="wheelChangesTimezone" type="Bool">
<label>Whether the mouse wheel switches between the timezones configured in selectedTimeZones.</label>
<default>false</default>
</entry>
<entry name="displayTimezoneAsCode" type="Bool">
<label>Whether the timezone is displayed as a code i.e. "GMT" or full text i.e. "London".</label>
<default>true</default>
......
......@@ -17,6 +17,8 @@
*/
import QtQuick 2.4
import QtQuick.Layouts 1.1
import org.kde.kquickcontrolsaddons 2.0 // For kcmshell
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.calendar 2.0 as PlasmaCalendar
import org.kde.plasma.components 3.0 as PlasmaComponents3
......@@ -429,17 +431,31 @@ PlasmaComponents3.Page {
// Clocks stuff
// ------------
// Header text
PlasmaExtras.Heading {
// Header text + button to change time & timezone
RowLayout {
Layout.fillWidth: true
visible: worldClocks.visible
Layout.fillWidth: true
PlasmaExtras.Heading {
Layout.fillWidth: true
level: 2
level: 2
text: i18n("Time Zones")
maximumLineCount: 1
elide: Text.ElideRight
text: i18n("Time Zones")
maximumLineCount: 1
elide: Text.ElideRight
}
PlasmaComponents3.ToolButton {
visible: KCMShell.authorize("clock.desktop").length > 0
text: i18n("Switch...")
icon.name: "preferences-system-time"
onClicked: KCMShell.openSystemSettings("clock")
PlasmaComponents3.ToolTip {
text: i18n("Switch to another timezone")
}
}
}
// Clocks view itself
......@@ -473,12 +489,6 @@ PlasmaComponents3.Page {
width: clocksList.width
height: units.gridUnit + units.smallSpacing
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: plasmoid.configuration.lastSelectedTimezone = modelData
}
RowLayout {
anchors.fill: parent
......
......@@ -411,46 +411,8 @@ Item {
]
MouseArea {
id: mouseArea
property int wheelDelta: 0
anchors.fill: parent
onClicked: plasmoid.expanded = !plasmoid.expanded
onWheel: {
if (!plasmoid.configuration.wheelChangesTimezone) {
return;
}
var delta = wheel.angleDelta.y || wheel.angleDelta.x
var newIndex = main.tzIndex;
wheelDelta += delta;
// magic number 120 for common "one click"
// See: https://doc.qt.io/qt-5/qml-qtquick-wheelevent.html#angleDelta-prop
while (wheelDelta >= 120) {
wheelDelta -= 120;
newIndex--;
}
while (wheelDelta <= -120) {
wheelDelta += 120;
newIndex++;
}
if (newIndex >= plasmoid.configuration.selectedTimeZones.length) {
newIndex = 0;
} else if (newIndex < 0) {
newIndex = plasmoid.configuration.selectedTimeZones.length - 1;
}
if (newIndex !== main.tzIndex) {
plasmoid.configuration.lastSelectedTimezone = plasmoid.configuration.selectedTimeZones[newIndex];
main.tzIndex = newIndex;
dataSource.dataChanged();
setupLabels();
}
}
}
/*
......
......@@ -23,84 +23,197 @@ import QtQuick.Controls 2.8 as QQC2
import QtQuick.Layouts 1.0
import QtQuick.Dialogs 1.1
import org.kde.kquickcontrolsaddons 2.0 // For kcmshell
import org.kde.plasma.private.digitalclock 1.0
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.kirigami 2.8 as Kirigami
import org.kde.kirigami 2.14 as Kirigami
ColumnLayout {
id: timeZonesPage
property alias cfg_selectedTimeZones: timeZones.selectedTimeZones
property alias cfg_wheelChangesTimezone: enableWheelCheckBox.checked
TimeZoneModel {
id: timeZones
id: timeZones
onSelectedTimeZonesChanged: {
if (selectedTimeZones.length === 0) {
// Don't let the user remove all time zones
messageWidget.visible = true;
timeZones.selectLocalTimeZone();
}
}
}
Kirigami.InlineMessage {
id: messageWidget
QQC2.ScrollView {
Layout.fillWidth: true
Layout.margins: Kirigami.Units.smallSpacing
type: Kirigami.MessageType.Warning
text: i18n("At least one time zone needs to be enabled. 'Local' was enabled automatically.")
showCloseButton: true
Layout.preferredHeight: Kirigami.Units.gridUnit * 19
Component.onCompleted: background.visible = true // enable border
ListView {
id: configuredTimezoneList
clip: true // Avoid visual glitches
focus: true // keyboard navigation
activeFocusOnTab: true // keyboard navigation
model: TimeZoneFilterProxy {
sourceModel: timeZones
onlyShowChecked: true
}
// Using a hand-rolled delegate because Kirigami.BasicListItem doesn't
// support being given extra items to display on the end
delegate: Kirigami.AbstractListItem {
width: configuredTimezoneList.width
// Don't need a highlight effect since the list item does
// nothing when clicked
activeBackgroundColor: "transparent"
contentItem: RowLayout {
QQC2.RadioButton {
visible: configuredTimezoneList.count > 1
checked: plasmoid.configuration.lastSelectedTimezone === model.timeZoneId
onToggled: plasmoid.configuration.lastSelectedTimezone = model.timeZoneId
}
ColumnLayout {
Layout.minimumHeight: Kirigami.Units.iconSizes.large
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
QQC2.Label {
Layout.fillWidth: true
text: model.city
elide: Text.ElideRight
}
QQC2.Label {
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
text: plasmoid.configuration.lastSelectedTimezone === model.timeZoneId && configuredTimezoneList.count > 1 ? "Clock is currently using this time zone" : ""
elide: Text.ElideRight
font: Kirigami.Theme.smallFont
opacity: 0.7
visible: text.length > 0
}
}
QQC2.Button {
visible: model.isLocalTimeZone && KCMShell.authorize("clock.desktop").length > 0
text: i18n("Switch Local Time Zone...")
icon.name: "preferences-system-time"
onClicked: KCMShell.openSystemSettings("clock")
}
QQC2.Button {
visible: !model.isLocalTimeZone && configuredTimezoneList.count > 1
icon.name: "edit-delete"
onClicked: model.checked = false;
QQC2.ToolTip {
text: i18n("Remove this time zone")
}
}
}
}
section {
property: "isLocalTimeZone"
delegate: Kirigami.ListSectionHeader {
label: section == "true" ? i18n("System's Local Time Zone") : i18n("Additional Time Zones")
}
}
Kirigami.PlaceholderMessage {
visible: configuredTimezoneList.count === 1
anchors {
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom
bottomMargin: Kirigami.Units.gridUnit * 7
}
width: parent.width - (Kirigami.Units.largeSpacing * 12)
text: i18n("Add more time zones to display all of them in the applet's pop-up, or use one of them for the clock itself")
}
}
}
Kirigami.SearchField {
id: filter
QQC2.Button {
Layout.alignment: Qt.AlignLeft // Explicitly set so it gets reversed for LTR mode
text: i18n("Add Time Zones...")
icon.name: "list-add"
onClicked: timezoneSheet.open()
}
QQC2.Label {
visible: configuredTimezoneList.count > 1
Layout.fillWidth: true
Layout.margins: Kirigami.Units.largeSpacing * 2
text: i18n("Note that using a different time zone for the clock does not change the systemwide local time zone. When you travel, switch the local time zone instead.")
wrapMode: Text.Wrap
}
Item {
Layout.fillWidth: true
// Tighten up the layout
Layout.fillHeight: true
}
QQC2.ScrollView {
anchors.fill: parent
clip: true
Component.onCompleted: background.visible = true // enable border
Kirigami.OverlaySheet {
id: timezoneSheet
ListView {
id: listView
focus: true // keyboard navigation
activeFocusOnTab: true // keyboard navigation
onSheetOpenChanged: {
filter.text = "";
messageWidget.visible = false;
}
model: TimeZoneFilterProxy {
sourceModel: timeZones
filterString: filter.text
}
// Need to manually set the parent when using this in a Plasma config dialog
parent: timeZonesPage.parent
delegate: QQC2.CheckDelegate {
id: checkbox
focus: true // keyboard navigation
width: listView.width
text: !city || city.indexOf("UTC") === 0 ? comment : comment ? i18n("%1, %2 (%3)", city, region, comment) : i18n("%1, %2", city, region)
checked: model.checked
onToggled: {
model.checked = checkbox.checked
listView.currentIndex = index // highlight
listView.forceActiveFocus() // keyboard navigation
}
highlighted: ListView.isCurrentItem
}
// It interferes with the search field in the header
showCloseButton: false
header: ColumnLayout {
Layout.preferredWidth: Kirigami.Units.gridUnit * 25
Kirigami.Heading {
Layout.fillWidth: true
text: i18n("Add More Timezones")
wrapMode: Text.Wrap
}
Kirigami.SearchField {
id: filter
Layout.fillWidth: true
}
Kirigami.InlineMessage {
id: messageWidget
Layout.fillWidth: true
type: Kirigami.MessageType.Warning
text: i18n("At least one time zone needs to be enabled. Your local timezone was enabled automatically.")
showCloseButton: true
}
}
}
RowLayout {
Layout.fillWidth: true
QQC2.CheckBox {
id: enableWheelCheckBox
text: i18n("Switch time zone with mouse wheel")
footer: QQC2.DialogButtonBox {
standardButtons: QQC2.DialogButtonBox.Ok
onAccepted: timezoneSheet.close()
}
}
ListView {
id: listView
focus: true // keyboard navigation
activeFocusOnTab: true // keyboard navigation
implicitWidth: Kirigami.Units.gridUnit * 25
model: TimeZoneFilterProxy {
sourceModel: timeZones
filterString: filter.text
}
delegate: QQC2.CheckDelegate {
id: checkbox
width: listView.width
focus: true // keyboard navigation
text: !city || city.indexOf("UTC") === 0 ? comment : comment ? i18n("%1, %2 (%3)", city, region, comment) : i18n("%1, %2", city, region)
checked: model.checked
onToggled: {
model.checked = checkbox.checked
listView.currentIndex = index // highlight
listView.forceActiveFocus() // keyboard navigation
}
highlighted: ListView.isCurrentItem
}
}
}
}
......@@ -31,6 +31,7 @@ public:
QString city;
QString comment;
bool checked;
bool isLocalTimeZone;
int offsetFromUtc;
};
......
......@@ -33,10 +33,15 @@ TimeZoneFilterProxy::TimeZoneFilterProxy(QObject *parent)
bool TimeZoneFilterProxy::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
if (!sourceModel() || m_filterString.isEmpty()) {
if (!sourceModel() || (m_filterString.isEmpty() && !m_onlyShowChecked)) {
return true;
}
const bool checked = sourceModel()->index(source_row, 0, source_parent).data(TimeZoneModel::CheckedRole).toBool();
if (m_onlyShowChecked && !checked) {
return false;
}
const QString city = sourceModel()->index(source_row, 0, source_parent).data(TimeZoneModel::CityRole).toString();
const QString region = sourceModel()->index(source_row, 0, source_parent).data(TimeZoneModel::RegionRole).toString();
const QString comment = sourceModel()->index(source_row, 0, source_parent).data(TimeZoneModel::CommentRole).toString();
......@@ -57,6 +62,15 @@ void TimeZoneFilterProxy::setFilterString(const QString &filterString)
invalidateFilter();
}
void TimeZoneFilterProxy::setOnlyShowChecked(const bool show)
{
if (m_onlyShowChecked == show) {
return;
}
m_onlyShowChecked = show;
emit onlyShowCheckedChanged();
}
//=============================================================================
TimeZoneModel::TimeZoneModel(QObject *parent)
......@@ -92,6 +106,8 @@ QVariant TimeZoneModel::data(const QModelIndex &index, int role) const
return currentData.comment;
case CheckedRole:
return currentData.checked;
case IsLocalTimeZoneRole:
return currentData.isLocalTimeZone;
}
}
......@@ -134,10 +150,11 @@ void TimeZoneModel::update()
const QStringList data = QString::fromUtf8(localZone.id()).split(QLatin1Char('/'));
TimeZoneData local;
local.isLocalTimeZone = true;
local.id = QStringLiteral("Local");
local.region = i18nc("This means \"Local Timezone\"", "Local");
local.city = m_timezonesI18n->i18nCity(data.last());
local.comment = i18n("Your system time zone");
local.comment = i18n("System's local time zone");
local.checked = false;
m_data.append(local);
......@@ -172,6 +189,7 @@ void TimeZoneModel::update()
const QStringList cityCountryContinent = key.split(QLatin1Char('|'));
TimeZoneData newData;
newData.isLocalTimeZone = false;
newData.id = timeZone.id();
newData.region = timeZone.country() == QLocale::AnyCountry ? QString()
: m_timezonesI18n->i18nContinents(cityCountryContinent.at(2)) + QLatin1Char('/') + m_timezonesI18n->i18nCountry(timeZone.country());
......@@ -219,7 +237,8 @@ QHash<int, QByteArray> TimeZoneModel::roleNames() const
{RegionRole, "region"},
{CityRole, "city"},
{CommentRole, "comment"},
{CheckedRole, "checked"}
{CheckedRole, "checked"},
{IsLocalTimeZoneRole, "isLocalTimeZone"}
});
}
......
......@@ -32,18 +32,22 @@ class TimeZoneFilterProxy : public QSortFilterProxyModel
{
Q_OBJECT
Q_PROPERTY(QString filterString WRITE setFilterString MEMBER m_filterString NOTIFY filterStringChanged)
Q_PROPERTY(bool onlyShowChecked WRITE setOnlyShowChecked MEMBER m_onlyShowChecked NOTIFY onlyShowCheckedChanged)
public:
explicit TimeZoneFilterProxy(QObject *parent = nullptr);
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
void setFilterString(const QString &filterString);
void setOnlyShowChecked(const bool show);
Q_SIGNALS:
void filterStringChanged();
void onlyShowCheckedChanged();
private:
QString m_filterString;
bool m_onlyShowChecked;
QStringMatcher m_stringMatcher;
};
......@@ -63,7 +67,8 @@ public:
RegionRole,
CityRole,
CommentRole,
CheckedRole
CheckedRole,
IsLocalTimeZoneRole
};
int rowCount(const QModelIndex &parent) const override;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment