1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtWidgets module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qfontcombobox.h"
41
42#include <qstringlistmodel.h>
43#include <qitemdelegate.h>
44#include <qlistview.h>
45#include <qpainter.h>
46#include <qevent.h>
47#include <qapplication.h>
48#include <private/qcombobox_p.h>
49#include <QDesktopWidget>
50#include <private/qdesktopwidget_p.h>
51#include <qdebug.h>
52
53QT_BEGIN_NAMESPACE
54
55static QFontDatabase::WritingSystem writingSystemFromScript(QLocale::Script script)
56{
57 switch (script) {
58 case QLocale::ArabicScript:
59 return QFontDatabase::Arabic;
60 case QLocale::CyrillicScript:
61 return QFontDatabase::Cyrillic;
62 case QLocale::GurmukhiScript:
63 return QFontDatabase::Gurmukhi;
64 case QLocale::SimplifiedHanScript:
65 return QFontDatabase::SimplifiedChinese;
66 case QLocale::TraditionalHanScript:
67 return QFontDatabase::TraditionalChinese;
68 case QLocale::LatinScript:
69 return QFontDatabase::Latin;
70 case QLocale::ArmenianScript:
71 return QFontDatabase::Armenian;
72 case QLocale::BengaliScript:
73 return QFontDatabase::Bengali;
74 case QLocale::DevanagariScript:
75 return QFontDatabase::Devanagari;
76 case QLocale::GeorgianScript:
77 return QFontDatabase::Georgian;
78 case QLocale::GreekScript:
79 return QFontDatabase::Greek;
80 case QLocale::GujaratiScript:
81 return QFontDatabase::Gujarati;
82 case QLocale::HebrewScript:
83 return QFontDatabase::Hebrew;
84 case QLocale::JapaneseScript:
85 return QFontDatabase::Japanese;
86 case QLocale::KhmerScript:
87 return QFontDatabase::Khmer;
88 case QLocale::KannadaScript:
89 return QFontDatabase::Kannada;
90 case QLocale::KoreanScript:
91 return QFontDatabase::Korean;
92 case QLocale::LaoScript:
93 return QFontDatabase::Lao;
94 case QLocale::MalayalamScript:
95 return QFontDatabase::Malayalam;
96 case QLocale::MyanmarScript:
97 return QFontDatabase::Myanmar;
98 case QLocale::TamilScript:
99 return QFontDatabase::Tamil;
100 case QLocale::TeluguScript:
101 return QFontDatabase::Telugu;
102 case QLocale::ThaanaScript:
103 return QFontDatabase::Thaana;
104 case QLocale::ThaiScript:
105 return QFontDatabase::Thai;
106 case QLocale::TibetanScript:
107 return QFontDatabase::Tibetan;
108 case QLocale::SinhalaScript:
109 return QFontDatabase::Sinhala;
110 case QLocale::SyriacScript:
111 return QFontDatabase::Syriac;
112 case QLocale::OriyaScript:
113 return QFontDatabase::Oriya;
114 case QLocale::OghamScript:
115 return QFontDatabase::Ogham;
116 case QLocale::RunicScript:
117 return QFontDatabase::Runic;
118 case QLocale::NkoScript:
119 return QFontDatabase::Nko;
120 default:
121 return QFontDatabase::Any;
122 }
123}
124
125static QFontDatabase::WritingSystem writingSystemFromLocale()
126{
127 QStringList uiLanguages = QLocale::system().uiLanguages();
128 QLocale::Script script;
129 if (!uiLanguages.isEmpty())
130 script = QLocale(uiLanguages.at(0)).script();
131 else
132 script = QLocale::system().script();
133
134 return writingSystemFromScript(script);
135}
136
137static QFontDatabase::WritingSystem writingSystemForFont(const QFont &font, bool *hasLatin)
138{
139 QList<QFontDatabase::WritingSystem> writingSystems = QFontDatabase().writingSystems(font.family());
140// qDebug() << font.family() << writingSystems;
141
142 // this just confuses the algorithm below. Vietnamese is Latin with lots of special chars
143 writingSystems.removeOne(QFontDatabase::Vietnamese);
144 *hasLatin = writingSystems.removeOne(QFontDatabase::Latin);
145
146 if (writingSystems.isEmpty())
147 return QFontDatabase::Any;
148
149 QFontDatabase::WritingSystem system = writingSystemFromLocale();
150
151 if (writingSystems.contains(system))
152 return system;
153
154 if (system == QFontDatabase::TraditionalChinese
155 && writingSystems.contains(QFontDatabase::SimplifiedChinese)) {
156 return QFontDatabase::SimplifiedChinese;
157 }
158
159 if (system == QFontDatabase::SimplifiedChinese
160 && writingSystems.contains(QFontDatabase::TraditionalChinese)) {
161 return QFontDatabase::TraditionalChinese;
162 }
163
164 system = writingSystems.constLast();
165
166 if (!*hasLatin) {
167 // we need to show something
168 return system;
169 }
170
171 if (writingSystems.count() == 1 && system > QFontDatabase::Cyrillic)
172 return system;
173
174 if (writingSystems.count() <= 2 && system > QFontDatabase::Armenian && system < QFontDatabase::Vietnamese)
175 return system;
176
177 if (writingSystems.count() <= 5 && system >= QFontDatabase::SimplifiedChinese && system <= QFontDatabase::Korean)
178 return system;
179
180 return QFontDatabase::Any;
181}
182
183class QFontFamilyDelegate : public QAbstractItemDelegate
184{
185 Q_OBJECT
186public:
187 explicit QFontFamilyDelegate(QObject *parent);
188
189 // painting
190 void paint(QPainter *painter,
191 const QStyleOptionViewItem &option,
192 const QModelIndex &index) const override;
193
194 QSize sizeHint(const QStyleOptionViewItem &option,
195 const QModelIndex &index) const override;
196
197 const QIcon truetype;
198 const QIcon bitmap;
199 QFontDatabase::WritingSystem writingSystem;
200};
201
202QFontFamilyDelegate::QFontFamilyDelegate(QObject *parent)
203 : QAbstractItemDelegate(parent),
204 truetype(QStringLiteral(":/qt-project.org/styles/commonstyle/images/fonttruetype-16.png")),
205 bitmap(QStringLiteral(":/qt-project.org/styles/commonstyle/images/fontbitmap-16.png")),
206 writingSystem(QFontDatabase::Any)
207{
208}
209
210void QFontFamilyDelegate::paint(QPainter *painter,
211 const QStyleOptionViewItem &option,
212 const QModelIndex &index) const
213{
214 QString text = index.data(Qt::DisplayRole).toString();
215 QFont font(option.font);
216 font.setPointSize(QFontInfo(font).pointSize() * 3 / 2);
217 QFont font2 = font;
218 font2.setFamily(text);
219
220 bool hasLatin;
221 QFontDatabase::WritingSystem system = writingSystemForFont(font2, &hasLatin);
222 if (hasLatin)
223 font = font2;
224
225 QRect r = option.rect;
226
227 if (option.state & QStyle::State_Selected) {
228 painter->save();
229 painter->setBrush(option.palette.highlight());
230 painter->setPen(Qt::NoPen);
231 painter->drawRect(option.rect);
232 painter->setPen(QPen(option.palette.highlightedText(), 0));
233 }
234
235 const QIcon *icon = &bitmap;
236 if (QFontDatabase().isSmoothlyScalable(text)) {
237 icon = &truetype;
238 }
239 const QSize actualSize = icon->actualSize(r.size());
240 const QRect iconRect = QStyle::alignedRect(option.direction, option.displayAlignment,
241 actualSize, r);
242 icon->paint(painter, iconRect, Qt::AlignLeft|Qt::AlignVCenter);
243 if (option.direction == Qt::RightToLeft)
244 r.setRight(r.right() - actualSize.width() - 4);
245 else
246 r.setLeft(r.left() + actualSize.width() + 4);
247
248 QFont old = painter->font();
249 painter->setFont(font);
250
251 const Qt::Alignment textAlign = QStyle::visualAlignment(option.direction, option.displayAlignment);
252 // If the ascent of the font is larger than the height of the rect,
253 // we will clip the text, so it's better to align the tight bounding rect in this case
254 // This is specifically for fonts where the ascent is very large compared to
255 // the descent, like certain of the Stix family.
256 QFontMetricsF fontMetrics(font);
257 if (fontMetrics.ascent() > r.height()) {
258 QRectF tbr = fontMetrics.tightBoundingRect(text);
259 QRect textRect(r);
260 textRect.setHeight(textRect.height() + (r.height() - tbr.height()));
261 painter->drawText(textRect, Qt::AlignBottom|Qt::TextSingleLine|textAlign, text);
262 } else {
263 painter->drawText(r, Qt::AlignVCenter|Qt::TextSingleLine|textAlign, text);
264 }
265
266 if (writingSystem != QFontDatabase::Any)
267 system = writingSystem;
268
269 if (system != QFontDatabase::Any) {
270 int w = painter->fontMetrics().horizontalAdvance(text + QLatin1String(" "));
271 painter->setFont(font2);
272 QString sample = QFontDatabase().writingSystemSample(system);
273 if (option.direction == Qt::RightToLeft)
274 r.setRight(r.right() - w);
275 else
276 r.setLeft(r.left() + w);
277 painter->drawText(r, Qt::AlignVCenter|Qt::TextSingleLine|textAlign, sample);
278 }
279 painter->setFont(old);
280
281 if (option.state & QStyle::State_Selected)
282 painter->restore();
283
284}
285
286QSize QFontFamilyDelegate::sizeHint(const QStyleOptionViewItem &option,
287 const QModelIndex &index) const
288{
289 QString text = index.data(Qt::DisplayRole).toString();
290 QFont font(option.font);
291// font.setFamily(text);
292 font.setPointSize(QFontInfo(font).pointSize() * 3/2);
293 QFontMetrics fontMetrics(font);
294 return QSize(fontMetrics.horizontalAdvance(text), fontMetrics.height());
295}
296
297
298class QFontComboBoxPrivate : public QComboBoxPrivate
299{
300public:
301 inline QFontComboBoxPrivate() { filters = QFontComboBox::AllFonts; }
302
303 QFontComboBox::FontFilters filters;
304 QFont currentFont;
305
306 void _q_updateModel();
307 void _q_currentChanged(const QString &);
308
309 Q_DECLARE_PUBLIC(QFontComboBox)
310};
311
312
313void QFontComboBoxPrivate::_q_updateModel()
314{
315 Q_Q(QFontComboBox);
316 const int scalableMask = (QFontComboBox::ScalableFonts | QFontComboBox::NonScalableFonts);
317 const int spacingMask = (QFontComboBox::ProportionalFonts | QFontComboBox::MonospacedFonts);
318
319 QStringListModel *m = qobject_cast<QStringListModel *>(q->model());
320 if (!m)
321 return;
322 QFontFamilyDelegate *delegate = qobject_cast<QFontFamilyDelegate *>(q->view()->itemDelegate());
323 QFontDatabase::WritingSystem system = delegate ? delegate->writingSystem : QFontDatabase::Any;
324
325 QFontDatabase fdb;
326 QStringList list = fdb.families(system);
327 QStringList result;
328
329 int offset = 0;
330 QFontInfo fi(currentFont);
331
332 for (int i = 0; i < list.size(); ++i) {
333 if (fdb.isPrivateFamily(list.at(i)))
334 continue;
335
336 if ((filters & scalableMask) && (filters & scalableMask) != scalableMask) {
337 if (bool(filters & QFontComboBox::ScalableFonts) != fdb.isSmoothlyScalable(list.at(i)))
338 continue;
339 }
340 if ((filters & spacingMask) && (filters & spacingMask) != spacingMask) {
341 if (bool(filters & QFontComboBox::MonospacedFonts) != fdb.isFixedPitch(list.at(i)))
342 continue;
343 }
344 result += list.at(i);
345 if (list.at(i) == fi.family() || list.at(i).startsWith(fi.family() + QLatin1String(" [")))
346 offset = result.count() - 1;
347 }
348 list = result;
349
350 //we need to block the signals so that the model doesn't emit reset
351 //this prevents the current index from changing
352 //it will be updated just after this
353 ///TODO: we should finda way to avoid blocking signals and have a real update of the model
354 {
355 const QSignalBlocker blocker(m);
356 m->setStringList(list);
357 }
358
359 if (list.isEmpty()) {
360 if (currentFont != QFont()) {
361 currentFont = QFont();
362 emit q->currentFontChanged(currentFont);
363 }
364 } else {
365 q->setCurrentIndex(offset);
366 }
367}
368
369
370void QFontComboBoxPrivate::_q_currentChanged(const QString &text)
371{
372 Q_Q(QFontComboBox);
373 if (currentFont.family() != text) {
374 currentFont.setFamily(text);
375 emit q->currentFontChanged(currentFont);
376 }
377}
378
379/*!
380 \class QFontComboBox
381 \brief The QFontComboBox widget is a combobox that lets the user
382 select a font family.
383
384 \since 4.2
385 \ingroup basicwidgets
386 \inmodule QtWidgets
387
388 The combobox is populated with an alphabetized list of font
389 family names, such as Arial, Helvetica, and Times New Roman.
390 Family names are displayed using the actual font when possible.
391 For fonts such as Symbol, where the name is not representable in
392 the font itself, a sample of the font is displayed next to the
393 family name.
394
395 QFontComboBox is often used in toolbars, in conjunction with a
396 QComboBox for controlling the font size and two \l{QToolButton}s
397 for bold and italic.
398
399 When the user selects a new font, the currentFontChanged() signal
400 is emitted in addition to currentIndexChanged().
401
402 Call setWritingSystem() to tell QFontComboBox to show only fonts
403 that support a given writing system, and setFontFilters() to
404 filter out certain types of fonts as e.g. non scalable fonts or
405 monospaced fonts.
406
407 \image windowsvista-fontcombobox.png Screenshot of QFontComboBox on Windows Vista
408
409 \sa QComboBox, QFont, QFontInfo, QFontMetrics, QFontDatabase, {Character Map Example}
410*/
411
412/*!
413 Constructs a font combobox with the given \a parent.
414*/
415QFontComboBox::QFontComboBox(QWidget *parent)
416 : QComboBox(*new QFontComboBoxPrivate, parent)
417{
418 Q_D(QFontComboBox);
419 d->currentFont = font();
420 setEditable(true);
421
422 QStringListModel *m = new QStringListModel(this);
423 setModel(m);
424 setItemDelegate(new QFontFamilyDelegate(this));
425 QListView *lview = qobject_cast<QListView*>(view());
426 if (lview)
427 lview->setUniformItemSizes(true);
428 setWritingSystem(QFontDatabase::Any);
429
430 connect(this, SIGNAL(currentIndexChanged(QString)),
431 this, SLOT(_q_currentChanged(QString)));
432
433 connect(qApp, SIGNAL(fontDatabaseChanged()),
434 this, SLOT(_q_updateModel()));
435}
436
437
438/*!
439 Destroys the combobox.
440*/
441QFontComboBox::~QFontComboBox()
442{
443}
444
445/*!
446 \property QFontComboBox::writingSystem
447 \brief the writing system that serves as a filter for the combobox
448
449 If \a script is QFontDatabase::Any (the default), all fonts are
450 listed.
451
452 \sa fontFilters
453*/
454
455void QFontComboBox::setWritingSystem(QFontDatabase::WritingSystem script)
456{
457 Q_D(QFontComboBox);
458 QFontFamilyDelegate *delegate = qobject_cast<QFontFamilyDelegate *>(view()->itemDelegate());
459 if (delegate)
460 delegate->writingSystem = script;
461 d->_q_updateModel();
462}
463
464QFontDatabase::WritingSystem QFontComboBox::writingSystem() const
465{
466 QFontFamilyDelegate *delegate = qobject_cast<QFontFamilyDelegate *>(view()->itemDelegate());
467 if (delegate)
468 return delegate->writingSystem;
469 return QFontDatabase::Any;
470}
471
472
473/*!
474 \enum QFontComboBox::FontFilter
475
476 This enum can be used to only show certain types of fonts in the font combo box.
477
478 \value AllFonts Show all fonts
479 \value ScalableFonts Show scalable fonts
480 \value NonScalableFonts Show non scalable fonts
481 \value MonospacedFonts Show monospaced fonts
482 \value ProportionalFonts Show proportional fonts
483*/
484
485/*!
486 \property QFontComboBox::fontFilters
487 \brief the filter for the combobox
488
489 By default, all fonts are listed.
490
491 \sa writingSystem
492*/
493void QFontComboBox::setFontFilters(FontFilters filters)
494{
495 Q_D(QFontComboBox);
496 d->filters = filters;
497 d->_q_updateModel();
498}
499
500QFontComboBox::FontFilters QFontComboBox::fontFilters() const
501{
502 Q_D(const QFontComboBox);
503 return d->filters;
504}
505
506/*!
507 \property QFontComboBox::currentFont
508 \brief the currently selected font
509
510 \sa currentIndex, currentText
511*/
512QFont QFontComboBox::currentFont() const
513{
514 Q_D(const QFontComboBox);
515 return d->currentFont;
516}
517
518void QFontComboBox::setCurrentFont(const QFont &font)
519{
520 Q_D(QFontComboBox);
521 if (font != d->currentFont) {
522 d->currentFont = font;
523 d->_q_updateModel();
524 if (d->currentFont == font) { //else the signal has already be emitted by _q_updateModel
525 emit currentFontChanged(d->currentFont);
526 }
527 }
528}
529
530/*!
531 \fn void QFontComboBox::currentFontChanged(const QFont &font)
532
533 This signal is emitted whenever the current font changes, with
534 the new \a font.
535
536 \sa currentFont
537*/
538
539/*!
540 \reimp
541*/
542bool QFontComboBox::event(QEvent *e)
543{
544 if (e->type() == QEvent::Resize) {
545 QListView *lview = qobject_cast<QListView*>(view());
546 if (lview) {
547 lview->window()->setFixedWidth(qMin(width() * 5 / 3,
548 QDesktopWidgetPrivate::availableGeometry(lview).width()));
549 }
550 }
551 return QComboBox::event(e);
552}
553
554/*!
555 \reimp
556*/
557QSize QFontComboBox::sizeHint() const
558{
559 QSize sz = QComboBox::sizeHint();
560 QFontMetrics fm(font());
561 sz.setWidth(fm.horizontalAdvance(QLatin1Char('m'))*14);
562 return sz;
563}
564
565QT_END_NAMESPACE
566
567#include "qfontcombobox.moc"
568#include "moc_qfontcombobox.cpp"
569