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