From e25f4a71ce293a7640f3a3def5094a3ad48a761f Mon Sep 17 00:00:00 2001 From: "A. Wilcox" Date: Thu, 14 Nov 2019 12:04:30 -0600 Subject: Qt UI: Implement UI.SysMeta.DateTime/Timezone page --- ui/qt5/CMakeLists.txt | 4 +- ui/qt5/datetimepage.cc | 229 +++++++++++++++++++++++++++++++++++++ ui/qt5/datetimepage.hh | 73 ++++++++++++ ui/qt5/horizon.qrc | 1 + ui/qt5/horizonwizard.cc | 4 + ui/qt5/horizonwizard.hh | 1 + ui/qt5/networkifacepage.cc | 2 +- ui/qt5/resources/datetime-help.txt | 17 +++ 8 files changed, 329 insertions(+), 2 deletions(-) create mode 100644 ui/qt5/datetimepage.cc create mode 100644 ui/qt5/datetimepage.hh create mode 100644 ui/qt5/resources/datetime-help.txt (limited to 'ui') diff --git a/ui/qt5/CMakeLists.txt b/ui/qt5/CMakeLists.txt index 7bf3107..fc2069d 100644 --- a/ui/qt5/CMakeLists.txt +++ b/ui/qt5/CMakeLists.txt @@ -12,6 +12,7 @@ set(UI_SOURCES networkingpage.cc networkifacepage.cc netsimplewifipage.cc + datetimepage.cc horizon.qrc) add_executable(horizon-qt5 ${UI_SOURCES}) @@ -22,6 +23,7 @@ install(TARGETS horizon-qt5 DESTINATION bin) IF(INSTALL) pkg_check_modules(XKBFile REQUIRED xkbfile) pkg_check_modules(LIBX11 REQUIRED x11) - target_link_libraries(horizon-qt5 ${LIBUDEV_LIBRARIES} ${LIBX11_LIBRARIES} ${XKBFile_LIBRARIES}) + pkg_check_modules(LibCap REQUIRED libcap) + target_link_libraries(horizon-qt5 ${LIBUDEV_LIBRARIES} ${LibCap_LIBRARIES} ${LIBX11_LIBRARIES} ${XKBFile_LIBRARIES}) # add_executable(horizon-run-qt5 ${RUN_QT_SOURCES}) ENDIF(INSTALL) diff --git a/ui/qt5/datetimepage.cc b/ui/qt5/datetimepage.cc new file mode 100644 index 0000000..a333c15 --- /dev/null +++ b/ui/qt5/datetimepage.cc @@ -0,0 +1,229 @@ +/* + * datetimepage.cc - Implementation of the UI.SysMeta.DateTime page + * horizon-qt5, the Qt 5 user interface for + * Project Horizon + * + * Copyright (c) 2019 Adélie Linux and contributors. All rights reserved. + * This code is licensed under the AGPL 3.0 license, as noted in the + * LICENSE-code file in the root directory of this repository. + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +#include "datetimepage.hh" + +#include +#include +#include +#include +#include +#include +#include + +#include +#ifdef HAS_INSTALL_ENV +# include +# include +#endif /* HAS_INSTALL_ENV */ + + +TimeZone::TimeZone() : ianaName(), friendlyName(), offset(0) {} + +TimeZone::TimeZone(QByteArray iana) { + QTimeZone zone(iana); + QString offset, longName, comment; + this->ianaName = iana; + + offset = zone.displayName(QTimeZone::GenericTime, QTimeZone::OffsetName); + this->friendlyName = "(" + offset + ") " + iana; + longName = zone.displayName(QTimeZone::GenericTime, QTimeZone::LongName); + if(longName != offset) { + this->friendlyName += " - " + longName; + } + + comment = zone.comment(); + if(comment.size() > 0 && comment != offset) { + this->friendlyName += " (" + comment + ")"; + } + + this->offset = zone.standardTimeOffset(QDateTime::currentDateTimeUtc()); +} + + +TimeZoneModel::TimeZoneModel(QWidget *parent) : QAbstractListModel(parent) { + for(auto &iana : QTimeZone::availableTimeZoneIds()) { + TimeZone tzObj(iana); + zones.push_back(tzObj); + } + + std::sort(zones.begin(), zones.end(), [](TimeZone tz1, TimeZone tz2) { + if(tz1.offset < tz2.offset) return true; + if(tz2.offset < tz1.offset) return false; + return tz1.friendlyName < tz2.friendlyName; + }); +} + +int TimeZoneModel::rowCount(const QModelIndex &) const { + return zones.size(); +} + +QVariant TimeZoneModel::data(const QModelIndex &index, int role) const { + if(!index.isValid()) { + return QVariant(); + } + + if(index.row() > zones.size()) { + return QVariant(); + } + + TimeZone zone = zones.at(index.row()); + + switch(role) + { + case Qt::DisplayRole: + return zone.friendlyName; + case Qt::ToolTipRole: + return QString(zone.ianaName); + default: + return QVariant(); + } +} + +QVariant TimeZoneModel::headerData(int, Qt::Orientation, int role) const { + if(role != Qt::DisplayRole) { + return QVariant(); + } + + return QString("Time Zone"); +} + + +/*! Try to gain CAP_SYS_TIME capability. + * If we do, then enable the date/time edit boxes. + * This does nothing in the Runtime Environment. + */ +void DateTimePage::maybeRaiseCap() { +#ifdef HAS_INSTALL_ENV + cap_t captain; + cap_value_t time_cap = CAP_SYS_TIME; + + if(!CAP_IS_SUPPORTED(CAP_SYS_TIME)) + return; + + captain = cap_get_proc(); + if(captain == nullptr) + return; + + if(cap_set_flag(captain, CAP_EFFECTIVE, 1, &time_cap, CAP_SET) == -1) + return; + + if(cap_set_proc(captain)) + return; + + cap_free(captain); + + dateEdit->setEnabled(true); + timeEdit->setEnabled(true); +#endif /* HAS_INSTALL_ENV */ +} + +DateTimePage::DateTimePage(QWidget *parent) : HorizonWizardPage(parent) { + setTitle(tr("Date and Time Settings")); + + dateEdit = new QDateEdit(QDate::currentDate()); + dateEdit->setDisplayFormat("dd MMMM yyyy"); + dateEdit->setEnabled(false); + timeEdit = new QTimeEdit(QTime::currentTime()); + timeEdit->setDisplayFormat("HH:mm:ss"); + timeEdit->setEnabled(false); + +#ifdef HAS_INSTALL_ENV + /* explanations: + * + * isEnabled check: + * to prevent wasting time in the case where we don't have perms + * + * Qt::UTC: + * CLOCK_REALTIME is always UTC, so we need the UTC form of the + * current date/time. + */ + connect(dateEdit, &QDateEdit::dateChanged, [=](const QDate &date) { + if(dateEdit->isEnabled() && date != QDate::currentDate()) { + QDateTime *newDT = new QDateTime(date, QTime::currentTime(), + Qt::UTC); + struct timespec ts = {newDT->toSecsSinceEpoch(), 0}; + clock_settime(CLOCK_REALTIME, &ts); + } + }); + connect(timeEdit, &QTimeEdit::timeChanged, [=](const QTime &time) { + if(timeEdit->isEnabled() && time != QTime::currentTime()) { + QDateTime *newDT = new QDateTime(QDate::currentDate(), time, + Qt::UTC); + struct timespec ts = {newDT->toSecsSinceEpoch(), 0}; + clock_settime(CLOCK_REALTIME, &ts); + } + }); +#endif /* HAS_INSTALL_ENV */ + + updateTimer = new QTimer(this); + updateTimer->setInterval(1000); + connect(updateTimer, &QTimer::timeout, [=]{ + dateEdit->setDate(QDate::currentDate()); + timeEdit->setTime(QTime::currentTime()); + }); + + QHBoxLayout *dateTimeLayout = new QHBoxLayout; + dateTimeLayout->addWidget(dateEdit); + dateTimeLayout->addWidget(timeEdit); + QGroupBox *dateTimeGroup = new QGroupBox(tr("Date and Time")); + dateTimeGroup->setLayout(dateTimeLayout); + + QLineEdit *timeZoneSearch = new QLineEdit; + timeZoneSearch->addAction(QIcon::fromTheme("edit-find"), + QLineEdit::LeadingPosition); + timeZoneSearch->setPlaceholderText(tr("Search for a time zone")); + QSortFilterProxyModel *sortModel = new QSortFilterProxyModel(this); + sortModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + sortModel->setSourceModel(&zoneModel); + connect(timeZoneSearch, &QLineEdit::textChanged, [=](const QString &text) { + sortModel->setFilterFixedString(text); + }); + timeZoneList = new QListView; + timeZoneList->setModel(sortModel); + timeZoneList->setSelectionMode(QAbstractItemView::SingleSelection); + connect(timeZoneList->selectionModel(), &QItemSelectionModel::currentChanged, + [=]() { + emit timezoneChanged(); + }); + + registerField("timezone*", this, "selectedTimeZone", SIGNAL(timezoneChanged())); + + QVBoxLayout *timeZoneLayout = new QVBoxLayout; + timeZoneLayout->addWidget(timeZoneSearch); + timeZoneLayout->addWidget(timeZoneList); + QGroupBox *timeZoneGroup = new QGroupBox(tr("Time Zone")); + timeZoneGroup->setLayout(timeZoneLayout); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addStretch(); + mainLayout->addWidget(dateTimeGroup); + mainLayout->addStretch(); + mainLayout->addWidget(timeZoneGroup); + mainLayout->addStretch(); + setLayout(mainLayout); + + maybeRaiseCap(); +} + +QString DateTimePage::selectedTimeZone() { + QModelIndex curr = timeZoneList->selectionModel()->currentIndex(); + return zoneModel.data(curr, Qt::ToolTipRole).toString(); +} + +void DateTimePage::initializePage() { + updateTimer->start(); +} + +void DateTimePage::cleanupPage() { + updateTimer->stop(); +} diff --git a/ui/qt5/datetimepage.hh b/ui/qt5/datetimepage.hh new file mode 100644 index 0000000..2c753dd --- /dev/null +++ b/ui/qt5/datetimepage.hh @@ -0,0 +1,73 @@ +/* + * datetimepage.hh - Definition of the UI.SysMeta.DateTime page + * horizon-qt5, the Qt 5 user interface for + * Project Horizon + * + * Copyright (c) 2019 Adélie Linux and contributors. All rights reserved. + * This code is licensed under the AGPL 3.0 license, as noted in the + * LICENSE-code file in the root directory of this repository. + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +#ifndef DATETIMEPAGE_HH +#define DATETIMEPAGE_HH + +#include "horizonwizardpage.hh" + +#include +#include +#include +#include +#include +#include + +/*! Represents a single time zone */ +struct TimeZone { + /*! The IANA name, i.e. America/Chicago */ + QByteArray ianaName; + /*! The 'friendly' name, i.e. 'Central Time' */ + QString friendlyName; + /*! The UTC offset in seconds */ + int offset; + + TimeZone(); + TimeZone(QByteArray iana); +}; + +class TimeZoneModel : public QAbstractListModel { +public: + TimeZoneModel(QWidget *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const override; +private: + QVector zones; +}; + +class DateTimePage : public HorizonWizardPage { + Q_OBJECT + Q_PROPERTY(QString selectedTimeZone READ selectedTimeZone) +public: + DateTimePage(QWidget *parent = nullptr); + void initializePage(); + void cleanupPage(); + + TimeZoneModel zoneModel; + QString selectedTimeZone(); + +signals: + void timezoneChanged(); + +private: + QDateEdit *dateEdit; + QTimeEdit *timeEdit; + QTimer *updateTimer; + QListView *timeZoneList; + + void maybeRaiseCap(); +}; + +#endif /* !DATETIMEPAGE_HH */ diff --git a/ui/qt5/horizon.qrc b/ui/qt5/horizon.qrc index 562418c..f715934 100644 --- a/ui/qt5/horizon.qrc +++ b/ui/qt5/horizon.qrc @@ -13,6 +13,7 @@ resources/partition-help.txt resources/network-start-help.txt resources/network-iface-help.txt + resources/datetime-help.txt resources/packages-simple-help.txt diff --git a/ui/qt5/horizonwizard.cc b/ui/qt5/horizonwizard.cc index 2b58e43..dda631f 100644 --- a/ui/qt5/horizonwizard.cc +++ b/ui/qt5/horizonwizard.cc @@ -28,6 +28,7 @@ #include "networkingpage.hh" #include "networkifacepage.hh" #include "netsimplewifipage.hh" +#include "datetimepage.hh" static std::map help_id_map = { {HorizonWizard::Page_Intro, "intro"}, @@ -42,6 +43,7 @@ static std::map help_id_map = { {HorizonWizard::Page_Network, "network-start"}, {HorizonWizard::Page_Network_Iface, "network-iface"}, {HorizonWizard::Page_Network_Wireless, "network-wifi"}, + {HorizonWizard::Page_Network_CustomAP, "network-wifi-ap"}, {HorizonWizard::Page_Network_DHCP, "none"}, {HorizonWizard::Page_Network_Portal, "network-portal"}, {HorizonWizard::Page_Network_Manual, "network-manual"}, @@ -148,6 +150,8 @@ HorizonWizard::HorizonWizard(QWidget *parent) : QWizard(parent) { setPage(Page_Input, new InputPage); setPage(Page_Network, new NetworkingPage); setPage(Page_Network_Iface, new NetworkIfacePage); + setPage(Page_Network_Wireless, new NetworkSimpleWirelessPage); + setPage(Page_DateTime, new DateTimePage); QObject::connect(this, &QWizard::helpRequested, [=](void) { if(help_id_map.find(currentId()) == help_id_map.end()) { diff --git a/ui/qt5/horizonwizard.hh b/ui/qt5/horizonwizard.hh index 3f3454b..d6f84cd 100644 --- a/ui/qt5/horizonwizard.hh +++ b/ui/qt5/horizonwizard.hh @@ -33,6 +33,7 @@ public: Page_Network, /* network type selection (DHCP/static) */ Page_Network_Iface, /* network interface selection */ Page_Network_Wireless, /* wireless */ + Page_Network_CustomAP, /* custom AP */ Page_Network_DHCP, /* interstitial for DHCP */ Page_Network_Portal, /* shown if captive portal is detected */ Page_Network_Manual, /* static addressing */ diff --git a/ui/qt5/networkifacepage.cc b/ui/qt5/networkifacepage.cc index ce09a82..f850cfd 100644 --- a/ui/qt5/networkifacepage.cc +++ b/ui/qt5/networkifacepage.cc @@ -123,6 +123,6 @@ int NetworkIfacePage::nextId() const { case HorizonWizard::Wireless: return HorizonWizard::Page_Network_Wireless; default: - return HorizonWizard::Page_Network_DHCP; + return HorizonWizard::Page_DateTime; } } diff --git a/ui/qt5/resources/datetime-help.txt b/ui/qt5/resources/datetime-help.txt new file mode 100644 index 0000000..811b6cf --- /dev/null +++ b/ui/qt5/resources/datetime-help.txt @@ -0,0 +1,17 @@ +

Date and Time Settings

+ +

This page allows you to configure your computer's date and time. It also +allows you to configure your computer's time zone.

+ +

How do I know which time zone to select?

+ +

If you're installing Adélie Linux on a desktop, workstation, or laptop +computer, choose the time zone where you are located, or where you will use +the computer the most. If you have another device with you that has Internet +access, you may find your local time zone by using the interactive map at + +https://www.timeanddate.com/time/map/.

+ +

If you're installing Adélie Linux on a server system, you should almost +always use UTC. This ensures that Daylight Savings Time will not adversely +affect running services.

-- cgit v1.2.3-60-g2f50