File indexing completed on 2024-05-12 15:42:39

0001 /*
0002  *  SPDX-FileCopyrightText: 2020 Jonah BrĂ¼chert <jbb@kaidan.im>
0003  *  SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
0004  *
0005  *  SPDX-License-Identifier: LGPL-2.0-or-later
0006  */
0007 
0008 #include "units.h"
0009 
0010 #include <QFont>
0011 #include <QFontMetrics>
0012 #include <QGuiApplication>
0013 #include <QQmlComponent>
0014 #include <QQmlEngine>
0015 #include <QStyleHints>
0016 
0017 #include <chrono>
0018 #include <cmath>
0019 
0020 #include "loggingcategory.h"
0021 
0022 namespace Kirigami {
0023 using clock = std::chrono::steady_clock;
0024 
0025 const clock::duration rateLimit = std::chrono::seconds(1);
0026 
0027 /* Print a deprecation warning that is rate limited to only display once in
0028  * every time period as determined by rateLimit. We keep track of how often this
0029  * is called and display that if it is larger than 0.
0030  *
0031  * This is done to prevent flooding the logs with "X is deprecated" messages
0032  * that are all the same and don't provide any new information after the first.
0033  */
0034 void rateLimitWarning(const char *method, const char *since, const char *message)
0035 {
0036     static QMap<QString, QPair<clock::time_point, int>> messages;
0037 
0038     auto methodString = QString::fromUtf8(method);
0039 
0040     if (!messages.contains(methodString)) {
0041         messages.insert(methodString, qMakePair(clock::time_point{}, 0));
0042     }
0043 
0044     auto entry = messages.value(methodString);
0045     if (clock::now() - entry.first < rateLimit) {
0046         messages[methodString].second += 1;
0047         return;
0048     }
0049 
0050     qCWarning(KirigamiLog).nospace() << method << " is deprecated (since " << since << "): " << message;
0051 
0052     if (entry.second > 0) {
0053         qCWarning(KirigamiLog) << "Previous message repeats" << entry.second << "times.";
0054     }
0055 
0056     messages[methodString] = qMakePair(clock::now(), 0);
0057 }
0058 
0059 class UnitsPrivate
0060 {
0061     Q_DISABLE_COPY(UnitsPrivate)
0062 
0063 public:
0064     explicit UnitsPrivate(Units *units)
0065 #if KIRIGAMI2_BUILD_DEPRECATED_SINCE(5, 86)
0066         : qmlFontMetrics(nullptr)
0067 #endif
0068         // Cache font so we don't have to go through QVariant and property every time
0069         , fontMetrics(QFontMetricsF(QGuiApplication::font()))
0070         , gridUnit(fontMetrics.height())
0071         , smallSpacing(std::floor(gridUnit / 4))
0072         , mediumSpacing(std::round(smallSpacing * 1.5))
0073         , largeSpacing(smallSpacing * 2)
0074         , veryLongDuration(400)
0075         , longDuration(200)
0076         , shortDuration(100)
0077         , veryShortDuration(50)
0078         , humanMoment(2000)
0079         , toolTipDelay(700)
0080 #if KIRIGAMI2_BUILD_DEPRECATED_SINCE(5, 86)
0081         , wheelScrollLines(QGuiApplication::styleHints()->wheelScrollLines())
0082 #endif
0083         , iconSizes(new IconSizes(units))
0084     {
0085     }
0086 
0087     // Only stored for QML API compatiblity
0088     // TODO KF6 drop
0089 #if KIRIGAMI2_BUILD_DEPRECATED_SINCE(5, 86)
0090     std::unique_ptr<QObject> qmlFontMetrics = nullptr;
0091 #endif
0092 
0093     // Font metrics used for Units.
0094     // TextMetrics uses QFontMetricsF internally, so this should do the same
0095     QFontMetricsF fontMetrics;
0096 
0097     // units
0098     int gridUnit;
0099     int smallSpacing;
0100     int mediumSpacing;
0101     int largeSpacing;
0102 
0103     // durations
0104     int veryLongDuration;
0105     int longDuration;
0106     int shortDuration;
0107     int veryShortDuration;
0108     int humanMoment;
0109     int toolTipDelay;
0110 
0111 #if KIRIGAMI2_BUILD_DEPRECATED_SINCE(5, 86)
0112     int wheelScrollLines;
0113 #endif
0114 
0115     IconSizes *const iconSizes;
0116 
0117     // To prevent overriding custom set units if the font changes
0118     bool customUnitsSet = false;
0119     bool customWheelScrollLinesSet = false;
0120 
0121 #if KIRIGAMI2_BUILD_DEPRECATED_SINCE(5, 86)
0122     std::unique_ptr<QObject> createQmlFontMetrics(QQmlEngine *engine)
0123     {
0124         QQmlComponent component(engine);
0125         component.setData(QByteArrayLiteral(
0126             "import QtQuick 2.14\n"
0127             "import org.kde.kirigami 2.0\n"
0128             "FontMetrics {\n"
0129             "   function roundedIconSize(size) {\n"
0130             R"(     console.warn("Units.fontMetrics.roundedIconSize is deprecated, use Units.iconSizes.roundedIconSize instead.");)"
0131             "       return Units.iconSizes.roundedIconSize(size)\n"
0132             "   }\n"
0133             "}\n"
0134         ), QUrl(QStringLiteral("units.cpp")));
0135 
0136         return std::unique_ptr<QObject>(component.create());
0137     }
0138 #endif
0139 };
0140 
0141 Units::~Units() = default;
0142 
0143 Units::Units(QObject *parent)
0144     : QObject(parent)
0145     , d(std::make_unique<UnitsPrivate>(this))
0146 {
0147     connect(QGuiApplication::styleHints(), &QStyleHints::wheelScrollLinesChanged, this, [this](int scrollLines) {
0148         if (d->customWheelScrollLinesSet) {
0149             return;
0150         }
0151 
0152         setWheelScrollLines(scrollLines);
0153     });
0154     connect(qGuiApp, &QGuiApplication::fontChanged, this, [this](const QFont &font) {
0155         d->fontMetrics = QFontMetricsF(font);
0156 
0157         if (d->customUnitsSet) {
0158             return;
0159         }
0160 
0161         d->gridUnit = d->fontMetrics.height();
0162         Q_EMIT gridUnitChanged();
0163         d->smallSpacing = std::floor(d->gridUnit / 4);
0164         Q_EMIT smallSpacingChanged();
0165         d->mediumSpacing = std::round(d->smallSpacing * 1.5);
0166         Q_EMIT mediumSpacingChanged();
0167         d->largeSpacing = d->smallSpacing * 2;
0168         Q_EMIT largeSpacingChanged();
0169         Q_EMIT d->iconSizes->sizeForLabelsChanged();
0170     });
0171 }
0172 
0173 qreal Units::devicePixelRatio() const
0174 {
0175     rateLimitWarning("Units.devicePixelRatio", "5.86", "This returns 1 when using Qt HiDPI scaling.");
0176     const int pixelSize = QGuiApplication::font().pixelSize();
0177     const qreal pointSize = QGuiApplication::font().pointSize();
0178 
0179     return std::fmax(1, (pixelSize * 0.75 / pointSize));
0180 }
0181 
0182 int Units::gridUnit() const
0183 {
0184     return d->gridUnit;
0185 }
0186 
0187 void Kirigami::Units::setGridUnit(int size)
0188 {
0189     if (d->gridUnit == size) {
0190         return;
0191     }
0192 
0193     d->gridUnit = size;
0194     d->customUnitsSet = true;
0195     Q_EMIT gridUnitChanged();
0196 }
0197 
0198 int Units::smallSpacing() const
0199 {
0200     return d->smallSpacing;
0201 }
0202 
0203 void Kirigami::Units::setSmallSpacing(int size)
0204 {
0205     if (d->smallSpacing == size) {
0206         return;
0207     }
0208 
0209     d->smallSpacing = size;
0210     d->customUnitsSet = true;
0211     Q_EMIT smallSpacingChanged();
0212 }
0213 
0214 int Units::mediumSpacing() const
0215 {
0216     return d->mediumSpacing;
0217 }
0218 
0219 void Kirigami::Units::setMediumSpacing(int size)
0220 {
0221     if (d->mediumSpacing == size) {
0222         return;
0223     }
0224 
0225     d->mediumSpacing = size;
0226     d->customUnitsSet = true;
0227     Q_EMIT mediumSpacingChanged();
0228 }
0229 
0230 int Units::largeSpacing() const
0231 {
0232     return d->largeSpacing;
0233 }
0234 
0235 void Kirigami::Units::setLargeSpacing(int size)
0236 {
0237     if (d->largeSpacing) {
0238         return;
0239     }
0240 
0241     d->largeSpacing = size;
0242     d->customUnitsSet = true;
0243     Q_EMIT largeSpacingChanged();
0244 }
0245 
0246 int Units::veryLongDuration() const
0247 {
0248     return d->veryLongDuration;
0249 }
0250 
0251 void Units::setVeryLongDuration(int duration)
0252 {
0253     if (d->veryLongDuration == duration) {
0254         return;
0255     }
0256 
0257     d->veryLongDuration = duration;
0258     Q_EMIT veryLongDurationChanged();
0259 }
0260 
0261 int Units::longDuration() const
0262 {
0263     return d->longDuration;
0264 }
0265 
0266 void Units::setLongDuration(int duration)
0267 {
0268     if (d->longDuration == duration) {
0269         return;
0270     }
0271 
0272     d->longDuration = duration;
0273     Q_EMIT longDurationChanged();
0274 }
0275 
0276 int Units::shortDuration() const
0277 {
0278     return d->shortDuration;
0279 }
0280 
0281 void Units::setShortDuration(int duration)
0282 {
0283     if (d->shortDuration == duration) {
0284         return;
0285     }
0286 
0287     d->shortDuration = duration;
0288     Q_EMIT shortDurationChanged();
0289 }
0290 
0291 int Units::veryShortDuration() const
0292 {
0293     return d->veryShortDuration;
0294 }
0295 
0296 void Units::setVeryShortDuration(int duration)
0297 {
0298     if (d->veryShortDuration == duration) {
0299         return;
0300     }
0301 
0302     d->veryShortDuration = duration;
0303     Q_EMIT veryShortDurationChanged();
0304 }
0305 
0306 int Units::humanMoment() const
0307 {
0308     return d->humanMoment;
0309 }
0310 
0311 void Units::setHumanMoment(int duration)
0312 {
0313     if (d->humanMoment == duration) {
0314         return;
0315     }
0316 
0317     d->humanMoment = duration;
0318     Q_EMIT humanMomentChanged();
0319 }
0320 
0321 int Units::toolTipDelay() const
0322 {
0323     return d->toolTipDelay;
0324 }
0325 
0326 void Units::setToolTipDelay(int delay)
0327 {
0328     if (d->toolTipDelay == delay) {
0329         return;
0330     }
0331 
0332     d->toolTipDelay = delay;
0333     Q_EMIT toolTipDelayChanged();
0334 }
0335 
0336 int Units::maximumInteger() const
0337 {
0338     return std::numeric_limits<int>::max();
0339 }
0340 
0341 #if KIRIGAMI2_BUILD_DEPRECATED_SINCE(5, 86)
0342 int Units::wheelScrollLines() const
0343 {
0344     rateLimitWarning("Units.wheelScrollLines", "5.86", "Use Qt.styleHints.wheelScrollLines instead");
0345     return d->wheelScrollLines;
0346 }
0347 
0348 void Units::setWheelScrollLines(int lines)
0349 {
0350     if (d->wheelScrollLines == lines) {
0351         return;
0352     }
0353 
0354     d->wheelScrollLines = lines;
0355     d->customWheelScrollLinesSet = true;
0356     Q_EMIT wheelScrollLinesChanged();
0357 }
0358 #endif
0359 
0360 IconSizes *Units::iconSizes() const
0361 {
0362     return d->iconSizes;
0363 }
0364 
0365 #if KIRIGAMI2_BUILD_DEPRECATED_SINCE(5, 86)
0366 QObject *Units::fontMetrics() const
0367 {
0368     rateLimitWarning("Units.fontMetrics", "5.86", "Create your own FontMetrics object instead.");
0369     if (!d->qmlFontMetrics) {
0370         d->qmlFontMetrics = d->createQmlFontMetrics(qmlEngine(this));
0371     }
0372     return d->qmlFontMetrics.get();
0373 }
0374 #endif
0375 
0376 IconSizes::IconSizes(Units *units)
0377     : QObject(units)
0378     , m_units(units)
0379 {
0380 }
0381 
0382 int IconSizes::roundedIconSize(int size) const
0383 {
0384     if (size < 16) {
0385         return size;
0386     }
0387 
0388     if (size < 22) {
0389         return 16;
0390     }
0391 
0392     if (size < 32) {
0393         return 22;
0394     }
0395 
0396     if (size < 48) {
0397         return 32;
0398     }
0399 
0400     if (size < 64) {
0401         return 48;
0402     }
0403 
0404     return size;
0405 }
0406 
0407 int IconSizes::sizeForLabels() const
0408 {
0409     // gridUnit is the height of textMetrics
0410     return roundedIconSize(m_units->d->fontMetrics.height());
0411 }
0412 
0413 int IconSizes::small() const
0414 {
0415     return 16;
0416 }
0417 
0418 int IconSizes::smallMedium() const
0419 {
0420     return 22;
0421 }
0422 
0423 int IconSizes::medium() const
0424 {
0425     return 32;
0426 }
0427 
0428 int IconSizes::large() const
0429 {
0430     return 48;
0431 }
0432 
0433 int IconSizes::huge() const
0434 {
0435     return 64;
0436 }
0437 
0438 int IconSizes::enormous() const
0439 {
0440     return 128;
0441 }
0442 
0443 }
0444 
0445 #include "moc_units.cpp"