[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kde-core-devel
Subject:    Re: Drop-in replacement for QFontComboBox, better previews
From:       Chusslove Illich <caslav.ilic () gmx ! net>
Date:       2008-05-09 22:34:40
Message-ID: 200805100034.41312.caslav.ilic () gmx ! net
[Download RAW message or body]

[Attachment #2 (multipart/mixed)]


> [: Chusslove Illich :]
> A possibility would also be to backtrack one level, inherit from KComboBox
> instead [...]

...which I just did, and now really like it better than inheriting from
QFontComboBox. After implementing the script resolutions as I've outlined
them in the previous message, almost nothing has been left to reuse of what
QFontComboBox did above QComboBox. The text completion is there too, so that
the user can quickly select a font when having a particular one in mind and
not wanting to browse the list.

Also, I was already able to reuse some of the functionality of KFontChooser,
and more is to come when common i18n is factored out. It seems to me natural
that KFontChooser and KFontComboBox behave as a "team", the latter a
lightweigt counterpart to the former.

I only didn't go for all of QFontComboBox functionality, left out filtering
of fonts by writing systems and font properties, which no KDE app uses.
Instead there is only the possibility to limit fonts to fixed-width, in line
with what KFontChooser allows too.

(And no Qt code is left inside, only its spirit, so no licensing issues.)

-- 
Chusslove Illich (Часлав Илић)

["kfontcombobox.h" (text/x-c++hdr)]

/*  This file is part of the KDE libraries

    Copyright (C) 2008 Chusslove Illich <caslav.ilic@gmx.net>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/
#ifndef KFONTCOMBOBOX_P_H
#define KFONTCOMBOBOX_P_H

#include <kdeui_export.h>

#include <kcombobox.h>

class KFontComboBoxPrivate;

/**
 * @short A lightweight font selection widget.
 *
 * A combobox to select the font from. Lightweight counterpart to KFontChooser,
 * for situations where only the font family should be selected, while the
 * font style and size are handled by other means. Like in KFontChooser,
 * this widget will show the font previews in the unrolled dropdown list.
 *
 * @note The class is similar to QFontComboBox, but more tightly integrated
 * with KDE desktop. Use it instead of QFontComboBox by default in KDE code.
 *
 * @author Chusslove Illich \<caslav.ilic@gmx.net\>
 *
 * @see KFontAction
 * @see KFontChooser
 *
 * @since 4.1
 */
class KDEUI_EXPORT KFontComboBox : public KComboBox
{
    Q_OBJECT

    Q_PROPERTY(QFont currentFont READ currentFont WRITE setCurrentFont)

public:

    /**
     * Constructor.
     *
     * @param parent the parent widget
     */
    explicit KFontComboBox (QWidget *parent = 0);

    /**
     * Toggle selectable fonts to be only those of fixed width or all.
     *
     * @param onlyFixed only fixed width fonts when @p true,
     *                  all fonts when @p false
     */
    void setOnlyFixed (bool onlyFixed);

    /**
     * Destructor.
     */
    virtual ~KFontComboBox ();

    /**
     * The font currently selected from the list.
     *
     * @return the selected font
     */
    QFont currentFont () const;

    /**
     * The recommended size of the widget.
     * Reimplemented to make the recommended width independent
     * of the particular fonts installed.
     *
     * @return recommended size
     */
    virtual QSize sizeHint() const;

public Q_SLOTS:
    /**
     * Set the font to show as selected in the combobox.
     *
     * @param font the new font
     */
    void setCurrentFont (const QFont &font);

Q_SIGNALS:
    /**
     * Emitted when a new font has been selected,
     * either through user input or by setFont().
     *
     * @param font the new font
     */
    void currentFontChanged (const QFont &font);

protected:
    bool event (QEvent *e);

private:

    friend class KFontComboBoxPrivate;
    KFontComboBoxPrivate * const d;

    Q_DISABLE_COPY(KFontComboBox)

    Q_PRIVATE_SLOT(d, void _k_currentFontChanged (int))
};

#endif

["kfontcombobox.cpp" (text/x-c++src)]

/*  This file is part of the KDE libraries

    Copyright (C) 2008 Chusslove Illich <caslav.ilic@gmx.net>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/

#include "kfontcombobox.h"

#include "kdebug.h"
#include "klocale.h"
#include "kcolorscheme.h"
#include "kglobalsettings.h"
#include "kfontchooser.h"
#include "kcompletion.h"

#include <QEvent>
#include <QListView>
#include <QFontDatabase>
#include <QIcon>
#include <QAbstractItemDelegate>
#include <QStringListModel>
#include <QPainter>
#include <QList>
#include <QHash>
#include <QScrollBar>

static QString alphabetSample ()
{
    return i18nc("short",
    // i18n: A shorter version of the alphabet test phrase translated in
    // another message. It is displayed in the dropdown list of font previews
    // (the font selection combo box), so keep it under the length equivalent
    // to 60 or so proportional Latin characters.
                 "The Quick Brown Fox Jumps Over The Lazy Dog");
}

static QString localizedFontFamily (const QString &fontFamily)
{
    // FIXME: Font family translation should be shared with KFontChooser.
    return i18nc("@item Font name", "%1", fontFamily);
}

class KFontFamilyDelegate : public QAbstractItemDelegate
{
    Q_OBJECT
public:
    explicit KFontFamilyDelegate (QObject *parent);

    void paint (QPainter *painter,
                const QStyleOptionViewItem &option,
                const QModelIndex &index) const;

    QSize sizeHint (const QStyleOptionViewItem &option,
                    const QModelIndex &index) const;

    QIcon truetype;
    QIcon bitmap;
    double sizeFactFamily;
    double sizeFactSample;

    QHash<QString, QString> fontFamilyTrMap;
};

KFontFamilyDelegate::KFontFamilyDelegate (QObject *parent)
: QAbstractItemDelegate(parent)
{
    truetype = QIcon(QLatin1String(":/trolltech/styles/commonstyle/images/fonttruetype-16.png"));
    bitmap = QIcon(QLatin1String(":/trolltech/styles/commonstyle/images/fontbitmap-16.png"));

    // Font size factors for family name and text sample in font previes,
    // multiplies normal font size.
    sizeFactFamily = 1.0;
    sizeFactSample = 1.0; // better leave at 1, so that user can relate sizes to default
}

void KFontFamilyDelegate::paint (QPainter *painter,
                                 const QStyleOptionViewItem &option,
                                 const QModelIndex &index) const
{
    QBrush sampleBrush;
    if (option.state & QStyle::State_Selected) {
        painter->save();
        painter->setBrush(option.palette.highlight());
        painter->setPen(Qt::NoPen);
        painter->drawRect(option.rect);
        painter->setPen(QPen(option.palette.highlightedText(), 0));
        sampleBrush = option.palette.highlightedText();
    } else {
        sampleBrush = KColorScheme(QPalette::Normal).foreground(KColorScheme::InactiveText);
    }

    QFont baseFont = KGlobalSettings::generalFont();
    QString trFontFamily = index.data(Qt::DisplayRole).toString();
    QString fontFamily = fontFamilyTrMap[trFontFamily];

    // Writing systems provided by the font.
    QList<QFontDatabase::WritingSystem> availableSystems = QFontDatabase().writingSystems(fontFamily);

    // Intersect font's writing systems with that specified for
    // the language's sample text, to see if the sample can be shown.
    // If the font reports no writing systems, assume it can show the sample.
    bool canShowLanguageSample = true;
    if (availableSystems.count() > 0) {
        canShowLanguageSample = false;
        QString scriptsSpec = i18nc("Numeric IDs of scripts for font previews",
        // i18n: Integer which indicates the script you used in the sample text
        // for font previews in your language. For the possible values, see
        // http://doc.trolltech.com/qfontdatabase.html#WritingSystem-enum
        // If the sample text contains several scripts, their IDs can be given
        // as a comma-separated list (e.g. for Japanese it is "1,27").
                                    "1");
        QStringList scriptStrIds = scriptsSpec.split(',');
        foreach (const QString &scriptStrId, scriptStrIds) {
            bool convOk;
            int ws = scriptStrId.toInt(&convOk);
            if (   convOk && ws > 0 && ws < QFontDatabase::WritingSystemsCount
                && availableSystems.contains(static_cast<QFontDatabase::WritingSystem>(ws))) {
                canShowLanguageSample = true;
                break;
            }
        }
    }

    // Choose and paint an icon according to the font type, scalable or bitmat.
    const QIcon *icon = &bitmap;
    if (QFontDatabase().isSmoothlyScalable(fontFamily)) {
        icon = &truetype;
    }
    QRect r = option.rect;
    icon->paint(painter, r, Qt::AlignLeft|Qt::AlignTop);

    // Claim space taken up by the icon.
    QSize actualSize = icon->actualSize(r.size());
    if (option.direction == Qt::RightToLeft) {
        r.setRight(r.right() - actualSize.width() - 4);
    } else {
        r.setLeft(r.left() + actualSize.width() + 4);
    }

    // Draw the font family.
    QFont oldPainterFont = painter->font();
    QFont familyFont = baseFont;
    familyFont.setPointSize(qRound(familyFont.pointSize() * sizeFactFamily));
    painter->setFont(familyFont);
    painter->drawText(r, Qt::AlignTop|Qt::AlignLeading|Qt::TextSingleLine, trFontFamily);

    // Claim space taken up by the font family name.
    int h = painter->fontMetrics().lineSpacing();
    r.setTop(r.top() + h);

    // Show text sample in user's language if the writing system is supported,
    // otherwise show a collage of generic script samples provided by Qt.
    // If the font does not report what it supports, assume all.
    QString sample;
    if (canShowLanguageSample) {
        sample = alphabetSample();
    } else {
        foreach (QFontDatabase::WritingSystem ws, availableSystems) {
            sample += QFontDatabase::writingSystemSample(ws) + "  ";
            if (sample.length() > 40) { // do not let the sample be too long
                break;
            }
        }
        sample = sample.trimmed();
    }
    QFont sampleFont;
    sampleFont.setFamily(fontFamily);
    sampleFont.setPointSize(qRound(sampleFont.pointSize() * sizeFactSample));
    painter->setFont(sampleFont);
    QPen oldPen = painter->pen();
    painter->setPen(sampleBrush.color());
    painter->drawText(r, Qt::AlignTop|Qt::AlignLeading|Qt::TextSingleLine, sample);
    painter->setFont(oldPainterFont);
    painter->setPen(oldPen);

    if (option.state & QStyle::State_Selected) {
        painter->restore();
    }
}

QSize KFontFamilyDelegate::sizeHint (const QStyleOptionViewItem &option,
                                     const QModelIndex &index) const
{
    Q_UNUSED(option);

    QFont baseFont = KGlobalSettings::generalFont();
    QString trFontFamily = index.data(Qt::DisplayRole).toString();
    QString fontFamily = fontFamilyTrMap[trFontFamily];

    QFont familyFont = baseFont;
    familyFont.setPointSize(qRound(QFontInfo(familyFont).pointSize() * sizeFactFamily));
    QFontMetrics familyMetrics(familyFont);

    QFont sampleFont = baseFont;
    sampleFont.setFamily(fontFamily);
    sampleFont.setPointSize(qRound(QFontInfo(sampleFont).pointSize() * sizeFactSample));
    QFontMetrics sampleMetrics(familyFont);
    QString sample = alphabetSample();

    // Only the hight matters here, the width is mandated by KFontComboBox::event()
    return QSize(qMax(familyMetrics.width(trFontFamily), sampleMetrics.width(sample)),
                 qRound(familyMetrics.lineSpacing() + sampleMetrics.lineSpacing() * 1.2));
}

class KFontComboBoxPrivate
{
public:
    KFontComboBoxPrivate (KFontComboBox *parent);
    void updateDatabase ();
    void updateIndexToFont ();
    void _k_currentFontChanged (int index);

    KFontComboBox *k;
    QFont currentFont;
    bool onlyFixed;
    bool signalsAllowed;
    KFontFamilyDelegate *delegate;
    QStringListModel *model;
};

KFontComboBoxPrivate::KFontComboBoxPrivate (KFontComboBox *parent)
{
    k = parent;
    currentFont = KGlobalSettings::generalFont();
    onlyFixed = false;
    signalsAllowed = true;
}

static bool localeLessThan (const QString &a, const QString &b)
{
    return QString::localeAwareCompare(a, b) < 0;
}

void KFontComboBoxPrivate::updateDatabase ()
{
    QStringList fontFamilies;
    KFontChooser::getFontList(fontFamilies,
                              onlyFixed ? KFontChooser::FixedWidthFonts : 0);

    // Translate font families for the list model.
    // Separate generic families into another list.
    delegate->fontFamilyTrMap.clear();
    QStringList genFontFamilies;
    genFontFamilies.append("Sans Serif");
    genFontFamilies.append("Serif");
    genFontFamilies.append("Monospace");
    QStringList trFontFamilies;
    QStringList trGenFontFamilies;
    foreach (const QString &fontFamily, fontFamilies) {
        QString trFontFamily = localizedFontFamily(fontFamily);
        delegate->fontFamilyTrMap.insert(trFontFamily, fontFamily);
        if (!genFontFamilies.contains(fontFamily)) {
            trFontFamilies.append(trFontFamily);
        } else {
            trGenFontFamilies.append(trFontFamily);
        }
    }

    // Add found generic font families in order as specified above.
    QStringList trGenFontFamiliesPresent;
    foreach (const QString &genFontFamily, genFontFamilies) {
        QString trGenFontFamily = localizedFontFamily(genFontFamily);
        if (delegate->fontFamilyTrMap.contains(trGenFontFamily)) {
            trGenFontFamiliesPresent.append(trGenFontFamily);
        }
    }

    // Sort real font families alphabetically.
    qSort(trFontFamilies.begin(), trFontFamilies.end(), localeLessThan);

    // Add families to the list model and completion, with generics at the top.
    trFontFamilies = trGenFontFamiliesPresent + trFontFamilies;
    model->setStringList(trFontFamilies);
    KCompletion *completion = k->completionObject();
    if (completion) {
        completion->insertItems(trFontFamilies);
        completion->setIgnoreCase(true);
    }
}

void KFontComboBoxPrivate::updateIndexToFont ()
{
    // QFontInfo necessary to return the family with proper casing.
    QString selectedFontFamily = QFontInfo(currentFont).family();
    QString trSelectedFontFamily = localizedFontFamily(selectedFontFamily);
    QStringList trFontFamilies = model->stringList();
    if (!trFontFamilies.count()) {
        return;
    }

    // Match the font's family with an item in the list.
    int index = 0;
    foreach (const QString &trFontFamily, trFontFamilies) {
        if (trSelectedFontFamily == trFontFamily) {
            break;
        }
        ++index;
    }
    if (index == trFontFamilies.count()) {
        // If no family matched, change font to first on the list.
        index = 0;
        currentFont = QFont(delegate->fontFamilyTrMap[trFontFamilies[0]]);
        emit k->currentFontChanged(currentFont);
    }

    // Set the new list item.
    signalsAllowed = false;
    k->setCurrentIndex(index);
    signalsAllowed = true;
}

void KFontComboBoxPrivate::_k_currentFontChanged (int index)
{
    if (!signalsAllowed) {
        return;
    }

    QString trFontFamily = k->itemText(index);
    QString fontFamily = delegate->fontFamilyTrMap[trFontFamily];
    if (!fontFamily.isEmpty()) {
        currentFont = QFont(fontFamily);
        emit k->currentFontChanged(currentFont);
    } else {
        // Unknown font family given. Just remove from the list.
        // This should not happen, as adding arbitrary font names is prevented.
        QStringList lst = model->stringList();
        lst.removeAll(trFontFamily);
        model->setStringList(lst);
    }
}

KFontComboBox::KFontComboBox (QWidget *parent)
: KComboBox(true, parent), d(new KFontComboBoxPrivate(this))
{
    // Inputing arbitrary font names does not make sense.
    setInsertPolicy(QComboBox::NoInsert);

    // Special list item painter showing font previews and its list model.
    d->delegate = new KFontFamilyDelegate(this);
    setItemDelegate(d->delegate);
    d->model = new QStringListModel(this);
    setModel(d->model);

    // Set current font when a new family has been chosen in the combo.
    connect(this, SIGNAL(currentIndexChanged(int)),
            this, SLOT(_k_currentFontChanged(int)));

    // Initialize font selection and list of available fonts.
    d->updateDatabase();
    d->updateIndexToFont();
}

KFontComboBox::~KFontComboBox ()
{
    delete d;
}

void KFontComboBox::setOnlyFixed (bool onlyFixed)
{
    if (onlyFixed != d->onlyFixed) {
        d->onlyFixed = onlyFixed;
        d->updateDatabase();
    }
}

QFont KFontComboBox::currentFont () const
{
    return d->currentFont;
}

void KFontComboBox::setCurrentFont (const QFont &font)
{
    if (font != d->currentFont) {
        d->currentFont = font;
        emit currentFontChanged(d->currentFont);
        d->updateIndexToFont();
    }
}

bool KFontComboBox::event (QEvent *e)
{
    if (e->type() == QEvent::Resize) {
        QListView *lview = qobject_cast<QListView*>(view());
        if (lview) {
            QString sample = alphabetSample();
            // Limit text sample length to avoid too wide list view.
            if (sample.length() > 60) {
                sample = sample.left(57) + "...";
            }
            QFont baseFont = KGlobalSettings::generalFont();
            int widgetWidth = width();
            int sampleWidth = QFontMetrics(baseFont).width(sample);
            sampleWidth = qRound(sampleWidth * 1.1); // extra for wider fonts
            int iconWidth = d->delegate->truetype.actualSize(size()).width();
            int vsbarWidth = 0;
            if (lview->verticalScrollBar()) {
                // The height is the apparent width for the vertical scrollbar.
                vsbarWidth = lview->verticalScrollBar()->height();
            }
            lview->window()->setFixedWidth(  qMax(widgetWidth, sampleWidth)
                                           + iconWidth + vsbarWidth);
        }
    }
    return KComboBox::event(e);
}

QSize KFontComboBox::sizeHint() const
{
    QSize sz = KComboBox::sizeHint();
    QFontMetrics fm(KGlobalSettings::generalFont());
    sz.setWidth(fm.width("m") * 14);
    return sz;
}

#include "kfontcombobox.moc"
#include "moc_kfontcombobox.moc"


["signature.asc" (application/pgp-signature)]

[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic