cf12defd28
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
979 lines
33 KiB
C++
979 lines
33 KiB
C++
/*
|
|
SPDX-FileCopyrightText: 1996 Bernd Johannes Wuebben <wuebben@kde.org>
|
|
SPDX-FileCopyrightText: 1999 Preston Brown <pbrown@kde.org>
|
|
SPDX-FileCopyrightText: 1999 Mario Weilguni <mweilguni@kde.org>
|
|
|
|
SPDX-License-Identifier: LGPL-2.0-or-later
|
|
*/
|
|
|
|
#include "kfontchooser.h"
|
|
#include "fonthelpers_p.h"
|
|
#include "ui_kfontchooserwidget.h"
|
|
|
|
#include "loggingcategory.h"
|
|
|
|
#include <QCheckBox>
|
|
#include <QDoubleSpinBox>
|
|
#include <QFontDatabase>
|
|
#include <QGroupBox>
|
|
#include <QGuiApplication>
|
|
#include <QLabel>
|
|
#include <QLayout>
|
|
#include <QListWidget>
|
|
#include <QLocale>
|
|
#include <QScrollBar>
|
|
#include <QSplitter>
|
|
#include <QTextEdit>
|
|
#include <QTimer>
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
|
|
// When message extraction needs to be avoided.
|
|
#define TR_NOX tr
|
|
|
|
static int minimumListWidth(const QListWidget *list)
|
|
{
|
|
QFontMetrics fm = list->fontMetrics();
|
|
|
|
const int extraSpace = fm.horizontalAdvance(QLatin1Char(' ')) * 2;
|
|
|
|
// Minimum initial size
|
|
int width = 40;
|
|
for (int i = 0, rows = list->count(); i < rows; ++i) {
|
|
int itemWidth = fm.horizontalAdvance(list->item(i)->text());
|
|
// ...and add a space on both sides for a not too tight look.
|
|
itemWidth += extraSpace;
|
|
width = std::max(width, itemWidth);
|
|
}
|
|
|
|
width += list->frameWidth() * 2;
|
|
width += list->verticalScrollBar()->sizeHint().width();
|
|
return width;
|
|
}
|
|
|
|
static int minimumListHeight(const QListWidget *list, int numVisibleEntry)
|
|
{
|
|
int w = list->fontMetrics().lineSpacing();
|
|
if (w < 0) {
|
|
w = 10;
|
|
}
|
|
|
|
if (numVisibleEntry <= 0) {
|
|
numVisibleEntry = 4;
|
|
}
|
|
|
|
w = w * numVisibleEntry;
|
|
w += list->frameWidth() * 2;
|
|
w += list->horizontalScrollBar()->sizeHint().height();
|
|
return w;
|
|
}
|
|
|
|
static QString formatFontSize(qreal size)
|
|
{
|
|
return QLocale::system().toString(size, 'f', (size == floor(size)) ? 0 : 1);
|
|
}
|
|
|
|
class KFontChooserPrivate
|
|
{
|
|
Q_DECLARE_TR_FUNCTIONS(KFontChooser)
|
|
|
|
public:
|
|
KFontChooserPrivate(KFontChooser::DisplayFlags flags, KFontChooser *qq)
|
|
: q(qq)
|
|
, m_flags(flags)
|
|
{
|
|
m_palette.setColor(QPalette::Active, QPalette::Text, Qt::black);
|
|
m_palette.setColor(QPalette::Active, QPalette::Base, Qt::white);
|
|
}
|
|
|
|
void init();
|
|
void setFamilyBoxItems(const QStringList &fonts = {});
|
|
int nearestSizeRow(qreal val, bool customize);
|
|
qreal fillSizeList(const QList<qreal> &sizes = QList<qreal>());
|
|
qreal setupSizeListBox(const QString &family, const QString &style);
|
|
|
|
void setupDisplay();
|
|
QString styleIdentifier(const QFont &font);
|
|
|
|
void slotFamilySelected(const QString &);
|
|
void slotSizeSelected(const QString &);
|
|
void slotStyleSelected(const QString &);
|
|
void displaySample(const QFont &font);
|
|
void slotSizeValue(double);
|
|
void slotFeaturesChanged(const QString &features);
|
|
|
|
KFontChooser *q;
|
|
|
|
std::unique_ptr<Ui_KFontChooserWidget> m_ui;
|
|
|
|
KFontChooser::DisplayFlags m_flags = KFontChooser::NoDisplayFlags;
|
|
|
|
QPalette m_palette;
|
|
|
|
QFont m_selectedFont;
|
|
|
|
QString m_selectedStyle;
|
|
qreal m_selectedSize = -1.0;
|
|
|
|
QString m_standardSizeAtCustom;
|
|
int m_customSizeRow = -1;
|
|
|
|
bool m_signalsAllowed = true;
|
|
bool m_usingFixed = false;
|
|
|
|
// Mappings of translated to Qt originated family and style strings.
|
|
FontFamiliesMap m_qtFamilies;
|
|
std::map<QString, QString> m_qtStyles;
|
|
// Mapping of translated style strings to internal style identifiers.
|
|
std::map<QString, QString> m_styleIDs;
|
|
|
|
QTimer m_fontFeatureChangedTimer;
|
|
};
|
|
|
|
KFontChooser::KFontChooser(QWidget *parent)
|
|
: QWidget(parent)
|
|
, d(new KFontChooserPrivate(KFontChooser::DisplayFrame, this))
|
|
{
|
|
d->init();
|
|
}
|
|
|
|
KFontChooser::KFontChooser(const DisplayFlags flags, QWidget *parent)
|
|
: QWidget(parent)
|
|
, d(new KFontChooserPrivate(flags, this))
|
|
{
|
|
d->init();
|
|
}
|
|
|
|
KFontChooser::~KFontChooser() = default;
|
|
|
|
void KFontChooserPrivate::init()
|
|
{
|
|
m_usingFixed = m_flags & KFontChooser::FixedFontsOnly;
|
|
|
|
// The main layout is divided horizontally into a top part with
|
|
// the font attribute widgets (family, style, size) and a bottom
|
|
// part with a preview of the selected font
|
|
QVBoxLayout *mainLayout = new QVBoxLayout(q);
|
|
mainLayout->setContentsMargins(0, 0, 0, 0);
|
|
|
|
QWidget *page = m_flags & KFontChooser::DisplayFrame ? new QGroupBox(KFontChooser::tr("Requested Font", "@title:group"), q) : new QWidget(q);
|
|
mainLayout->addWidget(page);
|
|
|
|
m_ui.reset(new Ui_KFontChooserWidget);
|
|
m_ui->setupUi(page);
|
|
|
|
#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0)
|
|
m_ui->fontFeaturesLabel->setVisible(false);
|
|
m_ui->fontFeaturesLineEdit->setVisible(false);
|
|
#endif
|
|
|
|
// Increase spacing on top of the preview field and then reset the other layouts
|
|
// back to a standard value.
|
|
m_ui->sampleTextEditLayout->setSpacing(q->style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing));
|
|
m_ui->mainHorizontalLayout->setSpacing(q->style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing));
|
|
m_ui->gridLayout->setVerticalSpacing(q->style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing) * 2);
|
|
m_ui->gridLayout->setHorizontalSpacing(q->style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing));
|
|
|
|
// Deprecated, we'll call show() if building with deprecated code
|
|
m_ui->sizeIsRelativeCheckBox->hide();
|
|
|
|
const bool isDiffMode = m_flags & KFontChooser::ShowDifferences;
|
|
|
|
QObject::connect(m_ui->familyListWidget, &QListWidget::currentTextChanged, [this](const QString &family) {
|
|
slotFamilySelected(family);
|
|
});
|
|
|
|
if (isDiffMode) {
|
|
m_ui->familyLabel->hide();
|
|
m_ui->familyListWidget->setEnabled(false);
|
|
QObject::connect(m_ui->familyCheckBox, &QCheckBox::toggled, m_ui->familyListWidget, &QWidget::setEnabled);
|
|
} else {
|
|
m_ui->familyCheckBox->hide();
|
|
}
|
|
|
|
setFamilyBoxItems();
|
|
|
|
// If the calling app sets FixedFontsOnly, don't show the "show fixed only" checkbox
|
|
m_ui->onlyFixedCheckBox->setVisible(!m_usingFixed);
|
|
|
|
if (!m_ui->onlyFixedCheckBox->isHidden()) {
|
|
QObject::connect(m_ui->onlyFixedCheckBox, &QCheckBox::toggled, q, [this](const bool state) {
|
|
q->setFont(m_selectedFont, state);
|
|
});
|
|
|
|
if (isDiffMode) { // In this mode follow the state of the m_ui->familyCheckBox
|
|
m_ui->onlyFixedCheckBox->setEnabled(false);
|
|
QObject::connect(m_ui->familyCheckBox, &QCheckBox::toggled, m_ui->onlyFixedCheckBox, &QWidget::setEnabled);
|
|
}
|
|
}
|
|
|
|
// Populate usual styles, to determine minimum list width;
|
|
// will be replaced later with correct styles.
|
|
m_ui->styleListWidget->addItem(KFontChooser::tr("Normal", "@item font"));
|
|
m_ui->styleListWidget->addItem(KFontChooser::tr("Italic", "@item font"));
|
|
m_ui->styleListWidget->addItem(KFontChooser::tr("Oblique", "@item font"));
|
|
m_ui->styleListWidget->addItem(KFontChooser::tr("Bold", "@item font"));
|
|
m_ui->styleListWidget->addItem(KFontChooser::tr("Bold Condensed Oblique", "@item font"));
|
|
m_ui->styleListWidget->setMinimumWidth(minimumListWidth(m_ui->styleListWidget));
|
|
|
|
QObject::connect(m_ui->styleListWidget, &QListWidget::currentTextChanged, [this](const QString &style) {
|
|
slotStyleSelected(style);
|
|
});
|
|
|
|
if (isDiffMode) {
|
|
m_ui->styleLabel->hide();
|
|
m_ui->styleListWidget->setEnabled(false);
|
|
QObject::connect(m_ui->styleCheckBox, &QCheckBox::toggled, m_ui->styleListWidget, &QWidget::setEnabled);
|
|
} else {
|
|
m_ui->styleCheckBox->hide();
|
|
}
|
|
|
|
// Populate with usual sizes, to determine minimum list width;
|
|
// will be replaced later with correct sizes.
|
|
fillSizeList();
|
|
|
|
QObject::connect(m_ui->sizeSpinBox, &QDoubleSpinBox::valueChanged, [this](const double size) {
|
|
slotSizeValue(size);
|
|
});
|
|
|
|
QObject::connect(m_ui->sizeListWidget, &QListWidget::currentTextChanged, [this](const QString &size) {
|
|
slotSizeSelected(size);
|
|
});
|
|
|
|
m_fontFeatureChangedTimer.setInterval(200);
|
|
m_fontFeatureChangedTimer.setSingleShot(true);
|
|
m_fontFeatureChangedTimer.callOnTimeout([this]() {
|
|
slotFeaturesChanged(m_ui->fontFeaturesLineEdit->text());
|
|
});
|
|
|
|
QObject::connect(m_ui->fontFeaturesLineEdit, &QLineEdit::textChanged, [this](const QString &) {
|
|
m_fontFeatureChangedTimer.start();
|
|
});
|
|
|
|
if (isDiffMode) {
|
|
m_ui->sizeLabel->hide();
|
|
m_ui->sizeListWidget->setEnabled(false);
|
|
m_ui->sizeSpinBox->setEnabled(false);
|
|
QObject::connect(m_ui->sizeCheckBox, &QCheckBox::toggled, m_ui->sizeListWidget, &QWidget::setEnabled);
|
|
QObject::connect(m_ui->sizeCheckBox, &QCheckBox::toggled, m_ui->sizeSpinBox, &QWidget::setEnabled);
|
|
} else {
|
|
m_ui->sizeCheckBox->hide();
|
|
}
|
|
|
|
QFont tmpFont(q->font().family(), 64, QFont::Black);
|
|
m_ui->sampleTextEdit->setFont(tmpFont);
|
|
m_ui->sampleTextEdit->setMinimumHeight(m_ui->sampleTextEdit->fontMetrics().lineSpacing());
|
|
// tr: A classical test phrase, with all letters of the English alphabet.
|
|
// Replace it with a sample text in your language, such that it is
|
|
// representative of language's writing system.
|
|
// If you wish, you can input several lines of text separated by \n.
|
|
q->setSampleText(KFontChooser::tr("The Quick Brown Fox Jumps Over The Lazy Dog"));
|
|
m_ui->sampleTextEdit->setTextCursor(QTextCursor(m_ui->sampleTextEdit->document()));
|
|
|
|
QObject::connect(q, &KFontChooser::fontSelected, q, [this](const QFont &font) {
|
|
displaySample(font);
|
|
});
|
|
|
|
// lets initialize the display if possible
|
|
if (m_usingFixed) {
|
|
q->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont), true);
|
|
} else {
|
|
q->setFont(QGuiApplication::font(), false);
|
|
}
|
|
|
|
// Set the minimum height for the list widgets
|
|
q->setMinVisibleItems(4);
|
|
|
|
// Set focus to the size list as this is the most commonly changed property
|
|
m_ui->sizeListWidget->setFocus();
|
|
}
|
|
|
|
void KFontChooser::setColor(const QColor &col)
|
|
{
|
|
d->m_palette.setColor(QPalette::Active, QPalette::Text, col);
|
|
QPalette pal = d->m_ui->sampleTextEdit->palette();
|
|
pal.setColor(QPalette::Active, QPalette::Text, col);
|
|
d->m_ui->sampleTextEdit->setPalette(pal);
|
|
QTextCursor cursor = d->m_ui->sampleTextEdit->textCursor();
|
|
d->m_ui->sampleTextEdit->selectAll();
|
|
d->m_ui->sampleTextEdit->setTextColor(col);
|
|
d->m_ui->sampleTextEdit->setTextCursor(cursor);
|
|
}
|
|
|
|
QColor KFontChooser::color() const
|
|
{
|
|
return d->m_palette.color(QPalette::Active, QPalette::Text);
|
|
}
|
|
|
|
void KFontChooser::setBackgroundColor(const QColor &col)
|
|
{
|
|
d->m_palette.setColor(QPalette::Active, QPalette::Base, col);
|
|
QPalette pal = d->m_ui->sampleTextEdit->palette();
|
|
pal.setColor(QPalette::Active, QPalette::Base, col);
|
|
d->m_ui->sampleTextEdit->setPalette(pal);
|
|
}
|
|
|
|
QColor KFontChooser::backgroundColor() const
|
|
{
|
|
return d->m_palette.color(QPalette::Active, QPalette::Base);
|
|
}
|
|
|
|
QString KFontChooser::sampleText() const
|
|
{
|
|
return d->m_ui->sampleTextEdit->toPlainText();
|
|
}
|
|
|
|
void KFontChooser::setSampleText(const QString &text)
|
|
{
|
|
d->m_ui->sampleTextEdit->setPlainText(text);
|
|
}
|
|
|
|
void KFontChooser::setSampleBoxVisible(bool visible)
|
|
{
|
|
d->m_ui->sampleTextEdit->setVisible(visible);
|
|
}
|
|
|
|
QSize KFontChooser::sizeHint(void) const
|
|
{
|
|
return minimumSizeHint();
|
|
}
|
|
|
|
void KFontChooser::enableColumn(int column, bool state)
|
|
{
|
|
if (column & FamilyList) {
|
|
d->m_ui->familyListWidget->setEnabled(state);
|
|
}
|
|
if (column & StyleList) {
|
|
d->m_ui->styleListWidget->setEnabled(state);
|
|
}
|
|
if (column & SizeList) {
|
|
d->m_ui->sizeListWidget->setEnabled(state);
|
|
d->m_ui->sizeSpinBox->setEnabled(state);
|
|
}
|
|
}
|
|
|
|
void KFontChooser::setFont(const QFont &aFont, bool onlyFixed)
|
|
{
|
|
d->m_selectedFont = aFont;
|
|
d->m_selectedSize = aFont.pointSizeF();
|
|
if (d->m_selectedSize == -1) {
|
|
d->m_selectedSize = QFontInfo(aFont).pointSizeF();
|
|
}
|
|
|
|
if (onlyFixed != d->m_usingFixed) {
|
|
d->m_usingFixed = onlyFixed;
|
|
d->setFamilyBoxItems();
|
|
}
|
|
d->setupDisplay();
|
|
}
|
|
|
|
KFontChooser::FontDiffFlags KFontChooser::fontDiffFlags() const
|
|
{
|
|
FontDiffFlags diffFlags = NoFontDiffFlags;
|
|
|
|
if (d->m_ui->familyCheckBox->isChecked()) {
|
|
diffFlags |= FontDiffFamily;
|
|
}
|
|
|
|
if (d->m_ui->styleCheckBox->isChecked()) {
|
|
diffFlags |= FontDiffStyle;
|
|
}
|
|
|
|
if (d->m_ui->sizeCheckBox->isChecked()) {
|
|
diffFlags |= FontDiffSize;
|
|
}
|
|
|
|
return diffFlags;
|
|
}
|
|
|
|
QFont KFontChooser::font() const
|
|
{
|
|
return d->m_selectedFont;
|
|
}
|
|
|
|
static bool isDefaultFontStyleName(const QString &style)
|
|
{
|
|
/* clang-format off */
|
|
// Ordered by commonness, i.e. "Regular" is the most common
|
|
return style == QLatin1String("Regular")
|
|
|| style == QLatin1String("Normal")
|
|
|| style == QLatin1String("Book")
|
|
|| style == QLatin1String("Roman");
|
|
/* clang-format on */
|
|
}
|
|
|
|
void KFontChooserPrivate::slotFamilySelected(const QString &family)
|
|
{
|
|
if (!m_signalsAllowed) {
|
|
return;
|
|
}
|
|
m_signalsAllowed = false;
|
|
|
|
QString currentFamily;
|
|
if (family.isEmpty()) {
|
|
Q_ASSERT(m_ui->familyListWidget->currentItem());
|
|
if (m_ui->familyListWidget->currentItem()) {
|
|
currentFamily = m_qtFamilies[m_ui->familyListWidget->currentItem()->text()];
|
|
}
|
|
} else {
|
|
currentFamily = m_qtFamilies[family];
|
|
}
|
|
|
|
// Get the list of styles available in this family.
|
|
QStringList styles = QFontDatabase::styles(currentFamily);
|
|
if (styles.isEmpty()) {
|
|
// Avoid extraction, it is in kdeqt.po
|
|
styles.append(TR_NOX("Normal", "QFontDatabase"));
|
|
}
|
|
|
|
// Always prepend Regular, Normal, Book or Roman, this way if "m_selectedStyle"
|
|
// in the code below is empty, selecting index 0 should work better
|
|
std::sort(styles.begin(), styles.end(), [](const QString &a, const QString &b) {
|
|
if (isDefaultFontStyleName(a)) {
|
|
return true;
|
|
} else if (isDefaultFontStyleName(b)) {
|
|
return false;
|
|
}
|
|
return false;
|
|
});
|
|
|
|
// Filter style strings and add to the listbox.
|
|
QString pureFamily;
|
|
splitFontString(family, &pureFamily);
|
|
QStringList filteredStyles;
|
|
m_qtStyles.clear();
|
|
m_styleIDs.clear();
|
|
|
|
const QStringList origStyles = styles;
|
|
for (const QString &style : origStyles) {
|
|
// Sometimes the font database will report an invalid style,
|
|
// that falls back back to another when set.
|
|
// Remove such styles, by checking set/get round-trip.
|
|
QFont testFont = QFontDatabase::font(currentFamily, style, 10);
|
|
if (QFontDatabase::styleString(testFont) != style) {
|
|
styles.removeAll(style);
|
|
continue;
|
|
}
|
|
|
|
QString fstyle = tr("%1", "@item Font style").arg(style);
|
|
if (!filteredStyles.contains(fstyle)) {
|
|
filteredStyles.append(fstyle);
|
|
m_qtStyles.insert({fstyle, style});
|
|
m_styleIDs.insert({fstyle, styleIdentifier(testFont)});
|
|
}
|
|
}
|
|
m_ui->styleListWidget->clear();
|
|
m_ui->styleListWidget->addItems(filteredStyles);
|
|
|
|
// Try to set the current style in the listbox to that previous.
|
|
int listPos = filteredStyles.indexOf(m_selectedStyle.isEmpty() ? TR_NOX("Normal", "QFontDatabase") : m_selectedStyle);
|
|
if (listPos < 0) {
|
|
// Make extra effort to have Italic selected when Oblique was chosen,
|
|
// and vice versa, as that is what the user would probably want.
|
|
QString styleIt = tr("Italic", "@item font");
|
|
QString styleOb = tr("Oblique", "@item font");
|
|
for (int i = 0; i < 2; ++i) {
|
|
int pos = m_selectedStyle.indexOf(styleIt);
|
|
if (pos >= 0) {
|
|
QString style = m_selectedStyle;
|
|
style.replace(pos, styleIt.length(), styleOb);
|
|
listPos = filteredStyles.indexOf(style);
|
|
if (listPos >= 0) {
|
|
break;
|
|
}
|
|
}
|
|
qSwap(styleIt, styleOb);
|
|
}
|
|
}
|
|
m_ui->styleListWidget->setCurrentRow(listPos >= 0 ? listPos : 0);
|
|
const QString currentStyle = m_qtStyles[m_ui->styleListWidget->currentItem()->text()];
|
|
|
|
// Recompute the size listbox for this family/style.
|
|
qreal currentSize = setupSizeListBox(currentFamily, currentStyle);
|
|
m_ui->sizeSpinBox->setValue(currentSize);
|
|
|
|
m_selectedFont = QFontDatabase::font(currentFamily, currentStyle, static_cast<int>(currentSize));
|
|
if (QFontDatabase::isSmoothlyScalable(currentFamily, currentStyle) && m_selectedFont.pointSize() == floor(currentSize)) {
|
|
m_selectedFont.setPointSizeF(currentSize);
|
|
}
|
|
Q_EMIT q->fontSelected(m_selectedFont);
|
|
|
|
m_signalsAllowed = true;
|
|
}
|
|
|
|
void KFontChooserPrivate::slotStyleSelected(const QString &style)
|
|
{
|
|
if (!m_signalsAllowed) {
|
|
return;
|
|
}
|
|
m_signalsAllowed = false;
|
|
|
|
const QString currentFamily = m_qtFamilies[m_ui->familyListWidget->currentItem()->text()];
|
|
const QString currentStyle = !style.isEmpty() ? m_qtStyles[style] : m_qtStyles[m_ui->styleListWidget->currentItem()->text()];
|
|
|
|
// Recompute the size listbox for this family/style.
|
|
qreal currentSize = setupSizeListBox(currentFamily, currentStyle);
|
|
m_ui->sizeSpinBox->setValue(currentSize);
|
|
|
|
m_selectedFont = QFontDatabase::font(currentFamily, currentStyle, static_cast<int>(currentSize));
|
|
if (QFontDatabase::isSmoothlyScalable(currentFamily, currentStyle) && m_selectedFont.pointSize() == floor(currentSize)) {
|
|
m_selectedFont.setPointSizeF(currentSize);
|
|
}
|
|
Q_EMIT q->fontSelected(m_selectedFont);
|
|
|
|
if (!style.isEmpty()) {
|
|
m_selectedStyle = currentStyle;
|
|
}
|
|
|
|
m_signalsAllowed = true;
|
|
}
|
|
|
|
void KFontChooserPrivate::slotSizeSelected(const QString &size)
|
|
{
|
|
if (!m_signalsAllowed) {
|
|
return;
|
|
}
|
|
|
|
m_signalsAllowed = false;
|
|
|
|
qreal currentSize = 0.0;
|
|
if (size.isEmpty()) {
|
|
currentSize = QLocale::system().toDouble(m_ui->sizeListWidget->currentItem()->text());
|
|
} else {
|
|
currentSize = QLocale::system().toDouble(size);
|
|
}
|
|
|
|
// Reset the customized size slot in the list if not needed.
|
|
if (m_customSizeRow >= 0 && m_selectedFont.pointSizeF() != currentSize) {
|
|
m_ui->sizeListWidget->item(m_customSizeRow)->setText(m_standardSizeAtCustom);
|
|
m_customSizeRow = -1;
|
|
}
|
|
|
|
m_ui->sizeSpinBox->setValue(currentSize);
|
|
m_selectedFont.setPointSizeF(currentSize);
|
|
Q_EMIT q->fontSelected(m_selectedFont);
|
|
|
|
if (!size.isEmpty()) {
|
|
m_selectedSize = currentSize;
|
|
}
|
|
|
|
m_signalsAllowed = true;
|
|
}
|
|
|
|
void KFontChooserPrivate::slotSizeValue(double dval)
|
|
{
|
|
if (!m_signalsAllowed) {
|
|
return;
|
|
}
|
|
m_signalsAllowed = false;
|
|
|
|
// We compare with qreal, so convert for platforms where qreal != double.
|
|
qreal val = qreal(dval);
|
|
|
|
// Reset current size slot in list if it was customized.
|
|
if (m_customSizeRow >= 0 && m_ui->sizeListWidget->currentRow() == m_customSizeRow) {
|
|
m_ui->sizeListWidget->item(m_customSizeRow)->setText(m_standardSizeAtCustom);
|
|
m_customSizeRow = -1;
|
|
}
|
|
|
|
bool canCustomize = true;
|
|
|
|
const QString family = m_qtFamilies[m_ui->familyListWidget->currentItem()->text()];
|
|
const QString style = m_qtStyles[m_ui->styleListWidget->currentItem()->text()];
|
|
|
|
// For Qt-bad-sizes workaround: skip this block unconditionally
|
|
if (!QFontDatabase::isSmoothlyScalable(family, style)) {
|
|
// Bitmap font, allow only discrete sizes.
|
|
// Determine the nearest in the direction of change.
|
|
canCustomize = false;
|
|
int nrows = m_ui->sizeListWidget->count();
|
|
int row = m_ui->sizeListWidget->currentRow();
|
|
int nrow;
|
|
if (val - m_selectedFont.pointSizeF() > 0) {
|
|
for (nrow = row + 1; nrow < nrows; ++nrow) {
|
|
if (QLocale::system().toDouble(m_ui->sizeListWidget->item(nrow)->text()) >= val) {
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
for (nrow = row - 1; nrow >= 0; --nrow) {
|
|
if (QLocale::system().toDouble(m_ui->sizeListWidget->item(nrow)->text()) <= val) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// Make sure the new row is not out of bounds.
|
|
nrow = nrow < 0 ? 0 : nrow >= nrows ? nrows - 1 : nrow;
|
|
// Get the size from the new row and set the spinbox to that size.
|
|
val = QLocale::system().toDouble(m_ui->sizeListWidget->item(nrow)->text());
|
|
m_ui->sizeSpinBox->setValue(val);
|
|
}
|
|
|
|
// Set the current size in the size listbox.
|
|
int row = nearestSizeRow(val, canCustomize);
|
|
m_ui->sizeListWidget->setCurrentRow(row);
|
|
|
|
m_selectedSize = val;
|
|
m_selectedFont.setPointSizeF(val);
|
|
Q_EMIT q->fontSelected(m_selectedFont);
|
|
|
|
m_signalsAllowed = true;
|
|
}
|
|
|
|
void KFontChooserPrivate::slotFeaturesChanged(const QString &features)
|
|
{
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
|
|
m_selectedFont.clearFeatures();
|
|
|
|
if (features.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
const QStringList rawFeaturesList = features.split(QLatin1Char(','), Qt::SkipEmptyParts);
|
|
for (const QString &feature : rawFeaturesList) {
|
|
auto f = QStringView(feature).trimmed();
|
|
if (f.isEmpty()) {
|
|
continue;
|
|
}
|
|
QList<QStringView> parts = f.split(QStringLiteral("="), Qt::SkipEmptyParts);
|
|
if (parts.length() == 2) {
|
|
const auto tag = QFont::Tag::fromString(parts[0]);
|
|
bool ok = false;
|
|
const int number = parts[1].toInt(&ok);
|
|
if (tag.has_value() && ok) {
|
|
m_selectedFont.setFeature(tag.value(), number);
|
|
}
|
|
} else if (f.size() <= 4) {
|
|
const auto tag = QFont::Tag::fromString(feature);
|
|
if (tag.has_value()) {
|
|
m_selectedFont.setFeature(tag.value(), 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
Q_EMIT q->fontSelected(m_selectedFont);
|
|
#endif
|
|
}
|
|
|
|
void KFontChooserPrivate::displaySample(const QFont &font)
|
|
{
|
|
m_ui->sampleTextEdit->setFont(font);
|
|
}
|
|
|
|
int KFontChooserPrivate::nearestSizeRow(qreal val, bool customize)
|
|
{
|
|
qreal diff = 1000;
|
|
int row = 0;
|
|
for (int r = 0; r < m_ui->sizeListWidget->count(); ++r) {
|
|
qreal cval = QLocale::system().toDouble(m_ui->sizeListWidget->item(r)->text());
|
|
if (qAbs(cval - val) < diff) {
|
|
diff = qAbs(cval - val);
|
|
row = r;
|
|
}
|
|
}
|
|
// For Qt-bad-sizes workaround: ignore value of customize, use true
|
|
if (customize && diff > 0) {
|
|
m_customSizeRow = row;
|
|
m_standardSizeAtCustom = m_ui->sizeListWidget->item(row)->text();
|
|
m_ui->sizeListWidget->item(row)->setText(formatFontSize(val));
|
|
}
|
|
return row;
|
|
}
|
|
|
|
qreal KFontChooserPrivate::fillSizeList(const QList<qreal> &sizes_)
|
|
{
|
|
if (m_ui->sizeListWidget->isHidden()) {
|
|
qCWarning(KWidgetsAddonsLog) << "Trying to fill the font size listwidget, but the widget is hidden.";
|
|
return 0.0;
|
|
}
|
|
|
|
QList<qreal> sizes = sizes_;
|
|
bool canCustomize = false;
|
|
if (sizes.isEmpty()) {
|
|
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};
|
|
for (int i = 0; c[i]; ++i) {
|
|
sizes.append(c[i]);
|
|
}
|
|
// Since sizes were not supplied, this is a vector font,
|
|
// and size slot customization is allowed.
|
|
canCustomize = true;
|
|
}
|
|
|
|
// Insert sizes into the listbox.
|
|
m_ui->sizeListWidget->clear();
|
|
std::sort(sizes.begin(), sizes.end());
|
|
for (qreal size : std::as_const(sizes)) {
|
|
m_ui->sizeListWidget->addItem(formatFontSize(size));
|
|
}
|
|
|
|
// Return the nearest to selected size.
|
|
// If the font is vector, the nearest size is always same as selected,
|
|
// thus size slot customization is allowed.
|
|
// If the font is bitmap, the nearest size need not be same as selected,
|
|
// thus size slot customization is not allowed.
|
|
m_customSizeRow = -1;
|
|
int row = nearestSizeRow(m_selectedSize, canCustomize);
|
|
return QLocale::system().toDouble(m_ui->sizeListWidget->item(row)->text());
|
|
}
|
|
|
|
qreal KFontChooserPrivate::setupSizeListBox(const QString &family, const QString &style)
|
|
{
|
|
QList<qreal> sizes;
|
|
const bool smoothlyScalable = QFontDatabase::isSmoothlyScalable(family, style);
|
|
if (!smoothlyScalable) {
|
|
const QList<int> smoothSizes = QFontDatabase::smoothSizes(family, style);
|
|
for (int size : smoothSizes) {
|
|
sizes.append(size);
|
|
}
|
|
}
|
|
|
|
// Fill the listbox (uses default list of sizes if the given is empty).
|
|
// Collect the best fitting size to selected size, to use if not smooth.
|
|
qreal bestFitSize = fillSizeList(sizes);
|
|
|
|
// Set the best fit size as current in the listbox if available.
|
|
const QList<QListWidgetItem *> selectedSizeList = m_ui->sizeListWidget->findItems(formatFontSize(bestFitSize), Qt::MatchExactly);
|
|
if (!selectedSizeList.isEmpty()) {
|
|
m_ui->sizeListWidget->setCurrentItem(selectedSizeList.first());
|
|
}
|
|
|
|
return bestFitSize;
|
|
}
|
|
|
|
void KFontChooserPrivate::setupDisplay()
|
|
{
|
|
qreal size = m_selectedFont.pointSizeF();
|
|
if (size == -1) {
|
|
size = QFontInfo(m_selectedFont).pointSizeF();
|
|
}
|
|
|
|
// Set font features
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
|
|
const auto tags = m_selectedFont.featureTags();
|
|
QStringList features;
|
|
for (const auto &tag : tags) {
|
|
const QString name = QString::fromUtf8(tag.toString());
|
|
const quint32 value = m_selectedFont.featureValue(tag);
|
|
if (value == 1) {
|
|
features.push_back(name);
|
|
} else {
|
|
features.push_back(QStringLiteral("%1=%2").arg(name, QString::number(value)));
|
|
}
|
|
}
|
|
m_ui->fontFeaturesLineEdit->setText(features.join(QStringLiteral(",")));
|
|
#endif
|
|
|
|
int numEntries;
|
|
int i;
|
|
|
|
// Get the styleID here before familyListWidget->setCurrentRow() is called
|
|
// as it may change the font style
|
|
const QString styleID = styleIdentifier(m_selectedFont);
|
|
|
|
QString family = m_selectedFont.family().toLower();
|
|
// Direct family match.
|
|
numEntries = m_ui->familyListWidget->count();
|
|
for (i = 0; i < numEntries; ++i) {
|
|
if (family == m_qtFamilies[m_ui->familyListWidget->item(i)->text()].toLower()) {
|
|
m_ui->familyListWidget->setCurrentRow(i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 1st family fallback.
|
|
if (i == numEntries) {
|
|
const int bracketPos = family.indexOf(QLatin1Char('['));
|
|
if (bracketPos != -1) {
|
|
family = QStringView(family).left(bracketPos).trimmed().toString();
|
|
for (i = 0; i < numEntries; ++i) {
|
|
if (family == m_qtFamilies[m_ui->familyListWidget->item(i)->text()].toLower()) {
|
|
m_ui->familyListWidget->setCurrentRow(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 2nd family fallback.
|
|
if (i == numEntries) {
|
|
QString fallback = family + QLatin1String(" [");
|
|
for (i = 0; i < numEntries; ++i) {
|
|
if (m_qtFamilies[m_ui->familyListWidget->item(i)->text()].toLower().startsWith(fallback)) {
|
|
m_ui->familyListWidget->setCurrentRow(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 3rd family fallback.
|
|
if (i == numEntries) {
|
|
for (i = 0; i < numEntries; ++i) {
|
|
if (m_qtFamilies[m_ui->familyListWidget->item(i)->text()].toLower().startsWith(family)) {
|
|
m_ui->familyListWidget->setCurrentRow(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Family fallback in case nothing matched. Otherwise, diff doesn't work
|
|
if (i == numEntries) {
|
|
m_ui->familyListWidget->setCurrentRow(0);
|
|
}
|
|
|
|
// By setting the current item in the family box, the available
|
|
// styles and sizes for that family have been collected.
|
|
// Try now to set the current items in the style and size boxes.
|
|
|
|
// Set current style in the listbox.
|
|
numEntries = m_ui->styleListWidget->count();
|
|
for (i = 0; i < numEntries; ++i) {
|
|
if (styleID == m_styleIDs[m_ui->styleListWidget->item(i)->text()]) {
|
|
m_ui->styleListWidget->setCurrentRow(i);
|
|
break;
|
|
}
|
|
}
|
|
if (i == numEntries) {
|
|
// Style not found, fallback.
|
|
m_ui->styleListWidget->setCurrentRow(0);
|
|
}
|
|
|
|
// Set current size in the listbox.
|
|
// If smoothly scalable, allow customizing one of the standard size slots,
|
|
// otherwise just select the nearest available size.
|
|
const QString currentFamily = m_qtFamilies[m_ui->familyListWidget->currentItem()->text()];
|
|
const QString currentStyle = m_qtStyles[m_ui->styleListWidget->currentItem()->text()];
|
|
const bool canCustomize = QFontDatabase::isSmoothlyScalable(currentFamily, currentStyle);
|
|
m_ui->sizeListWidget->setCurrentRow(nearestSizeRow(size, canCustomize));
|
|
|
|
// Set current size in the spinbox.
|
|
m_ui->sizeSpinBox->setValue(QLocale::system().toDouble(m_ui->sizeListWidget->currentItem()->text()));
|
|
}
|
|
|
|
// static
|
|
QStringList KFontChooser::createFontList(uint fontListCriteria)
|
|
{
|
|
QStringList lstSys(QFontDatabase::families());
|
|
|
|
// if we have criteria; then check fonts before adding
|
|
if (fontListCriteria) {
|
|
QStringList lstFonts;
|
|
for (const QString &family : std::as_const(lstSys)) {
|
|
if ((fontListCriteria & FixedWidthFonts) > 0 && !QFontDatabase::isFixedPitch(family)) {
|
|
continue;
|
|
}
|
|
if (((fontListCriteria & (SmoothScalableFonts | ScalableFonts)) == ScalableFonts) && !QFontDatabase::isBitmapScalable(family)) {
|
|
continue;
|
|
}
|
|
if ((fontListCriteria & SmoothScalableFonts) > 0 && !QFontDatabase::isSmoothlyScalable(family)) {
|
|
continue;
|
|
}
|
|
lstFonts.append(family);
|
|
}
|
|
|
|
if ((fontListCriteria & FixedWidthFonts) > 0) {
|
|
// Fallback.. if there are no fixed fonts found, it's probably a
|
|
// bug in the font server or Qt. In this case, just use 'fixed'
|
|
if (lstFonts.isEmpty()) {
|
|
lstFonts.append(QStringLiteral("fixed"));
|
|
}
|
|
}
|
|
|
|
lstSys = lstFonts;
|
|
}
|
|
|
|
lstSys.sort();
|
|
|
|
return lstSys;
|
|
}
|
|
|
|
void KFontChooser::setFontListItems(const QStringList &fontList)
|
|
{
|
|
d->setFamilyBoxItems(fontList);
|
|
}
|
|
|
|
void KFontChooserPrivate::setFamilyBoxItems(const QStringList &fonts)
|
|
{
|
|
m_signalsAllowed = false;
|
|
|
|
m_ui->familyListWidget->clear();
|
|
|
|
m_qtFamilies = translateFontNameList(!fonts.isEmpty() ? fonts : KFontChooser::createFontList(m_usingFixed ? KFontChooser::FixedWidthFonts : 0));
|
|
|
|
QStringList list;
|
|
list.reserve(m_qtFamilies.size());
|
|
|
|
// Generic font names
|
|
const QStringList genericTranslatedNames{
|
|
translateFontName(QStringLiteral("Sans Serif")),
|
|
translateFontName(QStringLiteral("Serif")),
|
|
translateFontName(QStringLiteral("Monospace")),
|
|
};
|
|
|
|
// Add generic family names to the top of the list
|
|
for (const QString &s : genericTranslatedNames) {
|
|
auto nIt = m_qtFamilies.find(s);
|
|
if (nIt != m_qtFamilies.cend()) {
|
|
list.push_back(s);
|
|
}
|
|
}
|
|
|
|
for (auto it = m_qtFamilies.cbegin(); it != m_qtFamilies.cend(); ++it) {
|
|
const QString &name = it->first;
|
|
if (genericTranslatedNames.contains(name)) {
|
|
continue;
|
|
}
|
|
|
|
list.push_back(name);
|
|
}
|
|
|
|
m_ui->familyListWidget->addItems(list);
|
|
m_ui->familyListWidget->setMinimumWidth(minimumListWidth(m_ui->familyListWidget));
|
|
|
|
m_signalsAllowed = true;
|
|
}
|
|
|
|
void KFontChooser::setMinVisibleItems(int visibleItems)
|
|
{
|
|
for (auto *widget : {d->m_ui->familyListWidget, d->m_ui->styleListWidget, d->m_ui->sizeListWidget}) {
|
|
widget->setMinimumHeight(minimumListHeight(widget, visibleItems));
|
|
}
|
|
}
|
|
|
|
// Human-readable style identifiers returned by QFontDatabase::styleString()
|
|
// do not always survive round trip of QFont serialization/deserialization,
|
|
// causing wrong style in the style box to be highlighted when
|
|
// the chooser dialog is opened. This will cause the style to be changed
|
|
// when the dialog is closed and the user did not touch the style box.
|
|
// Hence, construct custom style identifiers sufficient for the purpose.
|
|
QString KFontChooserPrivate::styleIdentifier(const QFont &font)
|
|
{
|
|
const int weight = font.weight();
|
|
QString styleName = font.styleName();
|
|
// If the styleName property is empty and the weight is QFont::Normal, that
|
|
// could mean it's a "Regular"-like style with the styleName part stripped
|
|
// so that subsequent calls to setBold(true) can work properly (i.e. selecting
|
|
// the "Bold" style provided by the font itself) without resorting to font
|
|
// "emboldening" which looks ugly.
|
|
// See also KConfigGroupGui::writeEntryGui().
|
|
if (styleName.isEmpty() && weight == QFont::Normal) {
|
|
const QStringList styles = QFontDatabase::styles(font.family());
|
|
for (const QString &style : styles) {
|
|
if (isDefaultFontStyleName(style)) {
|
|
styleName = style;
|
|
break;
|
|
} else {
|
|
// nothing more we can do
|
|
}
|
|
}
|
|
}
|
|
|
|
const QChar comma(QLatin1Char(','));
|
|
return QString::number(weight) + comma //
|
|
+ QString::number((int)font.style()) + comma //
|
|
+ QString::number(font.stretch()) + comma //
|
|
+ styleName;
|
|
}
|
|
|
|
#include "moc_kfontchooser.cpp"
|