File indexing completed on 2024-09-15 12:23:44
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