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"