File indexing completed on 2024-04-28 15:32:03

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