File indexing completed on 2024-05-12 05:35:39

0001 /*
0002     Copyright (C) 2005, S.R.Haque <srhaque@iee.org>.
0003     Copyright (C) 2009, David Faure <faure@kde.org>
0004     This file is part of the KDE project
0005 
0006     This library is free software; you can redistribute it and/or
0007     modify it under the terms of the GNU Library General Public
0008     License version 2, as published by the Free Software Foundation.
0009 
0010     This library is distributed in the hope that it will be useful,
0011     but WITHOUT ANY WARRANTY; without even the implied warranty of
0012     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013     Library General Public License for more details.
0014 
0015     You should have received a copy of the GNU Library General Public License
0016     along with this library; see the file COPYING.LIB.  If not, write to
0017     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0018     Boston, MA 02110-1301, USA.
0019 */
0020 
0021 #include "k4timezonewidget.h"
0022 
0023 #include <KCountry>
0024 #include <KCountryFlagEmojiIconEngine>
0025 #include <KLocalizedString>
0026 
0027 #include <QDebug>
0028 #include <QFile>
0029 #include <QPixmap>
0030 #include <QTimeZone>
0031 
0032 #include <unicode/localebuilder.h>
0033 #include <unicode/tznames.h>
0034 #include <unicode/unistr.h>
0035 
0036 class Q_DECL_HIDDEN K4TimeZoneWidget::Private
0037 {
0038 public:
0039     Private()
0040         : itemsCheckable(false)
0041         , singleSelection(true)
0042     {
0043     }
0044 
0045     enum Columns { CityColumn = 0, RegionColumn, CommentColumn };
0046 
0047     enum Roles { ZoneRole = Qt::UserRole + 0xF3A3CB1 };
0048 
0049     bool itemsCheckable;
0050     bool singleSelection;
0051 };
0052 
0053 static bool localeLessThan(const QString &a, const QString &b)
0054 {
0055     return QString::localeAwareCompare(a, b) < 0;
0056 }
0057 
0058 
0059 K4TimeZoneWidget::K4TimeZoneWidget(QWidget *parent)
0060     : QTreeWidget(parent)
0061     , d(new K4TimeZoneWidget::Private)
0062 {
0063     // If the user did not provide a timezone database, we'll use the system default.
0064     setRootIsDecorated(false);
0065     setHeaderLabels(QStringList() << i18nc("Define an area in the time zone, like a town area", "Area") << i18nc("Time zone", "Region") << i18n("Comment"));
0066 
0067     const auto locale = icu::Locale(QLocale::system().name().toLatin1());
0068     UErrorCode error = U_ZERO_ERROR;
0069     std::unique_ptr<icu::TimeZoneNames> tzNames(icu::TimeZoneNames::createInstance(locale, error));
0070     if (!U_SUCCESS(error)) {
0071         qWarning() << "failed to create timezone names" << u_errorName(error);
0072         return;
0073     };
0074     icu::UnicodeString result;
0075 
0076     const auto timezones = QTimeZone::availableTimeZoneIds();
0077 
0078     struct TimeZoneInfo {
0079         QTimeZone zone;
0080         QString region;
0081         QString city;
0082     };
0083 
0084     std::vector<TimeZoneInfo> zones;
0085     zones.reserve(timezones.size());
0086     for (const auto &id : timezones) {
0087         const int separator = id.lastIndexOf('/');
0088         // Make up the localized key that will be used for sorting.
0089         // Example: Asia/Tokyo -> key = "i18n(Tokyo)|i18n(Asia)|Asia/Tokyo"
0090         // The zone name is appended to ensure unicity even with equal translations (#174918)
0091         // since there is no  API continent names, we use the translation catalog of the digitalclock applet which contain these
0092         const char *domain = "plasma_applet_org.kde.plasma.digitalclock.mo";
0093         const QString continent = separator > 0 ? i18nd(domain, id.left(separator)) : QString();
0094         const icu::UnicodeString &exemplarCity = tzNames->getExemplarLocationName(icu::UnicodeString::fromUTF8(icu::StringPiece(id.data(), id.size())), result);
0095         zones.push_back(
0096             {QTimeZone(id), continent, exemplarCity.isBogus() ? QString::fromLatin1(id) : QString::fromUtf16(exemplarCity.getBuffer(), exemplarCity.length())});
0097     }
0098     std::sort(zones.begin(), zones.end(), [](const TimeZoneInfo &left, const TimeZoneInfo &right) {
0099         if (auto result = QString::localeAwareCompare(left.city, right.city); result != 0) {
0100             return result < 0;
0101         }
0102         if (auto result = QString::localeAwareCompare(left.region, right.region); result != 0) {
0103             return result < 0;
0104         }
0105         return QString::localeAwareCompare(left.zone.id(), right.zone.id()) < 0;
0106     });
0107 
0108     for (const auto &zoneInfo : zones) {
0109         const QTimeZone &zone = zoneInfo.zone;
0110         const QString tzName = zone.id();
0111         QString comment = zone.comment();
0112 
0113         if (!comment.isEmpty()) {
0114             comment = i18n(comment.toUtf8());
0115         }
0116 
0117         QTreeWidgetItem *listItem = new QTreeWidgetItem(this);
0118         listItem->setText(Private::CityColumn, zoneInfo.city);
0119         QString countryName = KCountry::fromQLocale(zone.territory()).name();
0120         if (countryName.isEmpty()) {
0121             countryName = QLocale::territoryToCode(zone.territory());
0122         }
0123         QString regionLabel = zoneInfo.region;
0124         if (!regionLabel.isEmpty() && !countryName.isEmpty()) {
0125             regionLabel += '/';
0126         }
0127         regionLabel += countryName;
0128 
0129         listItem->setText(Private::RegionColumn, regionLabel);
0130         listItem->setText(Private::CommentColumn, comment);
0131         listItem->setData(Private::CityColumn, Private::ZoneRole, tzName); // store complete path in custom role
0132 
0133         listItem->setIcon(Private::RegionColumn, QIcon(new KCountryFlagEmojiIconEngine(QLocale::territoryToCode(zone.territory()))));
0134     }
0135 }
0136 
0137 K4TimeZoneWidget::~K4TimeZoneWidget()
0138 {
0139     delete d;
0140 }
0141 
0142 void K4TimeZoneWidget::setItemsCheckable(bool enable)
0143 {
0144     d->itemsCheckable = enable;
0145     const int count = topLevelItemCount();
0146     for (int row = 0; row < count; ++row) {
0147         QTreeWidgetItem *listItem = topLevelItem(row);
0148         listItem->setCheckState(Private::CityColumn, Qt::Unchecked);
0149     }
0150     QTreeWidget::setSelectionMode(QAbstractItemView::NoSelection);
0151 }
0152 
0153 bool K4TimeZoneWidget::itemsCheckable() const
0154 {
0155     return d->itemsCheckable;
0156 }
0157 
0158 QStringList K4TimeZoneWidget::selection() const
0159 {
0160     QStringList selection;
0161 
0162     // Loop through all entries.
0163     // Do not use selectedItems() because it skips hidden items, making it
0164     // impossible to use a KTreeWidgetSearchLine.
0165     // There is no QTreeWidgetItemConstIterator, hence the const_cast :/
0166     QTreeWidgetItemIterator it(const_cast<K4TimeZoneWidget *>(this), d->itemsCheckable ? QTreeWidgetItemIterator::Checked : QTreeWidgetItemIterator::Selected);
0167     for (; *it; ++it) {
0168         selection.append((*it)->data(Private::CityColumn, Private::ZoneRole).toString());
0169     }
0170 
0171     return selection;
0172 }
0173 
0174 void K4TimeZoneWidget::setSelected(const QString &zone, bool selected)
0175 {
0176     bool found = false;
0177 
0178     // The code was using findItems( zone, Qt::MatchExactly, Private::ZoneColumn )
0179     // previously, but the underlying model only has 3 columns, the "hidden" column
0180     // wasn't available in there.
0181 
0182     if (!d->itemsCheckable) {
0183         // Runtime compatibility for < 4.3 apps, which don't call the setMultiSelection reimplementation.
0184         d->singleSelection = (QTreeWidget::selectionMode() == QAbstractItemView::SingleSelection);
0185     }
0186 
0187     // Loop through all entries.
0188     const int rowCount = model()->rowCount(QModelIndex());
0189     for (int row = 0; row < rowCount; ++row) {
0190         const QModelIndex index = model()->index(row, Private::CityColumn);
0191         const QString tzName = index.data(Private::ZoneRole).toString();
0192         if (tzName == zone) {
0193             if (d->singleSelection && selected) {
0194                 clearSelection();
0195             }
0196 
0197             if (d->itemsCheckable) {
0198                 QTreeWidgetItem *listItem = itemFromIndex(index);
0199                 listItem->setCheckState(Private::CityColumn, selected ? Qt::Checked : Qt::Unchecked);
0200             } else {
0201                 selectionModel()->select(index,
0202                                          selected ? (QItemSelectionModel::Select | QItemSelectionModel::Rows)
0203                                                   : (QItemSelectionModel::Deselect | QItemSelectionModel::Rows));
0204             }
0205 
0206             // Ensure the selected item is visible as appropriate.
0207             scrollTo(index);
0208 
0209             found = true;
0210 
0211             if (d->singleSelection && selected) {
0212                 break;
0213             }
0214         }
0215     }
0216 
0217     if (!found) {
0218         qDebug() << "No such zone: " << zone;
0219     }
0220 }
0221 
0222 void K4TimeZoneWidget::clearSelection()
0223 {
0224     if (d->itemsCheckable) {
0225         // Un-select all items
0226         const int rowCount = model()->rowCount(QModelIndex());
0227         for (int row = 0; row < rowCount; ++row) {
0228             const QModelIndex index = model()->index(row, 0);
0229             QTreeWidgetItem *listItem = itemFromIndex(index);
0230             listItem->setCheckState(Private::CityColumn, Qt::Unchecked);
0231         }
0232     } else {
0233         QTreeWidget::clearSelection();
0234     }
0235 }
0236 
0237 void K4TimeZoneWidget::setSelectionMode(QAbstractItemView::SelectionMode mode)
0238 {
0239     d->singleSelection = (mode == QAbstractItemView::SingleSelection);
0240     if (!d->itemsCheckable) {
0241         QTreeWidget::setSelectionMode(mode);
0242     }
0243 }
0244 
0245 QAbstractItemView::SelectionMode K4TimeZoneWidget::selectionMode() const
0246 {
0247     if (d->itemsCheckable) {
0248         return d->singleSelection ? QTreeWidget::SingleSelection : QTreeWidget::MultiSelection;
0249     } else {
0250         return QTreeWidget::selectionMode();
0251     }
0252 }
0253 
0254 
0255 #include "moc_k4timezonewidget.cpp"