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