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"