File indexing completed on 2024-04-14 04:36:16

0001 /* This file is part of the KDE project
0002    Copyright (C) 2010-2017 Jarosław Staniek <staniek@kde.org>
0003 
0004    This library is free software; you can redistribute it and/or
0005    modify it under the terms of the GNU Library General Public
0006    License as published by the Free Software Foundation; either
0007    version 2 of the License, or (at your option) any later version.
0008 
0009    This library is distributed in the hope that it will be useful,
0010    but WITHOUT ANY WARRANTY; without even the implied warranty of
0011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0012    Library General Public License for more details.
0013 
0014    You should have received a copy of the GNU Library General Public License
0015    along with this library; see the file COPYING.LIB.  If not, write to
0016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0017  * Boston, MA 02110-1301, USA.
0018 */
0019 
0020 #include "KPropertyUtils_p.h"
0021 #include "KPropertyEditorView.h"
0022 #include "KProperty.h"
0023 
0024 #include <config-kproperty.h>
0025 #if defined KPropertyCore_EXPORTS || defined KPropertyWidgets_EXPORTS
0026 #include "kproperty_debug.h"
0027 #else
0028 # define kprDebug qDebug
0029 # define kprWarning qWarning
0030 # define kprCritical qCritical
0031 #endif
0032 
0033 #include <QApplication>
0034 #include <QDebug>
0035 #include <QDir>
0036 #include <QFileInfo>
0037 #include <QPainter>
0038 #include <QRegularExpression>
0039 #include <QResource>
0040 #include <QStandardPaths>
0041 
0042 #ifdef KPROPERTY_KF
0043 #include <KConfigGroup>
0044 #include <KSharedConfig>
0045 #include <KMessageBox>
0046 #else
0047 #include <QMessageBox>
0048 #endif
0049 
0050 #ifdef QT_GUI_LIB
0051 #include <QIcon>
0052 #endif
0053 
0054 #include <algorithm>
0055 
0056 class QColor;
0057 class QWidget;
0058 
0059 namespace KPropertyUtilsPrivate
0060 {
0061 
0062 void showMessageBox(QWidget *parent, const QString &errorMessage, const QString &detailedErrorMessage)
0063 {
0064     if (detailedErrorMessage.isEmpty()) {
0065 #ifdef KPROPERTY_KF
0066         KMessageBox::error(parent, errorMessage);
0067 #else
0068         QMessageBox::warning(parent, QString(), errorMessage);
0069 #endif
0070     } else {
0071 #ifdef KPROPERTY_KF
0072         KMessageBox::detailedError(parent, errorMessage, detailedErrorMessage);
0073 #else
0074         QMessageBox::warning(parent, QString(), errorMessage + QLatin1Char('\n') + detailedErrorMessage);
0075 #endif
0076     }
0077 }
0078 
0079 // -- icon support
0080 
0081 QString supportedIconTheme()
0082 {
0083     return QLatin1String("breeze");
0084 }
0085 
0086 //! @brief @return true if @a path is readable
0087 bool fileReadable(const QString &path)
0088 {
0089     return !path.isEmpty() && QFileInfo(path).isReadable();
0090 }
0091 
0092 //! @brief Used for a workaround: locations for QStandardPaths::AppDataLocation end with app name.
0093 //! If this is not an expected app but for example a test app, replace
0094 //! the subdir name with app name so we can find resource file(s).
0095 QStringList correctStandardLocations(const QString &privateName,
0096                                      QStandardPaths::StandardLocation location,
0097                                      const QString &extraLocation)
0098 {
0099     QSet<QString> result;
0100     if (!privateName.isEmpty()) {
0101         QRegularExpression re(QLatin1Char('/') + QCoreApplication::applicationName() + QLatin1Char('$'));
0102         QStringList standardLocations(QStandardPaths::standardLocations(location));
0103         if (!extraLocation.isEmpty()) {
0104             standardLocations.append(extraLocation);
0105         }
0106         for(const QString &dir : standardLocations) {
0107             if (dir.indexOf(re) != -1) {
0108                 QString realDir(dir);
0109                 realDir.replace(re, QLatin1Char('/') + privateName);
0110                 result.insert(realDir);
0111             }
0112         }
0113     }
0114     return result.toList();
0115 }
0116 
0117 #ifdef Q_OS_WIN
0118 #define KPATH_SEPARATOR ';'
0119 #else
0120 #define KPATH_SEPARATOR ':'
0121 #endif
0122 
0123 /*! @brief Locates a file path for specified parameters
0124  * @param privateName Name to be used instead of application name for resource lookup
0125  * @param path Relative path to the resource file
0126  * @param location Standard file location to use for file lookup
0127  * @param extraLocation Extra directory path for file lookup
0128  * @return Empty string on failure
0129  */
0130 QString locateFile(const QString &privateName,
0131                    const QString& path, QStandardPaths::StandardLocation location,
0132                    const QString &extraLocation)
0133 {
0134     // let QStandardPaths handle this, it will look for app local stuff
0135     QString fullPath = QFileInfo(
0136         QStandardPaths::locate(location, path)).canonicalFilePath();
0137     if (fileReadable(fullPath)) {
0138         return fullPath;
0139     }
0140 
0141     // Try extra location
0142     fullPath = QFileInfo(extraLocation + QLatin1Char('/') + path).canonicalFilePath();
0143     if (fileReadable(fullPath)) {
0144         return fullPath;
0145     }
0146     // Try in PATH subdirs, useful for running apps from the build dir, without installing
0147     for(const QByteArray &pathDir : qgetenv("PATH").split(KPATH_SEPARATOR)) {
0148         const QString dataDirFromPath = QFileInfo(QFile::decodeName(pathDir) + QStringLiteral("/data/")
0149                                                   + path).canonicalFilePath();
0150         if (fileReadable(dataDirFromPath)) {
0151             return dataDirFromPath;
0152         }
0153     }
0154 
0155     const QStringList correctedStandardLocations(correctStandardLocations(privateName, location, extraLocation));
0156     for(const QString &dir : correctedStandardLocations) {
0157         fullPath = QFileInfo(dir + QLatin1Char('/') + path).canonicalFilePath();
0158         if (fileReadable(fullPath)) {
0159             return fullPath;
0160         }
0161     }
0162     return QString();
0163 }
0164 
0165 /*! @brief Registers icons resource file
0166  * @param privateName Name to be used instead of application name for resource lookup
0167  * @param path Relative path to the resource file
0168  * @param location Standard file location to use for file lookup
0169  * @param resourceRoot A resource root for QResource::registerResource()
0170  * @param errorMessage On failure it is set to a brief error message.
0171  * @param errorDescription On failure it is set to a detailed error message.
0172  */
0173 bool registerIconsResource(const QString &privateName, const QString& path,
0174                              QStandardPaths::StandardLocation location,
0175                              const QString &resourceRoot, const QString &extraLocation,
0176                              QString *errorMessage, QString *detailedErrorMessage)
0177 {
0178     const QString fullPath = locateFile(privateName, path, location, extraLocation);
0179     if (fullPath.isEmpty() || !QFileInfo(fullPath).isReadable()
0180         || !QResource::registerResource(fullPath, resourceRoot))
0181     {
0182         QStringList triedLocations(QStandardPaths::standardLocations(location));
0183         if (!extraLocation.isEmpty()) {
0184             triedLocations.append(extraLocation);
0185         }
0186         triedLocations.append(correctStandardLocations(privateName, location, extraLocation));
0187         std::transform(triedLocations.begin(), triedLocations.end(),
0188                        triedLocations.begin(), [](const QString &path) {
0189                          return QDir::toNativeSeparators(path);
0190                        });
0191         {
0192             std::list<QString> triedLocationsList(triedLocations.toStdList());
0193             triedLocationsList.unique();
0194             triedLocations = QStringList::fromStdList(triedLocationsList);
0195         }
0196         const QString triedLocationsString = QLocale().createSeparatedList(triedLocations);
0197 #ifdef QT_ONLY
0198         *errorMessage = QString("Could not open icon resource file %1.").arg(path);
0199         *detailedErrorMessage = QString("Tried to find in %1.").arg(triedLocationsString);
0200 #else
0201         *errorMessage = QObject::tr(
0202             "Could not open icon resource file \"%1\". "
0203             "Application will not start. "
0204             "Please check if it is properly installed.")
0205             .arg(QDir::toNativeSeparators(path));
0206         *detailedErrorMessage = QObject::tr("Tried to find in %1.").arg(triedLocationsString);
0207 #endif
0208         return false;
0209     }
0210     *errorMessage = QString();
0211     *detailedErrorMessage = QString();
0212     return true;
0213 }
0214 
0215 /*! @brief Registers a global icon resource file
0216  * @param themeName A name of icon theme to use.
0217  * @param errorMessage On failure it is set to a brief error message.
0218  * @param errorDescription On failure it is set to a detailed error message.
0219  */
0220 bool registerGlobalIconsResource(const QString &themeName,
0221                                  QString *errorMessage,
0222                                  QString *detailedErrorMessage)
0223 {
0224     QString extraLocation;
0225 #ifdef CMAKE_INSTALL_FULL_ICONDIR
0226     extraLocation = QDir::fromNativeSeparators(QFile::decodeName(CMAKE_INSTALL_FULL_ICONDIR));
0227     if (extraLocation.endsWith("/icons")) {
0228         extraLocation.chop(QLatin1String("/icons").size());
0229     }
0230 #elif defined(Q_OS_WIN)
0231     extraLocation = QCoreApplication::applicationDirPath() + QStringLiteral("/data");
0232 #endif
0233     return registerIconsResource(QString(), QString::fromLatin1("icons/%1/%1-icons.rcc").arg(themeName),
0234                             QStandardPaths::GenericDataLocation,
0235                             QStringLiteral("/icons/") + themeName,
0236                             extraLocation, errorMessage,
0237                             detailedErrorMessage);
0238 }
0239 
0240 /*! @brief Registers a global icon resource file
0241  * @param themeName A name of icon theme to use.
0242  */
0243 bool registerGlobalIconsResource(const QString &themeName)
0244 {
0245     QString errorMessage;
0246     QString detailedErrorMessage;
0247     if (!registerGlobalIconsResource(themeName, &errorMessage, &detailedErrorMessage)) {
0248         showMessageBox(nullptr, errorMessage, detailedErrorMessage);
0249         kprWarning() << qPrintable(errorMessage);
0250         return false;
0251     }
0252     return true;
0253 }
0254 
0255 /*! @brief Registers a global icon resource file for default theme name.
0256  */
0257 bool registerGlobalIconsResource()
0258 {
0259     return registerGlobalIconsResource(supportedIconTheme());
0260 }
0261 
0262 /*! @brief Sets up a private icon resource file
0263  * @return @c false on failure and sets error message. Does not warn or exit on failure.
0264  * @param privateName Name to be used instead of application name for resource lookup
0265  * @param path Relative path to the resource file
0266  * @param themeName Icon theme to use. It affects filename.
0267  * @param errorMessage On failure it is set to a brief error message
0268  * @param errorDescription On failure it is set to a detailed error message
0269  * @param prefix Resource path prefix. The default is useful for library-global resource,
0270  * other values is useful for plugins.
0271  */
0272 bool setupPrivateIconsResource(const QString &privateName, const QString& path,
0273                                const QString &themeName,
0274                                QString *errorMessage, QString *detailedErrorMessage,
0275                                const QString &prefix = QLatin1String(":/icons"))
0276 {
0277     // Register application's resource first to have priority over the theme.
0278     // Some icons may exists in both resources.
0279     QString extraLocation = QCoreApplication::applicationDirPath() + QStringLiteral("/data");
0280     if (!registerIconsResource(privateName, path,
0281                           QStandardPaths::AppDataLocation,
0282                           QString(), QString(), errorMessage, detailedErrorMessage))
0283     {
0284         return false;
0285     }
0286     bool changeTheme = false;
0287 #ifdef QT_GUI_LIB
0288     QIcon::setThemeSearchPaths(QStringList() << prefix << QIcon::themeSearchPaths());
0289     changeTheme = 0 != QIcon::themeName().compare(themeName, Qt::CaseInsensitive);
0290     if (changeTheme) {
0291         QIcon::setThemeName(themeName);
0292     }
0293 #endif
0294 
0295 #ifdef KPROPERTY_KF
0296     KConfigGroup cg(KSharedConfig::openConfig(), "Icons");
0297     changeTheme = changeTheme || 0 != cg.readEntry("Theme", QString()).compare(themeName, Qt::CaseInsensitive);
0298     // tell KIconLoader an co. about the theme
0299     if (changeTheme) {
0300         cg.writeEntry("Theme", themeName);
0301         cg.sync();
0302     }
0303 #endif
0304     return true;
0305 }
0306 
0307 /*! @brief Sets up a private icon resource file
0308  * @return @c false on failure and sets error message.
0309  * @param privateName Name to be used instead of application name for resource lookup
0310  * @param path Relative path to the resource file
0311  * @param themeName Icon theme to use. It affects filename.
0312  * @param errorMessage On failure it is set to a brief error message.
0313  * @param errorDescription On failure it is set to a detailed error message.
0314  * @param prefix Resource path prefix. The default is useful for library-global resource,
0315  * other values is useful for plugins.
0316  */
0317 bool setupPrivateIconsResourceWithMessage(const QString &privateName, const QString& path,
0318                                           const QString &themeName,
0319                                           QString *errorMessage, QString *detailedErrorMessage,
0320                                           const QString &prefix = QLatin1String(":/icons"))
0321 {
0322     if (!setupPrivateIconsResource(privateName, path, themeName,
0323                                    errorMessage, detailedErrorMessage, prefix))
0324     {
0325         showMessageBox(nullptr, *errorMessage, *detailedErrorMessage);
0326         return false;
0327     }
0328     return true;
0329 }
0330 
0331 /*! @overload setupPrivateIconsResourceWithMessage(QString &privateName, const QString& path,
0332                                           const QString &themeName,
0333                                           QString *errorMessage, QString *detailedErrorMessage,
0334                                           const QString &prefix = QLatin1String(":/icons"))
0335     Uses default theme name.
0336  */
0337 bool setupPrivateIconsResourceWithMessage(const QString &privateName, const QString& path,
0338                                           QString *errorMessage, QString *detailedErrorMessage,
0339                                           const QString &prefix = QLatin1String(":/icons"))
0340 {
0341     return setupPrivateIconsResourceWithMessage(privateName, path, supportedIconTheme(),
0342                                                 errorMessage, detailedErrorMessage, prefix);
0343 }
0344 
0345 bool setupPrivateIconsResourceWithMessage(const QString &privateName, const QString& path,
0346                                           QtMsgType messageType,
0347                                           const QString &prefix)
0348 {
0349     QString errorMessage;
0350     QString detailedErrorMessage;
0351     if (!setupPrivateIconsResourceWithMessage(privateName, path,
0352                                               &errorMessage, &detailedErrorMessage, prefix)) {
0353         if (messageType == QtFatalMsg) {
0354             kprCritical() << qPrintable(errorMessage) << qPrintable(detailedErrorMessage);
0355         } else {
0356             kprWarning() << qPrintable(errorMessage) << qPrintable(detailedErrorMessage);
0357         }
0358         return false;
0359     }
0360     return true;
0361 }
0362 
0363 bool setupGlobalIconTheme()
0364 {
0365     if (0 != QIcon::themeName().compare(supportedIconTheme(), Qt::CaseInsensitive)) {
0366         const QString message = QObject::tr(
0367             "\"%1\"  supports only \"%2\" icon theme but current system theme is \"%3\". "
0368             "Application's icon theme will be changed to \"%2\". "
0369             "Please consider adding support for other themes to %4.")
0370             .arg(QLatin1String(KPROPERTYWIDGETS_BASE_NAME)).arg(supportedIconTheme()).arg(QIcon::themeName())
0371             .arg(QCoreApplication::applicationName());
0372         kprDebug() << qPrintable(message);
0373         if (!registerGlobalIconsResource()) {
0374             // don't fail, just warn
0375             const QString message = QObject::tr(
0376                 "Failed to set icon theme to \"%1\". Icons in the application will be inconsistent. "
0377                 "Please install .rcc file(s) for the system theme.")
0378                 .arg(supportedIconTheme());
0379             kprDebug() << qPrintable(message);
0380             return false;
0381         }
0382     }
0383     return true;
0384 }
0385 
0386 // ----
0387 
0388 ValueOptionsHandler::ValueOptionsHandler(const KProperty &property)
0389 {
0390     minValueText = property.option("minValueText");
0391     prefix = property.option("prefix").toString().trimmed();
0392     suffix = property.option("suffix").toString().trimmed();
0393 }
0394 
0395 QString ValueOptionsHandler::valueWithPrefixAndSuffix(const QString &valueString, const QLocale &locale) const
0396 {
0397     QString result = valueString;
0398     if (!suffix.isEmpty()) {
0399         if (locale.language() == QLocale::C) {
0400             result = QString::fromLatin1("%1 %2").arg(result).arg(suffix);
0401         } else {
0402             result = QObject::tr("%1 %2", "<value> <suffix>").arg(result).arg(suffix);
0403         }
0404     }
0405     if (!prefix.isEmpty()) {
0406         if (locale.language() == QLocale::C) {
0407             result = QString::fromLatin1("%1 %2").arg(prefix).arg(result);
0408         } else {
0409             result = QObject::tr("%1 %2", "<prefix> <value>").arg(prefix).arg(result);
0410         }
0411     }
0412     return result;
0413 }
0414 
0415 // ----
0416 
0417 PainterSaver::PainterSaver(QPainter *p)
0418     : m_painter(p)
0419 {
0420     if (m_painter) {
0421         m_painter->save();
0422     }
0423 }
0424 
0425 PainterSaver::~PainterSaver()
0426 {
0427     if (m_painter) {
0428         m_painter->restore();
0429     }
0430 }
0431 
0432 } // namespace KPropertyUtilsPrivate