File indexing completed on 2024-04-21 14:55:53

0001 /*  This file is part of the KDE libraries
0002 
0003     Copyright (C) 2008 Chusslove Illich <caslav.ilic@gmx.net>
0004 
0005     This library is free software; you can redistribute it and/or
0006     modify it under the terms of the GNU Library General Public
0007     License as published by the Free Software Foundation; either
0008     version 2 of the License, or (at your option) any later version.
0009 
0010     This library is distributed in the hope that it will be useful,
0011     but WITHOUT ANY WARRANTY; without even the implied warranty of
0012     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013     Library General Public License for more details.
0014 
0015     You should have received a copy of the GNU Library General Public License
0016     along with this library; see the file COPYING.LIB.  If not, write to
0017     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0018     Boston, MA 02110-1301, USA.
0019 */
0020 
0021 #include "kfontcombobox.h"
0022 #include "fonthelpers_p.h"
0023 
0024 #include "kdebug.h"
0025 #include "klocalizedstring.h"
0026 #include "kcolorscheme.h"
0027 #include "kfontchooser.h"
0028 #include "kcompletion.h"
0029 
0030 #include <QEvent>
0031 #include <QListView>
0032 #include <QFontDatabase>
0033 #include <QIcon>
0034 #include <QAbstractItemDelegate>
0035 #include <QStringListModel>
0036 #include <QPainter>
0037 #include <QList>
0038 #include <QHash>
0039 #include <QScrollBar>
0040 
0041 static QString alphabetSample()
0042 {
0043     return i18nc("short",
0044                  // i18n: A shorter version of the alphabet test phrase translated in
0045                  // another message. It is displayed in the dropdown list of font previews
0046                  // (the font selection combo box), so keep it under the length equivalent
0047                  // to 60 or so proportional Latin characters.
0048                  "The Quick Brown Fox Jumps Over The Lazy Dog");
0049 }
0050 
0051 class KFontFamilyDelegate : public QAbstractItemDelegate
0052 {
0053     Q_OBJECT
0054 public:
0055     KDELIBS4SUPPORT_DEPRECATED explicit KFontFamilyDelegate(QObject *parent);
0056 
0057     void paint(QPainter *painter,
0058                const QStyleOptionViewItem &option,
0059                const QModelIndex &index) const override;
0060 
0061     QSize sizeHint(const QStyleOptionViewItem &option,
0062                    const QModelIndex &index) const override;
0063 
0064     QIcon truetype;
0065     QIcon bitmap;
0066     double sizeFactFamily;
0067     double sizeFactSample;
0068 
0069     QHash<QString, QString> fontFamilyTrMap;
0070 };
0071 
0072 KFontFamilyDelegate::KFontFamilyDelegate(QObject *parent)
0073     : QAbstractItemDelegate(parent)
0074 {
0075     truetype = QIcon(QLatin1String(":/trolltech/styles/commonstyle/images/fonttruetype-16.png"));
0076     bitmap = QIcon(QLatin1String(":/trolltech/styles/commonstyle/images/fontbitmap-16.png"));
0077 
0078     // Font size factors for family name and text sample in font previes,
0079     // multiplies normal font size.
0080     sizeFactFamily = 1.0;
0081     sizeFactSample = 1.0; // better leave at 1, so that user can relate sizes to default
0082 }
0083 
0084 void KFontFamilyDelegate::paint(QPainter *painter,
0085                                 const QStyleOptionViewItem &option,
0086                                 const QModelIndex &index) const
0087 {
0088     QBrush sampleBrush;
0089     if (option.state & QStyle::State_Selected) {
0090         painter->save();
0091         painter->setBrush(option.palette.highlight());
0092         painter->setPen(Qt::NoPen);
0093         painter->drawRect(option.rect);
0094         painter->setPen(QPen(option.palette.highlightedText(), 0));
0095         sampleBrush = option.palette.highlightedText();
0096     } else {
0097         sampleBrush = KColorScheme(QPalette::Normal).foreground(KColorScheme::InactiveText);
0098     }
0099 
0100     QFont baseFont = QFontDatabase::systemFont(QFontDatabase::GeneralFont);
0101     QString trFontFamily = index.data(Qt::DisplayRole).toString();
0102     QString fontFamily = fontFamilyTrMap[trFontFamily];
0103 
0104     // Writing systems provided by the font.
0105     QList<QFontDatabase::WritingSystem> availableSystems = QFontDatabase().writingSystems(fontFamily);
0106 
0107     // Intersect font's writing systems with that specified for
0108     // the language's sample text, to see if the sample can be shown.
0109     // If the font reports no writing systems, assume it can show the sample.
0110     bool canShowLanguageSample = true;
0111     if (availableSystems.count() > 0) {
0112         canShowLanguageSample = false;
0113         QString scriptsSpec = i18nc("Numeric IDs of scripts for font previews",
0114                                     // i18n: Integer which indicates the script you used in the sample text
0115                                     // for font previews in your language. For the possible values, see
0116                                     // https://doc.qt.io/qt-5/qfontdatabase.html#WritingSystem-enum
0117                                     // If the sample text contains several scripts, their IDs can be given
0118                                     // as a comma-separated list (e.g. for Japanese it is "1,27").
0119                                     "1");
0120         QStringList scriptStrIds = scriptsSpec.split(',');
0121         foreach (const QString &scriptStrId, scriptStrIds) {
0122             bool convOk;
0123             int ws = scriptStrId.toInt(&convOk);
0124             if (convOk && ws > 0 && ws < QFontDatabase::WritingSystemsCount
0125                     && availableSystems.contains(static_cast<QFontDatabase::WritingSystem>(ws))) {
0126                 canShowLanguageSample = true;
0127                 break;
0128             }
0129         }
0130     }
0131 
0132     // Choose and paint an icon according to the font type, scalable or bitmat.
0133     const QIcon *icon = &bitmap;
0134     if (QFontDatabase().isSmoothlyScalable(fontFamily)) {
0135         icon = &truetype;
0136     }
0137     QRect r = option.rect;
0138     icon->paint(painter, r, Qt::AlignLeft | Qt::AlignTop);
0139 
0140     // Claim space taken up by the icon.
0141     QSize actualSize = icon->actualSize(r.size());
0142     if (option.direction == Qt::RightToLeft) {
0143         r.setRight(r.right() - actualSize.width() - 4);
0144     } else {
0145         r.setLeft(r.left() + actualSize.width() + 4);
0146     }
0147 
0148     // Draw the font family.
0149     QFont oldPainterFont = painter->font();
0150     QFont familyFont = baseFont;
0151     familyFont.setPointSizeF(familyFont.pointSizeF() * sizeFactFamily);
0152     painter->setFont(familyFont);
0153     painter->drawText(r, Qt::AlignTop | Qt::AlignLeading | Qt::TextSingleLine, trFontFamily);
0154 
0155     // Claim space taken up by the font family name.
0156     int h = painter->fontMetrics().lineSpacing();
0157     r.setTop(r.top() + h);
0158 
0159     // Show text sample in user's language if the writing system is supported,
0160     // otherwise show a collage of generic script samples provided by Qt.
0161     // If the font does not report what it supports, assume all.
0162     QString sample;
0163     if (canShowLanguageSample) {
0164         sample = alphabetSample();
0165     } else {
0166         foreach (const QFontDatabase::WritingSystem &ws, availableSystems) {
0167             sample += QFontDatabase::writingSystemSample(ws) + "  ";
0168             if (sample.length() > 40) { // do not let the sample be too long
0169                 break;
0170             }
0171         }
0172         sample = sample.trimmed();
0173     }
0174     QFont sampleFont;
0175     sampleFont.setFamily(fontFamily);
0176     sampleFont.setPointSizeF(sampleFont.pointSizeF() * sizeFactSample);
0177     painter->setFont(sampleFont);
0178     QPen oldPen = painter->pen();
0179     painter->setPen(sampleBrush.color());
0180     painter->drawText(r, Qt::AlignTop | Qt::AlignLeading | Qt::TextSingleLine, sample);
0181     painter->setFont(oldPainterFont);
0182     painter->setPen(oldPen);
0183 
0184     if (option.state & QStyle::State_Selected) {
0185         painter->restore();
0186     }
0187 }
0188 
0189 QSize KFontFamilyDelegate::sizeHint(const QStyleOptionViewItem &option,
0190                                     const QModelIndex &index) const
0191 {
0192     Q_UNUSED(option);
0193 
0194     QFont baseFont = QFontDatabase::systemFont(QFontDatabase::GeneralFont);
0195     QString trFontFamily = index.data(Qt::DisplayRole).toString();
0196     QString fontFamily = fontFamilyTrMap[trFontFamily];
0197 
0198     QFont familyFont = baseFont;
0199     familyFont.setPointSizeF(familyFont.pointSizeF() * sizeFactFamily);
0200     QFontMetrics familyMetrics(familyFont);
0201 
0202     QFont sampleFont = baseFont;
0203     sampleFont.setFamily(fontFamily);
0204     sampleFont.setPointSizeF(sampleFont.pointSizeF() * sizeFactSample);
0205     QFontMetrics sampleMetrics(sampleFont);
0206     QString sample = alphabetSample();
0207 
0208     // Only the hight matters here, the width is mandated by KFontComboBox::event()
0209     return QSize(qMax(familyMetrics.width(trFontFamily), sampleMetrics.width(sample)),
0210                  qRound(familyMetrics.lineSpacing() + sampleMetrics.lineSpacing() * 1.2));
0211 }
0212 
0213 class KFontComboBoxPrivate
0214 {
0215 public:
0216     KFontComboBoxPrivate(KFontComboBox *parent);
0217     void updateDatabase();
0218     void updateIndexToFont();
0219     void _k_currentFontChanged(int index);
0220 
0221     KFontComboBox *k;
0222     QFont currentFont;
0223     bool onlyFixed;
0224     bool signalsAllowed;
0225     KFontFamilyDelegate *delegate;
0226     QStringListModel *model;
0227     QStringList fontList;
0228 };
0229 
0230 KFontComboBoxPrivate::KFontComboBoxPrivate(KFontComboBox *parent)
0231     : k(parent),
0232       currentFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont)),
0233       onlyFixed(false),
0234       signalsAllowed(true)
0235 {
0236 }
0237 
0238 void KFontComboBoxPrivate::updateDatabase()
0239 {
0240     QStringList fontFamilies = fontList;
0241     if (fontList.isEmpty()) {
0242         KFontChooser::getFontList(fontFamilies,
0243                                   onlyFixed ? KFontChooser::FixedWidthFonts : 0);
0244     }
0245 
0246     // Translate font families for the list model.
0247     delegate->fontFamilyTrMap.clear();
0248     QStringList trFontFamilies =
0249         translateFontNameList(fontFamilies, &(delegate->fontFamilyTrMap));
0250 
0251     // Add families to the list model and completion.
0252     model->setStringList(trFontFamilies);
0253     KCompletion *completion = k->completionObject();
0254     if (completion) {
0255         completion->setItems(trFontFamilies);
0256         completion->setIgnoreCase(true);
0257     }
0258 }
0259 
0260 void KFontComboBoxPrivate::updateIndexToFont()
0261 {
0262     // QFontInfo necessary to return the family with proper casing.
0263     QString selectedFontFamily = QFontInfo(currentFont).family();
0264     QString trSelectedFontFamily = translateFontName(selectedFontFamily);
0265     const QStringList trFontFamilies = model->stringList();
0266     if (trFontFamilies.isEmpty()) {
0267         return;
0268     }
0269 
0270     // Match the font's family with an item in the list.
0271     int index = 0;
0272     foreach (const QString &trFontFamily, trFontFamilies) {
0273         if (trSelectedFontFamily == trFontFamily) {
0274             break;
0275         }
0276         ++index;
0277     }
0278     if (index == trFontFamilies.count()) {
0279         // If no family matched, change font to first on the list.
0280         index = 0;
0281         currentFont = QFont(delegate->fontFamilyTrMap[trFontFamilies[0]]);
0282         emit k->currentFontChanged(currentFont);
0283     }
0284 
0285     // Set the new list item.
0286     signalsAllowed = false;
0287     k->setCurrentIndex(index);
0288     signalsAllowed = true;
0289 }
0290 
0291 void KFontComboBoxPrivate::_k_currentFontChanged(int index)
0292 {
0293     if (!signalsAllowed) {
0294         return;
0295     }
0296 
0297     QString trFontFamily = k->itemText(index);
0298     QString fontFamily = delegate->fontFamilyTrMap[trFontFamily];
0299     if (!fontFamily.isEmpty()) {
0300         currentFont = QFont(fontFamily);
0301         emit k->currentFontChanged(currentFont);
0302     } else {
0303         // Unknown font family given. Just remove from the list.
0304         // This should not happen, as adding arbitrary font names is prevented.
0305         QStringList lst = model->stringList();
0306         lst.removeAll(trFontFamily);
0307         model->setStringList(lst);
0308     }
0309 }
0310 
0311 KFontComboBox::KFontComboBox(QWidget *parent)
0312     : KComboBox(true, parent), d(new KFontComboBoxPrivate(this))
0313 {
0314     // Inputing arbitrary font names does not make sense.
0315     setInsertPolicy(QComboBox::NoInsert);
0316 
0317     // Special list item painter showing font previews and its list model.
0318     d->delegate = new KFontFamilyDelegate(this);
0319     setItemDelegate(d->delegate);
0320     d->model = new QStringListModel(this);
0321     setModel(d->model);
0322 
0323     // Set current font when a new family has been chosen in the combo.
0324     connect(this, SIGNAL(currentIndexChanged(int)),
0325             this, SLOT(_k_currentFontChanged(int)));
0326 
0327     // Initialize font selection and list of available fonts.
0328     d->updateDatabase();
0329     d->updateIndexToFont();
0330 }
0331 
0332 KFontComboBox::~KFontComboBox()
0333 {
0334     delete d;
0335 }
0336 
0337 void KFontComboBox::setOnlyFixed(bool onlyFixed)
0338 {
0339     if (onlyFixed != d->onlyFixed) {
0340         d->onlyFixed = onlyFixed;
0341         d->updateDatabase();
0342     }
0343 }
0344 
0345 void KFontComboBox::setFontList(const QStringList &fontList)
0346 {
0347     if (fontList != d->fontList) {
0348         d->fontList = fontList;
0349         d->updateDatabase();
0350     }
0351 }
0352 
0353 QFont KFontComboBox::currentFont() const
0354 {
0355     return d->currentFont;
0356 }
0357 
0358 void KFontComboBox::setCurrentFont(const QFont &font)
0359 {
0360     if (font != d->currentFont) {
0361         d->currentFont = font;
0362         emit currentFontChanged(d->currentFont);
0363         d->updateIndexToFont();
0364     }
0365 }
0366 
0367 bool KFontComboBox::event(QEvent *e)
0368 {
0369     if (e->type() == QEvent::Resize) {
0370         QListView *lview = qobject_cast<QListView *>(view());
0371         if (lview) {
0372             QString sample = alphabetSample();
0373             // Limit text sample length to avoid too wide list view.
0374             if (sample.length() > 60) {
0375                 sample = sample.left(57) + "...";
0376             }
0377             QFont approxFont = QFontDatabase::systemFont(QFontDatabase::GeneralFont);
0378             approxFont.setPointSizeF(approxFont.pointSizeF()
0379                                      * d->delegate->sizeFactSample);
0380             int widgetWidth = width();
0381             int sampleWidth = QFontMetrics(approxFont).width(sample);
0382             sampleWidth = qRound(sampleWidth * 1.1); // extra for wider fonts
0383             int iconWidth = d->delegate->truetype.actualSize(size()).width();
0384             int vsbarWidth = 0;
0385             if (lview->verticalScrollBar()) {
0386                 vsbarWidth = lview->verticalScrollBar()->width();
0387             }
0388             lview->window()->setFixedWidth(qMax(widgetWidth, sampleWidth)
0389                                            + iconWidth + vsbarWidth);
0390         }
0391     }
0392     return KComboBox::event(e);
0393 }
0394 
0395 QSize KFontComboBox::sizeHint() const
0396 {
0397     QSize sz = KComboBox::sizeHint();
0398     QFontMetrics fm(QFontDatabase::systemFont(QFontDatabase::GeneralFont));
0399     sz.setWidth(fm.width("m") * 14);
0400     return sz;
0401 }
0402 
0403 #include "fonthelpers.cpp"
0404 
0405 #include "kfontcombobox.moc"
0406 #include "moc_kfontcombobox.cpp"