KWidgetsAddons

kfontchooser.cpp
1/*
2 SPDX-FileCopyrightText: 1996 Bernd Johannes Wuebben <wuebben@kde.org>
3 SPDX-FileCopyrightText: 1999 Preston Brown <pbrown@kde.org>
4 SPDX-FileCopyrightText: 1999 Mario Weilguni <mweilguni@kde.org>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7*/
8
9#include "kfontchooser.h"
10#include "fonthelpers_p.h"
11#include "ui_kfontchooserwidget.h"
12
13#include "loggingcategory.h"
14
15#include <QCheckBox>
16#include <QDoubleSpinBox>
17#include <QFontDatabase>
18#include <QGroupBox>
19#include <QGuiApplication>
20#include <QLabel>
21#include <QLayout>
22#include <QListWidget>
23#include <QLocale>
24#include <QScrollBar>
25#include <QSplitter>
26#include <QTextEdit>
27#include <QTimer>
28
29#include <algorithm>
30#include <cmath>
31
32// When message extraction needs to be avoided.
33#define TR_NOX tr
34
35static int minimumListWidth(const QListWidget *list)
36{
37 QFontMetrics fm = list->fontMetrics();
38
39 const int extraSpace = fm.horizontalAdvance(QLatin1Char(' ')) * 2;
40
41 // Minimum initial size
42 int width = 40;
43 for (int i = 0, rows = list->count(); i < rows; ++i) {
44 int itemWidth = fm.horizontalAdvance(list->item(i)->text());
45 // ...and add a space on both sides for a not too tight look.
46 itemWidth += extraSpace;
47 width = std::max(width, itemWidth);
48 }
49
50 width += list->frameWidth() * 2;
51 width += list->verticalScrollBar()->sizeHint().width();
52 return width;
53}
54
55static int minimumListHeight(const QListWidget *list, int numVisibleEntry)
56{
57 int w = list->fontMetrics().lineSpacing();
58 if (w < 0) {
59 w = 10;
60 }
61
62 if (numVisibleEntry <= 0) {
63 numVisibleEntry = 4;
64 }
65
66 w = w * numVisibleEntry;
67 w += list->frameWidth() * 2;
68 w += list->horizontalScrollBar()->sizeHint().height();
69 return w;
70}
71
72static QString formatFontSize(qreal size)
73{
74 return QLocale::system().toString(size, 'f', (size == floor(size)) ? 0 : 1);
75}
76
77class KFontChooserPrivate
78{
79 Q_DECLARE_TR_FUNCTIONS(KFontChooser)
80
81public:
82 KFontChooserPrivate(KFontChooser::DisplayFlags flags, KFontChooser *qq)
83 : q(qq)
84 , m_flags(flags)
85 {
86 m_palette.setColor(QPalette::Active, QPalette::Text, Qt::black);
87 m_palette.setColor(QPalette::Active, QPalette::Base, Qt::white);
88 }
89
90 void init();
91 void setFamilyBoxItems(const QStringList &fonts = {});
92 int nearestSizeRow(qreal val, bool customize);
93 qreal fillSizeList(const QList<qreal> &sizes = QList<qreal>());
94 qreal setupSizeListBox(const QString &family, const QString &style);
95
96 void setupDisplay();
97 QString styleIdentifier(const QFont &font);
98
99 void slotFamilySelected(const QString &);
100 void slotSizeSelected(const QString &);
101 void slotStyleSelected(const QString &);
102 void displaySample(const QFont &font);
103 void slotSizeValue(double);
104 void slotFeaturesChanged(const QString &features);
105
106 KFontChooser *q;
107
108 std::unique_ptr<Ui_KFontChooserWidget> m_ui;
109
111
112 QPalette m_palette;
113
114 QFont m_selectedFont;
115
116 QString m_selectedStyle;
117 qreal m_selectedSize = -1.0;
118
119 QString m_standardSizeAtCustom;
120 int m_customSizeRow = -1;
121
122 bool m_signalsAllowed = true;
123 bool m_usingFixed = false;
124
125 // Mappings of translated to Qt originated family and style strings.
126 FontFamiliesMap m_qtFamilies;
127 std::map<QString, QString> m_qtStyles;
128 // Mapping of translated style strings to internal style identifiers.
129 std::map<QString, QString> m_styleIDs;
130
131 QTimer m_fontFeatureChangedTimer;
132};
133
135 : QWidget(parent)
136 , d(new KFontChooserPrivate(KFontChooser::DisplayFrame, this))
137{
138 d->init();
139}
140
142 : QWidget(parent)
143 , d(new KFontChooserPrivate(flags, this))
144{
145 d->init();
146}
147
149
150void KFontChooserPrivate::init()
151{
152 m_usingFixed = m_flags & KFontChooser::FixedFontsOnly;
153
154 // The main layout is divided horizontally into a top part with
155 // the font attribute widgets (family, style, size) and a bottom
156 // part with a preview of the selected font
157 QVBoxLayout *mainLayout = new QVBoxLayout(q);
158 mainLayout->setContentsMargins(0, 0, 0, 0);
159
160 QWidget *page = m_flags & KFontChooser::DisplayFrame ? new QGroupBox(KFontChooser::tr("Requested Font", "@title:group"), q) : new QWidget(q);
161 mainLayout->addWidget(page);
162
163 m_ui.reset(new Ui_KFontChooserWidget);
164 m_ui->setupUi(page);
165
166 // Increase spacing on top of the preview field and then reset the other layouts
167 // back to a standard value.
168 m_ui->sampleTextEditLayout->setSpacing(q->style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing));
169 m_ui->mainHorizontalLayout->setSpacing(q->style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing));
170 m_ui->gridLayout->setVerticalSpacing(q->style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing) * 2);
171 m_ui->gridLayout->setHorizontalSpacing(q->style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing));
172
173 // Deprecated, we'll call show() if building with deprecated code
174 m_ui->sizeIsRelativeCheckBox->hide();
175
176 const bool isDiffMode = m_flags & KFontChooser::ShowDifferences;
177
178 QObject::connect(m_ui->familyListWidget, &QListWidget::currentTextChanged, [this](const QString &family) {
179 slotFamilySelected(family);
180 });
181
182 if (isDiffMode) {
183 m_ui->familyLabel->hide();
184 m_ui->familyListWidget->setEnabled(false);
185 QObject::connect(m_ui->familyCheckBox, &QCheckBox::toggled, m_ui->familyListWidget, &QWidget::setEnabled);
186 } else {
187 m_ui->familyCheckBox->hide();
188 }
189
190 setFamilyBoxItems();
191
192 // If the calling app sets FixedFontsOnly, don't show the "show fixed only" checkbox
193 m_ui->onlyFixedCheckBox->setVisible(!m_usingFixed);
194
195 if (!m_ui->onlyFixedCheckBox->isHidden()) {
196 QObject::connect(m_ui->onlyFixedCheckBox, &QCheckBox::toggled, q, [this](const bool state) {
197 q->setFont(m_selectedFont, state);
198 });
199
200 if (isDiffMode) { // In this mode follow the state of the m_ui->familyCheckBox
201 m_ui->onlyFixedCheckBox->setEnabled(false);
202 QObject::connect(m_ui->familyCheckBox, &QCheckBox::toggled, m_ui->onlyFixedCheckBox, &QWidget::setEnabled);
203 }
204 }
205
206 // Populate usual styles, to determine minimum list width;
207 // will be replaced later with correct styles.
208 m_ui->styleListWidget->addItem(KFontChooser::tr("Normal", "@item font"));
209 m_ui->styleListWidget->addItem(KFontChooser::tr("Italic", "@item font"));
210 m_ui->styleListWidget->addItem(KFontChooser::tr("Oblique", "@item font"));
211 m_ui->styleListWidget->addItem(KFontChooser::tr("Bold", "@item font"));
212 m_ui->styleListWidget->addItem(KFontChooser::tr("Bold Condensed Oblique", "@item font"));
213 m_ui->styleListWidget->setMinimumWidth(minimumListWidth(m_ui->styleListWidget));
214
215 QObject::connect(m_ui->styleListWidget, &QListWidget::currentTextChanged, [this](const QString &style) {
216 slotStyleSelected(style);
217 });
218
219 if (isDiffMode) {
220 m_ui->styleLabel->hide();
221 m_ui->styleListWidget->setEnabled(false);
222 QObject::connect(m_ui->styleCheckBox, &QCheckBox::toggled, m_ui->styleListWidget, &QWidget::setEnabled);
223 } else {
224 m_ui->styleCheckBox->hide();
225 }
226
227 // Populate with usual sizes, to determine minimum list width;
228 // will be replaced later with correct sizes.
229 fillSizeList();
230
231 QObject::connect(m_ui->sizeSpinBox, &QDoubleSpinBox::valueChanged, [this](const double size) {
232 slotSizeValue(size);
233 });
234
235 QObject::connect(m_ui->sizeListWidget, &QListWidget::currentTextChanged, [this](const QString &size) {
236 slotSizeSelected(size);
237 });
238
239 m_fontFeatureChangedTimer.setInterval(200);
240 m_fontFeatureChangedTimer.setSingleShot(true);
241 m_fontFeatureChangedTimer.callOnTimeout([this]() {
242 slotFeaturesChanged(m_ui->fontFeaturesLineEdit->text());
243 });
244
245 QObject::connect(m_ui->fontFeaturesLineEdit, &QLineEdit::textChanged, [this](const QString &) {
246 m_fontFeatureChangedTimer.start();
247 });
248
249 if (isDiffMode) {
250 m_ui->sizeLabel->hide();
251 m_ui->sizeListWidget->setEnabled(false);
252 m_ui->sizeSpinBox->setEnabled(false);
253 QObject::connect(m_ui->sizeCheckBox, &QCheckBox::toggled, m_ui->sizeListWidget, &QWidget::setEnabled);
254 QObject::connect(m_ui->sizeCheckBox, &QCheckBox::toggled, m_ui->sizeSpinBox, &QWidget::setEnabled);
255 } else {
256 m_ui->sizeCheckBox->hide();
257 }
258
259 QFont tmpFont(q->font().family(), 64, QFont::Black);
260 m_ui->sampleTextEdit->setFont(tmpFont);
261 m_ui->sampleTextEdit->setMinimumHeight(m_ui->sampleTextEdit->fontMetrics().lineSpacing());
262 // tr: A classical test phrase, with all letters of the English alphabet.
263 // Replace it with a sample text in your language, such that it is
264 // representative of language's writing system.
265 // If you wish, you can input several lines of text separated by \n.
266 q->setSampleText(KFontChooser::tr("The Quick Brown Fox Jumps Over The Lazy Dog"));
267 m_ui->sampleTextEdit->setTextCursor(QTextCursor(m_ui->sampleTextEdit->document()));
268
269 QObject::connect(q, &KFontChooser::fontSelected, q, [this](const QFont &font) {
270 displaySample(font);
271 });
272
273 // lets initialize the display if possible
274 if (m_usingFixed) {
276 } else {
277 q->setFont(QGuiApplication::font(), false);
278 }
279
280 // Set the minimum height for the list widgets
281 q->setMinVisibleItems(4);
282
283 // Set focus to the size list as this is the most commonly changed property
284 m_ui->sizeListWidget->setFocus();
285}
286
288{
289 d->m_palette.setColor(QPalette::Active, QPalette::Text, col);
290 QPalette pal = d->m_ui->sampleTextEdit->palette();
292 d->m_ui->sampleTextEdit->setPalette(pal);
293 QTextCursor cursor = d->m_ui->sampleTextEdit->textCursor();
294 d->m_ui->sampleTextEdit->selectAll();
295 d->m_ui->sampleTextEdit->setTextColor(col);
296 d->m_ui->sampleTextEdit->setTextCursor(cursor);
297}
298
299QColor KFontChooser::color() const
300{
301 return d->m_palette.color(QPalette::Active, QPalette::Text);
302}
303
305{
306 d->m_palette.setColor(QPalette::Active, QPalette::Base, col);
307 QPalette pal = d->m_ui->sampleTextEdit->palette();
309 d->m_ui->sampleTextEdit->setPalette(pal);
310}
311
312QColor KFontChooser::backgroundColor() const
313{
314 return d->m_palette.color(QPalette::Active, QPalette::Base);
315}
316
317QString KFontChooser::sampleText() const
318{
319 return d->m_ui->sampleTextEdit->toPlainText();
320}
321
323{
324 d->m_ui->sampleTextEdit->setPlainText(text);
325}
326
328{
329 d->m_ui->sampleTextEdit->setVisible(visible);
330}
331
333{
334 return minimumSizeHint();
335}
336
337void KFontChooser::enableColumn(int column, bool state)
338{
339 if (column & FamilyList) {
340 d->m_ui->familyListWidget->setEnabled(state);
341 }
342 if (column & StyleList) {
343 d->m_ui->styleListWidget->setEnabled(state);
344 }
345 if (column & SizeList) {
346 d->m_ui->sizeListWidget->setEnabled(state);
347 d->m_ui->sizeSpinBox->setEnabled(state);
348 }
349}
350
351void KFontChooser::setFont(const QFont &aFont, bool onlyFixed)
352{
353 d->m_selectedFont = aFont;
354 d->m_selectedSize = aFont.pointSizeF();
355 if (d->m_selectedSize == -1) {
356 d->m_selectedSize = QFontInfo(aFont).pointSizeF();
357 }
358
359 if (onlyFixed != d->m_usingFixed) {
360 d->m_usingFixed = onlyFixed;
361 d->setFamilyBoxItems();
362 }
363 d->setupDisplay();
364}
365
367{
368 FontDiffFlags diffFlags = NoFontDiffFlags;
369
370 if (d->m_ui->familyCheckBox->isChecked()) {
371 diffFlags |= FontDiffFamily;
372 }
373
374 if (d->m_ui->styleCheckBox->isChecked()) {
375 diffFlags |= FontDiffStyle;
376 }
377
378 if (d->m_ui->sizeCheckBox->isChecked()) {
379 diffFlags |= FontDiffSize;
380 }
381
382 return diffFlags;
383}
384
385QFont KFontChooser::font() const
386{
387 return d->m_selectedFont;
388}
389
390static bool isDefaultFontStyleName(const QString &style)
391{
392 /* clang-format off */
393 // Ordered by commonness, i.e. "Regular" is the most common
394 return style == QLatin1String("Regular")
395 || style == QLatin1String("Normal")
396 || style == QLatin1String("Book")
397 || style == QLatin1String("Roman");
398 /* clang-format on */
399}
400
401void KFontChooserPrivate::slotFamilySelected(const QString &family)
402{
403 if (!m_signalsAllowed) {
404 return;
405 }
406 m_signalsAllowed = false;
407
408 QString currentFamily;
409 if (family.isEmpty()) {
410 Q_ASSERT(m_ui->familyListWidget->currentItem());
411 if (m_ui->familyListWidget->currentItem()) {
412 currentFamily = m_qtFamilies[m_ui->familyListWidget->currentItem()->text()];
413 }
414 } else {
415 currentFamily = m_qtFamilies[family];
416 }
417
418 // Get the list of styles available in this family.
419 QStringList styles = QFontDatabase::styles(currentFamily);
420 if (styles.isEmpty()) {
421 // Avoid extraction, it is in kdeqt.po
422 styles.append(TR_NOX("Normal", "QFontDatabase"));
423 }
424
425 // Always prepend Regular, Normal, Book or Roman, this way if "m_selectedStyle"
426 // in the code below is empty, selecting index 0 should work better
427 std::sort(styles.begin(), styles.end(), [](const QString &a, const QString &b) {
428 if (isDefaultFontStyleName(a)) {
429 return true;
430 } else if (isDefaultFontStyleName(b)) {
431 return false;
432 }
433 return false;
434 });
435
436 // Filter style strings and add to the listbox.
437 QString pureFamily;
438 splitFontString(family, &pureFamily);
439 QStringList filteredStyles;
440 m_qtStyles.clear();
441 m_styleIDs.clear();
442
443 const QStringList origStyles = styles;
444 for (const QString &style : origStyles) {
445 // Sometimes the font database will report an invalid style,
446 // that falls back back to another when set.
447 // Remove such styles, by checking set/get round-trip.
448 QFont testFont = QFontDatabase::font(currentFamily, style, 10);
449 if (QFontDatabase::styleString(testFont) != style) {
450 styles.removeAll(style);
451 continue;
452 }
453
454 QString fstyle = tr("%1", "@item Font style").arg(style);
455 if (!filteredStyles.contains(fstyle)) {
456 filteredStyles.append(fstyle);
457 m_qtStyles.insert({fstyle, style});
458 m_styleIDs.insert({fstyle, styleIdentifier(testFont)});
459 }
460 }
461 m_ui->styleListWidget->clear();
462 m_ui->styleListWidget->addItems(filteredStyles);
463
464 // Try to set the current style in the listbox to that previous.
465 int listPos = filteredStyles.indexOf(m_selectedStyle.isEmpty() ? TR_NOX("Normal", "QFontDatabase") : m_selectedStyle);
466 if (listPos < 0) {
467 // Make extra effort to have Italic selected when Oblique was chosen,
468 // and vice versa, as that is what the user would probably want.
469 QString styleIt = tr("Italic", "@item font");
470 QString styleOb = tr("Oblique", "@item font");
471 for (int i = 0; i < 2; ++i) {
472 int pos = m_selectedStyle.indexOf(styleIt);
473 if (pos >= 0) {
474 QString style = m_selectedStyle;
475 style.replace(pos, styleIt.length(), styleOb);
476 listPos = filteredStyles.indexOf(style);
477 if (listPos >= 0) {
478 break;
479 }
480 }
481 qSwap(styleIt, styleOb);
482 }
483 }
484 m_ui->styleListWidget->setCurrentRow(listPos >= 0 ? listPos : 0);
485 const QString currentStyle = m_qtStyles[m_ui->styleListWidget->currentItem()->text()];
486
487 // Recompute the size listbox for this family/style.
488 qreal currentSize = setupSizeListBox(currentFamily, currentStyle);
489 m_ui->sizeSpinBox->setValue(currentSize);
490
491 m_selectedFont = QFontDatabase::font(currentFamily, currentStyle, static_cast<int>(currentSize));
492 if (QFontDatabase::isSmoothlyScalable(currentFamily, currentStyle) && m_selectedFont.pointSize() == floor(currentSize)) {
493 m_selectedFont.setPointSizeF(currentSize);
494 }
495 Q_EMIT q->fontSelected(m_selectedFont);
496
497 m_signalsAllowed = true;
498}
499
500void KFontChooserPrivate::slotStyleSelected(const QString &style)
501{
502 if (!m_signalsAllowed) {
503 return;
504 }
505 m_signalsAllowed = false;
506
507 const QString currentFamily = m_qtFamilies[m_ui->familyListWidget->currentItem()->text()];
508 const QString currentStyle = !style.isEmpty() ? m_qtStyles[style] : m_qtStyles[m_ui->styleListWidget->currentItem()->text()];
509
510 // Recompute the size listbox for this family/style.
511 qreal currentSize = setupSizeListBox(currentFamily, currentStyle);
512 m_ui->sizeSpinBox->setValue(currentSize);
513
514 m_selectedFont = QFontDatabase::font(currentFamily, currentStyle, static_cast<int>(currentSize));
515 if (QFontDatabase::isSmoothlyScalable(currentFamily, currentStyle) && m_selectedFont.pointSize() == floor(currentSize)) {
516 m_selectedFont.setPointSizeF(currentSize);
517 }
518 Q_EMIT q->fontSelected(m_selectedFont);
519
520 if (!style.isEmpty()) {
521 m_selectedStyle = currentStyle;
522 }
523
524 m_signalsAllowed = true;
525}
526
527void KFontChooserPrivate::slotSizeSelected(const QString &size)
528{
529 if (!m_signalsAllowed) {
530 return;
531 }
532
533 m_signalsAllowed = false;
534
535 qreal currentSize = 0.0;
536 if (size.isEmpty()) {
537 currentSize = QLocale::system().toDouble(m_ui->sizeListWidget->currentItem()->text());
538 } else {
539 currentSize = QLocale::system().toDouble(size);
540 }
541
542 // Reset the customized size slot in the list if not needed.
543 if (m_customSizeRow >= 0 && m_selectedFont.pointSizeF() != currentSize) {
544 m_ui->sizeListWidget->item(m_customSizeRow)->setText(m_standardSizeAtCustom);
545 m_customSizeRow = -1;
546 }
547
548 m_ui->sizeSpinBox->setValue(currentSize);
549 m_selectedFont.setPointSizeF(currentSize);
550 Q_EMIT q->fontSelected(m_selectedFont);
551
552 if (!size.isEmpty()) {
553 m_selectedSize = currentSize;
554 }
555
556 m_signalsAllowed = true;
557}
558
559void KFontChooserPrivate::slotSizeValue(double dval)
560{
561 if (!m_signalsAllowed) {
562 return;
563 }
564 m_signalsAllowed = false;
565
566 // We compare with qreal, so convert for platforms where qreal != double.
567 qreal val = qreal(dval);
568
569 // Reset current size slot in list if it was customized.
570 if (m_customSizeRow >= 0 && m_ui->sizeListWidget->currentRow() == m_customSizeRow) {
571 m_ui->sizeListWidget->item(m_customSizeRow)->setText(m_standardSizeAtCustom);
572 m_customSizeRow = -1;
573 }
574
575 bool canCustomize = true;
576
577 const QString family = m_qtFamilies[m_ui->familyListWidget->currentItem()->text()];
578 const QString style = m_qtStyles[m_ui->styleListWidget->currentItem()->text()];
579
580 // For Qt-bad-sizes workaround: skip this block unconditionally
581 if (!QFontDatabase::isSmoothlyScalable(family, style)) {
582 // Bitmap font, allow only discrete sizes.
583 // Determine the nearest in the direction of change.
584 canCustomize = false;
585 int nrows = m_ui->sizeListWidget->count();
586 int row = m_ui->sizeListWidget->currentRow();
587 int nrow;
588 if (val - m_selectedFont.pointSizeF() > 0) {
589 for (nrow = row + 1; nrow < nrows; ++nrow) {
590 if (QLocale::system().toDouble(m_ui->sizeListWidget->item(nrow)->text()) >= val) {
591 break;
592 }
593 }
594 } else {
595 for (nrow = row - 1; nrow >= 0; --nrow) {
596 if (QLocale::system().toDouble(m_ui->sizeListWidget->item(nrow)->text()) <= val) {
597 break;
598 }
599 }
600 }
601 // Make sure the new row is not out of bounds.
602 nrow = nrow < 0 ? 0 : nrow >= nrows ? nrows - 1 : nrow;
603 // Get the size from the new row and set the spinbox to that size.
604 val = QLocale::system().toDouble(m_ui->sizeListWidget->item(nrow)->text());
605 m_ui->sizeSpinBox->setValue(val);
606 }
607
608 // Set the current size in the size listbox.
609 int row = nearestSizeRow(val, canCustomize);
610 m_ui->sizeListWidget->setCurrentRow(row);
611
612 m_selectedSize = val;
613 m_selectedFont.setPointSizeF(val);
614 Q_EMIT q->fontSelected(m_selectedFont);
615
616 m_signalsAllowed = true;
617}
618
619void KFontChooserPrivate::slotFeaturesChanged(const QString &features)
620{
621 m_selectedFont.clearFeatures();
622
623 if (features.isEmpty()) {
624 return;
625 }
626
627 const QStringList rawFeaturesList = features.split(QLatin1Char(','), Qt::SkipEmptyParts);
628 for (const QString &feature : rawFeaturesList) {
629 auto f = QStringView(feature).trimmed();
630 if (f.isEmpty()) {
631 continue;
632 }
633 QList<QStringView> parts = f.split(QStringLiteral("="), Qt::SkipEmptyParts);
634 if (parts.length() == 2) {
635 const auto tag = QFont::Tag::fromString(parts[0]);
636 bool ok = false;
637 const int number = parts[1].toInt(&ok);
638 if (tag.has_value() && ok) {
639 m_selectedFont.setFeature(tag.value(), number);
640 }
641 } else if (f.size() <= 4) {
642 const auto tag = QFont::Tag::fromString(feature);
643 if (tag.has_value()) {
644 m_selectedFont.setFeature(tag.value(), 1);
645 }
646 }
647 }
648
649 Q_EMIT q->fontSelected(m_selectedFont);
650}
651
652void KFontChooserPrivate::displaySample(const QFont &font)
653{
654 m_ui->sampleTextEdit->setFont(font);
655}
656
657int KFontChooserPrivate::nearestSizeRow(qreal val, bool customize)
658{
659 qreal diff = 1000;
660 int row = 0;
661 for (int r = 0; r < m_ui->sizeListWidget->count(); ++r) {
662 qreal cval = QLocale::system().toDouble(m_ui->sizeListWidget->item(r)->text());
663 if (qAbs(cval - val) < diff) {
664 diff = qAbs(cval - val);
665 row = r;
666 }
667 }
668 // For Qt-bad-sizes workaround: ignore value of customize, use true
669 if (customize && diff > 0) {
670 m_customSizeRow = row;
671 m_standardSizeAtCustom = m_ui->sizeListWidget->item(row)->text();
672 m_ui->sizeListWidget->item(row)->setText(formatFontSize(val));
673 }
674 return row;
675}
676
677qreal KFontChooserPrivate::fillSizeList(const QList<qreal> &sizes_)
678{
679 if (m_ui->sizeListWidget->isHidden()) {
680 qCWarning(KWidgetsAddonsLog) << "Trying to fill the font size listwidget, but the widget is hidden.";
681 return 0.0;
682 }
683
684 QList<qreal> sizes = sizes_;
685 bool canCustomize = false;
686 if (sizes.isEmpty()) {
687 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};
688 for (int i = 0; c[i]; ++i) {
689 sizes.append(c[i]);
690 }
691 // Since sizes were not supplied, this is a vector font,
692 // and size slot customization is allowed.
693 canCustomize = true;
694 }
695
696 // Insert sizes into the listbox.
697 m_ui->sizeListWidget->clear();
698 std::sort(sizes.begin(), sizes.end());
699 for (qreal size : std::as_const(sizes)) {
700 m_ui->sizeListWidget->addItem(formatFontSize(size));
701 }
702
703 // Return the nearest to selected size.
704 // If the font is vector, the nearest size is always same as selected,
705 // thus size slot customization is allowed.
706 // If the font is bitmap, the nearest size need not be same as selected,
707 // thus size slot customization is not allowed.
708 m_customSizeRow = -1;
709 int row = nearestSizeRow(m_selectedSize, canCustomize);
710 return QLocale::system().toDouble(m_ui->sizeListWidget->item(row)->text());
711}
712
713qreal KFontChooserPrivate::setupSizeListBox(const QString &family, const QString &style)
714{
715 QList<qreal> sizes;
716 const bool smoothlyScalable = QFontDatabase::isSmoothlyScalable(family, style);
717 if (!smoothlyScalable) {
718 const QList<int> smoothSizes = QFontDatabase::smoothSizes(family, style);
719 for (int size : smoothSizes) {
720 sizes.append(size);
721 }
722 }
723
724 // Fill the listbox (uses default list of sizes if the given is empty).
725 // Collect the best fitting size to selected size, to use if not smooth.
726 qreal bestFitSize = fillSizeList(sizes);
727
728 // Set the best fit size as current in the listbox if available.
729 const QList<QListWidgetItem *> selectedSizeList = m_ui->sizeListWidget->findItems(formatFontSize(bestFitSize), Qt::MatchExactly);
730 if (!selectedSizeList.isEmpty()) {
731 m_ui->sizeListWidget->setCurrentItem(selectedSizeList.first());
732 }
733
734 return bestFitSize;
735}
736
737void KFontChooserPrivate::setupDisplay()
738{
739 qreal size = m_selectedFont.pointSizeF();
740 if (size == -1) {
741 size = QFontInfo(m_selectedFont).pointSizeF();
742 }
743
744 // Set font features
745 const auto tags = m_selectedFont.featureTags();
746 QStringList features;
747 for (const auto &tag : tags) {
748 const QString name = QString::fromUtf8(tag.toString());
749 const quint32 value = m_selectedFont.featureValue(tag);
750 if (value == 1) {
751 features.push_back(name);
752 } else {
753 features.push_back(QStringLiteral("%1=%2").arg(name, QString::number(value)));
754 }
755 }
756 m_ui->fontFeaturesLineEdit->setText(features.join(QStringLiteral(",")));
757
758 int numEntries;
759 int i;
760
761 // Get the styleID here before familyListWidget->setCurrentRow() is called
762 // as it may change the font style
763 const QString styleID = styleIdentifier(m_selectedFont);
764
765 QString family = m_selectedFont.family().toLower();
766 // Direct family match.
767 numEntries = m_ui->familyListWidget->count();
768 for (i = 0; i < numEntries; ++i) {
769 if (family == m_qtFamilies[m_ui->familyListWidget->item(i)->text()].toLower()) {
770 m_ui->familyListWidget->setCurrentRow(i);
771 break;
772 }
773 }
774
775 // 1st family fallback.
776 if (i == numEntries) {
777 const int bracketPos = family.indexOf(QLatin1Char('['));
778 if (bracketPos != -1) {
779 family = QStringView(family).left(bracketPos).trimmed().toString();
780 for (i = 0; i < numEntries; ++i) {
781 if (family == m_qtFamilies[m_ui->familyListWidget->item(i)->text()].toLower()) {
782 m_ui->familyListWidget->setCurrentRow(i);
783 break;
784 }
785 }
786 }
787 }
788
789 // 2nd family fallback.
790 if (i == numEntries) {
791 QString fallback = family + QLatin1String(" [");
792 for (i = 0; i < numEntries; ++i) {
793 if (m_qtFamilies[m_ui->familyListWidget->item(i)->text()].toLower().startsWith(fallback)) {
794 m_ui->familyListWidget->setCurrentRow(i);
795 break;
796 }
797 }
798 }
799
800 // 3rd family fallback.
801 if (i == numEntries) {
802 for (i = 0; i < numEntries; ++i) {
803 if (m_qtFamilies[m_ui->familyListWidget->item(i)->text()].toLower().startsWith(family)) {
804 m_ui->familyListWidget->setCurrentRow(i);
805 break;
806 }
807 }
808 }
809
810 // Family fallback in case nothing matched. Otherwise, diff doesn't work
811 if (i == numEntries) {
812 m_ui->familyListWidget->setCurrentRow(0);
813 }
814
815 // By setting the current item in the family box, the available
816 // styles and sizes for that family have been collected.
817 // Try now to set the current items in the style and size boxes.
818
819 // Set current style in the listbox.
820 numEntries = m_ui->styleListWidget->count();
821 for (i = 0; i < numEntries; ++i) {
822 if (styleID == m_styleIDs[m_ui->styleListWidget->item(i)->text()]) {
823 m_ui->styleListWidget->setCurrentRow(i);
824 break;
825 }
826 }
827 if (i == numEntries) {
828 // Style not found, fallback.
829 m_ui->styleListWidget->setCurrentRow(0);
830 }
831
832 // Set current size in the listbox.
833 // If smoothly scalable, allow customizing one of the standard size slots,
834 // otherwise just select the nearest available size.
835 const QString currentFamily = m_qtFamilies[m_ui->familyListWidget->currentItem()->text()];
836 const QString currentStyle = m_qtStyles[m_ui->styleListWidget->currentItem()->text()];
837 const bool canCustomize = QFontDatabase::isSmoothlyScalable(currentFamily, currentStyle);
838 m_ui->sizeListWidget->setCurrentRow(nearestSizeRow(size, canCustomize));
839
840 // Set current size in the spinbox.
841 m_ui->sizeSpinBox->setValue(QLocale::system().toDouble(m_ui->sizeListWidget->currentItem()->text()));
842}
843
844// static
846{
848
849 // if we have criteria; then check fonts before adding
850 if (fontListCriteria) {
851 QStringList lstFonts;
852 for (const QString &family : std::as_const(lstSys)) {
853 if ((fontListCriteria & FixedWidthFonts) > 0 && !QFontDatabase::isFixedPitch(family)) {
854 continue;
855 }
856 if (((fontListCriteria & (SmoothScalableFonts | ScalableFonts)) == ScalableFonts) && !QFontDatabase::isBitmapScalable(family)) {
857 continue;
858 }
859 if ((fontListCriteria & SmoothScalableFonts) > 0 && !QFontDatabase::isSmoothlyScalable(family)) {
860 continue;
861 }
862 lstFonts.append(family);
863 }
864
865 if ((fontListCriteria & FixedWidthFonts) > 0) {
866 // Fallback.. if there are no fixed fonts found, it's probably a
867 // bug in the font server or Qt. In this case, just use 'fixed'
868 if (lstFonts.isEmpty()) {
869 lstFonts.append(QStringLiteral("fixed"));
870 }
871 }
872
873 lstSys = lstFonts;
874 }
875
876 lstSys.sort();
877
878 return lstSys;
879}
880
882{
883 d->setFamilyBoxItems(fontList);
884}
885
886void KFontChooserPrivate::setFamilyBoxItems(const QStringList &fonts)
887{
888 m_signalsAllowed = false;
889
890 m_ui->familyListWidget->clear();
891
892 m_qtFamilies = translateFontNameList(!fonts.isEmpty() ? fonts : KFontChooser::createFontList(m_usingFixed ? KFontChooser::FixedWidthFonts : 0));
893
894 QStringList list;
895 list.reserve(m_qtFamilies.size());
896
897 // Generic font names
898 const QStringList genericTranslatedNames{
899 translateFontName(QStringLiteral("Sans Serif")),
900 translateFontName(QStringLiteral("Serif")),
901 translateFontName(QStringLiteral("Monospace")),
902 };
903
904 // Add generic family names to the top of the list
905 for (const QString &s : genericTranslatedNames) {
906 auto nIt = m_qtFamilies.find(s);
907 if (nIt != m_qtFamilies.cend()) {
908 list.push_back(s);
909 }
910 }
911
912 for (auto it = m_qtFamilies.cbegin(); it != m_qtFamilies.cend(); ++it) {
913 const QString &name = it->first;
914 if (genericTranslatedNames.contains(name)) {
915 continue;
916 }
917
918 list.push_back(name);
919 }
920
921 m_ui->familyListWidget->addItems(list);
922 m_ui->familyListWidget->setMinimumWidth(minimumListWidth(m_ui->familyListWidget));
923
924 m_signalsAllowed = true;
925}
926
928{
929 for (auto *widget : {d->m_ui->familyListWidget, d->m_ui->styleListWidget, d->m_ui->sizeListWidget}) {
930 widget->setMinimumHeight(minimumListHeight(widget, visibleItems));
931 }
932}
933
934// Human-readable style identifiers returned by QFontDatabase::styleString()
935// do not always survive round trip of QFont serialization/deserialization,
936// causing wrong style in the style box to be highlighted when
937// the chooser dialog is opened. This will cause the style to be changed
938// when the dialog is closed and the user did not touch the style box.
939// Hence, construct custom style identifiers sufficient for the purpose.
940QString KFontChooserPrivate::styleIdentifier(const QFont &font)
941{
942 const int weight = font.weight();
943 QString styleName = font.styleName();
944 // If the styleName property is empty and the weight is QFont::Normal, that
945 // could mean it's a "Regular"-like style with the styleName part stripped
946 // so that subsequent calls to setBold(true) can work properly (i.e. selecting
947 // the "Bold" style provided by the font itself) without resorting to font
948 // "emboldening" which looks ugly.
949 // See also KConfigGroupGui::writeEntryGui().
950 if (styleName.isEmpty() && weight == QFont::Normal) {
951 const QStringList styles = QFontDatabase::styles(font.family());
952 for (const QString &style : styles) {
953 if (isDefaultFontStyleName(style)) {
954 styleName = style;
955 break;
956 } else {
957 // nothing more we can do
958 }
959 }
960 }
961
962 const QChar comma(QLatin1Char(','));
963 return QString::number(weight) + comma //
964 + QString::number((int)font.style()) + comma //
965 + QString::number(font.stretch()) + comma //
966 + styleName;
967}
968
969#include "moc_kfontchooser.cpp"
@ FamilyList
Identifies the family (leftmost) list.
@ SizeList
Identifies the size (rightmost) list.
@ StyleList
Identifies the style (center) list.
QFlags< DisplayFlag > DisplayFlags
Stores a combination of DisplayFlag values.
void setColor(const QColor &col)
Sets the color to use for the font in the preview area.
void setBackgroundColor(const QColor &col)
Sets the background color to use in the preview area.
void setSampleBoxVisible(bool visible)
If visible is true the preview area will be shown, and vice-versa is it's false.
~KFontChooser() override
Destructor.
QSize sizeHint(void) const override
Reimplemented for internal reasons.
@ FixedWidthFonts
If set, only show fixed fixed-width (monospace) fonts.
@ ScalableFonts
If set, only show scalable fonts.
@ SmoothScalableFonts
If set, only show smooth scalable fonts.
@ DisplayFrame
Show a visual frame around the chooser.
@ ShowDifferences
Display the font differences interfaces.
@ FixedFontsOnly
Only show monospaced/fixed-width fonts, excluding proportional fonts, (the checkbox to toggle showing...
@ NoDisplayFlags
No flags set.
void fontSelected(const QFont &font)
Emitted when the selected font changes.
void setSampleText(const QString &text)
Sets the sample text in the preview area; this is useful if you want to use text in your native langu...
FontDiffFlags fontDiffFlags() const
Returns the bitmask corresponding to the attributes the user wishes to change.
void setMinVisibleItems(int visibleItems)
Sets the minimum number of items that should be visible in the child list widgets; this number will b...
KFontChooser(QWidget *parent=nullptr)
Constructs a font picker widget.
void setFont(const QFont &font, bool onlyFixed=false)
Sets the currently selected font in the widget.
void enableColumn(int column, bool state)
Enables or disables a column (family, style, size) in the widget.
@ NoFontDiffFlags
No flags set.
@ FontDiffFamily
Identifies a requested change in the font family.
@ FontDiffStyle
Identifies a requested change in the font style.
@ FontDiffSize
Identifies a requested change in the font size.
void setFontListItems(const QStringList &fontList)
Uses fontList to fill the font family list in the widget.
QFlags< FontDiff > FontDiffFlags
Stores an combination of FontDiff values.
static QStringList createFontList(uint fontListCriteria)
Returns a list of font faimly name strings filtered based on fontListCriteria.
KIOCORE_EXPORT QString number(KIO::filesize_t size)
std::vector< Feature > features(QStringView coachNumber, QStringView coachClassification)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
QString name(StandardAction id)
KGuiItem ok()
Returns the 'Ok' gui item.
void toggled(bool checked)
void addWidget(QWidget *widget, int stretch, Qt::Alignment alignment)
void valueChanged(double d)
QString family() const const
qreal pointSizeF() const const
int stretch() const const
Style style() const const
QString styleName() const const
Weight weight() const const
QStringList families(WritingSystem writingSystem)
QFont font(const QString &family, const QString &style, int pointSize)
bool isBitmapScalable(const QString &family, const QString &style)
bool isFixedPitch(const QString &family, const QString &style)
bool isSmoothlyScalable(const QString &family, const QString &style)
QList< int > smoothSizes(const QString &family, const QString &styleName)
QString styleString(const QFont &font)
QStringList styles(const QString &family)
QFont systemFont(SystemFont type)
qreal pointSizeF() const const
int horizontalAdvance(QChar ch) const const
void setContentsMargins(const QMargins &margins)
void textChanged(const QString &text)
void append(QList< T > &&value)
iterator begin()
void clear()
qsizetype count() const const
iterator end()
T & first()
bool isEmpty() const const
qsizetype length() const const
void push_back(parameter_type value)
qsizetype removeAll(const AT &t)
void reserve(qsizetype size)
void currentTextChanged(const QString &currentText)
QLocale system()
double toDouble(QStringView s, bool *ok) const const
QString toString(QDate date, FormatType format) const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QObject * parent() const const
QString tr(const char *sourceText, const char *disambiguation, int n)
void setColor(ColorGroup group, ColorRole role, const QColor &color)
QString arg(Args &&... args) const const
QString first(qsizetype n) const const
QString fromUtf8(QByteArrayView str)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
QString left(qsizetype n) const const
qsizetype length() const const
QString number(double n, char format, int precision)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
QString toLower() const const
QString trimmed() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
qsizetype indexOf(const QRegularExpression &re, qsizetype from) const const
void sort(Qt::CaseSensitivity cs)
PM_LayoutVerticalSpacing
virtual int pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const const=0
MatchExactly
SkipEmptyParts
QWidget(QWidget *parent, Qt::WindowFlags f)
void setEnabled(bool)
QStyle * style() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Apr 25 2025 11:50:34 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.