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
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(i)->text());
44 // ...and add a space on both sides for a not too tight look.
45 itemWidth += extraSpace;
46 width = std::max(width, 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(list->item(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(size, 'f', (size == floor(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 {
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
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
127 : QWidget(parent)
128 , d(new KFontChooserPrivate(KFontChooser::DisplayFrame, this))
129{
130 d->init();
131}
132
134 : QWidget(parent)
135 , d(new KFontChooserPrivate(flags, this))
136{
137 d->init();
138}
139
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(0, 0, 0, 0);
151
152 QWidget *page = m_flags & KFontChooser::DisplayFrame ? new QGroupBox(KFontChooser::tr("Requested Font", "@title:group"), q) : new QWidget(q);
153 mainLayout->addWidget(page);
154
155 m_ui.reset(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(QStyle::PM_LayoutVerticalSpacing));
160 m_ui->mainHorizontalLayout->setSpacing(q->style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing));
161 m_ui->gridLayout->setVerticalSpacing(q->style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing) * 2);
162 m_ui->gridLayout->setHorizontalSpacing(q->style()->pixelMetric(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(m_ui->familyListWidget, &QListWidget::currentTextChanged, [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(m_ui->familyCheckBox, &QCheckBox::toggled, m_ui->familyListWidget, &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(m_ui->onlyFixedCheckBox, &QCheckBox::toggled, q, [this](const bool state) {
188 q->setFont(m_selectedFont, 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(m_ui->familyCheckBox, &QCheckBox::toggled, m_ui->onlyFixedCheckBox, &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(KFontChooser::tr("Normal", "@item font"));
200 m_ui->styleListWidget->addItem(KFontChooser::tr("Italic", "@item font"));
201 m_ui->styleListWidget->addItem(KFontChooser::tr("Oblique", "@item font"));
202 m_ui->styleListWidget->addItem(KFontChooser::tr("Bold", "@item font"));
203 m_ui->styleListWidget->addItem(KFontChooser::tr("Bold Condensed Oblique", "@item font"));
204 m_ui->styleListWidget->setMinimumWidth(minimumListWidth(m_ui->styleListWidget));
205
206 QObject::connect(m_ui->styleListWidget, &QListWidget::currentTextChanged, [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(m_ui->styleCheckBox, &QCheckBox::toggled, m_ui->styleListWidget, &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(m_ui->sizeSpinBox, &QDoubleSpinBox::valueChanged, [this](const double size) {
223 slotSizeValue(size);
224 });
225
226 QObject::connect(m_ui->sizeListWidget, &QListWidget::currentTextChanged, [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(m_ui->sizeCheckBox, &QCheckBox::toggled, m_ui->sizeListWidget, &QWidget::setEnabled);
235 QObject::connect(m_ui->sizeCheckBox, &QCheckBox::toggled, m_ui->sizeSpinBox, &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("The Quick Brown Fox Jumps Over The Lazy Dog"));
248 m_ui->sampleTextEdit->setTextCursor(QTextCursor(m_ui->sampleTextEdit->document()));
249
250 QObject::connect(q, &KFontChooser::fontSelected, q, [this](const QFont &font) {
251 displaySample(font);
252 });
253
254 // lets initialize the display if possible
255 if (m_usingFixed) {
257 } else {
258 q->setFont(QGuiApplication::font(), 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
269{
270 d->m_palette.setColor(QPalette::Active, QPalette::Text, col);
271 QPalette pal = d->m_ui->sampleTextEdit->palette();
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(QPalette::Active, QPalette::Text);
283}
284
286{
287 d->m_palette.setColor(QPalette::Active, QPalette::Base, col);
288 QPalette pal = d->m_ui->sampleTextEdit->palette();
290 d->m_ui->sampleTextEdit->setPalette(pal);
291}
292
293QColor KFontChooser::backgroundColor() const
294{
295 return d->m_palette.color(QPalette::Active, QPalette::Base);
296}
297
298QString KFontChooser::sampleText() const
299{
300 return d->m_ui->sampleTextEdit->toPlainText();
301}
302
304{
305 d->m_ui->sampleTextEdit->setPlainText(text);
306}
307
309{
310 d->m_ui->sampleTextEdit->setVisible(visible);
311}
312
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
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(currentFamily);
401 if (styles.isEmpty()) {
402 // Avoid extraction, it is in kdeqt.po
403 styles.append(TR_NOX("Normal", "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(styles.begin(), styles.end(), [](const QString &a, const QString &b) {
409 if (isDefaultFontStyleName(a)) {
410 return true;
411 } else if (isDefaultFontStyleName(b)) {
412 return false;
413 }
414 return false;
415 });
416
417 // Filter style strings and add to the listbox.
418 QString pureFamily;
419 splitFontString(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(currentFamily, style, 10);
430 if (QFontDatabase::styleString(testFont) != style) {
431 styles.removeAll(style);
432 continue;
433 }
434
435 QString fstyle = tr("%1", "@item Font style").arg(style);
436 if (!filteredStyles.contains(fstyle)) {
437 filteredStyles.append(fstyle);
438 m_qtStyles.insert({fstyle, style});
439 m_styleIDs.insert({fstyle, styleIdentifier(testFont)});
440 }
441 }
442 m_ui->styleListWidget->clear();
443 m_ui->styleListWidget->addItems(filteredStyles);
444
445 // Try to set the current style in the listbox to that previous.
446 int listPos = filteredStyles.indexOf(m_selectedStyle.isEmpty() ? TR_NOX("Normal", "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("Italic", "@item font");
451 QString styleOb = tr("Oblique", "@item font");
452 for (int i = 0; i < 2; ++i) {
453 int pos = m_selectedStyle.indexOf(styleIt);
454 if (pos >= 0) {
455 QString style = m_selectedStyle;
456 style.replace(pos, styleIt.length(), styleOb);
457 listPos = filteredStyles.indexOf(style);
458 if (listPos >= 0) {
459 break;
460 }
461 }
462 qSwap(styleIt, 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(currentFamily, currentStyle);
470 m_ui->sizeSpinBox->setValue(currentSize);
471
472 m_selectedFont = QFontDatabase::font(currentFamily, currentStyle, static_cast<int>(currentSize));
473 if (QFontDatabase::isSmoothlyScalable(currentFamily, currentStyle) && m_selectedFont.pointSize() == floor(currentSize)) {
474 m_selectedFont.setPointSizeF(currentSize);
475 }
476 Q_EMIT q->fontSelected(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(currentFamily, currentStyle);
493 m_ui->sizeSpinBox->setValue(currentSize);
494
495 m_selectedFont = QFontDatabase::font(currentFamily, currentStyle, static_cast<int>(currentSize));
496 if (QFontDatabase::isSmoothlyScalable(currentFamily, currentStyle) && m_selectedFont.pointSize() == floor(currentSize)) {
497 m_selectedFont.setPointSizeF(currentSize);
498 }
499 Q_EMIT q->fontSelected(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(m_ui->sizeListWidget->currentItem()->text());
519 } else {
520 currentSize = QLocale::system().toDouble(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(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(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(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(m_ui->sizeListWidget->item(nrow)->text()) >= val) {
572 break;
573 }
574 }
575 } else {
576 for (nrow = row - 1; nrow >= 0; --nrow) {
577 if (QLocale::system().toDouble(m_ui->sizeListWidget->item(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(m_ui->sizeListWidget->item(nrow)->text());
586 m_ui->sizeSpinBox->setValue(val);
587 }
588
589 // Set the current size in the size listbox.
590 int row = nearestSizeRow(val, canCustomize);
591 m_ui->sizeListWidget->setCurrentRow(row);
592
593 m_selectedSize = val;
594 m_selectedFont.setPointSizeF(val);
595 Q_EMIT q->fontSelected(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(m_ui->sizeListWidget->item(r)->text());
611 if (qAbs(cval - val) < diff) {
612 diff = qAbs(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(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(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(sizes.begin(), sizes.end());
647 for (qreal size : std::as_const(sizes)) {
648 m_ui->sizeListWidget->addItem(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(m_selectedSize, canCustomize);
658 return QLocale::system().toDouble(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(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);
675
676 // Set the best fit size as current in the listbox if available.
677 const QList<QListWidgetItem *> selectedSizeList = m_ui->sizeListWidget->findItems(formatFontSize(bestFitSize), 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(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(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(QLatin1Char('['));
712 if (bracketPos != -1) {
713 family = QStringView(family).left(bracketPos).trimmed().toString();
714 for (i = 0; i < numEntries; ++i) {
715 if (family == m_qtFamilies[m_ui->familyListWidget->item(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(i)->text()].toLower().startsWith(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(i)->text()].toLower().startsWith(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(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(currentFamily, currentStyle);
772 m_ui->sizeListWidget->setCurrentRow(nearestSizeRow(size, canCustomize));
773
774 // Set current size in the spinbox.
775 m_ui->sizeSpinBox->setValue(QLocale::system().toDouble(m_ui->sizeListWidget->currentItem()->text()));
776}
777
778// static
780{
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(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(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
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(!fonts.isEmpty() ? fonts : KFontChooser::createFontList(m_usingFixed ? KFontChooser::FixedWidthFonts : 0));
827
828 QStringList list;
829 list.reserve(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(s);
841 if (nIt != m_qtFamilies.cend()) {
842 list.push_back(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(name)) {
849 continue;
850 }
851
852 list.push_back(name);
853 }
854
855 m_ui->familyListWidget->addItems(list);
856 m_ui->familyListWidget->setMinimumWidth(minimumListWidth(m_ui->familyListWidget));
857
858 m_signalsAllowed = true;
859}
860
862{
863 for (auto *widget : {d->m_ui->familyListWidget, d->m_ui->styleListWidget, d->m_ui->sizeListWidget}) {
864 widget->setMinimumHeight(minimumListHeight(widget, 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(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"
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 QStringList list(const QString &fileClass)
QString name(StandardAction id)
void toggled(bool checked)
void addWidget(QWidget *widget, int stretch, Qt::Alignment alignment)
void valueChanged(double d)
QString family() const const
int pointSize() const const
qreal pointSizeF() const const
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 append(QList< T > &&value)
iterator begin()
void clear()
qsizetype count() const const
iterator end()
T & first()
bool isEmpty() 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
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
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 Jul 19 2024 11:51:03 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.