1 /* This file is part of the KDE libraries
2
3 Copyright (C) 2008 Chusslove Illich <caslav.ilic@gmx.net>
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Library General Public License for more details.
14
15 You should have received a copy of the GNU Library General Public License
16 along with this library; see the file COPYING.LIB. If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
19 */
20
21 #include "kfontcombobox.h"
22 #include "fonthelpers_p.h"
23
24 #include "kdebug.h"
25 #include "klocalizedstring.h"
26 #include "kcolorscheme.h"
27 #include "kfontchooser.h"
28 #include "kcompletion.h"
29
30 #include <QEvent>
31 #include <QListView>
32 #include <QFontDatabase>
33 #include <QIcon>
34 #include <QAbstractItemDelegate>
35 #include <QStringListModel>
36 #include <QPainter>
37 #include <QList>
38 #include <QHash>
39 #include <QScrollBar>
40
alphabetSample()41 static QString alphabetSample()
42 {
43 return i18nc("short",
44 // i18n: A shorter version of the alphabet test phrase translated in
45 // another message. It is displayed in the dropdown list of font previews
46 // (the font selection combo box), so keep it under the length equivalent
47 // to 60 or so proportional Latin characters.
48 "The Quick Brown Fox Jumps Over The Lazy Dog");
49 }
50
51 class KFontFamilyDelegate : public QAbstractItemDelegate
52 {
53 Q_OBJECT
54 public:
55 KDELIBS4SUPPORT_DEPRECATED explicit KFontFamilyDelegate(QObject *parent);
56
57 void paint(QPainter *painter,
58 const QStyleOptionViewItem &option,
59 const QModelIndex &index) const override;
60
61 QSize sizeHint(const QStyleOptionViewItem &option,
62 const QModelIndex &index) const override;
63
64 QIcon truetype;
65 QIcon bitmap;
66 double sizeFactFamily;
67 double sizeFactSample;
68
69 QHash<QString, QString> fontFamilyTrMap;
70 };
71
KFontFamilyDelegate(QObject * parent)72 KFontFamilyDelegate::KFontFamilyDelegate(QObject *parent)
73 : QAbstractItemDelegate(parent)
74 {
75 truetype = QIcon(QLatin1String(":/trolltech/styles/commonstyle/images/fonttruetype-16.png"));
76 bitmap = QIcon(QLatin1String(":/trolltech/styles/commonstyle/images/fontbitmap-16.png"));
77
78 // Font size factors for family name and text sample in font previes,
79 // multiplies normal font size.
80 sizeFactFamily = 1.0;
81 sizeFactSample = 1.0; // better leave at 1, so that user can relate sizes to default
82 }
83
paint(QPainter * painter,const QStyleOptionViewItem & option,const QModelIndex & index) const84 void KFontFamilyDelegate::paint(QPainter *painter,
85 const QStyleOptionViewItem &option,
86 const QModelIndex &index) const
87 {
88 QBrush sampleBrush;
89 if (option.state & QStyle::State_Selected) {
90 painter->save();
91 painter->setBrush(option.palette.highlight());
92 painter->setPen(Qt::NoPen);
93 painter->drawRect(option.rect);
94 painter->setPen(QPen(option.palette.highlightedText(), 0));
95 sampleBrush = option.palette.highlightedText();
96 } else {
97 sampleBrush = KColorScheme(QPalette::Normal).foreground(KColorScheme::InactiveText);
98 }
99
100 QFont baseFont = QFontDatabase::systemFont(QFontDatabase::GeneralFont);
101 QString trFontFamily = index.data(Qt::DisplayRole).toString();
102 QString fontFamily = fontFamilyTrMap[trFontFamily];
103
104 // Writing systems provided by the font.
105 QList<QFontDatabase::WritingSystem> availableSystems = QFontDatabase().writingSystems(fontFamily);
106
107 // Intersect font's writing systems with that specified for
108 // the language's sample text, to see if the sample can be shown.
109 // If the font reports no writing systems, assume it can show the sample.
110 bool canShowLanguageSample = true;
111 if (availableSystems.count() > 0) {
112 canShowLanguageSample = false;
113 QString scriptsSpec = i18nc("Numeric IDs of scripts for font previews",
114 // i18n: Integer which indicates the script you used in the sample text
115 // for font previews in your language. For the possible values, see
116 // https://doc.qt.io/qt-5/qfontdatabase.html#WritingSystem-enum
117 // If the sample text contains several scripts, their IDs can be given
118 // as a comma-separated list (e.g. for Japanese it is "1,27").
119 "1");
120 QStringList scriptStrIds = scriptsSpec.split(',');
121 foreach (const QString &scriptStrId, scriptStrIds) {
122 bool convOk;
123 int ws = scriptStrId.toInt(&convOk);
124 if (convOk && ws > 0 && ws < QFontDatabase::WritingSystemsCount
125 && availableSystems.contains(static_cast<QFontDatabase::WritingSystem>(ws))) {
126 canShowLanguageSample = true;
127 break;
128 }
129 }
130 }
131
132 // Choose and paint an icon according to the font type, scalable or bitmat.
133 const QIcon *icon = &bitmap;
134 if (QFontDatabase().isSmoothlyScalable(fontFamily)) {
135 icon = &truetype;
136 }
137 QRect r = option.rect;
138 icon->paint(painter, r, Qt::AlignLeft | Qt::AlignTop);
139
140 // Claim space taken up by the icon.
141 QSize actualSize = icon->actualSize(r.size());
142 if (option.direction == Qt::RightToLeft) {
143 r.setRight(r.right() - actualSize.width() - 4);
144 } else {
145 r.setLeft(r.left() + actualSize.width() + 4);
146 }
147
148 // Draw the font family.
149 QFont oldPainterFont = painter->font();
150 QFont familyFont = baseFont;
151 familyFont.setPointSizeF(familyFont.pointSizeF() * sizeFactFamily);
152 painter->setFont(familyFont);
153 painter->drawText(r, Qt::AlignTop | Qt::AlignLeading | Qt::TextSingleLine, trFontFamily);
154
155 // Claim space taken up by the font family name.
156 int h = painter->fontMetrics().lineSpacing();
157 r.setTop(r.top() + h);
158
159 // Show text sample in user's language if the writing system is supported,
160 // otherwise show a collage of generic script samples provided by Qt.
161 // If the font does not report what it supports, assume all.
162 QString sample;
163 if (canShowLanguageSample) {
164 sample = alphabetSample();
165 } else {
166 foreach (const QFontDatabase::WritingSystem &ws, availableSystems) {
167 sample += QFontDatabase::writingSystemSample(ws) + " ";
168 if (sample.length() > 40) { // do not let the sample be too long
169 break;
170 }
171 }
172 sample = sample.trimmed();
173 }
174 QFont sampleFont;
175 sampleFont.setFamily(fontFamily);
176 sampleFont.setPointSizeF(sampleFont.pointSizeF() * sizeFactSample);
177 painter->setFont(sampleFont);
178 QPen oldPen = painter->pen();
179 painter->setPen(sampleBrush.color());
180 painter->drawText(r, Qt::AlignTop | Qt::AlignLeading | Qt::TextSingleLine, sample);
181 painter->setFont(oldPainterFont);
182 painter->setPen(oldPen);
183
184 if (option.state & QStyle::State_Selected) {
185 painter->restore();
186 }
187 }
188
sizeHint(const QStyleOptionViewItem & option,const QModelIndex & index) const189 QSize KFontFamilyDelegate::sizeHint(const QStyleOptionViewItem &option,
190 const QModelIndex &index) const
191 {
192 Q_UNUSED(option);
193
194 QFont baseFont = QFontDatabase::systemFont(QFontDatabase::GeneralFont);
195 QString trFontFamily = index.data(Qt::DisplayRole).toString();
196 QString fontFamily = fontFamilyTrMap[trFontFamily];
197
198 QFont familyFont = baseFont;
199 familyFont.setPointSizeF(familyFont.pointSizeF() * sizeFactFamily);
200 QFontMetrics familyMetrics(familyFont);
201
202 QFont sampleFont = baseFont;
203 sampleFont.setFamily(fontFamily);
204 sampleFont.setPointSizeF(sampleFont.pointSizeF() * sizeFactSample);
205 QFontMetrics sampleMetrics(sampleFont);
206 QString sample = alphabetSample();
207
208 // Only the hight matters here, the width is mandated by KFontComboBox::event()
209 return QSize(qMax(familyMetrics.width(trFontFamily), sampleMetrics.width(sample)),
210 qRound(familyMetrics.lineSpacing() + sampleMetrics.lineSpacing() * 1.2));
211 }
212
213 class KFontComboBoxPrivate
214 {
215 public:
216 KFontComboBoxPrivate(KFontComboBox *parent);
217 void updateDatabase();
218 void updateIndexToFont();
219 void _k_currentFontChanged(int index);
220
221 KFontComboBox *k;
222 QFont currentFont;
223 bool onlyFixed;
224 bool signalsAllowed;
225 KFontFamilyDelegate *delegate;
226 QStringListModel *model;
227 QStringList fontList;
228 };
229
KFontComboBoxPrivate(KFontComboBox * parent)230 KFontComboBoxPrivate::KFontComboBoxPrivate(KFontComboBox *parent)
231 : k(parent),
232 currentFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont)),
233 onlyFixed(false),
234 signalsAllowed(true)
235 {
236 }
237
updateDatabase()238 void KFontComboBoxPrivate::updateDatabase()
239 {
240 QStringList fontFamilies = fontList;
241 if (fontList.isEmpty()) {
242 KFontChooser::getFontList(fontFamilies,
243 onlyFixed ? KFontChooser::FixedWidthFonts : 0);
244 }
245
246 // Translate font families for the list model.
247 delegate->fontFamilyTrMap.clear();
248 QStringList trFontFamilies =
249 translateFontNameList(fontFamilies, &(delegate->fontFamilyTrMap));
250
251 // Add families to the list model and completion.
252 model->setStringList(trFontFamilies);
253 KCompletion *completion = k->completionObject();
254 if (completion) {
255 completion->setItems(trFontFamilies);
256 completion->setIgnoreCase(true);
257 }
258 }
259
updateIndexToFont()260 void KFontComboBoxPrivate::updateIndexToFont()
261 {
262 // QFontInfo necessary to return the family with proper casing.
263 QString selectedFontFamily = QFontInfo(currentFont).family();
264 QString trSelectedFontFamily = translateFontName(selectedFontFamily);
265 const QStringList trFontFamilies = model->stringList();
266 if (trFontFamilies.isEmpty()) {
267 return;
268 }
269
270 // Match the font's family with an item in the list.
271 int index = 0;
272 foreach (const QString &trFontFamily, trFontFamilies) {
273 if (trSelectedFontFamily == trFontFamily) {
274 break;
275 }
276 ++index;
277 }
278 if (index == trFontFamilies.count()) {
279 // If no family matched, change font to first on the list.
280 index = 0;
281 currentFont = QFont(delegate->fontFamilyTrMap[trFontFamilies[0]]);
282 emit k->currentFontChanged(currentFont);
283 }
284
285 // Set the new list item.
286 signalsAllowed = false;
287 k->setCurrentIndex(index);
288 signalsAllowed = true;
289 }
290
_k_currentFontChanged(int index)291 void KFontComboBoxPrivate::_k_currentFontChanged(int index)
292 {
293 if (!signalsAllowed) {
294 return;
295 }
296
297 QString trFontFamily = k->itemText(index);
298 QString fontFamily = delegate->fontFamilyTrMap[trFontFamily];
299 if (!fontFamily.isEmpty()) {
300 currentFont = QFont(fontFamily);
301 emit k->currentFontChanged(currentFont);
302 } else {
303 // Unknown font family given. Just remove from the list.
304 // This should not happen, as adding arbitrary font names is prevented.
305 QStringList lst = model->stringList();
306 lst.removeAll(trFontFamily);
307 model->setStringList(lst);
308 }
309 }
310
KFontComboBox(QWidget * parent)311 KFontComboBox::KFontComboBox(QWidget *parent)
312 : KComboBox(true, parent), d(new KFontComboBoxPrivate(this))
313 {
314 // Inputing arbitrary font names does not make sense.
315 setInsertPolicy(QComboBox::NoInsert);
316
317 // Special list item painter showing font previews and its list model.
318 d->delegate = new KFontFamilyDelegate(this);
319 setItemDelegate(d->delegate);
320 d->model = new QStringListModel(this);
321 setModel(d->model);
322
323 // Set current font when a new family has been chosen in the combo.
324 connect(this, SIGNAL(currentIndexChanged(int)),
325 this, SLOT(_k_currentFontChanged(int)));
326
327 // Initialize font selection and list of available fonts.
328 d->updateDatabase();
329 d->updateIndexToFont();
330 }
331
~KFontComboBox()332 KFontComboBox::~KFontComboBox()
333 {
334 delete d;
335 }
336
setOnlyFixed(bool onlyFixed)337 void KFontComboBox::setOnlyFixed(bool onlyFixed)
338 {
339 if (onlyFixed != d->onlyFixed) {
340 d->onlyFixed = onlyFixed;
341 d->updateDatabase();
342 }
343 }
344
setFontList(const QStringList & fontList)345 void KFontComboBox::setFontList(const QStringList &fontList)
346 {
347 if (fontList != d->fontList) {
348 d->fontList = fontList;
349 d->updateDatabase();
350 }
351 }
352
currentFont() const353 QFont KFontComboBox::currentFont() const
354 {
355 return d->currentFont;
356 }
357
setCurrentFont(const QFont & font)358 void KFontComboBox::setCurrentFont(const QFont &font)
359 {
360 if (font != d->currentFont) {
361 d->currentFont = font;
362 emit currentFontChanged(d->currentFont);
363 d->updateIndexToFont();
364 }
365 }
366
event(QEvent * e)367 bool KFontComboBox::event(QEvent *e)
368 {
369 if (e->type() == QEvent::Resize) {
370 QListView *lview = qobject_cast<QListView *>(view());
371 if (lview) {
372 QString sample = alphabetSample();
373 // Limit text sample length to avoid too wide list view.
374 if (sample.length() > 60) {
375 sample = sample.left(57) + "...";
376 }
377 QFont approxFont = QFontDatabase::systemFont(QFontDatabase::GeneralFont);
378 approxFont.setPointSizeF(approxFont.pointSizeF()
379 * d->delegate->sizeFactSample);
380 int widgetWidth = width();
381 int sampleWidth = QFontMetrics(approxFont).width(sample);
382 sampleWidth = qRound(sampleWidth * 1.1); // extra for wider fonts
383 int iconWidth = d->delegate->truetype.actualSize(size()).width();
384 int vsbarWidth = 0;
385 if (lview->verticalScrollBar()) {
386 vsbarWidth = lview->verticalScrollBar()->width();
387 }
388 lview->window()->setFixedWidth(qMax(widgetWidth, sampleWidth)
389 + iconWidth + vsbarWidth);
390 }
391 }
392 return KComboBox::event(e);
393 }
394
sizeHint() const395 QSize KFontComboBox::sizeHint() const
396 {
397 QSize sz = KComboBox::sizeHint();
398 QFontMetrics fm(QFontDatabase::systemFont(QFontDatabase::GeneralFont));
399 sz.setWidth(fm.width("m") * 14);
400 return sz;
401 }
402
403 #include "fonthelpers.cpp"
404
405 #include "kfontcombobox.moc"
406 #include "moc_kfontcombobox.moc"
407
408