File indexing completed on 2024-04-28 03:59:05

0001 /*
0002     SPDX-FileCopyrightText: 1996 Bernd Johannes Wuebben <wuebben@kde.org>
0003     SPDX-FileCopyrightText: 1999 Preston Brown <pbrown@kde.org>
0004     SPDX-FileCopyrightText: 1999 Mario Weilguni <mweilguni@kde.org>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "kfontchooser.h"
0010 #include "fonthelpers_p.h"
0011 #include "ui_kfontchooserwidget.h"
0012 
0013 #include "loggingcategory.h"
0014 
0015 #include <QCheckBox>
0016 #include <QDoubleSpinBox>
0017 #include <QFontDatabase>
0018 #include <QGroupBox>
0019 #include <QGuiApplication>
0020 #include <QLabel>
0021 #include <QLayout>
0022 #include <QListWidget>
0023 #include <QLocale>
0024 #include <QScrollBar>
0025 #include <QSplitter>
0026 #include <QTextEdit>
0027 
0028 #include <algorithm>
0029 #include <cmath>
0030 
0031 // When message extraction needs to be avoided.
0032 #define TR_NOX tr
0033 
0034 static int minimumListWidth(const QListWidget *list)
0035 {
0036     QFontMetrics fm = list->fontMetrics();
0037 
0038     const int extraSpace = fm.horizontalAdvance(QLatin1Char(' ')) * 2;
0039 
0040     // Minimum initial size
0041     int width = 40;
0042     for (int i = 0, rows = list->count(); i < rows; ++i) {
0043         int itemWidth = fm.horizontalAdvance(list->item(i)->text());
0044         // ...and add a space on both sides for a not too tight look.
0045         itemWidth += extraSpace;
0046         width = std::max(width, itemWidth);
0047     }
0048 
0049     width += list->frameWidth() * 2;
0050     width += list->verticalScrollBar()->sizeHint().width();
0051     return width;
0052 }
0053 
0054 static int minimumListHeight(const QListWidget *list, int numVisibleEntry)
0055 {
0056     int w = list->count() > 0 ? list->visualItemRect(list->item(0)).height() : list->fontMetrics().lineSpacing();
0057 
0058     if (w < 0) {
0059         w = 10;
0060     }
0061     if (numVisibleEntry <= 0) {
0062         numVisibleEntry = 4;
0063     }
0064     return (w * numVisibleEntry + 2 * list->frameWidth());
0065 }
0066 
0067 static QString formatFontSize(qreal size)
0068 {
0069     return QLocale::system().toString(size, 'f', (size == floor(size)) ? 0 : 1);
0070 }
0071 
0072 class KFontChooserPrivate
0073 {
0074     Q_DECLARE_TR_FUNCTIONS(KFontChooser)
0075 
0076 public:
0077     KFontChooserPrivate(KFontChooser::DisplayFlags flags, KFontChooser *qq)
0078         : q(qq)
0079         , m_flags(flags)
0080     {
0081         m_palette.setColor(QPalette::Active, QPalette::Text, Qt::black);
0082         m_palette.setColor(QPalette::Active, QPalette::Base, Qt::white);
0083     }
0084 
0085     void init();
0086     void setFamilyBoxItems(const QStringList &fonts = {});
0087     int nearestSizeRow(qreal val, bool customize);
0088     qreal fillSizeList(const QList<qreal> &sizes = QList<qreal>());
0089     qreal setupSizeListBox(const QString &family, const QString &style);
0090 
0091     void setupDisplay();
0092     QString styleIdentifier(const QFont &font);
0093 
0094     void slotFamilySelected(const QString &);
0095     void slotSizeSelected(const QString &);
0096     void slotStyleSelected(const QString &);
0097     void displaySample(const QFont &font);
0098     void slotSizeValue(double);
0099 
0100     KFontChooser *q;
0101 
0102     std::unique_ptr<Ui_KFontChooserWidget> m_ui;
0103 
0104     KFontChooser::DisplayFlags m_flags = KFontChooser::NoDisplayFlags;
0105 
0106     QPalette m_palette;
0107 
0108     QFont m_selectedFont;
0109 
0110     QString m_selectedStyle;
0111     qreal m_selectedSize = -1.0;
0112 
0113     QString m_standardSizeAtCustom;
0114     int m_customSizeRow = -1;
0115 
0116     bool m_signalsAllowed = true;
0117     bool m_usingFixed = false;
0118 
0119     // Mappings of translated to Qt originated family and style strings.
0120     FontFamiliesMap m_qtFamilies;
0121     std::map<QString, QString> m_qtStyles;
0122     // Mapping of translated style strings to internal style identifiers.
0123     std::map<QString, QString> m_styleIDs;
0124 };
0125 
0126 KFontChooser::KFontChooser(QWidget *parent)
0127     : QWidget(parent)
0128     , d(new KFontChooserPrivate(KFontChooser::DisplayFrame, this))
0129 {
0130     d->init();
0131 }
0132 
0133 KFontChooser::KFontChooser(const DisplayFlags flags, QWidget *parent)
0134     : QWidget(parent)
0135     , d(new KFontChooserPrivate(flags, this))
0136 {
0137     d->init();
0138 }
0139 
0140 KFontChooser::~KFontChooser() = default;
0141 
0142 void KFontChooserPrivate::init()
0143 {
0144     m_usingFixed = m_flags & KFontChooser::FixedFontsOnly;
0145 
0146     // The main layout is divided horizontally into a top part with
0147     // the font attribute widgets (family, style, size) and a bottom
0148     // part with a preview of the selected font
0149     QVBoxLayout *mainLayout = new QVBoxLayout(q);
0150     mainLayout->setContentsMargins(0, 0, 0, 0);
0151 
0152     QWidget *page = m_flags & KFontChooser::DisplayFrame ? new QGroupBox(KFontChooser::tr("Requested Font", "@title:group"), q) : new QWidget(q);
0153     mainLayout->addWidget(page);
0154 
0155     m_ui.reset(new Ui_KFontChooserWidget);
0156     m_ui->setupUi(page);
0157     // Increase spacing on top of the preview field and then reset the other layouts
0158     // back to a standard value.
0159     m_ui->sampleTextEditLayout->setSpacing(q->style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing));
0160     m_ui->mainHorizontalLayout->setSpacing(q->style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing));
0161     m_ui->gridLayout->setVerticalSpacing(q->style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing) * 2);
0162     m_ui->gridLayout->setHorizontalSpacing(q->style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing));
0163 
0164     // Deprecated, we'll call show() if building with deprecated code
0165     m_ui->sizeIsRelativeCheckBox->hide();
0166 
0167     const bool isDiffMode = m_flags & KFontChooser::ShowDifferences;
0168 
0169     QObject::connect(m_ui->familyListWidget, &QListWidget::currentTextChanged, [this](const QString &family) {
0170         slotFamilySelected(family);
0171     });
0172 
0173     if (isDiffMode) {
0174         m_ui->familyLabel->hide();
0175         m_ui->familyListWidget->setEnabled(false);
0176         QObject::connect(m_ui->familyCheckBox, &QCheckBox::toggled, m_ui->familyListWidget, &QWidget::setEnabled);
0177     } else {
0178         m_ui->familyCheckBox->hide();
0179     }
0180 
0181     setFamilyBoxItems();
0182 
0183     // If the calling app sets FixedFontsOnly, don't show the "show fixed only" checkbox
0184     m_ui->onlyFixedCheckBox->setVisible(!m_usingFixed);
0185 
0186     if (!m_ui->onlyFixedCheckBox->isHidden()) {
0187         QObject::connect(m_ui->onlyFixedCheckBox, &QCheckBox::toggled, q, [this](const bool state) {
0188             q->setFont(m_selectedFont, state);
0189         });
0190 
0191         if (isDiffMode) { // In this mode follow the state of the m_ui->familyCheckBox
0192             m_ui->onlyFixedCheckBox->setEnabled(false);
0193             QObject::connect(m_ui->familyCheckBox, &QCheckBox::toggled, m_ui->onlyFixedCheckBox, &QWidget::setEnabled);
0194         }
0195     }
0196 
0197     // Populate usual styles, to determine minimum list width;
0198     // will be replaced later with correct styles.
0199     m_ui->styleListWidget->addItem(KFontChooser::tr("Normal", "@item font"));
0200     m_ui->styleListWidget->addItem(KFontChooser::tr("Italic", "@item font"));
0201     m_ui->styleListWidget->addItem(KFontChooser::tr("Oblique", "@item font"));
0202     m_ui->styleListWidget->addItem(KFontChooser::tr("Bold", "@item font"));
0203     m_ui->styleListWidget->addItem(KFontChooser::tr("Bold Condensed Oblique", "@item font"));
0204     m_ui->styleListWidget->setMinimumWidth(minimumListWidth(m_ui->styleListWidget));
0205 
0206     QObject::connect(m_ui->styleListWidget, &QListWidget::currentTextChanged, [this](const QString &style) {
0207         slotStyleSelected(style);
0208     });
0209 
0210     if (isDiffMode) {
0211         m_ui->styleLabel->hide();
0212         m_ui->styleListWidget->setEnabled(false);
0213         QObject::connect(m_ui->styleCheckBox, &QCheckBox::toggled, m_ui->styleListWidget, &QWidget::setEnabled);
0214     } else {
0215         m_ui->styleCheckBox->hide();
0216     }
0217 
0218     // Populate with usual sizes, to determine minimum list width;
0219     // will be replaced later with correct sizes.
0220     fillSizeList();
0221 
0222     QObject::connect(m_ui->sizeSpinBox, &QDoubleSpinBox::valueChanged, [this](const double size) {
0223         slotSizeValue(size);
0224     });
0225 
0226     QObject::connect(m_ui->sizeListWidget, &QListWidget::currentTextChanged, [this](const QString &size) {
0227         slotSizeSelected(size);
0228     });
0229 
0230     if (isDiffMode) {
0231         m_ui->sizeLabel->hide();
0232         m_ui->sizeListWidget->setEnabled(false);
0233         m_ui->sizeSpinBox->setEnabled(false);
0234         QObject::connect(m_ui->sizeCheckBox, &QCheckBox::toggled, m_ui->sizeListWidget, &QWidget::setEnabled);
0235         QObject::connect(m_ui->sizeCheckBox, &QCheckBox::toggled, m_ui->sizeSpinBox, &QWidget::setEnabled);
0236     } else {
0237         m_ui->sizeCheckBox->hide();
0238     }
0239 
0240     QFont tmpFont(q->font().family(), 64, QFont::Black);
0241     m_ui->sampleTextEdit->setFont(tmpFont);
0242     m_ui->sampleTextEdit->setMinimumHeight(m_ui->sampleTextEdit->fontMetrics().lineSpacing());
0243     // tr: A classical test phrase, with all letters of the English alphabet.
0244     // Replace it with a sample text in your language, such that it is
0245     // representative of language's writing system.
0246     // If you wish, you can input several lines of text separated by \n.
0247     q->setSampleText(KFontChooser::tr("The Quick Brown Fox Jumps Over The Lazy Dog"));
0248     m_ui->sampleTextEdit->setTextCursor(QTextCursor(m_ui->sampleTextEdit->document()));
0249 
0250     QObject::connect(q, &KFontChooser::fontSelected, q, [this](const QFont &font) {
0251         displaySample(font);
0252     });
0253 
0254     // lets initialize the display if possible
0255     if (m_usingFixed) {
0256         q->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont), true);
0257     } else {
0258         q->setFont(QGuiApplication::font(), false);
0259     }
0260 
0261     // Set the minimum height for the list widgets
0262     q->setMinVisibleItems(4);
0263 
0264     // Set focus to the size list as this is the most commonly changed property
0265     m_ui->sizeListWidget->setFocus();
0266 }
0267 
0268 void KFontChooser::setColor(const QColor &col)
0269 {
0270     d->m_palette.setColor(QPalette::Active, QPalette::Text, col);
0271     QPalette pal = d->m_ui->sampleTextEdit->palette();
0272     pal.setColor(QPalette::Active, QPalette::Text, col);
0273     d->m_ui->sampleTextEdit->setPalette(pal);
0274     QTextCursor cursor = d->m_ui->sampleTextEdit->textCursor();
0275     d->m_ui->sampleTextEdit->selectAll();
0276     d->m_ui->sampleTextEdit->setTextColor(col);
0277     d->m_ui->sampleTextEdit->setTextCursor(cursor);
0278 }
0279 
0280 QColor KFontChooser::color() const
0281 {
0282     return d->m_palette.color(QPalette::Active, QPalette::Text);
0283 }
0284 
0285 void KFontChooser::setBackgroundColor(const QColor &col)
0286 {
0287     d->m_palette.setColor(QPalette::Active, QPalette::Base, col);
0288     QPalette pal = d->m_ui->sampleTextEdit->palette();
0289     pal.setColor(QPalette::Active, QPalette::Base, col);
0290     d->m_ui->sampleTextEdit->setPalette(pal);
0291 }
0292 
0293 QColor KFontChooser::backgroundColor() const
0294 {
0295     return d->m_palette.color(QPalette::Active, QPalette::Base);
0296 }
0297 
0298 QString KFontChooser::sampleText() const
0299 {
0300     return d->m_ui->sampleTextEdit->toPlainText();
0301 }
0302 
0303 void KFontChooser::setSampleText(const QString &text)
0304 {
0305     d->m_ui->sampleTextEdit->setPlainText(text);
0306 }
0307 
0308 void KFontChooser::setSampleBoxVisible(bool visible)
0309 {
0310     d->m_ui->sampleTextEdit->setVisible(visible);
0311 }
0312 
0313 QSize KFontChooser::sizeHint(void) const
0314 {
0315     return minimumSizeHint();
0316 }
0317 
0318 void KFontChooser::enableColumn(int column, bool state)
0319 {
0320     if (column & FamilyList) {
0321         d->m_ui->familyListWidget->setEnabled(state);
0322     }
0323     if (column & StyleList) {
0324         d->m_ui->styleListWidget->setEnabled(state);
0325     }
0326     if (column & SizeList) {
0327         d->m_ui->sizeListWidget->setEnabled(state);
0328         d->m_ui->sizeSpinBox->setEnabled(state);
0329     }
0330 }
0331 
0332 void KFontChooser::setFont(const QFont &aFont, bool onlyFixed)
0333 {
0334     d->m_selectedFont = aFont;
0335     d->m_selectedSize = aFont.pointSizeF();
0336     if (d->m_selectedSize == -1) {
0337         d->m_selectedSize = QFontInfo(aFont).pointSizeF();
0338     }
0339 
0340     if (onlyFixed != d->m_usingFixed) {
0341         d->m_usingFixed = onlyFixed;
0342         d->setFamilyBoxItems();
0343     }
0344     d->setupDisplay();
0345 }
0346 
0347 KFontChooser::FontDiffFlags KFontChooser::fontDiffFlags() const
0348 {
0349     FontDiffFlags diffFlags = NoFontDiffFlags;
0350 
0351     if (d->m_ui->familyCheckBox->isChecked()) {
0352         diffFlags |= FontDiffFamily;
0353     }
0354 
0355     if (d->m_ui->styleCheckBox->isChecked()) {
0356         diffFlags |= FontDiffStyle;
0357     }
0358 
0359     if (d->m_ui->sizeCheckBox->isChecked()) {
0360         diffFlags |= FontDiffSize;
0361     }
0362 
0363     return diffFlags;
0364 }
0365 
0366 QFont KFontChooser::font() const
0367 {
0368     return d->m_selectedFont;
0369 }
0370 
0371 static bool isDefaultFontStyleName(const QString &style)
0372 {
0373     /* clang-format off */
0374     // Ordered by commonness, i.e. "Regular" is the most common
0375     return style == QLatin1String("Regular")
0376         || style == QLatin1String("Normal")
0377         || style == QLatin1String("Book")
0378         || style == QLatin1String("Roman");
0379     /* clang-format on */
0380 }
0381 
0382 void KFontChooserPrivate::slotFamilySelected(const QString &family)
0383 {
0384     if (!m_signalsAllowed) {
0385         return;
0386     }
0387     m_signalsAllowed = false;
0388 
0389     QString currentFamily;
0390     if (family.isEmpty()) {
0391         Q_ASSERT(m_ui->familyListWidget->currentItem());
0392         if (m_ui->familyListWidget->currentItem()) {
0393             currentFamily = m_qtFamilies[m_ui->familyListWidget->currentItem()->text()];
0394         }
0395     } else {
0396         currentFamily = m_qtFamilies[family];
0397     }
0398 
0399     // Get the list of styles available in this family.
0400     QStringList styles = QFontDatabase::styles(currentFamily);
0401     if (styles.isEmpty()) {
0402         // Avoid extraction, it is in kdeqt.po
0403         styles.append(TR_NOX("Normal", "QFontDatabase"));
0404     }
0405 
0406     // Always prepend Regular, Normal, Book or Roman, this way if "m_selectedStyle"
0407     // in the code below is empty, selecting index 0 should work better
0408     std::sort(styles.begin(), styles.end(), [](const QString &a, const QString &b) {
0409         if (isDefaultFontStyleName(a)) {
0410             return true;
0411         } else if (isDefaultFontStyleName(b)) {
0412             return false;
0413         }
0414         return false;
0415     });
0416 
0417     // Filter style strings and add to the listbox.
0418     QString pureFamily;
0419     splitFontString(family, &pureFamily);
0420     QStringList filteredStyles;
0421     m_qtStyles.clear();
0422     m_styleIDs.clear();
0423 
0424     const QStringList origStyles = styles;
0425     for (const QString &style : origStyles) {
0426         // Sometimes the font database will report an invalid style,
0427         // that falls back back to another when set.
0428         // Remove such styles, by checking set/get round-trip.
0429         QFont testFont = QFontDatabase::font(currentFamily, style, 10);
0430         if (QFontDatabase::styleString(testFont) != style) {
0431             styles.removeAll(style);
0432             continue;
0433         }
0434 
0435         QString fstyle = tr("%1", "@item Font style").arg(style);
0436         if (!filteredStyles.contains(fstyle)) {
0437             filteredStyles.append(fstyle);
0438             m_qtStyles.insert({fstyle, style});
0439             m_styleIDs.insert({fstyle, styleIdentifier(testFont)});
0440         }
0441     }
0442     m_ui->styleListWidget->clear();
0443     m_ui->styleListWidget->addItems(filteredStyles);
0444 
0445     // Try to set the current style in the listbox to that previous.
0446     int listPos = filteredStyles.indexOf(m_selectedStyle.isEmpty() ? TR_NOX("Normal", "QFontDatabase") : m_selectedStyle);
0447     if (listPos < 0) {
0448         // Make extra effort to have Italic selected when Oblique was chosen,
0449         // and vice versa, as that is what the user would probably want.
0450         QString styleIt = tr("Italic", "@item font");
0451         QString styleOb = tr("Oblique", "@item font");
0452         for (int i = 0; i < 2; ++i) {
0453             int pos = m_selectedStyle.indexOf(styleIt);
0454             if (pos >= 0) {
0455                 QString style = m_selectedStyle;
0456                 style.replace(pos, styleIt.length(), styleOb);
0457                 listPos = filteredStyles.indexOf(style);
0458                 if (listPos >= 0) {
0459                     break;
0460                 }
0461             }
0462             qSwap(styleIt, styleOb);
0463         }
0464     }
0465     m_ui->styleListWidget->setCurrentRow(listPos >= 0 ? listPos : 0);
0466     const QString currentStyle = m_qtStyles[m_ui->styleListWidget->currentItem()->text()];
0467 
0468     // Recompute the size listbox for this family/style.
0469     qreal currentSize = setupSizeListBox(currentFamily, currentStyle);
0470     m_ui->sizeSpinBox->setValue(currentSize);
0471 
0472     m_selectedFont = QFontDatabase::font(currentFamily, currentStyle, static_cast<int>(currentSize));
0473     if (QFontDatabase::isSmoothlyScalable(currentFamily, currentStyle) && m_selectedFont.pointSize() == floor(currentSize)) {
0474         m_selectedFont.setPointSizeF(currentSize);
0475     }
0476     Q_EMIT q->fontSelected(m_selectedFont);
0477 
0478     m_signalsAllowed = true;
0479 }
0480 
0481 void KFontChooserPrivate::slotStyleSelected(const QString &style)
0482 {
0483     if (!m_signalsAllowed) {
0484         return;
0485     }
0486     m_signalsAllowed = false;
0487 
0488     const QString currentFamily = m_qtFamilies[m_ui->familyListWidget->currentItem()->text()];
0489     const QString currentStyle = !style.isEmpty() ? m_qtStyles[style] : m_qtStyles[m_ui->styleListWidget->currentItem()->text()];
0490 
0491     // Recompute the size listbox for this family/style.
0492     qreal currentSize = setupSizeListBox(currentFamily, currentStyle);
0493     m_ui->sizeSpinBox->setValue(currentSize);
0494 
0495     m_selectedFont = QFontDatabase::font(currentFamily, currentStyle, static_cast<int>(currentSize));
0496     if (QFontDatabase::isSmoothlyScalable(currentFamily, currentStyle) && m_selectedFont.pointSize() == floor(currentSize)) {
0497         m_selectedFont.setPointSizeF(currentSize);
0498     }
0499     Q_EMIT q->fontSelected(m_selectedFont);
0500 
0501     if (!style.isEmpty()) {
0502         m_selectedStyle = currentStyle;
0503     }
0504 
0505     m_signalsAllowed = true;
0506 }
0507 
0508 void KFontChooserPrivate::slotSizeSelected(const QString &size)
0509 {
0510     if (!m_signalsAllowed) {
0511         return;
0512     }
0513 
0514     m_signalsAllowed = false;
0515 
0516     qreal currentSize = 0.0;
0517     if (size.isEmpty()) {
0518         currentSize = QLocale::system().toDouble(m_ui->sizeListWidget->currentItem()->text());
0519     } else {
0520         currentSize = QLocale::system().toDouble(size);
0521     }
0522 
0523     // Reset the customized size slot in the list if not needed.
0524     if (m_customSizeRow >= 0 && m_selectedFont.pointSizeF() != currentSize) {
0525         m_ui->sizeListWidget->item(m_customSizeRow)->setText(m_standardSizeAtCustom);
0526         m_customSizeRow = -1;
0527     }
0528 
0529     m_ui->sizeSpinBox->setValue(currentSize);
0530     m_selectedFont.setPointSizeF(currentSize);
0531     Q_EMIT q->fontSelected(m_selectedFont);
0532 
0533     if (!size.isEmpty()) {
0534         m_selectedSize = currentSize;
0535     }
0536 
0537     m_signalsAllowed = true;
0538 }
0539 
0540 void KFontChooserPrivate::slotSizeValue(double dval)
0541 {
0542     if (!m_signalsAllowed) {
0543         return;
0544     }
0545     m_signalsAllowed = false;
0546 
0547     // We compare with qreal, so convert for platforms where qreal != double.
0548     qreal val = qreal(dval);
0549 
0550     // Reset current size slot in list if it was customized.
0551     if (m_customSizeRow >= 0 && m_ui->sizeListWidget->currentRow() == m_customSizeRow) {
0552         m_ui->sizeListWidget->item(m_customSizeRow)->setText(m_standardSizeAtCustom);
0553         m_customSizeRow = -1;
0554     }
0555 
0556     bool canCustomize = true;
0557 
0558     const QString family = m_qtFamilies[m_ui->familyListWidget->currentItem()->text()];
0559     const QString style = m_qtStyles[m_ui->styleListWidget->currentItem()->text()];
0560 
0561     // For Qt-bad-sizes workaround: skip this block unconditionally
0562     if (!QFontDatabase::isSmoothlyScalable(family, style)) {
0563         // Bitmap font, allow only discrete sizes.
0564         // Determine the nearest in the direction of change.
0565         canCustomize = false;
0566         int nrows = m_ui->sizeListWidget->count();
0567         int row = m_ui->sizeListWidget->currentRow();
0568         int nrow;
0569         if (val - m_selectedFont.pointSizeF() > 0) {
0570             for (nrow = row + 1; nrow < nrows; ++nrow) {
0571                 if (QLocale::system().toDouble(m_ui->sizeListWidget->item(nrow)->text()) >= val) {
0572                     break;
0573                 }
0574             }
0575         } else {
0576             for (nrow = row - 1; nrow >= 0; --nrow) {
0577                 if (QLocale::system().toDouble(m_ui->sizeListWidget->item(nrow)->text()) <= val) {
0578                     break;
0579                 }
0580             }
0581         }
0582         // Make sure the new row is not out of bounds.
0583         nrow = nrow < 0 ? 0 : nrow >= nrows ? nrows - 1 : nrow;
0584         // Get the size from the new row and set the spinbox to that size.
0585         val = QLocale::system().toDouble(m_ui->sizeListWidget->item(nrow)->text());
0586         m_ui->sizeSpinBox->setValue(val);
0587     }
0588 
0589     // Set the current size in the size listbox.
0590     int row = nearestSizeRow(val, canCustomize);
0591     m_ui->sizeListWidget->setCurrentRow(row);
0592 
0593     m_selectedSize = val;
0594     m_selectedFont.setPointSizeF(val);
0595     Q_EMIT q->fontSelected(m_selectedFont);
0596 
0597     m_signalsAllowed = true;
0598 }
0599 
0600 void KFontChooserPrivate::displaySample(const QFont &font)
0601 {
0602     m_ui->sampleTextEdit->setFont(font);
0603 }
0604 
0605 int KFontChooserPrivate::nearestSizeRow(qreal val, bool customize)
0606 {
0607     qreal diff = 1000;
0608     int row = 0;
0609     for (int r = 0; r < m_ui->sizeListWidget->count(); ++r) {
0610         qreal cval = QLocale::system().toDouble(m_ui->sizeListWidget->item(r)->text());
0611         if (qAbs(cval - val) < diff) {
0612             diff = qAbs(cval - val);
0613             row = r;
0614         }
0615     }
0616     // For Qt-bad-sizes workaround: ignore value of customize, use true
0617     if (customize && diff > 0) {
0618         m_customSizeRow = row;
0619         m_standardSizeAtCustom = m_ui->sizeListWidget->item(row)->text();
0620         m_ui->sizeListWidget->item(row)->setText(formatFontSize(val));
0621     }
0622     return row;
0623 }
0624 
0625 qreal KFontChooserPrivate::fillSizeList(const QList<qreal> &sizes_)
0626 {
0627     if (m_ui->sizeListWidget->isHidden()) {
0628         qCWarning(KWidgetsAddonsLog) << "Trying to fill the font size listwidget, but the widget is hidden.";
0629         return 0.0;
0630     }
0631 
0632     QList<qreal> sizes = sizes_;
0633     bool canCustomize = false;
0634     if (sizes.isEmpty()) {
0635         static const int c[] = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 22, 24, 26, 28, 32, 48, 64, 72, 80, 96, 128, 0};
0636         for (int i = 0; c[i]; ++i) {
0637             sizes.append(c[i]);
0638         }
0639         // Since sizes were not supplied, this is a vector font,
0640         // and size slot customization is allowed.
0641         canCustomize = true;
0642     }
0643 
0644     // Insert sizes into the listbox.
0645     m_ui->sizeListWidget->clear();
0646     std::sort(sizes.begin(), sizes.end());
0647     for (qreal size : std::as_const(sizes)) {
0648         m_ui->sizeListWidget->addItem(formatFontSize(size));
0649     }
0650 
0651     // Return the nearest to selected size.
0652     // If the font is vector, the nearest size is always same as selected,
0653     // thus size slot customization is allowed.
0654     // If the font is bitmap, the nearest size need not be same as selected,
0655     // thus size slot customization is not allowed.
0656     m_customSizeRow = -1;
0657     int row = nearestSizeRow(m_selectedSize, canCustomize);
0658     return QLocale::system().toDouble(m_ui->sizeListWidget->item(row)->text());
0659 }
0660 
0661 qreal KFontChooserPrivate::setupSizeListBox(const QString &family, const QString &style)
0662 {
0663     QList<qreal> sizes;
0664     const bool smoothlyScalable = QFontDatabase::isSmoothlyScalable(family, style);
0665     if (!smoothlyScalable) {
0666         const QList<int> smoothSizes = QFontDatabase::smoothSizes(family, style);
0667         for (int size : smoothSizes) {
0668             sizes.append(size);
0669         }
0670     }
0671 
0672     // Fill the listbox (uses default list of sizes if the given is empty).
0673     // Collect the best fitting size to selected size, to use if not smooth.
0674     qreal bestFitSize = fillSizeList(sizes);
0675 
0676     // Set the best fit size as current in the listbox if available.
0677     const QList<QListWidgetItem *> selectedSizeList = m_ui->sizeListWidget->findItems(formatFontSize(bestFitSize), Qt::MatchExactly);
0678     if (!selectedSizeList.isEmpty()) {
0679         m_ui->sizeListWidget->setCurrentItem(selectedSizeList.first());
0680     }
0681 
0682     return bestFitSize;
0683 }
0684 
0685 void KFontChooserPrivate::setupDisplay()
0686 {
0687     qreal size = m_selectedFont.pointSizeF();
0688     if (size == -1) {
0689         size = QFontInfo(m_selectedFont).pointSizeF();
0690     }
0691 
0692     int numEntries;
0693     int i;
0694 
0695     // Get the styleID here before familyListWidget->setCurrentRow() is called
0696     // as it may change the font style
0697     const QString styleID = styleIdentifier(m_selectedFont);
0698 
0699     QString family = m_selectedFont.family().toLower();
0700     // Direct family match.
0701     numEntries = m_ui->familyListWidget->count();
0702     for (i = 0; i < numEntries; ++i) {
0703         if (family == m_qtFamilies[m_ui->familyListWidget->item(i)->text()].toLower()) {
0704             m_ui->familyListWidget->setCurrentRow(i);
0705             break;
0706         }
0707     }
0708 
0709     // 1st family fallback.
0710     if (i == numEntries) {
0711         const int bracketPos = family.indexOf(QLatin1Char('['));
0712         if (bracketPos != -1) {
0713             family = QStringView(family).left(bracketPos).trimmed().toString();
0714             for (i = 0; i < numEntries; ++i) {
0715                 if (family == m_qtFamilies[m_ui->familyListWidget->item(i)->text()].toLower()) {
0716                     m_ui->familyListWidget->setCurrentRow(i);
0717                     break;
0718                 }
0719             }
0720         }
0721     }
0722 
0723     // 2nd family fallback.
0724     if (i == numEntries) {
0725         QString fallback = family + QLatin1String(" [");
0726         for (i = 0; i < numEntries; ++i) {
0727             if (m_qtFamilies[m_ui->familyListWidget->item(i)->text()].toLower().startsWith(fallback)) {
0728                 m_ui->familyListWidget->setCurrentRow(i);
0729                 break;
0730             }
0731         }
0732     }
0733 
0734     // 3rd family fallback.
0735     if (i == numEntries) {
0736         for (i = 0; i < numEntries; ++i) {
0737             if (m_qtFamilies[m_ui->familyListWidget->item(i)->text()].toLower().startsWith(family)) {
0738                 m_ui->familyListWidget->setCurrentRow(i);
0739                 break;
0740             }
0741         }
0742     }
0743 
0744     // Family fallback in case nothing matched. Otherwise, diff doesn't work
0745     if (i == numEntries) {
0746         m_ui->familyListWidget->setCurrentRow(0);
0747     }
0748 
0749     // By setting the current item in the family box, the available
0750     // styles and sizes for that family have been collected.
0751     // Try now to set the current items in the style and size boxes.
0752 
0753     // Set current style in the listbox.
0754     numEntries = m_ui->styleListWidget->count();
0755     for (i = 0; i < numEntries; ++i) {
0756         if (styleID == m_styleIDs[m_ui->styleListWidget->item(i)->text()]) {
0757             m_ui->styleListWidget->setCurrentRow(i);
0758             break;
0759         }
0760     }
0761     if (i == numEntries) {
0762         // Style not found, fallback.
0763         m_ui->styleListWidget->setCurrentRow(0);
0764     }
0765 
0766     // Set current size in the listbox.
0767     // If smoothly scalable, allow customizing one of the standard size slots,
0768     // otherwise just select the nearest available size.
0769     const QString currentFamily = m_qtFamilies[m_ui->familyListWidget->currentItem()->text()];
0770     const QString currentStyle = m_qtStyles[m_ui->styleListWidget->currentItem()->text()];
0771     const bool canCustomize = QFontDatabase::isSmoothlyScalable(currentFamily, currentStyle);
0772     m_ui->sizeListWidget->setCurrentRow(nearestSizeRow(size, canCustomize));
0773 
0774     // Set current size in the spinbox.
0775     m_ui->sizeSpinBox->setValue(QLocale::system().toDouble(m_ui->sizeListWidget->currentItem()->text()));
0776 }
0777 
0778 // static
0779 QStringList KFontChooser::createFontList(uint fontListCriteria)
0780 {
0781     QStringList lstSys(QFontDatabase::families());
0782 
0783     // if we have criteria; then check fonts before adding
0784     if (fontListCriteria) {
0785         QStringList lstFonts;
0786         for (const QString &family : std::as_const(lstSys)) {
0787             if ((fontListCriteria & FixedWidthFonts) > 0 && !QFontDatabase::isFixedPitch(family)) {
0788                 continue;
0789             }
0790             if (((fontListCriteria & (SmoothScalableFonts | ScalableFonts)) == ScalableFonts) && !QFontDatabase::isBitmapScalable(family)) {
0791                 continue;
0792             }
0793             if ((fontListCriteria & SmoothScalableFonts) > 0 && !QFontDatabase::isSmoothlyScalable(family)) {
0794                 continue;
0795             }
0796             lstFonts.append(family);
0797         }
0798 
0799         if ((fontListCriteria & FixedWidthFonts) > 0) {
0800             // Fallback.. if there are no fixed fonts found, it's probably a
0801             // bug in the font server or Qt.  In this case, just use 'fixed'
0802             if (lstFonts.isEmpty()) {
0803                 lstFonts.append(QStringLiteral("fixed"));
0804             }
0805         }
0806 
0807         lstSys = lstFonts;
0808     }
0809 
0810     lstSys.sort();
0811 
0812     return lstSys;
0813 }
0814 
0815 void KFontChooser::setFontListItems(const QStringList &fontList)
0816 {
0817     d->setFamilyBoxItems(fontList);
0818 }
0819 
0820 void KFontChooserPrivate::setFamilyBoxItems(const QStringList &fonts)
0821 {
0822     m_signalsAllowed = false;
0823 
0824     m_ui->familyListWidget->clear();
0825 
0826     m_qtFamilies = translateFontNameList(!fonts.isEmpty() ? fonts : KFontChooser::createFontList(m_usingFixed ? KFontChooser::FixedWidthFonts : 0));
0827 
0828     QStringList list;
0829     list.reserve(m_qtFamilies.size());
0830 
0831     // Generic font names
0832     const QStringList genericTranslatedNames{
0833         translateFontName(QStringLiteral("Sans Serif")),
0834         translateFontName(QStringLiteral("Serif")),
0835         translateFontName(QStringLiteral("Monospace")),
0836     };
0837 
0838     // Add generic family names to the top of the list
0839     for (const QString &s : genericTranslatedNames) {
0840         auto nIt = m_qtFamilies.find(s);
0841         if (nIt != m_qtFamilies.cend()) {
0842             list.push_back(s);
0843         }
0844     }
0845 
0846     for (auto it = m_qtFamilies.cbegin(); it != m_qtFamilies.cend(); ++it) {
0847         const QString &name = it->first;
0848         if (genericTranslatedNames.contains(name)) {
0849             continue;
0850         }
0851 
0852         list.push_back(name);
0853     }
0854 
0855     m_ui->familyListWidget->addItems(list);
0856     m_ui->familyListWidget->setMinimumWidth(minimumListWidth(m_ui->familyListWidget));
0857 
0858     m_signalsAllowed = true;
0859 }
0860 
0861 void KFontChooser::setMinVisibleItems(int visibleItems)
0862 {
0863     for (auto *widget : {d->m_ui->familyListWidget, d->m_ui->styleListWidget, d->m_ui->sizeListWidget}) {
0864         widget->setMinimumHeight(minimumListHeight(widget, visibleItems));
0865     }
0866 }
0867 
0868 // Human-readable style identifiers returned by QFontDatabase::styleString()
0869 // do not always survive round trip of QFont serialization/deserialization,
0870 // causing wrong style in the style box to be highlighted when
0871 // the chooser dialog is opened. This will cause the style to be changed
0872 // when the dialog is closed and the user did not touch the style box.
0873 // Hence, construct custom style identifiers sufficient for the purpose.
0874 QString KFontChooserPrivate::styleIdentifier(const QFont &font)
0875 {
0876     const int weight = font.weight();
0877     QString styleName = font.styleName();
0878     // If the styleName property is empty and the weight is QFont::Normal, that
0879     // could mean it's a "Regular"-like style with the styleName part stripped
0880     // so that subsequent calls to setBold(true) can work properly (i.e. selecting
0881     // the "Bold" style provided by the font itself) without resorting to font
0882     // "emboldening" which looks ugly.
0883     // See also KConfigGroupGui::writeEntryGui().
0884     if (styleName.isEmpty() && weight == QFont::Normal) {
0885         const QStringList styles = QFontDatabase::styles(font.family());
0886         for (const QString &style : styles) {
0887             if (isDefaultFontStyleName(style)) {
0888                 styleName = style;
0889                 break;
0890             } else {
0891                 // nothing more we can do
0892             }
0893         }
0894     }
0895 
0896     const QChar comma(QLatin1Char(','));
0897     return QString::number(weight) + comma //
0898         + QString::number((int)font.style()) + comma //
0899         + QString::number(font.stretch()) + comma //
0900         + styleName;
0901 }
0902 
0903 #include "moc_kfontchooser.cpp"