File indexing completed on 2024-05-12 16:40:07

0001 /* This file is part of the KDE project
0002    Copyright (C) 2016 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 <QCoreApplication>
0021 #include <QDir>
0022 #include <QFileInfo>
0023 #include <QRegularExpression>
0024 #include <QResource>
0025 #include <QStandardPaths>
0026 #include <QDebug>
0027 
0028 #ifdef QT_ONLY
0029 #define KLocalizedString QString
0030 #else
0031 #include <KConfigGroup>
0032 #include <KSharedConfig>
0033 #include <KMessageBox>
0034 #endif
0035 
0036 //! @todo Support other themes
0037 const QString supportedIconTheme = QLatin1String("breeze");
0038 
0039 //! @return true if @a path is readable
0040 static bool fileReadable(const QString &path)
0041 {
0042     return !path.isEmpty() && QFileInfo(path).isReadable();
0043 }
0044 
0045 #ifdef Q_OS_WIN
0046 #define KPATH_SEPARATOR ';'
0047 #else
0048 #define KPATH_SEPARATOR ':'
0049 #endif
0050 
0051 //! @brief Used for a workaround: locations for QStandardPaths::AppDataLocation end with app name.
0052 //! If this is not an expected app but for example a test app, replace
0053 //! the subdir name with app name so we can find resource file(s).
0054 static QStringList correctStandardLocations(const QString &privateName,
0055                                      QStandardPaths::StandardLocation location,
0056                                      const QString &extraLocation)
0057 {
0058     QStringList result;
0059     QStringList standardLocations(QStandardPaths::standardLocations(location));
0060     if (!extraLocation.isEmpty()) {
0061         standardLocations.append(extraLocation);
0062     }
0063     if (privateName.isEmpty()) {
0064         result = standardLocations;
0065     } else {
0066         QRegularExpression re(QLatin1Char('/') + QCoreApplication::applicationName() + QLatin1Char('$'));
0067         for(const QString &dir : standardLocations) {
0068             if (dir.indexOf(re) != -1) {
0069                 QString realDir(dir);
0070                 realDir.replace(re, QLatin1Char('/') + privateName);
0071                 result.append(realDir);
0072             }
0073         }
0074     }
0075     return result;
0076 }
0077 
0078 static QString locateFile(const QString &privateName,
0079                           const QString& path, QStandardPaths::StandardLocation location,
0080                           const QString &extraLocation, QStringList *triedLocations)
0081 {
0082     Q_ASSERT(triedLocations);
0083     const QString subdirPath = QFileInfo(path).dir().path();
0084     {
0085         // Priority #1: This makes the app portable and working without installation, from the build dir
0086         const QString dataDir = QCoreApplication::applicationDirPath() + QStringLiteral("/data/") + privateName;
0087         triedLocations->append(QDir::cleanPath(dataDir + '/' + subdirPath));
0088         const QString dataFile = QFileInfo(dataDir + '/' + path).canonicalFilePath();
0089         if (fileReadable(dataFile)) {
0090             return dataFile;
0091         }
0092     }
0093     // Priority #2: Let QStandardPaths handle this, it will look for app local stuff
0094     const QStringList correctedStandardLocations(correctStandardLocations(privateName, location, extraLocation));
0095     for (const QString &dir : correctedStandardLocations) {
0096         triedLocations->append(QDir::cleanPath(dir + '/' + subdirPath));
0097         const QString dataFile = QFileInfo(dir + QLatin1Char('/') + path).canonicalFilePath();
0098         if (fileReadable(dataFile)) {
0099             return dataFile;
0100         }
0101     }
0102     // Priority #3: Try in PATH subdirs, useful for running apps from the build dir, without installing
0103     for(const QByteArray &pathDir : qgetenv("PATH").split(KPATH_SEPARATOR)) {
0104         const QString dataDir = QFile::decodeName(pathDir) + QStringLiteral("/data/");
0105         triedLocations->append(QDir::cleanPath(dataDir + '/' + subdirPath));
0106         const QString dataDirFromPath = QFileInfo(dataDir + '/' + path).canonicalFilePath();
0107         if (fileReadable(dataDirFromPath)) {
0108             return dataDirFromPath;
0109         }
0110     }
0111     return QString();
0112 }
0113 
0114 #ifndef KEXI_SKIP_REGISTERRESOURCE
0115 
0116 #ifdef KEXI_BASE_PATH
0117 const QString BASE_PATH(KEXI_BASE_PATH);
0118 #else
0119 const QString BASE_PATH(QCoreApplication::applicationName());
0120 #endif
0121 
0122 static bool registerResource(const QString& path, QStandardPaths::StandardLocation location,
0123                              const QString &resourceRoot, const QString &extraLocation,
0124                              KLocalizedString *errorMessage, KLocalizedString *detailsErrorMessage)
0125 {
0126     QStringList triedLocations;
0127     const QString privateName = location == QStandardPaths::GenericDataLocation
0128         ? QString() : BASE_PATH;
0129     const QString fullPath = locateFile(privateName, path, location, extraLocation, &triedLocations);
0130     if (fullPath.isEmpty()
0131         || !QResource::registerResource(fullPath, resourceRoot))
0132     {
0133         const QString triedLocationsString = QLocale().createSeparatedList(triedLocations);
0134 #ifdef QT_ONLY
0135         *errorMessage = QString("Could not open icon resource file %1.").arg(path);
0136         *detailsErrorMessage = QString("Tried to find in %1.").arg(triedLocationsString);
0137 #else
0138         *errorMessage = kxi18nc("@info",
0139             "<para>Could not open icon resource file <filename>%1</filename>.</para>"
0140             "<para><application>Kexi</application> will not start. "
0141             "Please check if <application>Kexi</application> is properly installed.</para>")
0142             .subs(QFileInfo(path).fileName());
0143         *detailsErrorMessage = kxi18nc("@info Tried to find files in <dir list>",
0144                                        "Tried to find in %1.").subs(triedLocationsString);
0145 #endif
0146         return false;
0147     }
0148     *errorMessage = KLocalizedString();
0149     *detailsErrorMessage = KLocalizedString();
0150     return true;
0151 }
0152 #endif // !KEXI_SKIP_REGISTERRESOURCE
0153 
0154 #ifndef KEXI_SKIP_SETUPBREEZEICONTHEME
0155 
0156 inline bool registerGlobalBreezeIconsResource(KLocalizedString *errorMessage,
0157                                               KLocalizedString *detailsErrorMessage)
0158 {
0159     QString extraLocation;
0160 #ifdef CMAKE_INSTALL_FULL_ICONDIR
0161     extraLocation = QDir::fromNativeSeparators(QFile::decodeName(CMAKE_INSTALL_FULL_ICONDIR));
0162     if (extraLocation.endsWith("/icons")) {
0163         extraLocation.chop(QLatin1String("/icons").size());
0164     }
0165 #endif
0166     return registerResource("icons/breeze/breeze-icons.rcc", QStandardPaths::GenericDataLocation,
0167                             QStringLiteral("/icons/breeze"), extraLocation, errorMessage,
0168                             detailsErrorMessage);
0169 }
0170 
0171 //! Tell Qt about the theme
0172 inline void setupBreezeIconTheme()
0173 {
0174 #ifdef QT_GUI_LIB
0175     QIcon::setThemeSearchPaths(QStringList() << QStringLiteral(":/icons"));
0176     QIcon::setThemeName(QStringLiteral("breeze"));
0177 #endif
0178 }
0179 #endif // !KEXI_SETUPBREEZEICONTHEME
0180 
0181 #ifndef KEXI_SKIP_REGISTERICONSRESOURCE
0182 /*! @brief Registers icons resource file
0183  * @param privateName Name to be used instead of application name for resource lookup
0184  * @param path Relative path to the resource file
0185  * @param location Standard file location to use for file lookup
0186  * @param resourceRoot A resource root for QResource::registerResource()
0187  * @param extraLocation Extra location
0188  * @param errorMessage On failure it is set to a brief error message.
0189  * @param detailedErrorMessage On failure it is set to a detailed error message.
0190  * other for warning
0191  */
0192 static bool registerIconsResource(const QString &privateName, const QString& path,
0193                              QStandardPaths::StandardLocation location,
0194                              const QString &resourceRoot, const QString &extraLocation,
0195                              QString *errorMessage, QString *detailedErrorMessage)
0196 {
0197     QStringList triedLocations;
0198     const QString fullPath = locateFile(privateName, path, location, extraLocation, &triedLocations);
0199     if (fullPath.isEmpty() || !QFileInfo(fullPath).isReadable()
0200         || !QResource::registerResource(fullPath, resourceRoot))
0201     {
0202         const QString triedLocationsString = QLocale().createSeparatedList(triedLocations);
0203 #ifdef QT_ONLY
0204         *errorMessage
0205             = QString("Could not open icon resource file \"%1\". Please check if application "
0206                       "is properly installed.").arg(path);
0207         *detailedErrorMessage = QString("Tried to find in %1.").arg(triedLocationsString);
0208 #else
0209         *errorMessage = xi18nc(
0210             "@info", "Could not open icon resource file <filename>%1</filename>. "
0211                      "Please check if <application>%2</application> is properly installed.",
0212             QFileInfo(path).fileName(), QApplication::applicationDisplayName());
0213         *detailedErrorMessage = xi18nc("@info Tried to find files in <dir list>",
0214                                        "Tried to find in %1.", triedLocationsString);
0215 #endif
0216         return false;
0217     }
0218     *errorMessage = QString();
0219     *detailedErrorMessage = QString();
0220     return true;
0221 }
0222 #endif // !KEXI_SKIP_SETUPBREEZEICONTHEME
0223 
0224 #if !defined QT_ONLY  && !defined KEXI_SKIP_SETUPPRIVATEICONSRESOURCE
0225 /*! @brief Sets up a private icon resource file
0226  * @return @c false on failure and sets error message. Does not warn or exit on failure.
0227  * @param privateName Name to be used instead of application name for resource lookup
0228  * @param path Relative path to the resource file
0229  * @param themeName Icon theme to use. It affects filename.
0230  * @param errorMessage On failure it is set to a brief error message
0231  * @param detailedErrorMessage On failure it is set to a detailed error message
0232  * other for warning
0233  * @param prefix Resource path prefix. The default is useful for library-global resource,
0234  * other values is useful for plugins.
0235  */
0236 static bool setupPrivateIconsResource(const QString &privateName, const QString& path,
0237                                const QString &themeName,
0238                                QString *errorMessage, QString *detailedErrorMessage,
0239                                const QString &prefix = QLatin1String(":/icons"))
0240 {
0241     // Register application's resource first to have priority over the theme.
0242     // Some icons may exists in both resources.
0243     if (!registerIconsResource(privateName, path,
0244                           QStandardPaths::AppDataLocation,
0245                           QString(), QString(), errorMessage, detailedErrorMessage))
0246     {
0247         return false;
0248     }
0249     bool changeTheme = false;
0250 #ifdef QT_GUI_LIB
0251     QIcon::setThemeSearchPaths(QStringList() << prefix << QIcon::themeSearchPaths());
0252     changeTheme = 0 != QIcon::themeName().compare(themeName, Qt::CaseInsensitive);
0253     if (changeTheme) {
0254         QIcon::setThemeName(themeName);
0255     }
0256 #endif
0257 
0258     KConfigGroup cg(KSharedConfig::openConfig(), "Icons");
0259     changeTheme = changeTheme || 0 != cg.readEntry("Theme", QString()).compare(themeName, Qt::CaseInsensitive);
0260     // tell KIconLoader an co. about the theme
0261     if (changeTheme) {
0262         cg.writeEntry("Theme", themeName);
0263         cg.sync();
0264     }
0265     return true;
0266 }
0267 
0268 /*! @brief Sets up a private icon resource file
0269  * @return @c false on failure and sets error message.
0270  * @param privateName Name to be used instead of application name for resource lookup
0271  * @param path Relative path to the resource file
0272  * @param themeName Icon theme to use. It affects filename.
0273  * @param errorMessage On failure it is set to a brief error message.
0274  * @param detailedErrorMessage On failure it is set to a detailed error message.
0275  * other for warning
0276  * @param prefix Resource path prefix. The default is useful for library-global resource,
0277  * other values is useful for plugins.
0278  */
0279 static bool setupPrivateIconsResourceWithMessage(const QString &privateName, const QString& path,
0280                                           const QString &themeName,
0281                                           QString *errorMessage, QString *detailedErrorMessage,
0282                                           const QString &prefix = QLatin1String(":/icons"))
0283 {
0284     if (!setupPrivateIconsResource(privateName, path, themeName,
0285                                    errorMessage, detailedErrorMessage, prefix))
0286     {
0287         if (detailedErrorMessage->isEmpty()) {
0288             KMessageBox::error(nullptr, *errorMessage);
0289         } else {
0290             KMessageBox::detailedError(nullptr, *errorMessage, *detailedErrorMessage);
0291         }
0292         return false;
0293     }
0294     return true;
0295 }
0296 
0297 /*! @overload setupPrivateIconsResourceWithMessage(QString &privateName, const QString& path,
0298                                           const QString &themeName,
0299                                           QString *errorMessage, QString *detailedErrorMessage,
0300                                           const QString &prefix = QLatin1String(":/icons"))
0301     Uses default theme name.
0302  */
0303 static bool setupPrivateIconsResourceWithMessage(const QString &privateName, const QString& path,
0304                                           QString *errorMessage, QString *detailedErrorMessage,
0305                                           const QString &prefix = QLatin1String(":/icons"))
0306 {
0307     return setupPrivateIconsResourceWithMessage(privateName, path, supportedIconTheme,
0308                                                 errorMessage, detailedErrorMessage, prefix);
0309 }
0310 
0311 /*! @brief Sets up a private icon resource file
0312  * Warns on failure and returns @c false.
0313  * @param privateName Name to be used instead of application name for resource lookup
0314  * @param path Relative path to the resource file
0315  * @param messageType Type of message to use on error, QtFatalMsg for fatal exit and any
0316  * other for warning
0317  * @param prefix Resource path prefix. The default is useful for library-global resource,
0318  * other values is useful for plugins.
0319  */
0320 static bool setupPrivateIconsResourceWithMessage(const QString &privateName, const QString& path,
0321                                           QtMsgType messageType,
0322                                           const QString &prefix = QLatin1String(":/icons"))
0323 {
0324     QString errorMessage;
0325     QString detailedErrorMessage;
0326     if (!setupPrivateIconsResourceWithMessage(privateName, path,
0327                                               &errorMessage, &detailedErrorMessage, prefix)) {
0328         if (messageType == QtFatalMsg) {
0329             qFatal("%s %s", qPrintable(errorMessage), qPrintable(detailedErrorMessage));
0330         } else {
0331             qWarning() << qPrintable(errorMessage) << qPrintable(detailedErrorMessage);
0332         }
0333         return false;
0334     }
0335     return true;
0336 }
0337 
0338 #endif // !QT_ONLY && !KEXI_SKIP_SETUPPRIVATEICONSRESOURCE