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 {
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#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0)
167 m_ui->fontFeaturesLabel->setVisible(false);
168 m_ui->fontFeaturesLineEdit->setVisible(false);
169#endif
170
171 // Increase spacing on top of the preview field and then reset the other layouts
172 // back to a standard value.
173 m_ui->sampleTextEditLayout->setSpacing(q->style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing));
174 m_ui->mainHorizontalLayout->setSpacing(q->style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing));
175 m_ui->gridLayout->setVerticalSpacing(q->style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing) * 2);
176 m_ui->gridLayout->setHorizontalSpacing(q->style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing));
177
178 // Deprecated, we'll call show() if building with deprecated code
179 m_ui->sizeIsRelativeCheckBox->hide();
180
181 const bool isDiffMode = m_flags & KFontChooser::ShowDifferences;
182
183 QObject::connect(m_ui->familyListWidget, &QListWidget::currentTextChanged, [this](const QString &family) {
184 slotFamilySelected(family);
185 });
186
187 if (isDiffMode) {
188 m_ui->familyLabel->hide();
189 m_ui->familyListWidget->setEnabled(false);
190 QObject::connect(m_ui->familyCheckBox, &QCheckBox::toggled, m_ui->familyListWidget, &QWidget::setEnabled);
191 } else {
192 m_ui->familyCheckBox->hide();
193 }
194
195 setFamilyBoxItems();
196
197 // If the calling app sets FixedFontsOnly, don't show the "show fixed only" checkbox
198 m_ui->onlyFixedCheckBox->setVisible(!m_usingFixed);
199
200 if (!m_ui->onlyFixedCheckBox->isHidden()) {
201 QObject::connect(m_ui->onlyFixedCheckBox, &QCheckBox::toggled, q, [this](const bool state) {
202 q->setFont(m_selectedFont, state);
203 });
204
205 if (isDiffMode) { // In this mode follow the state of the m_ui->familyCheckBox
206 m_ui->onlyFixedCheckBox->setEnabled(false);
207 QObject::connect(m_ui->familyCheckBox, &QCheckBox::toggled, m_ui->onlyFixedCheckBox, &QWidget::setEnabled);
208 }
209 }
210
211 // Populate usual styles, to determine minimum list width;
212 // will be replaced later with correct styles.
213 m_ui->styleListWidget->addItem(KFontChooser::tr("Normal", "@item font"));
214 m_ui->styleListWidget->addItem(KFontChooser::tr("Italic", "@item font"));
215 m_ui->styleListWidget->addItem(KFontChooser::tr("Oblique", "@item font"));
216 m_ui->styleListWidget->addItem(KFontChooser::tr("Bold", "@item font"));
217 m_ui->styleListWidget->addItem(KFontChooser::tr("Bold Condensed Oblique", "@item font"));
218 m_ui->styleListWidget->setMinimumWidth(minimumListWidth(m_ui->styleListWidget));
219
220 QObject::connect(m_ui->styleListWidget, &QListWidget::currentTextChanged, [this](const QString &style) {
221 slotStyleSelected(style);
222 });
223
224 if (isDiffMode) {
225 m_ui->styleLabel->hide();
226 m_ui->styleListWidget->setEnabled(false);
227 QObject::connect(m_ui->styleCheckBox, &QCheckBox::toggled, m_ui->styleListWidget, &QWidget::setEnabled);
228 } else {
229 m_ui->styleCheckBox->hide();
230 }
231
232 // Populate with usual sizes, to determine minimum list width;
233 // will be replaced later with correct sizes.
234 fillSizeList();
235
236 QObject::connect(m_ui->sizeSpinBox, &QDoubleSpinBox::valueChanged, [this](const double size) {
237 slotSizeValue(size);
238 });
239
240 QObject::connect(m_ui->sizeListWidget, &QListWidget::currentTextChanged, [this](const QString &size) {
241 slotSizeSelected(size);
242 });
243
244 m_fontFeatureChangedTimer.setInterval(200);
245 m_fontFeatureChangedTimer.setSingleShot(true);
246 m_fontFeatureChangedTimer.callOnTimeout([this]() {
247 slotFeaturesChanged(m_ui->fontFeaturesLineEdit->text());
248 });
249
250 QObject::connect(m_ui->fontFeaturesLineEdit, &QLineEdit::textChanged, [this](const QString &) {
251 m_fontFeatureChangedTimer.start();
252 });
253
254 if (isDiffMode) {
255 m_ui->sizeLabel->hide();
256 m_ui->sizeListWidget->setEnabled(false);
257 m_ui->sizeSpinBox->setEnabled(false);
258 QObject::connect(m_ui->sizeCheckBox, &QCheckBox::toggled, m_ui->sizeListWidget, &QWidget::setEnabled);
259 QObject::connect(m_ui->sizeCheckBox, &QCheckBox::toggled, m_ui->sizeSpinBox, &QWidget::setEnabled);
260 } else {
261 m_ui->sizeCheckBox->hide();
262 }
263
264 QFont tmpFont(q->font().family(), 64, QFont::Black);
265 m_ui->sampleTextEdit->setFont(tmpFont);
266 m_ui->sampleTextEdit->setMinimumHeight(m_ui->sampleTextEdit->fontMetrics().lineSpacing());
267 // tr: A classical test phrase, with all letters of the English alphabet.
268 // Replace it with a sample text in your language, such that it is
269 // representative of language's writing system.
270 // If you wish, you can input several lines of text separated by \n.
271 q->setSampleText(KFontChooser::tr("The Quick Brown Fox Jumps Over The Lazy Dog"));
272 m_ui->sampleTextEdit->setTextCursor(QTextCursor(m_ui->sampleTextEdit->document()));
273
274 QObject::connect(q, &KFontChooser::fontSelected, q, [this](const QFont &font) {
275 displaySample(font);
276 });
277
278 // lets initialize the display if possible
279 if (m_usingFixed) {
281 } else {
282 q->setFont(QGuiApplication::font(), false);
283 }
284
285 // Set the minimum height for the list widgets
286 q->setMinVisibleItems(4);
287
288 // Set focus to the size list as this is the most commonly changed property
289 m_ui->sizeListWidget->setFocus();
290}
291
293{
294 d->m_palette.setColor(QPalette::Active, QPalette::Text, col);
295 QPalette pal = d->m_ui->sampleTextEdit->palette();
297 d->m_ui->sampleTextEdit->setPalette(pal);
298 QTextCursor cursor = d->m_ui->sampleTextEdit->textCursor();
299 d->m_ui->sampleTextEdit->selectAll();
300 d->m_ui->sampleTextEdit->setTextColor(col);
301 d->m_ui->sampleTextEdit->setTextCursor(cursor);
302}
303
304QColor KFontChooser::color() const
305{
306 return d->m_palette.color(QPalette::Active, QPalette::Text);
307}
308
310{
311 d->m_palette.setColor(QPalette::Active, QPalette::Base, col);
312 QPalette pal = d->m_ui->sampleTextEdit->palette();
314 d->m_ui->sampleTextEdit->setPalette(pal);
315}
316
317QColor KFontChooser::backgroundColor() const
318{
319 return d->m_palette.color(QPalette::Active, QPalette::Base);
320}
321
322QString KFontChooser::sampleText() const
323{
324 return d->m_ui->sampleTextEdit->toPlainText();
325}
326
328{
329 d->m_ui->sampleTextEdit->setPlainText(text);
330}
331
333{
334 d->m_ui->sampleTextEdit->setVisible(visible);
335}
336
338{
339 return minimumSizeHint();
340}
341
342void KFontChooser::enableColumn(int column, bool state)
343{
344 if (column & FamilyList) {
345 d->m_ui->familyListWidget->setEnabled(state);
346 }
347 if (column & StyleList) {
348 d->m_ui->styleListWidget->setEnabled(state);
349 }
350 if (column & SizeList) {
351 d->m_ui->sizeListWidget->setEnabled(state);
352 d->m_ui->sizeSpinBox->setEnabled(state);
353 }
354}
355
356void KFontChooser::setFont(const QFont &aFont, bool onlyFixed)
357{
358 d->m_selectedFont = aFont;
359 d->m_selectedSize = aFont.pointSizeF();
360 if (d->m_selectedSize == -1) {
361 d->m_selectedSize = QFontInfo(aFont).pointSizeF();
362 }
363
364 if (onlyFixed != d->m_usingFixed) {
365 d->m_usingFixed = onlyFixed;
366 d->setFamilyBoxItems();
367 }
368 d->setupDisplay();
369}
370
372{
373 FontDiffFlags diffFlags = NoFontDiffFlags;
374
375 if (d->m_ui->familyCheckBox->isChecked()) {
376 diffFlags |= FontDiffFamily;
377 }
378
379 if (d->m_ui->styleCheckBox->isChecked()) {
380 diffFlags |= FontDiffStyle;
381 }
382
383 if (d->m_ui->sizeCheckBox->isChecked()) {
384 diffFlags |= FontDiffSize;
385 }
386
387 return diffFlags;
388}
389
390QFont KFontChooser::font() const
391{
392 return d->m_selectedFont;
393}
394
395static bool isDefaultFontStyleName(const QString &style)
396{
397 /* clang-format off */
398 // Ordered by commonness, i.e. "Regular" is the most common
399 return style == QLatin1String("Regular")
400 || style == QLatin1String("Normal")
401 || style == QLatin1String("Book")
402 || style == QLatin1String("Roman");
403 /* clang-format on */
404}
405
406void KFontChooserPrivate::slotFamilySelected(const QString &family)
407{
408 if (!m_signalsAllowed) {
409 return;
410 }
411 m_signalsAllowed = false;
412
413 QString currentFamily;
414 if (family.isEmpty()) {
415 Q_ASSERT(m_ui->familyListWidget->currentItem());
416 if (m_ui->familyListWidget->currentItem()) {
417 currentFamily = m_qtFamilies[m_ui->familyListWidget->currentItem()->text()];
418 }
419 } else {
420 currentFamily = m_qtFamilies[family];
421 }
422
423 // Get the list of styles available in this family.
424 QStringList styles = QFontDatabase::styles(currentFamily);
425 if (styles.isEmpty()) {
426 // Avoid extraction, it is in kdeqt.po
427 styles.append(TR_NOX("Normal", "QFontDatabase"));
428 }
429
430 // Always prepend Regular, Normal, Book or Roman, this way if "m_selectedStyle"
431 // in the code below is empty, selecting index 0 should work better
432 std::sort(styles.begin(), styles.end(), [](const QString &a, const QString &b) {
433 if (isDefaultFontStyleName(a)) {
434 return true;
435 } else if (isDefaultFontStyleName(b)) {
436 return false;
437 }
438 return false;
439 });
440
441 // Filter style strings and add to the listbox.
442 QString pureFamily;
443 splitFontString(family, &pureFamily);
444 QStringList filteredStyles;
445 m_qtStyles.clear();
446 m_styleIDs.clear();
447
448 const QStringList origStyles = styles;
449 for (const QString &style : origStyles) {
450 // Sometimes the font database will report an invalid style,
451 // that falls back back to another when set.
452 // Remove such styles, by checking set/get round-trip.
453 QFont testFont = QFontDatabase::font(currentFamily, style, 10);
454 if (QFontDatabase::styleString(testFont) != style) {
455 styles.removeAll(style);
456 continue;
457 }
458
459 QString fstyle = tr("%1", "@item Font style").arg(style);
460 if (!filteredStyles.contains(fstyle)) {
461 filteredStyles.append(fstyle);
462 m_qtStyles.insert({fstyle, style});
463 m_styleIDs.insert({fstyle, styleIdentifier(testFont)});
464 }
465 }
466 m_ui->styleListWidget->clear();
467 m_ui->styleListWidget->addItems(filteredStyles);
468
469 // Try to set the current style in the listbox to that previous.
470 int listPos = filteredStyles.indexOf(m_selectedStyle.isEmpty() ? TR_NOX("Normal", "QFontDatabase") : m_selectedStyle);
471 if (listPos < 0) {
472 // Make extra effort to have Italic selected when Oblique was chosen,
473 // and vice versa, as that is what the user would probably want.
474 QString styleIt = tr("Italic", "@item font");
475 QString styleOb = tr("Oblique", "@item font");
476 for (int i = 0; i < 2; ++i) {
477 int pos = m_selectedStyle.indexOf(styleIt);
478 if (pos >= 0) {
479 QString style = m_selectedStyle;
480 style.replace(pos, styleIt.length(), styleOb);
481 listPos = filteredStyles.indexOf(style);
482 if (listPos >= 0) {
483 break;
484 }
485 }
486 qSwap(styleIt, styleOb);
487 }
488 }
489 m_ui->styleListWidget->setCurrentRow(listPos >= 0 ? listPos : 0);
490 const QString currentStyle = m_qtStyles[m_ui->styleListWidget->currentItem()->text()];
491
492 // Recompute the size listbox for this family/style.
493 qreal currentSize = setupSizeListBox(currentFamily, currentStyle);
494 m_ui->sizeSpinBox->setValue(currentSize);
495
496 m_selectedFont = QFontDatabase::font(currentFamily, currentStyle, static_cast<int>(currentSize));
497 if (QFontDatabase::isSmoothlyScalable(currentFamily, currentStyle) && m_selectedFont.pointSize() == floor(currentSize)) {
498 m_selectedFont.setPointSizeF(currentSize);
499 }
500 Q_EMIT q->fontSelected(m_selectedFont);
501
502 m_signalsAllowed = true;
503}
504
505void KFontChooserPrivate::slotStyleSelected(const QString &style)
506{
507 if (!m_signalsAllowed) {
508 return;
509 }
510 m_signalsAllowed = false;
511
512 const QString currentFamily = m_qtFamilies[m_ui->familyListWidget->currentItem()->text()];
513 const QString currentStyle = !style.isEmpty() ? m_qtStyles[style] : m_qtStyles[m_ui->styleListWidget->currentItem()->text()];
514
515 // Recompute the size listbox for this family/style.
516 qreal currentSize = setupSizeListBox(currentFamily, currentStyle);
517 m_ui->sizeSpinBox->setValue(currentSize);
518
519 m_selectedFont = QFontDatabase::font(currentFamily, currentStyle, static_cast<int>(currentSize));
520 if (QFontDatabase::isSmoothlyScalable(currentFamily, currentStyle) && m_selectedFont.pointSize() == floor(currentSize)) {
521 m_selectedFont.setPointSizeF(currentSize);
522 }
523 Q_EMIT q->fontSelected(m_selectedFont);
524
525 if (!style.isEmpty()) {
526 m_selectedStyle = currentStyle;
527 }
528
529 m_signalsAllowed = true;
530}
531
532void KFontChooserPrivate::slotSizeSelected(const QString &size)
533{
534 if (!m_signalsAllowed) {
535 return;
536 }
537
538 m_signalsAllowed = false;
539
540 qreal currentSize = 0.0;
541 if (size.isEmpty()) {
542 currentSize = QLocale::system().toDouble(m_ui->sizeListWidget->currentItem()->text());
543 } else {
544 currentSize = QLocale::system().toDouble(size);
545 }
546
547 // Reset the customized size slot in the list if not needed.
548 if (m_customSizeRow >= 0 && m_selectedFont.pointSizeF() != currentSize) {
549 m_ui->sizeListWidget->item(m_customSizeRow)->setText(m_standardSizeAtCustom);
550 m_customSizeRow = -1;
551 }
552
553 m_ui->sizeSpinBox->setValue(currentSize);
554 m_selectedFont.setPointSizeF(currentSize);
555 Q_EMIT q->fontSelected(m_selectedFont);
556
557 if (!size.isEmpty()) {
558 m_selectedSize = currentSize;
559 }
560
561 m_signalsAllowed = true;
562}
563
564void KFontChooserPrivate::slotSizeValue(double dval)
565{
566 if (!m_signalsAllowed) {
567 return;
568 }
569 m_signalsAllowed = false;
570
571 // We compare with qreal, so convert for platforms where qreal != double.
572 qreal val = qreal(dval);
573
574 // Reset current size slot in list if it was customized.
575 if (m_customSizeRow >= 0 && m_ui->sizeListWidget->currentRow() == m_customSizeRow) {
576 m_ui->sizeListWidget->item(m_customSizeRow)->setText(m_standardSizeAtCustom);
577 m_customSizeRow = -1;
578 }
579
580 bool canCustomize = true;
581
582 const QString family = m_qtFamilies[m_ui->familyListWidget->currentItem()->text()];
583 const QString style = m_qtStyles[m_ui->styleListWidget->currentItem()->text()];
584
585 // For Qt-bad-sizes workaround: skip this block unconditionally
586 if (!QFontDatabase::isSmoothlyScalable(family, style)) {
587 // Bitmap font, allow only discrete sizes.
588 // Determine the nearest in the direction of change.
589 canCustomize = false;
590 int nrows = m_ui->sizeListWidget->count();
591 int row = m_ui->sizeListWidget->currentRow();
592 int nrow;
593 if (val - m_selectedFont.pointSizeF() > 0) {
594 for (nrow = row + 1; nrow < nrows; ++nrow) {
595 if (QLocale::system().toDouble(m_ui->sizeListWidget->item(nrow)->text()) >= val) {
596 break;
597 }
598 }
599 } else {
600 for (nrow = row - 1; nrow >= 0; --nrow) {
601 if (QLocale::system().toDouble(m_ui->sizeListWidget->item(nrow)->text()) <= val) {
602 break;
603 }
604 }
605 }
606 // Make sure the new row is not out of bounds.
607 nrow = nrow < 0 ? 0 : nrow >= nrows ? nrows - 1 : nrow;
608 // Get the size from the new row and set the spinbox to that size.
609 val = QLocale::system().toDouble(m_ui->sizeListWidget->item(nrow)->text());
610 m_ui->sizeSpinBox->setValue(val);
611 }
612
613 // Set the current size in the size listbox.
614 int row = nearestSizeRow(val, canCustomize);
615 m_ui->sizeListWidget->setCurrentRow(row);
616
617 m_selectedSize = val;
618 m_selectedFont.setPointSizeF(val);
619 Q_EMIT q->fontSelected(m_selectedFont);
620
621 m_signalsAllowed = true;
622}
623
624void KFontChooserPrivate::slotFeaturesChanged(const QString &features)
625{
626#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
627 m_selectedFont.clearFeatures();
628
629 if (features.isEmpty()) {
630 return;
631 }
632
633 const QStringList rawFeaturesList = features.split(QLatin1Char(','), Qt::SkipEmptyParts);
634 for (const QString &feature : rawFeaturesList) {
635 auto f = QStringView(feature).trimmed();
636 if (f.isEmpty()) {
637 continue;
638 }
639 QList<QStringView> parts = f.split(QStringLiteral("="), Qt::SkipEmptyParts);
640 if (parts.length() == 2) {
641 const auto tag = QFont::Tag::fromString(parts[0]);
642 bool ok = false;
643 const int number = parts[1].toInt(&ok);
644 if (tag.has_value() && ok) {
645 m_selectedFont.setFeature(tag.value(), number);
646 }
647 } else if (f.size() <= 4) {
648 const auto tag = QFont::Tag::fromString(feature);
649 if (tag.has_value()) {
650 m_selectedFont.setFeature(tag.value(), 1);
651 }
652 }
653 }
654
655 Q_EMIT q->fontSelected(m_selectedFont);
656#endif
657}
658
659void KFontChooserPrivate::displaySample(const QFont &font)
660{
661 m_ui->sampleTextEdit->setFont(font);
662}
663
664int KFontChooserPrivate::nearestSizeRow(qreal val, bool customize)
665{
666 qreal diff = 1000;
667 int row = 0;
668 for (int r = 0; r < m_ui->sizeListWidget->count(); ++r) {
669 qreal cval = QLocale::system().toDouble(m_ui->sizeListWidget->item(r)->text());
670 if (qAbs(cval - val) < diff) {
671 diff = qAbs(cval - val);
672 row = r;
673 }
674 }
675 // For Qt-bad-sizes workaround: ignore value of customize, use true
676 if (customize && diff > 0) {
677 m_customSizeRow = row;
678 m_standardSizeAtCustom = m_ui->sizeListWidget->item(row)->text();
679 m_ui->sizeListWidget->item(row)->setText(formatFontSize(val));
680 }
681 return row;
682}
683
684qreal KFontChooserPrivate::fillSizeList(const QList<qreal> &sizes_)
685{
686 if (m_ui->sizeListWidget->isHidden()) {
687 qCWarning(KWidgetsAddonsLog) << "Trying to fill the font size listwidget, but the widget is hidden.";
688 return 0.0;
689 }
690
691 QList<qreal> sizes = sizes_;
692 bool canCustomize = false;
693 if (sizes.isEmpty()) {
694 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};
695 for (int i = 0; c[i]; ++i) {
696 sizes.append(c[i]);
697 }
698 // Since sizes were not supplied, this is a vector font,
699 // and size slot customization is allowed.
700 canCustomize = true;
701 }
702
703 // Insert sizes into the listbox.
704 m_ui->sizeListWidget->clear();
705 std::sort(sizes.begin(), sizes.end());
706 for (qreal size : std::as_const(sizes)) {
707 m_ui->sizeListWidget->addItem(formatFontSize(size));
708 }
709
710 // Return the nearest to selected size.
711 // If the font is vector, the nearest size is always same as selected,
712 // thus size slot customization is allowed.
713 // If the font is bitmap, the nearest size need not be same as selected,
714 // thus size slot customization is not allowed.
715 m_customSizeRow = -1;
716 int row = nearestSizeRow(m_selectedSize, canCustomize);
717 return QLocale::system().toDouble(m_ui->sizeListWidget->item(row)->text());
718}
719
720qreal KFontChooserPrivate::setupSizeListBox(const QString &family, const QString &style)
721{
722 QList<qreal> sizes;
723 const bool smoothlyScalable = QFontDatabase::isSmoothlyScalable(family, style);
724 if (!smoothlyScalable) {
725 const QList<int> smoothSizes = QFontDatabase::smoothSizes(family, style);
726 for (int size : smoothSizes) {
727 sizes.append(size);
728 }
729 }
730
731 // Fill the listbox (uses default list of sizes if the given is empty).
732 // Collect the best fitting size to selected size, to use if not smooth.
733 qreal bestFitSize = fillSizeList(sizes);
734
735 // Set the best fit size as current in the listbox if available.
736 const QList<QListWidgetItem *> selectedSizeList = m_ui->sizeListWidget->findItems(formatFontSize(bestFitSize), Qt::MatchExactly);
737 if (!selectedSizeList.isEmpty()) {
738 m_ui->sizeListWidget->setCurrentItem(selectedSizeList.first());
739 }
740
741 return bestFitSize;
742}
743
744void KFontChooserPrivate::setupDisplay()
745{
746 qreal size = m_selectedFont.pointSizeF();
747 if (size == -1) {
748 size = QFontInfo(m_selectedFont).pointSizeF();
749 }
750
751 // Set font features
752#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
753 const auto tags = m_selectedFont.featureTags();
755 for (const auto &tag : tags) {
756 const QString name = QString::fromUtf8(tag.toString());
757 const quint32 value = m_selectedFont.featureValue(tag);
758 if (value == 1) {
759 features.push_back(name);
760 } else {
761 features.push_back(QStringLiteral("%1=%2").arg(name, QString::number(value)));
762 }
763 }
764 m_ui->fontFeaturesLineEdit->setText(features.join(QStringLiteral(",")));
765#endif
766
767 int numEntries;
768 int i;
769
770 // Get the styleID here before familyListWidget->setCurrentRow() is called
771 // as it may change the font style
772 const QString styleID = styleIdentifier(m_selectedFont);
773
774 QString family = m_selectedFont.family().toLower();
775 // Direct family match.
776 numEntries = m_ui->familyListWidget->count();
777 for (i = 0; i < numEntries; ++i) {
778 if (family == m_qtFamilies[m_ui->familyListWidget->item(i)->text()].toLower()) {
779 m_ui->familyListWidget->setCurrentRow(i);
780 break;
781 }
782 }
783
784 // 1st family fallback.
785 if (i == numEntries) {
786 const int bracketPos = family.indexOf(QLatin1Char('['));
787 if (bracketPos != -1) {
788 family = QStringView(family).left(bracketPos).trimmed().toString();
789 for (i = 0; i < numEntries; ++i) {
790 if (family == m_qtFamilies[m_ui->familyListWidget->item(i)->text()].toLower()) {
791 m_ui->familyListWidget->setCurrentRow(i);
792 break;
793 }
794 }
795 }
796 }
797
798 // 2nd family fallback.
799 if (i == numEntries) {
800 QString fallback = family + QLatin1String(" [");
801 for (i = 0; i < numEntries; ++i) {
802 if (m_qtFamilies[m_ui->familyListWidget->item(i)->text()].toLower().startsWith(fallback)) {
803 m_ui->familyListWidget->setCurrentRow(i);
804 break;
805 }
806 }
807 }
808
809 // 3rd family fallback.
810 if (i == numEntries) {
811 for (i = 0; i < numEntries; ++i) {
812 if (m_qtFamilies[m_ui->familyListWidget->item(i)->text()].toLower().startsWith(family)) {
813 m_ui->familyListWidget->setCurrentRow(i);
814 break;
815 }
816 }
817 }
818
819 // Family fallback in case nothing matched. Otherwise, diff doesn't work
820 if (i == numEntries) {
821 m_ui->familyListWidget->setCurrentRow(0);
822 }
823
824 // By setting the current item in the family box, the available
825 // styles and sizes for that family have been collected.
826 // Try now to set the current items in the style and size boxes.
827
828 // Set current style in the listbox.
829 numEntries = m_ui->styleListWidget->count();
830 for (i = 0; i < numEntries; ++i) {
831 if (styleID == m_styleIDs[m_ui->styleListWidget->item(i)->text()]) {
832 m_ui->styleListWidget->setCurrentRow(i);
833 break;
834 }
835 }
836 if (i == numEntries) {
837 // Style not found, fallback.
838 m_ui->styleListWidget->setCurrentRow(0);
839 }
840
841 // Set current size in the listbox.
842 // If smoothly scalable, allow customizing one of the standard size slots,
843 // otherwise just select the nearest available size.
844 const QString currentFamily = m_qtFamilies[m_ui->familyListWidget->currentItem()->text()];
845 const QString currentStyle = m_qtStyles[m_ui->styleListWidget->currentItem()->text()];
846 const bool canCustomize = QFontDatabase::isSmoothlyScalable(currentFamily, currentStyle);
847 m_ui->sizeListWidget->setCurrentRow(nearestSizeRow(size, canCustomize));
848
849 // Set current size in the spinbox.
850 m_ui->sizeSpinBox->setValue(QLocale::system().toDouble(m_ui->sizeListWidget->currentItem()->text()));
851}
852
853// static
855{
857
858 // if we have criteria; then check fonts before adding
859 if (fontListCriteria) {
860 QStringList lstFonts;
861 for (const QString &family : std::as_const(lstSys)) {
862 if ((fontListCriteria & FixedWidthFonts) > 0 && !QFontDatabase::isFixedPitch(family)) {
863 continue;
864 }
865 if (((fontListCriteria & (SmoothScalableFonts | ScalableFonts)) == ScalableFonts) && !QFontDatabase::isBitmapScalable(family)) {
866 continue;
867 }
868 if ((fontListCriteria & SmoothScalableFonts) > 0 && !QFontDatabase::isSmoothlyScalable(family)) {
869 continue;
870 }
871 lstFonts.append(family);
872 }
873
874 if ((fontListCriteria & FixedWidthFonts) > 0) {
875 // Fallback.. if there are no fixed fonts found, it's probably a
876 // bug in the font server or Qt. In this case, just use 'fixed'
877 if (lstFonts.isEmpty()) {
878 lstFonts.append(QStringLiteral("fixed"));
879 }
880 }
881
882 lstSys = lstFonts;
883 }
884
885 lstSys.sort();
886
887 return lstSys;
888}
889
891{
892 d->setFamilyBoxItems(fontList);
893}
894
895void KFontChooserPrivate::setFamilyBoxItems(const QStringList &fonts)
896{
897 m_signalsAllowed = false;
898
899 m_ui->familyListWidget->clear();
900
901 m_qtFamilies = translateFontNameList(!fonts.isEmpty() ? fonts : KFontChooser::createFontList(m_usingFixed ? KFontChooser::FixedWidthFonts : 0));
902
903 QStringList list;
904 list.reserve(m_qtFamilies.size());
905
906 // Generic font names
907 const QStringList genericTranslatedNames{
908 translateFontName(QStringLiteral("Sans Serif")),
909 translateFontName(QStringLiteral("Serif")),
910 translateFontName(QStringLiteral("Monospace")),
911 };
912
913 // Add generic family names to the top of the list
914 for (const QString &s : genericTranslatedNames) {
915 auto nIt = m_qtFamilies.find(s);
916 if (nIt != m_qtFamilies.cend()) {
917 list.push_back(s);
918 }
919 }
920
921 for (auto it = m_qtFamilies.cbegin(); it != m_qtFamilies.cend(); ++it) {
922 const QString &name = it->first;
923 if (genericTranslatedNames.contains(name)) {
924 continue;
925 }
926
927 list.push_back(name);
928 }
929
930 m_ui->familyListWidget->addItems(list);
931 m_ui->familyListWidget->setMinimumWidth(minimumListWidth(m_ui->familyListWidget));
932
933 m_signalsAllowed = true;
934}
935
937{
938 for (auto *widget : {d->m_ui->familyListWidget, d->m_ui->styleListWidget, d->m_ui->sizeListWidget}) {
939 widget->setMinimumHeight(minimumListHeight(widget, visibleItems));
940 }
941}
942
943// Human-readable style identifiers returned by QFontDatabase::styleString()
944// do not always survive round trip of QFont serialization/deserialization,
945// causing wrong style in the style box to be highlighted when
946// the chooser dialog is opened. This will cause the style to be changed
947// when the dialog is closed and the user did not touch the style box.
948// Hence, construct custom style identifiers sufficient for the purpose.
949QString KFontChooserPrivate::styleIdentifier(const QFont &font)
950{
951 const int weight = font.weight();
952 QString styleName = font.styleName();
953 // If the styleName property is empty and the weight is QFont::Normal, that
954 // could mean it's a "Regular"-like style with the styleName part stripped
955 // so that subsequent calls to setBold(true) can work properly (i.e. selecting
956 // the "Bold" style provided by the font itself) without resorting to font
957 // "emboldening" which looks ugly.
958 // See also KConfigGroupGui::writeEntryGui().
959 if (styleName.isEmpty() && weight == QFont::Normal) {
960 const QStringList styles = QFontDatabase::styles(font.family());
961 for (const QString &style : styles) {
962 if (isDefaultFontStyleName(style)) {
963 styleName = style;
964 break;
965 } else {
966 // nothing more we can do
967 }
968 }
969 }
970
971 const QChar comma(QLatin1Char(','));
972 return QString::number(weight) + comma //
973 + QString::number((int)font.style()) + comma //
974 + QString::number(font.stretch()) + comma //
975 + styleName;
976}
977
978#include "moc_kfontchooser.cpp"
A font selection widget.
@ FamilyList
Identifies the family (leftmost) list.
@ SizeList
Identifies the size (rightmost) list.
@ StyleList
Identifies the style (center) list.
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.
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)
void clearFeatures()
QString family() const const
QList< quint32 > featureTags() const const
quint32 featureValue(quint32 tag) const const
int pointSize() const const
qreal pointSizeF() const const
void setFeature(const char *feature, quint32 value)
void setPointSizeF(qreal pointSize)
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)
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
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
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
qsizetype indexOf(const QRegularExpression &re, qsizetype from) const const
void sort(Qt::CaseSensitivity cs)
QStringView left(qsizetype length) const const
QString toString() const const
QStringView trimmed() const const
PM_LayoutVerticalSpacing
virtual int pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const const=0
MatchExactly
SkipEmptyParts
QMetaObject::Connection callOnTimeout(Functor &&slot)
void setInterval(int msec)
void setSingleShot(bool singleShot)
void setEnabled(bool)
QStyle * style() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Oct 11 2024 12:19:04 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.