1 | /* |
2 | SPDX-FileCopyrightText: 1999-2003 Hans Petter Bieker <bieker@kde.org> |
3 | SPDX-FileCopyrightText: 2007 David Jarvie <djarvie@kde.org> |
4 | |
5 | SPDX-License-Identifier: LGPL-2.0-or-later |
6 | */ |
7 | |
8 | #include "klanguagebutton.h" |
9 | |
10 | #include <QDir> |
11 | #include <QEvent> |
12 | #include <QFile> |
13 | #include <QHBoxLayout> |
14 | #include <QLocale> |
15 | #include <QMenu> |
16 | #include <QPushButton> |
17 | |
18 | #include <KConfig> |
19 | #include <KConfigGroup> |
20 | |
21 | static void (QMenu *, const QString &str, int &index) |
22 | { |
23 | if (index != -1) { |
24 | return; |
25 | } |
26 | |
27 | int a = 0; |
28 | const QList<QAction *> actions = popup->actions(); |
29 | int b = actions.count(); |
30 | |
31 | while (a < b) { |
32 | int w = (a + b) / 2; |
33 | QAction *ac = actions[w]; |
34 | int j = str.localeAwareCompare(s: ac->text()); |
35 | if (j > 0) { |
36 | a = w + 1; |
37 | } else { |
38 | b = w; |
39 | } |
40 | } |
41 | |
42 | index = a; // it doesn't really matter ... a == b here. |
43 | |
44 | Q_ASSERT(a == b); |
45 | } |
46 | |
47 | class KLanguageButtonPrivate |
48 | { |
49 | public: |
50 | explicit KLanguageButtonPrivate(KLanguageButton *parent); |
51 | ~KLanguageButtonPrivate() |
52 | { |
53 | delete button; |
54 | delete popup; |
55 | } |
56 | void setCurrentItem(QAction *); |
57 | void clear(); |
58 | QAction *findAction(const QString &data) const; |
59 | |
60 | QPushButton *button = nullptr; |
61 | QStringList ids; |
62 | QMenu * = nullptr; |
63 | QString current; |
64 | QString locale; |
65 | bool staticText : 1; |
66 | bool showCodes : 1; |
67 | }; |
68 | |
69 | KLanguageButton::KLanguageButton(QWidget *parent) |
70 | : QWidget(parent) |
71 | , d(new KLanguageButtonPrivate(this)) |
72 | { |
73 | } |
74 | |
75 | KLanguageButton::KLanguageButton(const QString &text, QWidget *parent) |
76 | : QWidget(parent) |
77 | , d(new KLanguageButtonPrivate(this)) |
78 | { |
79 | setText(text); |
80 | } |
81 | |
82 | KLanguageButtonPrivate::KLanguageButtonPrivate(KLanguageButton *parent) |
83 | : button(new QPushButton(parent)) |
84 | , popup(new QMenu(parent)) |
85 | , locale(QLocale::system().name()) |
86 | , staticText(false) |
87 | , showCodes(false) |
88 | { |
89 | QHBoxLayout *layout = new QHBoxLayout(parent); |
90 | layout->setContentsMargins(left: 0, top: 0, right: 0, bottom: 0); |
91 | layout->addWidget(button); |
92 | |
93 | parent->setFocusProxy(button); |
94 | parent->setFocusPolicy(button->focusPolicy()); |
95 | |
96 | button->setMenu(popup); |
97 | |
98 | QObject::connect(sender: popup, signal: &QMenu::triggered, context: parent, slot: &KLanguageButton::slotTriggered); |
99 | QObject::connect(sender: popup, signal: &QMenu::hovered, context: parent, slot: &KLanguageButton::slotHovered); |
100 | } |
101 | |
102 | KLanguageButton::~KLanguageButton() = default; |
103 | |
104 | void KLanguageButton::setText(const QString &text) |
105 | { |
106 | d->staticText = true; |
107 | d->button->setText(text); |
108 | } |
109 | |
110 | void KLanguageButton::setLocale(const QString &locale) |
111 | { |
112 | d->locale = locale; |
113 | } |
114 | |
115 | void KLanguageButton::showLanguageCodes(bool show) |
116 | { |
117 | d->showCodes = show; |
118 | } |
119 | |
120 | static QString nameFromEntryFile(const QString &entryFile) |
121 | { |
122 | const KConfig entry(entryFile, KConfig::SimpleConfig); |
123 | const KConfigGroup group(&entry, QStringLiteral("KCM Locale" )); |
124 | return group.readEntry(key: "Name" , aDefault: QString()); |
125 | } |
126 | |
127 | void KLanguageButton::insertLanguage(const QString &languageCode, const QString &name, int index) |
128 | { |
129 | QString text; |
130 | bool showCodes = d->showCodes; |
131 | if (name.isEmpty()) { |
132 | const QString entryFile = |
133 | QStandardPaths::locate(type: QStandardPaths::GenericDataLocation, fileName: QLatin1String("locale/" ) + languageCode + QLatin1String("/kf6_entry.desktop" )); |
134 | if (QFile::exists(fileName: entryFile)) { |
135 | text = nameFromEntryFile(entryFile); |
136 | } |
137 | |
138 | if (text.isEmpty()) { |
139 | text = languageCode; |
140 | QLocale locale(languageCode); |
141 | if (locale != QLocale::c()) { |
142 | text = locale.nativeLanguageName(); |
143 | // For some languages the native name might be empty. |
144 | // In this case use the non native language name as fallback. |
145 | // See: QTBUG-51323 |
146 | text = text.isEmpty() ? QLocale::languageToString(language: locale.language()) : text; |
147 | } else { |
148 | showCodes = false; |
149 | } |
150 | } |
151 | } else { |
152 | text = name; |
153 | } |
154 | if (showCodes) { |
155 | text += QLatin1String(" (" ) + languageCode + QLatin1Char(')'); |
156 | } |
157 | |
158 | checkInsertPos(popup: d->popup, str: text, index); |
159 | QAction *a = new QAction(QIcon(), text, this); |
160 | a->setData(languageCode); |
161 | if (index >= 0 && index < d->popup->actions().count() - 1) { |
162 | d->popup->insertAction(before: d->popup->actions()[index], action: a); |
163 | } else { |
164 | d->popup->addAction(action: a); |
165 | } |
166 | d->ids.append(t: languageCode); |
167 | } |
168 | |
169 | void KLanguageButton::insertSeparator(int index) |
170 | { |
171 | if (index >= 0 && index < d->popup->actions().count() - 1) { |
172 | d->popup->insertSeparator(before: d->popup->actions()[index]); |
173 | } else { |
174 | d->popup->addSeparator(); |
175 | } |
176 | } |
177 | |
178 | void KLanguageButton::loadAllLanguages() |
179 | { |
180 | const QStringList localeDirs = QStandardPaths::locateAll(type: QStandardPaths::GenericDataLocation, QStringLiteral("locale" ), options: QStandardPaths::LocateDirectory); |
181 | for (const QString &localeDir : localeDirs) { |
182 | const QStringList entries = QDir(localeDir).entryList(filters: QDir::Dirs, sort: QDir::Name); |
183 | for (const QString &d : entries) { |
184 | const QString entryFile = localeDir + QLatin1Char('/') + d + QStringLiteral("/kf6_entry.desktop" ); |
185 | if (QFile::exists(fileName: entryFile)) { |
186 | insertLanguage(languageCode: d); |
187 | } |
188 | } |
189 | } |
190 | |
191 | d->ids.removeDuplicates(); |
192 | setCurrentItem(d->locale); |
193 | } |
194 | |
195 | void KLanguageButton::slotTriggered(QAction *a) |
196 | { |
197 | // qCDebug(KCONFIG_WIDGETS_LOG) << "slotTriggered" << index; |
198 | if (!a) { |
199 | return; |
200 | } |
201 | |
202 | d->setCurrentItem(a); |
203 | |
204 | // Forward event from popup menu as if it was emitted from this widget: |
205 | Q_EMIT activated(languageCode: d->current); |
206 | } |
207 | |
208 | void KLanguageButton::slotHovered(QAction *a) |
209 | { |
210 | // qCDebug(KCONFIG_WIDGETS_LOG) << "slotHovered" << index; |
211 | |
212 | Q_EMIT highlighted(languageCode: a->data().toString()); |
213 | } |
214 | |
215 | int KLanguageButton::count() const |
216 | { |
217 | return d->ids.count(); |
218 | } |
219 | |
220 | void KLanguageButton::clear() |
221 | { |
222 | d->clear(); |
223 | } |
224 | |
225 | void KLanguageButtonPrivate::clear() |
226 | { |
227 | ids.clear(); |
228 | popup->clear(); |
229 | |
230 | if (!staticText) { |
231 | button->setText(QString()); |
232 | } |
233 | } |
234 | |
235 | bool KLanguageButton::contains(const QString &languageCode) const |
236 | { |
237 | return d->ids.contains(str: languageCode); |
238 | } |
239 | |
240 | QString KLanguageButton::current() const |
241 | { |
242 | return d->current.isEmpty() ? QStringLiteral("en" ) : d->current; |
243 | } |
244 | |
245 | QAction *KLanguageButtonPrivate::findAction(const QString &data) const |
246 | { |
247 | const auto listActions = popup->actions(); |
248 | for (QAction *a : listActions) { |
249 | if (!a->data().toString().compare(s: data)) { |
250 | return a; |
251 | } |
252 | } |
253 | return nullptr; |
254 | } |
255 | |
256 | void KLanguageButton::setCurrentItem(const QString &languageCode) |
257 | { |
258 | if (d->ids.isEmpty()) { |
259 | return; |
260 | } |
261 | QAction *a; |
262 | if (d->ids.indexOf(str: languageCode) < 0) { |
263 | a = d->findAction(data: d->ids[0]); |
264 | } else { |
265 | a = d->findAction(data: languageCode); |
266 | } |
267 | if (a) { |
268 | d->setCurrentItem(a); |
269 | } |
270 | } |
271 | |
272 | void KLanguageButtonPrivate::setCurrentItem(QAction *a) |
273 | { |
274 | if (!a->data().isValid()) { |
275 | return; |
276 | } |
277 | current = a->data().toString(); |
278 | |
279 | if (!staticText) { |
280 | button->setText(a->text()); |
281 | } |
282 | } |
283 | |
284 | bool KLanguageButton::event(QEvent *event) |
285 | { |
286 | if (event->type() == QEvent::ToolTipChange) { |
287 | d->button->setToolTip(toolTip()); |
288 | } |
289 | return QObject::event(event); |
290 | } |
291 | |
292 | #include "moc_klanguagebutton.cpp" |
293 | |