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 
53 QT_BEGIN_NAMESPACE
54 
writingSystemFromScript(QLocale::Script script)55 static 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 
writingSystemFromLocale()125 static 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 
writingSystemForFont(const QFont & font,bool * hasLatin)137 static 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 
183 class QFontFamilyDelegate : public QAbstractItemDelegate
184 {
185     Q_OBJECT
186 public:
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 
QFontFamilyDelegate(QObject * parent)202 QFontFamilyDelegate::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 
paint(QPainter * painter,const QStyleOptionViewItem & option,const QModelIndex & index) const210 void 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 
sizeHint(const QStyleOptionViewItem & option,const QModelIndex & index) const286 QSize 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 
298 class QFontComboBoxPrivate : public QComboBoxPrivate
299 {
300 public:
QFontComboBoxPrivate()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 
_q_updateModel()313 void 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 
_q_currentChanged(const QString & text)370 void 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 */
QFontComboBox(QWidget * parent)415 QFontComboBox::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 */
~QFontComboBox()441 QFontComboBox::~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 
setWritingSystem(QFontDatabase::WritingSystem script)455 void 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 
writingSystem() const464 QFontDatabase::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 */
setFontFilters(FontFilters filters)493 void QFontComboBox::setFontFilters(FontFilters filters)
494 {
495     Q_D(QFontComboBox);
496     d->filters = filters;
497     d->_q_updateModel();
498 }
499 
fontFilters() const500 QFontComboBox::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 */
currentFont() const512 QFont QFontComboBox::currentFont() const
513 {
514     Q_D(const QFontComboBox);
515     return d->currentFont;
516 }
517 
setCurrentFont(const QFont & font)518 void 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 */
event(QEvent * e)542 bool 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 */
sizeHint() const557 QSize 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 
565 QT_END_NAMESPACE
566 
567 #include "qfontcombobox.moc"
568 #include "moc_qfontcombobox.cpp"
569