File indexing completed on 2025-02-16 13:11:42
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"