Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qfontcombobox.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
6
7#include <qabstractitemdelegate.h>
8#include <qaccessible.h>
9#include <qstringlistmodel.h>
10#include <qlistview.h>
11#include <qpainter.h>
12#include <qevent.h>
13#include <qapplication.h>
14#include <private/qcombobox_p.h>
15#include <qdebug.h>
16
17#include <array>
18
20
21using namespace Qt::StringLiterals;
22
24{
25 switch (script) {
26 case QLocale::ArabicScript:
27 return QFontDatabase::Arabic;
28 case QLocale::CyrillicScript:
29 return QFontDatabase::Cyrillic;
30 case QLocale::GurmukhiScript:
31 return QFontDatabase::Gurmukhi;
32 case QLocale::SimplifiedHanScript:
33 return QFontDatabase::SimplifiedChinese;
34 case QLocale::TraditionalHanScript:
35 return QFontDatabase::TraditionalChinese;
36 case QLocale::LatinScript:
37 return QFontDatabase::Latin;
38 case QLocale::ArmenianScript:
39 return QFontDatabase::Armenian;
40 case QLocale::BengaliScript:
41 return QFontDatabase::Bengali;
42 case QLocale::DevanagariScript:
43 return QFontDatabase::Devanagari;
44 case QLocale::GeorgianScript:
45 return QFontDatabase::Georgian;
46 case QLocale::GreekScript:
47 return QFontDatabase::Greek;
48 case QLocale::GujaratiScript:
49 return QFontDatabase::Gujarati;
50 case QLocale::HebrewScript:
51 return QFontDatabase::Hebrew;
52 case QLocale::JapaneseScript:
53 return QFontDatabase::Japanese;
54 case QLocale::KhmerScript:
55 return QFontDatabase::Khmer;
56 case QLocale::KannadaScript:
57 return QFontDatabase::Kannada;
58 case QLocale::KoreanScript:
59 return QFontDatabase::Korean;
60 case QLocale::LaoScript:
61 return QFontDatabase::Lao;
62 case QLocale::MalayalamScript:
63 return QFontDatabase::Malayalam;
64 case QLocale::MyanmarScript:
65 return QFontDatabase::Myanmar;
66 case QLocale::TamilScript:
67 return QFontDatabase::Tamil;
68 case QLocale::TeluguScript:
69 return QFontDatabase::Telugu;
70 case QLocale::ThaanaScript:
71 return QFontDatabase::Thaana;
72 case QLocale::ThaiScript:
73 return QFontDatabase::Thai;
74 case QLocale::TibetanScript:
75 return QFontDatabase::Tibetan;
76 case QLocale::SinhalaScript:
77 return QFontDatabase::Sinhala;
78 case QLocale::SyriacScript:
79 return QFontDatabase::Syriac;
80 case QLocale::OriyaScript:
81 return QFontDatabase::Oriya;
82 case QLocale::OghamScript:
83 return QFontDatabase::Ogham;
84 case QLocale::RunicScript:
85 return QFontDatabase::Runic;
86 case QLocale::NkoScript:
87 return QFontDatabase::Nko;
88 default:
89 return QFontDatabase::Any;
90 }
91}
92
94{
95 QStringList uiLanguages = QLocale::system().uiLanguages();
96 QLocale::Script script;
97 if (!uiLanguages.isEmpty())
98 script = QLocale(uiLanguages.at(0)).script();
99 else
100 script = QLocale::system().script();
101
102 return writingSystemFromScript(script);
103}
104
105static QFontDatabase::WritingSystem writingSystemForFont(const QFont &font, bool *hasLatin)
106{
107 QList<QFontDatabase::WritingSystem> writingSystems = QFontDatabase::writingSystems(font.families().constFirst());
108// qDebug() << font.families().first() << writingSystems;
109
110 // this just confuses the algorithm below. Vietnamese is Latin with lots of special chars
111 writingSystems.removeOne(QFontDatabase::Vietnamese);
112 *hasLatin = writingSystems.removeOne(QFontDatabase::Latin);
113
114 if (writingSystems.isEmpty())
115 return QFontDatabase::Any;
116
117 QFontDatabase::WritingSystem system = writingSystemFromLocale();
118
119 if (writingSystems.contains(system))
120 return system;
121
122 if (system == QFontDatabase::TraditionalChinese
123 && writingSystems.contains(QFontDatabase::SimplifiedChinese)) {
124 return QFontDatabase::SimplifiedChinese;
125 }
126
127 if (system == QFontDatabase::SimplifiedChinese
128 && writingSystems.contains(QFontDatabase::TraditionalChinese)) {
129 return QFontDatabase::TraditionalChinese;
130 }
131
132 system = writingSystems.constLast();
133
134 if (!*hasLatin) {
135 // we need to show something
136 return system;
137 }
138
139 if (writingSystems.size() == 1 && system > QFontDatabase::Cyrillic)
140 return system;
141
142 if (writingSystems.size() <= 2 && system > QFontDatabase::Armenian && system < QFontDatabase::Vietnamese)
143 return system;
144
145 if (writingSystems.size() <= 5 && system >= QFontDatabase::SimplifiedChinese && system <= QFontDatabase::Korean)
146 return system;
147
148 return QFontDatabase::Any;
149}
150
166
168{
170public:
172
173 // painting
174 void paint(QPainter *painter,
175 const QStyleOptionViewItem &option,
176 const QModelIndex &index) const override;
177
178 QSize sizeHint(const QStyleOptionViewItem &option,
179 const QModelIndex &index) const override;
180
181 const QIcon truetype;
182 const QIcon bitmap;
185};
186
187QFontFamilyDelegate::QFontFamilyDelegate(QObject *parent, QFontComboBoxPrivate *comboP)
188 : QAbstractItemDelegate(parent),
189 truetype(QStringLiteral(":/qt-project.org/styles/commonstyle/images/fonttruetype-16.png")),
190 bitmap(QStringLiteral(":/qt-project.org/styles/commonstyle/images/fontbitmap-16.png")),
191 writingSystem(QFontDatabase::Any),
192 comboPrivate(comboP)
193{
194}
195
197 const QStyleOptionViewItem &option,
198 const QModelIndex &index) const
199{
200 QString text = index.data(Qt::DisplayRole).toString();
201 QFont font(option.font);
202 font.setPointSize(QFontInfo(font).pointSize() * 3 / 2);
203 QFont font2 = font;
204 font2.setFamilies(QStringList{text});
205
206 bool hasLatin;
207 QFontDatabase::WritingSystem system = writingSystemForFont(font2, &hasLatin);
208 if (hasLatin)
209 font = font2;
210
211 font = comboPrivate->displayFontForFontFamily.value(text, font);
212
213 QRect r = option.rect;
214
215 if (option.state & QStyle::State_Selected) {
216 painter->save();
217 painter->setBrush(option.palette.highlight());
218 painter->setPen(Qt::NoPen);
219 painter->drawRect(option.rect);
220 painter->setPen(QPen(option.palette.highlightedText(), 0));
221 }
222
223 const QIcon *icon = &bitmap;
224 if (QFontDatabase::isSmoothlyScalable(text)) {
225 icon = &truetype;
226 }
227 const QSize actualSize = icon->actualSize(r.size());
228 const QRect iconRect = QStyle::alignedRect(option.direction, option.displayAlignment,
229 actualSize, r);
230 icon->paint(painter, iconRect, Qt::AlignLeft|Qt::AlignVCenter);
231 if (option.direction == Qt::RightToLeft)
232 r.setRight(r.right() - actualSize.width() - 4);
233 else
234 r.setLeft(r.left() + actualSize.width() + 4);
235
236 QFont old = painter->font();
237 painter->setFont(font);
238
239 const Qt::Alignment textAlign = QStyle::visualAlignment(option.direction, option.displayAlignment);
240 // If the ascent of the font is larger than the height of the rect,
241 // we will clip the text, so it's better to align the tight bounding rect in this case
242 // This is specifically for fonts where the ascent is very large compared to
243 // the descent, like certain of the Stix family.
244 QFontMetricsF fontMetrics(font);
245 if (fontMetrics.ascent() > r.height()) {
246 QRectF tbr = fontMetrics.tightBoundingRect(text);
247 QRect textRect(r);
248 textRect.setHeight(textRect.height() + (r.height() - tbr.height()));
249 painter->drawText(textRect, Qt::AlignBottom|Qt::TextSingleLine|textAlign, text);
250 } else {
251 painter->drawText(r, Qt::AlignVCenter|Qt::TextSingleLine|textAlign, text);
252 }
253
254 if (writingSystem != QFontDatabase::Any)
255 system = writingSystem;
256
257 const QString sampleText = comboPrivate->sampleTextForFontFamily.value(text, comboPrivate->sampleTextForWritingSystem.value(system));
258 if (system != QFontDatabase::Any || !sampleText.isEmpty()) {
259 int w = painter->fontMetrics().horizontalAdvance(text + " "_L1);
260 painter->setFont(font2);
261 const QString sample = !sampleText.isEmpty() ? sampleText : QFontDatabase::writingSystemSample(system);
262 if (option.direction == Qt::RightToLeft)
263 r.setRight(r.right() - w);
264 else
265 r.setLeft(r.left() + w);
266 painter->drawText(r, Qt::AlignVCenter|Qt::TextSingleLine|textAlign, sample);
267 }
268 painter->setFont(old);
269
270 if (option.state & QStyle::State_Selected)
271 painter->restore();
272
273}
274
275QSize QFontFamilyDelegate::sizeHint(const QStyleOptionViewItem &option,
276 const QModelIndex &index) const
277{
278 QString text = index.data(Qt::DisplayRole).toString();
279 QFont font(option.font);
280// font.setFamilies(QStringList{text});
281 font.setPointSize(QFontInfo(font).pointSize() * 3/2);
282 QFontMetrics fontMetrics(font);
283 return QSize(fontMetrics.horizontalAdvance(text), fontMetrics.height());
284}
285
286
288{
289 Q_Q(QFontComboBox);
290
291 if (QCoreApplication::closingDown())
292 return;
293
294 const int scalableMask = (QFontComboBox::ScalableFonts | QFontComboBox::NonScalableFonts);
295 const int spacingMask = (QFontComboBox::ProportionalFonts | QFontComboBox::MonospacedFonts);
296
297 QStringListModel *m = qobject_cast<QStringListModel *>(q->model());
298 if (!m)
299 return;
300 QFontFamilyDelegate *delegate = qobject_cast<QFontFamilyDelegate *>(q->view()->itemDelegate());
301 QFontDatabase::WritingSystem system = delegate ? delegate->writingSystem : QFontDatabase::Any;
302
303 const QStringList list = QFontDatabase::families(system);
304 QStringList result;
305
306 int offset = 0;
307 QFontInfo fi(currentFont);
308
309 for (const auto &family : list) {
310 if (QFontDatabase::isPrivateFamily(family))
311 continue;
312
313 if ((filters & scalableMask) && (filters & scalableMask) != scalableMask) {
314 if (bool(filters & QFontComboBox::ScalableFonts) != QFontDatabase::isSmoothlyScalable(family))
315 continue;
316 }
317 if ((filters & spacingMask) && (filters & spacingMask) != spacingMask) {
318 if (bool(filters & QFontComboBox::MonospacedFonts) != QFontDatabase::isFixedPitch(family))
319 continue;
320 }
321 result += family;
322 if (family == fi.family() || family.startsWith(fi.family() + " ["_L1))
323 offset = result.size() - 1;
324 }
325
326 //we need to block the signals so that the model doesn't emit reset
327 //this prevents the current index from changing
328 //it will be updated just after this
329 ///TODO: we should finda way to avoid blocking signals and have a real update of the model
330 {
331 const QSignalBlocker blocker(m);
332 m->setStringList(result);
333 // Since the modelReset signal is blocked the view will not emit an accessibility event
334 #if QT_CONFIG(accessibility)
335 if (QAccessible::isActive()) {
336 QAccessibleTableModelChangeEvent accessibleEvent(q->view(), QAccessibleTableModelChangeEvent::ModelReset);
337 QAccessible::updateAccessibility(&accessibleEvent);
338 }
339 #endif
340 }
341
342 if (result.isEmpty()) {
343 if (currentFont != QFont()) {
344 currentFont = QFont();
345 emit q->currentFontChanged(currentFont);
346 }
347 } else {
348 q->setCurrentIndex(offset);
349 }
350}
351
352
353void QFontComboBoxPrivate::currentChanged(const QString &text)
354{
355 Q_Q(QFontComboBox);
356 const QStringList families = currentFont.families();
357 if (families.isEmpty() || families.first() != text) {
358 currentFont.setFamilies(QStringList{text});
359 emit q->currentFontChanged(currentFont);
360 }
361}
362
363/*!
364 \class QFontComboBox
365 \brief The QFontComboBox widget is a combobox that lets the user
366 select a font family.
367
368 \since 4.2
369 \ingroup basicwidgets
370 \inmodule QtWidgets
371
372 The combobox is populated with an alphabetized list of font
373 family names, such as Arial, Helvetica, and Times New Roman.
374 Family names are displayed using the actual font when possible.
375 For fonts such as Symbol, where the name is not representable in
376 the font itself, a sample of the font is displayed next to the
377 family name.
378
379 QFontComboBox is often used in toolbars, in conjunction with a
380 QComboBox for controlling the font size and two \l{QToolButton}s
381 for bold and italic.
382
383 When the user selects a new font, the currentFontChanged() signal
384 is emitted in addition to currentIndexChanged().
385
386 Call setWritingSystem() to tell QFontComboBox to show only fonts
387 that support a given writing system, and setFontFilters() to
388 filter out certain types of fonts as e.g. non scalable fonts or
389 monospaced fonts.
390
391 \image windowsvista-fontcombobox.png Screenshot of QFontComboBox on Windows Vista
392
393 \sa QComboBox, QFont, QFontInfo, QFontMetrics, QFontDatabase
394*/
395
396/*!
397 Constructs a font combobox with the given \a parent.
398*/
399QFontComboBox::QFontComboBox(QWidget *parent)
400 : QComboBox(*new QFontComboBoxPrivate, parent)
401{
402 Q_D(QFontComboBox);
403 d->currentFont = font();
404 setEditable(true);
405
406 QStringListModel *m = new QStringListModel(this);
407 setModel(m);
408 setItemDelegate(new QFontFamilyDelegate(this, d));
409 QListView *lview = qobject_cast<QListView*>(view());
410 if (lview)
411 lview->setUniformItemSizes(true);
412 setWritingSystem(QFontDatabase::Any);
413
414 d->connections = {
415 QObjectPrivate::connect(this, &QFontComboBox::currentTextChanged,
416 d, &QFontComboBoxPrivate::currentChanged),
417 QObjectPrivate::connect(qApp, &QGuiApplication::fontDatabaseChanged,
418 d, &QFontComboBoxPrivate::updateModel),
419 };
420}
421
422
423/*!
424 Destroys the combobox.
425*/
426QFontComboBox::~QFontComboBox()
427{
428 Q_D(const QFontComboBox);
429 for (const QMetaObject::Connection &connection : d->connections)
430 QObject::disconnect(connection);
431}
432
433/*!
434 \property QFontComboBox::writingSystem
435 \brief the writing system that serves as a filter for the combobox
436
437 If \a script is QFontDatabase::Any (the default), all fonts are
438 listed.
439
440 \sa fontFilters
441*/
442
443void QFontComboBox::setWritingSystem(QFontDatabase::WritingSystem script)
444{
445 Q_D(QFontComboBox);
446 QFontFamilyDelegate *delegate = qobject_cast<QFontFamilyDelegate *>(view()->itemDelegate());
447 if (delegate)
448 delegate->writingSystem = script;
449 d->updateModel();
450}
451
452QFontDatabase::WritingSystem QFontComboBox::writingSystem() const
453{
454 QFontFamilyDelegate *delegate = qobject_cast<QFontFamilyDelegate *>(view()->itemDelegate());
455 if (delegate)
456 return delegate->writingSystem;
457 return QFontDatabase::Any;
458}
459
460
461/*!
462 \enum QFontComboBox::FontFilter
463
464 This enum can be used to only show certain types of fonts in the font combo box.
465
466 \value AllFonts Show all fonts
467 \value ScalableFonts Show scalable fonts
468 \value NonScalableFonts Show non scalable fonts
469 \value MonospacedFonts Show monospaced fonts
470 \value ProportionalFonts Show proportional fonts
471*/
472
473/*!
474 \property QFontComboBox::fontFilters
475 \brief the filter for the combobox
476
477 By default, all fonts are listed.
478
479 \sa writingSystem
480*/
481void QFontComboBox::setFontFilters(FontFilters filters)
482{
483 Q_D(QFontComboBox);
484 d->filters = filters;
485 d->updateModel();
486}
487
488QFontComboBox::FontFilters QFontComboBox::fontFilters() const
489{
490 Q_D(const QFontComboBox);
491 return d->filters;
492}
493
494/*!
495 \property QFontComboBox::currentFont
496 \brief the currently selected font
497
498 \sa currentIndex, currentText
499*/
500QFont QFontComboBox::currentFont() const
501{
502 Q_D(const QFontComboBox);
503 return d->currentFont;
504}
505
506void QFontComboBox::setCurrentFont(const QFont &font)
507{
508 Q_D(QFontComboBox);
509 if (font != d->currentFont) {
510 d->currentFont = font;
511 d->updateModel();
512 if (d->currentFont == font) { //else the signal has already be emitted by updateModel
513 emit currentFontChanged(d->currentFont);
514 }
515 }
516}
517
518/*!
519 \fn void QFontComboBox::currentFontChanged(const QFont &font)
520
521 This signal is emitted whenever the current font changes, with
522 the new \a font.
523
524 \sa currentFont
525*/
526
527/*!
528 \reimp
529*/
530bool QFontComboBox::event(QEvent *e)
531{
532 if (e->type() == QEvent::Resize) {
533 QListView *lview = qobject_cast<QListView*>(view());
534 if (lview) {
535 lview->window()->setFixedWidth(qMin(width() * 5 / 3,
536 QWidgetPrivate::availableScreenGeometry(lview).width()));
537 }
538 }
539 return QComboBox::event(e);
540}
541
542/*!
543 \reimp
544*/
545QSize QFontComboBox::sizeHint() const
546{
547 QSize sz = QComboBox::sizeHint();
548 QFontMetrics fm(font());
549 sz.setWidth(fm.horizontalAdvance(u'm') * 14);
550 return sz;
551}
552
553/*!
554 Sets the \a sampleText to show after the font name (when the combo is open) for a given \a writingSystem.
555
556 The sample text given with setSampleTextForFont() has priority.
557
558 \since 6.3
559*/
560void QFontComboBox::setSampleTextForSystem(QFontDatabase::WritingSystem writingSystem, const QString &sampleText)
561{
562 Q_D(QFontComboBox);
563 d->sampleTextForWritingSystem[writingSystem] = sampleText;
564}
565
566
567/*!
568 Returns the sample text to show after the font name (when the combo is open) for a given \a writingSystem.
569
570 \since 6.3
571*/
572QString QFontComboBox::sampleTextForSystem(QFontDatabase::WritingSystem writingSystem) const
573{
574 Q_D(const QFontComboBox);
575 return d->sampleTextForWritingSystem.value(writingSystem);
576}
577
578/*!
579 Sets the \a sampleText to show after the font name (when the combo is open) for a given \a fontFamily.
580
581 The sample text given with this function has priority over the one set with setSampleTextForSystem().
582
583 \since 6.3
584*/
585void QFontComboBox::setSampleTextForFont(const QString &fontFamily, const QString &sampleText)
586{
587 Q_D(QFontComboBox);
588 d->sampleTextForFontFamily[fontFamily] = sampleText;
589}
590
591/*!
592 Returns the sample text to show after the font name (when the combo is open) for a given \a fontFamily.
593
594 \since 6.3
595*/
596QString QFontComboBox::sampleTextForFont(const QString &fontFamily) const
597{
598 Q_D(const QFontComboBox);
599 return d->sampleTextForFontFamily.value(fontFamily);
600}
601
602/*!
603 Sets the \a font to be used to display a given \a fontFamily (when the combo is open).
604
605 \since 6.3
606*/
607void QFontComboBox::setDisplayFont(const QString &fontFamily, const QFont &font)
608{
609 Q_D(QFontComboBox);
610 d->displayFontForFontFamily[fontFamily] = font;
611}
612
613/*!
614 Returns the font (if set) to be used to display a given \a fontFamily (when the combo is open).
615
616 \since 6.3
617*/
618std::optional<QFont> QFontComboBox::displayFont(const QString &fontFamily) const
619{
620 Q_D(const QFontComboBox);
621 return d->displayFontForFontFamily.value(fontFamily, {});
622}
623
624QT_END_NAMESPACE
625
626#include "qfontcombobox.moc"
627#include "moc_qfontcombobox.cpp"
std::array< QMetaObject::Connection, 2 > connections
void currentChanged(const QString &)
QHash< QString, QFont > displayFontForFontFamily
QHash< QFontDatabase::WritingSystem, QString > sampleTextForWritingSystem
QHash< QString, QString > sampleTextForFontFamily
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
This pure abstract function must be reimplemented if you want to provide custom rendering.
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override
This pure abstract function must be reimplemented if you want to provide custom rendering.
QFontComboBoxPrivate * comboPrivate
QFontDatabase::WritingSystem writingSystem
\inmodule QtCore
friend class QPainter
#define qApp
static QFontDatabase::WritingSystem writingSystemFromLocale()
static QFontDatabase::WritingSystem writingSystemFromScript(QLocale::Script script)
static QFontDatabase::WritingSystem writingSystemForFont(const QFont &font, bool *hasLatin)