File indexing completed on 2021-10-26 16:39:37

0001 /*
0002     SPDX-FileCopyrightText: 2013 Marco Martin <mart@kde.org>
0003     SPDX-FileCopyrightText: 2014 Sebastian K├╝gler <sebas@kde.org>
0004     SPDX-FileCopyrightText: 2014 David Edmundson <davidedmunsdon@kde.org>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "units.h"
0010 
0011 #include <QDebug>
0012 #include <QFontMetrics>
0013 #include <QGuiApplication>
0014 #include <QQuickItem>
0015 #include <QQuickWindow>
0016 #include <QScreen>
0017 #include <QtGlobal>
0018 #include <cmath>
0019 
0020 #include <KIconLoader>
0021 
0022 const int defaultLongDuration = 200;
0023 
0024 SharedAppFilter::SharedAppFilter(QObject *parent)
0025     : QObject(parent)
0026 {
0027     QCoreApplication::instance()->installEventFilter(this);
0028 }
0029 
0030 SharedAppFilter::~SharedAppFilter()
0031 {
0032 }
0033 
0034 bool SharedAppFilter::eventFilter(QObject *watched, QEvent *event)
0035 {
0036     if (watched == QCoreApplication::instance()) {
0037         if (event->type() == QEvent::ApplicationFontChange) {
0038             Q_EMIT fontChanged();
0039         }
0040     }
0041     return QObject::eventFilter(watched, event);
0042 }
0043 
0044 SharedAppFilter *Units::s_sharedAppFilter = nullptr;
0045 
0046 Units::Units(QObject *parent)
0047     : QObject(parent)
0048     , m_gridUnit(-1)
0049     , m_devicePixelRatio(-1)
0050     , m_smallSpacing(-1)
0051     , m_largeSpacing(-1)
0052     , m_longDuration(defaultLongDuration) // default base value for animations
0053 {
0054     if (!s_sharedAppFilter) {
0055         s_sharedAppFilter = new SharedAppFilter();
0056     }
0057 
0058     m_iconSizes = new QQmlPropertyMap(this);
0059     m_iconSizeHints = new QQmlPropertyMap(this);
0060     updateDevicePixelRatio(); // also updates icon sizes
0061     updateSpacing(); // updates gridUnit and *Spacing properties
0062 
0063     connect(KIconLoader::global(), &KIconLoader::iconLoaderSettingsChanged, this, &Units::iconLoaderSettingsChanged);
0064     QObject::connect(s_sharedAppFilter, &SharedAppFilter::fontChanged, this, &Units::updateSpacing);
0065 
0066     m_animationSpeedWatcher = KConfigWatcher::create(KSharedConfig::openConfig());
0067     connect(m_animationSpeedWatcher.data(), &KConfigWatcher::configChanged, this, [this](const KConfigGroup &group, const QByteArrayList &names) {
0068         if (group.name() == QLatin1String("KDE") && names.contains(QByteArrayLiteral("AnimationDurationFactor"))) {
0069             updateAnimationSpeed();
0070         }
0071     });
0072     updateAnimationSpeed();
0073 }
0074 
0075 Units::~Units()
0076 {
0077 }
0078 
0079 Units &Units::instance()
0080 {
0081     static Units units;
0082     return units;
0083 }
0084 
0085 void Units::updateAnimationSpeed()
0086 {
0087     KConfigGroup generalCfg = KConfigGroup(KSharedConfig::openConfig(), QStringLiteral("KDE"));
0088     const qreal animationSpeedModifier = qMax(0.0, generalCfg.readEntry("AnimationDurationFactor", 1.0));
0089 
0090     // Read the old longDuration value for compatibility
0091     KConfigGroup cfg = KConfigGroup(KSharedConfig::openConfig(QStringLiteral("plasmarc")), QStringLiteral("Units"));
0092     int longDuration = cfg.readEntry("longDuration", defaultLongDuration);
0093 
0094     longDuration = qRound(longDuration * animationSpeedModifier);
0095 
0096     // Animators with a duration of 0 do not fire reliably
0097     // see Bug 357532 and QTBUG-39766
0098     longDuration = qMax(1, longDuration);
0099 
0100     if (longDuration != m_longDuration) {
0101         m_longDuration = longDuration;
0102         Q_EMIT durationChanged();
0103     }
0104 }
0105 
0106 void Units::iconLoaderSettingsChanged()
0107 {
0108     m_iconSizes->insert(QStringLiteral("desktop"), devicePixelIconSize(KIconLoader::global()->currentSize(KIconLoader::Desktop)));
0109 
0110     m_iconSizes->insert(QStringLiteral("tiny"), devicePixelIconSize(KIconLoader::SizeSmall) / 2);
0111     m_iconSizes->insert(QStringLiteral("small"), devicePixelIconSize(KIconLoader::SizeSmall));
0112     m_iconSizes->insert(QStringLiteral("smallMedium"), devicePixelIconSize(KIconLoader::SizeSmallMedium));
0113     m_iconSizes->insert(QStringLiteral("medium"), devicePixelIconSize(KIconLoader::SizeMedium));
0114     m_iconSizes->insert(QStringLiteral("large"), devicePixelIconSize(KIconLoader::SizeLarge));
0115     m_iconSizes->insert(QStringLiteral("huge"), devicePixelIconSize(KIconLoader::SizeHuge));
0116     m_iconSizes->insert(QStringLiteral("enormous"), devicePixelIconSize(KIconLoader::SizeEnormous));
0117     // gridUnit is always the font height here
0118     m_iconSizes->insert(QStringLiteral("sizeForLabels"), devicePixelIconSize(roundToIconSize(QFontMetrics(QGuiApplication::font()).height())));
0119 
0120     m_iconSizeHints->insert(QStringLiteral("panel"), devicePixelIconSize(KIconLoader::global()->currentSize(KIconLoader::Panel)));
0121     m_iconSizeHints->insert(QStringLiteral("desktop"), devicePixelIconSize(KIconLoader::global()->currentSize(KIconLoader::Desktop)));
0122 
0123     Q_EMIT iconSizesChanged();
0124     Q_EMIT iconSizeHintsChanged();
0125 }
0126 
0127 QQmlPropertyMap *Units::iconSizes() const
0128 {
0129     return m_iconSizes;
0130 }
0131 
0132 QQmlPropertyMap *Units::iconSizeHints() const
0133 {
0134     return m_iconSizeHints;
0135 }
0136 
0137 int Units::roundToIconSize(int size)
0138 {
0139     // If not using Qt scaling (e.g. on X11 by default), manually scale all sizes
0140     // according to the DPR because Qt hasn't done it for us. Otherwise all
0141     // returned sizes are too small and icons are only the right size by pure
0142     // chance for the larger sizes due to their large differences in size
0143     qreal multiplier = 1.0;
0144     QScreen *primary = QGuiApplication::primaryScreen();
0145     if (primary) {
0146         // Note that when using Qt scaling, the multiplier will always be 1
0147         // because Qt will scale everything for us. This is good and intentional.
0148         multiplier = bestIconScaleForDevicePixelRatio((qreal)primary->logicalDotsPerInchX() / (qreal)96);
0149     }
0150 
0151     if (size <= 0) {
0152         return 0;
0153 
0154     } else if (size < KIconLoader::SizeSmall * multiplier) {
0155         return qRound(KIconLoader::SizeSmall * multiplier);
0156 
0157     } else if (size < KIconLoader::SizeSmallMedium * multiplier) {
0158         return qRound(KIconLoader::SizeSmall * multiplier);
0159 
0160     } else if (size < KIconLoader::SizeMedium * multiplier) {
0161         return qRound(KIconLoader::SizeSmallMedium * multiplier);
0162 
0163     } else if (size < KIconLoader::SizeLarge * multiplier) {
0164         return qRound(KIconLoader::SizeMedium * multiplier);
0165 
0166     } else if (size < KIconLoader::SizeHuge * multiplier) {
0167         return qRound(KIconLoader::SizeLarge * multiplier);
0168 
0169     } else if (size < KIconLoader::SizeEnormous * multiplier) {
0170         return qRound(KIconLoader::SizeHuge * multiplier);
0171 
0172     } else {
0173         return size;
0174     }
0175 }
0176 
0177 qreal Units::bestIconScaleForDevicePixelRatio(const qreal ratio)
0178 {
0179     if (ratio < 1.5) {
0180         return 1;
0181     } else if (ratio < 2.0) {
0182         return 1.5;
0183     } else if (ratio < 2.5) {
0184         return 2.0;
0185     } else if (ratio < 3.0) {
0186         return 2.5;
0187     } else if (ratio < 3.5) {
0188         return 3.0;
0189     } else {
0190         return ratio;
0191     }
0192 }
0193 int Units::devicePixelIconSize(const int size) const
0194 {
0195     /* in kiconloader.h
0196     enum StdSizes {
0197         SizeSmall=16,
0198         SizeSmallMedium=22,
0199         SizeMedium=32,
0200         SizeLarge=48,
0201         SizeHuge=64,
0202         SizeEnormous=128
0203     };
0204     */
0205     // Scale the icon sizes up using the devicePixelRatio
0206     // This function returns the next stepping icon size
0207     // and multiplies the global settings with the dpi ratio.
0208     const qreal ratio = devicePixelRatio();
0209 
0210     return size * bestIconScaleForDevicePixelRatio(ratio);
0211 }
0212 
0213 qreal Units::devicePixelRatio() const
0214 {
0215     return m_devicePixelRatio;
0216 }
0217 
0218 void Units::updateDevicePixelRatio()
0219 {
0220     // Using QGuiApplication::devicePixelRatio() gives too coarse values,
0221     // i.e. it directly jumps from 1.0 to 2.0. We want tighter control on
0222     // sizing, so we compute the exact ratio and use that.
0223     // TODO: make it possible to adapt to the dpi for the current screen dpi
0224     //  instead of assuming that all of them use the same dpi which applies for
0225     //  X11 but not for other systems.
0226     QScreen *primary = QGuiApplication::primaryScreen();
0227     if (!primary) {
0228         return;
0229     }
0230     const qreal dpi = primary->logicalDotsPerInchX();
0231     // Usual "default" is 96 dpi
0232     // that magic ratio follows the definition of "device independent pixel" by Microsoft
0233     m_devicePixelRatio = (qreal)dpi / (qreal)96;
0234     iconLoaderSettingsChanged();
0235     Q_EMIT devicePixelRatioChanged();
0236 }
0237 
0238 int Units::gridUnit() const
0239 {
0240     return m_gridUnit;
0241 }
0242 
0243 int Units::smallSpacing() const
0244 {
0245     return m_smallSpacing;
0246 }
0247 
0248 int Units::largeSpacing() const
0249 {
0250     return m_largeSpacing;
0251 }
0252 
0253 void Units::updateSpacing()
0254 {
0255     int gridUnit = QFontMetrics(QGuiApplication::font()).boundingRect(QStringLiteral("M")).height();
0256 
0257     if (gridUnit % 2 != 0) {
0258         gridUnit++;
0259     }
0260     if (gridUnit != m_gridUnit) {
0261         m_gridUnit = gridUnit;
0262         Q_EMIT gridUnitChanged();
0263     }
0264 
0265     if (gridUnit != m_largeSpacing) {
0266         m_smallSpacing = qMax(2, (int)(gridUnit / 4)); // 1/4 of gridUnit, at least 2
0267         m_largeSpacing = gridUnit; // msize.height
0268         Q_EMIT spacingChanged();
0269     }
0270 }
0271 
0272 int Units::longDuration() const
0273 {
0274     return m_longDuration;
0275 }
0276 
0277 int Units::shortDuration() const
0278 {
0279     return qMax(1, qRound(m_longDuration * 0.5));
0280 }
0281 
0282 int Units::veryShortDuration() const
0283 {
0284     return qRound(m_longDuration * 0.25);
0285 }
0286 
0287 int Units::veryLongDuration() const
0288 {
0289     return m_longDuration * 2;
0290 }
0291 
0292 int Units::humanMoment() const
0293 {
0294     return 2000;
0295 }
0296 
0297 #include "moc_units.cpp"