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

source code of kwidgetsaddons/src/kfontchooser.cpp