diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c89a2b50..48c82cb03 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,228 +1,228 @@ cmake_minimum_required(VERSION 3.5) set(KF5_VERSION "5.70.0") # handled by release scripts set(KF5_DEP_VERSION "5.69.0") # handled by release scripts project(Plasma VERSION ${KF5_VERSION}) # ECM setup include(FeatureSummary) find_package(ECM 5.69.0 NO_MODULE) set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules") feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(ECMGenerateExportHeader) include(ECMGenerateHeaders) include(CMakePackageConfigHelpers) include(ECMSetupVersion) include(ECMQtDeclareLoggingCategory) include(ECMAddQch) include(KDEPackageAppTemplates) include(ECMGenerateQmlTypes) include(ECMSetupQtPluginMacroNames) option(BUILD_QCH "Build API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)" OFF) add_feature_info(QCH ${BUILD_QCH} "API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)") ecm_setup_version(PROJECT VARIABLE_PREFIX PLASMA VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/plasma_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5PlasmaConfigVersion.cmake" SOVERSION 5) if(KDE_PLATFORM_FEATURE_DISABLE_DEPRECATED) set(KDE_NO_DEPRECATED TRUE) set(CMAKE_AUTOMOC_MOC_OPTIONS "-DKDE_NO_DEPRECATED") endif() - +add_definitions(-DQT_NO_FOREACH) ################# now find all used packages ################# set (REQUIRED_QT_VERSION 5.12.0) find_package(Qt5 ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE COMPONENTS Quick Gui Sql Qml Svg QuickControls2) find_package(KF5 ${KF5_DEP_VERSION} REQUIRED COMPONENTS Activities Archive Config ConfigWidgets CoreAddons DBusAddons Declarative GlobalAccel GuiAddons I18n IconThemes KIO Service WindowSystem XmlGui Notifications Package Kirigami2 OPTIONAL_COMPONENTS Wayland DocTools ) set_package_properties(KF5Wayland PROPERTIES DESCRIPTION "Integration with the Wayland compositor" TYPE OPTIONAL ) if(KF5Wayland_FOUND) set(HAVE_KWAYLAND 1) endif() set_package_properties(KF5DocTools PROPERTIES DESCRIPTION "Tools to generate documentation" TYPE OPTIONAL ) #optional features find_package(X11 MODULE) set_package_properties(X11 PROPERTIES DESCRIPTION "X11 libraries" URL "https://www.x.org/" TYPE OPTIONAL ) find_package(XCB MODULE COMPONENTS XCB COMPOSITE DAMAGE SHAPE XFIXES RENDER) set_package_properties(XCB PROPERTIES DESCRIPTION "X protocol C-language Binding" URL "https://xcb.freedesktop.org/" TYPE OPTIONAL ) if(X11_FOUND AND XCB_XCB_FOUND) set(HAVE_X11 1) find_package(Qt5 REQUIRED NO_MODULE COMPONENTS X11Extras) #X11_Xrender discovery is done by FindX11 #add_feature_info("X Rendering Extension (libXrender)" X11_Xrender_FOUND "Support for compositing, rendering operations, and alpha-blending. STRONGLY RECOMMENDED") endif() find_package(OpenGL) set_package_properties(OpenGL PROPERTIES DESCRIPTION "The OpenGL libraries" URL "https://www.opengl.org/" TYPE OPTIONAL ) find_package(EGL) set_package_properties(EGL PROPERTIES PURPOSE "Support for Window Thumbnail on EGL platform" TYPE OPTIONAL ) set(HAVE_EGL ${EGL_FOUND}) if(OPENGL_FOUND AND (${Qt5Gui_OPENGL_IMPLEMENTATION} STREQUAL "GL")) set(HAVE_GLX ${HAVE_X11}) else() set(HAVE_GLX 0) endif() ######################################################################### ecm_setup_qtplugin_macro_names( JSON_NONE "K_EXPORT_PLASMA_SERVICE" "K_EXPORT_PLASMA_APPLET" "K_EXPORT_PLASMA_PACKAGE" "K_EXPORT_PLASMA_APPLETSCRIPTENGINE" "K_EXPORT_PLASMA_DATAENGINESCRIPTENGINE" "K_EXPORT_PLASMA_DATAENGINE" JSON_ARG2 "K_EXPORT_PLASMA_PACKAGE_WITH_JSON" JSON_ARG3 "K_EXPORT_PLASMA_SERVICE_WITH_JSON" "K_EXPORT_PLASMA_APPLET_WITH_JSON" "K_EXPORT_PLASMA_APPLETSCRIPTENGINE_WITH_JSON" "K_EXPORT_PLASMA_DATAENGINESCRIPTENGINE_WITH_JSON" "K_EXPORT_PLASMA_CONTAINMENTACTIONS_WITH_JSON" "K_EXPORT_PLASMA_DATAENGINE_WITH_JSON" CONFIG_CODE_VARIABLE PACKAGE_SETUP_AUTOMOC_VARIABLES ) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050d00) add_definitions(-DKF_DISABLE_DEPRECATED_BEFORE_AND_AT=0x054400) #add_definitions(-Wno-deprecated) include(KF5PlasmaMacros.cmake) ######################################################################### option(BUILD_EXAMPLES "Build and install Plasma examples." OFF) option(BUILD_COVERAGE "Build Plasma Frameworks with gcov support" OFF) if(BUILD_COVERAGE) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lgcov") endif() # make plasma_version.h available include_directories(${CMAKE_CURRENT_BINARY_DIR}) ################# list the subdirectories ################# if (KF5DocTools_FOUND) add_subdirectory(docs) endif() add_definitions(-DTRANSLATION_DOMAIN=\"libplasma5\") if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/po") ki18n_install(po) if (KF5DocTools_FOUND) kdoctools_install(po) endif() endif() add_subdirectory(src) if (BUILD_EXAMPLES) add_subdirectory(examples) endif() if (BUILD_TESTING) add_subdirectory(autotests) add_subdirectory(tests) endif() add_subdirectory(templates) ################ create PlasmaConfig.cmake and install it ########################### # create a Config.cmake and a ConfigVersion.cmake file and install them set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF5Plasma") if (BUILD_QCH) ecm_install_qch_export( TARGETS KF5Plasma_QCH FILE KF5PlasmaQchTargets.cmake DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) set(PACKAGE_INCLUDE_QCHTARGETS "include(\"\${CMAKE_CURRENT_LIST_DIR}/KF5PlasmaQchTargets.cmake\")") endif() configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/KF5PlasmaConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KF5PlasmaConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} PATH_VARS KF5_INCLUDE_INSTALL_DIR CMAKE_INSTALL_PREFIX ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KF5PlasmaConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KF5PlasmaConfigVersion.cmake" "${CMAKE_CURRENT_SOURCE_DIR}/KF5PlasmaMacros.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) install(EXPORT KF5PlasmaTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KF5PlasmaTargets.cmake NAMESPACE KF5:: COMPONENT Devel) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/plasma_version.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5} COMPONENT Devel ) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/autotests/coronatest.cpp b/autotests/coronatest.cpp index d52caabfa..25e95914d 100644 --- a/autotests/coronatest.cpp +++ b/autotests/coronatest.cpp @@ -1,251 +1,260 @@ /******************************************************************************** * Copyright 2014 Marco Martin * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public License * * along with this library; see the file COPYING.LIB. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * *********************************************************************************/ #include "coronatest.h" #include #include #include #include #include #include #include #include Plasma::Applet *SimpleLoader::internalLoadApplet(const QString &name, uint appletId, const QVariantList &args) { Q_UNUSED(args) if (name == QLatin1String("simpleapplet")) { return new SimpleApplet(nullptr, QString(), appletId); } else if (name == QLatin1String("simplecontainment")) { return new SimpleContainment(nullptr, QString(), appletId); } else if (name == QLatin1String("simplenoscreencontainment")) { return new SimpleNoScreenContainment(nullptr, QString(), appletId); } else { return nullptr; } } SimpleCorona::SimpleCorona(QObject *parent) : Plasma::Corona(parent) { Plasma::PluginLoader::setPluginLoader(new SimpleLoader); } SimpleCorona::~SimpleCorona() {} QRect SimpleCorona::screenGeometry(int screen) const { //completely arbitrary, still not tested return QRect(100*screen, 100, 100, 100); } int SimpleCorona::screenForContainment(const Plasma::Containment *c) const { if (qobject_cast(c)) { return -1; } return 0; } SimpleApplet::SimpleApplet(QObject *parent , const QString &serviceId, uint appletId) : Plasma::Applet(parent, serviceId, appletId) { //updateConstraints(Plasma::Types::UiReadyConstraint); m_timer.setSingleShot(true); m_timer.setInterval(QRandomGenerator::global()->bounded((500 + 1) - 100) + 100); m_timer.start(); connect(&m_timer, &QTimer::timeout, [=]() { updateConstraints(Plasma::Types::UiReadyConstraint); }); } SimpleContainment::SimpleContainment(QObject *parent , const QString &serviceId, uint appletId) : Plasma::Containment(parent, serviceId, appletId) { //updateConstraints(Plasma::Types::UiReadyConstraint); m_timer.setSingleShot(true); m_timer.setInterval(QRandomGenerator::global()->bounded((500 + 1) - 100) + 100); m_timer.start(); connect(&m_timer, &QTimer::timeout, [=]() { updateConstraints(Plasma::Types::UiReadyConstraint); }); } SimpleNoScreenContainment::SimpleNoScreenContainment(QObject *parent , const QString &serviceId, uint appletId) : Plasma::Containment(parent, serviceId, appletId) { //This containment will *never* be isUiReady() } static void runKBuildSycoca() { QProcess proc; const QString kbuildsycoca = QStandardPaths::findExecutable(QStringLiteral(KBUILDSYCOCA_EXENAME)); QVERIFY(!kbuildsycoca.isEmpty()); QStringList args; args << QStringLiteral("--testmode"); proc.setProcessChannelMode(QProcess::MergedChannels); // silence kbuildsycoca output proc.start(kbuildsycoca, args); QSignalSpy spy(KSycoca::self(), SIGNAL(databaseChanged(QStringList))); QVERIFY(spy.wait(10000)); proc.waitForFinished(); QCOMPARE(proc.exitStatus(), QProcess::NormalExit); } void CoronaTest::initTestCase() { if (!KSycoca::isAvailable()) { runKBuildSycoca(); } QStandardPaths::setTestModeEnabled(true); m_corona = new SimpleCorona; m_configDir = QDir(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation)); m_configDir.removeRecursively(); QVERIFY(m_configDir.mkpath(QStringLiteral("."))); QVERIFY(QFile::copy(QStringLiteral(":/plasma-test-appletsrc"), m_configDir.filePath(QStringLiteral("plasma-test-appletsrc")))); } void CoronaTest::cleanupTestCase() { m_configDir.removeRecursively(); delete m_corona; } void CoronaTest::restore() { m_corona->loadLayout(QStringLiteral("plasma-test-appletsrc")); QCOMPARE(m_corona->containments().count(), 3); - foreach (auto cont, m_corona->containments()) { + const auto containments = m_corona->containments(); + for (auto cont : containments) { switch (cont->id()) { case 1: QCOMPARE(cont->applets().count(), 2); break; default: QCOMPARE(cont->applets().count(), 0); break; } } } void CoronaTest::checkOrder() { QCOMPARE(m_corona->containments().count(), 3); //check containments order QCOMPARE(m_corona->containments().at(0)->id(), (uint)1); QCOMPARE(m_corona->containments().at(1)->id(), (uint)4); QCOMPARE(m_corona->containments().at(2)->id(), (uint)5); //check applets order QCOMPARE(m_corona->containments().at(0)->applets().count(), 2); QCOMPARE(m_corona->containments().at(0)->applets().at(0)->id(), (uint)2); QCOMPARE(m_corona->containments().at(0)->applets().at(1)->id(), (uint)3); } void CoronaTest::startupCompletion() { QVERIFY(!m_corona->isStartupCompleted()); QVERIFY(!m_corona->containments().at(0)->isUiReady()); QSignalSpy spy(m_corona, SIGNAL(startupCompleted())); QVERIFY(spy.wait(1000)); QVERIFY(m_corona->isStartupCompleted()); QVERIFY(m_corona->containments().at(0)->isUiReady()); } void CoronaTest::addRemoveApplets() { m_corona->containments().at(0)->createApplet(QStringLiteral("invalid")); QCOMPARE(m_corona->containments().at(0)->applets().count(), 3); //remove action present QVERIFY(m_corona->containments().at(0)->applets().at(0)->actions()->action(QStringLiteral("remove"))); //kill an applet m_corona->containments().at(0)->applets().at(0)->destroy(); QSignalSpy spy(m_corona->containments().at(0)->applets().at(0), SIGNAL(destroyed())); QVERIFY(spy.wait(1000)); QCOMPARE(m_corona->containments().at(0)->applets().count(), 2); } //this test has to be the last, since systemimmutability //can't be programmatically unlocked void CoronaTest::immutability() { //immutability QCOMPARE(m_corona->immutability(), Plasma::Types::Mutable); m_corona->setImmutability(Plasma::Types::UserImmutable); QCOMPARE(m_corona->immutability(), Plasma::Types::UserImmutable); - foreach (Plasma::Containment *cont, m_corona->containments()) { + auto containments = m_corona->containments(); + for (Plasma::Containment *cont : qAsConst(containments)) { QCOMPARE(cont->immutability(), Plasma::Types::UserImmutable); - foreach (Plasma::Applet *app, cont->applets()) { + const auto lstApplets = cont->applets(); + for (Plasma::Applet *app : lstApplets) { QCOMPARE(app->immutability(), Plasma::Types::UserImmutable); } } m_corona->setImmutability(Plasma::Types::Mutable); QCOMPARE(m_corona->immutability(), Plasma::Types::Mutable); - foreach (Plasma::Containment *cont, m_corona->containments()) { + containments = m_corona->containments(); + for (Plasma::Containment *cont : qAsConst(containments)) { QCOMPARE(cont->immutability(), Plasma::Types::Mutable); - foreach (Plasma::Applet *app, cont->applets()) { + const auto lstApplets = cont->applets(); + for (Plasma::Applet *app : lstApplets) { QCOMPARE(app->immutability(), Plasma::Types::Mutable); } } m_corona->setImmutability(Plasma::Types::SystemImmutable); QCOMPARE(m_corona->immutability(), Plasma::Types::SystemImmutable); - foreach (Plasma::Containment *cont, m_corona->containments()) { + containments = m_corona->containments(); + for (Plasma::Containment *cont : qAsConst(containments)) { QCOMPARE(cont->immutability(), Plasma::Types::SystemImmutable); - foreach (Plasma::Applet *app, cont->applets()) { + const auto lstApplets = cont->applets(); + for (Plasma::Applet *app : lstApplets) { QCOMPARE(app->immutability(), Plasma::Types::SystemImmutable); } } //can't unlock systemimmutable m_corona->setImmutability(Plasma::Types::Mutable); QCOMPARE(m_corona->immutability(), Plasma::Types::SystemImmutable); - foreach (Plasma::Containment *cont, m_corona->containments()) { + containments = m_corona->containments(); + for (Plasma::Containment *cont : qAsConst(containments)) { QCOMPARE(cont->immutability(), Plasma::Types::SystemImmutable); - foreach (Plasma::Applet *app, cont->applets()) { + const auto lstApplets = cont->applets(); + for (Plasma::Applet *app : lstApplets) { QCOMPARE(app->immutability(), Plasma::Types::SystemImmutable); } } } QTEST_MAIN(CoronaTest) diff --git a/autotests/utils.h b/autotests/utils.h index edf26baf6..e37146cde 100644 --- a/autotests/utils.h +++ b/autotests/utils.h @@ -1,71 +1,73 @@ /* Copyright (C) 2019 Aleix Pol This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef UTILS_H #define UTILS_H #include #include #include #include namespace Plasma { namespace TestUtils { static void copyPath(const QString &src, const QString &dst) { QDir dir(src); - foreach (const auto &d, dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { + const auto dirList = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + for (const auto &d : dirList ) { QString dst_path = dst + QLatin1Char('/') + d; dir.mkpath(dst_path); copyPath(src + QLatin1Char('/') + d, dst_path); } - foreach (const auto &f, dir.entryList(QDir::Files)) { + const auto entryList = dir.entryList(QDir::Files); + for (const auto &f : entryList) { QFile::copy(src + QLatin1Char('/') + f, dst + QLatin1Char('/') + f); } } static void installPlasmaTheme(const QString &theme = QStringLiteral("breeze")) { QString destinationTheme = (theme == QLatin1String("breeze") ? QStringLiteral("default") : theme); QStandardPaths::setTestModeEnabled(true); const auto qttestPath = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation).constFirst(); Q_ASSERT(!qttestPath.isEmpty()); QDir themePath(qttestPath + QLatin1String("/plasma/desktoptheme/") + destinationTheme); auto data = QFINDTESTDATA("../src/desktoptheme/" + theme + "/metadata.desktop"); QFileInfo f(data); QVERIFY(f.dir().mkpath(themePath.path())); copyPath(f.dir().filePath("default.gzipped"), themePath.path()); QFile::copy(f.dir().filePath("metadata.desktop"), themePath.filePath("metadata.desktop")); const QString colorsFile = QFINDTESTDATA("../src/desktoptheme/" + theme + "/colors"); if (!colorsFile.isEmpty()) { QFile::copy(colorsFile, themePath.filePath("colors")); } } } //TestUtils } //Plasma #endif diff --git a/src/declarativeimports/calendar/daysmodel.cpp b/src/declarativeimports/calendar/daysmodel.cpp index de2ff8aec..31a831a1c 100644 --- a/src/declarativeimports/calendar/daysmodel.cpp +++ b/src/declarativeimports/calendar/daysmodel.cpp @@ -1,282 +1,283 @@ /* Copyright (C) 2013 Mark Gaiser Copyright (C) 2016 Martin Klapetek This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "daysmodel.h" #include "eventdatadecorator.h" #include "eventpluginsmanager.h" #include #include #include #include DaysModel::DaysModel(QObject *parent) : QAbstractListModel(parent), m_pluginsManager(nullptr), m_lastRequestedEventsStartDate(QDate()), m_agendaNeedsUpdate(false) { } DaysModel::~DaysModel() { qDeleteAll(m_eventPlugins); } void DaysModel::setSourceData(QList *data) { if (m_data != data) { beginResetModel(); m_data = data; endResetModel(); } } int DaysModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent) if (m_data->size() <= 0) { return 0; } else { return m_data->size(); } } QVariant DaysModel::data(const QModelIndex &index, int role) const { if (index.isValid()) { const DayData ¤tData = m_data->at(index.row()); const QDate currentDate(currentData.yearNumber, currentData.monthNumber, currentData.dayNumber); switch (role) { case isCurrent: return currentData.isCurrent; case containsEventItems: return m_eventsData.contains(currentDate); case containsMajorEventItems: return hasMajorEventAtDate(currentDate); case containsMinorEventItems: return hasMinorEventAtDate(currentDate); case dayNumber: return currentData.dayNumber; case monthNumber: return currentData.monthNumber; case yearNumber: return currentData.yearNumber; } } return QVariant(); } void DaysModel::update() { if (m_data->size() <= 0) { return; } m_eventsData.clear(); const QDate modelFirstDay(m_data->at(0).yearNumber, m_data->at(0).monthNumber, m_data->at(0).dayNumber); if (m_pluginsManager) { - Q_FOREACH (CalendarEvents::CalendarEventsPlugin *eventsPlugin, m_pluginsManager->plugins()) { + const auto plugins = m_pluginsManager->plugins(); + for (CalendarEvents::CalendarEventsPlugin *eventsPlugin : plugins) { eventsPlugin->loadEventsForDateRange(modelFirstDay, modelFirstDay.addDays(42)); } } // We always have 42 items (or weeks * num of days in week) so we only have to tell the view that the data changed. emit dataChanged(index(0, 0), index(m_data->count() - 1, 0)); } void DaysModel::onDataReady(const QMultiHash &data) { m_eventsData.reserve(m_eventsData.size() + data.size()); m_eventsData += data; if (data.contains(QDate::currentDate())) { m_agendaNeedsUpdate = true; } // only the containsEventItems roles may have changed emit dataChanged(index(0, 0), index(m_data->count() - 1, 0), {containsEventItems, containsMajorEventItems, containsMinorEventItems}); Q_EMIT agendaUpdated(QDate::currentDate()); } void DaysModel::onEventModified(const CalendarEvents::EventData &data) { QList updatesList; auto i = m_eventsData.begin(); while (i != m_eventsData.end()) { if (i->uid() == data.uid()) { *i = data; updatesList << i.key(); } ++i; } if (!updatesList.isEmpty()) { m_agendaNeedsUpdate = true; } - Q_FOREACH (const QDate date, updatesList) { + for (const QDate date : qAsConst(updatesList)) { const QModelIndex changedIndex = indexForDate(date); if (changedIndex.isValid()) { Q_EMIT dataChanged(changedIndex, changedIndex, {containsEventItems, containsMajorEventItems, containsMinorEventItems}); } Q_EMIT agendaUpdated(date); } } void DaysModel::onEventRemoved(const QString &uid) { QList updatesList; auto i = m_eventsData.begin(); while (i != m_eventsData.end()) { if (i->uid() == uid) { updatesList << i.key(); i = m_eventsData.erase(i); } else { ++i; } } if (!updatesList.isEmpty()) { m_agendaNeedsUpdate = true; } - Q_FOREACH (const QDate date, updatesList) { + for (const QDate date : qAsConst(updatesList)) { const QModelIndex changedIndex = indexForDate(date); if (changedIndex.isValid()) { Q_EMIT dataChanged(changedIndex, changedIndex, {containsEventItems, containsMajorEventItems, containsMinorEventItems}); } Q_EMIT agendaUpdated(date); } } QList DaysModel::eventsForDate(const QDate &date) { if (m_lastRequestedAgendaDate == date && !m_agendaNeedsUpdate) { return m_qmlData; } m_lastRequestedAgendaDate = date; qDeleteAll(m_qmlData); m_qmlData.clear(); QList events = m_eventsData.values(date); m_qmlData.reserve(events.size()); // sort events by their time and type std::sort(events.begin(), events.end(), [](const CalendarEvents::EventData &a, const CalendarEvents::EventData &b) { return b.type() > a.type() || b.startDateTime() > a.startDateTime(); }); - Q_FOREACH (const CalendarEvents::EventData &event, events) { + for (const CalendarEvents::EventData &event : qAsConst(events)) { m_qmlData << new EventDataDecorator(event, this); } m_agendaNeedsUpdate = false; return m_qmlData; } QModelIndex DaysModel::indexForDate(const QDate &date) { if (!m_data) { return QModelIndex(); } const DayData &firstDay = m_data->at(0); const QDate firstDate(firstDay.yearNumber, firstDay.monthNumber, firstDay.dayNumber); qint64 daysTo = firstDate.daysTo(date); return createIndex(daysTo, 0); } bool DaysModel::hasMajorEventAtDate(const QDate &date) const { auto it = m_eventsData.find(date); while (it != m_eventsData.end() && it.key() == date) { if (!it.value().isMinor()) { return true; } ++it; } return false; } bool DaysModel::hasMinorEventAtDate(const QDate &date) const { auto it = m_eventsData.find(date); while (it != m_eventsData.end() && it.key() == date) { if (it.value().isMinor()) { return true; } ++it; } return false; } void DaysModel::setPluginsManager(QObject *manager) { EventPluginsManager *m = qobject_cast(manager); if (!m) { return; } if (m_pluginsManager) { m_pluginsManager->deleteLater(); m_pluginsManager = nullptr; } m_pluginsManager = m; connect(m_pluginsManager, &EventPluginsManager::dataReady, this, &DaysModel::onDataReady); connect(m_pluginsManager, &EventPluginsManager::eventModified, this, &DaysModel::onEventModified); connect(m_pluginsManager, &EventPluginsManager::eventRemoved, this, &DaysModel::onEventRemoved); connect(m_pluginsManager, &EventPluginsManager::pluginsChanged, this, &DaysModel::update); QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection); } QHash DaysModel::roleNames() const { return { {isCurrent, "isCurrent"}, {containsEventItems, "containsEventItems"}, {containsMajorEventItems, "containsMajorEventItems"}, {containsMinorEventItems, "containsMinorEventItems"}, {dayNumber, "dayNumber"}, {monthNumber, "monthNumber"}, {yearNumber, "yearNumber"} }; } diff --git a/src/declarativeimports/calendar/eventpluginsmanager.cpp b/src/declarativeimports/calendar/eventpluginsmanager.cpp index bdfcb6176..92238ebf1 100644 --- a/src/declarativeimports/calendar/eventpluginsmanager.cpp +++ b/src/declarativeimports/calendar/eventpluginsmanager.cpp @@ -1,277 +1,277 @@ /* Copyright (C) 2015 Martin Klapetek This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "eventpluginsmanager.h" #include #include #include #include #include #include #include #include #include class EventPluginsModel : public QAbstractListModel { Q_OBJECT public: EventPluginsModel(EventPluginsManager *manager) : QAbstractListModel(manager) { m_manager = manager; m_roles = QAbstractListModel::roleNames(); m_roles.insert(Qt::EditRole, QByteArrayLiteral("checked")); m_roles.insert(Qt::UserRole, QByteArrayLiteral("configUi")); m_roles.insert(Qt::UserRole + 1, QByteArrayLiteral("pluginPath")); } // make these two available to the manager void beginResetModel() { QAbstractListModel::beginResetModel(); } void endResetModel() { QAbstractListModel::endResetModel(); } QHash roleNames() const override { return m_roles; } Q_INVOKABLE int rowCount(const QModelIndex &parent = QModelIndex()) const override { Q_UNUSED(parent); return m_manager->m_availablePlugins.size(); } QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override { if (!index.isValid() && !m_manager) { return QVariant(); } const auto it = m_manager->m_availablePlugins.cbegin() + index.row(); const QString currentPlugin = it.key(); const EventPluginsManager::PluginData metadata = it.value(); switch (role) { case Qt::DisplayRole: return metadata.name; case Qt::ToolTipRole: return metadata.desc; case Qt::DecorationRole: return metadata.icon; case Qt::UserRole: { // The currentPlugin path contains the full path including // the plugin filename, so it needs to be cut off from the last '/' const QStringRef pathRef = currentPlugin.leftRef(currentPlugin.lastIndexOf(QLatin1Char('/'))); const QString qmlFilePath = metadata.configUi; return QString(pathRef % QLatin1Char('/') % qmlFilePath); } case Qt::UserRole + 1: return currentPlugin; case Qt::EditRole: return m_manager->m_enabledPlugins.contains(currentPlugin); } return QVariant(); } bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override { if (role != Qt::EditRole || !index.isValid()) { return false; } bool enabled = value.toBool(); const QString pluginPath = m_manager->m_availablePlugins.keys().at(index.row()); if (enabled) { if (!m_manager->m_enabledPlugins.contains(pluginPath)) { m_manager->m_enabledPlugins << pluginPath; } } else { m_manager->m_enabledPlugins.removeOne(pluginPath); } emit dataChanged(index, index); return true; } Q_INVOKABLE QVariant get(int row, const QByteArray &role) { return data(createIndex(row, 0), roleNames().key(role)); } private: EventPluginsManager *m_manager; QHash m_roles; }; EventPluginsManager::EventPluginsManager(QObject *parent) : QObject(parent) { auto plugins = KPluginLoader::findPlugins( QStringLiteral("plasmacalendarplugins"), [](const KPluginMetaData &md) { return md.serviceTypes().contains(QLatin1String("PlasmaCalendar/Plugin")); }); - Q_FOREACH (const KPluginMetaData &plugin, plugins) { + for (const KPluginMetaData &plugin : qAsConst(plugins)) { m_availablePlugins.insert(plugin.fileName(), { plugin.name(), plugin.description(), plugin.iconName(), plugin.value(QStringLiteral("X-KDE-PlasmaCalendar-ConfigUi")) }); } // Fallback for legacy pre-KPlugin plugins so we can still load them const QStringList paths = QCoreApplication::libraryPaths(); - Q_FOREACH (const QString &libraryPath, paths) { + for (const QString &libraryPath : paths) { const QString path(libraryPath + QStringLiteral("/plasmacalendarplugins")); QDir dir(path); if (!dir.exists()) { continue; } - QStringList entryList = dir.entryList(QDir::Files | QDir::NoDotAndDotDot); + const QStringList entryList = dir.entryList(QDir::Files | QDir::NoDotAndDotDot); - Q_FOREACH (const QString &fileName, entryList) { + for (const QString &fileName : entryList) { const QString absolutePath = dir.absoluteFilePath(fileName); if (m_availablePlugins.contains(absolutePath)) { continue; } QPluginLoader loader(absolutePath); // Load only our own plugins if (loader.metaData().value(QStringLiteral("IID")) == QLatin1String("org.kde.CalendarEventsPlugin")) { const auto md = loader.metaData().value(QStringLiteral("MetaData")).toObject(); m_availablePlugins.insert(absolutePath, { md.value(QStringLiteral("Name")).toString(), md.value(QStringLiteral("Description")).toString(), md.value(QStringLiteral("Icon")).toString(), md.value(QStringLiteral("ConfigUi")).toString() }); } } } m_model = new EventPluginsModel(this); } EventPluginsManager::~EventPluginsManager() { qDeleteAll(m_plugins); } void EventPluginsManager::populateEnabledPluginsList(const QStringList &pluginsList) { m_model->beginResetModel(); m_enabledPlugins = pluginsList; m_model->endResetModel(); } void EventPluginsManager::setEnabledPlugins(QStringList &pluginsList) { m_model->beginResetModel(); m_enabledPlugins = pluginsList; // Remove all already loaded plugins from the pluginsList // and unload those plugins that are not in the pluginsList auto i = m_plugins.begin(); while (i != m_plugins.end()) { const QString pluginPath = (*i)->property("pluginPath").toString(); if (pluginsList.contains(pluginPath)) { pluginsList.removeAll(pluginPath); ++i; } else { (*i)->deleteLater(); i = m_plugins.erase(i); } } // Now load all the plugins left in pluginsList - Q_FOREACH (const QString &pluginPath, pluginsList) { + for (const QString &pluginPath : qAsConst(pluginsList)) { loadPlugin(pluginPath); } m_model->endResetModel(); Q_EMIT pluginsChanged(); } QStringList EventPluginsManager::enabledPlugins() const { return m_enabledPlugins; } void EventPluginsManager::loadPlugin(const QString &absolutePath) { QPluginLoader loader(absolutePath); if (!loader.load()) { qWarning() << "Could not create Plasma Calendar Plugin: " << absolutePath; qWarning() << loader.errorString(); return; } QObject *obj = loader.instance(); if (obj) { CalendarEvents::CalendarEventsPlugin *eventsPlugin = qobject_cast(obj); if (eventsPlugin) { qDebug() << "Loading Calendar plugin" << eventsPlugin; eventsPlugin->setProperty("pluginPath", absolutePath); m_plugins << eventsPlugin; // Connect the relay signals connect(eventsPlugin, &CalendarEvents::CalendarEventsPlugin::dataReady, this, &EventPluginsManager::dataReady); connect(eventsPlugin, &CalendarEvents::CalendarEventsPlugin::eventModified, this, &EventPluginsManager::eventModified); connect(eventsPlugin, &CalendarEvents::CalendarEventsPlugin::eventRemoved, this, &EventPluginsManager::eventRemoved); } else { // not our/valid plugin, so unload it loader.unload(); } } else { loader.unload(); } } QList EventPluginsManager::plugins() const { return m_plugins; } QAbstractListModel* EventPluginsManager::pluginsModel() const { return m_model; } #include "eventpluginsmanager.moc" diff --git a/src/declarativeimports/core/datamodel.cpp b/src/declarativeimports/core/datamodel.cpp index 801e73880..0f3c7129e 100644 --- a/src/declarativeimports/core/datamodel.cpp +++ b/src/declarativeimports/core/datamodel.cpp @@ -1,586 +1,588 @@ /* * Copyright 2010 by Marco Martin * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "datamodel.h" #include "datasource.h" #include #include #include namespace Plasma { SortFilterModel::SortFilterModel(QObject *parent) : QSortFilterProxyModel(parent) { setObjectName(QStringLiteral("SortFilterModel")); setDynamicSortFilter(true); connect(this, &QAbstractItemModel::rowsInserted, this, &SortFilterModel::countChanged); connect(this, &QAbstractItemModel::rowsRemoved, this, &SortFilterModel::countChanged); connect(this, &QAbstractItemModel::modelReset, this, &SortFilterModel::countChanged); connect(this, &SortFilterModel::countChanged, this, &SortFilterModel::syncRoleNames); } SortFilterModel::~SortFilterModel() { } void SortFilterModel::syncRoleNames() { if (!sourceModel()) { return; } m_roleIds.clear(); const QHash rNames = roleNames(); m_roleIds.reserve(rNames.count()); for (auto i = rNames.constBegin(); i != rNames.constEnd(); ++i) { m_roleIds[QString::fromUtf8(i.value())] = i.key(); } setFilterRole(m_filterRole); setSortRole(m_sortRole); } QHash SortFilterModel::roleNames() const { if (sourceModel()) { return sourceModel()->roleNames(); } return {}; } int SortFilterModel::roleNameToId(const QString &name) const { return m_roleIds.value(name, Qt::DisplayRole); } void SortFilterModel::setModel(QAbstractItemModel *model) { if (model == sourceModel()) { return; } if (sourceModel()) { disconnect(sourceModel(), &QAbstractItemModel::modelReset, this, &SortFilterModel::syncRoleNames); } QSortFilterProxyModel::setSourceModel(model); if (model) { connect(model, &QAbstractItemModel::modelReset, this, &SortFilterModel::syncRoleNames); syncRoleNames(); } emit sourceModelChanged(model); } bool SortFilterModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const { if (m_filterCallback.isCallable()) { QJSValueList args; args << QJSValue(source_row); const QModelIndex idx = sourceModel()->index(source_row, filterKeyColumn(), source_parent); QQmlEngine *engine = QQmlEngine::contextForObject(this)->engine(); args << engine->toScriptValue(idx.data(m_roleIds.value(m_filterRole))); return const_cast(this)->m_filterCallback.call(args).toBool(); } return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent); } void SortFilterModel::setFilterRegExp(const QString &exp) { if (exp == filterRegExp()) { return; } QSortFilterProxyModel::setFilterRegExp(QRegExp(exp, Qt::CaseInsensitive)); Q_EMIT filterRegExpChanged(exp); } QString SortFilterModel::filterRegExp() const { return QSortFilterProxyModel::filterRegExp().pattern(); } void SortFilterModel::setFilterString(const QString &filterString) { if (filterString == m_filterString) { return; } m_filterString = filterString; QSortFilterProxyModel::setFilterFixedString(filterString); Q_EMIT filterStringChanged(filterString); } QString SortFilterModel::filterString() const { return m_filterString; } QJSValue SortFilterModel::filterCallback() const { return m_filterCallback; } void SortFilterModel::setFilterCallback(const QJSValue& callback) { if (m_filterCallback.strictlyEquals(callback)) { return; } if (!callback.isNull() && !callback.isCallable()) { return; } m_filterCallback = callback; invalidateFilter(); Q_EMIT filterCallbackChanged(callback); } void SortFilterModel::setFilterRole(const QString &role) { QSortFilterProxyModel::setFilterRole(roleNameToId(role)); m_filterRole = role; } QString SortFilterModel::filterRole() const { return m_filterRole; } void SortFilterModel::setSortRole(const QString &role) { m_sortRole = role; if (role.isEmpty()) { sort(-1, Qt::AscendingOrder); } else if (sourceModel()) { QSortFilterProxyModel::setSortRole(roleNameToId(role)); sort(sortColumn(), sortOrder()); } } QString SortFilterModel::sortRole() const { return m_sortRole; } void SortFilterModel::setSortOrder(const Qt::SortOrder order) { if (order == sortOrder()) { return; } sort(sortColumn(), order); } void SortFilterModel::setSortColumn(int column) { if (column == sortColumn()) { return; } sort(column, sortOrder()); emit sortColumnChanged(); } QVariantMap SortFilterModel::get(int row) const { QModelIndex idx = index(row, 0); QVariantMap hash; const QHash rNames = roleNames(); for (auto i = rNames.begin(); i != rNames.end(); ++i) { hash[QString::fromUtf8(i.value())] = data(idx, i.key()); } return hash; } int SortFilterModel::mapRowToSource(int row) const { QModelIndex idx = index(row, 0); return mapToSource(idx).row(); } int SortFilterModel::mapRowFromSource(int row) const { if (!sourceModel()) { qWarning() << "No source model defined!"; return -1; } QModelIndex idx = sourceModel()->index(row, 0); return mapFromSource(idx).row(); } DataModel::DataModel(QObject *parent) : QAbstractItemModel(parent), m_dataSource(nullptr), m_maxRoleId(Qt::UserRole + 1) { //There is one reserved role name: DataEngineSource m_roleNames[m_maxRoleId] = QByteArrayLiteral("DataEngineSource"); m_roleIds[QStringLiteral("DataEngineSource")] = m_maxRoleId; ++m_maxRoleId; setObjectName(QStringLiteral("DataModel")); connect(this, &QAbstractItemModel::rowsInserted, this, &DataModel::countChanged); connect(this, &QAbstractItemModel::rowsRemoved, this, &DataModel::countChanged); connect(this, &QAbstractItemModel::modelReset, this, &DataModel::countChanged); } DataModel::~DataModel() { } void DataModel::dataUpdated(const QString &sourceName, const QVariantMap &data) { if (!m_sourceFilter.isEmpty() && m_sourceFilterRE.isValid() && !m_sourceFilterRE.exactMatch(sourceName)) { return; } if (m_keyRoleFilter.isEmpty()) { //an item is represented by a source: keys are roles m_roleLevel == FirstLevel QVariantList list; if (!m_dataSource->data()->isEmpty()) { - foreach (const QString &key, m_dataSource->data()->keys()) { + const auto lst = m_dataSource->data()->keys(); + for (const QString &key : lst) { if (!m_sourceFilter.isEmpty() && m_sourceFilterRE.isValid() && !m_sourceFilterRE.exactMatch(key)) { continue; } QVariant value = m_dataSource->data()->value(key); if (value.isValid() && value.canConvert()) { Plasma::DataEngine::Data data = value.value(); data[QStringLiteral("DataEngineSource")] = key; list.append(data); } } } setItems(QString(), list); } else { //a key that matches the one we want exists and is a list of DataEngine::Data if (data.contains(m_keyRoleFilter) && data.value(m_keyRoleFilter).canConvert()) { setItems(sourceName, data.value(m_keyRoleFilter).value()); } else if (m_keyRoleFilterRE.isValid()) { //try to match the key we want with a regular expression if set QVariantList list; QVariantMap::const_iterator i; for (i = data.constBegin(); i != data.constEnd(); ++i) { if (m_keyRoleFilterRE.exactMatch(i.key())) { list.append(i.value()); } } setItems(sourceName, list); } } } void DataModel::setDataSource(QObject *object) { DataSource *source = qobject_cast(object); if (!source) { qWarning() << "Error: DataSource type expected"; return; } if (m_dataSource == source) { return; } if (m_dataSource) { disconnect(m_dataSource, nullptr, this, nullptr); } m_dataSource = source; - foreach (const QString &key, m_dataSource->data()->keys()) { + const auto keys = m_dataSource->data()->keys(); + for (const QString &key : keys) { dataUpdated(key, m_dataSource->data()->value(key).value()); } connect(m_dataSource, &DataSource::newData, this, &DataModel::dataUpdated); connect(m_dataSource, &DataSource::sourceRemoved, this, &DataModel::removeSource); connect(m_dataSource, &DataSource::sourceDisconnected, this, &DataModel::removeSource); } QObject *DataModel::dataSource() const { return m_dataSource; } void DataModel::setKeyRoleFilter(const QString &key) { // the "key role filter" can be used in one of three ways: // // 1) empty string -> all data is used, each source is one row in the model // 2) matches a key in the data exactly -> only that key/value pair is used, and the value is // treated as a collection where each item in the collection becomes a row in the model // 3) regular expression -> matches zero or more keys in the data, and each matching key/value // pair becomes a row in the model if (m_keyRoleFilter == key) { return; } m_keyRoleFilter = key; m_keyRoleFilterRE = QRegExp(m_keyRoleFilter); } QString DataModel::keyRoleFilter() const { return m_keyRoleFilter; } void DataModel::setSourceFilter(const QString &key) { if (m_sourceFilter == key) { return; } m_sourceFilter = key; m_sourceFilterRE = QRegExp(key); /* FIXME: if the user changes the source filter, it won't immediately be reflected in the available data if (m_sourceFilterRE.isValid()) { .. iterate through all items and weed out the ones that don't match .. } */ } QString DataModel::sourceFilter() const { return m_sourceFilter; } void DataModel::setItems(const QString &sourceName, const QVariantList &list) { const int oldLength = m_items.value(sourceName).count(); const int delta = list.length() - oldLength; const bool firstRun = m_items.isEmpty(); //At what row number the first item associated to this source starts int sourceIndex = 0; QMap >::const_iterator i; for (i = m_items.constBegin(); i != m_items.constEnd(); ++i) { if (i.key() == sourceName) { break; } sourceIndex += i.value().count(); } //signal as inserted the rows at the end, all the other rows will signal a dataupdated. //better than a model reset because doesn't cause deletion and re-creation of every list item on a qml ListView, repeaters etc. //the first run it gets reset because otherwise setRoleNames gets broken if (firstRun) { beginResetModel(); } else if (delta > 0) { beginInsertRows(QModelIndex(), sourceIndex + oldLength, sourceIndex + list.length() - 1); } else if (delta < 0) { beginRemoveRows(QModelIndex(), sourceIndex + list.length(), sourceIndex + oldLength - 1); } //convert to vector, so data() will be O(1) m_items[sourceName] = list.toVector(); if (!list.isEmpty()) { if (list.first().canConvert()) { - foreach (const QVariant &item, list) { + for (const QVariant &item : list) { const QVariantMap &vh = item.value(); QMapIterator it(vh); while (it.hasNext()) { it.next(); const QString &roleName = it.key(); if (!m_roleIds.contains(roleName)) { ++m_maxRoleId; m_roleNames[m_maxRoleId] = roleName.toLatin1(); m_roleIds[roleName] = m_maxRoleId; } } } } else { - foreach (const QVariant &item, list) { + for (const QVariant &item : list) { const QVariantMap &vh = item.value(); QMapIterator it(vh); while (it.hasNext()) { it.next(); const QString &roleName = it.key(); if (!m_roleIds.contains(roleName)) { ++m_maxRoleId; m_roleNames[m_maxRoleId] = roleName.toLatin1(); m_roleIds[roleName] = m_maxRoleId; } } } } } if (firstRun) { endResetModel(); } else if (delta > 0) { endInsertRows(); } else if (delta < 0) { endRemoveRows(); } emit dataChanged(createIndex(sourceIndex, 0), createIndex(sourceIndex + qMin(list.length(), oldLength), 0)); } QHash DataModel::roleNames() const { return m_roleNames; } void DataModel::removeSource(const QString &sourceName) { //FIXME: find a way to remove only the proper things also in the case where sources are items if (m_keyRoleFilter.isEmpty()) { //source name in the map, linear scan for (int i = 0; i < m_items.value(QString()).count(); ++i) { if (m_items.value(QString())[i].value().value(QStringLiteral("DataEngineSource")) == sourceName) { beginRemoveRows(QModelIndex(), i, i); m_items[QString()].remove(i); endRemoveRows(); break; } } } else { if (m_items.contains(sourceName)) { //At what row number the first item associated to this source starts int sourceIndex = 0; for (auto i = m_items.constBegin(); i != m_items.constEnd(); ++i) { if (i.key() == sourceName) { break; } sourceIndex += i.value().count(); } //source name as key of the map int count = m_items.value(sourceName).count(); if (count > 0) { beginRemoveRows(QModelIndex(), sourceIndex, sourceIndex + count - 1); } m_items.remove(sourceName); if (count > 0) { endRemoveRows(); } } } } QVariant DataModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.column() > 0 || index.row() < 0 || index.row() >= countItems()) { return QVariant(); } int count = 0; int actualRow = 0; QString source; QMap >::const_iterator i; for (i = m_items.constBegin(); i != m_items.constEnd(); ++i) { const int oldCount = count; count += i.value().count(); if (index.row() < count) { source = i.key(); actualRow = index.row() - oldCount; break; } } //is it the reserved role: DataEngineSource ? //also, if each source is an item DataEngineSource is a role between all the others, otherwise we know it from the role variable if (!m_keyRoleFilter.isEmpty() && m_roleNames.value(role) == "DataEngineSource") { return source; } else { return m_items.value(source).value(actualRow).value().value(QString::fromUtf8(m_roleNames.value(role))); } } QVariant DataModel::headerData(int section, Qt::Orientation orientation, int role) const { Q_UNUSED(section) Q_UNUSED(orientation) Q_UNUSED(role) return QVariant(); } QModelIndex DataModel::index(int row, int column, const QModelIndex &parent) const { if (parent.isValid() || column > 0 || row < 0 || row >= countItems()) { return QModelIndex(); } return createIndex(row, column); } QModelIndex DataModel::parent(const QModelIndex &child) const { Q_UNUSED(child) return QModelIndex(); } int DataModel::rowCount(const QModelIndex &parent) const { //this is not a tree //TODO: make it possible some day? if (parent.isValid()) { return 0; } return countItems(); } int DataModel::columnCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return 1; } QVariantMap DataModel::get(int row) const { QModelIndex idx = index(row, 0); QVariantMap map; const QHash rNames = roleNames(); for (auto i = rNames.constBegin(); i != rNames.constEnd(); ++i) { map[QString::fromUtf8(i.value())] = data(idx, i.key()); } return map; } } diff --git a/src/declarativeimports/core/datamodel.h b/src/declarativeimports/core/datamodel.h index 877f8d3a7..49192ad44 100644 --- a/src/declarativeimports/core/datamodel.h +++ b/src/declarativeimports/core/datamodel.h @@ -1,271 +1,271 @@ /* * Copyright 2010 by Marco MArtin * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef DATAMODEL_H #define DATAMODEL_H #include #include #include #include #include class QTimer; namespace Plasma { class DataSource; class DataModel; /** * @class SortFilterModel * @short Filter and sort an existing QAbstractItemModel */ class SortFilterModel : public QSortFilterProxyModel { Q_OBJECT /** * The source model of this sorting proxy model. It has to inherit QAbstractItemModel (ListModel is not supported) */ Q_PROPERTY(QAbstractItemModel *sourceModel READ sourceModel WRITE setModel NOTIFY sourceModelChanged) /** * The regular expression for the filter, only items with their filterRole matching filterRegExp will be displayed */ Q_PROPERTY(QString filterRegExp READ filterRegExp WRITE setFilterRegExp NOTIFY filterRegExpChanged) /** * The string for the filter, only items with their filterRole matching filterString will be displayed */ Q_PROPERTY(QString filterString READ filterString WRITE setFilterString NOTIFY filterStringChanged REVISION 1) /** * A JavaScript callable that is passed the source model row index as first argument and the value * of filterRole as second argument. The callable's return value is evaluated as boolean to determine * whether the row is accepted (true) or filtered out (false). It overrides the default implementation * that uses filterRegExp or filterString; while filterCallable is set those two properties are * ignored. Attempts to write a non-callable to this property are silently ignored, but you can set * it to null. */ Q_PROPERTY(QJSValue filterCallback READ filterCallback WRITE setFilterCallback NOTIFY filterCallbackChanged REVISION 1) /** * The role of the sourceModel on which filterRegExp must be applied. */ Q_PROPERTY(QString filterRole READ filterRole WRITE setFilterRole) /** * The role of the sourceModel that will be used for sorting. if empty the order will be left unaltered */ Q_PROPERTY(QString sortRole READ sortRole WRITE setSortRole) /** * One of Qt.Ascending or Qt.Descending */ Q_PROPERTY(Qt::SortOrder sortOrder READ sortOrder WRITE setSortOrder) /** * Specify which column should be used for sorting */ Q_PROPERTY(int sortColumn READ sortColumn WRITE setSortColumn NOTIFY sortColumnChanged) /** * How many items are in this model */ Q_PROPERTY(int count READ count NOTIFY countChanged) friend class DataModel; public: explicit SortFilterModel(QObject *parent = nullptr); ~SortFilterModel() override; void setModel(QAbstractItemModel *source); void setFilterRegExp(const QString &exp); QString filterRegExp() const; void setFilterString(const QString &filterString); QString filterString() const; void setFilterCallback(const QJSValue &callback); QJSValue filterCallback() const; void setFilterRole(const QString &role); QString filterRole() const; void setSortRole(const QString &role); QString sortRole() const; void setSortOrder(const Qt::SortOrder order); void setSortColumn(int column); int count() const { return QSortFilterProxyModel::rowCount(); } /** * Returns the item at index in the list model. * This allows the item data to be accessed (but not modified) from JavaScript. * It returns an Object with a property for each role. * * @param i the row we want */ Q_INVOKABLE QVariantMap get(int i) const; Q_INVOKABLE int mapRowToSource(int i) const; Q_INVOKABLE int mapRowFromSource(int i) const; Q_SIGNALS: void countChanged(); void sortColumnChanged(); void sourceModelChanged(QObject *); void filterRegExpChanged(const QString &); Q_REVISION(1) void filterStringChanged(const QString &); Q_REVISION(1) void filterCallbackChanged(const QJSValue &); protected: int roleNameToId(const QString &name) const; bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; QHash roleNames() const override; protected Q_SLOTS: void syncRoleNames(); private: QString m_filterRole; QString m_sortRole; QString m_filterString; QJSValue m_filterCallback; QHash m_roleIds; }; /** * @class DataModel * @short DataSource data as a model */ class DataModel : public QAbstractItemModel { Q_OBJECT /** * The instance of DataSource to construct this model on */ Q_PROPERTY(QObject *dataSource READ dataSource WRITE setDataSource) /** * It's a regular expression. Only data with keys that match this filter expression will be inserted in the model */ Q_PROPERTY(QString keyRoleFilter READ keyRoleFilter WRITE setKeyRoleFilter) /** * it's a regular expression. If the DataSource is connected to more than one source, only inserts data from sources matching this filter expression in the model. * If we want to have a source watch all sources beginning with say "name:", the required regexp would be sourceFilter: "name:.*" */ Q_PROPERTY(QString sourceFilter READ sourceFilter WRITE setSourceFilter) /** * How many items are in this model */ Q_PROPERTY(int count READ count NOTIFY countChanged) public: DataModel(QObject *parent = nullptr); ~DataModel(); void setDataSource(QObject *source); QObject *dataSource() const; /** * Include only items with a key that matches this regexp in the model */ void setKeyRoleFilter(const QString &key); QString keyRoleFilter() const; /** * Include only sources that matches this regexp in the model */ void setSourceFilter(const QString &key); QString sourceFilter() const; //Reimplemented QVariant data(const QModelIndex &index, int role) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex &child) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; int count() const { return countItems(); } /** * Returns the item at index in the list model. * This allows the item data to be accessed (but not modified) from JavaScript. * It returns an Object with a property for each role. * * @param i the row we want */ Q_INVOKABLE QVariantMap get(int i) const; protected: void setItems(const QString &sourceName, const QVariantList &list); inline int countItems() const; QHash roleNames() const override; Q_SIGNALS: void countChanged(); void sourceModelChanged(QObject *); void filterRegExpChanged(const QString &); private Q_SLOTS: void dataUpdated(const QString &sourceName, const QVariantMap &data); void removeSource(const QString &sourceName); private: DataSource *m_dataSource; QString m_keyRoleFilter; QRegExp m_keyRoleFilterRE; QString m_sourceFilter; QRegExp m_sourceFilterRE; QMap > m_items; QHash m_roleNames; QHash m_roleIds; int m_maxRoleId; }; int DataModel::countItems() const { int count = 0; - foreach (const QVector &v, m_items) { + for (const QVector &v : qAsConst(m_items)) { count += v.count(); } return count; } } #endif diff --git a/src/declarativeimports/core/datasource.cpp b/src/declarativeimports/core/datasource.cpp index 72d4be30d..b98501b4a 100644 --- a/src/declarativeimports/core/datasource.cpp +++ b/src/declarativeimports/core/datasource.cpp @@ -1,267 +1,267 @@ /* * Copyright 2009 by Alan Alpert * Copyright 2010 by Ménard Alexis * Copyright 2010 by Marco Martin * Copyright 2013 by Sebastian Kügler * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "datasource.h" namespace Plasma { DataSource::DataSource(QObject *parent) : QObject(parent), m_ready(false), m_interval(0), m_intervalAlignment(Plasma::Types::NoAlignment) { m_models = new QQmlPropertyMap(this); m_data = new QQmlPropertyMap(this); setObjectName(QStringLiteral("DataSource")); } void DataSource::classBegin() { } void DataSource::componentComplete() { m_ready = true; setupData(); } void DataSource::setConnectedSources(const QStringList &sources) { bool sourcesChanged = false; - foreach (const QString &source, sources) { + for (const QString &source : sources) { if (!m_connectedSources.contains(source)) { sourcesChanged = true; if (m_dataEngine) { m_connectedSources.append(source); m_dataEngine->connectSource(source, this, m_interval, m_intervalAlignment); emit sourceConnected(source); } } } - foreach (const QString &source, m_connectedSources) { + for (const QString &source : qAsConst(m_connectedSources)) { if (!sources.contains(source)) { m_data->clear(source); sourcesChanged = true; if (m_dataEngine) { m_dataEngine->disconnectSource(source, this); emit sourceDisconnected(source); } } } if (sourcesChanged) { m_connectedSources = sources; emit connectedSourcesChanged(); } } void DataSource::setEngine(const QString &e) { if (e == m_engine) { return; } m_engine = e; if (m_engine.isEmpty()) { emit engineChanged(); return; } m_dataEngineConsumer.reset(new Plasma::DataEngineConsumer()); Plasma::DataEngine *engine = dataEngine(m_engine); if (!engine) { qWarning() << "DataEngine" << m_engine << "not found"; emit engineChanged(); return; } if (m_dataEngine) { m_dataEngine->disconnect(this); // Deleting the consumer triggers the reference counting m_dataEngineConsumer.reset(); } /* * It is due little explanation why this is a queued connection: * If sourceAdded arrives immediately, in the case we have a datamodel * with items at source level we connect too soon (before setData for * all roles is done), having a dataupdated in the datamodel with only * the first role, killing off the other roles. * Besides causing a model reset more, unfortunately setRoleNames can be done a single time, so is not possible adding new roles after the * first setRoleNames() is called. * This fixes engines that have 1 item per source like the * recommendations engine. */ m_dataEngine = engine; connect(m_dataEngine, &DataEngine::sourceAdded, this, &DataSource::updateSources, Qt::QueuedConnection); connect(m_dataEngine, &DataEngine::sourceRemoved, this, &DataSource::updateSources); connect(m_dataEngine, &DataEngine::sourceAdded, this, &DataSource::sourceAdded, Qt::QueuedConnection); connect(m_dataEngine, &DataEngine::sourceRemoved, this, &DataSource::removeSource); connect(m_dataEngine, &DataEngine::sourceRemoved, this, &DataSource::sourceRemoved); updateSources(); emit engineChanged(); } void DataSource::setInterval(const int interval) { if (interval == m_interval) { return; } m_interval = interval; setupData(); emit intervalChanged(); } void DataSource::setIntervalAlignment(Plasma::Types::IntervalAlignment intervalAlignment) { if (intervalAlignment == m_intervalAlignment) { return; } m_intervalAlignment = intervalAlignment; setupData(); emit intervalAlignmentChanged(); } void DataSource::setupData() { if (!m_ready) { return; } // qDebug() << " loading engine " << m_engine; //FIXME: should all services be deleted just because we're changing the interval, etc? qDeleteAll(m_services); m_services.clear(); - foreach (const QString &source, m_connectedSources) { + for (const QString &source : qAsConst(m_connectedSources)) { m_dataEngine->connectSource(source, this, m_interval, m_intervalAlignment); emit sourceConnected(source); } } void DataSource::dataUpdated(const QString &sourceName, const Plasma::DataEngine::Data &data) { //it can arrive also data we don't explicitly connected a source if (m_connectedSources.contains(sourceName)) { m_data->insert(sourceName, data); emit dataChanged(); emit newData(sourceName, data); } else if (m_dataEngine) { m_dataEngine->disconnectSource(sourceName, this); } } void DataSource::modelChanged(const QString &sourceName, QAbstractItemModel *model) { if (!model) { m_models->clear(sourceName); return; } m_models->insert(sourceName, QVariant::fromValue(model)); //FIXME: this will break in the case a second model is set connect(model, &QObject::destroyed, m_models, [ = ]() { m_models->clear(sourceName); }); } void DataSource::removeSource(const QString &source) { m_data->clear(source); m_models->clear(source); //TODO: emit those signals as last thing if (m_connectedSources.contains(source)) { m_connectedSources.removeAll(source); emit sourceDisconnected(source); emit connectedSourcesChanged(); } if (m_dataEngine) { QHash::iterator it = m_services.find(source); if (it != m_services.end()) { delete it.value(); m_services.erase(it); } } } QObject *DataSource::serviceForSource(const QString &source) { if (!m_services.contains(source)) { Plasma::Service *service = m_dataEngine->serviceForSource(source); if (!service) { return nullptr; } m_services[source] = service; } return m_services.value(source); } void DataSource::connectSource(const QString &source) { if (m_connectedSources.contains(source)) { return; } m_connectedSources.append(source); if (m_dataEngine) { m_dataEngine->connectSource(source, this, m_interval, m_intervalAlignment); emit sourceConnected(source); emit connectedSourcesChanged(); } } void DataSource::disconnectSource(const QString &source) { if (m_dataEngine && m_connectedSources.contains(source)) { m_connectedSources.removeAll(source); m_dataEngine->disconnectSource(source, this); emit sourceDisconnected(source); emit connectedSourcesChanged(); } } void DataSource::updateSources() { QStringList sources; if (m_dataEngine) { sources = m_dataEngine->sources(); } if (sources != m_sources) { m_sources = sources; emit sourcesChanged(); } } } diff --git a/src/declarativeimports/core/iconitem.cpp b/src/declarativeimports/core/iconitem.cpp index a65fa5554..98dd42b37 100644 --- a/src/declarativeimports/core/iconitem.cpp +++ b/src/declarativeimports/core/iconitem.cpp @@ -1,707 +1,707 @@ /* * Copyright 2012 Marco Martin * Copyright 2014 David Edmundson * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "iconitem.h" #include #include #include #include #include #include #include #include #include #include #include #include "fadingnode_p.h" #include #include "theme.h" #include "units.h" IconItem::IconItem(QQuickItem *parent) : QQuickItem(parent), m_svgIcon(nullptr), m_status(Plasma::Svg::Normal), m_active(false), m_animated(true), m_usesPlasmaTheme(true), m_roundToIconSize(true), m_textureChanged(false), m_sizeChanged(false), m_allowNextAnimation(false), m_blockNextAnimation(false), m_implicitHeightSetByUser(false), m_implicitWidthSetByUser(false), m_colorGroup(Plasma::Theme::NormalColorGroup), m_animValue(0) { m_animation = new QPropertyAnimation(this); connect(m_animation, &QPropertyAnimation::valueChanged, this, &IconItem::valueChanged); connect(m_animation, &QPropertyAnimation::finished, this, &IconItem::animationFinished); m_animation->setTargetObject(this); m_animation->setEasingCurve(QEasingCurve::InOutQuad); m_animation->setDuration(250); //FIXME from theme setFlag(ItemHasContents, true); connect(KIconLoader::global(), &KIconLoader::iconLoaderSettingsChanged, this, &IconItem::updateImplicitSize); connect(this, &IconItem::implicitWidthChanged, this, &IconItem::implicitWidthChanged2); connect(this, &IconItem::implicitHeightChanged, this, &IconItem::implicitHeightChanged2); updateImplicitSize(); } IconItem::~IconItem() { } void IconItem::updateImplicitSize() { if (!m_imageIcon.isNull()) { const QSize &s = m_imageIcon.size(); if (s.isValid()) { if (!m_implicitWidthSetByUser && !m_implicitHeightSetByUser) { setImplicitSize(s.width(), s.height()); } else if (!m_implicitWidthSetByUser) { setImplicitWidth(s.width()); } else if (!m_implicitHeightSetByUser) { setImplicitHeight(s.height()); } return; } } else if (m_svgIcon) { // FIXME: Check Svg::isValid()? Considered expensive by apidox. //resize() resets the icon to its implicit size, specified m_svgIcon->resize(); QSize s; const QString &sourceString = m_source.toString(); //plasma theme icon, where one file contains multiple images if (m_svgIcon->hasElement(sourceString)) { s = m_svgIcon->elementSize(sourceString); //normal icon: one image per file, page size is icon size } else { s = m_svgIcon->size(); } if (s.isValid()) { if (!m_implicitWidthSetByUser && !m_implicitHeightSetByUser) { setImplicitSize(s.width(), s.height()); } else if (!m_implicitWidthSetByUser) { setImplicitWidth(s.width()); } else if (!m_implicitHeightSetByUser) { setImplicitHeight(s.height()); } return; } } // Fall back to initializing implicit size to the Dialog size. const int implicitSize = KIconLoader::global()->currentSize(KIconLoader::Dialog); if (!m_implicitWidthSetByUser && !m_implicitHeightSetByUser) { setImplicitSize(implicitSize, implicitSize); } else if (!m_implicitWidthSetByUser) { setImplicitWidth(implicitSize); } else if (!m_implicitHeightSetByUser) { setImplicitHeight(implicitSize); } } void IconItem::setSource(const QVariant &source) { if (source == m_source) { return; } disconnect(KIconLoader::global(), &KIconLoader::iconChanged, this, &IconItem::iconLoaderIconChanged); const bool oldValid = isValid(); m_source = source; QString sourceString = source.toString(); // If the QIcon was created with QIcon::fromTheme(), try to load it as svg if (source.canConvert() && !source.value().name().isEmpty()) { sourceString = source.value().name(); } if (!sourceString.isEmpty()) { // If a file:// URL or a absolute path is passed, take the image pointed by that from disk QString localFile; if (sourceString.startsWith(QLatin1String("file:"))) { localFile = QUrl(sourceString).toLocalFile(); } else if (sourceString.startsWith(QLatin1Char('/'))) { localFile = sourceString; } if (!localFile.isEmpty()) { if (sourceString.endsWith(QLatin1String(".svg")) || sourceString.endsWith(QLatin1String(".svgz"))) { m_icon = QIcon(localFile); m_imageIcon = QImage(); } else { m_icon = QIcon(); m_imageIcon = QImage(localFile); } m_svgIconName.clear(); delete m_svgIcon; m_svgIcon = nullptr; } else { if (!m_svgIcon) { m_svgIcon = new Plasma::Svg(this); m_svgIcon->setColorGroup(m_colorGroup); m_svgIcon->setStatus(m_status); m_svgIcon->setDevicePixelRatio((window() ? window()->devicePixelRatio() : qApp->devicePixelRatio())); connect(m_svgIcon, &Plasma::Svg::repaintNeeded, this, &IconItem::schedulePixmapUpdate); } if (m_usesPlasmaTheme) { //try as a svg icon from plasma theme m_svgIcon->setImagePath(QLatin1String("icons/") + sourceString.section(QLatin1Char('-'), 0, 0)); m_svgIcon->setContainsMultipleImages(true); } //success? if (m_usesPlasmaTheme && m_svgIcon->isValid() && m_svgIcon->hasElement(sourceString)) { m_icon = QIcon(); m_imageIcon = QImage(); m_svgIconName = sourceString; //ok, svg not available from the plasma theme } else { //try to load from iconloader an svg with Plasma::Svg const auto *iconTheme = KIconLoader::global()->theme(); QString iconPath; if (iconTheme) { iconPath = iconTheme->iconPath(sourceString + QLatin1String(".svg"), qMin(width(), height()), KIconLoader::MatchBest); if (iconPath.isEmpty()) { iconPath = iconTheme->iconPath(sourceString + QLatin1String(".svgz"), qMin(width(), height()), KIconLoader::MatchBest); } } else { qWarning() << "KIconLoader has no theme set"; } if (!iconPath.isEmpty()) { m_svgIcon->setImagePath(iconPath); m_svgIconName = sourceString; //fail, use QIcon } else { //if we started with a QIcon use that. m_icon = source.value(); if (m_icon.isNull()) { m_icon = QIcon::fromTheme(sourceString); } //since QIcon is rendered by KIconLoader, watch for when its configuration changes now and reload as needed. connect(KIconLoader::global(), &KIconLoader::iconChanged, this, &IconItem::iconLoaderIconChanged); m_svgIconName.clear(); delete m_svgIcon; m_svgIcon = nullptr; m_imageIcon = QImage(); } } } } else if (source.canConvert()) { m_icon = source.value(); m_imageIcon = QImage(); m_svgIconName.clear(); delete m_svgIcon; m_svgIcon = nullptr; } else if (source.canConvert()) { m_icon = QIcon(); m_imageIcon = source.value(); m_svgIconName.clear(); delete m_svgIcon; m_svgIcon = nullptr; } else { m_icon = QIcon(); m_imageIcon = QImage(); m_svgIconName.clear(); delete m_svgIcon; m_svgIcon = nullptr; } if (width() > 0 && height() > 0) { schedulePixmapUpdate(); } updateImplicitSize(); emit sourceChanged(); if (isValid() != oldValid) { emit validChanged(); } } QVariant IconItem::source() const { return m_source; } void IconItem::setColorGroup(Plasma::Theme::ColorGroup group) { if (m_colorGroup == group) { return; } m_colorGroup = group; if (m_svgIcon) { m_svgIcon->setColorGroup(group); } emit colorGroupChanged(); } Plasma::Theme::ColorGroup IconItem::colorGroup() const { return m_colorGroup; } void IconItem::setOverlays(const QStringList &overlays) { if (overlays == m_overlays) { return; } m_overlays = overlays; schedulePixmapUpdate(); emit overlaysChanged(); } QStringList IconItem::overlays() const { return m_overlays; } bool IconItem::isActive() const { return m_active; } void IconItem::setActive(bool active) { if (m_active == active) { return; } m_active = active; if (isComponentComplete()) { m_allowNextAnimation = true; schedulePixmapUpdate(); } emit activeChanged(); } bool IconItem::isAnimated() const { return m_animated; } void IconItem::setAnimated(bool animated) { if (m_animated == animated) { return; } m_animated = animated; emit animatedChanged(); } bool IconItem::usesPlasmaTheme() const { return m_usesPlasmaTheme; } void IconItem::setUsesPlasmaTheme(bool usesPlasmaTheme) { if (m_usesPlasmaTheme == usesPlasmaTheme) { return; } m_usesPlasmaTheme = usesPlasmaTheme; // Reload icon with new settings const QVariant src = m_source; m_source.clear(); setSource(src); update(); emit usesPlasmaThemeChanged(); } bool IconItem::roundToIconSize() const { return m_roundToIconSize; } void IconItem::setRoundToIconSize(bool roundToIconSize) { if (m_roundToIconSize == roundToIconSize) { return; } const QSize oldPaintedSize = paintedSize(); m_roundToIconSize = roundToIconSize; emit roundToIconSizeChanged(); if (oldPaintedSize != paintedSize()) { emit paintedSizeChanged(); } schedulePixmapUpdate(); } bool IconItem::isValid() const { return !m_icon.isNull() || m_svgIcon || !m_imageIcon.isNull(); } int IconItem::paintedWidth() const { return paintedSize(boundingRect().size()).width(); } int IconItem::paintedHeight() const { return paintedSize(boundingRect().size()).height(); } QSize IconItem::paintedSize(const QSizeF &containerSize) const { const QSize &actualContainerSize = (containerSize.isValid() ? containerSize : boundingRect().size()).toSize(); const QSize paintedSize = m_iconPixmap.size().scaled(actualContainerSize, Qt::KeepAspectRatio); const int width = paintedSize.width(); const int height = paintedSize.height(); if (width == height) { if (m_roundToIconSize) { return QSize(Units::roundToIconSize(width), Units::roundToIconSize(height)); } else { return QSize(width, height); } } // if we don't have a square image, we still want it to be rounded to icon size // but we cannot just blindly round both as we might erroneously change a 50x45 image to be 48x32 // instead, round the bigger of the two and then downscale the smaller with the ratio if (width > height) { const int roundedWidth = m_roundToIconSize ? Units::roundToIconSize(width) : width; return QSize(roundedWidth, qRound(height * (roundedWidth / static_cast(width)))); } else { const int roundedHeight = m_roundToIconSize ? Units::roundToIconSize(height) : height; return QSize(qRound(width * (roundedHeight / static_cast(height))), roundedHeight); } } void IconItem::setStatus(Plasma::Svg::Status status) { if (m_status == status) { return; } m_status = status; if (m_svgIcon) { m_svgIcon->setStatus(status); } emit statusChanged(); } Plasma::Svg::Status IconItem::status() const { return m_status; } void IconItem::setImplicitHeight2(int height) { m_implicitHeightSetByUser = true; setImplicitHeight(height); emit implicitHeightChanged2(); } void IconItem::setImplicitWidth2(int width) { m_implicitWidthSetByUser = true; setImplicitWidth(width); emit implicitWidthChanged2(); } void IconItem::updatePolish() { QQuickItem::updatePolish(); loadPixmap(); } QSGNode* IconItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData) { Q_UNUSED(updatePaintNodeData) if (m_iconPixmap.isNull() || width() == 0.0 || height() == 0.0) { delete oldNode; return nullptr; } if (m_animation->state() == QAbstractAnimation::Running) { FadingNode *animatingNode = dynamic_cast(oldNode); if (!animatingNode || m_textureChanged) { delete oldNode; QSGTexture *source = window()->createTextureFromImage(m_oldIconPixmap.toImage(), QQuickWindow::TextureCanUseAtlas); source->setFiltering(smooth() ? QSGTexture::Linear : QSGTexture::Nearest); QSGTexture *target = window()->createTextureFromImage(m_iconPixmap.toImage(), QQuickWindow::TextureCanUseAtlas); target->setFiltering(smooth() ? QSGTexture::Linear : QSGTexture::Nearest); animatingNode = new FadingNode(source, target); m_sizeChanged = true; m_textureChanged = false; } animatingNode->setProgress(m_animValue); if (m_sizeChanged) { const QSize newSize = paintedSize(); const QRect destRect(QPointF(boundingRect().center() - QPointF(newSize.width(), newSize.height()) / 2).toPoint(), newSize); animatingNode->setRect(destRect); m_sizeChanged = false; } return animatingNode; } else { ManagedTextureNode *textureNode = dynamic_cast(oldNode); if (!textureNode || m_textureChanged) { delete oldNode; textureNode = new ManagedTextureNode; textureNode->setTexture(QSharedPointer(window()->createTextureFromImage(m_iconPixmap.toImage(), QQuickWindow::TextureCanUseAtlas))); m_sizeChanged = true; m_textureChanged = false; } textureNode->setFiltering(smooth() ? QSGTexture::Linear : QSGTexture::Nearest); if (m_sizeChanged) { const QSize newSize = paintedSize(); const QRect destRect(QPointF(boundingRect().center() - QPointF(newSize.width(), newSize.height()) / 2).toPoint(), newSize); textureNode->setRect(destRect); m_sizeChanged = false; } return textureNode; } } void IconItem::valueChanged(const QVariant &value) { m_animValue = value.toReal(); update(); } void IconItem::onEnabledChanged() { m_allowNextAnimation = true; schedulePixmapUpdate(); } void IconItem::animationFinished() { m_oldIconPixmap = QPixmap(); m_textureChanged = true; update(); } void IconItem::iconLoaderIconChanged(int group) { Q_UNUSED(group); schedulePixmapUpdate(); } void IconItem::windowVisibleChanged(bool visible) { if (visible) { m_blockNextAnimation = true; } } void IconItem::schedulePixmapUpdate() { polish(); } void IconItem::loadPixmap() { if (!isComponentComplete()) { return; } int size = qMin(qRound(width()), qRound(height())); if (m_roundToIconSize) { size = Units::roundToIconSize(size); } //final pixmap to paint QPixmap result; if (size <= 0) { m_iconPixmap = QPixmap(); m_animation->stop(); update(); return; } else if (m_svgIcon) { m_svgIcon->setDevicePixelRatio(window() ? window()->devicePixelRatio() : qApp->devicePixelRatio()); m_svgIcon->resize(size, size); if (!m_svgIconName.isEmpty() && m_svgIcon->hasElement(m_svgIconName)) { result = m_svgIcon->pixmap(m_svgIconName); } else if (!m_svgIconName.isEmpty()) { const auto *iconTheme = KIconLoader::global()->theme(); if (iconTheme) { QString iconPath = iconTheme->iconPath(m_svgIconName + QLatin1String(".svg"), size, KIconLoader::MatchBest); if (iconPath.isEmpty()) { iconPath = iconTheme->iconPath(m_svgIconName + QLatin1String(".svgz"), size, KIconLoader::MatchBest); } if (!iconPath.isEmpty()) { m_svgIcon->setImagePath(iconPath); } } else { qWarning() << "KIconLoader has no theme set"; } result = m_svgIcon->pixmap(); } } else if (!m_icon.isNull()) { KIconLoader::global()->setCustomPalette(Plasma::Theme().palette()); result = m_icon.pixmap(QSize(size, size) * (window() ? window()->devicePixelRatio() : qApp->devicePixelRatio())); KIconLoader::global()->resetPalette(); } else if (!m_imageIcon.isNull()) { result = QPixmap::fromImage(m_imageIcon); } else { m_iconPixmap = QPixmap(); m_animation->stop(); update(); return; } // Strangely KFileItem::overlays() returns empty string-values, so // we need to check first whether an overlay must be drawn at all. // It is more efficient to do it here, as KIconLoader::drawOverlays() // assumes that an overlay will be drawn and has some additional // setup time. - foreach (const QString& overlay, m_overlays) { + for (const QString &overlay : qAsConst(m_overlays)) { if (!overlay.isEmpty()) { // There is at least one overlay, draw all overlays above m_pixmap // and cancel the check KIconLoader::global()->drawOverlays(m_overlays, result, KIconLoader::Desktop); break; } } if (!isEnabled()) { result = KIconLoader::global()->iconEffect()->apply(result, KIconLoader::Desktop, KIconLoader::DisabledState); } else if (m_active) { result = KIconLoader::global()->iconEffect()->apply(result, KIconLoader::Desktop, KIconLoader::ActiveState); } const QSize oldPaintedSize = paintedSize(); m_oldIconPixmap = m_iconPixmap; m_iconPixmap = result; m_textureChanged = true; if (oldPaintedSize != paintedSize()) { emit paintedSizeChanged(); } //don't animate initial setting bool animated = (m_animated || m_allowNextAnimation) && !m_oldIconPixmap.isNull() && !m_sizeChanged && !m_blockNextAnimation; if (QQuickWindow::sceneGraphBackend() == QLatin1String("software")) { animated = false; } if (animated) { m_animValue = 0.0; m_animation->setStartValue((qreal)0); m_animation->setEndValue((qreal)1); m_animation->start(); m_allowNextAnimation = false; } else { m_animValue = 1.0; m_animation->stop(); m_blockNextAnimation = false; } update(); } void IconItem::itemChange(ItemChange change, const ItemChangeData &value) { if (change == ItemVisibleHasChanged && value.boolValue) { m_blockNextAnimation = true; } else if (change == ItemEnabledHasChanged) { onEnabledChanged(); } else if (change == ItemSceneChange && value.window) { if (m_window) { disconnect(m_window.data(), &QWindow::visibleChanged, this, &IconItem::windowVisibleChanged); } m_window = value.window; if (m_window) { connect(m_window.data(), &QWindow::visibleChanged, this, &IconItem::windowVisibleChanged); } schedulePixmapUpdate(); } QQuickItem::itemChange(change, value); } void IconItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) { if (newGeometry.size() != oldGeometry.size()) { m_sizeChanged = true; if (newGeometry.width() > 0 && newGeometry.height() > 0) { schedulePixmapUpdate(); } else { update(); } if (paintedSize(oldGeometry.size()) != paintedSize(newGeometry.size())) { emit paintedSizeChanged(); } } QQuickItem::geometryChanged(newGeometry, oldGeometry); } void IconItem::componentComplete() { QQuickItem::componentComplete(); schedulePixmapUpdate(); } diff --git a/src/declarativeimports/core/tooltipdialog.cpp b/src/declarativeimports/core/tooltipdialog.cpp index 787f19fde..9c092e8c8 100644 --- a/src/declarativeimports/core/tooltipdialog.cpp +++ b/src/declarativeimports/core/tooltipdialog.cpp @@ -1,144 +1,145 @@ /*************************************************************************** * Copyright 2013 Sebastian Kügler * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ #include "tooltipdialog.h" #include #include #include #include #include #include ToolTipDialog::ToolTipDialog(QQuickItem *parent) : Dialog(parent), m_qmlObject(nullptr), m_hideTimeout(4000), m_interactive(false), m_owner(nullptr) { setLocation(Plasma::Types::Floating); setType(Dialog::WindowType::Tooltip); m_showTimer = new QTimer(this); m_showTimer->setSingleShot(true); connect(m_showTimer, &QTimer::timeout, [ = ]() { setVisible(false); }); } ToolTipDialog::~ToolTipDialog() { } QQuickItem *ToolTipDialog::loadDefaultItem() { if (!m_qmlObject) { m_qmlObject = new KDeclarative::QmlObjectSharedEngine(this); } if (!m_qmlObject->rootObject()) { //HACK: search our own import - foreach (const QString &path, m_qmlObject->engine()->importPathList()) { + const auto paths = m_qmlObject->engine()->importPathList(); + for (const QString &path : paths) { if (QFile::exists(path + QStringLiteral("/org/kde/plasma/core"))) { m_qmlObject->setSource(QUrl::fromLocalFile(path + QStringLiteral("/org/kde/plasma/core/private/DefaultToolTip.qml"))); break; } } } return qobject_cast(m_qmlObject->rootObject()); } void ToolTipDialog::showEvent(QShowEvent *event) { m_showTimer->start(m_hideTimeout); Dialog::showEvent(event); } void ToolTipDialog::hideEvent(QHideEvent *event) { m_showTimer->stop(); Dialog::hideEvent(event); } void ToolTipDialog::resizeEvent(QResizeEvent *re) { Dialog::resizeEvent(re); } bool ToolTipDialog::event(QEvent *e) { if (e->type() == QEvent::Enter) { if (m_interactive) { m_showTimer->stop(); } } else if (e->type() == QEvent::Leave) { dismiss(); } bool ret = Dialog::event(e); Qt::WindowFlags flags = Qt::ToolTip | Qt::WindowDoesNotAcceptFocus | Qt::WindowStaysOnTopHint; if (KWindowSystem::isPlatformX11()) { flags = flags | Qt::BypassWindowManagerHint; } setFlags(flags); return ret; } QObject *ToolTipDialog::owner() const { return m_owner; } void ToolTipDialog::setOwner(QObject *owner) { m_owner = owner; } void ToolTipDialog::dismiss() { m_showTimer->start(m_hideTimeout / 20); // pretty short: 200ms } void ToolTipDialog::keepalive() { m_showTimer->start(m_hideTimeout); } bool ToolTipDialog::interactive() { return m_interactive; } void ToolTipDialog::setInteractive(bool interactive) { m_interactive = interactive; setOutputOnly(!interactive); } void ToolTipDialog::valueChanged(const QVariant &value) { setPosition(value.toPoint()); } #include "moc_tooltipdialog.cpp" diff --git a/src/declarativeimports/plasmacomponents/qmenu.cpp b/src/declarativeimports/plasmacomponents/qmenu.cpp index 893ee06f3..578133b59 100644 --- a/src/declarativeimports/plasmacomponents/qmenu.cpp +++ b/src/declarativeimports/plasmacomponents/qmenu.cpp @@ -1,479 +1,479 @@ /*************************************************************************** * Copyright 2011 Viranch Mehta * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ #include "qmenu.h" #include #include #include #include #include #include #include #include #include "plasmacomponentsplugin.h" QMenuProxy::QMenuProxy(QObject *parent) : QObject(parent), m_menu(nullptr), m_status(DialogStatus::Closed), m_placement(Plasma::Types::LeftPosedTopAlignedPopup) { if (qobject_cast(QCoreApplication::instance())) { m_menu = new QMenu(nullptr); // Breeze and Oxygen have rounded corners on menus. They set this attribute in polish() // but at that time the underlying surface has already been created where setting this // flag makes no difference anymore (Bug 385311) m_menu->setAttribute(Qt::WA_TranslucentBackground); KAcceleratorManager::manage(m_menu); connect(m_menu, &QMenu::triggered, this, &QMenuProxy::itemTriggered); connect(m_menu, &QMenu::aboutToHide, this, [ = ]() { m_status = DialogStatus::Closed; emit statusChanged(); }); } } QMenuProxy::~QMenuProxy() { delete m_menu; } QQmlListProperty QMenuProxy::content() { return QQmlListProperty(this, m_items); } int QMenuProxy::actionCount() const { return m_items.count(); } QMenuItem *QMenuProxy::action(int index) const { return m_items.at(index); } DialogStatus::Status QMenuProxy::status() const { return m_status; } QObject *QMenuProxy::visualParent() const { return m_visualParent.data(); } void QMenuProxy::setVisualParent(QObject *parent) { if (m_visualParent.data() == parent) { return; } //if the old parent was a QAction, disconnect the menu from it QAction *action = qobject_cast(m_visualParent.data()); if (action) { action->setMenu(nullptr); m_menu->clear(); } //if parent is a QAction, become a submenu action = qobject_cast(parent); if (action) { action->setMenu(m_menu); m_menu->clear(); - foreach (QMenuItem *item, m_items) { + for (QMenuItem *item : qAsConst(m_items)) { if (item->section()) { if (!item->isVisible()) { continue; } m_menu->addSection(item->text()); } else { m_menu->addAction(item->action()); } } m_menu->updateGeometry(); } m_visualParent = parent; emit visualParentChanged(); } QWindow *QMenuProxy::transientParent() { if (!m_menu) { return nullptr; } return m_menu->windowHandle()->transientParent(); } void QMenuProxy::setTransientParent(QWindow *parent) { if (parent == m_menu->windowHandle()->transientParent()) { return; } m_menu->windowHandle()->setTransientParent(parent); emit transientParentChanged(); } Plasma::Types::PopupPlacement QMenuProxy::placement() const { return m_placement; } void QMenuProxy::setPlacement(Plasma::Types::PopupPlacement placement) { if (m_placement != placement) { m_placement = placement; emit placementChanged(); } } int QMenuProxy::minimumWidth() const { return m_menu->minimumWidth(); } void QMenuProxy::setMinimumWidth(int width) { if (m_menu->minimumWidth() != width) { m_menu->setMinimumWidth(width); emit minimumWidthChanged(); } } int QMenuProxy::maximumWidth() const { return m_menu->maximumWidth(); } void QMenuProxy::setMaximumWidth(int width) { if (m_menu->maximumWidth() != width) { m_menu->setMaximumWidth(width); emit maximumWidthChanged(); } } void QMenuProxy::resetMaximumWidth() { setMaximumWidth(QWIDGETSIZE_MAX); } bool QMenuProxy::event(QEvent *event) { switch (event->type()) { case QEvent::ChildAdded: { QChildEvent *ce = static_cast(event); QMenuItem *mi = qobject_cast(ce->child()); //FIXME: linear complexity here if (mi && !m_items.contains(mi)) { if (mi->separator()) { m_menu->addSection(mi->text()); } else { m_menu->addAction(mi->action()); } m_items << mi; } break; } case QEvent::ChildRemoved: { QChildEvent *ce = static_cast(event); QMenuItem *mi = qobject_cast(ce->child()); //FIXME: linear complexity here if (mi) { m_menu->removeAction(mi->action()); m_items.removeAll(mi); } break; } default: break; } return QObject::event(event); } void QMenuProxy::clearMenuItems() { qDeleteAll(m_items); m_items.clear(); } void QMenuProxy::addMenuItem(const QString &text) { QMenuItem *item = new QMenuItem(); item->setText(text); m_menu->addAction(item->action()); m_items << item; } void QMenuProxy::addMenuItem(QMenuItem *item, QMenuItem *before) { if (before) { if (m_items.contains(item)) { m_menu->removeAction(item->action()); m_items.removeAll(item); } m_menu->insertAction(before->action(), item->action()); const int index = m_items.indexOf(before); if (index != -1) { m_items.insert(index, item); } else { m_items << item; } } else if (!m_items.contains(item)) { m_menu->addAction(item->action()); m_items << item; } } void QMenuProxy::addSection(const QString &text) { m_menu->addSection(text); } void QMenuProxy::removeMenuItem(QMenuItem *item) { if (!item) { return; } m_menu->removeAction(item->action()); m_items.removeOne(item); } void QMenuProxy::itemTriggered(QAction *action) { for (int i = 0; i < m_items.count(); ++i) { QMenuItem *item = m_items.at(i); if (item->action() == action) { emit triggered(item); emit triggeredIndex(i); break; } } } void QMenuProxy::rebuildMenu() { m_menu->clear(); - foreach (QMenuItem *item, m_items) { + for (QMenuItem *item : qAsConst(m_items)) { if (item->section()) { if (!item->isVisible()) { continue; } m_menu->addSection(item->text()); } else { m_menu->addAction(item->action()); if (item->action()->menu()) { //This ensures existence of the QWindow m_menu->winId(); item->action()->menu()->winId(); item->action()->menu()->windowHandle()->setTransientParent(m_menu->windowHandle()); } } } m_menu->adjustSize(); } void QMenuProxy::open(int x, int y) { qDebug() << "Opening menu at" << x << y; QQuickItem *parentItem = nullptr; if (m_visualParent) { parentItem = qobject_cast(m_visualParent.data()); } else { parentItem = qobject_cast(parent()); } if (!parentItem) { return; } rebuildMenu(); QPointF pos = parentItem->mapToScene(QPointF(x, y)); if (parentItem->window() && parentItem->window()->screen()) { pos = parentItem->window()->mapToGlobal(pos.toPoint()); } openInternal(pos.toPoint()); } void QMenuProxy::openRelative() { QQuickItem *parentItem = nullptr; if (m_visualParent) { parentItem = qobject_cast(m_visualParent.data()); } else { parentItem = qobject_cast(parent()); } if (!parentItem) { return; } rebuildMenu(); QPointF pos; using namespace Plasma; auto boundaryCorrection = [&pos, this, parentItem](int hDelta, int vDelta) { if (!parentItem->window()) { return; } QScreen *screen = parentItem->window()->screen(); if (!screen) { return; } QRect geo = screen->geometry(); pos = parentItem->window()->mapToGlobal(pos.toPoint()); if (pos.x() < geo.x()) { pos.setX(pos.x() + hDelta); } if (pos.y() < geo.y()) { pos.setY(pos.y() + vDelta); } if (geo.x() + geo.width() < pos.x() + this->m_menu->width()) { pos.setX(pos.x() + hDelta); } if (geo.y() + geo.height() < pos.y() + this->m_menu->height()) { pos.setY(pos.y() + vDelta); } }; switch(m_placement) { case Types::TopPosedLeftAlignedPopup: { pos = parentItem->mapToScene(QPointF(0, -m_menu->height())); boundaryCorrection(-m_menu->width() + parentItem->width(), m_menu->height() + parentItem->height()); break; } case Types::LeftPosedTopAlignedPopup: { pos = parentItem->mapToScene(QPointF(-m_menu->width(), 0)); boundaryCorrection(m_menu->width() + parentItem->width(), -m_menu->height() + parentItem->height()); break; } case Types::TopPosedRightAlignedPopup: pos = parentItem->mapToScene(QPointF(parentItem->width() - m_menu->width(), -m_menu->height())); boundaryCorrection(m_menu->width() - parentItem->width(), m_menu->height() + parentItem->height()); break; case Types::RightPosedTopAlignedPopup: { pos = parentItem->mapToScene(QPointF(parentItem->width(), 0)); boundaryCorrection(-m_menu->width() - parentItem->width(), -m_menu->height() + parentItem->height()); break; } case Types::LeftPosedBottomAlignedPopup: pos = parentItem->mapToScene(QPointF(-m_menu->width(), -m_menu->height() + parentItem->height())); boundaryCorrection(m_menu->width() + parentItem->width(), m_menu->height() - parentItem->height()); break; case Types::BottomPosedLeftAlignedPopup: { pos = parentItem->mapToScene(QPointF(0, parentItem->height())); boundaryCorrection(-m_menu->width() + parentItem->width(), -m_menu->height() - parentItem->height()); break; } case Types::BottomPosedRightAlignedPopup: { pos = parentItem->mapToScene(QPointF(parentItem->width() - m_menu->width(), parentItem->height())); boundaryCorrection(m_menu->width() - parentItem->width(), -m_menu->height() - parentItem->height()); break; } case Types::RightPosedBottomAlignedPopup: { pos = parentItem->mapToScene(QPointF(parentItem->width(), -m_menu->height() + parentItem->height())); boundaryCorrection(-m_menu->width() - parentItem->width(), m_menu->height() - parentItem->height()); break; } default: open(); return; } openInternal(pos.toPoint()); } void QMenuProxy::openInternal(QPoint pos) { QQuickItem *parentItem = this->parentItem(); if (parentItem && parentItem->window()) { //create the QWindow m_menu->winId(); m_menu->windowHandle()->setTransientParent(parentItem->window()); // Workaround for QTBUG-59044 auto ungrabMouseHack = [this]() { QQuickItem *parentItem = this->parentItem(); if (parentItem && parentItem->window() && parentItem->window()->mouseGrabberItem()) { parentItem->window()->mouseGrabberItem()->ungrabMouse(); } }; //pre 5.8.0 QQuickWindow code is "item->grabMouse(); sendEvent(item, mouseEvent)" //post 5.8.0 QQuickWindow code is sendEvent(item, mouseEvent); item->grabMouse() if (QVersionNumber::fromString(QString::fromLatin1(qVersion())) > QVersionNumber(5, 8, 0)) { QTimer::singleShot(0, this, ungrabMouseHack); } else { ungrabMouseHack(); } //end workaround } m_menu->popup(pos); m_status = DialogStatus::Open; emit statusChanged(); } QQuickItem *QMenuProxy::parentItem() const { if (m_visualParent) { return qobject_cast(m_visualParent.data()); } return qobject_cast(parent()); } void QMenuProxy::close() { m_menu->hide(); } diff --git a/src/declarativeimports/plasmaextracomponents/fallbackcomponent.cpp b/src/declarativeimports/plasmaextracomponents/fallbackcomponent.cpp index 87ad33d24..92e0b47f1 100644 --- a/src/declarativeimports/plasmaextracomponents/fallbackcomponent.cpp +++ b/src/declarativeimports/plasmaextracomponents/fallbackcomponent.cpp @@ -1,89 +1,89 @@ /* * Copyright 2011 Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "fallbackcomponent.h" #include #include #include #include FallbackComponent::FallbackComponent(QObject *parent) : QObject(parent) { } QString FallbackComponent::basePath() const { return m_basePath; } void FallbackComponent::setBasePath(const QString &basePath) { if (basePath != m_basePath) { m_basePath = basePath; emit basePathChanged(); } } QStringList FallbackComponent::candidates() const { return m_candidates; } void FallbackComponent::setCandidates(const QStringList &candidates) { m_candidates = candidates; emit candidatesChanged(); } QString FallbackComponent::filePath(const QString &key) { QString resolved; - foreach (const QString &path, m_candidates) { + for (const QString &path : qAsConst(m_candidates)) { // qDebug() << "Searching for:" << path + path; if (m_possiblePaths.contains(path + key)) { resolved = *m_possiblePaths.object(path + key); if (!resolved.isEmpty()) { break; } else { continue; } } QDir tmpPath(m_basePath); if (tmpPath.isAbsolute()) { resolved = m_basePath + path + key; } else { resolved = QStandardPaths::locate(QStandardPaths::GenericDataLocation, m_basePath + QLatin1Char('/') + path + key); } m_possiblePaths.insert(path + key, new QString(resolved)); if (!resolved.isEmpty()) { break; } } return resolved; } diff --git a/src/plasma/containment.cpp b/src/plasma/containment.cpp index 8557d27ba..f58f87e39 100644 --- a/src/plasma/containment.cpp +++ b/src/plasma/containment.cpp @@ -1,603 +1,607 @@ /* * Copyright 2007 by Aaron Seigo * Copyright 2008 by Ménard Alexis * Copyright 2009 Chani Armitage * Copyright 2012 Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "containment.h" #include "private/containment_p.h" #include "config-plasma.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "containmentactions.h" #include "corona.h" #include "pluginloader.h" #include "debug_p.h" #include "private/applet_p.h" #include "plasma/plasma.h" namespace Plasma { Containment::Containment(QObject *parent, const QString &serviceId, uint containmentId) : Applet(parent, serviceId, containmentId), d(new ContainmentPrivate(this)) { // WARNING: do not access config() OR globalConfig() in this method! // that requires a scene, which is not available at this point setContainmentType(Types::CustomContainment); setHasConfigurationInterface(true); } Containment::Containment(QObject *parent, const QVariantList &args) : Applet(parent, args), d(new ContainmentPrivate(this)) { // WARNING: do not access config() OR globalConfig() in this method! // that requires a scene, which is not available at this point setHasConfigurationInterface(true); } Containment::Containment(const KPluginMetaData &md, uint appletId) : Applet(md, nullptr, appletId), d(new ContainmentPrivate(this)) { // WARNING: do not access config() OR globalConfig() in this method! // that requires a scene, which is not available at this point setHasConfigurationInterface(true); } Containment::~Containment() { qDeleteAll(d->localActionPlugins); delete d; } void Containment::init() { Applet::init(); static_cast(this)->d->setupScripting(); if (d->type == Types::NoContainmentType) { //setContainmentType(Plasma::Types::DesktopContainment); //Try to determine the containment type. It must be done as soon as possible QString type = pluginMetaData().value(QStringLiteral("X-Plasma-ContainmentType")); if (type == QLatin1String("Panel")) { setContainmentType(Plasma::Types::PanelContainment); } else if (type == QLatin1String("Custom")) { setContainmentType(Plasma::Types::CustomContainment); } else if (type == QLatin1String("CustomPanel")) { setContainmentType(Plasma::Types::CustomPanelContainment); //default to desktop } else { setContainmentType(Plasma::Types::DesktopContainment); } } //connect actions ContainmentPrivate::addDefaultActions(actions(), this); bool unlocked = immutability() == Types::Mutable; //fix the text of the actions that need title() //btw, do we really want to use title() when it's a desktopcontainment? QAction *closeApplet = actions()->action(QStringLiteral("remove")); if (closeApplet) { closeApplet->setText(i18nc("%1 is the name of the applet", "Remove %1", title())); } QAction *configAction = actions()->action(QStringLiteral("configure")); if (configAction) { if (d->type == Types::PanelContainment || d->type == Types::CustomPanelContainment) { configAction->setText(i18nc("%1 is the name of the containment", "Edit %1...", title())); configAction->setIcon(QIcon::fromTheme(QStringLiteral("document-edit"))); } else { configAction->setText(i18nc("%1 is the name of the applet", "Configure %1...", title())); } } QAction *appletBrowserAction = actions()->action(QStringLiteral("add widgets")); if (appletBrowserAction) { appletBrowserAction->setVisible(unlocked); appletBrowserAction->setEnabled(unlocked); connect(appletBrowserAction, SIGNAL(triggered()), this, SLOT(triggerShowAddWidgets())); } if (immutability() != Types::SystemImmutable && corona()) { QAction *lockDesktopAction = corona()->actions()->action(QStringLiteral("lock widgets")); //keep a pointer so nobody notices it moved to corona if (lockDesktopAction) { actions()->addAction(QStringLiteral("lock widgets"), lockDesktopAction); } } //HACK: this is valid only in the systray case connect(this, &Containment::configureRequested, this, [=] (Plasma::Applet *a) { if (Plasma::Applet *p = qobject_cast(parent())) { emit p->containment()->configureRequested(a); } }); } // helper function for sorting the list of applets bool appletConfigLessThan(const KConfigGroup &c1, const KConfigGroup &c2) { int i1 = c1.readEntry("id", 0); int i2 = c2.readEntry("id", 0); return (i1 < i2); } void Containment::restore(KConfigGroup &group) { /* #ifndef NDEBUG // qCDebug(LOG_PLASMA) << "!!!!!!!!!!!!initConstraints" << group.name() << d->type; // qCDebug(LOG_PLASMA) << " location:" << group.readEntry("location", (int)d->location); // qCDebug(LOG_PLASMA) << " geom:" << group.readEntry("geometry", geometry()); // qCDebug(LOG_PLASMA) << " formfactor:" << group.readEntry("formfactor", (int)d->formFactor); // qCDebug(LOG_PLASMA) << " screen:" << group.readEntry("screen", d->screen); #endif */ setLocation((Plasma::Types::Location)group.readEntry("location", (int)d->location)); setFormFactor((Plasma::Types::FormFactor)group.readEntry("formfactor", (int)d->formFactor)); d->lastScreen = group.readEntry("lastScreen", d->lastScreen); setWallpaper(group.readEntry("wallpaperplugin", ContainmentPrivate::defaultWallpaper)); d->activityId = group.readEntry("activityId", QString()); flushPendingConstraintsEvents(); restoreContents(group); setImmutability((Types::ImmutabilityType)group.readEntry("immutability", (int)Types::Mutable)); if (isContainment() && KAuthorized::authorize(QStringLiteral("plasma/containment_actions"))) { KConfigGroup cfg = KConfigGroup(corona()->config(), "ActionPlugins"); cfg = KConfigGroup(&cfg, QString::number(containmentType())); //qCDebug(LOG_PLASMA) << cfg.keyList(); if (cfg.exists()) { - foreach (const QString &key, cfg.keyList()) { + const auto keyList = cfg.keyList(); + for (const QString &key : keyList) { //qCDebug(LOG_PLASMA) << "loading" << key; setContainmentActions(key, cfg.readEntry(key, QString())); } } else { //shell defaults KConfigGroup defaultActionsCfg; switch (d->type) { case Plasma::Types::PanelContainment: /* fall through*/ case Plasma::Types::CustomPanelContainment: defaultActionsCfg = KConfigGroup(KSharedConfig::openConfig(corona()->kPackage().filePath("defaults")), "Panel"); break; case Plasma::Types::DesktopContainment: defaultActionsCfg = KConfigGroup(KSharedConfig::openConfig(corona()->kPackage().filePath("defaults")), "Desktop"); break; default: //for any other type of containment, there are no defaults break; } if (defaultActionsCfg.isValid()) { defaultActionsCfg = KConfigGroup(&defaultActionsCfg, "ContainmentActions"); - foreach (const QString &key, defaultActionsCfg.keyList()) { + const auto keyList = defaultActionsCfg.keyList(); + for (const QString &key : keyList) { setContainmentActions(key, defaultActionsCfg.readEntry(key, QString())); } } } } /* #ifndef NDEBUG // qCDebug(LOG_PLASMA) << "Containment" << id() << #endif "screen" << screen() << "geometry is" << geometry() << "config entries" << group.entryMap(); */ } void Containment::save(KConfigGroup &g) const { if (Applet::d->transient) { return; } KConfigGroup group = g; if (!group.isValid()) { group = config(); } // locking is saved in Applet::save Applet::save(group); // group.writeEntry("screen", d->screen); group.writeEntry("lastScreen", d->lastScreen); group.writeEntry("formfactor", (int)d->formFactor); group.writeEntry("location", (int)d->location); group.writeEntry("activityId", d->activityId); group.writeEntry("wallpaperplugin", d->wallpaper); saveContents(group); } void Containment::saveContents(KConfigGroup &group) const { KConfigGroup applets(&group, "Applets"); - foreach (const Applet *applet, d->applets) { + for (const Applet *applet : qAsConst(d->applets)) { KConfigGroup appletConfig(&applets, QString::number(applet->id())); applet->save(appletConfig); } } void Containment::restoreContents(KConfigGroup &group) { KConfigGroup applets(&group, "Applets"); //restore the applets ordered by id QStringList groups = applets.groupList(); std::sort(groups.begin(), groups.end()); // Sort the applet configs in order of geometry to ensure that applets // are added from left to right or top to bottom for a panel containment QList appletConfigs; - foreach (const QString &appletGroup, groups) { + for (const QString &appletGroup : qAsConst(groups)) { //qCDebug(LOG_PLASMA) << "reading from applet group" << appletGroup; KConfigGroup appletConfig(&applets, appletGroup); appletConfigs.append(appletConfig); } std::stable_sort(appletConfigs.begin(), appletConfigs.end(), appletConfigLessThan); QMutableListIterator it(appletConfigs); while (it.hasNext()) { KConfigGroup &appletConfig = it.next(); int appId = appletConfig.name().toUInt(); QString plugin = appletConfig.readEntry("plugin", QString()); if (plugin.isEmpty()) { continue; } d->createApplet(plugin, QVariantList(), appId); } //if there are no applets, none of them is "loading" if (Containment::applets().isEmpty()) { d->appletsUiReady = true; } - foreach (Applet *applet, Containment::applets()) { + const auto lstApplets = Containment::applets(); + for (Applet *applet : lstApplets) { if (!applet->pluginMetaData().isValid()) { applet->updateConstraints(Plasma::Types::UiReadyConstraint); } } } Plasma::Types::ContainmentType Containment::containmentType() const { return d->type; } void Containment::setContainmentType(Plasma::Types::ContainmentType type) { if (d->type == type) { return; } d->type = type; emit containmentTypeChanged(); } Corona *Containment::corona() const { if(Plasma::Corona* corona = qobject_cast(parent())) { return corona; //case in which this containment is child of an applet, hello systray :) } else { Plasma::Applet *parentApplet = qobject_cast(parent()); if (parentApplet && parentApplet->containment()) { return parentApplet->containment()->corona(); } } return nullptr; } void Containment::setFormFactor(Types::FormFactor formFactor) { if (d->formFactor == formFactor) { return; } //qCDebug(LOG_PLASMA) << "switching FF to " << formFactor; d->formFactor = formFactor; updateConstraints(Plasma::Types::FormFactorConstraint); KConfigGroup c = config(); c.writeEntry("formfactor", (int)formFactor); emit configNeedsSaving(); emit formFactorChanged(formFactor); } void Containment::setLocation(Types::Location location) { if (d->location == location) { return; } d->location = location; - foreach (Applet *applet, d->applets) { + for (Applet *applet : qAsConst(d->applets)) { applet->updateConstraints(Plasma::Types::LocationConstraint); } updateConstraints(Plasma::Types::LocationConstraint); KConfigGroup c = config(); c.writeEntry("location", (int)location); emit configNeedsSaving(); emit locationChanged(location); } Applet *Containment::createApplet(const QString &name, const QVariantList &args) { Plasma::Applet *applet = d->createApplet(name, args); if (applet) { emit appletCreated(applet); } return applet; } void Containment::addApplet(Applet *applet) { if (!applet) { #ifndef NDEBUG // qCDebug(LOG_PLASMA) << "adding null applet!?!"; #endif return; } if (immutability() != Types::Mutable && !applet->property("org.kde.plasma:force-create").toBool()) { return; } #ifndef NDEBUG if (d->applets.contains(applet)) { // qCDebug(LOG_PLASMA) << "already have this applet!"; } #endif Containment *currentContainment = applet->containment(); if (currentContainment && currentContainment != this) { emit currentContainment->appletRemoved(applet); disconnect(applet, nullptr, currentContainment, nullptr); KConfigGroup oldConfig = applet->config(); currentContainment->d->applets.removeAll(applet); applet->setParent(this); // now move the old config to the new location //FIXME: this doesn't seem to get the actual main config group containing plugin=, etc KConfigGroup c = config().group("Applets").group(QString::number(applet->id())); oldConfig.reparent(&c); applet->d->resetConfigurationObject(); disconnect(applet, &Applet::activated, currentContainment, &Applet::activated); //change the group to its configloader, if any //FIXME: this is very, very brutal if (applet->configScheme()) { const QString oldGroupPrefix = QStringLiteral("Containments") + QString::number(currentContainment->id()) + QStringLiteral("Applets"); const QString newGroupPrefix = QStringLiteral("Containments") + QString::number(id()) + QStringLiteral("Applets"); applet->configScheme()->setCurrentGroup(applet->configScheme()->currentGroup().replace(0, oldGroupPrefix.length(), newGroupPrefix)); - foreach (KConfigSkeletonItem *item, applet->configScheme()->items()) { + const auto items = applet->configScheme()->items(); + for (KConfigSkeletonItem *item : items) { item->setGroup(item->group().replace(0, oldGroupPrefix.length(), newGroupPrefix)); } } } else { applet->setParent(this); } //make sure the applets are sorted by id auto position = std::lower_bound(d->applets.begin(), d->applets.end(), applet, [](Plasma::Applet *a1, Plasma::Applet *a2) { return a1->id() < a2->id(); }); d->applets.insert(position, applet); if (!d->uiReady) { d->loadingApplets << applet; } connect(applet, &Applet::configNeedsSaving, this, &Applet::configNeedsSaving); connect(applet, SIGNAL(appletDeleted(Plasma::Applet*)), this, SLOT(appletDeleted(Plasma::Applet*))); connect(applet, SIGNAL(statusChanged(Plasma::Types::ItemStatus)), this, SLOT(checkStatus(Plasma::Types::ItemStatus))); connect(applet, &Applet::activated, this, &Applet::activated); if (!currentContainment) { const bool isNew = applet->d->mainConfigGroup()->entryMap().isEmpty(); if (!isNew) { applet->restore(*applet->d->mainConfigGroup()); } applet->init(); applet->d->setupScripting(); if (isNew) { applet->save(*applet->d->mainConfigGroup()); emit configNeedsSaving(); } //FIXME: an on-appear animation would be nice to have again } applet->updateConstraints(Plasma::Types::AllConstraints); applet->flushPendingConstraintsEvents(); emit appletAdded(applet); if (!currentContainment) { applet->updateConstraints(Plasma::Types::StartupCompletedConstraint); applet->flushPendingConstraintsEvents(); } applet->d->scheduleModificationNotification(); } QList Containment::applets() const { return d->applets; } int Containment::screen() const { Q_ASSERT(corona()); if (Corona* c = corona()) { return c->screenForContainment(this); } else { return -1; } } int Containment::lastScreen() const { return d->lastScreen; } void Containment::setWallpaper(const QString &pluginName) { if (pluginName != d->wallpaper) { d->wallpaper = pluginName; KConfigGroup cfg = config(); cfg.writeEntry("wallpaperplugin", d->wallpaper); emit configNeedsSaving(); emit wallpaperChanged(); } } QString Containment::wallpaper() const { return d->wallpaper; } void Containment::setContainmentActions(const QString &trigger, const QString &pluginName) { KConfigGroup cfg = d->containmentActionsConfig(); ContainmentActions *plugin = nullptr; plugin = containmentActions().value(trigger); if (plugin && plugin->metadata().pluginId() != pluginName) { containmentActions().remove(trigger); delete plugin; plugin = nullptr; } if (pluginName.isEmpty()) { cfg.deleteEntry(trigger); } else if (plugin) { //it already existed, just reload config plugin->setContainment(this); //to be safe //FIXME make a truly unique config group KConfigGroup pluginConfig = KConfigGroup(&cfg, trigger); plugin->restore(pluginConfig); } else { plugin = PluginLoader::self()->loadContainmentActions(this, pluginName); if (plugin) { cfg.writeEntry(trigger, pluginName); containmentActions().insert(trigger, plugin); plugin->setContainment(this); KConfigGroup pluginConfig = KConfigGroup(&cfg, trigger); plugin->restore(pluginConfig); } else { //bad plugin... gets removed. is this a feature or a bug? cfg.deleteEntry(trigger); } } emit configNeedsSaving(); } QHash &Containment::containmentActions() { return d->localActionPlugins; } bool Containment::isUiReady() const { return d->uiReady && d->appletsUiReady && Applet::d->started; } void Containment::setActivity(const QString &activityId) { if (activityId.isEmpty() || d->activityId == activityId) { return; } d->activityId = activityId; KConfigGroup c = config(); c.writeEntry("activityId", activityId); emit configNeedsSaving(); emit activityChanged(activityId); } QString Containment::activity() const { return d->activityId; } void Containment::reactToScreenChange() { int newScreen = screen(); if (newScreen >= 0) { d->lastScreen = newScreen; KConfigGroup c = config(); c.writeEntry("lastScreen", d->lastScreen); emit configNeedsSaving(); } emit screenChanged(newScreen); } } // Plasma namespace #include "moc_containment.cpp" diff --git a/src/plasma/corona.cpp b/src/plasma/corona.cpp index b8d26784c..0b3ac6ea1 100644 --- a/src/plasma/corona.cpp +++ b/src/plasma/corona.cpp @@ -1,736 +1,739 @@ /* * Copyright 2007 Matt Broadstone * Copyright 2007-2011 Aaron Seigo * Copyright 2007 Riccardo Iaconelli * Copyright (c) 2009 Chani Armitage * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "corona.h" #include "private/corona_p.h" #include #include #include #include #include #include #include #include #include #include "containment.h" #include "pluginloader.h" #include "packagestructure.h" #include "private/applet_p.h" #include "private/containment_p.h" #include "private/package_p.h" #include "private/timetracker.h" #include "debug_p.h" using namespace Plasma; namespace Plasma { Corona::Corona(QObject *parent) : QObject(parent), d(new CoronaPrivate(this)) { #ifndef NDEBUG // qCDebug(LOG_PLASMA) << "!!{} STARTUP TIME" << QTime().msecsTo(QTime::currentTime()) << "Corona ctor start"; #endif d->init(); #ifndef NDEBUG if (qEnvironmentVariableIsSet("PLASMA_TRACK_STARTUP")) { new TimeTracker(this); } #endif } Corona::~Corona() { KConfigGroup trans(KSharedConfig::openConfig(), "PlasmaTransientsConfig"); trans.deleteGroup(); delete d; } Plasma::Package Corona::package() const { return Package(d->package); } void Corona::setPackage(const Plasma::Package &package) { setKPackage(*package.d->internalPackage); emit packageChanged(package); } KPackage::Package Corona::kPackage() const { return d->package; } void Corona::setKPackage(const KPackage::Package &package) { d->package = package; emit kPackageChanged(package); } void Corona::saveLayout(const QString &configName) const { KSharedConfigPtr c; if (configName.isEmpty() || configName == d->configName) { c = config(); } else { c = KSharedConfig::openConfig(configName, KConfig::SimpleConfig); } d->saveLayout(c); } void Corona::exportLayout(KConfigGroup &config, QList containments) { - foreach (const QString &group, config.groupList()) { + const auto groupList = config.groupList(); + for (const QString &group : groupList) { KConfigGroup cg(&config, group); cg.deleteGroup(); } //temporarily unlock so that removal works Types::ImmutabilityType oldImm = immutability(); d->immutability = Types::Mutable; KConfigGroup dest(&config, "Containments"); KConfigGroup dummy; - foreach (Plasma::Containment *c, containments) { + for (Plasma::Containment *c : qAsConst(containments)) { c->save(dummy); c->config().reparent(&dest); //ensure the containment is unlocked //this is done directly because we have to bypass any Types::SystemImmutable checks c->Applet::d->immutability = Types::Mutable; - foreach (Applet *a, c->applets()) { + const auto lstApplet = c->applets(); + for (Applet *a : lstApplet) { a->d->immutability = Types::Mutable; } c->destroy(); } //restore immutability d->immutability = oldImm; config.sync(); } void Corona::requestConfigSync() { // constant controlling how long between requesting a configuration sync // and one happening should occur. currently 10 seconds static const int CONFIG_SYNC_TIMEOUT = 10000; // TODO: should we check into our immutability before doing this? //NOTE: this is a pretty simplistic model: we simply save no more than CONFIG_SYNC_TIMEOUT // after the first time this is called. not much of a heuristic for save points, but // it should at least compress these activities a bit and provide a way for applet // authors to ween themselves from the sync() disease. A more interesting/dynamic // algorithm for determining when to actually sync() to disk might be better, though. if (!d->configSyncTimer->isActive()) { d->configSyncTimer->start(CONFIG_SYNC_TIMEOUT); } } void Corona::requireConfigSync() { d->syncConfig(); } void Corona::loadLayout(const QString &configName) { if (!configName.isEmpty() && configName != d->configName) { // if we have a new config name passed in, then use that as the config file for this Corona d->config = nullptr; d->configName = configName; } KConfigGroup conf(config(), QString()); if (!config()->groupList().isEmpty()) { d->importLayout(conf, false); } else { loadDefaultLayout(); d->notifyContainmentsReady(); } KConfigGroup cg(config(), "General"); setImmutability((Plasma::Types::ImmutabilityType)cg.readEntry("immutability", (int)Plasma::Types::Mutable)); } QList Corona::importLayout(const KConfigGroup &conf) { return d->importLayout(conf, true); } Containment *Corona::containmentForScreen(int screen) const { - foreach (Containment *containment, d->containments) { + for (Containment *containment : qAsConst(d->containments)) { if (containment->screen() == screen && (containment->containmentType() == Plasma::Types::DesktopContainment || containment->containmentType() == Plasma::Types::CustomContainment)) { return containment; } } return nullptr; } Containment *Corona::containmentForScreen(int screen, const QString &defaultPluginIfNonExistent, const QVariantList &defaultArgs) { return containmentForScreen(screen, QString(), defaultPluginIfNonExistent, defaultArgs); } Containment *Corona::containmentForScreen(int screen, const QString &activity, const QString &defaultPluginIfNonExistent, const QVariantList &defaultArgs) { Containment *containment = nullptr; - foreach (Containment *cont, d->containments) { + for (Containment *cont : qAsConst(d->containments)) { if (cont->lastScreen() == screen && (cont->activity().isEmpty() || cont->activity() == activity) && (cont->containmentType() == Plasma::Types::DesktopContainment || cont->containmentType() == Plasma::Types::CustomContainment)) { containment = cont; } } if (!containment && !defaultPluginIfNonExistent.isEmpty()) { // screen requests are allowed to bypass immutability if (screen >= 0) { Plasma::Types::ImmutabilityType imm = d->immutability; d->immutability = Types::Mutable; containment = d->addContainment(defaultPluginIfNonExistent, defaultArgs, 0, screen, false); d->immutability = imm; } } if (containment) { containment->setActivity(activity); } return containment; } QList Corona::containmentsForActivity(const QString &activity) { QList conts; if (activity.isEmpty()) { return conts; } std::copy_if(d->containments.begin(), d->containments.end(), std::back_inserter(conts), [activity](Containment *cont) { return cont->activity() == activity && (cont->containmentType() == Plasma::Types::DesktopContainment || cont->containmentType() == Plasma::Types::CustomContainment);} ); return conts; } QList Corona::containmentsForScreen(int screen) { QList conts; if (screen < 0) { return conts; } std::copy_if(d->containments.begin(), d->containments.end(), std::back_inserter(conts), [screen](Containment *cont) { return cont->lastScreen() == screen && (cont->containmentType() == Plasma::Types::DesktopContainment || cont->containmentType() == Plasma::Types::CustomContainment);} ); return conts; } QList Corona::containments() const { return d->containments; } bool Corona::isStartupCompleted() const { return d->containmentsStarting <= 0; } KSharedConfigPtr Corona::config() const { if (!d->config) { d->config = KSharedConfig::openConfig(d->configName, KConfig::SimpleConfig); } return d->config; } Containment *Corona::createContainment(const QString &name, const QVariantList &args) { if (d->immutability == Types::Mutable || args.contains(QVariant::fromValue(QStringLiteral("org.kde.plasma:force-create")))) { return d->addContainment(name, args, 0, -1, false); } return nullptr; } Containment *Corona::createContainmentDelayed(const QString &name, const QVariantList &args) { if (d->immutability == Types::Mutable) { return d->addContainment(name, args, 0, -1, true); } return nullptr; } int Corona::screenForContainment(const Containment *) const { return -1; } int Corona::numScreens() const { return 1; } QRegion Corona::availableScreenRegion(int id) const { return QRegion(screenGeometry(id)); } QRect Corona::availableScreenRect(int id) const { return screenGeometry(id); } void Corona::loadDefaultLayout() { //Default implementation does nothing } Types::ImmutabilityType Corona::immutability() const { return d->immutability; } void Corona::setImmutability(const Types::ImmutabilityType immutable) { if (d->immutability == immutable || d->immutability == Types::SystemImmutable) { return; } #ifndef NDEBUG // qCDebug(LOG_PLASMA) << "setting immutability to" << immutable; #endif d->immutability = immutable; d->updateContainmentImmutability(); //tell non-containments that might care (like plasmaapp or a custom corona) emit immutabilityChanged(immutable); //update our actions QAction *action = d->actions.action(QStringLiteral("lock widgets")); if (action) { if (d->immutability == Types::SystemImmutable) { action->setEnabled(false); action->setVisible(false); } else { bool unlocked = d->immutability == Types::Mutable; action->setText(unlocked ? i18n("Lock Widgets") : i18n("Unlock Widgets")); action->setIcon(QIcon::fromTheme(unlocked ? QStringLiteral("object-locked") : QStringLiteral("object-unlocked"))); action->setEnabled(true); action->setVisible(true); } } action = d->actions.action(QStringLiteral("edit mode")); if (action) { switch (d->immutability) { case Types::UserImmutable: action->setEnabled(false); action->setVisible(true); break; case Types::SystemImmutable: action->setEnabled(false); action->setVisible(false); break; case Types::Mutable: default: action->setEnabled(true); action->setVisible(true); break; } } if (d->immutability != Types::SystemImmutable) { KConfigGroup cg(config(), "General"); // we call the dptr member directly for locked since isImmutable() // also checks kiosk and parent containers cg.writeEntry("immutability", (int)d->immutability); requestConfigSync(); } if (d->immutability != Types::Mutable) { setEditMode(false); } } void Corona::setEditMode(bool edit) { if (edit == d->editMode || (edit && d->immutability != Plasma::Types::Mutable)) { return; } QAction *editAction = d->actions.action(QStringLiteral("edit mode")); if (editAction) { if (edit) { editAction->setText(i18n("Finish Customizing Layout")); } else { editAction->setText(i18n("Customize Layout...")); } } d->editMode = edit; emit editModeChanged(edit); } bool Corona::isEditMode() const { return d->editMode; } QList Corona::freeEdges(int screen) const { QList freeEdges; freeEdges << Plasma::Types::TopEdge << Plasma::Types::BottomEdge << Plasma::Types::LeftEdge << Plasma::Types::RightEdge; - foreach (Containment *containment, containments()) { + const auto containments = this->containments(); + for (Containment *containment : containments) { if (containment->screen() == screen && freeEdges.contains(containment->location())) { freeEdges.removeAll(containment->location()); } } return freeEdges; } KActionCollection *Corona::actions() const { return &d->actions; } CoronaPrivate::CoronaPrivate(Corona *corona) : q(corona), immutability(Types::Mutable), config(nullptr), configSyncTimer(new QTimer(corona)), actions(corona), containmentsStarting(0) { //TODO: make Package path configurable if (QCoreApplication::instance()) { configName = QCoreApplication::instance()->applicationName() + QStringLiteral("-appletsrc"); } else { configName = QStringLiteral("plasma-appletsrc"); } } CoronaPrivate::~CoronaPrivate() { qDeleteAll(containments); } void CoronaPrivate::init() { desktopDefaultsConfig = KConfigGroup(KSharedConfig::openConfig(package.filePath("defaults")), "Desktop"); configSyncTimer->setSingleShot(true); QObject::connect(configSyncTimer, SIGNAL(timeout()), q, SLOT(syncConfig())); //some common actions actions.setConfigGroup(QStringLiteral("Shortcuts")); QAction *lockAction = actions.add(QStringLiteral("lock widgets")); QObject::connect(lockAction, SIGNAL(triggered(bool)), q, SLOT(toggleImmutability())); lockAction->setText(i18n("Lock Widgets")); lockAction->setAutoRepeat(true); lockAction->setIcon(QIcon::fromTheme(QStringLiteral("object-locked"))); lockAction->setData(Plasma::Types::ControlAction); lockAction->setShortcut(QKeySequence(QStringLiteral("alt+d, l"))); lockAction->setShortcutContext(Qt::ApplicationShortcut); //fake containment/applet actions KActionCollection *containmentActions = AppletPrivate::defaultActions(q); //containment has to start with applet stuff ContainmentPrivate::addDefaultActions(containmentActions); //now it's really containment QAction *editAction = actions.add(QStringLiteral("edit mode")); QObject::connect(editAction, &QAction::triggered, q, [this] () { q->setEditMode(!q->isEditMode()); }); editAction->setText(i18n("Customize Layout...")); editAction->setAutoRepeat(true); editAction->setIcon(QIcon::fromTheme(QStringLiteral("document-edit"))); editAction->setData(Plasma::Types::ControlAction); editAction->setShortcut(QKeySequence(QStringLiteral("alt+d, e"))); editAction->setShortcutContext(Qt::ApplicationShortcut); } void CoronaPrivate::toggleImmutability() { if (immutability == Types::Mutable) { q->setImmutability(Types::UserImmutable); } else { q->setImmutability(Types::Mutable); } } void CoronaPrivate::saveLayout(KSharedConfigPtr cg) const { KConfigGroup containmentsGroup(cg, "Containments"); - foreach (const Containment *containment, containments) { + for (const Containment *containment : containments) { QString cid = QString::number(containment->id()); KConfigGroup containmentConfig(&containmentsGroup, cid); containment->save(containmentConfig); } } void CoronaPrivate::updateContainmentImmutability() { - foreach (Containment *c, containments) { + for (Containment *c : qAsConst(containments)) { // we need to tell each containment that immutability has been altered c->updateConstraints(Types::ImmutableConstraint); } } void CoronaPrivate::containmentDestroyed(QObject *obj) { // we do a static_cast here since it really isn't an Containment by this // point anymore since we are in the qobject dtor. we don't actually // try and do anything with it, we just need the value of the pointer // so this unsafe looking code is actually just fine. Containment *containment = static_cast(obj); int index = containments.indexOf(containment); if (index > -1) { containments.removeAt(index); q->requestConfigSync(); } } void CoronaPrivate::syncConfig() { q->config()->sync(); emit q->configSynced(); } Containment *CoronaPrivate::addContainment(const QString &name, const QVariantList &args, uint id, int lastScreen, bool delayedInit) { QString pluginName = name; Containment *containment = nullptr; Applet *applet = nullptr; // qCDebug(LOG_PLASMA) << "Loading" << name << args << id; if (pluginName.isEmpty() || pluginName == QLatin1String("default")) { // default to the desktop containment pluginName = desktopDefaultsConfig.readEntry("Containment", "org.kde.desktopcontainment"); } bool loadingNull = pluginName == QLatin1String("null"); if (!loadingNull) { applet = PluginLoader::self()->loadApplet(pluginName, id, args); containment = dynamic_cast(applet); if (containment) { containment->setParent(q); } } if (!containment) { if (!loadingNull) { #ifndef NDEBUG // qCDebug(LOG_PLASMA) << "loading of containment" << name << "failed."; #endif } // in case we got a non-Containment from Applet::loadApplet or // a null containment was requested if (applet) { // the applet probably doesn't know what's hit it, so let's pretend it can be // initialized to make assumptions in the applet's dtor safer applet->init(); delete applet; } applet = containment = new Containment(q, {}, id); if (lastScreen >= 0) { containment->d->lastScreen = lastScreen; } //if it's a dummy containment, just say its ui is ready, not blocking the corona applet->updateConstraints(Plasma::Types::UiReadyConstraint); // we want to provide something and don't care about the failure to launch containment->setFormFactor(Plasma::Types::Planar); } // if this is a new containment, we need to ensure that there are no stale // configuration data around if (id == 0) { KConfigGroup conf(q->config(), "Containments"); conf = KConfigGroup(&conf, QString::number(containment->id())); conf.deleteGroup(); } //make sure the containments are sorted by id auto position = std::lower_bound(containments.begin(), containments.end(), containment, [](Plasma::Containment *c1, Plasma::Containment *c2) { return c1->id() < c2->id(); }); containments.insert(position, containment); QObject::connect(containment, SIGNAL(destroyed(QObject*)), q, SLOT(containmentDestroyed(QObject*))); QObject::connect(containment, &Applet::configNeedsSaving, q, &Corona::requestConfigSync); QObject::connect(containment, &Containment::screenChanged, q, &Corona::screenOwnerChanged); if (!delayedInit) { containment->init(); KConfigGroup cg = containment->config(); containment->restore(cg); containment->updateConstraints(Plasma::Types::StartupCompletedConstraint); containment->save(cg); q->requestConfigSync(); containment->flushPendingConstraintsEvents(); emit q->containmentAdded(containment); //if id = 0 a new containment has been created, not restored if (id == 0) { emit q->containmentCreated(containment); } } return containment; } QList CoronaPrivate::importLayout(const KConfigGroup &conf, bool mergeConfig) { if (!conf.isValid()) { return QList(); } QList newContainments; QSet containmentsIds; - foreach (Containment *containment, containments) { + for (Containment *containment : qAsConst(containments)) { containmentsIds.insert(containment->id()); } KConfigGroup containmentsGroup(&conf, "Containments"); QStringList groups = containmentsGroup.groupList(); std::sort(groups.begin(), groups.end()); - foreach (const QString &group, groups) { + for (const QString &group : qAsConst(groups)) { KConfigGroup containmentConfig(&containmentsGroup, group); if (containmentConfig.entryMap().isEmpty()) { continue; } uint cid = group.toUInt(); if (containmentsIds.contains(cid)) { cid = ++AppletPrivate::s_maxAppletId; } else if (cid > AppletPrivate::s_maxAppletId) { AppletPrivate::s_maxAppletId = cid; } if (mergeConfig) { KConfigGroup realConf(q->config(), "Containments"); realConf = KConfigGroup(&realConf, QString::number(cid)); // in case something was there before us realConf.deleteGroup(); containmentConfig.copyTo(&realConf); } //qCDebug(LOG_PLASMA) << "got a containment in the config, trying to make a" << containmentConfig.readEntry("plugin", QString()) << "from" << group; #ifndef NDEBUG // qCDebug(LOG_PLASMA) << "!!{} STARTUP TIME" << QTime().msecsTo(QTime::currentTime()) << "Adding Containment" << containmentConfig.readEntry("plugin", QString()); #endif Containment *c = addContainment(containmentConfig.readEntry("plugin", QString()), QVariantList(), cid, -1); if (!c) { continue; } newContainments.append(c); containmentsIds.insert(c->id()); #ifndef NDEBUG // qCDebug(LOG_PLASMA) << "!!{} STARTUP TIME" << QTime().msecsTo(QTime::currentTime()) << "Restored Containment" << c->pluginName(); #endif } if (!mergeConfig) { notifyContainmentsReady(); } return newContainments; } void CoronaPrivate::notifyContainmentsReady() { containmentsStarting = 0; - foreach (Containment *containment, containments) { + for (Containment *containment : qAsConst(containments)) { if (!containment->isUiReady() && containment->screen() >= 0) { ++containmentsStarting; QObject::connect(containment, &Plasma::Containment::uiReadyChanged, q, [this](bool ready) { containmentReady(ready); } ); } } if (containmentsStarting <= 0) { emit q->startupCompleted(); } } void CoronaPrivate::containmentReady(bool ready) { if (!ready) { return; } --containmentsStarting; if (containmentsStarting <= 0) { emit q->startupCompleted(); } } } // namespace Plasma #include "moc_corona.cpp" diff --git a/src/plasma/datacontainer.cpp b/src/plasma/datacontainer.cpp index 85a96f0a2..d21b41940 100644 --- a/src/plasma/datacontainer.cpp +++ b/src/plasma/datacontainer.cpp @@ -1,422 +1,422 @@ /* * Copyright 2006-2007 Aaron Seigo * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "datacontainer.h" #include "private/datacontainer_p.h" #include "private/storage_p.h" #include #include #include #include "plasma.h" #include "debug_p.h" namespace Plasma { DataContainer::DataContainer(QObject *parent) : QObject(parent), d(new DataContainerPrivate(this)) { } DataContainer::~DataContainer() { delete d; } const DataEngine::Data DataContainer::data() const { return d->data; } void DataContainer::setData(const QString &key, const QVariant &value) { if (!value.isValid()) { d->data.remove(key); } else { d->data.insert(key, value); } d->dirty = true; d->updateTimer.start(); //check if storage is enabled and if storage is needed. //If it is not set to be stored,then this is the first //setData() since the last time it was stored. This //gives us only one singleShot timer. if (isStorageEnabled() || !needsToBeStored()) { d->storageTimer.start(180000, this); } setNeedsToBeStored(true); } void DataContainer::setModel(QAbstractItemModel *model) { if (d->model.data() == model) { return; } if (d->model) { d->model.data()->deleteLater(); } d->model = model; model->setParent(this); emit modelChanged(objectName(), model); } QAbstractItemModel *DataContainer::model() { return d->model.data(); } void DataContainer::removeAllData() { if (d->data.isEmpty()) { // avoid an update if we don't have any data anyways return; } d->data.clear(); d->dirty = true; d->updateTimer.start(); } bool DataContainer::visualizationIsConnected(QObject *visualization) const { return d->relayObjects.contains(visualization); } void DataContainer::connectVisualization(QObject *visualization, uint pollingInterval, Plasma::Types::IntervalAlignment alignment) { //qCDebug(LOG_PLASMA) << "connecting visualization" <::iterator objIt = d->relayObjects.find(visualization); bool connected = objIt != d->relayObjects.end(); if (connected) { // this visualization is already connected. just adjust the update // frequency if necessary SignalRelay *relay = objIt.value(); if (relay) { // connected to a relay //qCDebug(LOG_PLASMA) << " already connected, but to a relay"; if (relay->m_interval == pollingInterval && relay->m_align == alignment) { //qCDebug(LOG_PLASMA) << " already connected to a relay of the same interval of" // << pollingInterval << ", nothing to do"; return; } if (relay->receiverCount() == 1) { //qCDebug(LOG_PLASMA) << " removing relay, as it is now unused"; d->relays.remove(relay->m_interval); delete relay; } else { if (visualization->metaObject()->indexOfSlot("dataUpdated(QString,Plasma::DataEngine::Data)") >= 0) { disconnect(relay, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)), visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data))); } //modelChanged is always emitted by the dataSource since there is no polling there if (visualization->metaObject()->indexOfSlot("modelChanged(QString,QAbstractItemModel*)") >= 0) { disconnect(this, SIGNAL(modelChanged(QString,QAbstractItemModel*)), visualization, SLOT(modelChanged(QString,QAbstractItemModel*))); } //relay->isUnused(); } } else if (pollingInterval < 1) { // the visualization was connected already, but not to a relay // and it still doesn't want to connect to a relay, so we have // nothing to do! //qCDebug(LOG_PLASMA) << " already connected, nothing to do"; return; } else { if (visualization->metaObject()->indexOfSlot("dataUpdated(QString,Plasma::DataEngine::Data)") >= 0) { disconnect(this, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)), visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data))); } if (visualization->metaObject()->indexOfSlot("modelChanged(QString,QAbstractItemModel*)") >= 0) { disconnect(this, SIGNAL(modelChanged(QString,QAbstractItemModel*)), visualization, SLOT(modelChanged(QString,QAbstractItemModel*))); } } } else { connect(visualization, &QObject::destroyed, this, &DataContainer::disconnectVisualization); //, Qt::QueuedConnection); } if (pollingInterval < 1) { //qCDebug(LOG_PLASMA) << " connecting directly"; d->relayObjects[visualization] = nullptr; if (visualization->metaObject()->indexOfSlot("dataUpdated(QString,Plasma::DataEngine::Data)") >= 0) { connect(this, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)), visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data))); } if (visualization->metaObject()->indexOfSlot("modelChanged(QString,QAbstractItemModel*)") >= 0) { connect(this, SIGNAL(modelChanged(QString,QAbstractItemModel*)), visualization, SLOT(modelChanged(QString,QAbstractItemModel*))); } } else { //qCDebug(LOG_PLASMA) << " connecting to a relay"; // we only want to do an immediate update if this is not the first object to connect to us // if it is the first visualization, then the source will already have been populated // engine's sourceRequested method bool immediateUpdate = connected || d->relayObjects.count() > 1; SignalRelay *relay = d->signalRelay(this, visualization, pollingInterval, alignment, immediateUpdate); if (visualization->metaObject()->indexOfSlot("dataUpdated(QString,Plasma::DataEngine::Data)") >= 0) { connect(relay, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)), visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data))); } //modelChanged is always emitted by the dataSource since there is no polling there if (visualization->metaObject()->indexOfSlot("modelChanged(QString,QAbstractItemModel*)") >= 0) { connect(this, SIGNAL(modelChanged(QString,QAbstractItemModel*)), visualization, SLOT(modelChanged(QString,QAbstractItemModel*))); } } } void DataContainer::setStorageEnabled(bool store) { d->enableStorage = store; if (store) { QTimer::singleShot(QRandomGenerator::global()->bounded(2000 + 1), this, SLOT(retrieve())); } } bool DataContainer::isStorageEnabled() const { return d->enableStorage; } bool DataContainer::needsToBeStored() const { return !d->isStored; } void DataContainer::setNeedsToBeStored(bool store) { d->isStored = !store; } DataEngine *DataContainer::getDataEngine() { QObject *o = this; DataEngine *de = nullptr; while (de == nullptr) { o = dynamic_cast(o->parent()); if (o == nullptr) { return nullptr; } de = dynamic_cast(o); } return de; } void DataContainerPrivate::store() { if (!q->needsToBeStored() || !q->isStorageEnabled()) { return; } DataEngine *de = q->getDataEngine(); if (!de) { return; } q->setNeedsToBeStored(false); if (!storage) { storage = new Storage(q); } QVariantMap op = storage->operationDescription(QStringLiteral("save")); op[QStringLiteral("group")] = q->objectName(); StorageJob *job = static_cast(storage->startOperationCall(op)); job->setData(data); storageCount++; QObject::connect(job, SIGNAL(finished(KJob*)), q, SLOT(storeJobFinished(KJob*))); } void DataContainerPrivate::storeJobFinished(KJob *) { --storageCount; if (storageCount < 1) { storage->deleteLater(); storage = nullptr; } } void DataContainerPrivate::retrieve() { DataEngine *de = q->getDataEngine(); if (de == nullptr) { return; } if (!storage) { storage = new Storage(q); } QVariantMap retrieveGroup = storage->operationDescription(QStringLiteral("retrieve")); retrieveGroup[QStringLiteral("group")] = q->objectName(); ServiceJob *retrieveJob = storage->startOperationCall(retrieveGroup); QObject::connect(retrieveJob, SIGNAL(result(KJob*)), q, SLOT(populateFromStoredData(KJob*))); } void DataContainerPrivate::populateFromStoredData(KJob *job) { if (job->error()) { return; } StorageJob *ret = dynamic_cast(job); if (!ret) { return; } // Only fill the source with old stored // data if it is not already populated with new data. if (data.isEmpty() && !ret->data().isEmpty()) { data = ret->data(); dirty = true; q->forceImmediateUpdate(); } QVariantMap expireGroup = storage->operationDescription(QStringLiteral("expire")); //expire things older than 4 days expireGroup[QStringLiteral("age")] = 345600; storage->startOperationCall(expireGroup); } void DataContainer::disconnectVisualization(QObject *visualization) { QMap::iterator objIt = d->relayObjects.find(visualization); disconnect(visualization, &QObject::destroyed, this, &DataContainer::disconnectVisualization); //, Qt::QueuedConnection); if (objIt == d->relayObjects.end() || !objIt.value()) { // it is connected directly to the DataContainer itself if (visualization->metaObject()->indexOfSlot("dataUpdated(QString,Plasma::DataEngine::Data)") >= 0) { disconnect(this, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)), visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data))); } if (visualization->metaObject()->indexOfSlot("modelChanged(QString,QAbstractItemModel*)") >= 0) { disconnect(this, SIGNAL(modelChanged(QString,QAbstractItemModel*)), visualization, SLOT(modelChanged(QString,QAbstractItemModel*))); } } else { SignalRelay *relay = objIt.value(); if (relay->receiverCount() == 1) { d->relays.remove(relay->m_interval); delete relay; } else { if (visualization->metaObject()->indexOfSlot("dataUpdated(QString,Plasma::DataEngine::Data)") >= 0) { disconnect(relay, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)), visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data))); } //modelChanged is always emitted by the dataSource since there is no polling there if (visualization->metaObject()->indexOfSlot("modelChanged(QString,QAbstractItemModel*)") >= 0) { disconnect(this, SIGNAL(modelChanged(QString,QAbstractItemModel*)), visualization, SLOT(modelChanged(QString,QAbstractItemModel*))); } } } d->relayObjects.erase(objIt); d->checkUsage(); } void DataContainer::checkForUpdate() { //qCDebug(LOG_PLASMA) << objectName() << d->dirty; if (d->dirty) { emit dataUpdated(objectName(), d->data); - foreach (SignalRelay *relay, d->relays) { + for (SignalRelay *relay : qAsConst(d->relays)) { relay->checkQueueing(); } d->dirty = false; } } void DataContainer::forceImmediateUpdate() { if (d->dirty) { d->dirty = false; emit dataUpdated(objectName(), d->data); } - foreach (SignalRelay *relay, d->relays) { + for (SignalRelay *relay : qAsConst(d->relays)) { relay->forceImmediateUpdate(); } } uint DataContainer::timeSinceLastUpdate() const { return d->updateTimer.elapsed(); } void DataContainer::setNeedsUpdate(bool update) { d->cached = update; } bool DataContainer::isUsed() const { return !d->relays.isEmpty() || receivers(SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data))) > 0; } void DataContainerPrivate::checkUsage() { if (!checkUsageTimer.isActive()) { checkUsageTimer.start(10, q); } } void DataContainer::timerEvent(QTimerEvent *event) { if (event->timerId() == d->checkUsageTimer.timerId()) { if (!isUsed()) { // DO NOT CALL ANYTHING AFTER THIS LINE AS IT MAY GET DELETED! //qCDebug(LOG_PLASMA) << objectName() << "is unused"; //NOTE: Notifying visualization of the model destruction before actual deletion avoids crashes in some edge cases if (d->model) { d->model.clear(); emit modelChanged(objectName(), nullptr); } emit becameUnused(objectName()); } d->checkUsageTimer.stop(); } else if (event->timerId() == d->storageTimer.timerId()) { d->store(); d->storageTimer.stop(); } } } // Plasma namespace #include "moc_datacontainer.cpp" diff --git a/src/plasma/dataengine.cpp b/src/plasma/dataengine.cpp index 310bfae23..2eb3f0287 100644 --- a/src/plasma/dataengine.cpp +++ b/src/plasma/dataengine.cpp @@ -1,653 +1,653 @@ /* * Copyright 2006-2007 Aaron Seigo * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "dataengine.h" #include "private/dataengine_p.h" #include "private/datacontainer_p.h" #include #include #include #include #include #include #include #include #include #include #include #include "datacontainer.h" #include "package.h" #include "pluginloader.h" #include "service.h" #include "scripting/dataenginescript.h" #include "private/service_p.h" #include "private/storage_p.h" #include "config-plasma.h" namespace Plasma { DataEngine::DataEngine(const KPluginInfo &plugin, QObject *parent) : DataEngine(plugin.toMetaData(), parent) {} DataEngine::DataEngine(const KPluginMetaData &plugin, QObject *parent) : QObject(parent), d(new DataEnginePrivate(this, plugin)) { if (d->script) { d->setupScriptSupport(); d->script->init(); } else { // default implementation does nothing. this is for engines that have to // start things in motion external to themselves before they can work } } DataEngine::DataEngine(QObject *parent, const QVariantList &args) : QObject(parent), d(new DataEnginePrivate(this, KPluginInfo(args).toMetaData(), args)) { if (d->script) { d->setupScriptSupport(); d->script->init(); } } DataEngine::~DataEngine() { //qCDebug(LOG_PLASMA) << objectName() << ": bye bye birdy! "; delete d; } QStringList DataEngine::sources() const { if (d->script) { return d->script->sources(); } else { return d->sources.keys(); } } Service *DataEngine::serviceForSource(const QString &source) { if (d->script) { Service *s = d->script->serviceForSource(source); if (s) { return s; } } return new NullService(source, this); } KPluginInfo DataEngine::pluginInfo() const { return KPluginInfo(d->dataEngineDescription); } KPluginMetaData DataEngine::metadata() const { return d->dataEngineDescription; } void DataEngine::connectSource(const QString &source, QObject *visualization, uint pollingInterval, Plasma::Types::IntervalAlignment intervalAlignment) const { //qCDebug(LOG_PLASMA) << "connectSource" << source; bool newSource; DataContainer *s = d->requestSource(source, &newSource); if (s) { // we suppress the immediate invocation of dataUpdated here if the // source was prexisting and they don't request delayed updates // (we want to do an immediate update in that case so they don't // have to wait for the first time out) if (newSource && !s->data().isEmpty()) { newSource = false; } d->connectSource(s, visualization, pollingInterval, intervalAlignment, !newSource || pollingInterval > 0); //qCDebug(LOG_PLASMA) << " ==> source connected"; } } void DataEngine::connectAllSources(QObject *visualization, uint pollingInterval, Plasma::Types::IntervalAlignment intervalAlignment) const { - foreach (DataContainer *s, d->sources) { + for (DataContainer *s : qAsConst(d->sources)) { d->connectSource(s, visualization, pollingInterval, intervalAlignment); } } void DataEngine::disconnectSource(const QString &source, QObject *visualization) const { DataContainer *s = d->source(source, false); if (s) { s->disconnectVisualization(visualization); } } DataContainer *DataEngine::containerForSource(const QString &source) { return d->source(source, false); } bool DataEngine::sourceRequestEvent(const QString &name) { if (d->script) { return d->script->sourceRequestEvent(name); } else { return false; } } bool DataEngine::updateSourceEvent(const QString &source) { if (d->script) { return d->script->updateSourceEvent(source); } else { //qCDebug(LOG_PLASMA) << source; return false; //TODO: should this be true to trigger, even needless, updates on every tick? } } void DataEngine::setData(const QString &source, const QVariant &value) { setData(source, source, value); } void DataEngine::setData(const QString &source, const QString &key, const QVariant &value) { DataContainer *s = d->source(source, false); bool isNew = !s; if (isNew) { s = d->source(source); } s->setData(key, value); if (isNew && source != d->waitingSourceRequest) { emit sourceAdded(source); } d->scheduleSourcesUpdated(); } void DataEngine::setData(const QString &source, const QVariantMap &data) { DataContainer *s = d->source(source, false); bool isNew = !s; if (isNew) { s = d->source(source); } Data::const_iterator it = data.constBegin(); while (it != data.constEnd()) { s->setData(it.key(), it.value()); ++it; } if (isNew && source != d->waitingSourceRequest) { emit sourceAdded(source); } d->scheduleSourcesUpdated(); } void DataEngine::removeAllData(const QString &source) { DataContainer *s = d->source(source, false); if (s) { s->removeAllData(); d->scheduleSourcesUpdated(); } } void DataEngine::removeData(const QString &source, const QString &key) { DataContainer *s = d->source(source, false); if (s) { s->setData(key, QVariant()); d->scheduleSourcesUpdated(); } } void DataEngine::setModel(const QString &source, QAbstractItemModel *model) { if (model) { setData(source, QStringLiteral("HasModel"), true); } else { removeData(source, QStringLiteral("HasModel")); } Plasma::DataContainer *s = containerForSource(source); if (s) { s->setModel(model); } } QAbstractItemModel *DataEngine::modelForSource(const QString &source) { Plasma::DataContainer *s = containerForSource(source); if (s) { return s->model(); } else { return nullptr; } } void DataEngine::addSource(DataContainer *source) { if (d->sources.contains(source->objectName())) { #ifndef NDEBUG // qCDebug(LOG_PLASMA) << "source named \"" << source->objectName() << "\" already exists."; #endif return; } QObject::connect(source, SIGNAL(updateRequested(DataContainer*)), this, SLOT(internalUpdateSource(DataContainer*))); QObject::connect(source, SIGNAL(destroyed(QObject*)), this, SLOT(sourceDestroyed(QObject*))); d->sources.insert(source->objectName(), source); emit sourceAdded(source->objectName()); d->scheduleSourcesUpdated(); } void DataEngine::setMinimumPollingInterval(int minimumMs) { d->minPollingInterval = minimumMs; } int DataEngine::minimumPollingInterval() const { return d->minPollingInterval; } void DataEngine::setPollingInterval(uint frequency) { killTimer(d->updateTimerId); d->updateTimerId = 0; if (frequency > 0) { d->updateTimerId = startTimer(frequency); } } void DataEngine::removeSource(const QString &source) { QHash::iterator it = d->sources.find(source); if (it != d->sources.end()) { DataContainer *s = it.value(); s->d->store(); d->sources.erase(it); s->disconnect(this); s->deleteLater(); emit sourceRemoved(source); } } void DataEngine::removeAllSources() { QMutableHashIterator it(d->sources); while (it.hasNext()) { it.next(); Plasma::DataContainer *s = it.value(); const QString source = it.key(); it.remove(); s->disconnect(this); s->deleteLater(); emit sourceRemoved(source); } } bool DataEngine::isValid() const { return d->valid; } bool DataEngine::isEmpty() const { return d->sources.isEmpty(); } void DataEngine::setValid(bool valid) { d->valid = valid; } QHash DataEngine::containerDict() const { return d->sources; } void DataEngine::timerEvent(QTimerEvent *event) { //qCDebug(LOG_PLASMA); if (event->timerId() == d->updateTimerId) { // if the freq update is less than 0, don't bother if (d->minPollingInterval < 0) { //qCDebug(LOG_PLASMA) << "uh oh.. no polling allowed!"; return; } // minPollingInterval if (d->updateTimer.elapsed() < d->minPollingInterval) { //qCDebug(LOG_PLASMA) << "hey now.. slow down!"; return; } d->updateTimer.start(); updateAllSources(); } else if (event->timerId() == d->checkSourcesTimerId) { killTimer(d->checkSourcesTimerId); d->checkSourcesTimerId = 0; QHashIterator it(d->sources); while (it.hasNext()) { it.next(); it.value()->checkForUpdate(); } } else { QObject::timerEvent(event); } } void DataEngine::updateAllSources() { QHashIterator it(d->sources); while (it.hasNext()) { it.next(); //qCDebug(LOG_PLASMA) << "updating" << it.key(); if (it.value()->isUsed()) { updateSourceEvent(it.key()); } } d->scheduleSourcesUpdated(); } void DataEngine::forceImmediateUpdateOfAllVisualizations() { - foreach (DataContainer *source, d->sources) { + for (DataContainer *source : qAsConst(d->sources)) { if (source->isUsed()) { source->forceImmediateUpdate(); } } } Package DataEngine::package() const { return d->package ? *d->package : Package(); } void DataEngine::setStorageEnabled(const QString &source, bool store) { DataContainer *s = d->source(source, false); if (s) { s->setStorageEnabled(store); } } // Private class implementations DataEnginePrivate::DataEnginePrivate(DataEngine *e, const KPluginMetaData &md, const QVariantList &args) : q(e), dataEngineDescription(md), refCount(-1), // first ref checkSourcesTimerId(0), updateTimerId(0), minPollingInterval(-1), valid(false), script(nullptr), package(nullptr) { updateTimer.start(); if (dataEngineDescription.isValid()) { valid = true; e->setObjectName(dataEngineDescription.name()); } if (dataEngineDescription.isValid()) { QString api = dataEngineDescription.value(QStringLiteral("X-Plasma-API")); if (!api.isEmpty()) { const QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral(PLASMA_RELATIVE_DATA_INSTALL_DIR "/dataengines/") + dataEngineDescription.pluginId(), QStandardPaths::LocateDirectory); package = new Package(PluginLoader::self()->loadPackage(QStringLiteral("Plasma/DataEngine"), api)); package->setPath(path); if (package->isValid()) { script = Plasma::loadScriptEngine(api, q, args); } if (!script) { #ifndef NDEBUG // qCDebug(LOG_PLASMA) << "Could not create a" << api << "ScriptEngine for the" // << dataEngineDescription.name() << "DataEngine."; #endif delete package; package = nullptr; } } } } DataEnginePrivate::~DataEnginePrivate() { delete script; script = nullptr; delete package; package = nullptr; } void DataEnginePrivate::internalUpdateSource(DataContainer *source) { if (minPollingInterval > 0 && source->timeSinceLastUpdate() < (uint)minPollingInterval) { // skip updating this source; it's been too soon //qCDebug(LOG_PLASMA) << "internal update source is delaying" << source->timeSinceLastUpdate() << minPollingInterval; //but fake an update so that the signalrelay that triggered this gets the data from the //recent update. this way we don't have to worry about queuing - the relay will send a //signal immediately and everyone else is undisturbed. source->setNeedsUpdate(); return; } if (q->updateSourceEvent(source->objectName())) { //qCDebug(LOG_PLASMA) << "queuing an update"; scheduleSourcesUpdated(); }/* else { #ifndef NDEBUG // qCDebug(LOG_PLASMA) << "no update"; #endif }*/ } void DataEnginePrivate::ref() { --refCount; } void DataEnginePrivate::deref() { ++refCount; } bool DataEnginePrivate::isUsed() const { return refCount != 0; } DataContainer *DataEnginePrivate::source(const QString &sourceName, bool createWhenMissing) { QHash::const_iterator it = sources.constFind(sourceName); if (it != sources.constEnd()) { DataContainer *s = it.value(); return s; } if (!createWhenMissing) { return nullptr; } //qCDebug(LOG_PLASMA) << "DataEngine " << q->objectName() << ": could not find DataContainer " << sourceName << ", creating"; DataContainer *s = new DataContainer(q); s->setObjectName(sourceName); sources.insert(sourceName, s); QObject::connect(s, SIGNAL(destroyed(QObject*)), q, SLOT(sourceDestroyed(QObject*))); QObject::connect(s, SIGNAL(updateRequested(DataContainer*)), q, SLOT(internalUpdateSource(DataContainer*))); return s; } void DataEnginePrivate::connectSource(DataContainer *s, QObject *visualization, uint pollingInterval, Plasma::Types::IntervalAlignment align, bool immediateCall) { //qCDebug(LOG_PLASMA) << "connect source called" << s->objectName() << "with interval" << pollingInterval; if (pollingInterval > 0) { // never more frequently than allowed, never more than 20 times per second uint min = qMax(50, minPollingInterval); // for qMax below pollingInterval = qMax(min, pollingInterval); // align on the 50ms pollingInterval = pollingInterval - (pollingInterval % 50); } if (immediateCall) { // we don't want to do an immediate call if we are simply // reconnecting //qCDebug(LOG_PLASMA) << "immediate call requested, we have:" << s->visualizationIsConnected(visualization); immediateCall = !s->data().isEmpty() && !s->visualizationIsConnected(visualization); } s->connectVisualization(visualization, pollingInterval, align); if (immediateCall) { QMetaObject::invokeMethod(visualization, "dataUpdated", Q_ARG(QString, s->objectName()), Q_ARG(Plasma::DataEngine::Data, s->data())); if (s->d->model) { QMetaObject::invokeMethod(visualization, "modelChanged", Q_ARG(QString, s->objectName()), Q_ARG(QAbstractItemModel*, s->d->model.data())); } s->d->dirty = false; } } void DataEnginePrivate::sourceDestroyed(QObject *object) { QHash::iterator it = sources.begin(); while (it != sources.end()) { if (it.value() == object) { sources.erase(it); emit q->sourceRemoved(object->objectName()); break; } ++it; } } DataContainer *DataEnginePrivate::requestSource(const QString &sourceName, bool *newSource) { if (newSource) { *newSource = false; } //qCDebug(LOG_PLASMA) << "requesting source " << sourceName; DataContainer *s = source(sourceName, false); if (!s) { // we didn't find a data source, so give the engine an opportunity to make one /*// qCDebug(LOG_PLASMA) << "DataEngine " << q->objectName() << ": could not find DataContainer " << sourceName << " will create on request" << endl;*/ waitingSourceRequest = sourceName; if (q->sourceRequestEvent(sourceName)) { s = source(sourceName, false); if (s) { // now we have a source; since it was created on demand, assume // it should be removed when not used if (newSource) { *newSource = true; } QObject::connect(s, &DataContainer::becameUnused, q, &DataEngine::removeSource); emit q->sourceAdded(sourceName); } } waitingSourceRequest.clear(); } return s; } // put all setup routines for script here. at this point we can assume that // package exists and that we have a script engine void DataEnginePrivate::setupScriptSupport() { if (!package) { return; } /* #ifndef NDEBUG // qCDebug(LOG_PLASMA) << "sletting up script support, package is in" << package->path() #endif << "which is a" << package->structure()->type() << "package" << ", main script is" << package->filePath("mainscript"); */ // FIXME: Replace with ki18n functionality once semantics is clear. // const QString translationsPath = package->filePath("translations"); // if (!translationsPath.isEmpty()) { // KGlobal::dirs()->addResourceDir("locale", translationsPath); // } } void DataEnginePrivate::scheduleSourcesUpdated() { if (checkSourcesTimerId) { return; } checkSourcesTimerId = q->startTimer(0); } } #include "moc_dataengine.cpp" diff --git a/src/plasma/dataengineconsumer.cpp b/src/plasma/dataengineconsumer.cpp index b9e9bfeb4..c9f2ead00 100644 --- a/src/plasma/dataengineconsumer.cpp +++ b/src/plasma/dataengineconsumer.cpp @@ -1,107 +1,107 @@ /* * Copyright 2005 by Aaron Seigo * Copyright 2007 by Riccardo Iaconelli * Copyright 2008 by Ménard Alexis * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "dataengineconsumer.h" #include "private/dataengineconsumer_p.h" #include #include #include #include "private/dataenginemanager_p.h" #include "servicejob.h" #include "debug_p.h" namespace Plasma { void DataEngineConsumerPrivate::slotJobFinished(Plasma::ServiceJob *job) { #ifndef NDEBUG // qCDebug(LOG_PLASMA) << "engine ready!"; QString engineName = job->parameters().value(QStringLiteral("EngineName")).toString(); QString location = job->destination(); QPair pair(location, engineName); // qCDebug(LOG_PLASMA) << "pair = " << pair; #endif } void DataEngineConsumerPrivate::slotServiceReady(Plasma::Service *plasmoidService) { #ifndef NDEBUG // qCDebug(LOG_PLASMA) << "service ready!"; #endif if (!engineNameForService.contains(plasmoidService)) { #ifndef NDEBUG // qCDebug(LOG_PLASMA) << "no engine name for service!"; #endif #ifndef NDEBUG // qCDebug(LOG_PLASMA) << "amount of services in map: " << engineNameForService.count(); #endif } else { #ifndef NDEBUG // qCDebug(LOG_PLASMA) << "value = " << engineNameForService.value(plasmoidService); #endif } #ifndef NDEBUG // qCDebug(LOG_PLASMA) << "requesting dataengine!"; #endif QVariantMap op = plasmoidService->operationDescription(QStringLiteral("DataEngine")); op[QStringLiteral("EngineName")] = engineNameForService.value(plasmoidService); plasmoidService->startOperationCall(op); connect(plasmoidService, SIGNAL(finished(Plasma::ServiceJob*)), this, SLOT(slotJobFinished(Plasma::ServiceJob*))); } DataEngineConsumer::DataEngineConsumer() : d(new DataEngineConsumerPrivate) { } DataEngineConsumer::~DataEngineConsumer() { - foreach (const QString &engine, d->loadedEngines) { + for (const QString &engine : qAsConst(d->loadedEngines)) { DataEngineManager::self()->unloadEngine(engine); } delete d; } DataEngine *DataEngineConsumer::dataEngine(const QString &name) { if (d->loadedEngines.contains(name)) { DataEngine *engine = DataEngineManager::self()->engine(name); if (engine->isValid()) { return engine; } } DataEngine *engine = DataEngineManager::self()->loadEngine(name); d->loadedEngines.insert(name); return engine; } } // namespace Plasma #include "private/moc_dataengineconsumer_p.cpp" diff --git a/src/plasma/package.cpp b/src/plasma/package.cpp index db97a501b..0972960cd 100644 --- a/src/plasma/package.cpp +++ b/src/plasma/package.cpp @@ -1,317 +1,321 @@ /****************************************************************************** * Copyright 2007 by Aaron Seigo * * Copyright 2010 by Marco Martin * * Copyright 2010 by Kevin Ottens * * Copyright 2009 by Rob Scheepmaker * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public License * * along with this library; see the file COPYING.LIB. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * *******************************************************************************/ #include "package.h" #include #include #include #include #include #include #include #include "config-plasma.h" #include #include #include "packagestructure.h" #include "pluginloader.h" #include "private/package_p.h" #include "private/packagestructure_p.h" namespace Plasma { PackagePrivate::PackagePrivate() : internalPackage(nullptr), fallbackPackage(nullptr), structure(nullptr) { } PackagePrivate::~PackagePrivate() { } Package::Package(PackageStructure *structure) : d(new Plasma::PackagePrivate()) { d->structure = structure; if (!structure) { d->internalPackage = new KPackage::Package(); return; } if (!structure->d->internalStructure) { d->structure->d->internalStructure = new KPackage::PackageStructure; } d->internalPackage = new KPackage::Package(structure->d->internalStructure); PackageStructureWrapper::s_packagesMap[d->internalPackage] = this; structure->initPackage(this); } Package::Package(const Package &other) : d(new Plasma::PackagePrivate()) { d->internalPackage = new KPackage::Package(*other.d->internalPackage); d->structure = other.d->structure; PackageStructureWrapper::s_packagesMap[d->internalPackage] = this; } Package::Package(const KPackage::Package &other) : d(new Plasma::PackagePrivate()) { d->internalPackage = new KPackage::Package(other); PackageStructureWrapper::s_packagesMap[d->internalPackage] = this; } Package::~Package() { PackageStructureWrapper::s_packagesMap.remove(d->internalPackage); delete d->internalPackage; } Package &Package::operator=(const Package &rhs) { if (&rhs != this) { d->internalPackage = new KPackage::Package(*rhs.d->internalPackage); d->structure = rhs.d->structure; PackageStructureWrapper::s_packagesMap[d->internalPackage] = this; } return *this; } bool Package::hasValidStructure() const { return d->internalPackage->hasValidStructure(); } bool Package::isValid() const { return d->internalPackage->isValid(); } QString Package::name(const char *key) const { return d->internalPackage->name(key); } bool Package::isRequired(const char *key) const { return d->internalPackage->isRequired(key); } QStringList Package::mimeTypes(const char *key) const { return d->internalPackage->mimeTypes(key); } QString Package::defaultPackageRoot() const { return d->internalPackage->defaultPackageRoot(); } void Package::setDefaultPackageRoot(const QString &packageRoot) { d->internalPackage->setDefaultPackageRoot(packageRoot); } void Package::setFallbackPackage(const Plasma::Package &package) { d->fallbackPackage = new Package(package); d->internalPackage->setFallbackPackage(*package.d->internalPackage); } Plasma::Package Package::fallbackPackage() const { if (d->fallbackPackage) { return (*d->fallbackPackage); } else { return Package(); } } QString Package::servicePrefix() const { return d->servicePrefix; } void Package::setServicePrefix(const QString &servicePrefix) { d->servicePrefix = servicePrefix; } KPackage::Package Package::kPackage() const { return *d->internalPackage; } bool Package::allowExternalPaths() const { return d->internalPackage->allowExternalPaths(); } void Package::setAllowExternalPaths(bool allow) { d->internalPackage->setAllowExternalPaths(allow); } KPluginInfo Package::metadata() const { return KPluginInfo::fromMetaData(d->internalPackage->metadata()); } QString Package::filePath(const char *fileType, const QString &filename) const { return d->internalPackage->filePath(fileType, filename); } QStringList Package::entryList(const char *key) const { return d->internalPackage->entryList(key); } void Package::setPath(const QString &path) { if (path == d->internalPackage->path()) { return; } d->internalPackage->setPath(path); } const QString Package::path() const { return d->internalPackage->path(); } QStringList Package::contentsPrefixPaths() const { return d->internalPackage->contentsPrefixPaths(); } void Package::setContentsPrefixPaths(const QStringList &prefixPaths) { d->internalPackage->setContentsPrefixPaths(prefixPaths); } QString Package::contentsHash() const { return QString::fromLocal8Bit(d->internalPackage->cryptographicHash(QCryptographicHash::Sha1)); } void Package::addDirectoryDefinition(const char *key, const QString &path, const QString &name) { d->internalPackage->addDirectoryDefinition(key, path, name); } void Package::addFileDefinition(const char *key, const QString &path, const QString &name) { d->internalPackage->addFileDefinition(key, path, name); } void Package::removeDefinition(const char *key) { d->internalPackage->removeDefinition(key); } void Package::setRequired(const char *key, bool required) { d->internalPackage->setRequired(key, required); } void Package::setDefaultMimeTypes(QStringList mimeTypes) { d->internalPackage->setDefaultMimeTypes(mimeTypes); } void Package::setMimeTypes(const char *key, QStringList mimeTypes) { d->internalPackage->setMimeTypes(key, mimeTypes); } QList Package::directories() const { QList dirs; - foreach (const auto &data, d->internalPackage->directories()) { + const auto directories = d->internalPackage->directories(); + for (const auto &data : directories) { dirs << data.constData(); } return dirs; } QList Package::requiredDirectories() const { QList dirs; - foreach (const auto &data, d->internalPackage->requiredDirectories()) { + const auto directories = d->internalPackage->requiredDirectories(); + for (const auto &data : directories) { dirs << data.constData(); } return dirs; } QList Package::files() const { QList files; - foreach (const auto &data, d->internalPackage->files()) { + const auto lstFiles = d->internalPackage->files(); + for (const auto &data : lstFiles) { files << data.constData(); } return files; } QList Package::requiredFiles() const { QList files; - foreach (const auto &data, d->internalPackage->requiredFiles()) { + const auto lstFiles = d->internalPackage->requiredFiles(); + for (const auto &data : lstFiles) { files << data.constData(); } return files; } KJob *Package::install(const QString &sourcePackage, const QString &packageRoot) { const QString src = sourcePackage; const QString dest = packageRoot.isEmpty() ? defaultPackageRoot() : packageRoot; //qCDebug(LOG_PLASMA) << "Source: " << src; //qCDebug(LOG_PLASMA) << "PackageRoot: " << dest; KJob *j = d->structure->install(this, src, dest); return j; } KJob *Package::uninstall(const QString &packageName, const QString &packageRoot) { setPath(packageRoot + packageName); return d->structure->uninstall(this, packageRoot); } } // Namespace diff --git a/src/plasma/pluginloader.cpp b/src/plasma/pluginloader.cpp index 79250336c..6f06846ad 100644 --- a/src/plasma/pluginloader.cpp +++ b/src/plasma/pluginloader.cpp @@ -1,901 +1,901 @@ /* * Copyright 2010 Ryan Rix * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "pluginloader.h" #include #include #include #include #include #include #include #include #include "config-plasma.h" #include "applet.h" #include "containment.h" #include "containmentactions.h" #include "dataengine.h" #include "package.h" #include "private/applet_p.h" #include "private/service_p.h" // for NullService #include "private/storage_p.h" #include "private/package_p.h" #include "private/packagestructure_p.h" #include #include "debug_p.h" namespace Plasma { static PluginLoader *s_pluginLoader = nullptr; class PluginLoaderPrivate { public: PluginLoaderPrivate() : isDefaultLoader(false) { } static QSet knownCategories(); static QSet s_customCategories; QHash > structures; bool isDefaultLoader; static QString s_dataEnginePluginDir; static QString s_packageStructurePluginDir; static QString s_plasmoidsPluginDir; static QString s_servicesPluginDir; static QString s_containmentActionsPluginDir; class Cache { // We only use this cache during start of the process to speed up many consecutive calls // After that, we're too afraid to produce race conditions and it's not that time-critical anyway // the 20 seconds here means that the cache is only used within 20sec during startup, after that, // complexity goes up and we'd have to update the cache in order to avoid subtle bugs // just not using the cache is way easier then, since it doesn't make *that* much of a difference, // anyway int maxCacheAge = 20; qint64 pluginCacheAge = 0; QHash> plugins; public: QVector findPluginsById(const QString& name, const QStringList &dirs); }; Cache plasmoidCache; Cache dataengineCache; Cache containmentactionCache; }; QSet PluginLoaderPrivate::s_customCategories; QString PluginLoaderPrivate::s_dataEnginePluginDir = QStringLiteral("plasma/dataengine"); QString PluginLoaderPrivate::s_packageStructurePluginDir = QStringLiteral("plasma/packagestructure"); QString PluginLoaderPrivate::s_plasmoidsPluginDir = QStringLiteral("plasma/applets"); QString PluginLoaderPrivate::s_servicesPluginDir = QStringLiteral("plasma/services"); QString PluginLoaderPrivate::s_containmentActionsPluginDir = QStringLiteral("plasma/containmentactions"); QSet PluginLoaderPrivate::knownCategories() { // this is to trick the translation tools into making the correct // strings for translation QSet categories = s_customCategories; categories << QStringLiteral(I18N_NOOP("Accessibility")).toLower() << QStringLiteral(I18N_NOOP("Application Launchers")).toLower() << QStringLiteral(I18N_NOOP("Astronomy")).toLower() << QStringLiteral(I18N_NOOP("Date and Time")).toLower() << QStringLiteral(I18N_NOOP("Development Tools")).toLower() << QStringLiteral(I18N_NOOP("Education")).toLower() << QStringLiteral(I18N_NOOP("Environment and Weather")).toLower() << QStringLiteral(I18N_NOOP("Examples")).toLower() << QStringLiteral(I18N_NOOP("File System")).toLower() << QStringLiteral(I18N_NOOP("Fun and Games")).toLower() << QStringLiteral(I18N_NOOP("Graphics")).toLower() << QStringLiteral(I18N_NOOP("Language")).toLower() << QStringLiteral(I18N_NOOP("Mapping")).toLower() << QStringLiteral(I18N_NOOP("Miscellaneous")).toLower() << QStringLiteral(I18N_NOOP("Multimedia")).toLower() << QStringLiteral(I18N_NOOP("Online Services")).toLower() << QStringLiteral(I18N_NOOP("Productivity")).toLower() << QStringLiteral(I18N_NOOP("System Information")).toLower() << QStringLiteral(I18N_NOOP("Utilities")).toLower() << QStringLiteral(I18N_NOOP("Windows and Tasks")).toLower() << QStringLiteral(I18N_NOOP("Clipboard")).toLower() << QStringLiteral(I18N_NOOP("Tasks")).toLower(); return categories; } PluginLoader::PluginLoader() : d(new PluginLoaderPrivate) { } PluginLoader::~PluginLoader() { typedef QPointer pswp; - foreach (pswp wp, d->structures) { + for (pswp wp : qAsConst(d->structures)) { delete wp.data(); } delete d; } void PluginLoader::setPluginLoader(PluginLoader *loader) { if (!s_pluginLoader) { s_pluginLoader = loader; } else { #ifndef NDEBUG // qCDebug(LOG_PLASMA) << "Cannot set pluginLoader, already set!" << s_pluginLoader; #endif } } PluginLoader *PluginLoader::self() { if (!s_pluginLoader) { // we have been called before any PluginLoader was set, so just use the default // implementation. this prevents plugins from nefariously injecting their own // plugin loader if the app doesn't s_pluginLoader = new PluginLoader; s_pluginLoader->d->isDefaultLoader = true; } return s_pluginLoader; } Applet *PluginLoader::loadApplet(const QString &name, uint appletId, const QVariantList &args) { if (name.isEmpty()) { return nullptr; } Applet *applet = d->isDefaultLoader ? nullptr : internalLoadApplet(name, appletId, args); if (applet) { return applet; } if (appletId == 0) { appletId = ++AppletPrivate::s_maxAppletId; } // Need to pass the empty directory because it's where plasmoids used to be auto plugins = d->plasmoidCache.findPluginsById(name, { PluginLoaderPrivate::s_plasmoidsPluginDir, {} }); const KPackage::Package p = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Applet"), name); // If the applet is using another applet package, search for the plugin of the other applet if (plugins.isEmpty()) { const QString parentPlugin = p.metadata().value(QStringLiteral("X-Plasma-RootPath")); if (!parentPlugin.isEmpty()) { plugins = d->plasmoidCache.findPluginsById(parentPlugin, { PluginLoaderPrivate::s_plasmoidsPluginDir, {} }); } } if (!plugins.isEmpty()) { KPluginLoader loader(plugins.first().fileName()); if (!isPluginVersionCompatible(loader)) { return nullptr; } KPluginFactory *factory = loader.factory(); if (factory) { QVariantList allArgs; allArgs << QVariant::fromValue(p) << loader.metaData().toVariantMap() << appletId << args; applet = factory->create(nullptr, allArgs); } } if (applet) { return applet; } if (!applet) { //qCDebug(LOG_PLASMA) << name << "not a C++ applet: Falling back to an empty one"; QVariantList allArgs; allArgs << QVariant::fromValue(p) << p.metadata().fileName() << appletId << args; if (p.metadata().serviceTypes().contains(QLatin1String("Plasma/Containment"))) { applet = new Containment(nullptr, allArgs); } else { applet = new Applet(nullptr, allArgs); } } const QString localePath = p.filePath("translations"); if (!localePath.isEmpty()) { KLocalizedString::addDomainLocaleDir(QByteArray("plasma_applet_") + name.toLatin1(), localePath); } return applet; } DataEngine *PluginLoader::loadDataEngine(const QString &name) { DataEngine *engine = d->isDefaultLoader ? nullptr : internalLoadDataEngine(name); if (engine) { return engine; } // Look for C++ plugins first const QVector plugins = d->dataengineCache.findPluginsById(name, {PluginLoaderPrivate::s_dataEnginePluginDir}); if (!plugins.isEmpty()) { KPluginLoader loader(plugins.constFirst().fileName()); const QVariantList argsWithMetaData = QVariantList() << loader.metaData().toVariantMap(); KPluginFactory *factory = loader.factory(); return factory ? factory->create(nullptr, argsWithMetaData) : nullptr; } if (engine) { return engine; } const KPackage::Package p = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/DataEngine"), name); if (!p.isValid()) { return nullptr; } return new DataEngine(KPluginInfo(p.metadata().fileName()), nullptr); } QStringList PluginLoader::listAllEngines(const QString &parentApp) { QStringList engines; // Look for C++ plugins first auto filter = [&parentApp](const KPluginMetaData &md) -> bool { return md.value(QStringLiteral("X-KDE-ParentApp")) == parentApp; }; QVector plugins; if (parentApp.isEmpty()) { plugins = KPluginLoader::findPlugins(PluginLoaderPrivate::s_dataEnginePluginDir); } else { plugins = KPluginLoader::findPlugins(PluginLoaderPrivate::s_dataEnginePluginDir, filter); } - foreach (auto& plugin, plugins) { + for (auto& plugin : qAsConst(plugins)) { engines << plugin.pluginId(); } const QList packagePlugins = KPackage::PackageLoader::self()->listPackages(QStringLiteral("Plasma/DataEngine")); - for (auto& plugin : packagePlugins) { + for (const auto &plugin : packagePlugins) { engines << plugin.pluginId(); } return engines; } KPluginInfo::List PluginLoader::listEngineInfo(const QString &parentApp) { return PluginLoader::self()->listDataEngineInfo(parentApp); } KPluginInfo::List PluginLoader::listEngineInfoByCategory(const QString &category, const QString &parentApp) { KPluginInfo::List list; // Look for C++ plugins first auto filterNormal = [&category](const KPluginMetaData &md) -> bool { return md.value(QStringLiteral("X-KDE-PluginInfo-Category")) == category; }; auto filterParentApp = [&category, &parentApp](const KPluginMetaData &md) -> bool { return md.value(QStringLiteral("X-KDE-ParentApp")) == parentApp && md.value(QStringLiteral("X-KDE-PluginInfo-Category")) == category; }; QVector plugins; if (parentApp.isEmpty()) { plugins = KPluginLoader::findPlugins(PluginLoaderPrivate::s_dataEnginePluginDir, filterNormal); } else { plugins = KPluginLoader::findPlugins(PluginLoaderPrivate::s_dataEnginePluginDir, filterParentApp); } list = KPluginInfo::fromMetaData(plugins); //TODO FIXME: PackageLoader needs to have a function to inject packageStructures const QList packagePlugins = KPackage::PackageLoader::self()->listPackages(QStringLiteral("Plasma/DataEngine")); list << KPluginInfo::fromMetaData(packagePlugins.toVector()); return list; } Service *PluginLoader::loadService(const QString &name, const QVariantList &args, QObject *parent) { Service *service = d->isDefaultLoader ? nullptr : internalLoadService(name, args, parent); if (service) { return service; } //TODO: scripting API support if (name.isEmpty()) { return new NullService(QString(), parent); } else if (name == QLatin1String("org.kde.servicestorage")) { return new Storage(parent); } // Look for C++ plugins first auto filter = [&name](const KPluginMetaData &md) -> bool { return md.pluginId() == name; }; QVector plugins = KPluginLoader::findPlugins(PluginLoaderPrivate::s_servicesPluginDir, filter); if (!plugins.isEmpty()) { KPluginLoader loader(plugins.first().fileName()); if (!isPluginVersionCompatible(loader)) { return nullptr; } KPluginFactory *factory = loader.factory(); if (factory) { service = factory->create(nullptr, args); } } if (service) { if (service->name().isEmpty()) { service->setName(name); } return service; } else { return new NullService(name, parent); } } ContainmentActions *PluginLoader::loadContainmentActions(Containment *parent, const QString &name, const QVariantList &args) { if (name.isEmpty()) { return nullptr; } ContainmentActions *actions = d->isDefaultLoader ? nullptr : internalLoadContainmentActions(parent, name, args); if (actions) { return actions; } const QVector plugins = d->containmentactionCache.findPluginsById(name, {PluginLoaderPrivate::s_containmentActionsPluginDir}); if (!plugins.isEmpty()) { KPluginLoader loader(plugins.first().fileName()); KPluginFactory *factory = loader.factory(); if (factory) { actions = factory->create(nullptr, {QVariant::fromValue(plugins.first())}); } } if (actions) { return actions; } //FIXME: this is only for backwards compatibility, but probably will have to stay //for the time being QString constraint = QStringLiteral("[X-KDE-PluginInfo-Name] == '%1'").arg(name); KService::List offers = KServiceTypeTrader::self()->query(QStringLiteral("Plasma/ContainmentActions"), constraint); if (offers.isEmpty()) { #ifndef NDEBUG qCDebug(LOG_PLASMA) << "offers is empty for " << name; #endif return nullptr; } KService::Ptr offer = offers.first(); KPluginLoader plugin(*offer); if (!isPluginVersionCompatible(plugin)) { return nullptr; } QVariantList allArgs; allArgs << offer->storageId() << args; QString error; actions = offer->createInstance(parent, allArgs, &error); if (!actions) { #ifndef NDEBUG // qCDebug(LOG_PLASMA) << "Couldn't load containmentActions \"" << name << "\"! reason given: " << error; #endif } return actions; } Package PluginLoader::loadPackage(const QString &packageFormat, const QString &specialization) { if (!d->isDefaultLoader) { Package p = internalLoadPackage(packageFormat, specialization); if (p.hasValidStructure()) { return p; } } if (packageFormat.isEmpty()) { return Package(); } const QString hashkey = packageFormat + QLatin1Char('%') + specialization; PackageStructure *structure = d->structures.value(hashkey).data(); if (structure) { return Package(structure); } KPackage::PackageStructure *internalStructure = KPackage::PackageLoader::self()->loadPackageStructure(packageFormat); if (internalStructure) { structure = new PackageStructure(); structure->d->internalStructure = internalStructure; //fallback to old structures } else { const QString constraint = QStringLiteral("[X-KDE-PluginInfo-Name] == '%1'").arg(packageFormat); structure = KPluginTrader::createInstanceFromQuery(PluginLoaderPrivate::s_packageStructurePluginDir, QStringLiteral("Plasma/PackageStructure"), constraint, nullptr); if (structure) { structure->d->internalStructure = new PackageStructureWrapper(structure); } } if (structure) { d->structures.insert(hashkey, structure); return Package(structure); } #ifndef NDEBUG // qCDebug(LOG_PLASMA) << "Couldn't load Package for" << packageFormat << "! reason given: " << error; #endif return Package(); } QList PluginLoader::listAppletMetaData(const QString &category, const QString &parentApp) { //FIXME: this assumes we are always use packages.. no pure c++ std::function filter; if (category.isEmpty()) { //use all but the excluded categories KConfigGroup group(KSharedConfig::openConfig(), "General"); QStringList excluded = group.readEntry("ExcludeCategories", QStringList()); filter = [excluded, parentApp](const KPluginMetaData &md) -> bool { const QString pa = md.value(QStringLiteral("X-KDE-ParentApp")); return (parentApp.isEmpty() || pa == parentApp) && !excluded.contains(md.category()); }; } else { //specific category (this could be an excluded one - is that bad?) filter = [category, parentApp](const KPluginMetaData &md) -> bool { const QString pa = md.value(QStringLiteral("X-KDE-ParentApp")); if (category == QLatin1String("Miscellaneous")) { return (parentApp.isEmpty() || pa == parentApp) && (md.category() == category || md.category().isEmpty()); } else { return (parentApp.isEmpty() || pa == parentApp) && md.category() == category; } }; } QList list; if (!d->isDefaultLoader && (parentApp.isEmpty() || parentApp == QCoreApplication::instance()->applicationName())) { list = KPluginInfo::toMetaData(internalAppletInfo(category)).toList(); } return KPackage::PackageLoader::self()->findPackages(QStringLiteral("Plasma/Applet"), QString(), filter); } KPluginInfo::List PluginLoader::listAppletInfo(const QString &category, const QString &parentApp) { KPluginInfo::List list; const auto plugins = listAppletMetaData(category, parentApp); //NOTE: it still produces kplugininfos from KServices because some user code expects //info.service() to be valid and would crash otherwise - foreach (auto& md, plugins) { + for (const auto &md : plugins) { auto pi = md.metaDataFileName().endsWith(QLatin1String(".json")) ? KPluginInfo(md) : KPluginInfo(KService::serviceByStorageId(md.metaDataFileName())); if (!pi.isValid()) { qCWarning(LOG_PLASMA) << "Could not load plugin info for plugin :" << md.pluginId() << "skipping plugin"; continue; } list << pi; } return list; } QList PluginLoader::listAppletMetaDataForMimeType(const QString &mimeType) { auto filter = [&mimeType](const KPluginMetaData &md) -> bool { return KPluginMetaData::readStringList(md.rawData(), QStringLiteral("X-Plasma-DropMimeTypes")).contains(mimeType); }; return KPackage::PackageLoader::self()->findPackages(QStringLiteral("Plasma/Applet"), QString(), filter); } KPluginInfo::List PluginLoader::listAppletInfoForMimeType(const QString &mimeType) { return KPluginInfo::fromMetaData(listAppletMetaDataForMimeType(mimeType).toVector()); } QList PluginLoader::listAppletMetaDataForUrl(const QUrl &url) { QString parentApp; QCoreApplication *app = QCoreApplication::instance(); if (app) { parentApp = app->applicationName(); } auto filter = [&parentApp](const KPluginMetaData &md) -> bool { const QString pa = md.value(QStringLiteral("X-KDE-ParentApp")); return (parentApp.isEmpty() || pa == parentApp) && !KPluginMetaData::readStringList(md.rawData(), QStringLiteral("X-Plasma-DropUrlPatterns")).isEmpty(); }; const QList allApplets = KPackage::PackageLoader::self()->findPackages(QStringLiteral("Plasma/Applet"), QString(), filter); QList filtered; - foreach (const KPluginMetaData &md, allApplets) { - QStringList urlPatterns = KPluginMetaData::readStringList(md.rawData(), QStringLiteral("X-Plasma-DropUrlPatterns")); - foreach (const QString &glob, urlPatterns) { + for (const KPluginMetaData &md : allApplets) { + const QStringList urlPatterns = KPluginMetaData::readStringList(md.rawData(), QStringLiteral("X-Plasma-DropUrlPatterns")); + for (const QString &glob : urlPatterns) { QRegExp rx(glob); rx.setPatternSyntax(QRegExp::Wildcard); if (rx.exactMatch(url.toString())) { #ifndef NDEBUG // qCDebug(LOG_PLASMA) << md.name() << "matches" << glob << url; #endif filtered << md; } } } return filtered; } KPluginInfo::List PluginLoader::listAppletInfoForUrl(const QUrl &url) { return KPluginInfo::fromMetaData(listAppletMetaDataForUrl(url).toVector()); } QStringList PluginLoader::listAppletCategories(const QString &parentApp, bool visibleOnly) { KConfigGroup group(KSharedConfig::openConfig(), "General"); const QStringList excluded = group.readEntry("ExcludeCategories", QStringList()); auto filter = [&parentApp, &excluded, visibleOnly](const KPluginMetaData &md) -> bool { const QString pa = md.value(QStringLiteral("X-KDE-ParentApp")); return (parentApp.isEmpty() || pa == parentApp) && (excluded.isEmpty() || excluded.contains(md.value(QStringLiteral("X-KDE-PluginInfo-Category")))) && (!visibleOnly || !md.isHidden()); }; const QList allApplets = KPackage::PackageLoader::self()->findPackages(QStringLiteral("Plasma/Applet"), QString(), filter); QStringList categories; - foreach (auto& plugin, allApplets) { + for (auto &plugin : allApplets) { if (plugin.category().isEmpty()) { if (!categories.contains(i18nc("misc category", "Miscellaneous"))) { categories << i18nc("misc category", "Miscellaneous"); } } else { categories << plugin.category(); } } categories.sort(); return categories; } void PluginLoader::setCustomAppletCategories(const QStringList &categories) { PluginLoaderPrivate::s_customCategories = QSet::fromList(categories); } QStringList PluginLoader::customAppletCategories() const { return PluginLoaderPrivate::s_customCategories.values(); } QString PluginLoader::appletCategory(const QString &appletName) { if (appletName.isEmpty()) { return QString(); } const KPackage::Package p = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Applet"), appletName); if (!p.isValid()) { return QString(); } return p.metadata().category(); } KPluginInfo::List PluginLoader::listContainments(const QString &category, const QString &parentApp) { return listContainmentsOfType(QString(), category, parentApp); } KPluginInfo::List PluginLoader::listContainmentsOfType(const QString &type, const QString &category, const QString &parentApp) { KConfigGroup group(KSharedConfig::openConfig(), "General"); auto filter = [&type, &category, &parentApp](const KPluginMetaData &md) -> bool { if (!md.serviceTypes().contains(QLatin1String("Plasma/Containment"))) { return false; } if (!parentApp.isEmpty() && md.value(QStringLiteral("X-KDE-ParentApp")) != parentApp) { return false; } if (!type.isEmpty() && md.value(QStringLiteral("X-Plasma-ContainmentType")) != type) { return false; } if (!category.isEmpty() && md.value(QStringLiteral("X-KDE-PluginInfo-Category")) != category) { return false; } return true; }; return KPluginInfo::fromMetaData(KPackage::PackageLoader::self()->findPackages(QStringLiteral("Plasma/Applet"), QString(), filter).toVector()); } KPluginInfo::List PluginLoader::listContainmentsForMimeType(const QString &mimeType) { auto filter = [&mimeType](const KPluginMetaData &md) -> bool { return md.serviceTypes().contains(QLatin1String("Plasma/Containment")) && KPluginMetaData::readStringList(md.rawData(), QStringLiteral("X-Plasma-DropMimeTypes")).contains(mimeType); }; return KPluginInfo::fromMetaData(KPackage::PackageLoader::self()->findPackages(QStringLiteral("Plasma/Applet"), QString(), filter).toVector()); } QStringList PluginLoader::listContainmentTypes() { - KPluginInfo::List containmentInfos = listContainments(); + const KPluginInfo::List containmentInfos = listContainments(); QSet types; - foreach (const KPluginInfo &containmentInfo, containmentInfos) { - QStringList theseTypes = containmentInfo.service()->property(QStringLiteral("X-Plasma-ContainmentType")).toStringList(); - foreach (const QString &type, theseTypes) { + for (const KPluginInfo &containmentInfo : containmentInfos) { + const QStringList theseTypes = containmentInfo.service()->property(QStringLiteral("X-Plasma-ContainmentType")).toStringList(); + for (const QString &type : theseTypes) { types.insert(type); } } return types.values(); } KPluginInfo::List PluginLoader::listDataEngineInfo(const QString &parentApp) { KPluginInfo::List list; if (!d->isDefaultLoader && (parentApp.isEmpty() || parentApp == QCoreApplication::instance()->applicationName())) { list = internalDataEngineInfo(); } QString constraint; if (!parentApp.isEmpty()) { constraint = QLatin1String("[X-KDE-ParentApp] == '") + parentApp + QLatin1Char('\''); } list.append(KPluginTrader::self()->query(PluginLoaderPrivate::s_dataEnginePluginDir, QStringLiteral("Plasma/DataEngine"), constraint)); return list; } KPluginInfo::List PluginLoader::listContainmentActionsInfo(const QString &parentApp) { KPluginInfo::List list; if (!d->isDefaultLoader && (parentApp.isEmpty() || parentApp == QCoreApplication::instance()->applicationName())) { list = internalContainmentActionsInfo(); } QString constraint; if (!parentApp.isEmpty()) { constraint = QLatin1String("[X-KDE-ParentApp] == '") + parentApp + QLatin1Char('\''); } list.append(KPluginTrader::self()->query(PluginLoaderPrivate::s_containmentActionsPluginDir, QStringLiteral("Plasma/ContainmentActions"), constraint)); QSet knownPlugins; - foreach (const KPluginInfo &p, list) { + for (const KPluginInfo &p : qAsConst(list)) { knownPlugins.insert(p.pluginName()); } //FIXME: this is only for backwards compatibility, but probably will have to stay //for the time being - KService::List offers = KServiceTypeTrader::self()->query(QStringLiteral("Plasma/ContainmentActions"), constraint); - foreach (KService::Ptr s, offers) { + const KService::List offers = KServiceTypeTrader::self()->query(QStringLiteral("Plasma/ContainmentActions"), constraint); + for (KService::Ptr s : offers) { if (!knownPlugins.contains(s->pluginKeyword())) { list.append(KPluginInfo(s)); } } return list; } Applet *PluginLoader::internalLoadApplet(const QString &name, uint appletId, const QVariantList &args) { Q_UNUSED(name) Q_UNUSED(appletId) Q_UNUSED(args) return nullptr; } DataEngine *PluginLoader::internalLoadDataEngine(const QString &name) { Q_UNUSED(name) return nullptr; } ContainmentActions *PluginLoader::internalLoadContainmentActions(Containment *containment, const QString &name, const QVariantList &args) { Q_UNUSED(containment) Q_UNUSED(name) Q_UNUSED(args) return nullptr; } Service *PluginLoader::internalLoadService(const QString &name, const QVariantList &args, QObject *parent) { Q_UNUSED(name) Q_UNUSED(args) Q_UNUSED(parent) return nullptr; } Package PluginLoader::internalLoadPackage(const QString &name, const QString &specialization) { Q_UNUSED(name); Q_UNUSED(specialization); return Package(); } KPluginInfo::List PluginLoader::internalAppletInfo(const QString &category) const { Q_UNUSED(category) return KPluginInfo::List(); } KPluginInfo::List PluginLoader::internalDataEngineInfo() const { return KPluginInfo::List(); } KPluginInfo::List PluginLoader::internalServiceInfo() const { return KPluginInfo::List(); } KPluginInfo::List PluginLoader::internalContainmentActionsInfo() const { return KPluginInfo::List(); } static KPluginInfo::List standardInternalInfo(const QString &type, const QString &category = QString()) { QStringList files = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QLatin1String(PLASMA_RELATIVE_DATA_INSTALL_DIR "/internal/") + type + QLatin1String("/*.desktop"), QStandardPaths::LocateFile); - KPluginInfo::List allInfo = KPluginInfo::fromFiles(files); + const KPluginInfo::List allInfo = KPluginInfo::fromFiles(files); if (category.isEmpty() || allInfo.isEmpty()) { return allInfo; } KPluginInfo::List matchingInfo; - foreach (const KPluginInfo &info, allInfo) { + for (const KPluginInfo &info : allInfo) { if (info.category().compare(category, Qt::CaseInsensitive) == 0) { matchingInfo << info; } } return matchingInfo; } KPluginInfo::List PluginLoader::standardInternalAppletInfo(const QString &category) const { return standardInternalInfo(QStringLiteral("applets"), category); } KPluginInfo::List PluginLoader::standardInternalDataEngineInfo() const { return standardInternalInfo(QStringLiteral("dataengines")); } KPluginInfo::List PluginLoader::standardInternalServiceInfo() const { return standardInternalInfo(QStringLiteral("services")); } bool PluginLoader::isPluginVersionCompatible(KPluginLoader &loader) { const quint32 version = loader.pluginVersion(); if (version == quint32(-1)) { // unversioned, just let it through qCWarning(LOG_PLASMA) << loader.fileName() << "unversioned plugin detected, may result in instability"; return true; } // we require PLASMA_VERSION_MAJOR and PLASMA_VERSION_MINOR const quint32 minVersion = PLASMA_MAKE_VERSION(PLASMA_VERSION_MAJOR, 0, 0); const quint32 maxVersion = PLASMA_MAKE_VERSION(PLASMA_VERSION_MAJOR, PLASMA_VERSION_MINOR, 60); if (version < minVersion || version > maxVersion) { qCWarning(LOG_PLASMA) << loader.fileName() << ": this plugin is compiled against incompatible Plasma version" << version << "This build is compatible with" << PLASMA_VERSION_MAJOR << ".0.0 (" << minVersion << ") to" << PLASMA_VERSION_STRING << "(" << maxVersion << ")"; return false; } return true; } QVector PluginLoaderPrivate::Cache::findPluginsById(const QString& name, const QStringList &dirs) { const qint64 now = qRound64(QDateTime::currentMSecsSinceEpoch() / 1000.0); bool useRuntimeCache = true; if (pluginCacheAge == 0) { // Find all the plugins now, but only once pluginCacheAge = now; auto insertIntoCache = [this](const QString &pluginPath) { KPluginMetaData metadata(pluginPath); if (!metadata.isValid()) { qCDebug(LOG_PLASMA) << "invalid metadata" << pluginPath; return; } plugins[metadata.pluginId()].append(metadata); }; for (const QString &dir : dirs) KPluginLoader::forEachPlugin(dir, insertIntoCache); } else if (now - pluginCacheAge > maxCacheAge) { // cache is old and we're not within a few seconds of startup anymore useRuntimeCache = false; plugins.clear(); } //if name wasn't a path, pluginName == name const QString pluginName = name.section(QLatin1Char('/'), -1); QVector ret; if (useRuntimeCache) { auto it = plugins.constFind(pluginName); if (it != plugins.constEnd()) { ret = *it; } qCDebug(LOG_PLASMA) << "loading applet by name" << name << useRuntimeCache << ret.size(); } else { for (const auto& dir : dirs) { ret = KPluginLoader::findPluginsById(dir, pluginName); if (!ret.isEmpty()) break; } } return ret; } } // Plasma Namespace diff --git a/src/plasma/private/applet_p.cpp b/src/plasma/private/applet_p.cpp index c45618f37..f5d75b44f 100644 --- a/src/plasma/private/applet_p.cpp +++ b/src/plasma/private/applet_p.cpp @@ -1,606 +1,606 @@ /* * Copyright 2005 by Aaron Seigo * Copyright 2007 by Riccardo Iaconelli * Copyright 2008 by Ménard Alexis * Copyright (c) 2009 Chani Armitage * Copyright 2012 by Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "private/applet_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "containment.h" #include "corona.h" #include "pluginloader.h" #include "scripting/scriptengine.h" #include "scripting/appletscript.h" #include "private/containment_p.h" #include "private/package_p.h" #include "timetracker.h" #include "debug_p.h" namespace Plasma { AppletPrivate::AppletPrivate(const KPluginMetaData &info, int uniqueID, Applet *applet) : appletId(uniqueID), q(applet), immutability(Types::Mutable), oldImmutability(Types::Mutable), appletDescription(info), icon(appletDescription.iconName()), mainConfig(nullptr), pendingConstraints(Types::NoConstraint), script(nullptr), package(nullptr), configLoader(nullptr), actions(AppletPrivate::defaultActions(applet)), activationAction(nullptr), itemStatus(Types::UnknownStatus), modificationsTimer(nullptr), deleteNotificationTimer(nullptr), hasConfigurationInterface(false), failed(false), transient(false), needsConfig(false), started(false), globalShortcutEnabled(false), userConfiguring(false), busy(false) { if (appletId == 0) { appletId = ++s_maxAppletId; } else if (appletId > s_maxAppletId) { s_maxAppletId = appletId; } QObject::connect(actions->action(QStringLiteral("configure")), SIGNAL(triggered()), q, SLOT(requestConfiguration())); #ifndef NDEBUG if (qEnvironmentVariableIsSet("PLASMA_TRACK_STARTUP")) { new TimeTracker(q); } #endif } AppletPrivate::~AppletPrivate() { if (activationAction && globalShortcutEnabled) { //qCDebug(LOG_PLASMA) << "resetting global action for" << q->title() << activationAction->objectName(); KGlobalAccel::self()->removeAllShortcuts(activationAction); } if (deleteNotification) { deleteNotification->close(); } delete script; script = nullptr; delete configLoader; configLoader = nullptr; delete mainConfig; mainConfig = nullptr; delete modificationsTimer; } void AppletPrivate::init(const QString &_packagePath, const QVariantList &args) { // WARNING: do not access config() OR globalConfig() in this method! // that requires a Corona, which is not available at this point q->setHasConfigurationInterface(true); QAction *closeApplet = actions->action(QStringLiteral("remove")); if (closeApplet) { closeApplet->setText(i18nc("%1 is the name of the applet", "Remove %1", q->title())); } QAction *configAction = actions->action(QStringLiteral("configure")); if (configAction) { configAction->setText(i18nc("%1 is the name of the applet", "Configure %1...", q->title().replace(QLatin1Char('&'), QStringLiteral("&&")))); } if (!appletDescription.isValid()) { #ifndef NDEBUG // qCDebug(LOG_PLASMA) << "Check your constructor! " // << "You probably want to be passing in a Service::Ptr " // << "or a QVariantList with a valid storageid as arg[0]."; #endif return; } QString api = appletDescription.rawData().value(QStringLiteral("X-Plasma-API")).toString(); if (api.isEmpty()) { q->setLaunchErrorMessage(i18n("The %1 widget did not define which ScriptEngine to use.", appletDescription.name())); return; } //A constructor may have set a valid package already if (!package.isValid()) { const QString packagePath = _packagePath.isEmpty() && !appletDescription.metaDataFileName().isEmpty() ? QFileInfo(appletDescription.metaDataFileName()).dir().path() : _packagePath; QString path = appletDescription.rawData().value(QStringLiteral("X-Plasma-RootPath")).toString(); if (path.isEmpty()) { path = packagePath.isEmpty() ? appletDescription.pluginId() : packagePath; } package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Applet")); package.setPath(path); if (!package.isValid()) { q->setLaunchErrorMessage(i18nc("Package file, name of the widget", "Could not open the %1 package required for the %2 widget.", appletDescription.pluginId(), appletDescription.name())); return; } } // now we try and set up the script engine. // it will be parented to this applet and so will get // deleted when the applet does script = Plasma::loadScriptEngine(api, q, args); //It's valid, let's try to load the icon from within the package if (script) { //use the absolute path of the in-package icon as icon name if (appletDescription.iconName().startsWith(QLatin1Char('/'))) { icon = package.filePath({}, appletDescription.iconName()); } //package not valid, get rid of it } else { q->setLaunchErrorMessage( i18nc("API or programming language the widget was written in, name of the widget", "Could not create a %1 ScriptEngine for the %2 widget.", api, appletDescription.name())); } if (!q->isContainment()) { QAction *a = new QAction(QIcon::fromTheme(QStringLiteral("widget-alternatives")), i18n("Show Alternatives..."), q); a->setVisible(false); q->actions()->addAction(QStringLiteral("alternatives"), a); QObject::connect(a, &QAction::triggered, q, [this] { if (q->containment()) { emit q->containment()->appletAlternativesRequested(q); } }); QObject::connect(q, &Applet::contextualActionsAboutToShow, a, [=]() { bool hasAlternatives = false; const QStringList provides = KPluginMetaData::readStringList(q->pluginMetaData().rawData(), QStringLiteral("X-Plasma-Provides")); if (!provides.isEmpty() && q->immutability() == Types::Mutable) { auto filter = [&provides](const KPluginMetaData &md) -> bool { const QStringList provided = KPluginMetaData::readStringList(md.rawData(), QStringLiteral("X-Plasma-Provides")); - foreach (const QString &p, provides) { + for (const QString &p : provides) { if (provided.contains(p)) { return true; } } return false; }; QList applets = KPackage::PackageLoader::self()->findPackages(QStringLiteral("Plasma/Applet"), QString(), filter); if (applets.count() > 1) { hasAlternatives = true; } } a->setVisible(hasAlternatives); }); } } void AppletPrivate::cleanUpAndDelete() { // reimplemented in the UI specific library if (configLoader) { configLoader->clearItems(); } resetConfigurationObject(); if (q->isContainment()) { // prematurely emit our destruction if we are a Containment, // giving Corona a chance to remove this Containment from its collection emit q->QObject::destroyed(q); } q->deleteLater(); } void AppletPrivate::setDestroyed(bool destroyed) { transient = destroyed; emit q->destroyedChanged(destroyed); //when an applet gets transient, it's "systemimmutable" emit q->immutabilityChanged(q->immutability()); Plasma::Containment *asContainment = qobject_cast(q); if (asContainment) { - foreach(Applet *a , asContainment->applets()) + const auto lstApplets = asContainment->applets(); + for (Applet *a : lstApplets) a->d->setDestroyed(destroyed); } } void AppletPrivate::askDestroy() { if (q->immutability() != Types::Mutable || !started) { return; //don't double delete } if (transient) { cleanUpAndDelete(); } else { //There is no confirmation anymore for panels removal: //this needs users feedback setDestroyed(true); //no parent, but it won't leak, since it will be closed both in case of timeout //or direct action deleteNotification = new KNotification(QStringLiteral("plasmoidDeleted")); deleteNotification->setFlags(KNotification::Persistent | KNotification::SkipGrouping); deleteNotification->setComponentName(QStringLiteral("plasma_workspace")); QStringList actions; deleteNotification->setIconName(q->icon()); Plasma::Containment *asContainment = qobject_cast(q); if (!q->isContainment()) { deleteNotification->setTitle(i18n("Widget Removed")); deleteNotification->setText(i18n("The widget \"%1\" has been removed.", q->title().toHtmlEscaped())); } else if (asContainment && (asContainment->containmentType() == Types::PanelContainment || asContainment->containmentType() == Types::CustomPanelContainment)) { deleteNotification->setTitle(i18n("Panel Removed")); deleteNotification->setText(i18n("A panel has been removed.")); //This will never happen with our current shell, but could with a custom one } else { deleteNotification->setTitle(i18n("Desktop Removed")); deleteNotification->setText(i18n("A desktop has been removed.")); } actions.append(i18n("Undo")); deleteNotification->setActions(actions); QObject::connect(deleteNotification.data(), &KNotification::action1Activated, q, [=]() { setDestroyed(false); if (!q->isContainment() && q->containment()) { Plasma::Applet *containmentApplet = static_cast(q->containment()); if (containmentApplet && containmentApplet->d->deleteNotificationTimer) { emit containmentApplet->destroyedChanged(false); //when an applet gets transient, it's "systemimmutable" emit q->immutabilityChanged(q->immutability()); delete containmentApplet->d->deleteNotificationTimer; containmentApplet->d->deleteNotificationTimer = nullptr; } //make sure the applets are sorted by id auto position = std::lower_bound(q->containment()->d->applets.begin(), q->containment()->d->applets.end(), q, [](Plasma::Applet *a1, Plasma::Applet *a2) { return a1->id() < a2->id(); }); q->containment()->d->applets.insert(position, q); emit q->containment()->appletAdded(q); } if (deleteNotification) { deleteNotification->close(); } else if (deleteNotificationTimer) { deleteNotificationTimer->stop(); deleteNotificationTimer->deleteLater(); deleteNotificationTimer = nullptr; } }); QObject::connect(deleteNotification.data(), &KNotification::closed, q, [=]() { //If the timer still exists, it means the undo action was NOT triggered if (transient) { cleanUpAndDelete(); } if (deleteNotificationTimer) { deleteNotificationTimer->stop(); deleteNotificationTimer->deleteLater(); deleteNotificationTimer = nullptr; } }); deleteNotification->sendEvent(); if (!deleteNotificationTimer) { deleteNotificationTimer = new QTimer(q); //really delete after a minute deleteNotificationTimer->setInterval(60 * 1000); deleteNotificationTimer->setSingleShot(true); QObject::connect(deleteNotificationTimer, &QTimer::timeout, q, [=]() { transient = true; if (deleteNotification) { deleteNotification->close(); } else { emit q->destroyedChanged(true); cleanUpAndDelete(); } }); deleteNotificationTimer->start(); } if (!q->isContainment() && q->containment()) { q->containment()->d->applets.removeAll(q); emit q->containment()->appletRemoved(q); } } } void AppletPrivate::globalShortcutChanged() { if (!activationAction) { return; } KConfigGroup shortcutConfig(mainConfigGroup(), "Shortcuts"); QString newShortCut = activationAction->shortcut().toString(); QString oldShortCut = shortcutConfig.readEntry("global", QString()); if (newShortCut != oldShortCut) { shortcutConfig.writeEntry("global", newShortCut); scheduleModificationNotification(); } //qCDebug(LOG_PLASMA) << "after" << shortcut.primary() << d->activationAction->globalShortcut().primary(); } KActionCollection *AppletPrivate::defaultActions(QObject *parent) { KActionCollection *actions = new KActionCollection(parent); actions->setConfigGroup(QStringLiteral("Shortcuts-Applet")); QAction *configAction = actions->add(QStringLiteral("configure")); configAction->setAutoRepeat(false); configAction->setText(i18n("Widget Settings")); configAction->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); configAction->setShortcut(QKeySequence(QStringLiteral("alt+d, s"))); configAction->setData(Plasma::Types::ConfigureAction); QAction *closeApplet = actions->add(QStringLiteral("remove")); closeApplet->setAutoRepeat(false); closeApplet->setText(i18n("Remove this Widget")); closeApplet->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); closeApplet->setShortcut(QKeySequence(QStringLiteral("alt+d, r"))); closeApplet->setData(Plasma::Types::DestructiveAction); QAction *runAssociatedApplication = actions->add(QStringLiteral("run associated application")); runAssociatedApplication->setAutoRepeat(false); runAssociatedApplication->setText(i18n("Run the Associated Application")); runAssociatedApplication->setIcon(QIcon::fromTheme(QStringLiteral("system-run"))); runAssociatedApplication->setShortcut(QKeySequence(QStringLiteral("alt+d, t"))); runAssociatedApplication->setVisible(false); runAssociatedApplication->setEnabled(false); runAssociatedApplication->setData(Plasma::Types::ControlAction); return actions; } void AppletPrivate::requestConfiguration() { if (q->containment()) { emit q->containment()->configureRequested(q); } } void AppletPrivate::updateShortcuts() { if (q->isContainment()) { //a horrible hack to avoid clobbering corona settings //we pull them out, then read, then put them back - QList names; QList qactions; - names << QStringLiteral("add sibling containment") << QStringLiteral("configure shortcuts") << QStringLiteral("lock widgets"); - foreach (const QString &name, names) { + const QList names = { QStringLiteral("add sibling containment"), QStringLiteral("configure shortcuts"), QStringLiteral("lock widgets")}; + for (const QString &name : names) { QAction *a = actions->action(name); actions->takeAction(a); //FIXME this is stupid, KActionCollection needs a takeAction(QString) method qactions << a; } actions->readSettings(); for (int i = 0; i < names.size(); ++i) { QAction *a = qactions.at(i); if (a) { actions->addAction(names.at(i), a); } } } else { actions->readSettings(); } } void AppletPrivate::propagateConfigChanged() { Containment *c = qobject_cast(q); if (c) { c->d->configChanged(); } q->configChanged(); } void AppletPrivate::setUiReady() { //am i the containment? Containment *c = qobject_cast(q); if (c && c->isContainment()) { c->d->setUiReady(); } else if (Containment* cc = q->containment()) { cc->d->appletLoaded(q); } } // put all setup routines for script here. at this point we can assume that // package exists and that we have a script engine void AppletPrivate::setupPackage() { if (!package.isValid()) { return; } #ifndef NDEBUG // qCDebug(LOG_PLASMA) << "setting up script support, package is in" << package->path() // << ", main script is" << package->filePath("mainscript"); #endif // FIXME: Replace with ki18n functionality once semantics is clear. // const QString translationsPath = package->filePath("translations"); // if (!translationsPath.isEmpty()) { // KGlobal::dirs()->addResourceDir("locale", translationsPath); // } if (!package.filePath("mainconfigui").isEmpty()) { q->setHasConfigurationInterface(true); } } void AppletPrivate::setupScripting() { if (script) { if (!script->init() && !failed) { q->setLaunchErrorMessage(i18n("Script initialization failed")); } } } QString AppletPrivate::globalName() const { if (!appletDescription.isValid()) { return QString(); } return appletDescription.pluginId(); } void AppletPrivate::scheduleConstraintsUpdate(Plasma::Types::Constraints c) { // Don't start up a timer if we're just starting up // flushPendingConstraints will be called by Corona if (started && !constraintsTimer.isActive() && !(c & Plasma::Types::StartupCompletedConstraint)) { constraintsTimer.start(0, q); } if (c & Plasma::Types::StartupCompletedConstraint) { started = true; if (q->isContainment()) { qobject_cast(q)->d->setStarted(); } } pendingConstraints |= c; } void AppletPrivate::scheduleModificationNotification() { // modificationsTimer is not allocated until we get our notice of being started if (modificationsTimer) { // schedule a save modificationsTimer->start(1000, q); } } KConfigGroup *AppletPrivate::mainConfigGroup() { if (mainConfig) { return mainConfig; } Containment *c = q->containment(); Plasma::Applet *parentApplet = nullptr; if (c) { parentApplet = qobject_cast(c->parent()); } if (q->isContainment()) { Corona *corona = static_cast(q)->corona(); KConfigGroup containmentConfig; //qCDebug(LOG_PLASMA) << "got a corona, baby?" << (QObject*)corona << (QObject*)q; if (parentApplet) { containmentConfig = parentApplet->config(); containmentConfig = KConfigGroup(&containmentConfig, "Containments"); } else if (corona) { containmentConfig = KConfigGroup(corona->config(), "Containments"); } else { containmentConfig = KConfigGroup(KSharedConfig::openConfig(), "Containments"); } mainConfig = new KConfigGroup(&containmentConfig, QString::number(appletId)); } else { KConfigGroup appletConfig; if (c) { // applet directly in a Containment, as usual appletConfig = c->config(); appletConfig = KConfigGroup(&appletConfig, "Applets"); } else { qCWarning(LOG_PLASMA) << "requesting config for" << q->title() << "without a containment!"; appletConfig = KConfigGroup(KSharedConfig::openConfig(), "Applets"); } mainConfig = new KConfigGroup(&appletConfig, QString::number(appletId)); } if (configLoader) { configLoader->setSharedConfig(KSharedConfig::openConfig(mainConfig->config()->name())); configLoader->load(); } return mainConfig; } void AppletPrivate::resetConfigurationObject() { // make sure mainConfigGroup exists in all cases mainConfigGroup(); mainConfig->deleteEntry("plugin"); mainConfig->deleteEntry("formfactor"); mainConfig->deleteEntry("immutability"); mainConfig->deleteEntry("location"); //if it's not a containment, deleting the non existing activityId entry does nothing mainConfig->deleteEntry("activityId"); mainConfig->deleteGroup(); delete mainConfig; mainConfig = nullptr; Containment *cont = qobject_cast(q); if (cont && cont->corona()) { cont->corona()->requireConfigSync(); } else { if (!q->containment()) { return; } Corona *corona = q->containment()->corona(); if (corona) { corona->requireConfigSync(); } } } uint AppletPrivate::s_maxAppletId = 0; } //namespace Plasma diff --git a/src/plasma/private/containment_p.cpp b/src/plasma/private/containment_p.cpp index 89ee15617..2d284d10e 100644 --- a/src/plasma/private/containment_p.cpp +++ b/src/plasma/private/containment_p.cpp @@ -1,264 +1,264 @@ /* * Copyright 2007 by Aaron Seigo * Copyright 2008 by Ménard Alexis * Copyright 2009 Chani Armitage * Copyright 2012 Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "private/containment_p.h" #include #include #include #include #include "config-plasma.h" #include "containmentactions.h" #include "corona.h" #include "pluginloader.h" #include "private/applet_p.h" #include "timetracker.h" #include "debug_p.h" namespace Plasma { const char ContainmentPrivate::defaultWallpaper[] = "org.kde.image"; ContainmentPrivate::ContainmentPrivate(Containment *c): q(c), formFactor(Types::Planar), location(Types::Floating), lastScreen(-1), // never had a screen type(Plasma::Types::NoContainmentType), uiReady(false), appletsUiReady(false) { //if the parent is an applet (i.e we are the systray) //we want to follow screen changed signals from the parent's containment auto appletParent = qobject_cast(c->parent()); if (appletParent) { QObject::connect(appletParent->containment(), &Containment::screenChanged, c, &Containment::screenChanged); } } Plasma::ContainmentPrivate::~ContainmentPrivate() { applets.clear(); } void ContainmentPrivate::addDefaultActions(KActionCollection *actions, Containment *c) { actions->setConfigGroup(QStringLiteral("Shortcuts-Containment")); //adjust applet actions QAction *appAction = qobject_cast(actions->action(QStringLiteral("remove"))); appAction->setShortcut(QKeySequence(Qt::ALT+Qt::Key_D, Qt::ALT+Qt::Key_R)); if (c && c->d->isPanelContainment()) { appAction->setText(i18n("Remove this Panel")); } else { appAction->setText(i18n("Remove this Activity")); } appAction = qobject_cast(actions->action(QStringLiteral("configure"))); if (appAction) { appAction->setShortcut(QKeySequence(Qt::ALT+Qt::Key_D, Qt::ALT+Qt::Key_S)); appAction->setText(i18n("Activity Settings")); } //add our own actions QAction *appletBrowserAction = actions->add(QStringLiteral("add widgets")); appletBrowserAction->setAutoRepeat(false); appletBrowserAction->setText(i18n("Add Widgets...")); appletBrowserAction->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); appletBrowserAction->setShortcut(QKeySequence(Qt::ALT+Qt::Key_D, Qt::Key_A)); appletBrowserAction->setData(Plasma::Types::AddAction); } KConfigGroup ContainmentPrivate::containmentActionsConfig() const { KConfigGroup cfg = KConfigGroup(q->corona()->config(), "ActionPlugins"); return KConfigGroup(&cfg, QString::number(type)); } void ContainmentPrivate::configChanged() { KConfigGroup group = q->config(); q->setWallpaper(group.readEntry("wallpaperplugin", defaultWallpaper)); } void ContainmentPrivate::checkStatus(Plasma::Types::ItemStatus appletStatus) { //qCDebug(LOG_PLASMA) << "================== "<< appletStatus << q->status(); if (appletStatus == q->status()) { return; } if (appletStatus < q->status() || appletStatus == Plasma::Types::HiddenStatus) { // check to see if any other applet has a higher status, and stick with that if we do // we'll treat HiddenStatus as lowest as we cannot change the enum value which is highest anymore - foreach (Applet *applet, applets) { + for (Applet *applet : qAsConst(applets)) { if (applet->status() > appletStatus && applet->status() != Plasma::Types::HiddenStatus) { appletStatus = applet->status(); } } } if (appletStatus != Plasma::Types::HiddenStatus) { q->setStatus(appletStatus); } } void ContainmentPrivate::triggerShowAddWidgets() { emit q->showAddWidgetsInterface(QPointF()); } void ContainmentPrivate::containmentConstraintsEvent(Plasma::Types::Constraints constraints) { if (!q->isContainment()) { return; } //qCDebug(LOG_PLASMA) << "got containmentConstraintsEvent" << constraints; if (constraints & Plasma::Types::ImmutableConstraint) { //update actions const bool unlocked = q->immutability() == Types::Mutable; QAction *action = q->actions()->action(QStringLiteral("remove")); if (action) { action->setEnabled(unlocked); action->setVisible(unlocked); } action = q->actions()->action(QStringLiteral("add widgets")); if (action) { action->setEnabled(unlocked); action->setVisible(unlocked); } // tell the applets too - foreach (Applet *a, applets) { + for (Applet *a : qAsConst(applets)) { /*Why qMin? * the applets immutability() is the maximum between internal applet immutability * and the immutability of its containment. * so not set higher immutability in the internal member of Applet * or the applet will not be able to be unlocked properly */ a->setImmutability(qMin(q->immutability(), a->d->immutability)); a->updateConstraints(Types::ImmutableConstraint); } } // pass on the constraints that are relevant here Types::Constraints appletConstraints = Types::NoConstraint; if (constraints & Types::FormFactorConstraint) { appletConstraints |= Types::FormFactorConstraint; } if (constraints & Types::ScreenConstraint) { appletConstraints |= Types::ScreenConstraint; } if (appletConstraints != Types::NoConstraint) { - foreach (Applet *applet, applets) { + for (Applet *applet : qAsConst(applets)) { applet->updateConstraints(appletConstraints); } } } Applet *ContainmentPrivate::createApplet(const QString &name, const QVariantList &args, uint id) { if (!q->isContainment()) { return nullptr; } if (q->immutability() != Types::Mutable && !args.contains(QVariant::fromValue(QStringLiteral("org.kde.plasma:force-create")))) { #ifndef NDEBUG // qCDebug(LOG_PLASMA) << "addApplet for" << name << "requested, but we're currently immutable!"; #endif return nullptr; } Applet *applet = PluginLoader::self()->loadApplet(name, id, args); if (!applet) { qCWarning(LOG_PLASMA) << "Applet" << name << "could not be loaded."; applet = new Applet(nullptr, QString(), id); applet->setLaunchErrorMessage(i18n("Could not find requested component: %1", name)); } q->addApplet(applet); //mirror behavior of resorecontents: if an applet is not valid, set it immediately to uiReady if (!applet->pluginMetaData().isValid()) { applet->updateConstraints(Plasma::Types::UiReadyConstraint); } return applet; } void ContainmentPrivate::appletDeleted(Plasma::Applet *applet) { applets.removeAll(applet); emit q->appletRemoved(applet); emit q->configNeedsSaving(); } bool ContainmentPrivate::isPanelContainment() const { return type == Plasma::Types::PanelContainment || type == Plasma::Types::CustomPanelContainment; } void ContainmentPrivate::setStarted() { if (!q->Applet::d->started) { q->Applet::d->started = true; if (uiReady) { emit q->uiReadyChanged(true); } } } void ContainmentPrivate::setUiReady() { //if we are the containment and there is still some uncomplete applet, we're still incomplete if (!uiReady) { uiReady = true; if (q->Applet::d->started && (appletsUiReady || applets.isEmpty()) && loadingApplets.isEmpty()) { emit q->uiReadyChanged(true); } } } void ContainmentPrivate::appletLoaded(Applet* applet) { loadingApplets.remove(applet); if (loadingApplets.isEmpty() && !appletsUiReady) { appletsUiReady = true; if (q->Applet::d->started && uiReady) { emit q->uiReadyChanged(true); } } } } diff --git a/src/plasma/private/dataenginemanager.cpp b/src/plasma/private/dataenginemanager.cpp index 852c17a1b..ff809453b 100644 --- a/src/plasma/private/dataenginemanager.cpp +++ b/src/plasma/private/dataenginemanager.cpp @@ -1,209 +1,210 @@ /* * Copyright 2006-2007 Aaron Seigo * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "dataenginemanager_p.h" #include #include #include #include #include "datacontainer.h" #include "pluginloader.h" #include "private/dataengine_p.h" #include "private/datacontainer_p.h" #include "scripting/scriptengine.h" #include "debug_p.h" namespace Plasma { class NullEngine : public DataEngine { public: explicit NullEngine(QObject *parent = nullptr) : DataEngine(KPluginMetaData(), parent) { setValid(false); // ref() ourselves to ensure we never get deleted d->ref(); } }; class DataEngineManagerPrivate { public: DataEngineManagerPrivate() : nullEng(nullptr) {} ~DataEngineManagerPrivate() { - foreach (Plasma::DataEngine *engine, engines) { + for (Plasma::DataEngine *engine : qAsConst(engines)) { delete engine; } engines.clear(); delete nullEng; } DataEngine *nullEngine() { if (!nullEng) { nullEng = new NullEngine; } return nullEng; } DataEngine::Dict engines; DataEngine *nullEng; }; class DataEngineManagerSingleton { public: DataEngineManager self; }; Q_GLOBAL_STATIC(DataEngineManagerSingleton, privateDataEngineManagerSelf) DataEngineManager *DataEngineManager::self() { return &privateDataEngineManagerSelf()->self; } DataEngineManager::DataEngineManager() : d(new DataEngineManagerPrivate) { //startTimer(30000); } DataEngineManager::~DataEngineManager() { delete d; } Plasma::DataEngine *DataEngineManager::engine(const QString &name) const { if (name.isEmpty()) { return d->nullEngine(); } Plasma::DataEngine::Dict::const_iterator it = d->engines.constFind(name); if (it != d->engines.constEnd()) { return *it; } return d->nullEngine(); } Plasma::DataEngine *DataEngineManager::loadEngine(const QString &name) { if (name.isEmpty()) { qCDebug(LOG_PLASMA) << "Asked an engine with empty name"; return d->nullEngine(); } Plasma::DataEngine::Dict::const_iterator it = d->engines.constFind(name); if (it != d->engines.constEnd()) { DataEngine *engine = *it; engine->d->ref(); return engine; } DataEngine *engine = PluginLoader::self()->loadDataEngine(name); if (!engine) { qCDebug(LOG_PLASMA) << "Can't find a dataengine named" << name; return d->nullEngine(); } d->engines[name] = engine; return engine; } void DataEngineManager::unloadEngine(const QString &name) { Plasma::DataEngine::Dict::iterator it = d->engines.find(name); if (it != d->engines.end()) { Plasma::DataEngine *engine = *it; engine->d->deref(); if (!engine->d->isUsed()) { d->engines.erase(it); delete engine; } } } void DataEngineManager::timerEvent(QTimerEvent *) { #ifndef NDEBUG QString path = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QStringLiteral("/plasma_dataenginemanager_log"); QFile f(path); if (!f.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) { // qCDebug(LOG_PLASMA) << "failed to open" << path; return; } QTextStream out(&f); QHashIterator it(d->engines); out << "================================== " << QLocale().toString(QDateTime::currentDateTime()) << '\n'; while (it.hasNext()) { it.next(); DataEngine *engine = it.value(); out << "DataEngine: " << it.key() << ' ' << engine << '\n'; out << " Claimed # of sources: " << engine->sources().count() << '\n'; out << " Actual # of sources: " << engine->containerDict().count() << '\n'; out << "\n Source Details" << '\n'; - foreach (DataContainer *dc, engine->containerDict()) { + const auto lst = engine->containerDict(); + for (DataContainer *dc : lst) { out << " * " << dc->objectName() << '\n'; out << " Data count: " << dc->d->data.count() << '\n'; out << " Stored: " << dc->isStorageEnabled() << " \n"; const int directs = dc->receivers(SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data))); if (directs > 0) { out << " Direction Connections: " << directs << " \n"; } const int relays = dc->d->relays.count(); if (relays > 0) { out << " Relays: " << dc->d->relays.count() << '\n'; QString times; - foreach (SignalRelay *relay, dc->d->relays) { + for (SignalRelay *relay : qAsConst(dc->d->relays)) { times.append(QLatin1Char(' ') + QString::number(relay->m_interval)); } out << " Relay Timeouts: " << times << " \n"; } } out << "\n-----\n"; } out << "\n\n"; #endif // killTimer(event->timerId()); } } // namespace Plasma #include "moc_dataenginemanager_p.cpp" diff --git a/src/plasma/private/theme_p.cpp b/src/plasma/private/theme_p.cpp index 5cd13c65b..84c75244f 100644 --- a/src/plasma/private/theme_p.cpp +++ b/src/plasma/private/theme_p.cpp @@ -1,893 +1,893 @@ /* * Copyright 2006-2007 Aaron Seigo * Copyright 2013 Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "theme_p.h" #include "framesvg.h" #include "framesvg_p.h" #include "debug_p.h" #include #include #include #include #include #include #include #include #include namespace Plasma { const char ThemePrivate::defaultTheme[] = "default"; const char ThemePrivate::themeRcFile[] = "plasmarc"; // the system colors theme is used to cache unthemed svgs with colorization needs // these svgs do not follow the theme's colors, but rather the system colors const char ThemePrivate::systemColorsTheme[] = "internal-system-colors"; #if HAVE_X11 EffectWatcher *ThemePrivate::s_backgroundContrastEffectWatcher = nullptr; #endif ThemePrivate *ThemePrivate::globalTheme = nullptr; QHash ThemePrivate::themes = QHash(); ThemePrivate::ThemePrivate(QObject *parent) : QObject(parent), colorScheme(QPalette::Active, KColorScheme::Window, KSharedConfigPtr(nullptr)), selectionColorScheme(QPalette::Active, KColorScheme::Selection, KSharedConfigPtr(nullptr)), buttonColorScheme(QPalette::Active, KColorScheme::Button, KSharedConfigPtr(nullptr)), viewColorScheme(QPalette::Active, KColorScheme::View, KSharedConfigPtr(nullptr)), complementaryColorScheme(QPalette::Active, KColorScheme::Complementary, KSharedConfigPtr(nullptr)), defaultWallpaperTheme(QStringLiteral(DEFAULT_WALLPAPER_THEME)), defaultWallpaperSuffix(QStringLiteral(DEFAULT_WALLPAPER_SUFFIX)), defaultWallpaperWidth(DEFAULT_WALLPAPER_WIDTH), defaultWallpaperHeight(DEFAULT_WALLPAPER_HEIGHT), pixmapCache(nullptr), cacheSize(0), cachesToDiscard(NoCache), compositingActive(KWindowSystem::self()->compositingActive()), backgroundContrastActive(KWindowEffects::isEffectAvailable(KWindowEffects::BackgroundContrast)), isDefault(true), useGlobal(true), hasWallpapers(false), fixedName(false), backgroundContrast(qQNaN()), backgroundIntensity(qQNaN()), backgroundSaturation(qQNaN()), backgroundContrastEnabled(true), blurBehindEnabled(true), apiMajor(1), apiMinor(0), apiRevision(0) { ThemeConfig config; cacheTheme = config.cacheTheme(); pixmapSaveTimer = new QTimer(this); pixmapSaveTimer->setSingleShot(true); pixmapSaveTimer->setInterval(600); QObject::connect(pixmapSaveTimer, &QTimer::timeout, this, &ThemePrivate::scheduledCacheUpdate); rectSaveTimer = new QTimer(this); rectSaveTimer->setSingleShot(true); //2 minutes rectSaveTimer->setInterval(2 * 60 * 1000); QObject::connect(rectSaveTimer, &QTimer::timeout, this, &ThemePrivate::saveSvgElementsCache); updateNotificationTimer = new QTimer(this); updateNotificationTimer->setSingleShot(true); updateNotificationTimer->setInterval(100); QObject::connect(updateNotificationTimer, &QTimer::timeout, this, &ThemePrivate::notifyOfChanged); if (QPixmap::defaultDepth() > 8) { #if HAVE_X11 //watch for background contrast effect property changes as well if (!s_backgroundContrastEffectWatcher) { s_backgroundContrastEffectWatcher = new EffectWatcher(QStringLiteral("_KDE_NET_WM_BACKGROUND_CONTRAST_REGION")); } QObject::connect(s_backgroundContrastEffectWatcher, &EffectWatcher::effectChanged, this, [this](bool active) { if (backgroundContrastActive != active) { backgroundContrastActive = active; scheduleThemeChangeNotification(PixmapCache | SvgElementsCache); } }); #endif } QCoreApplication::instance()->installEventFilter(this); const QString configFile = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + QLatin1String(themeRcFile); KDirWatch::self()->addFile(configFile); // Catch both, direct changes to the config file ... connect(KDirWatch::self(), &KDirWatch::dirty, this, &ThemePrivate::settingsFileChanged); // ... but also remove/recreate cycles, like KConfig does it connect(KDirWatch::self(), &KDirWatch::created, this, &ThemePrivate::settingsFileChanged); QObject::connect(KIconLoader::global(), &KIconLoader::iconChanged, this, [this]() { scheduleThemeChangeNotification(PixmapCache|SvgElementsCache); }); connect(KWindowSystem::self(), &KWindowSystem::compositingChanged, this, &ThemePrivate::compositingChanged); } ThemePrivate::~ThemePrivate() { saveSvgElementsCache(); FrameSvgPrivate::s_sharedFrames.remove(this); delete pixmapCache; } KConfigGroup &ThemePrivate::config() { if (!cfg.isValid()) { QString groupName = QStringLiteral("Theme"); if (!useGlobal) { QString app = QCoreApplication::applicationName(); if (!app.isEmpty()) { #ifndef NDEBUG // qCDebug(LOG_PLASMA) << "using theme for app" << app; #endif groupName.append(QLatin1Char('-')).append(app); } } cfg = KConfigGroup(KSharedConfig::openConfig(QFile::decodeName(themeRcFile)), groupName); } return cfg; } bool ThemePrivate::useCache() { bool cachesTooOld = false; if (cacheTheme && !pixmapCache) { if (cacheSize == 0) { ThemeConfig config; cacheSize = config.themeCacheKb(); } const bool isRegularTheme = themeName != QLatin1String(systemColorsTheme); QString cacheFile = QLatin1String("plasma_theme_") + themeName; // clear any cached values from the previous theme cache themeVersion.clear(); if (!themeMetadataPath.isEmpty()) { KDirWatch::self()->removeFile(themeMetadataPath); } if (isRegularTheme) { themeMetadataPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral(PLASMA_RELATIVE_DATA_INSTALL_DIR "/desktoptheme/") % themeName % QStringLiteral("/metadata.desktop")); const auto *iconTheme = KIconLoader::global()->theme(); if (iconTheme) { iconThemeMetadataPath = iconTheme->dir() + QStringLiteral("index.theme"); } Q_ASSERT(!themeMetadataPath.isEmpty() || themeName.isEmpty()); const QString cacheFileBase = cacheFile + QLatin1String("*.kcache"); QString currentCacheFileName; if (!themeMetadataPath.isEmpty()) { // now we record the theme version, if we can const KPluginInfo pluginInfo(themeMetadataPath); if (pluginInfo.isValid()) { themeVersion = pluginInfo.version(); } if (!themeVersion.isEmpty()) { cacheFile += QLatin1String("_v") + themeVersion; currentCacheFileName = cacheFile + QLatin1String(".kcache"); } // watch the metadata file for changes at runtime KDirWatch::self()->addFile(themeMetadataPath); QObject::connect(KDirWatch::self(), &KDirWatch::created, this, &ThemePrivate::settingsFileChanged, Qt::UniqueConnection); QObject::connect(KDirWatch::self(), &KDirWatch::dirty, this, &ThemePrivate::settingsFileChanged, Qt::UniqueConnection); if (!iconThemeMetadataPath.isEmpty()) { KDirWatch::self()->addFile(iconThemeMetadataPath); } } // now we check for, and remove if necessary, old caches QDir cacheDir(QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation)); cacheDir.setNameFilters(QStringList({cacheFileBase})); const auto files = cacheDir.entryInfoList(); for (const QFileInfo &file : files) { if (currentCacheFileName.isEmpty() || !file.absoluteFilePath().endsWith(currentCacheFileName)) { QFile::remove(file.absoluteFilePath()); } } } // now we do a sanity check: if the metadata.desktop file is newer than the cache, drop the cache if (isRegularTheme && !themeMetadataPath.isEmpty()) { // now we check to see if the theme metadata file itself is newer than the pixmap cache // this is done before creating the pixmapCache object since that can change the mtime // on the cache file // FIXME: when using the system colors, if they change while the application is not running // the cache should be dropped; we need a way to detect system color change when the // application is not running. // check for expired cache const QString cacheFilePath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1Char('/') + cacheFile + QLatin1String(".kcache"); if (!cacheFilePath.isEmpty()) { const QFileInfo cacheFileInfo(cacheFilePath); const QFileInfo metadataFileInfo(themeMetadataPath); const QFileInfo iconThemeMetadataFileInfo(iconThemeMetadataPath); cachesTooOld = (cacheFileInfo.lastModified().toSecsSinceEpoch() < metadataFileInfo.lastModified().toSecsSinceEpoch()) || (cacheFileInfo.lastModified().toSecsSinceEpoch() < iconThemeMetadataFileInfo.lastModified().toSecsSinceEpoch()); } } ThemeConfig config; pixmapCache = new KImageCache(cacheFile, config.themeCacheKb() * 1024); if (cachesTooOld) { discardCache(PixmapCache | SvgElementsCache); } } if (cacheTheme && !svgElementsCache) { const QString svgElementsFileNameBase = QLatin1String("plasma-svgelements-") + themeName; QString svgElementsFileName = svgElementsFileNameBase; if (!themeVersion.isEmpty()) { svgElementsFileName += QLatin1String("_v") + themeVersion; } // now we check for (and remove) old caches QDir cacheDir(QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation)); cacheDir.setNameFilters(QStringList({svgElementsFileNameBase + QLatin1Char('*')})); const auto files = cacheDir.entryInfoList(); for (const QFileInfo &file : files) { if (!file.absoluteFilePath().endsWith(svgElementsFileName)) { QFile::remove(file.absoluteFilePath()); } } const QString svgElementsFile = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1Char('/') + svgElementsFileName; svgElementsCache = KSharedConfig::openConfig(svgElementsFile, KConfig::SimpleConfig); QString currentIconThemePath; const auto *iconTheme = KIconLoader::global()->theme(); if (iconTheme) { currentIconThemePath = iconTheme->dir(); } KConfigGroup globalGroup(svgElementsCache, QLatin1String("Global")); const QString oldIconThemePath = globalGroup.readEntry("currentIconThemePath", QString()); if (oldIconThemePath != currentIconThemePath) { discardCache(PixmapCache | SvgElementsCache); globalGroup.writeEntry("currentIconThemePath", currentIconThemePath); svgElementsCache = KSharedConfig::openConfig(svgElementsFile, KConfig::SimpleConfig); } } return cacheTheme; } void ThemePrivate::onAppExitCleanup() { pixmapsToCache.clear(); delete pixmapCache; pixmapCache = nullptr; cacheTheme = false; } QString ThemePrivate::imagePath(const QString& theme, const QString& type, const QString& image) { QString subdir = QLatin1String(PLASMA_RELATIVE_DATA_INSTALL_DIR "/desktoptheme/") % theme % type % image; return QStandardPaths::locate(QStandardPaths::GenericDataLocation, subdir); } QString ThemePrivate::findInTheme(const QString &image, const QString &theme, bool cache) { if (cache) { auto it = discoveries.constFind(image); if (it != discoveries.constEnd()) { return it.value(); } } QString type = QStringLiteral("/"); if (!compositingActive) { type = QStringLiteral("/opaque/"); } else if (backgroundContrastActive) { type = QStringLiteral("/translucent/"); } QString search = imagePath(theme, type, image); //not found or compositing enabled if (search.isEmpty()) { search = imagePath(theme, QStringLiteral("/"), image); } if (cache && !search.isEmpty()) { discoveries.insert(image, search); } return search; } void ThemePrivate::compositingChanged(bool active) { #if HAVE_X11 if (compositingActive != active) { compositingActive = active; //qCDebug(LOG_PLASMA) << QTime::currentTime(); scheduleThemeChangeNotification(PixmapCache | SvgElementsCache); } #endif } void ThemePrivate::discardCache(CacheTypes caches) { if (caches & PixmapCache) { pixmapsToCache.clear(); pixmapSaveTimer->stop(); if (pixmapCache) { pixmapCache->clear(); } } else { // This deletes the object but keeps the on-disk cache for later use delete pixmapCache; pixmapCache = nullptr; } cachedDefaultStyleSheet = QString(); cachedSvgStyleSheets.clear(); cachedSelectedSvgStyleSheets.clear(); if (caches & SvgElementsCache) { discoveries.clear(); invalidElements.clear(); svgElementsCache = nullptr; } } void ThemePrivate::scheduledCacheUpdate() { if (useCache()) { QHashIterator it(pixmapsToCache); while (it.hasNext()) { it.next(); pixmapCache->insertPixmap(idsToCache[it.key()], it.value()); } } pixmapsToCache.clear(); keysToCache.clear(); idsToCache.clear(); } void ThemePrivate::colorsChanged() { // in the case the theme follows the desktop settings, refetch the colorschemes // and discard the svg pixmap cache if (!colors) { KSharedConfig::openConfig()->reparseConfiguration(); } colorScheme = KColorScheme(QPalette::Active, KColorScheme::Window, colors); buttonColorScheme = KColorScheme(QPalette::Active, KColorScheme::Button, colors); viewColorScheme = KColorScheme(QPalette::Active, KColorScheme::View, colors); selectionColorScheme = KColorScheme(QPalette::Active, KColorScheme::Selection, colors); palette = KColorScheme::createApplicationPalette(colors); scheduleThemeChangeNotification(PixmapCache | SvgElementsCache); emit applicationPaletteChange(); } void ThemePrivate::scheduleThemeChangeNotification(CacheTypes caches) { cachesToDiscard |= caches; updateNotificationTimer->start(); } void ThemePrivate::notifyOfChanged() { //qCDebug(LOG_PLASMA) << cachesToDiscard; discardCache(cachesToDiscard); cachesToDiscard = NoCache; emit themeChanged(); } const QString ThemePrivate::processStyleSheet(const QString &css, Plasma::Svg::Status status) { QString stylesheet; if (css.isEmpty()) { stylesheet = cachedDefaultStyleSheet; if (stylesheet.isEmpty()) { stylesheet = QStringLiteral("\n\ body {\n\ color: %textcolor;\n\ generalfont-size: %fontsize;\n\ font-family: %fontfamily;\n\ }\n\ a:active { color: %activatedlink; }\n\ a:link { color: %link; }\n\ a:visited { color: %visitedlink; }\n\ a:hover { color: %hoveredlink; text-decoration: none; }\n\ "); stylesheet = cachedDefaultStyleSheet = processStyleSheet(stylesheet, status); } return stylesheet; } else { stylesheet = css; } QHash elements; // If you add elements here, make sure their names are sufficiently unique to not cause // clashes between element keys elements[QStringLiteral("%textcolor")] = color(status == Svg::Status::Selected ? Theme::HighlightedTextColor : Theme::TextColor, Theme::NormalColorGroup).name(); elements[QStringLiteral("%backgroundcolor")] = color(status == Svg::Status::Selected ? Theme::HighlightColor : Theme::BackgroundColor, Theme::NormalColorGroup).name(); elements[QStringLiteral("%highlightcolor")] = color(Theme::HighlightColor, Theme::NormalColorGroup).name(); elements[QStringLiteral("%highlightedtextcolor")] = color(Theme::HighlightedTextColor, Theme::NormalColorGroup).name(); elements[QStringLiteral("%visitedlink")] = color(Theme::VisitedLinkColor, Theme::NormalColorGroup).name(); elements[QStringLiteral("%activatedlink")] = color(Theme::HighlightColor, Theme::NormalColorGroup).name(); elements[QStringLiteral("%hoveredlink")] = color(Theme::HighlightColor, Theme::NormalColorGroup).name(); elements[QStringLiteral("%link")] = color(Theme::LinkColor, Theme::NormalColorGroup).name(); elements[QStringLiteral("%positivetextcolor")] = color(Theme::PositiveTextColor, Theme::NormalColorGroup).name(); elements[QStringLiteral("%neutraltextcolor")] = color(Theme::NeutralTextColor, Theme::NormalColorGroup).name(); elements[QStringLiteral("%negativetextcolor")] = color(Theme::NegativeTextColor, Theme::NormalColorGroup).name(); elements[QStringLiteral("%buttontextcolor")] = color(status == Svg::Status::Selected ? Theme::HighlightedTextColor : Theme::TextColor, Theme::ButtonColorGroup).name(); elements[QStringLiteral("%buttonbackgroundcolor")] = color(status == Svg::Status::Selected ? Theme::HighlightColor : Theme::BackgroundColor, Theme::ButtonColorGroup).name(); elements[QStringLiteral("%buttonhovercolor")] = color(Theme::HoverColor, Theme::ButtonColorGroup).name(); elements[QStringLiteral("%buttonfocuscolor")] = color(Theme::FocusColor, Theme::ButtonColorGroup).name(); elements[QStringLiteral("%buttonhighlightedtextcolor")] = color(Theme::HighlightedTextColor, Theme::ButtonColorGroup).name(); elements[QStringLiteral("%buttonpositivetextcolor")] = color(Theme::PositiveTextColor, Theme::ButtonColorGroup).name(); elements[QStringLiteral("%buttonneutraltextcolor")] = color(Theme::NeutralTextColor, Theme::ButtonColorGroup).name(); elements[QStringLiteral("%buttonnegativetextcolor")] = color(Theme::NegativeTextColor, Theme::ButtonColorGroup).name(); elements[QStringLiteral("%viewtextcolor")] = color(status == Svg::Status::Selected ? Theme::HighlightedTextColor : Theme::TextColor, Theme::ViewColorGroup).name(); elements[QStringLiteral("%viewbackgroundcolor")] = color(status == Svg::Status::Selected ? Theme::HighlightColor : Theme::BackgroundColor, Theme::ViewColorGroup).name(); elements[QStringLiteral("%viewhovercolor")] = color(Theme::HoverColor, Theme::ViewColorGroup).name(); elements[QStringLiteral("%viewfocuscolor")] = color(Theme::FocusColor, Theme::ViewColorGroup).name(); elements[QStringLiteral("%viewhighlightedtextcolor")] = color(Theme::HighlightedTextColor, Theme::ViewColorGroup).name(); elements[QStringLiteral("%viewpositivetextcolor")] = color(Theme::PositiveTextColor, Theme::ViewColorGroup).name(); elements[QStringLiteral("%viewneutraltextcolor")] = color(Theme::NeutralTextColor, Theme::ViewColorGroup).name(); elements[QStringLiteral("%viewnegativetextcolor")] = color(Theme::NegativeTextColor, Theme::ViewColorGroup).name(); elements[QStringLiteral("%complementarytextcolor")] = color(status == Svg::Status::Selected ? Theme::HighlightedTextColor : Theme::TextColor, Theme::ComplementaryColorGroup).name(); elements[QStringLiteral("%complementarybackgroundcolor")] = color(status == Svg::Status::Selected ? Theme::HighlightColor : Theme::BackgroundColor, Theme::ComplementaryColorGroup).name(); elements[QStringLiteral("%complementaryhovercolor")] = color(Theme::HoverColor, Theme::ComplementaryColorGroup).name(); elements[QStringLiteral("%complementaryfocuscolor")] = color(Theme::FocusColor, Theme::ComplementaryColorGroup).name(); elements[QStringLiteral("%complementaryhighlightedtextcolor")] = color(Theme::HighlightedTextColor, Theme::ComplementaryColorGroup).name(); elements[QStringLiteral("%complementarypositivetextcolor")] = color(Theme::PositiveTextColor, Theme::ComplementaryColorGroup).name(); elements[QStringLiteral("%complementaryneutraltextcolor")] = color(Theme::NeutralTextColor, Theme::ComplementaryColorGroup).name(); elements[QStringLiteral("%complementarynegativetextcolor")] = color(Theme::NegativeTextColor, Theme::ComplementaryColorGroup).name(); QFont font = QGuiApplication::font(); elements[QStringLiteral("%fontsize")] = QStringLiteral("%1pt").arg(font.pointSize()); elements[QStringLiteral("%fontfamily")] = font.family().splitRef(QLatin1Char('[')).first().toString(); elements[QStringLiteral("%smallfontsize")] = QStringLiteral("%1pt").arg(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont).pointSize()); QHash::const_iterator it = elements.constBegin(); QHash::const_iterator itEnd = elements.constEnd(); for (; it != itEnd; ++it) { stylesheet.replace(it.key(), it.value()); } return stylesheet; } const QString ThemePrivate::svgStyleSheet(Plasma::Theme::ColorGroup group, Plasma::Svg::Status status) { QString stylesheet = (status == Svg::Status::Selected) ? cachedSelectedSvgStyleSheets.value(group) : cachedSvgStyleSheets.value(group); if (stylesheet.isEmpty()) { QString skel = QStringLiteral(".ColorScheme-%1{color:%2;}"); switch (group) { case Theme::ButtonColorGroup: stylesheet += skel.arg(QStringLiteral("Text"), QStringLiteral("%buttontextcolor")); stylesheet += skel.arg(QStringLiteral("Background"), QStringLiteral("%buttonbackgroundcolor")); stylesheet += skel.arg(QStringLiteral("Highlight"), QStringLiteral("%buttonhovercolor")); stylesheet += skel.arg(QStringLiteral("HighlightedText"), QStringLiteral("%buttonhighlightedtextcolor")); stylesheet += skel.arg(QStringLiteral("PositiveText"), QStringLiteral("%buttonpositivetextcolor")); stylesheet += skel.arg(QStringLiteral("NeutralText"), QStringLiteral("%buttonneutraltextcolor")); stylesheet += skel.arg(QStringLiteral("NegativeText"), QStringLiteral("%buttonnegativetextcolor")); break; case Theme::ViewColorGroup: stylesheet += skel.arg(QStringLiteral("Text"), QStringLiteral("%viewtextcolor")); stylesheet += skel.arg(QStringLiteral("Background"), QStringLiteral("%viewbackgroundcolor")); stylesheet += skel.arg(QStringLiteral("Highlight"), QStringLiteral("%viewhovercolor")); stylesheet += skel.arg(QStringLiteral("HighlightedText"), QStringLiteral("%viewhighlightedtextcolor")); stylesheet += skel.arg(QStringLiteral("PositiveText"), QStringLiteral("%viewpositivetextcolor")); stylesheet += skel.arg(QStringLiteral("NeutralText"), QStringLiteral("%viewneutraltextcolor")); stylesheet += skel.arg(QStringLiteral("NegativeText"), QStringLiteral("%viewnegativetextcolor")); break; case Theme::ComplementaryColorGroup: stylesheet += skel.arg(QStringLiteral("Text"), QStringLiteral("%complementarytextcolor")); stylesheet += skel.arg(QStringLiteral("Background"), QStringLiteral("%complementarybackgroundcolor")); stylesheet += skel.arg(QStringLiteral("Highlight"), QStringLiteral("%complementaryhovercolor")); stylesheet += skel.arg(QStringLiteral("HighlightedText"), QStringLiteral("%complementaryhighlightedtextcolor")); stylesheet += skel.arg(QStringLiteral("PositiveText"), QStringLiteral("%complementarypositivetextcolor")); stylesheet += skel.arg(QStringLiteral("NeutralText"), QStringLiteral("%complementaryneutraltextcolor")); stylesheet += skel.arg(QStringLiteral("NegativeText"), QStringLiteral("%complementarynegativetextcolor")); break; default: stylesheet += skel.arg(QStringLiteral("Text"), QStringLiteral("%textcolor")); stylesheet += skel.arg(QStringLiteral("Background"), QStringLiteral("%backgroundcolor")); stylesheet += skel.arg(QStringLiteral("Highlight"), QStringLiteral("%highlightcolor")); stylesheet += skel.arg(QStringLiteral("HighlightedText"), QStringLiteral("%highlightedtextcolor")); stylesheet += skel.arg(QStringLiteral("PositiveText"), QStringLiteral("%positivetextcolor")); stylesheet += skel.arg(QStringLiteral("NeutralText"), QStringLiteral("%neutraltextcolor")); stylesheet += skel.arg(QStringLiteral("NegativeText"), QStringLiteral("%negativetextcolor")); } stylesheet += skel.arg(QStringLiteral("ButtonText"), QStringLiteral("%buttontextcolor")); stylesheet += skel.arg(QStringLiteral("ButtonBackground"), QStringLiteral("%buttonbackgroundcolor")); stylesheet += skel.arg(QStringLiteral("ButtonHover"), QStringLiteral("%buttonhovercolor")); stylesheet += skel.arg(QStringLiteral("ButtonFocus"), QStringLiteral("%buttonfocuscolor")); stylesheet += skel.arg(QStringLiteral("ButtonHighlightedText"), QStringLiteral("%buttonhighlightedtextcolor")); stylesheet += skel.arg(QStringLiteral("ButtonPositiveText"), QStringLiteral("%buttonpositivetextcolor")); stylesheet += skel.arg(QStringLiteral("ButtonNeutralText"), QStringLiteral("%buttonneutraltextcolor")); stylesheet += skel.arg(QStringLiteral("ButtonNegativeText"), QStringLiteral("%buttonnegativetextcolor")); stylesheet += skel.arg(QStringLiteral("ViewText"), QStringLiteral("%viewtextcolor")); stylesheet += skel.arg(QStringLiteral("ViewBackground"), QStringLiteral("%viewbackgroundcolor")); stylesheet += skel.arg(QStringLiteral("ViewHover"), QStringLiteral("%viewhovercolor")); stylesheet += skel.arg(QStringLiteral("ViewFocus"), QStringLiteral("%viewfocuscolor")); stylesheet += skel.arg(QStringLiteral("ViewHighlightedText"), QStringLiteral("%viewhighlightedtextcolor")); stylesheet += skel.arg(QStringLiteral("ViewPositiveText"), QStringLiteral("%viewpositivetextcolor")); stylesheet += skel.arg(QStringLiteral("ViewNeutralText"), QStringLiteral("%viewneutraltextcolor")); stylesheet += skel.arg(QStringLiteral("ViewNegativeText"), QStringLiteral("%viewnegativetextcolor")); stylesheet += skel.arg(QStringLiteral("ComplementaryText"), QStringLiteral("%complementarytextcolor")); stylesheet += skel.arg(QStringLiteral("ComplementaryBackground"), QStringLiteral("%complementarybackgroundcolor")); stylesheet += skel.arg(QStringLiteral("ComplementaryHover"), QStringLiteral("%complementaryhovercolor")); stylesheet += skel.arg(QStringLiteral("ComplementaryFocus"), QStringLiteral("%complementaryfocuscolor")); stylesheet += skel.arg(QStringLiteral("ComplementaryHighlightedText"), QStringLiteral("%complementaryhighlightedtextcolor")); stylesheet += skel.arg(QStringLiteral("ComplementaryPositiveText"), QStringLiteral("%complementarypositivetextcolor")); stylesheet += skel.arg(QStringLiteral("ComplementaryNeutralText"), QStringLiteral("%complementaryneutraltextcolor")); stylesheet += skel.arg(QStringLiteral("ComplementaryNegativeText"), QStringLiteral("%complementarynegativetextcolor")); stylesheet = processStyleSheet(stylesheet, status); if (status == Svg::Status::Selected) { cachedSelectedSvgStyleSheets.insert(group, stylesheet); } else { cachedSvgStyleSheets.insert(group, stylesheet); } } return stylesheet; } void ThemePrivate::settingsFileChanged(const QString &file) { qCDebug(LOG_PLASMA) << "settingsFile: " << file; if (file == themeMetadataPath) { const KPluginInfo pluginInfo(themeMetadataPath); if (!pluginInfo.isValid() || themeVersion != pluginInfo.version()) { scheduleThemeChangeNotification(SvgElementsCache); } } else if (file.endsWith(QLatin1String(themeRcFile))) { config().config()->reparseConfiguration(); settingsChanged(true); } } void ThemePrivate::settingsChanged(bool emitChanges) { if (fixedName) { return; } //qCDebug(LOG_PLASMA) << "Settings Changed!"; KConfigGroup cg = config(); setThemeName(cg.readEntry("name", ThemePrivate::defaultTheme), false, emitChanges); } void ThemePrivate::saveSvgElementsCache() { if (svgElementsCache) { QHashIterator > it(invalidElements); while (it.hasNext()) { it.next(); KConfigGroup imageGroup(svgElementsCache, it.key()); imageGroup.writeEntry("invalidElements", it.value().values()); //FIXME: add QSet support to KConfig } //Pretty drastic, but this is executed only very rarely svgElementsCache->sync(); } } QColor ThemePrivate::color(Theme::ColorRole role, Theme::ColorGroup group) const { const KColorScheme *scheme = nullptr; //Before 5.0 Plasma theme really only used Normal and Button //many old themes are built on this assumption and will break //otherwise if (apiMajor < 5 && group != Theme::NormalColorGroup) { group = Theme::ButtonColorGroup; } switch (group) { case Theme::ButtonColorGroup: { scheme = &buttonColorScheme; break; } case Theme::ViewColorGroup: { scheme = &viewColorScheme; break; } //this doesn't have a real kcolorscheme case Theme::ComplementaryColorGroup: { scheme = &complementaryColorScheme; break; } case Theme::NormalColorGroup: default: { scheme = &colorScheme; break; } } switch (role) { case Theme::TextColor: return scheme->foreground(KColorScheme::NormalText).color(); case Theme::BackgroundColor: return scheme->background(KColorScheme::NormalBackground).color(); case Theme::HoverColor: return scheme->decoration(KColorScheme::HoverColor).color(); case Theme::HighlightColor: return selectionColorScheme.background(KColorScheme::NormalBackground).color(); case Theme::FocusColor: return scheme->decoration(KColorScheme::FocusColor).color(); case Theme::LinkColor: return scheme->foreground(KColorScheme::LinkText).color(); case Theme::VisitedLinkColor: return scheme->foreground(KColorScheme::VisitedText).color(); case Theme::HighlightedTextColor: return selectionColorScheme.foreground(KColorScheme::NormalText).color(); case Theme::PositiveTextColor: return scheme->foreground(KColorScheme::PositiveText).color(); case Theme::NeutralTextColor: return scheme->foreground(KColorScheme::NeutralText).color(); case Theme::NegativeTextColor: return scheme->foreground(KColorScheme::NegativeText).color(); case Theme::DisabledTextColor: return scheme->foreground(KColorScheme::InactiveText).color(); } return QColor(); } void ThemePrivate::processWallpaperSettings(KConfigBase *metadata) { if (!defaultWallpaperTheme.isEmpty() && defaultWallpaperTheme != QLatin1String(DEFAULT_WALLPAPER_THEME)) { return; } KConfigGroup cg; if (metadata->hasGroup("Wallpaper")) { // we have a theme color config, so let's also check to see if // there is a wallpaper defined in there. cg = KConfigGroup(metadata, "Wallpaper"); } else { // since we didn't find an entry in the theme, let's look in the main // theme config cg = config(); } defaultWallpaperTheme = cg.readEntry("defaultWallpaperTheme", DEFAULT_WALLPAPER_THEME); defaultWallpaperSuffix = cg.readEntry("defaultFileSuffix", DEFAULT_WALLPAPER_SUFFIX); defaultWallpaperWidth = cg.readEntry("defaultWidth", DEFAULT_WALLPAPER_WIDTH); defaultWallpaperHeight = cg.readEntry("defaultHeight", DEFAULT_WALLPAPER_HEIGHT); } void ThemePrivate::processContrastSettings(KConfigBase *metadata) { KConfigGroup cg; if (metadata->hasGroup("ContrastEffect")) { cg = KConfigGroup(metadata, "ContrastEffect"); backgroundContrastEnabled = cg.readEntry("enabled", false); backgroundContrast = cg.readEntry("contrast", qQNaN()); backgroundIntensity = cg.readEntry("intensity", qQNaN()); backgroundSaturation = cg.readEntry("saturation", qQNaN()); } else { backgroundContrastEnabled = false; } } void ThemePrivate::processBlurBehindSettings(KConfigBase *metadata) { KConfigGroup cg; if (metadata->hasGroup("BlurBehindEffect")) { cg = KConfigGroup(metadata, "BlurBehindEffect"); blurBehindEnabled = cg.readEntry("enabled", true); } else { blurBehindEnabled = true; } } void ThemePrivate::setThemeName(const QString &tempThemeName, bool writeSettings, bool emitChanged) { QString theme = tempThemeName; if (theme.isEmpty() || theme == themeName) { // let's try and get the default theme at least if (themeName.isEmpty()) { theme = QLatin1String(ThemePrivate::defaultTheme); } else { return; } } // we have one special theme: essentially a dummy theme used to cache things with // the system colors. bool realTheme = theme != QLatin1String(systemColorsTheme); if (realTheme) { QString themePath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String(PLASMA_RELATIVE_DATA_INSTALL_DIR "/desktoptheme/") % theme % QLatin1String("/metadata.desktop")); if (themePath.isEmpty() && themeName.isEmpty()) { // note: can't use QStringLiteral("foo" "bar") on Windows themePath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral(PLASMA_RELATIVE_DATA_INSTALL_DIR "/desktoptheme/default"), QStandardPaths::LocateDirectory); if (themePath.isEmpty()) { return; } theme = QLatin1String(ThemePrivate::defaultTheme); } } // check again as ThemePrivate::defaultTheme might be empty if (themeName == theme) { return; } themeName = theme; // load the color scheme config const QString colorsFile = realTheme ? QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String(PLASMA_RELATIVE_DATA_INSTALL_DIR "/desktoptheme/") % theme % QLatin1String("/colors")) : QString(); //qCDebug(LOG_PLASMA) << "we're going for..." << colorsFile << "*******************"; if (colorsFile.isEmpty()) { colors = nullptr; } else { colors = KSharedConfig::openConfig(colorsFile); } colorScheme = KColorScheme(QPalette::Active, KColorScheme::Window, colors); selectionColorScheme = KColorScheme(QPalette::Active, KColorScheme::Selection, colors); buttonColorScheme = KColorScheme(QPalette::Active, KColorScheme::Button, colors); viewColorScheme = KColorScheme(QPalette::Active, KColorScheme::View, colors); complementaryColorScheme = KColorScheme(QPalette::Active, KColorScheme::Complementary, colors); palette = KColorScheme::createApplicationPalette(colors); const QString wallpaperPath = QLatin1String(PLASMA_RELATIVE_DATA_INSTALL_DIR "/desktoptheme/") % theme % QLatin1String("/wallpapers/"); hasWallpapers = !QStandardPaths::locate(QStandardPaths::GenericDataLocation, wallpaperPath, QStandardPaths::LocateDirectory).isEmpty(); // load the wallpaper settings, if any if (realTheme) { const QString metadataPath(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String(PLASMA_RELATIVE_DATA_INSTALL_DIR "/desktoptheme/") % theme % QLatin1String("/metadata.desktop"))); KConfig metadata(metadataPath, KConfig::SimpleConfig); pluginMetaData = KPluginMetaData(metadataPath); processContrastSettings(&metadata); processBlurBehindSettings(&metadata); processWallpaperSettings(&metadata); KConfigGroup cg(&metadata, "Settings"); QString fallback = cg.readEntry("FallbackTheme", QString()); fallbackThemes.clear(); while (!fallback.isEmpty() && !fallbackThemes.contains(fallback)) { fallbackThemes.append(fallback); - QString metadataPath(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String(PLASMA_RELATIVE_DATA_INSTALL_DIR "/desktoptheme/") % fallback % QLatin1String("/metadata.desktop"))); + QString metadataPath(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String(PLASMA_RELATIVE_DATA_INSTALL_DIR "/desktoptheme/") % fallback % QStringLiteral("/metadata.desktop"))); KConfig metadata(metadataPath, KConfig::SimpleConfig); KConfigGroup cg(&metadata, "Settings"); fallback = cg.readEntry("FallbackTheme", QString()); } if (!fallbackThemes.contains(QLatin1String(ThemePrivate::defaultTheme))) { fallbackThemes.append(QLatin1String(ThemePrivate::defaultTheme)); } - foreach (const QString &theme, fallbackThemes) { - QString metadataPath(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String(PLASMA_RELATIVE_DATA_INSTALL_DIR "/desktoptheme/") % theme % QLatin1String("/metadata.desktop"))); + for (const QString &theme : qAsConst(fallbackThemes)) { + QString metadataPath(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1Literal(PLASMA_RELATIVE_DATA_INSTALL_DIR "/desktoptheme/") % theme % QStringLiteral("/metadata.desktop"))); KConfig metadata(metadataPath, KConfig::SimpleConfig); processWallpaperSettings(&metadata); } //Check for what Plasma version the theme has been done //There are some behavioral differences between KDE4 Plasma and Plasma 5 cg = KConfigGroup(&metadata, "Desktop Entry"); const QString apiVersion = cg.readEntry("X-Plasma-API", QString()); apiMajor = 1; apiMinor = 0; apiRevision = 0; if (!apiVersion.isEmpty()) { QVector parts = apiVersion.splitRef(QLatin1Char('.')); if (!parts.isEmpty()) { apiMajor = parts.value(0).toInt(); } if (parts.count() > 1) { apiMinor = parts.value(1).toInt(); } if (parts.count() > 2) { apiRevision = parts.value(2).toInt(); } } } if (realTheme && isDefault && writeSettings) { // we're the default theme, let's save our status KConfigGroup &cg = config(); cg.writeEntry("name", themeName); cg.sync(); } if(emitChanged) { scheduleThemeChangeNotification(PixmapCache | SvgElementsCache); } } bool ThemePrivate::eventFilter(QObject *watched, QEvent *event) { if (watched == QCoreApplication::instance()) { if (event->type() == QEvent::ApplicationPaletteChange) { colorsChanged(); } if (event->type() == QEvent::ApplicationFontChange || event->type() == QEvent::FontChange) { Q_EMIT defaultFontChanged(); Q_EMIT smallestFontChanged(); } } return QObject::eventFilter(watched, event); } } #include "moc_theme_p.cpp" diff --git a/src/plasma/private/timetracker.cpp b/src/plasma/private/timetracker.cpp index 26f4e5afe..19934e16f 100644 --- a/src/plasma/private/timetracker.cpp +++ b/src/plasma/private/timetracker.cpp @@ -1,141 +1,141 @@ /* * Copyright 2014 by Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "timetracker.h" #include #include #include #include #include #include #include #include using namespace Plasma; Q_GLOBAL_STATIC_WITH_ARGS(const qint64, s_beginning, (QDateTime::currentDateTime().toMSecsSinceEpoch())) struct TimeTrackerWriter : QObject { Q_OBJECT public: TimeTrackerWriter() { QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, static_cast(&TimeTrackerWriter::print)); } void print() { QJsonArray array; - Q_FOREACH(const ObjectHistory& history, m_data) { + for (const ObjectHistory &history : qAsConst(m_data)) { array.append(QJsonObject { { QStringLiteral("events"), serializeEvents(history.events) }, { QStringLiteral("initial"), QJsonValue::fromVariant(history.initial) } }); } Q_ASSERT(array.count() == m_data.count()); QJsonDocument doc; doc.setArray(array); QFile f(QStringLiteral("/tmp/debug-") + QString::fromUtf8(qgetenv("USER")) + QStringLiteral(".json")); bool b = f.open(QFile::WriteOnly); Q_ASSERT(b); f.write(doc.toJson()); } void feed(QObject* obj, const ObjectHistory& tracker) { m_data[obj] = tracker; } QHash m_data; private: QJsonArray serializeEvents(const QVector& events) const { QJsonArray ret; Q_ASSERT(!events.isEmpty()); - foreach(const TimeEvent& ev, events) { + for (const TimeEvent &ev : events) { ret.append(QJsonObject { { QStringLiteral("comment"), ev.comment }, { QStringLiteral("time"), ev.moment.toMSecsSinceEpoch() - *s_beginning } }); } Q_ASSERT(ret.count() == events.count()); return ret; } }; Q_GLOBAL_STATIC(TimeTrackerWriter, s_writer) TimeTracker::TimeTracker(QObject* o) : QObject(o) { *s_beginning * 1; // ensure it's initialized QTimer* t = new QTimer(this); t->setInterval(2000); t->setSingleShot(false); connect(t, &QTimer::timeout, this, &TimeTracker::sync); t->start(); QMetaObject::invokeMethod(this, "init", Qt::QueuedConnection); } void TimeTracker::init() { m_history.events.append(TimeEvent { QDateTime::currentDateTime(), QStringLiteral("constructed %1 %2").arg(QString::fromUtf8(parent()->metaObject()->className()), parent()->objectName()) }); QMetaMethod propChange = metaObject()->method(metaObject()->indexOfSlot("propertyChanged()")); Q_ASSERT(propChange.isValid() && metaObject()->indexOfSlot("propertyChanged()")>=0); QObject* o = parent(); for (int i = 0, pc = o->metaObject()->propertyCount(); imetaObject()->property(i); m_history.initial[QString::fromUtf8(prop.name())] = prop.read(o); if (prop.hasNotifySignal()) connect(o, prop.notifySignal(), this, propChange); } } TimeTracker::~TimeTracker() { sync(); } void TimeTracker::sync() { s_writer->feed(parent(), m_history); } void TimeTracker::propertyChanged() { Q_ASSERT(sender() == parent()); const QMetaObject* mo = parent()->metaObject(); for (int i = 0, pc = mo->propertyCount(); iproperty(i); if (prop.notifySignalIndex() == senderSignalIndex()) { QString val; QDebug d(&val); d << prop.read(parent()); m_history.events.append(TimeEvent { QDateTime::currentDateTime(), QStringLiteral("property %1 changed to %2").arg(QString::fromUtf8(prop.name()), val.trimmed())}); } } } #include "timetracker.moc" diff --git a/src/plasma/service.cpp b/src/plasma/service.cpp index 8af35ec70..cab100c19 100644 --- a/src/plasma/service.cpp +++ b/src/plasma/service.cpp @@ -1,216 +1,218 @@ /* * Copyright 2008 Aaron Seigo * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "service.h" #include "private/service_p.h" #include "config-plasma.h" #include #include #include #include #include #include #include #include #include "version.h" #include "pluginloader.h" #include "debug_p.h" namespace Plasma { Service::Service(QObject *parent) : QObject(parent), d(new ServicePrivate(this)) { } Service::Service(QObject *parent, const QVariantList &args) : QObject(parent), d(new ServicePrivate(this)) { Q_UNUSED(args) } Service::~Service() { delete d; } void Service::setDestination(const QString &destination) { d->destination = destination; } QString Service::destination() const { return d->destination; } QStringList Service::operationNames() const { if (d->operationsMap.isEmpty()) { #ifndef NDEBUG // qCDebug(LOG_PLASMA) << "No valid operations scheme has been registered"; #endif return QStringList(); } return d->operationsMap.keys(); } QVariantMap Service::operationDescription(const QString &operationName) { if (d->operationsMap.isEmpty()) { #ifndef NDEBUG // qCDebug(LOG_PLASMA) << "No valid operations scheme has been registered"; #endif return QVariantMap(); } //qCDebug(LOG_PLASMA) << "operation" << operationName // << "requested, has keys" << d->operationsMap.keys(); return d->operationsMap.value(operationName); } ServiceJob *Service::startOperationCall(const QVariantMap &description, QObject *parent) { // TODO: nested groups? ServiceJob *job = nullptr; const QString op = !description.isEmpty() ? description.value(QStringLiteral("_name")).toString() : QString(); if (d->operationsMap.isEmpty()) { #ifndef NDEBUG // qCDebug(LOG_PLASMA) << "No valid operations scheme has been registered"; #endif } else if (!op.isEmpty() && d->operationsMap.contains(op)) { if (d->disabledOperations.contains(op)) { #ifndef NDEBUG // qCDebug(LOG_PLASMA) << "Operation" << op << "is disabled"; #endif } else { QVariantMap map = description; job = createJob(op, map); } } else { #ifndef NDEBUG // qCDebug(LOG_PLASMA) << op << "is not a valid group; valid groups are:" << d->operationsMap.keys(); #endif } if (!job) { job = new NullServiceJob(d->destination, op, this); } job->setParent(parent ? parent : this); QTimer::singleShot(0, job, SLOT(autoStart())); return job; } QString Service::name() const { return d->name; } void Service::setName(const QString &name) { d->name = name; // now reset the config, which may be based on our name d->operationsMap.clear(); registerOperationsScheme(); emit serviceReady(this); } void Service::setOperationEnabled(const QString &operation, bool enable) { if (d->operationsMap.isEmpty() || !d->operationsMap.contains(operation)) { return; } if (enable) { d->disabledOperations.remove(operation); } else { d->disabledOperations.insert(operation); } emit operationEnabledChanged(operation, enable); } bool Service::isOperationEnabled(const QString &operation) const { return d->operationsMap.contains(operation) && !d->disabledOperations.contains(operation); } void Service::setOperationsScheme(QIODevice *xml) { d->operationsMap.clear(); // /dev/null is because I need to pass a filename argument to construct a // KSharedConfig. We need a config object for the config loader even // though we dont' actually want to use any config parts from it, // we just want to share the KConfigLoader XML parsing. KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("/dev/null"), KConfig::SimpleConfig); KConfigLoader loader(config, xml); - foreach (const QString &group, loader.groupList()) { + const auto groupList = loader.groupList(); + for (const QString &group : groupList) { d->operationsMap[group][QStringLiteral("_name")] = group; } - foreach (KConfigSkeletonItem *item, loader.items()) { + const auto itemsList = loader.items(); + for (KConfigSkeletonItem *item : itemsList) { d->operationsMap[item->group()][item->key()] = item->property(); } } void Service::registerOperationsScheme() { if (!d->operationsMap.isEmpty()) { // we've already done our job. let's go home. return; } if (d->name.isEmpty()) { #ifndef NDEBUG // qCDebug(LOG_PLASMA) << "No name found"; #endif return; } const QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral(PLASMA_RELATIVE_DATA_INSTALL_DIR "/services/") + d->name + QStringLiteral(".operations")); if (path.isEmpty()) { #ifndef NDEBUG // qCDebug(LOG_PLASMA) << "Cannot find operations description:" << d->name << ".operations"; #endif return; } QFile file(path); setOperationsScheme(&file); } } // namespace Plasma #include "moc_service.cpp" diff --git a/src/plasma/svg.cpp b/src/plasma/svg.cpp index a7b89e9b8..bcf29cd77 100644 --- a/src/plasma/svg.cpp +++ b/src/plasma/svg.cpp @@ -1,1004 +1,1004 @@ /* * Copyright 2006-2007 Aaron Seigo * Copyright 2008-2010 Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "svg.h" #include "private/svg_p.h" #include "private/theme_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "applet.h" #include "package.h" #include "theme.h" #include "debug_p.h" namespace Plasma { SharedSvgRenderer::SharedSvgRenderer(QObject *parent) : QSvgRenderer(parent) { } SharedSvgRenderer::SharedSvgRenderer( const QString &filename, const QString &styleSheet, QHash &interestingElements, QObject *parent) : QSvgRenderer(parent) { KCompressionDevice file(filename, KCompressionDevice::GZip); if (!file.open(QIODevice::ReadOnly)) { return; } load(file.readAll(), styleSheet, interestingElements); } SharedSvgRenderer::SharedSvgRenderer( const QByteArray &contents, const QString &styleSheet, QHash &interestingElements, QObject *parent) : QSvgRenderer(parent) { load(contents, styleSheet, interestingElements); } bool SharedSvgRenderer::load( const QByteArray &contents, const QString &styleSheet, QHash &interestingElements) { // Apply the style sheet. if (!styleSheet.isEmpty() && contents.contains("current-color-scheme")) { QByteArray processedContents; processedContents.reserve(contents.size()); QXmlStreamReader reader(contents); QBuffer buffer(&processedContents); buffer.open(QIODevice::WriteOnly); QXmlStreamWriter writer(&buffer); while (!reader.atEnd()) { if (reader.readNext() == QXmlStreamReader::StartElement && reader.qualifiedName() == QLatin1String("style") && reader.attributes().value(QLatin1String("id")) == QLatin1String("current-color-scheme")) { writer.writeStartElement(QLatin1String("style")); writer.writeAttributes(reader.attributes()); writer.writeCharacters(styleSheet); writer.writeEndElement(); while (reader.tokenType() != QXmlStreamReader::EndElement) { reader.readNext(); } } else if (reader.tokenType() != QXmlStreamReader::Invalid) { writer.writeCurrentToken(reader); } } buffer.close(); if (!QSvgRenderer::load(processedContents)) { return false; } } else if (!QSvgRenderer::load(contents)) { return false; } // Search the SVG to find and store all ids that contain size hints. const QString contentsAsString(QString::fromLatin1(contents)); static const QRegularExpression idExpr(QLatin1String("id\\s*?=\\s*?(['\"])(\\d+?-\\d+?-.*?)\\1")); Q_ASSERT(idExpr.isValid()); auto matchIt = idExpr.globalMatch(contentsAsString); while (matchIt.hasNext()) { auto match = matchIt.next(); QString elementId = match.captured(2); QRectF elementRect = boundsOnElement(elementId); if (elementRect.isValid()) { interestingElements.insert(elementId, elementRect); } } return true; } #define QLSEP QLatin1Char('_') #define CACHE_ID_WITH_SIZE(size, id, status, devicePixelRatio) QString::number(int(size.width())) % QLSEP % QString::number(int(size.height())) % QLSEP % id % QLSEP % QString::number(status) % QLSEP % QString::number(int(devicePixelRatio)) #define CACHE_ID_NATURAL_SIZE(id, status, devicePixelRatio) QLatin1String("Natural") % QLSEP % id % QLSEP % QString::number(status) % QLSEP % QString::number(int(devicePixelRatio)) SvgPrivate::SvgPrivate(Svg *svg) : q(svg), renderer(nullptr), styleCrc(0), colorGroup(Plasma::Theme::NormalColorGroup), lastModified(0), devicePixelRatio(1.0), scaleFactor(s_lastScaleFactor), status(Svg::Status::Normal), multipleImages(false), themed(false), useSystemColors(false), fromCurrentTheme(false), applyColors(false), usesColors(false), cacheRendering(true), themeFailed(false) { } SvgPrivate::~SvgPrivate() { eraseRenderer(); } //This function is meant for the rects cache QString SvgPrivate::cacheId(const QString &elementId) const { if (size.isValid() && size != naturalSize) { return CACHE_ID_WITH_SIZE(size, elementId, status, devicePixelRatio); } else { return CACHE_ID_NATURAL_SIZE(elementId, status, devicePixelRatio); } } //This function is meant for the pixmap cache QString SvgPrivate::cachePath(const QString &path, const QSize &size) const { return CACHE_ID_WITH_SIZE(size, path, status, devicePixelRatio) % QLSEP % QString::number(colorGroup); } bool SvgPrivate::setImagePath(const QString &imagePath) { QString actualPath = imagePath; if (imagePath.startsWith(QLatin1String("file://"))) { //length of file:// actualPath.remove(0, 7); } bool isThemed = !actualPath.isEmpty() && !QDir::isAbsolutePath(actualPath); bool inIconTheme = false; //an absolute path.. let's try if this actually an *icon* theme if (!isThemed && !actualPath.isEmpty()) { const auto *iconTheme = KIconLoader::global()->theme(); isThemed = inIconTheme = iconTheme && actualPath.startsWith(iconTheme->dir()); } // lets check to see if we're already set to this file if (isThemed == themed && ((themed && themePath == actualPath) || (!themed && path == actualPath))) { return false; } eraseRenderer(); // if we don't have any path right now and are going to set one, // then lets not schedule a repaint because we are just initializing! bool updateNeeded = true; //!path.isEmpty() || !themePath.isEmpty(); QObject::disconnect(actualTheme(), SIGNAL(themeChanged()), q, SLOT(themeChanged())); if (isThemed && !themed && s_systemColorsCache) { // catch the case where we weren't themed, but now we are, and the colors cache was set up // ensure we are not connected to that theme previously QObject::disconnect(s_systemColorsCache.data(), nullptr, q, nullptr); } themed = isThemed; path.clear(); themePath.clear(); localRectCache.clear(); elementsWithSizeHints.clear(); bool oldFromCurrentTheme = fromCurrentTheme; fromCurrentTheme = !inIconTheme && isThemed && actualTheme()->currentThemeHasImage(imagePath); if (fromCurrentTheme != oldFromCurrentTheme) { emit q->fromCurrentThemeChanged(fromCurrentTheme); } if (inIconTheme) { themePath = actualPath; path = actualPath; QObject::connect(actualTheme(), SIGNAL(themeChanged()), q, SLOT(themeChanged())); } else if (themed) { themePath = actualPath; path = actualTheme()->imagePath(themePath); themeFailed = path.isEmpty(); QObject::connect(actualTheme(), SIGNAL(themeChanged()), q, SLOT(themeChanged())); } else if (QFileInfo::exists(actualPath)) { QObject::connect(cacheAndColorsTheme(), SIGNAL(themeChanged()), q, SLOT(themeChanged()), Qt::UniqueConnection); path = actualPath; } else { #ifndef NDEBUG // qCDebug(LOG_PLASMA) << "file '" << path << "' does not exist!"; #endif } // check if svg wants colorscheme applied checkColorHints(); // also images with absolute path needs to have a natural size initialized, // even if looks a bit weird using Theme to store non-themed stuff if ((themed && !path.isEmpty() && QFileInfo::exists(path)) || QFileInfo::exists(actualPath)) { QRectF rect; if (cacheAndColorsTheme()->findInRectsCache(path, QStringLiteral("_Natural_%1").arg(scaleFactor), rect)) { naturalSize = rect.size(); } else { createRenderer(); naturalSize = renderer->defaultSize() * scaleFactor; //qCDebug(LOG_PLASMA) << "natural size for" << path << "from renderer is" << naturalSize; cacheAndColorsTheme()->insertIntoRectsCache(path, QStringLiteral("_Natural_%1").arg(scaleFactor), QRectF(QPointF(0, 0), naturalSize)); //qCDebug(LOG_PLASMA) << "natural size for" << path << "from cache is" << naturalSize; } } if (!themed) { QFile f(actualPath); QFileInfo info(f); lastModified = info.lastModified().toSecsSinceEpoch(); } q->resize(); emit q->imagePathChanged(); return updateNeeded; } Theme *SvgPrivate::actualTheme() { if (!theme) { theme = new Plasma::Theme(q); } return theme.data(); } Theme *SvgPrivate::cacheAndColorsTheme() { if (themed || !useSystemColors) { return actualTheme(); } else { // use a separate cache source for unthemed svg's if (!s_systemColorsCache) { //FIXME: reference count this, so that it is deleted when no longer in use s_systemColorsCache = new Plasma::Theme(QStringLiteral("internal-system-colors")); } return s_systemColorsCache.data(); } } QPixmap SvgPrivate::findInCache(const QString &elementId, qreal ratio, const QSizeF &s) { QSize size; QString actualElementId; if (elementsWithSizeHints.isEmpty()) { // Fetch all size hinted element ids from the theme's rect cache // and store them locally. const QRegularExpression sizeHintedKeyExpr(QLatin1String("^") + CACHE_ID_NATURAL_SIZE(QStringLiteral("(\\d+)-(\\d+)-(.+)"), status, ratio) + QLatin1String("$")); - foreach (const QString &key, cacheAndColorsTheme()->listCachedRectKeys(path)) { + const auto lst = cacheAndColorsTheme()->listCachedRectKeys(path); + for (const QString &key : lst) { const auto match = sizeHintedKeyExpr.match(key); if (match.hasMatch()) { QString baseElementId = match.captured(3); QSize sizeHint(match.capturedRef(1).toInt(), match.capturedRef(2).toInt()); - if (sizeHint.isValid()) { elementsWithSizeHints.insert(baseElementId, sizeHint); } } } if (elementsWithSizeHints.isEmpty()) { // Make sure we won't query the theme unnecessarily. elementsWithSizeHints.insert(QString(), QSize()); } } // Look at the size hinted elements and try to find the smallest one with an // identical aspect ratio. if (s.isValid() && !elementId.isEmpty()) { const QList elementSizeHints = elementsWithSizeHints.values(elementId); if (!elementSizeHints.isEmpty()) { QSize bestFit(-1, -1); for (const QSize &hint : elementSizeHints) { if (hint.width() >= s.width() * ratio && hint.height() >= s.height() * ratio && (!bestFit.isValid() || (bestFit.width() * bestFit.height()) > (hint.width() * hint.height()))) { bestFit = hint; } } if (bestFit.isValid()) { actualElementId = QString::number(bestFit.width()) % QLatin1Char('-') % QString::number(bestFit.height()) % QLatin1Char('-') % elementId; } } } if (elementId.isEmpty() || !q->hasElement(actualElementId)) { actualElementId = elementId; } if (elementId.isEmpty() || (multipleImages && s.isValid())) { size = s.toSize() * ratio; } else { size = elementRect(actualElementId).size().toSize() * ratio; } if (size.isEmpty()) { return QPixmap(); } const QString id = cachePath(path, size) + actualElementId; //qCDebug(LOG_PLASMA) << "id is " << id; QPixmap p; if (cacheRendering && cacheAndColorsTheme()->findInCache(id, p, lastModified)) { p.setDevicePixelRatio(ratio); //qCDebug(LOG_PLASMA) << "found cached version of " << id << p.size(); return p; } //qCDebug(LOG_PLASMA) << "didn't find cached version of " << id << ", so re-rendering"; //qCDebug(LOG_PLASMA) << "size for " << actualElementId << " is " << s; // we have to re-render this puppy createRenderer(); QRectF finalRect = makeUniform(renderer->boundsOnElement(actualElementId), QRect(QPoint(0, 0), size)); //don't alter the pixmap size or it won't match up properly to, e.g., FrameSvg elements //makeUniform should never change the size so much that it gains or loses a whole pixel p = QPixmap(size); p.fill(Qt::transparent); QPainter renderPainter(&p); if (actualElementId.isEmpty()) { renderer->render(&renderPainter, finalRect); } else { renderer->render(&renderPainter, actualElementId, finalRect); } renderPainter.end(); p.setDevicePixelRatio(ratio); // Apply current color scheme if the svg asks for it if (applyColors) { QImage itmp = p.toImage(); KIconEffect::colorize(itmp, cacheAndColorsTheme()->color(Theme::BackgroundColor), 1.0); p = p.fromImage(itmp); } if (cacheRendering) { cacheAndColorsTheme()->insertIntoCache(id, p, QString::number((qint64)q, 16) % QLSEP % actualElementId); } return p; } void SvgPrivate::createRenderer() { if (renderer) { return; } //qCDebug(LOG_PLASMA) << kBacktrace(); if (themed && path.isEmpty() && !themeFailed) { Applet *applet = qobject_cast(q->parent()); //FIXME: this maybe could be more efficient if we knew if the package was empty, e.g. for //C++; however, I'm not sure this has any real world runtime impact. something to measure //for. if (applet && applet->kPackage().isValid()) { const KPackage::Package package = applet->kPackage(); path = package.filePath("images", themePath + QLatin1String(".svg")); if (path.isEmpty()) { path = package.filePath("images", themePath + QLatin1String(".svgz")); } } if (path.isEmpty()) { path = actualTheme()->imagePath(themePath); themeFailed = path.isEmpty(); if (themeFailed) { qCWarning(LOG_PLASMA) << "No image path found for" << themePath; } } } //qCDebug(LOG_PLASMA) << "********************************"; //qCDebug(LOG_PLASMA) << "FAIL! **************************"; //qCDebug(LOG_PLASMA) << path << "**"; QString styleSheet = cacheAndColorsTheme()->d->svgStyleSheet(colorGroup, status); styleCrc = qChecksum(styleSheet.toUtf8().constData(), styleSheet.size()); QHash::const_iterator it = s_renderers.constFind(styleCrc + path); if (it != s_renderers.constEnd()) { //qCDebug(LOG_PLASMA) << "gots us an existing one!"; renderer = it.value(); } else { if (path.isEmpty()) { renderer = new SharedSvgRenderer(); } else { QHash interestingElements; renderer = new SharedSvgRenderer(path, styleSheet, interestingElements); // Add interesting elements to the theme's rect cache. QHashIterator i(interestingElements); while (i.hasNext()) { i.next(); const QString &elementId = i.key(); const QRectF &elementRect = i.value(); const QString cacheId = CACHE_ID_NATURAL_SIZE(elementId, status, devicePixelRatio); localRectCache.insert(cacheId, elementRect); cacheAndColorsTheme()->insertIntoRectsCache(path, cacheId, elementRect); } } s_renderers[styleCrc + path] = renderer; } if (size == QSizeF()) { size = renderer->defaultSize(); } } void SvgPrivate::eraseRenderer() { if (renderer && renderer->ref.load() == 2) { // this and the cache reference it s_renderers.erase(s_renderers.find(styleCrc + path)); if (theme) { theme.data()->releaseRectsCache(path); } } renderer = nullptr; styleCrc = 0; localRectCache.clear(); elementsWithSizeHints.clear(); } QRectF SvgPrivate::elementRect(const QString &elementId) { if (themed && path.isEmpty()) { if (themeFailed) { return QRectF(); } path = actualTheme()->imagePath(themePath); themeFailed = path.isEmpty(); if (themeFailed) { return QRectF(); } } if (path.isEmpty()) { return QRectF(); } const QString id = cacheId(elementId); const auto it = localRectCache.constFind(id); if (it != localRectCache.constEnd()) { return *it; } QRectF rect; bool found = cacheAndColorsTheme()->findInRectsCache(path, id, rect); //This is a corner case where we are *sure* the element is not valid if (found && rect == QRectF()) { return rect; } else if (found) { localRectCache.insert(id, rect); } else { rect = findAndCacheElementRect(elementId, id); } return rect; } QRectF SvgPrivate::findAndCacheElementRect(const QString &elementId, const QString &id) { //we need to check the id before createRenderer(), otherwise it may generate a different id compared to the previous cacheId)( call createRenderer(); const auto it = localRectCache.constFind(id); if (it != localRectCache.constEnd()) { return *it; } //This code will usually never be run because createRenderer already caches all the boundingRect in the elements in the svg QRectF elementRect = renderer->elementExists(elementId) ? renderer->matrixForElement(elementId).map(renderer->boundsOnElement(elementId)).boundingRect() : QRectF(); naturalSize = renderer->defaultSize() * scaleFactor; qreal dx = size.width() / renderer->defaultSize().width(); qreal dy = size.height() / renderer->defaultSize().height(); elementRect = QRectF(elementRect.x() * dx, elementRect.y() * dy, elementRect.width() * dx, elementRect.height() * dy); cacheAndColorsTheme()->insertIntoRectsCache(path, id, elementRect); localRectCache.insert(id, elementRect); return elementRect; } QMatrix SvgPrivate::matrixForElement(const QString &elementId) { createRenderer(); return renderer->matrixForElement(elementId); } void SvgPrivate::checkColorHints() { if (elementRect(QStringLiteral("hint-apply-color-scheme")).isValid()) { applyColors = true; usesColors = true; } else if (elementRect(QStringLiteral("current-color-scheme")).isValid()) { applyColors = false; usesColors = true; } else { applyColors = false; usesColors = false; } // check to see if we are using colors, but the theme isn't being used or isn't providing // a colorscheme if (qGuiApp) { if (usesColors && (!themed || !actualTheme()->colorScheme())) { QObject::connect(actualTheme()->d, SIGNAL(applicationPaletteChange()), q, SLOT(colorsChanged())); } else { QObject::disconnect(actualTheme()->d, SIGNAL(applicationPaletteChange()), q, SLOT(colorsChanged())); } } } bool Svg::eventFilter(QObject *watched, QEvent *event) { return QObject::eventFilter(watched, event); } //Following two are utility functions to snap rendered elements to the pixel grid //to and from are always 0 <= val <= 1 qreal SvgPrivate::closestDistance(qreal to, qreal from) { qreal a = to - from; if (qFuzzyCompare(to, from)) { return 0; } else if (to > from) { qreal b = to - from - 1; return (qAbs(a) > qAbs(b)) ? b : a; } else { qreal b = 1 + to - from; return (qAbs(a) > qAbs(b)) ? b : a; } } QRectF SvgPrivate::makeUniform(const QRectF &orig, const QRectF &dst) { if (qFuzzyIsNull(orig.x()) || qFuzzyIsNull(orig.y())) { return dst; } QRectF res(dst); qreal div_w = dst.width() / orig.width(); qreal div_h = dst.height() / orig.height(); qreal div_x = dst.x() / orig.x(); qreal div_y = dst.y() / orig.y(); //horizontal snap if (!qFuzzyIsNull(div_x) && !qFuzzyCompare(div_w, div_x)) { qreal rem_orig = orig.x() - (floor(orig.x())); qreal rem_dst = dst.x() - (floor(dst.x())); qreal offset = closestDistance(rem_dst, rem_orig); res.translate(offset + offset * div_w, 0); res.setWidth(res.width() + offset); } //vertical snap if (!qFuzzyIsNull(div_y) && !qFuzzyCompare(div_h, div_y)) { qreal rem_orig = orig.y() - (floor(orig.y())); qreal rem_dst = dst.y() - (floor(dst.y())); qreal offset = closestDistance(rem_dst, rem_orig); res.translate(0, offset + offset * div_h); res.setHeight(res.height() + offset); } //qCDebug(LOG_PLASMA)<<"Aligning Rects, origin:"<imagePath().isEmpty()) { return; } if (themed) { // check if new theme svg wants colorscheme applied checkColorHints(); } QString currentPath = themed ? themePath : path; themePath.clear(); eraseRenderer(); setImagePath(currentPath); q->resize(); //qCDebug(LOG_PLASMA) << themePath << ">>>>>>>>>>>>>>>>>> theme changed"; emit q->repaintNeeded(); } void SvgPrivate::colorsChanged() { if (!usesColors) { return; } eraseRenderer(); qCDebug(LOG_PLASMA) << "repaint needed from colorsChanged"; emit q->repaintNeeded(); } QHash SvgPrivate::s_renderers; QPointer SvgPrivate::s_systemColorsCache; qreal SvgPrivate::s_lastScaleFactor = 1.0; Svg::Svg(QObject *parent) : QObject(parent), d(new SvgPrivate(this)) { } Svg::~Svg() { delete d; } void Svg::setDevicePixelRatio(qreal ratio) { //be completely integer for now //devicepixelratio is always set integer in the svg, so needs at least 192dpi to double up. //(it needs to be integer to have lines contained inside a svg piece to keep being pixel aligned) if (floor(d->devicePixelRatio) == floor(ratio)) { return; } if (FrameSvg *f = qobject_cast(this)) { f->clearCache(); } d->devicePixelRatio = floor(ratio); emit repaintNeeded(); } qreal Svg::devicePixelRatio() { return d->devicePixelRatio; } void Svg::setScaleFactor(qreal ratio) { //be completely integer for now //devicepixelratio is always set integer in the svg, so needs at least 192dpi to double up. //(it needs to be integer to have lines contained inside a svg piece to keep being pixel aligned) if (floor(d->scaleFactor) == floor(ratio)) { return; } d->scaleFactor = floor(ratio); d->s_lastScaleFactor = d->scaleFactor; //not resize() because we want to do it unconditionally QRectF rect; if (d->cacheAndColorsTheme()->findInRectsCache(d->path, QStringLiteral("_Natural_%1").arg(d->scaleFactor), rect)) { d->naturalSize = rect.size(); } else { d->createRenderer(); d->naturalSize = d->renderer->defaultSize() * d->scaleFactor; } d->size = d->naturalSize; emit repaintNeeded(); emit sizeChanged(); } qreal Svg::scaleFactor() const { return d->scaleFactor; } void Svg::setColorGroup(Plasma::Theme::ColorGroup group) { if (d->colorGroup == group) { return; } d->colorGroup = group; d->renderer = nullptr; emit colorGroupChanged(); emit repaintNeeded(); } Plasma::Theme::ColorGroup Svg::colorGroup() const { return d->colorGroup; } QPixmap Svg::pixmap(const QString &elementID) { if (elementID.isNull() || d->multipleImages) { return d->findInCache(elementID, d->devicePixelRatio, size()); } else { return d->findInCache(elementID, d->devicePixelRatio); } } QImage Svg::image(const QSize &size, const QString &elementID) { QPixmap pix(d->findInCache(elementID, d->devicePixelRatio, size)); return pix.toImage(); } void Svg::paint(QPainter *painter, const QPointF &point, const QString &elementID) { Q_ASSERT(painter->device()); const int ratio = painter->device()->devicePixelRatio(); QPixmap pix((elementID.isNull() || d->multipleImages) ? d->findInCache(elementID, ratio, size()) : d->findInCache(elementID, ratio)); if (pix.isNull()) { return; } painter->drawPixmap(QRectF(point, size()), pix, QRectF(QPointF(0, 0), pix.size())); } void Svg::paint(QPainter *painter, int x, int y, const QString &elementID) { paint(painter, QPointF(x, y), elementID); } void Svg::paint(QPainter *painter, const QRectF &rect, const QString &elementID) { Q_ASSERT(painter->device()); const int ratio = painter->device()->devicePixelRatio(); QPixmap pix(d->findInCache(elementID, ratio, rect.size())); painter->drawPixmap(QRectF(rect.topLeft(), rect.size()), pix, QRectF(QPointF(0, 0), pix.size())); } void Svg::paint(QPainter *painter, int x, int y, int width, int height, const QString &elementID) { Q_ASSERT(painter->device()); const int ratio = painter->device()->devicePixelRatio(); QPixmap pix(d->findInCache(elementID, ratio, QSizeF(width, height))); painter->drawPixmap(x, y, pix, 0, 0, pix.size().width(), pix.size().height()); } QSize Svg::size() const { if (d->size.isEmpty()) { d->size = d->naturalSize; } return d->size.toSize(); } void Svg::resize(qreal width, qreal height) { resize(QSize(width, height)); } void Svg::resize(const QSizeF &size) { if (qFuzzyCompare(size.width(), d->size.width()) && qFuzzyCompare(size.height(), d->size.height())) { return; } d->size = size; d->localRectCache.clear(); emit sizeChanged(); } void Svg::resize() { if (qFuzzyCompare(d->naturalSize.width(), d->size.width()) && qFuzzyCompare(d->naturalSize.height(), d->size.height())) { return; } d->size = d->naturalSize; d->localRectCache.clear(); emit sizeChanged(); } QSize Svg::elementSize(const QString &elementId) const { return d->elementRect(elementId).size().toSize(); } QRectF Svg::elementRect(const QString &elementId) const { return d->elementRect(elementId); } bool Svg::hasElement(const QString &elementId) const { if (d->path.isNull() && d->themePath.isNull()) { return false; } return d->elementRect(elementId).isValid(); } bool Svg::isValid() const { if (d->path.isNull() && d->themePath.isNull()) { return false; } //try very hard to avoid creation of a parser QRectF rect; if (d->cacheAndColorsTheme()->findInRectsCache(d->path, QStringLiteral("_Natural_%1").arg(d->scaleFactor), rect)) { return true; } if (d->path.isEmpty() || !QFileInfo::exists(d->path)) { return false; } d->createRenderer(); return d->renderer->isValid(); } void Svg::setContainsMultipleImages(bool multiple) { d->multipleImages = multiple; } bool Svg::containsMultipleImages() const { return d->multipleImages; } void Svg::setImagePath(const QString &svgFilePath) { if (d->setImagePath(svgFilePath)) { //qCDebug(LOG_PLASMA) << "repaintNeeded"; emit repaintNeeded(); } } QString Svg::imagePath() const { return d->themed ? d->themePath : d->path; } void Svg::setUsingRenderingCache(bool useCache) { d->cacheRendering = useCache; } bool Svg::isUsingRenderingCache() const { return d->cacheRendering; } bool Svg::fromCurrentTheme() const { return d->fromCurrentTheme; } void Svg::setUseSystemColors(bool system) { if (d->useSystemColors == system) { return; } d->useSystemColors = system; emit repaintNeeded(); } bool Svg::useSystemColors() const { return d->useSystemColors; } void Svg::setTheme(Plasma::Theme *theme) { if (!theme || theme == d->theme.data()) { return; } if (d->theme) { disconnect(d->theme.data(), nullptr, this, nullptr); } d->theme = theme; connect(theme, SIGNAL(themeChanged()), this, SLOT(themeChanged())); d->themeChanged(); } Theme *Svg::theme() const { return d->actualTheme(); } void Svg::setStatus(Plasma::Svg::Status status) { if (status == d->status) { return; } d->status = status; d->eraseRenderer(); emit statusChanged(status); emit repaintNeeded(); } Svg::Status Svg::status() const { return d->status; } } // Plasma namespace #include "private/moc_svg_p.cpp" #include "moc_svg.cpp" diff --git a/src/plasmaquick/appletquickitem.cpp b/src/plasmaquick/appletquickitem.cpp index b8ee273ce..d688c3591 100644 --- a/src/plasmaquick/appletquickitem.cpp +++ b/src/plasmaquick/appletquickitem.cpp @@ -1,931 +1,934 @@ /* * Copyright 2014 Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "appletquickitem.h" #include "private/appletquickitem_p.h" #include "debug_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace PlasmaQuick { QHash AppletQuickItemPrivate::s_rootObjects = QHash(); AppletQuickItemPrivate::PreloadPolicy AppletQuickItemPrivate::s_preloadPolicy = AppletQuickItemPrivate::Uninitialized; AppletQuickItemPrivate::AppletQuickItemPrivate(Plasma::Applet *a, AppletQuickItem *item) : q(item), switchWidth(-1), switchHeight(-1), applet(a), expanded(false), activationTogglesExpanded(false), initComplete(false) { if (s_preloadPolicy == Uninitialized) { //default as Adaptive s_preloadPolicy = Adaptive; if (qEnvironmentVariableIsSet("PLASMA_PRELOAD_POLICY")) { const QString policy = QString::fromUtf8(qgetenv("PLASMA_PRELOAD_POLICY")).toLower(); if (policy == QLatin1String("aggressive")) { s_preloadPolicy = Aggressive; } else if (policy == QLatin1String("none")) { s_preloadPolicy = None; } } qCInfo(LOG_PLASMAQUICK) << "Applet preload policy set to" << s_preloadPolicy; } } void AppletQuickItemPrivate::init() { if (!applet->pluginMetaData().isValid()) { // This `qmlObject` is used in other parts of the code qmlObject = new KDeclarative::QmlObject(q); return; } qmlObject = new KDeclarative::QmlObjectSharedEngine(q); if (!qmlObject->engine()->urlInterceptor()) { PackageUrlInterceptor *interceptor = new PackageUrlInterceptor(qmlObject->engine(), KPackage::Package()); interceptor->setForcePlasmaStyle(true); qmlObject->engine()->setUrlInterceptor(interceptor); } } int AppletQuickItemPrivate::preloadWeight() const { int defaultWeight; const QStringList provides(KPluginMetaData::readStringList(applet->pluginMetaData().rawData(), QStringLiteral("X-Plasma-Provides"))); //some applet types we want a bigger weight if (provides.contains(QLatin1String("org.kde.plasma.launchermenu"))) { defaultWeight = DefaultLauncherPreloadWeight; } else { defaultWeight = DefaultPreloadWeight; } //default widgets to be barely preloaded return qBound(0, applet->config().readEntry(QStringLiteral("PreloadWeight"), qMax(defaultWeight, applet->pluginMetaData().rawData().value(QStringLiteral("X-Plasma-PreloadWeight")).toInt())), 100); } void AppletQuickItemPrivate::connectLayoutAttached(QObject *item) { QObject *layout = nullptr; //Extract the representation's Layout, if any //No Item? if (!item) { return; } //Search a child that has the needed Layout properties //HACK: here we are not type safe, but is the only way to access to a pointer of Layout - foreach (QObject *child, item->children()) { + const auto lstChildren = item->children(); + for (QObject *child : lstChildren) { //find for the needed property of Layout: minimum/maximum/preferred sizes and fillWidth/fillHeight if (child->property("minimumWidth").isValid() && child->property("minimumHeight").isValid() && child->property("preferredWidth").isValid() && child->property("preferredHeight").isValid() && child->property("maximumWidth").isValid() && child->property("maximumHeight").isValid() && child->property("fillWidth").isValid() && child->property("fillHeight").isValid() ) { layout = child; break; } } //if the compact repr doesn't export a Layout.* attached property, //reset our own with default values if (!layout) { if (ownLayout) { ownLayout->setProperty("minimumWidth", 0); ownLayout->setProperty("minimumHeight", 0); ownLayout->setProperty("preferredWidth", -1); ownLayout->setProperty("preferredHeight", -1); ownLayout->setProperty("maximumWidth", std::numeric_limits::infinity()); ownLayout->setProperty("maximumHeight", std::numeric_limits::infinity()); ownLayout->setProperty("fillWidth", false); ownLayout->setProperty("fillHeight", false); } return; } //propagate all the size hints propagateSizeHint("minimumWidth"); propagateSizeHint("minimumHeight"); propagateSizeHint("preferredWidth"); propagateSizeHint("preferredHeight"); propagateSizeHint("maximumWidth"); propagateSizeHint("maximumHeight"); propagateSizeHint("fillWidth"); propagateSizeHint("fillHeight"); QObject *ownLayout = nullptr; - foreach (QObject *child, q->children()) { + const auto children = q->children(); + for (QObject *child : children) { //find for the needed property of Layout: minimum/maximum/preferred sizes and fillWidth/fillHeight if (child->property("minimumWidth").isValid() && child->property("minimumHeight").isValid() && child->property("preferredWidth").isValid() && child->property("preferredHeight").isValid() && child->property("maximumWidth").isValid() && child->property("maximumHeight").isValid() && child->property("fillWidth").isValid() && child->property("fillHeight").isValid() ) { ownLayout = child; break; } } //this should never happen, since we ask to create it if doesn't exists if (!ownLayout) { return; } //if the representation didn't change, don't do anything if (representationLayout == layout) { return; } if (representationLayout) { QObject::disconnect(representationLayout, nullptr, q, nullptr); } //Here we can't use the new connect syntax because we can't link against QtQuick layouts QObject::connect(layout, SIGNAL(minimumWidthChanged()), q, SLOT(minimumWidthChanged())); QObject::connect(layout, SIGNAL(minimumHeightChanged()), q, SLOT(minimumHeightChanged())); QObject::connect(layout, SIGNAL(preferredWidthChanged()), q, SLOT(preferredWidthChanged())); QObject::connect(layout, SIGNAL(preferredHeightChanged()), q, SLOT(preferredHeightChanged())); QObject::connect(layout, SIGNAL(maximumWidthChanged()), q, SLOT(maximumWidthChanged())); QObject::connect(layout, SIGNAL(maximumHeightChanged()), q, SLOT(maximumHeightChanged())); QObject::connect(layout, SIGNAL(fillWidthChanged()), q, SLOT(fillWidthChanged())); QObject::connect(layout, SIGNAL(fillHeightChanged()), q, SLOT(fillHeightChanged())); representationLayout = layout; AppletQuickItemPrivate::ownLayout = ownLayout; propagateSizeHint("minimumWidth"); propagateSizeHint("minimumHeight"); propagateSizeHint("preferredWidth"); propagateSizeHint("preferredHeight"); propagateSizeHint("maximumWidth"); propagateSizeHint("maximumHeight"); propagateSizeHint("fillWidth"); propagateSizeHint("fillHeight"); } void AppletQuickItemPrivate::propagateSizeHint(const QByteArray &layoutProperty) { if (ownLayout && representationLayout) { ownLayout->setProperty(layoutProperty.constData(), representationLayout->property(layoutProperty.constData())); } } QQuickItem *AppletQuickItemPrivate::createCompactRepresentationItem() { if (!compactRepresentation) { return nullptr; } if (compactRepresentationItem) { return compactRepresentationItem; } QVariantHash initialProperties; initialProperties[QStringLiteral("parent")] = QVariant::fromValue(q); compactRepresentationItem = qobject_cast(qmlObject->createObjectFromComponent(compactRepresentation, QtQml::qmlContext(qmlObject->rootObject()), initialProperties)); emit q->compactRepresentationItemChanged(compactRepresentationItem); return compactRepresentationItem; } QQuickItem *AppletQuickItemPrivate::createFullRepresentationItem() { if (fullRepresentationItem) { return fullRepresentationItem; } if (fullRepresentation && fullRepresentation != qmlObject->mainComponent()) { QVariantHash initialProperties; initialProperties[QStringLiteral("parent")] = QVariant(); fullRepresentationItem = qobject_cast(qmlObject->createObjectFromComponent(fullRepresentation, QtQml::qmlContext(qmlObject->rootObject()), initialProperties)); } else { fullRepresentation = qmlObject->mainComponent(); fullRepresentationItem = qobject_cast(qmlObject->rootObject()); emit q->fullRepresentationChanged(fullRepresentation); } if (!fullRepresentationItem) { return nullptr; } emit q->fullRepresentationItemChanged(fullRepresentationItem); return fullRepresentationItem; } QQuickItem *AppletQuickItemPrivate::createCompactRepresentationExpanderItem() { if (!compactRepresentationExpander) { return nullptr; } if (compactRepresentationExpanderItem) { return compactRepresentationExpanderItem; } compactRepresentationExpanderItem = qobject_cast(qmlObject->createObjectFromComponent(compactRepresentationExpander, QtQml::qmlContext(qmlObject->rootObject()))); if (!compactRepresentationExpanderItem) { return nullptr; } compactRepresentationExpanderItem->setProperty("compactRepresentation", QVariant::fromValue(createCompactRepresentationItem())); return compactRepresentationExpanderItem; } bool AppletQuickItemPrivate::appletShouldBeExpanded() const { if (applet->isContainment()) { return true; } else { if (switchWidth > 0 && switchHeight > 0) { return q->width() > switchWidth && q->height() > switchHeight; //if a size to switch wasn't set, determine what representation to always chose } else { //preferred representation set? if (preferredRepresentation) { return preferredRepresentation == fullRepresentation; //Otherwise, base on FormFactor } else { return (applet->formFactor() != Plasma::Types::Horizontal && applet->formFactor() != Plasma::Types::Vertical); } } } } void AppletQuickItemPrivate::preloadForExpansion() { qint64 time = 0; if (QLoggingCategory::defaultCategory()->isInfoEnabled()) { time = QDateTime::currentMSecsSinceEpoch(); } createFullRepresentationItem(); // When not already expanded, also preload the expander if (!expanded && !applet->isContainment() && (!preferredRepresentation || preferredRepresentation != fullRepresentation)) { createCompactRepresentationExpanderItem(); } if (!appletShouldBeExpanded() && compactRepresentationExpanderItem) { compactRepresentationExpanderItem->setProperty("fullRepresentation", QVariant::fromValue(createFullRepresentationItem())); } else if (fullRepresentationItem) { fullRepresentationItem->setProperty("parent", QVariant::fromValue(q)); } //preallocate nodes if (fullRepresentationItem && fullRepresentationItem->window()) { fullRepresentationItem->window()->create(); } qCDebug(LOG_PLASMAQUICK) << "Applet" << applet->title() << "loaded after" << ( QDateTime::currentMSecsSinceEpoch() - time) << "msec"; } void AppletQuickItemPrivate::compactRepresentationCheck() { if (!initComplete) { return; } if (!qmlObject->rootObject()) { return; } //ignore 0 sizes; if (q->width() <= 0 || q->height() <= 0) { return; } bool full = appletShouldBeExpanded(); if ((full && fullRepresentationItem && fullRepresentationItem == currentRepresentationItem) || (!full && compactRepresentationItem && compactRepresentationItem == currentRepresentationItem) ) { return; } //Expanded if (full) { QQuickItem *item = createFullRepresentationItem(); if (item) { //unwire with the expander if (compactRepresentationExpanderItem) { compactRepresentationExpanderItem->setProperty("fullRepresentation", QVariant()); compactRepresentationExpanderItem->setProperty("compactRepresentation", QVariant()); compactRepresentationExpanderItem->setVisible(false); } item->setParentItem(q); { //set anchors QQmlExpression expr(QtQml::qmlContext(qmlObject->rootObject()), item, QStringLiteral("parent")); QQmlProperty prop(item, QStringLiteral("anchors.fill")); prop.write(expr.evaluate()); } if (compactRepresentationItem) { compactRepresentationItem->setVisible(false); } currentRepresentationItem = item; connectLayoutAttached(item); expanded = true; emit q->expandedChanged(true); } //Icon } else { QQuickItem *compactItem = createCompactRepresentationItem(); QQuickItem *compactExpanderItem = createCompactRepresentationExpanderItem(); if (compactItem && compactExpanderItem) { //set the root item as the main visible item compactItem->setVisible(true); compactExpanderItem->setParentItem(q); compactExpanderItem->setVisible(true); { //set anchors QQmlExpression expr(QtQml::qmlContext(qmlObject->rootObject()), compactExpanderItem, QStringLiteral("parent")); QQmlProperty prop(compactExpanderItem, QStringLiteral("anchors.fill")); prop.write(expr.evaluate()); } if (fullRepresentationItem) { fullRepresentationItem->setProperty("parent", QVariant()); } compactExpanderItem->setProperty("compactRepresentation", QVariant::fromValue(compactItem)); //The actual full representation will be connected when created compactExpanderItem->setProperty("fullRepresentation", QVariant()); currentRepresentationItem = compactItem; connectLayoutAttached(compactItem); expanded = false; emit q->expandedChanged(false); } } } void AppletQuickItemPrivate::minimumWidthChanged() { propagateSizeHint("minimumWidth"); } void AppletQuickItemPrivate::minimumHeightChanged() { propagateSizeHint("minimumHeight"); } void AppletQuickItemPrivate::preferredWidthChanged() { propagateSizeHint("preferredWidth"); } void AppletQuickItemPrivate::preferredHeightChanged() { propagateSizeHint("preferredHeight"); } void AppletQuickItemPrivate::maximumWidthChanged() { propagateSizeHint("maximumWidth"); } void AppletQuickItemPrivate::maximumHeightChanged() { propagateSizeHint("maximumHeight"); } void AppletQuickItemPrivate::fillWidthChanged() { propagateSizeHint("fillWidth"); } void AppletQuickItemPrivate::fillHeightChanged() { propagateSizeHint("fillHeight"); } AppletQuickItem::AppletQuickItem(Plasma::Applet *applet, QQuickItem *parent) : QQuickItem(parent), d(new AppletQuickItemPrivate(applet, this)) { d->init(); if (d->applet) { d->appletPackage = d->applet->kPackage(); if (d->applet->containment()) { if (d->applet->containment()->corona()) { d->coronaPackage = d->applet->containment()->corona()->kPackage(); } d->containmentPackage = d->applet->containment()->kPackage(); } if (d->applet->pluginMetaData().isValid()) { const QString rootPath = d->applet->pluginMetaData().value(QStringLiteral("X-Plasma-RootPath")); if (!rootPath.isEmpty()) { d->qmlObject->setTranslationDomain(QLatin1String("plasma_applet_") + rootPath); } else { d->qmlObject->setTranslationDomain(QLatin1String("plasma_applet_") + d->applet->pluginMetaData().pluginId()); } } // set the graphicObject dynamic property on applet d->applet->setProperty("_plasma_graphicObject", QVariant::fromValue(this)); } d->qmlObject->setInitializationDelayed(true); setProperty("_plasma_applet", QVariant::fromValue(d->applet)); } AppletQuickItem::~AppletQuickItem() { //decrease weight if (d->s_preloadPolicy >= AppletQuickItemPrivate::Adaptive) { d->applet->config().writeEntry(QStringLiteral("PreloadWeight"), qMax(0, d->preloadWeight() - AppletQuickItemPrivate::PreloadWeightDecrement)); } //Here the order is important delete d->compactRepresentationItem; delete d->fullRepresentationItem; delete d->compactRepresentationExpanderItem; AppletQuickItemPrivate::s_rootObjects.remove(d->qmlObject->rootContext()); delete d; } AppletQuickItem *AppletQuickItem::qmlAttachedProperties(QObject *object) { QQmlContext *context; //is it using shared engine mode? if (!QtQml::qmlEngine(object)->parent()) { context = QtQml::qmlContext(object); //search the root context of the applet in which the object is in while (context) { //the rootcontext of an applet is a child of the engine root context if (context->parentContext() == QtQml::qmlEngine(object)->rootContext()) { break; } context = context->parentContext(); } //otherwise index by root context } else { context = QtQml::qmlEngine(object)->rootContext(); } //at the moment of the attached object creation, the root item is the only one that hasn't a parent //only way to avoid creation of this attached for everybody but the root item if (!object->parent() && AppletQuickItemPrivate::s_rootObjects.contains(context)) { return AppletQuickItemPrivate::s_rootObjects.value(context); } else { return nullptr; } } Plasma::Applet *AppletQuickItem::applet() const { return d->applet; } void AppletQuickItem::init() { //FIXME: Plasmoid attached property should be fixed since can't be indexed by engine anymore if (AppletQuickItemPrivate::s_rootObjects.contains(d->qmlObject->rootContext())) { return; } AppletQuickItemPrivate::s_rootObjects[d->qmlObject->rootContext()] = this; Q_ASSERT(d->applet); //Initialize the main QML file QQmlEngine *engine = d->qmlObject->engine(); //if the engine of the qmlObject is different from the static one, then we //are using an old version of the api in which every applet had one engine //so initialize a private url interceptor if (d->applet->kPackage().isValid() && !qobject_cast(d->qmlObject)) { PackageUrlInterceptor *interceptor = new PackageUrlInterceptor(engine, d->applet->kPackage()); interceptor->addAllowedPath(d->coronaPackage.path()); engine->setUrlInterceptor(interceptor); } //Force QtQuickControls to use the "Plasma" style for this engine. //this way is possible to mix QtQuickControls and plasma components in applets //while still having the desktop style in configuration dialogs if (!engine->property("_plasma_qqc_style_set").toBool()) { QQmlComponent c(engine); c.setData(QByteArrayLiteral("import QtQuick 2.1\n\ import QtQuick.Controls 1.0\n\ import QtQuick.Controls.Private 1.0\n \ QtObject {\ Component.onCompleted: {\ Settings.styleName = \"Plasma\";\ }\ }"), QUrl()); QObject *o = c.create(); o->deleteLater(); engine->setProperty(("_plasma_qqc_style_set"), true); } d->qmlObject->setSource(d->applet->kPackage().fileUrl("mainscript")); if (!engine || !engine->rootContext() || !engine->rootContext()->isValid() || !d->qmlObject->mainComponent() || d->qmlObject->mainComponent()->isError() || d->applet->failedToLaunch()) { QString reason; if (d->applet->failedToLaunch()) { reason = d->applet->launchErrorMessage(); } else if (d->applet->kPackage().isValid()) { - foreach (QQmlError error, d->qmlObject->mainComponent()->errors()) { + const auto errors = d->qmlObject->mainComponent()->errors(); + for (QQmlError error : errors) { reason += error.toString() + QLatin1Char('\n'); } reason = i18n("Error loading QML file: %1", reason); } else { reason = i18n("Error loading Applet: package inexistent. %1", applet()->launchErrorMessage()); } d->qmlObject->setSource(d->coronaPackage.fileUrl("appleterror")); d->qmlObject->completeInitialization(); //even the error message QML may fail if (d->qmlObject->mainComponent()->isError()) { return; } else { d->qmlObject->rootObject()->setProperty("reason", reason); } d->applet->setLaunchErrorMessage(reason); } d->qmlObject->rootContext()->setContextProperty(QStringLiteral("plasmoid"), this); //initialize size, so an useless resize less QVariantHash initialProperties; //initialize with our size only if valid if (width() > 0 && height() > 0) { const qreal w = parentItem() ? std::min(parentItem()->width(), width()) : width(); const qreal h = parentItem() ? std::min(parentItem()->height(), height()) : height(); initialProperties[QStringLiteral("width")] = w; initialProperties[QStringLiteral("height")] = h; } d->qmlObject->setInitializationDelayed(false); d->qmlObject->completeInitialization(initialProperties); //otherwise, initialize our size to root object's size if (d->qmlObject->rootObject() && (width() <= 0 || height() <= 0)) { const qreal w = d->qmlObject->rootObject()->property("width").value(); const qreal h = d->qmlObject->rootObject()->property("height").value(); setSize(parentItem() ? QSizeF(std::min(parentItem()->width(), w), std::min(parentItem()->height(), h)) : QSizeF(w, h)); } //default fullrepresentation is our root main component, if none specified if (!d->fullRepresentation) { d->fullRepresentation = d->qmlObject->mainComponent(); d->fullRepresentationItem = qobject_cast(d->qmlObject->rootObject()); emit fullRepresentationChanged(d->fullRepresentation); } //default compactRepresentation is a simple icon provided by the shell package if (!d->compactRepresentation) { d->compactRepresentation = new QQmlComponent(engine, this); d->compactRepresentation->loadUrl(d->coronaPackage.fileUrl("defaultcompactrepresentation")); emit compactRepresentationChanged(d->compactRepresentation); } //default compactRepresentationExpander is the popup in which fullRepresentation goes if (!d->compactRepresentationExpander) { d->compactRepresentationExpander = new QQmlComponent(engine, this); QUrl compactExpanderUrl = d->containmentPackage.fileUrl("compactapplet"); if (compactExpanderUrl.isEmpty()) { compactExpanderUrl = d->coronaPackage.fileUrl("compactapplet"); } d->compactRepresentationExpander->loadUrl(compactExpanderUrl); } d->initComplete = true; d->compactRepresentationCheck(); qmlObject()->engine()->rootContext()->setBaseUrl(qmlObject()->source()); qmlObject()->engine()->setContextForObject(this, qmlObject()->engine()->rootContext()); //if we're expanded we don't care about preloading because it will already be the case //as well as for containments if (d->applet->isContainment() || d->expanded || d->preferredRepresentation == d->fullRepresentation) { return; } if (!d->applet->isContainment() && d->applet->containment()) { connect(d->applet->containment(), &Plasma::Containment::uiReadyChanged, this, [this](bool uiReady) { if (uiReady && d->s_preloadPolicy >= AppletQuickItemPrivate::Adaptive) { const int preloadWeight = d->preloadWeight(); qCDebug(LOG_PLASMAQUICK) << "New Applet " << d->applet->title() << "with a weight of" << preloadWeight; //don't preload applets less then a certain weight if (d->s_preloadPolicy >= AppletQuickItemPrivate::Aggressive || preloadWeight >= AppletQuickItemPrivate::DelayedPreloadWeight) { //spread the creation over a random delay to make it look //plasma started already, and load the popup in the background //without big noticeable freezes, the bigger the weight the smaller is likely //to be the delay, smaller minimum walue, smaller spread const int min = (100 - preloadWeight) * 20; const int max = (100 - preloadWeight) * 100; const int delay = QRandomGenerator::global()->bounded((max + 1) - min) + min; QTimer::singleShot(delay, this, [this, delay]() { qCDebug(LOG_PLASMAQUICK) << "Delayed preload of " << d->applet->title() << "after" << (qreal)delay/1000 << "seconds"; d->preloadForExpansion(); }); } } }); } } Plasma::Package AppletQuickItem::appletPackage() const { return Plasma::Package(d->appletPackage); } void AppletQuickItem::setAppletPackage(const Plasma::Package &package) { d->appletPackage = package.kPackage(); } Plasma::Package AppletQuickItem::coronaPackage() const { return Plasma::Package(d->coronaPackage); } void AppletQuickItem::setCoronaPackage(const Plasma::Package &package) { d->coronaPackage = package.kPackage(); } int AppletQuickItem::switchWidth() const { return d->switchWidth; } void AppletQuickItem::setSwitchWidth(int width) { if (d->switchWidth == width) { return; } d->switchWidth = width; d->compactRepresentationCheck(); emit switchWidthChanged(width); } int AppletQuickItem::switchHeight() const { return d->switchHeight; } void AppletQuickItem::setSwitchHeight(int height) { if (d->switchHeight == height) { return; } d->switchHeight = height; d->compactRepresentationCheck(); emit switchHeightChanged(height); } QQmlComponent *AppletQuickItem::compactRepresentation() { return d->compactRepresentation; } void AppletQuickItem::setCompactRepresentation(QQmlComponent *component) { if (d->compactRepresentation == component) { return; } d->compactRepresentation = component; emit compactRepresentationChanged(component); } QQmlComponent *AppletQuickItem::fullRepresentation() { return d->fullRepresentation; } QObject *AppletQuickItem::testItem() { if (!d->testItem) { const QUrl url(d->appletPackage.fileUrl("test")); if (url.isEmpty()) { return nullptr; } d->testItem = d->qmlObject->createObjectFromSource(url, QtQml::qmlContext(rootItem())); if (d->testItem) { d->testItem->setProperty("plasmoidItem", QVariant::fromValue(this)); } } return d->testItem; } void AppletQuickItem::setFullRepresentation(QQmlComponent *component) { if (d->fullRepresentation == component) { return; } d->fullRepresentation = component; emit fullRepresentationChanged(component); } QQmlComponent *AppletQuickItem::preferredRepresentation() { return d->preferredRepresentation; } void AppletQuickItem::setPreferredRepresentation(QQmlComponent *component) { if (d->preferredRepresentation == component) { return; } d->preferredRepresentation = component; emit preferredRepresentationChanged(component); d->compactRepresentationCheck(); } bool AppletQuickItem::isExpanded() const { return d->expanded; } void AppletQuickItem::setExpanded(bool expanded) { if (d->expanded == expanded) { return; } if (expanded) { d->preloadForExpansion(); //increase on open, ignore containments if (d->s_preloadPolicy >= AppletQuickItemPrivate::Adaptive && !d->applet->isContainment()) { const int newWeight = qMin(d->preloadWeight() + AppletQuickItemPrivate::PreloadWeightIncrement, 100); d->applet->config().writeEntry(QStringLiteral("PreloadWeight"), newWeight); qCDebug(LOG_PLASMAQUICK) << "Increasing score for" << d->applet->title() << "to" << newWeight; } } d->expanded = expanded; emit expandedChanged(expanded); } bool AppletQuickItem::isActivationTogglesExpanded() const { return d->activationTogglesExpanded; } void AppletQuickItem::setActivationTogglesExpanded(bool activationTogglesExpanded) { if (d->activationTogglesExpanded == activationTogglesExpanded) { return; } d->activationTogglesExpanded = activationTogglesExpanded; emit activationTogglesExpandedChanged(activationTogglesExpanded); } ////////////Internals KDeclarative::QmlObject *AppletQuickItem::qmlObject() { return d->qmlObject; } QQuickItem *AppletQuickItem::compactRepresentationItem() { return d->compactRepresentationItem; } QQuickItem *AppletQuickItem::fullRepresentationItem() { return d->fullRepresentationItem; } QObject *AppletQuickItem::rootItem() { return d->qmlObject->rootObject(); } void AppletQuickItem::childEvent(QChildEvent *event) { // Added child may be QQuickLayoutAttached if (event->added() && !d->ownLayout && d->currentRepresentationItem) { // Child has not yet finished initialization at this point QTimer::singleShot(0, this, [this]() { if (!d->ownLayout) { d->connectLayoutAttached(d->currentRepresentationItem); } }); } QQuickItem::childEvent(event); } void AppletQuickItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) { Q_UNUSED(oldGeometry) QQuickItem::geometryChanged(newGeometry, oldGeometry); d->compactRepresentationCheck(); } void AppletQuickItem::itemChange(ItemChange change, const ItemChangeData &value) { if (change == QQuickItem::ItemSceneChange) { //we have a window: create the representations if needed if (value.window) { init(); } } QQuickItem::itemChange(change, value); } } #include "moc_appletquickitem.cpp" diff --git a/src/plasmaquick/configview.cpp b/src/plasmaquick/configview.cpp index c5e2228ef..c0a4b113a 100644 --- a/src/plasmaquick/configview.cpp +++ b/src/plasmaquick/configview.cpp @@ -1,362 +1,363 @@ /* * Copyright 2013 Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "private/configcategory_p.h" #include "configview.h" #include "configmodel.h" #include "Plasma/Applet" #include "Plasma/Containment" //#include "plasmoid/wallpaperinterface.h" #include "kdeclarative/configpropertymap.h" #include #include #include #include #include #include #include #include #include #include #include #include #include //Unfortunately QWINDOWSIZE_MAX is not exported #define DIALOGSIZE_MAX ((1<<24)-1) namespace PlasmaQuick { //////////////////////////////ConfigView class ConfigViewPrivate { public: ConfigViewPrivate(Plasma::Applet *appl, ConfigView *view); ~ConfigViewPrivate() = default; void init(); void updateMinimumWidth(); void updateMinimumHeight(); void updateMaximumWidth(); void updateMaximumHeight(); void mainItemLoaded(); ConfigView *q; QPointer applet; ConfigModel *configModel; ConfigModel *kcmConfigModel; Plasma::Corona *corona; //Attached Layout property of mainItem, if any QPointer mainItemLayout; }; ConfigViewPrivate::ConfigViewPrivate(Plasma::Applet *appl, ConfigView *view) : q(view), applet(appl), corona(nullptr) { } void ConfigViewPrivate::init() { if (!applet) { qWarning() << "Null applet passed to constructor"; return; } if (!applet.data()->pluginMetaData().isValid()) { qWarning() << "Invalid applet passed to constructor"; return; } applet.data()->setUserConfiguring(true); KDeclarative::KDeclarative kdeclarative; kdeclarative.setDeclarativeEngine(q->engine()); const QString rootPath = applet.data()->pluginMetaData().value(QStringLiteral("X-Plasma-RootPath")); if (!rootPath.isEmpty()) { kdeclarative.setTranslationDomain(QStringLiteral("plasma_applet_") + rootPath); } else { kdeclarative.setTranslationDomain(QStringLiteral("plasma_applet_") + applet.data()->pluginMetaData().pluginId()); } kdeclarative.setupContext(); KDeclarative::KDeclarative::setupEngine(q->engine()); // ### how to make sure to do this only once per engine? //FIXME: problem on nvidia, all windows should be transparent or won't show q->setColor(Qt::transparent); q->setTitle(i18n("%1 Settings", applet.data()->title())); //systray case if (!applet.data()->containment()->corona()) { Plasma::Applet *a = qobject_cast(applet.data()->containment()->parent()); if (a) { corona = a->containment()->corona(); } } else if (!applet.data()->containment()->corona()->kPackage().isValid()) { qWarning() << "Invalid home screen package"; } else { corona = applet.data()->containment()->corona(); } if (!corona) { qWarning() << "Cannot find a Corona, this should never happen!"; return; } const auto pkg = corona->kPackage(); if (pkg.isValid()) { PackageUrlInterceptor *interceptor = new PackageUrlInterceptor(q->engine(), pkg); interceptor->addAllowedPath(applet.data()->kPackage().path()); q->engine()->setUrlInterceptor(interceptor); new QQmlFileSelector(q->engine(), q->engine()); } q->setResizeMode(QQuickView::SizeViewToRootObject); auto plasmoid = applet.data()->property("_plasma_graphicObject").value(); q->engine()->rootContext()->setContextProperties({QQmlContext::PropertyPair{QStringLiteral("plasmoid"), QVariant::fromValue(plasmoid)}, QQmlContext::PropertyPair{QStringLiteral("configDialog"), QVariant::fromValue(q)}}); //config model local of the applet QQmlComponent *component = new QQmlComponent(q->engine(), applet.data()->kPackage().fileUrl("configmodel"), q); QObject *object = component->create(q->engine()->rootContext()); configModel = qobject_cast(object); if (configModel) { configModel->setApplet(applet.data()); } else { delete object; } QStringList kcms = KPluginMetaData::readStringList(applet.data()->pluginMetaData().rawData(), QStringLiteral("X-Plasma-ConfigPlugins")); // filter out non-authorized KCMs // KAuthorized expects KCMs with .desktop suffix, so we can't just pass everything // to KAuthorized::authorizeControlModules verbatim kcms.erase(std::remove_if(kcms.begin(), kcms.end(), [](const QString &kcm) { return !KAuthorized::authorizeControlModule(kcm + QLatin1String(".desktop")); }), kcms.end()); if (!kcms.isEmpty()) { if (!configModel) { configModel = new ConfigModel(q); } - foreach (const QString &kcm, kcms) { + for (const QString &kcm : qAsConst(kcms)) { KPluginLoader loader(KPluginLoader::findPlugin(QLatin1String("kcms/") + kcm)); KPluginMetaData md(loader.fileName()); if (!md.isValid()) { qWarning() << "Could not find" << kcm << "specified in X-Plasma-ConfigPlugins"; continue; } configModel->appendCategory(md.iconName(), md.name(), QString(), loader.fileName()); } } delete component; } void ConfigViewPrivate::updateMinimumWidth() { if (mainItemLayout) { q->setMinimumWidth(mainItemLayout.data()->property("minimumWidth").toInt()); //Sometimes setMinimumWidth doesn't actually resize: Qt bug? q->setWidth(qMax(q->width(), q->minimumWidth())); } else { q->setMinimumWidth(-1); } } void ConfigViewPrivate::updateMinimumHeight() { if (mainItemLayout) { q->setMinimumHeight(mainItemLayout.data()->property("minimumHeight").toInt()); //Sometimes setMinimumHeight doesn't actually resize: Qt bug? q->setHeight(qMax(q->height(), q->minimumHeight())); } else { q->setMinimumHeight(-1); } } void ConfigViewPrivate::updateMaximumWidth() { if (mainItemLayout) { const int hint = mainItemLayout.data()->property("maximumWidth").toInt(); if (hint > 0) { q->setMaximumWidth(hint); } else { q->setMaximumWidth(DIALOGSIZE_MAX); } } else { q->setMaximumWidth(DIALOGSIZE_MAX); } } void ConfigViewPrivate::updateMaximumHeight() { if (mainItemLayout) { const int hint = mainItemLayout.data()->property("maximumHeight").toInt(); if (hint > 0) { q->setMaximumHeight(hint); } else { q->setMaximumHeight(DIALOGSIZE_MAX); } } else { q->setMaximumHeight(DIALOGSIZE_MAX); } } void ConfigViewPrivate::mainItemLoaded() { if (applet) { KConfigGroup cg = applet.data()->config(); cg = KConfigGroup(&cg, "ConfigDialog"); q->resize(cg.readEntry("DialogWidth", q->width()), cg.readEntry("DialogHeight", q->height())); } //Extract the representation's Layout, if any QObject *layout = nullptr; //Search a child that has the needed Layout properties //HACK: here we are not type safe, but is the only way to access to a pointer of Layout - foreach (QObject *child, q->rootObject()->children()) { + const auto children = q->rootObject()->children(); + for (QObject *child : children) { //find for the needed property of Layout: minimum/maximum/preferred sizes and fillWidth/fillHeight if (child->property("minimumWidth").isValid() && child->property("minimumHeight").isValid() && child->property("preferredWidth").isValid() && child->property("preferredHeight").isValid() && child->property("maximumWidth").isValid() && child->property("maximumHeight").isValid() && child->property("fillWidth").isValid() && child->property("fillHeight").isValid() ) { layout = child; break; } } mainItemLayout = layout; if (layout) { QObject::connect(layout, SIGNAL(minimumWidthChanged()), q, SLOT(updateMinimumWidth())); QObject::connect(layout, SIGNAL(minimumHeightChanged()), q, SLOT(updateMinimumHeight())); QObject::connect(layout, SIGNAL(maximumWidthChanged()), q, SLOT(updateMaximumWidth())); QObject::connect(layout, SIGNAL(maximumHeightChanged()), q, SLOT(updateMaximumHeight())); updateMinimumWidth(); updateMinimumHeight(); updateMaximumWidth(); updateMaximumHeight(); } } ConfigView::ConfigView(Plasma::Applet *applet, QWindow *parent) : QQuickView(parent), d(new ConfigViewPrivate(applet, this)) { setIcon(QIcon::fromTheme(QStringLiteral("configure"))); qmlRegisterType("org.kde.plasma.configuration", 2, 0, "ConfigModel"); qmlRegisterType("org.kde.plasma.configuration", 2, 0, "ConfigCategory"); d->init(); connect(applet, &QObject::destroyed, this, &ConfigView::close); connect(this, &QQuickView::statusChanged, [=](QQuickView::Status status) { if (status == QQuickView::Ready) { d->mainItemLoaded(); } }); } ConfigView::~ConfigView() { if (d->applet) { d->applet.data()->setUserConfiguring(false); if (d->applet.data()->containment() && d->applet.data()->containment()->corona()) { d->applet.data()->containment()->corona()->requestConfigSync(); } } } void ConfigView::init() { setSource(d->corona->kPackage().fileUrl("appletconfigurationui")); } Plasma::Applet *ConfigView::applet() { return d->applet.data(); } ConfigModel *ConfigView::configModel() const { return d->configModel; } QString ConfigView::appletGlobalShortcut() const { if (!d->applet) { return QString(); } return d->applet.data()->globalShortcut().toString(); } void ConfigView::setAppletGlobalShortcut(const QString &shortcut) { if (!d->applet || d->applet.data()->globalShortcut().toString().toLower() == shortcut.toLower()) { return; } d->applet.data()->setGlobalShortcut(shortcut); emit appletGlobalShortcutChanged(); } //To emulate Qt::WA_DeleteOnClose that QWindow doesn't have void ConfigView::hideEvent(QHideEvent *ev) { QQuickWindow::hideEvent(ev); deleteLater(); } void ConfigView::resizeEvent(QResizeEvent *re) { if (!rootObject()) { return; } rootObject()->setSize(re->size()); if (d->applet) { KConfigGroup cg = d->applet.data()->config(); cg = KConfigGroup(&cg, "ConfigDialog"); cg.writeEntry("DialogWidth", re->size().width()); cg.writeEntry("DialogHeight", re->size().height()); } QQuickWindow::resizeEvent(re); } } #include "moc_configview.cpp" diff --git a/src/plasmaquick/dialog.cpp b/src/plasmaquick/dialog.cpp index cc4863a2d..f69ee1b9a 100644 --- a/src/plasmaquick/dialog.cpp +++ b/src/plasmaquick/dialog.cpp @@ -1,1455 +1,1457 @@ /*************************************************************************** * Copyright 2011 Marco Martin * * Copyright 2013 Sebastian Kügler * * Copyright 2014 Martin Gräßlin * * Copyright 2014 Vishesh Handa * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ #include "dialog.h" #include "config-plasma.h" #include "../declarativeimports/core/framesvgitem.h" #include "dialogshadows_p.h" #include "view.h" #include "configview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if HAVE_KWAYLAND #include "waylandintegration_p.h" #include #include #endif #if HAVE_XCB_SHAPE #include #include #endif #if HAVE_X11 #include #endif //Unfortunately QWINDOWSIZE_MAX is not exported #define DIALOGSIZE_MAX ((1<<24)-1) namespace PlasmaQuick { class DialogPrivate { public: DialogPrivate(Dialog *dialog) : q(dialog), location(Plasma::Types::BottomEdge), frameSvgItem(nullptr), hasMask(false), type(Dialog::Normal), hideOnWindowDeactivate(false), outputOnly(false), visible(false), componentComplete(dialog->parent() == nullptr), backgroundHints(Dialog::StandardBackground) { hintsCommitTimer.setSingleShot(true); hintsCommitTimer.setInterval(0); QObject::connect(&hintsCommitTimer, SIGNAL(timeout()), q, SLOT(updateLayoutParameters())); } void updateInputShape(); //SLOTS /** * Sync Borders updates the enabled borders of the frameSvgItem depending * on the geometry of the window. * * \param windowGeometry The window geometry which should be taken into * consideration when activating/deactivating certain borders */ void syncBorders(const QRect& windowGeometry); /** * This function sets the blurBehind, background contrast and shadows. It * does so wrt the frameSvgItem. So make sure the frameSvgItem is the * correct size before calling this function. */ void updateTheme(); void updateVisibility(bool visible); void updateMinimumWidth(); void updateMinimumHeight(); void updateMaximumWidth(); void updateMaximumHeight(); /** * Gets the maximum and minimum size hints for the window based on the contents. it doesn't actually resize anything */ void getSizeHints(QSize &min, QSize &max) const; /** * This function is an optimized version of updateMaximumHeight, * updateMaximumWidth,updateMinimumWidth and updateMinimumHeight. * It should be called when you need to call all 4 of these functions * AND you have called syncToMainItemSize before. */ void updateLayoutParameters(); QRect availableScreenGeometryForPosition(const QPoint& pos) const; /** * This function checks the current position of the dialog and repositions * it so that no part of it is not on the screen */ void repositionIfOffScreen(); void slotMainItemSizeChanged(); void slotWindowPositionChanged(); void syncToMainItemSize(); bool mainItemContainsPosition(const QPointF &point) const; QPointF positionAdjustedForMainItem(const QPointF &point) const; void setupWaylandIntegration(); void applyType(); Dialog *q; Plasma::Types::Location location; Plasma::FrameSvgItem *frameSvgItem; QPointer mainItem; QPointer visualParent; QTimer hintsCommitTimer; #if HAVE_KWAYLAND QPointer shellSurface; #endif QRect cachedGeometry; bool hasMask; Dialog::WindowType type; bool hideOnWindowDeactivate; bool outputOnly; bool visible; Plasma::Theme theme; bool componentComplete; Dialog::BackgroundHints backgroundHints; //Attached Layout property of mainItem, if any QPointer mainItemLayout; }; QRect DialogPrivate::availableScreenGeometryForPosition(const QPoint& pos) const { // FIXME: QWindow::screen() never ever changes if the window is moved across // virtual screens (normal two screens with X), this seems to be intentional // as it's explicitly mentioned in the docs. Until that's changed or some // more proper way of howto get the current QScreen for given QWindow is found, // we simply iterate over the virtual screens and pick the one our QWindow // says it's at. QRect avail; - Q_FOREACH (QScreen *screen, QGuiApplication::screens()) { + const auto screens = QGuiApplication::screens(); + for (QScreen *screen : screens) { //we check geometry() but then take availableGeometry() //to reliably check in which screen a position is, we need the full //geometry, including areas for panels if (screen->geometry().contains(pos)) { avail = screen->availableGeometry(); break; } } /* * if the heuristic fails (because the topleft of the dialog is offscreen) * use at least our screen() * the screen should be correctly updated now on Qt 5.3+ so should be * more reliable anyways (could be tried to remove the whole Q_FOREACH * at this point) * * important: screen can be a nullptr... see bug 345173 */ if (avail.isEmpty() && q->screen()) { avail = q->screen()->availableGeometry(); } return avail; } void DialogPrivate::syncBorders(const QRect& geom) { QRect avail = availableScreenGeometryForPosition(geom.topLeft()); int borders = Plasma::FrameSvg::AllBorders; //Tooltips always have all the borders // floating windows have all borders if (!q->flags().testFlag(Qt::ToolTip) && location != Plasma::Types::Floating) { if (geom.x() <= avail.x() || location == Plasma::Types::LeftEdge) { borders = borders & ~Plasma::FrameSvg::LeftBorder; } if (geom.y() <= avail.y() || location == Plasma::Types::TopEdge) { borders = borders & ~Plasma::FrameSvg::TopBorder; } if (avail.right() <= geom.x() + geom.width() || location == Plasma::Types::RightEdge) { borders = borders & ~Plasma::FrameSvg::RightBorder; } if (avail.bottom() <= geom.y() + geom.height() || location == Plasma::Types::BottomEdge) { borders = borders & ~Plasma::FrameSvg::BottomBorder; } } if (frameSvgItem->enabledBorders() != (Plasma::FrameSvg::EnabledBorder)borders) { frameSvgItem->setEnabledBorders((Plasma::FrameSvg::EnabledBorder)borders); } } void DialogPrivate::updateTheme() { if (backgroundHints == Dialog::NoBackground) { frameSvgItem->setImagePath(QString()); KWindowEffects::enableBlurBehind(q->winId(), false); KWindowEffects::enableBackgroundContrast(q->winId(), false); q->setMask(QRegion()); DialogShadows::self()->removeWindow(q); } else { if (type == Dialog::Tooltip) { frameSvgItem->setImagePath(QStringLiteral("widgets/tooltip")); } else { frameSvgItem->setImagePath(QStringLiteral("dialogs/background")); } KWindowEffects::enableBlurBehind(q->winId(), theme.blurBehindEnabled(), frameSvgItem->mask()); KWindowEffects::enableBackgroundContrast(q->winId(), theme.backgroundContrastEnabled(), theme.backgroundContrast(), theme.backgroundIntensity(), theme.backgroundSaturation(), frameSvgItem->mask()); if (KWindowSystem::compositingActive()) { if (hasMask) { hasMask = false; q->setMask(QRegion()); } } else { hasMask = true; q->setMask(frameSvgItem->mask()); } if (q->isVisible()) { DialogShadows::self()->addWindow(q, frameSvgItem->enabledBorders()); } } updateInputShape(); } void DialogPrivate::updateVisibility(bool visible) { if (visible) { if (visualParent && visualParent->window()) { q->setTransientParent(visualParent->window()); } if (q->location() == Plasma::Types::FullScreen) { frameSvgItem->setEnabledBorders(Plasma::FrameSvg::NoBorder); // We cache the original size of the item, to retrieve it // when the dialog is switched back from fullscreen. if (q->geometry() != q->screen()->availableGeometry()) { cachedGeometry = q->geometry(); } q->setGeometry(q->screen()->availableGeometry()); } else { if (!cachedGeometry.isNull()) { q->resize(cachedGeometry.size()); slotWindowPositionChanged(); if (visualParent) { q->setPosition(q->popupPosition(visualParent, q->size())); } cachedGeometry = QRect(); } if (mainItem) { syncToMainItemSize(); } if (mainItemLayout) { updateLayoutParameters(); } #if HAVE_KWAYLAND //if is a wayland window that was hidden, we need //to set its position again as there won't be any move event to sync QWindow::position and shellsurface::position if (shellSurface && type != Dialog::OnScreenDisplay) { shellSurface->setPosition(q->position()); } #endif } } if (!q->flags().testFlag(Qt::ToolTip) && type != Dialog::Notification && type != Dialog::CriticalNotification) { KWindowEffects::SlideFromLocation slideLocation = KWindowEffects::NoEdge; switch (location) { case Plasma::Types::TopEdge: slideLocation = KWindowEffects::TopEdge; break; case Plasma::Types::LeftEdge: slideLocation = KWindowEffects::LeftEdge; break; case Plasma::Types::RightEdge: slideLocation = KWindowEffects::RightEdge; break; case Plasma::Types::BottomEdge: slideLocation = KWindowEffects::BottomEdge; break; //no edge, no slide default: break; } KWindowEffects::slideWindow(q->winId(), slideLocation, -1); } if (visible) { q->raise(); applyType(); } } void DialogPrivate::updateMinimumWidth() { Q_ASSERT(mainItem); Q_ASSERT(mainItemLayout); if (!componentComplete) { return; } q->setMinimumWidth(0); //this is to try to get the internal item resized a tad before, but //the flicker almost always happen anyways, so is *probably* useless //this other kind of flicker is the view not being always focused exactly //on the scene auto margin = frameSvgItem->fixedMargins(); int minimumWidth = mainItemLayout->property("minimumWidth").toInt() + margin->left() + margin->right(); if (q->screen()) { minimumWidth = qMin(q->screen()->availableGeometry().width(), minimumWidth); } q->contentItem()->setWidth(qMax(q->width(), minimumWidth)); q->setWidth(qMax(q->width(), minimumWidth)); hintsCommitTimer.start(); } void DialogPrivate::updateMinimumHeight() { Q_ASSERT(mainItem); Q_ASSERT(mainItemLayout); if (!componentComplete) { return; } q->setMinimumHeight(0); //this is to try to get the internal item resized a tad before, but //the flicker almost always happen anyways, so is *probably* useless //this other kind of flicker is the view not being always focused exactly //on the scene auto margin = frameSvgItem->fixedMargins(); int minimumHeight = mainItemLayout->property("minimumHeight").toInt() + margin->top() + margin->bottom(); if (q->screen()) { minimumHeight = qMin(q->screen()->availableGeometry().height(), minimumHeight); } q->contentItem()->setHeight(qMax(q->height(), minimumHeight)); q->setHeight(qMax(q->height(), minimumHeight)); hintsCommitTimer.start(); } void DialogPrivate::updateMaximumWidth() { Q_ASSERT(mainItem); Q_ASSERT(mainItemLayout); if (!componentComplete) { return; } q->setMaximumWidth(DIALOGSIZE_MAX); auto margin = frameSvgItem->fixedMargins(); int maximumWidth = mainItemLayout->property("maximumWidth").toInt() + margin->left() + margin->right(); if (q->screen()) { maximumWidth = qMin(q->screen()->availableGeometry().width(), maximumWidth); } q->contentItem()->setWidth(qMax(q->width(), maximumWidth)); q->setWidth(qMax(q->width(), maximumWidth)); hintsCommitTimer.start(); } void DialogPrivate::updateMaximumHeight() { Q_ASSERT(mainItem); Q_ASSERT(mainItemLayout); if (!componentComplete) { return; } q->setMaximumHeight(DIALOGSIZE_MAX); auto margin = frameSvgItem->fixedMargins(); int maximumHeight = mainItemLayout->property("maximumHeight").toInt() + margin->top() + margin->bottom(); if (q->screen()) { maximumHeight = qMin(q->screen()->availableGeometry().height(), maximumHeight); } q->contentItem()->setHeight(qMax(q->height(), maximumHeight)); q->setHeight(qMin(q->height(), maximumHeight)); hintsCommitTimer.start(); } void DialogPrivate::getSizeHints(QSize &min, QSize &max) const { if (!componentComplete || !mainItem || !mainItemLayout) { return; } Q_ASSERT(mainItem); Q_ASSERT(mainItemLayout); auto margin = frameSvgItem->fixedMargins(); int minimumHeight = mainItemLayout->property("minimumHeight").toInt(); int maximumHeight = mainItemLayout->property("maximumHeight").toInt(); maximumHeight = maximumHeight ? maximumHeight : DIALOGSIZE_MAX; int minimumWidth = mainItemLayout->property("minimumWidth").toInt(); int maximumWidth = mainItemLayout->property("maximumWidth").toInt(); maximumWidth = maximumWidth ? maximumWidth : DIALOGSIZE_MAX; minimumHeight += margin->top() + margin->bottom(); maximumHeight += margin->top() + margin->bottom(); minimumWidth += margin->left() + margin->right(); maximumWidth += margin->left() + margin->right(); if (q->screen()) { minimumWidth = qMin(q->screen()->availableGeometry().width(), minimumWidth); minimumHeight = qMin(q->screen()->availableGeometry().height(), minimumHeight); maximumWidth = qMin(q->screen()->availableGeometry().width(), maximumWidth); maximumHeight = qMin(q->screen()->availableGeometry().height(), maximumHeight); } min = QSize(minimumWidth, minimumHeight); max = QSize(maximumWidth, maximumHeight); } void DialogPrivate::updateLayoutParameters() { if (!componentComplete || !mainItem || !mainItemLayout) { return; } mainItem->disconnect(q); auto margin = frameSvgItem->fixedMargins(); QSize min; QSize max(DIALOGSIZE_MAX, DIALOGSIZE_MAX); getSizeHints(min, max); const QSize finalSize(qBound(min.width(), q->width(), max.width()), qBound(min.height(), q->height(), max.height())); if (visualParent) { //it's important here that we're using re->size() as size, we don't want to do recursive resizeEvents const QRect geom(q->popupPosition(visualParent, finalSize), finalSize); q->adjustGeometry(geom); } else { q->resize(finalSize); } mainItem->setPosition(QPointF(margin->left(), margin->top())); mainItem->setSize(QSizeF(q->width() - margin->left() - margin->right(), q->height() - margin->top() - margin->bottom())); frameSvgItem->setSize(QSizeF(q->width(), q->height())); repositionIfOffScreen(); updateTheme(); //FIXME: this seems to interfere with the geometry change that //sometimes is still going on, causing flicker (this one is two repositions happening in quick succession). //it may have to be delayed further q->setMinimumSize(min); q->setMaximumSize(max); QObject::connect(mainItem, SIGNAL(widthChanged()), q, SLOT(slotMainItemSizeChanged())); QObject::connect(mainItem, SIGNAL(heightChanged()), q, SLOT(slotMainItemSizeChanged())); } void DialogPrivate::repositionIfOffScreen() { if (!componentComplete) { return; } const QRect avail = availableScreenGeometryForPosition(q->position()); int x = q->x(); int y = q->y(); if (x < avail.left()) { x = avail.left(); } else if (x + q->width() > avail.right()) { x = avail.right() - q->width() + 1; } if (y < avail.top()) { y = avail.top(); } else if (y + q->height() > avail.bottom()) { y = avail.bottom() - q->height() + 1; } q->setX(x); q->setY(y); } void DialogPrivate::updateInputShape() { if (!q->isVisible()) { return; } #if HAVE_XCB_SHAPE if (KWindowSystem::isPlatformX11()) { xcb_connection_t *c = QX11Info::connection(); static bool s_shapeExtensionChecked = false; static bool s_shapeAvailable = false; if (!s_shapeExtensionChecked) { xcb_prefetch_extension_data(c, &xcb_shape_id); const xcb_query_extension_reply_t *extension = xcb_get_extension_data(c, &xcb_shape_id); if (extension->present) { // query version auto cookie = xcb_shape_query_version(c); QScopedPointer version(xcb_shape_query_version_reply(c, cookie, nullptr)); if (!version.isNull()) { s_shapeAvailable = (version->major_version * 0x10 + version->minor_version) >= 0x11; } } s_shapeExtensionChecked = true; } if (!s_shapeAvailable) { return; } if (outputOnly) { // set input shape, so that it doesn't accept any input events xcb_shape_rectangles(c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_CLIP_ORDERING_UNSORTED, q->winId(), 0, 0, 0, nullptr); } else { // delete the shape xcb_shape_mask(c, XCB_SHAPE_SO_INTERSECT, XCB_SHAPE_SK_INPUT, q->winId(), 0, 0, XCB_PIXMAP_NONE); } } #endif } void DialogPrivate::syncToMainItemSize() { Q_ASSERT(mainItem); if (!componentComplete) { return; } if (mainItem->width() <= 0 || mainItem->height() <= 0) { qWarning() << "trying to show an empty dialog"; } updateTheme(); if (visualParent) { // fixedMargins will get all the borders, no matter if they are enabled auto margins = frameSvgItem->fixedMargins(); const QSize fullSize = QSize(mainItem->width(), mainItem->height()) + QSize(margins->left() + margins->right(), margins->top() + margins->bottom()); // We get the popup position with the fullsize as we need the popup // position in order to determine our actual size, as the position // determines which borders will be shown. const QRect geom(q->popupPosition(visualParent, fullSize), fullSize); // We're then moving the window to where we think we would be with all // the borders. This way when syncBorders is called, it has a geometry // to work with. syncBorders(geom); } else { syncBorders(q->geometry()); } QSize s = QSize(mainItem->width(), mainItem->height()) + QSize(frameSvgItem->fixedMargins()->left() + frameSvgItem->fixedMargins()->right(), frameSvgItem->fixedMargins()->top() + frameSvgItem->fixedMargins()->bottom()); QSize min; QSize max(DIALOGSIZE_MAX, DIALOGSIZE_MAX); getSizeHints(min, max); s = QSize(qBound(min.width(), s.width(), max.width()), qBound(min.height(), s.height(), max.height())); q->contentItem()->setSize(s); frameSvgItem->setSize(s); if (visualParent) { const QRect geom(q->popupPosition(visualParent, s), s); if (geom == q->geometry()) { return; } q->adjustGeometry(geom); // The borders will instantly be updated but the geometry might take a // while as sub-classes can reimplement adjustGeometry and animate it. syncBorders(geom); } else { q->resize(s); } mainItem->setPosition(QPointF(frameSvgItem->fixedMargins()->left(), frameSvgItem->fixedMargins()->top())); updateTheme(); } void DialogPrivate::slotWindowPositionChanged() { // Tooltips always have all the borders // floating windows have all borders if (!q->isVisible() || q->flags().testFlag(Qt::ToolTip) || location == Plasma::Types::Floating) { return; } syncBorders(q->geometry()); updateTheme(); if (mainItem) { auto margin = frameSvgItem->fixedMargins(); mainItem->setPosition(QPoint(margin->left(), margin->top())); mainItem->setSize(QSize(q->width() - margin->left() - margin->right(), q->height() - margin->top() - margin->bottom())); } } bool DialogPrivate::mainItemContainsPosition(const QPointF &point) const { if (!mainItem) { return false; } return QRectF(mainItem->mapToScene(QPoint(0,0)), QSizeF(mainItem->width(), mainItem->height())).contains(point); } QPointF DialogPrivate::positionAdjustedForMainItem(const QPointF &point) const { if (!mainItem) { return point; } QRectF itemRect(mainItem->mapToScene(QPoint(0,0)), QSizeF(mainItem->width(), mainItem->height())); return QPointF(qBound(itemRect.left(), point.x(), itemRect.right()), qBound(itemRect.top(), point.y(), itemRect.bottom())); } void DialogPrivate::setupWaylandIntegration() { #if HAVE_KWAYLAND if (shellSurface) { // already setup return; } using namespace KWayland::Client; PlasmaShell *interface = WaylandIntegration::self()->waylandPlasmaShell(); if (!interface) { return; } Surface *s = Surface::fromWindow(q); if (!s) { return; } shellSurface = interface->createSurface(s, q); #endif } void DialogPrivate::applyType() { if (type != Dialog::Normal) { /*QXcbWindowFunctions::WmWindowType*/ int wmType = 0; #if HAVE_X11 if (KWindowSystem::isPlatformX11()) { switch (type) { case Dialog::Normal: Q_UNREACHABLE(); break; case Dialog::Dock: wmType = QXcbWindowFunctions::WmWindowType::Dock; break; case Dialog::DialogWindow: wmType = QXcbWindowFunctions::WmWindowType::Dialog; break; case Dialog::PopupMenu: wmType = QXcbWindowFunctions::WmWindowType::PopupMenu; break; case Dialog::Tooltip: wmType = QXcbWindowFunctions::WmWindowType::Tooltip; break; case Dialog::Notification: wmType = QXcbWindowFunctions::WmWindowType::Notification; break; case Dialog::OnScreenDisplay: case Dialog::CriticalNotification: // Not supported by Qt break; } if (wmType) { QXcbWindowFunctions::setWmWindowType(q, static_cast(wmType)); } } #endif if (!wmType) { KWindowSystem::setType(q->winId(), static_cast(type)); } } else { q->setFlags(Qt::FramelessWindowHint | q->flags()); } //an OSD can't be a Dialog, as qt xcb would attempt to set a transient parent for it //see bug 370433 if (type == Dialog::OnScreenDisplay) { q->setFlags((q->flags() & ~Qt::Dialog) | Qt::Window); } if (backgroundHints == Dialog::NoBackground) { frameSvgItem->setImagePath(QString()); } else { if (type == Dialog::Tooltip) { frameSvgItem->setImagePath(QStringLiteral("widgets/tooltip")); } else { frameSvgItem->setImagePath(QStringLiteral("dialogs/background")); } } if (type == Dialog::Dock || type == Dialog::Notification || type == Dialog::OnScreenDisplay || type == Dialog::CriticalNotification) { KWindowSystem::setOnAllDesktops(q->winId(), true); } else { KWindowSystem::setOnAllDesktops(q->winId(), false); } #if HAVE_KWAYLAND if (shellSurface) { shellSurface->setPanelTakesFocus(!q->flags().testFlag(Qt::WindowDoesNotAcceptFocus)); } #endif } Dialog::Dialog(QQuickItem *parent) : QQuickWindow(parent ? parent->window() : nullptr), d(new DialogPrivate(this)) { setClearBeforeRendering(true); setColor(QColor(Qt::transparent)); setFlags(Qt::FramelessWindowHint | Qt::Dialog); connect(this, &QWindow::xChanged, [=]() { d->slotWindowPositionChanged(); }); connect(this, &QWindow::yChanged, [=]() { d->slotWindowPositionChanged(); }); // Given dialogs are skip task bar and don't have a decoration // minimizing them using e.g. "minimize all" should just close them connect(this, &QWindow::windowStateChanged, this, [this](Qt::WindowState newState) { if (newState == Qt::WindowMinimized) { setVisible(false); } }); connect(this, &QWindow::visibleChanged, this, &Dialog::visibleChangedProxy); connect(this, SIGNAL(visibleChanged(bool)), this, SLOT(updateInputShape())); connect(this, SIGNAL(outputOnlyChanged()), this, SLOT(updateInputShape())); //HACK: this property is invoked due to the initialization that gets done to contentItem() in the getter property("data"); //Create the FrameSvg background. d->frameSvgItem = new Plasma::FrameSvgItem(contentItem()); //This is needed as a transition thing for KWayland setProperty("__plasma_frameSvg", QVariant::fromValue(d->frameSvgItem->frameSvg())); connect(&d->theme, SIGNAL(themeChanged()), this, SLOT(updateTheme())); } Dialog::~Dialog() { if (!QCoreApplication::instance()->closingDown()) { DialogShadows::self()->removeWindow(this); } // Prevent signals from super-class destructor invoking our now-destroyed slots disconnect(this, nullptr, this, nullptr); } QQuickItem *Dialog::mainItem() const { return d->mainItem; } void Dialog::setMainItem(QQuickItem *mainItem) { if (d->mainItem != mainItem) { d->hintsCommitTimer.stop(); if (d->mainItem) { disconnect(d->mainItem, nullptr, this, nullptr); d->mainItem->setParentItem(nullptr); } if (d->mainItemLayout) { disconnect(d->mainItemLayout, nullptr, this, nullptr); } d->mainItem = mainItem; if (mainItem) { mainItem->setParentItem(contentItem()); connect(mainItem, SIGNAL(widthChanged()), this, SLOT(slotMainItemSizeChanged())); connect(mainItem, SIGNAL(heightChanged()), this, SLOT(slotMainItemSizeChanged())); d->slotMainItemSizeChanged(); //Extract the representation's Layout, if any QObject *layout = nullptr; setMinimumSize(QSize(0, 0)); setMaximumSize(QSize(DIALOGSIZE_MAX, DIALOGSIZE_MAX)); //Search a child that has the needed Layout properties //HACK: here we are not type safe, but is the only way to access to a pointer of Layout - foreach (QObject *child, mainItem->children()) { + const auto lstChild = mainItem->children(); + for (QObject *child : lstChild) { //find for the needed property of Layout: minimum/maximum/preferred sizes and fillWidth/fillHeight if (child->property("minimumWidth").isValid() && child->property("minimumHeight").isValid() && child->property("preferredWidth").isValid() && child->property("preferredHeight").isValid() && child->property("maximumWidth").isValid() && child->property("maximumHeight").isValid() && child->property("fillWidth").isValid() && child->property("fillHeight").isValid() ) { layout = child; break; } } d->mainItemLayout = layout; if (layout) { //Why queued connections? //we need to be sure that the properties are //already *all* updated when we call the management code connect(layout, SIGNAL(minimumWidthChanged()), this, SLOT(updateMinimumWidth())); connect(layout, SIGNAL(minimumHeightChanged()), this, SLOT(updateMinimumHeight())); connect(layout, SIGNAL(maximumWidthChanged()), this, SLOT(updateMaximumWidth())); connect(layout, SIGNAL(maximumHeightChanged()), this, SLOT(updateMaximumHeight())); d->updateLayoutParameters(); } } //if this is called in Component.onCompleted we have to wait a loop the item is added to a scene emit mainItemChanged(); } } void DialogPrivate::slotMainItemSizeChanged() { syncToMainItemSize(); } QQuickItem *Dialog::visualParent() const { return d->visualParent; } void Dialog::setVisualParent(QQuickItem *visualParent) { if (d->visualParent == visualParent) { return; } d->visualParent = visualParent; emit visualParentChanged(); if (visualParent) { if (visualParent->window()) { setTransientParent(visualParent->window()); } if (d->mainItem) { d->syncToMainItemSize(); } } } QPoint Dialog::popupPosition(QQuickItem *item, const QSize &size) { if (!item) { //If no item was specified try to align at the center of the parent view QQuickItem *parentItem = qobject_cast(parent()); if (parentItem) { QScreen *screen = parentItem->window()->screen(); switch (d->location) { case Plasma::Types::TopEdge: return QPoint(screen->availableGeometry().center().x() - size.width() / 2, screen->availableGeometry().y()); case Plasma::Types::LeftEdge: return QPoint(screen->availableGeometry().x(), screen->availableGeometry().center().y() - size.height() / 2); case Plasma::Types::RightEdge: return QPoint(screen->availableGeometry().right() - size.width(), screen->availableGeometry().center().y() - size.height() / 2); case Plasma::Types::BottomEdge: return QPoint(screen->availableGeometry().center().x() - size.width() / 2, screen->availableGeometry().bottom() - size.height()); //Default center in the screen default: return screen->geometry().center() - QPoint(size.width() / 2, size.height() / 2); } } else { return QPoint(); } } QPointF pos = item->mapToScene(QPointF(0, 0)); if (item->window()) { pos = item->window()->mapToGlobal(pos.toPoint()); } else { return QPoint(); } //if the item is in a dock or in a window that ignores WM we want to position the popups outside of the dock const KWindowInfo winInfo(item->window()->winId(), NET::WMWindowType); const bool outsideParentWindow = ((winInfo.windowType(NET::AllTypesMask) == NET::Dock) || (item->window()->flags() & Qt::X11BypassWindowManagerHint)) && item->window()->mask().isNull(); QRect parentGeometryBounds; if (outsideParentWindow) { parentGeometryBounds = item->window()->geometry(); } else { parentGeometryBounds = item->mapRectToScene(item->boundingRect()).toRect(); if (item->window()) { parentGeometryBounds.moveTopLeft(item->window()->mapToGlobal(parentGeometryBounds.topLeft())); pos = parentGeometryBounds.topLeft(); } } const QPoint topPoint(pos.x() + (item->mapRectToScene(item->boundingRect()).width() - size.width()) / 2, parentGeometryBounds.top() - size.height()); const QPoint bottomPoint(pos.x() + (item->mapRectToScene(item->boundingRect()).width() - size.width()) / 2, parentGeometryBounds.bottom()); const QPoint leftPoint(parentGeometryBounds.left() - size.width(), pos.y() + (item->mapRectToScene(item->boundingRect()).height() - size.height()) / 2); const QPoint rightPoint(parentGeometryBounds.right(), pos.y() + (item->mapRectToScene(item->boundingRect()).height() - size.height()) / 2); QPoint dialogPos; if (d->location == Plasma::Types::TopEdge) { dialogPos = bottomPoint; } else if (d->location == Plasma::Types::LeftEdge) { dialogPos = rightPoint; } else if (d->location == Plasma::Types::RightEdge) { dialogPos = leftPoint; } else { // Types::BottomEdge dialogPos = topPoint; } //find the correct screen for the item //we do not rely on item->window()->screen() because //QWindow::screen() is always only the screen where the window gets first created //not actually the current window. See QWindow::screen() documentation QRect avail = item->window()->screen()->availableGeometry(); if (outsideParentWindow && d->frameSvgItem->enabledBorders() != Plasma::FrameSvg::AllBorders) { //make the panel look it's inside the panel, in order to not make it look cut switch (d->location) { case Plasma::Types::LeftEdge: case Plasma::Types::RightEdge: avail.setTop(qMax(avail.top(), parentGeometryBounds.top())); avail.setBottom(qMin(avail.bottom(), parentGeometryBounds.bottom())); break; default: avail.setLeft(qMax(avail.left(), parentGeometryBounds.left())); avail.setRight(qMin(avail.right(), parentGeometryBounds.right())); break; } } if (dialogPos.x() < avail.left()) { // popup hits lhs if (d->location != Plasma::Types::LeftEdge || d->location == Plasma::Types::RightEdge) { // move it dialogPos.setX(avail.left()); } else { // swap edge dialogPos.setX(rightPoint.x()); } } if (dialogPos.x() + size.width() > avail.right()) { // popup hits rhs if (d->location == Plasma::Types::TopEdge || d->location == Plasma::Types::BottomEdge) { dialogPos.setX(qMax(avail.left(), (avail.right() - size.width() + 1))); } else { dialogPos.setX(leftPoint.x()); } } if (dialogPos.y() < avail.top()) { // hitting top if (d->location == Plasma::Types::LeftEdge || d->location == Plasma::Types::RightEdge) { dialogPos.setY(avail.top()); } else { dialogPos.setY(bottomPoint.y()); } } if (dialogPos.y() + size.height() > avail.bottom()) { // hitting bottom if (d->location == Plasma::Types::TopEdge || d->location == Plasma::Types::BottomEdge) { dialogPos.setY(topPoint.y()); } else { dialogPos.setY(qMax(avail.top(), (avail.bottom() - size.height() + 1))); } } return dialogPos; } Plasma::Types::Location Dialog::location() const { return d->location; } void Dialog::setLocation(Plasma::Types::Location location) { if (d->location == location) { return; } d->location = location; emit locationChanged(); if (d->mainItem) { d->syncToMainItemSize(); } } QObject *Dialog::margins() const { return d->frameSvgItem->fixedMargins(); } void Dialog::setFramelessFlags(Qt::WindowFlags flags) { setFlags(Qt::FramelessWindowHint | flags); d->applyType(); emit flagsChanged(); } void Dialog::adjustGeometry(const QRect &geom) { setGeometry(geom); } void Dialog::resizeEvent(QResizeEvent* re) { QQuickWindow::resizeEvent(re); //it's a spontaneous event generated in qguiapplication.cpp QGuiApplicationPrivate::processWindowScreenChangedEvent //QWindowSystemInterfacePrivate::GeometryChangeEvent gce(window, QHighDpi::fromNativePixels(window->handle()->geometry(), window), QRect()); //This happens before the first show event when there is more than one screen, //right after the window has been created, the window is still 0x0, //but the resize event gets delivered with 0x0 again and executed with all the bad side effects //this seems to happen for every window when there are multiple screens, so something we have probably to watch out for in the future if (re->size().isEmpty() || re->size() == re->oldSize()) { return; } //A dialog can be resized even if no mainItem has ever been set if (!d->mainItem) { return; } d->mainItem->disconnect(this); d->frameSvgItem->setSize(QSizeF(re->size().width(), re->size().height())); auto margin = d->frameSvgItem->fixedMargins(); d->mainItem->setPosition(QPointF(margin->left(), margin->top())); d->mainItem->setSize(QSize(re->size().width() - margin->left() - margin->right(), re->size().height() - margin->top() - margin->bottom())); QObject::connect(d->mainItem, SIGNAL(widthChanged()), this, SLOT(slotMainItemSizeChanged())); QObject::connect(d->mainItem, SIGNAL(heightChanged()), this, SLOT(slotMainItemSizeChanged())); } void Dialog::setType(WindowType type) { if (type == d->type) { return; } d->type = type; d->applyType(); emit typeChanged(); } Dialog::WindowType Dialog::type() const { return d->type; } void Dialog::focusInEvent(QFocusEvent *ev) { QQuickWindow::focusInEvent(ev); } void Dialog::focusOutEvent(QFocusEvent *ev) { if (d->hideOnWindowDeactivate) { bool parentHasFocus = false; QWindow *parentWindow = transientParent(); while (parentWindow) { if (parentWindow->isActive() && !(parentWindow->flags() & Qt::WindowDoesNotAcceptFocus)) { parentHasFocus = true; break; } parentWindow = parentWindow->transientParent(); } const QWindow *focusWindow = QGuiApplication::focusWindow(); bool childHasFocus = focusWindow && ((focusWindow->isActive() && isAncestorOf(focusWindow)) || focusWindow->type() & Qt::Popup); const bool viewClicked = qobject_cast(focusWindow) || qobject_cast(focusWindow) || qobject_cast(focusWindow); if (viewClicked || (!parentHasFocus && !childHasFocus)) { setVisible(false); emit windowDeactivated(); } } QQuickWindow::focusOutEvent(ev); } void Dialog::showEvent(QShowEvent *event) { if (d->backgroundHints != Dialog::NoBackground) { DialogShadows::self()->addWindow(this, d->frameSvgItem->enabledBorders()); } KWindowSystem::setState(winId(), NET::SkipTaskbar | NET::SkipPager); QQuickWindow::showEvent(event); } bool Dialog::event(QEvent *event) { if (event->type() == QEvent::Expose) { auto ee = static_cast(event); if (!KWindowSystem::isPlatformWayland() || ee->region().isNull()) { return QQuickWindow::event(event); } /* * expose event is the only place where to correctly * register our wayland extensions, as showevent is a bit too * soon and the platform window isn't shown yet * create a shell surface every time the window gets exposed * (only the first expose event, guarded by shelldurface existence) * and tear it down when the window gets hidden * see https://phabricator.kde.org/T6064 */ #if HAVE_KWAYLAND //sometimes non null regions arrive even for non visible windows //for which surface creation would fail if (!d->shellSurface && isVisible()) { KWindowSystem::setState(winId(), NET::SkipTaskbar | NET::SkipPager); d->setupWaylandIntegration(); d->updateVisibility(true); d->updateTheme(); } #endif } else if (event->type() == QEvent::PlatformSurface) { const QPlatformSurfaceEvent *pSEvent = static_cast(event); if (pSEvent->surfaceEventType() == QPlatformSurfaceEvent::SurfaceCreated) { KWindowSystem::setState(winId(), NET::SkipTaskbar | NET::SkipPager); } } else if (event->type() == QEvent::Show) { d->updateVisibility(true); } else if (event->type() == QEvent::Hide) { d->updateVisibility(false); #if HAVE_KWAYLAND delete d->shellSurface; d->shellSurface = nullptr; #endif } else if (event->type() == QEvent::Move) { #if HAVE_KWAYLAND if (d->shellSurface) { QMoveEvent *me = static_cast(event); d->shellSurface->setPosition(me->pos()); } #endif } /*Fitt's law: if the containment has margins, and the mouse cursor clicked * on the mouse edge, forward the click in the containment boundaries */ if (d->mainItem && !d->mainItem->size().isEmpty()) { switch (event->type()) { case QEvent::MouseMove: case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: { QMouseEvent *me = static_cast(event); //don't mess with position if the cursor is actually outside the view: //somebody is doing a click and drag that must not break when the cursor i outside if (geometry().contains(me->screenPos().toPoint()) && !d->mainItemContainsPosition(me->windowPos())) { QMouseEvent me2(me->type(), d->positionAdjustedForMainItem(me->windowPos()), d->positionAdjustedForMainItem(me->windowPos()), d->positionAdjustedForMainItem(me->windowPos()) + position(), me->button(), me->buttons(), me->modifiers()); if (isVisible()) { QCoreApplication::sendEvent(this, &me2); } return true; } break; } case QEvent::Wheel: { QWheelEvent *we = static_cast(event); #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) const QPoint pos = we->pos(); #else const QPoint pos = we->position().toPoint(); #endif if (!d->mainItemContainsPosition(pos)) { #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) QWheelEvent we2(d->positionAdjustedForMainItem(pos), d->positionAdjustedForMainItem(pos) + position(), we->pixelDelta(), we->angleDelta(), we->angleDelta().y(), we->orientation(), we->buttons(), we->modifiers(), we->phase()); #else QWheelEvent we2(d->positionAdjustedForMainItem(pos), d->positionAdjustedForMainItem(pos) + position(), we->pixelDelta(), we->angleDelta(), we->buttons(), we->modifiers(), we->phase(), false /*inverted*/); #endif if (isVisible()) { QCoreApplication::sendEvent(this, &we2); } return true; } break; } case QEvent::DragEnter: { QDragEnterEvent *de = static_cast(event); if (!d->mainItemContainsPosition(de->pos())) { QDragEnterEvent de2(d->positionAdjustedForMainItem(de->pos()).toPoint(), de->possibleActions(), de->mimeData(), de->mouseButtons(), de->keyboardModifiers()); if (isVisible()) { QCoreApplication::sendEvent(this, &de2); } return true; } break; } //DragLeave just works case QEvent::DragLeave: break; case QEvent::DragMove: { QDragMoveEvent *de = static_cast(event); if (!d->mainItemContainsPosition(de->pos())) { QDragMoveEvent de2(d->positionAdjustedForMainItem(de->pos()).toPoint(), de->possibleActions(), de->mimeData(), de->mouseButtons(), de->keyboardModifiers()); if (isVisible()) { QCoreApplication::sendEvent(this, &de2); } return true; } break; } case QEvent::Drop: { QDropEvent *de = static_cast(event); if (!d->mainItemContainsPosition(de->pos())) { QDropEvent de2(d->positionAdjustedForMainItem(de->pos()).toPoint(), de->possibleActions(), de->mimeData(), de->mouseButtons(), de->keyboardModifiers()); if (isVisible()) { QCoreApplication::sendEvent(this, &de2); } return true; } break; } default: break; } } return QQuickWindow::event(event); } void Dialog::hideEvent(QHideEvent *event) { QQuickWindow::hideEvent(event); } void Dialog::classBegin() { d->componentComplete = false; } void Dialog::componentComplete() { d->componentComplete = true; QQuickWindow::setVisible(d->visible); d->updateTheme(); } bool Dialog::hideOnWindowDeactivate() const { return d->hideOnWindowDeactivate; } void Dialog::setHideOnWindowDeactivate(bool hide) { if (d->hideOnWindowDeactivate == hide) { return; } d->hideOnWindowDeactivate = hide; emit hideOnWindowDeactivateChanged(); } bool Dialog::isOutputOnly() const { return d->outputOnly; } void Dialog::setOutputOnly(bool outputOnly) { if (d->outputOnly == outputOnly) { return; } d->outputOnly = outputOnly; emit outputOnlyChanged(); } void Dialog::setVisible(bool visible) { //only update real visibility when we have finished component completion //and all flags have been set d->visible = visible; if (d->componentComplete) { if (visible && d->visualParent) { setPosition(popupPosition(d->visualParent, size())); } // Bug 381242: Qt remembers minimize state and re-applies it when showing setWindowStates(windowStates() & ~Qt::WindowMinimized); QQuickWindow::setVisible(visible); //signal will be emitted and proxied from the QQuickWindow code } else { emit visibleChangedProxy(); } } bool Dialog::isVisible() const { if (d->componentComplete) { return QQuickWindow::isVisible(); } return d->visible; } Dialog::BackgroundHints Dialog::backgroundHints() const { return d->backgroundHints; } void Dialog::setBackgroundHints(Dialog::BackgroundHints hints) { if (d->backgroundHints == hints) { return; } d->backgroundHints = hints; d->updateTheme(); emit backgroundHintsChanged(); } } #include "moc_dialog.cpp" diff --git a/src/plasmaquick/dialogshadows.cpp b/src/plasmaquick/dialogshadows.cpp index 8c7e27e27..821f6e494 100644 --- a/src/plasmaquick/dialogshadows.cpp +++ b/src/plasmaquick/dialogshadows.cpp @@ -1,695 +1,695 @@ /* * Copyright 2011 by Aaron Seigo * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License version 2, * or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "dialogshadows_p.h" #include #include #include #include #if HAVE_X11 #include #include #include #include #include #endif #if HAVE_KWAYLAND #include "waylandintegration_p.h" #include #include #include #endif #include class DialogShadows::Private { public: Private(DialogShadows *shadows) : q(shadows) #if HAVE_X11 , _connection(nullptr) , _gc(0x0) , m_isX11(KWindowSystem::isPlatformX11()) #endif { } ~Private() { // Do not call clearPixmaps() from here: it creates new QPixmap(), // which causes a crash when application is stopping. freeX11Pixmaps(); } void freeX11Pixmaps(); void freeWaylandBuffers(); void clearPixmaps(); void setupPixmaps(); Qt::HANDLE createPixmap(const QPixmap &source); void initPixmap(const QString &element); QPixmap initEmptyPixmap(const QSize &size); void updateShadow(const QWindow *window, Plasma::FrameSvg::EnabledBorders); void updateShadowX11(const QWindow *window, Plasma::FrameSvg::EnabledBorders); void updateShadowWayland(const QWindow *window, Plasma::FrameSvg::EnabledBorders); void clearShadow(const QWindow *window); void clearShadowX11(const QWindow *window); void clearShadowWayland(const QWindow *window); void updateShadows(); void windowDestroyed(QObject *deletedObject); void setupData(Plasma::FrameSvg::EnabledBorders enabledBorders); DialogShadows *q; QList m_shadowPixmaps; QPixmap m_emptyCornerPix; QPixmap m_emptyCornerLeftPix; QPixmap m_emptyCornerTopPix; QPixmap m_emptyCornerRightPix; QPixmap m_emptyCornerBottomPix; QPixmap m_emptyVerticalPix; QPixmap m_emptyHorizontalPix; #if HAVE_X11 //! xcb connection xcb_connection_t *_connection; //! graphical context xcb_gcontext_t _gc; bool m_isX11; #endif #if HAVE_KWAYLAND struct Wayland { QList shadowBuffers; }; Wayland m_wayland; #endif QHash > data; QHash m_windows; }; class DialogShadowsSingleton { public: DialogShadowsSingleton() { } DialogShadows self; }; Q_GLOBAL_STATIC(DialogShadowsSingleton, privateDialogShadowsSelf) DialogShadows::DialogShadows(QObject *parent, const QString &prefix) : Plasma::Svg(parent), d(new Private(this)) { setImagePath(prefix); connect(this, SIGNAL(repaintNeeded()), this, SLOT(updateShadows())); } DialogShadows::~DialogShadows() { delete d; } DialogShadows *DialogShadows::self() { return &privateDialogShadowsSelf->self; } void DialogShadows::addWindow(const QWindow *window, Plasma::FrameSvg::EnabledBorders enabledBorders) { if (!window) { return; } d->m_windows[window] = enabledBorders; d->updateShadow(window, enabledBorders); connect(window, SIGNAL(destroyed(QObject*)), this, SLOT(windowDestroyed(QObject*)), Qt::UniqueConnection); } void DialogShadows::removeWindow(const QWindow *window) { if (!d->m_windows.contains(window)) { return; } d->m_windows.remove(window); disconnect(window, nullptr, this, nullptr); d->clearShadow(window); if (d->m_windows.isEmpty()) { d->clearPixmaps(); } } void DialogShadows::setEnabledBorders(const QWindow *window, Plasma::FrameSvg::EnabledBorders enabledBorders) { if (!window || !d->m_windows.contains(window)) { return; } d->updateShadow(window, enabledBorders); } void DialogShadows::Private::windowDestroyed(QObject *deletedObject) { m_windows.remove(static_cast(deletedObject)); if (m_windows.isEmpty()) { clearPixmaps(); } } void DialogShadows::Private::updateShadows() { setupPixmaps(); QHash::const_iterator i; for (i = m_windows.constBegin(); i != m_windows.constEnd(); ++i) { updateShadow(i.key(), i.value()); } } Qt::HANDLE DialogShadows::Private::createPixmap(const QPixmap &source) { // do nothing for invalid pixmaps if (source.isNull()) { return nullptr; } /* in some cases, pixmap handle is invalid. This is the case notably when Qt uses to RasterEngine. In this case, we create an X11 Pixmap explicitly and draw the source pixmap on it. */ #if HAVE_X11 if (!m_isX11) { return nullptr; } // check connection if (!_connection) { _connection = QX11Info::connection(); } const int width(source.width()); const int height(source.height()); // create X11 pixmap Pixmap pixmap = XCreatePixmap(QX11Info::display(), QX11Info::appRootWindow(), width, height, 32); // check gc if (!_gc) { _gc = xcb_generate_id(_connection); xcb_create_gc(_connection, _gc, pixmap, 0, nullptr); } // // create explicitly shared QPixmap from it // QPixmap dest( QPixmap::fromX11Pixmap( pixmap, QPixmap::ExplicitlyShared ) ); // // // create surface for pixmap // { // QPainter painter( &dest ); // painter.setCompositionMode( QPainter::CompositionMode_Source ); // painter.drawPixmap( 0, 0, source ); // } // // // return pixmap; QImage image(source.toImage()); xcb_put_image( _connection, XCB_IMAGE_FORMAT_Z_PIXMAP, pixmap, _gc, image.width(), image.height(), 0, 0, 0, 32, image.sizeInBytes(), image.constBits()); return (Qt::HANDLE)pixmap; #else return nullptr; #endif } void DialogShadows::Private::initPixmap(const QString &element) { m_shadowPixmaps << q->pixmap(element); } QPixmap DialogShadows::Private::initEmptyPixmap(const QSize &size) { #if HAVE_X11 if (!m_isX11) { return QPixmap(); } QPixmap tempEmptyPix(size); if (!size.isEmpty()) { tempEmptyPix.fill(Qt::transparent); } return tempEmptyPix; #else Q_UNUSED(size) return QPixmap(); #endif } void DialogShadows::Private::setupPixmaps() { clearPixmaps(); initPixmap(QStringLiteral("shadow-top")); initPixmap(QStringLiteral("shadow-topright")); initPixmap(QStringLiteral("shadow-right")); initPixmap(QStringLiteral("shadow-bottomright")); initPixmap(QStringLiteral("shadow-bottom")); initPixmap(QStringLiteral("shadow-bottomleft")); initPixmap(QStringLiteral("shadow-left")); initPixmap(QStringLiteral("shadow-topleft")); m_emptyCornerPix = initEmptyPixmap(QSize(1, 1)); m_emptyCornerLeftPix = initEmptyPixmap(QSize(q->elementSize(QStringLiteral("shadow-topleft")).width(), 1)); m_emptyCornerTopPix = initEmptyPixmap(QSize(1, q->elementSize(QStringLiteral("shadow-topleft")).height())); m_emptyCornerRightPix = initEmptyPixmap(QSize(q->elementSize(QStringLiteral("shadow-bottomright")).width(), 1)); m_emptyCornerBottomPix = initEmptyPixmap(QSize(1, q->elementSize(QStringLiteral("shadow-bottomright")).height())); m_emptyVerticalPix = initEmptyPixmap(QSize(1, q->elementSize(QStringLiteral("shadow-left")).height())); m_emptyHorizontalPix = initEmptyPixmap(QSize(q->elementSize(QStringLiteral("shadow-top")).width(), 1)); #if HAVE_KWAYLAND if (KWayland::Client::ShmPool *shmPool = WaylandIntegration::self()->waylandShmPool()) { for (auto it = m_shadowPixmaps.constBegin(); it != m_shadowPixmaps.constEnd(); ++it) { m_wayland.shadowBuffers << shmPool->createBuffer(it->toImage()); } } #endif } void DialogShadows::Private::setupData(Plasma::FrameSvg::EnabledBorders enabledBorders) { #if HAVE_X11 if (!m_isX11) { return; } //shadow-top if (enabledBorders & Plasma::FrameSvg::TopBorder) { data[enabledBorders] << reinterpret_cast(createPixmap(m_shadowPixmaps[0])); } else { data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyHorizontalPix)); } //shadow-topright if (enabledBorders & Plasma::FrameSvg::TopBorder && enabledBorders & Plasma::FrameSvg::RightBorder) { data[enabledBorders] << reinterpret_cast(createPixmap(m_shadowPixmaps[1])); } else if (enabledBorders & Plasma::FrameSvg::TopBorder) { data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyCornerTopPix)); } else if (enabledBorders & Plasma::FrameSvg::RightBorder) { data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyCornerRightPix)); } else { data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyCornerPix)); } //shadow-right if (enabledBorders & Plasma::FrameSvg::RightBorder) { data[enabledBorders] << reinterpret_cast(createPixmap(m_shadowPixmaps[2])); } else { data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyVerticalPix)); } //shadow-bottomright if (enabledBorders & Plasma::FrameSvg::BottomBorder && enabledBorders & Plasma::FrameSvg::RightBorder) { data[enabledBorders] << reinterpret_cast(createPixmap(m_shadowPixmaps[3])); } else if (enabledBorders & Plasma::FrameSvg::BottomBorder) { data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyCornerBottomPix)); } else if (enabledBorders & Plasma::FrameSvg::RightBorder) { data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyCornerRightPix)); } else { data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyCornerPix)); } //shadow-bottom if (enabledBorders & Plasma::FrameSvg::BottomBorder) { data[enabledBorders] << reinterpret_cast(createPixmap(m_shadowPixmaps[4])); } else { data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyHorizontalPix)); } //shadow-bottomleft if (enabledBorders & Plasma::FrameSvg::BottomBorder && enabledBorders & Plasma::FrameSvg::LeftBorder) { data[enabledBorders] << reinterpret_cast(createPixmap(m_shadowPixmaps[5])); } else if (enabledBorders & Plasma::FrameSvg::BottomBorder) { data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyCornerBottomPix)); } else if (enabledBorders & Plasma::FrameSvg::LeftBorder) { data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyCornerLeftPix)); } else { data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyCornerPix)); } //shadow-left if (enabledBorders & Plasma::FrameSvg::LeftBorder) { data[enabledBorders] << reinterpret_cast(createPixmap(m_shadowPixmaps[6])); } else { data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyVerticalPix)); } //shadow-topleft if (enabledBorders & Plasma::FrameSvg::TopBorder && enabledBorders & Plasma::FrameSvg::LeftBorder) { data[enabledBorders] << reinterpret_cast(createPixmap(m_shadowPixmaps[7])); } else if (enabledBorders & Plasma::FrameSvg::TopBorder) { data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyCornerTopPix)); } else if (enabledBorders & Plasma::FrameSvg::LeftBorder) { data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyCornerLeftPix)); } else { data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyCornerPix)); } #endif int left, top, right, bottom = 0; QSize marginHint; if (enabledBorders & Plasma::FrameSvg::TopBorder) { marginHint = q->elementSize(QStringLiteral("shadow-hint-top-margin")); if (marginHint.isValid()) { top = marginHint.height(); } else { top = m_shadowPixmaps[0].height(); // top } } else { top = 1; } if (enabledBorders & Plasma::FrameSvg::RightBorder) { marginHint = q->elementSize(QStringLiteral("shadow-hint-right-margin")); if (marginHint.isValid()) { right = marginHint.width(); } else { right = m_shadowPixmaps[2].width(); // right } } else { right = 1; } if (enabledBorders & Plasma::FrameSvg::BottomBorder) { marginHint = q->elementSize(QStringLiteral("shadow-hint-bottom-margin")); if (marginHint.isValid()) { bottom = marginHint.height(); } else { bottom = m_shadowPixmaps[4].height(); // bottom } } else { bottom = 1; } if (enabledBorders & Plasma::FrameSvg::LeftBorder) { marginHint = q->elementSize(QStringLiteral("shadow-hint-left-margin")); if (marginHint.isValid()) { left = marginHint.width(); } else { left = m_shadowPixmaps[6].width(); // left } } else { left = 1; } data[enabledBorders] << top << right << bottom << left; } void DialogShadows::Private::freeX11Pixmaps() { #if HAVE_X11 if (!m_isX11) { return; } auto *display = QX11Info::display(); if (!display) { return; } - foreach (const QPixmap &pixmap, m_shadowPixmaps) { + for (const QPixmap &pixmap : qAsConst(m_shadowPixmaps)) { if (!pixmap.isNull()) { XFreePixmap(display, reinterpret_cast(createPixmap(pixmap))); } } if (!m_emptyCornerPix.isNull()) { XFreePixmap(display, reinterpret_cast(createPixmap(m_emptyCornerPix))); } if (!m_emptyCornerBottomPix.isNull()) { XFreePixmap(display, reinterpret_cast(createPixmap(m_emptyCornerBottomPix))); } if (!m_emptyCornerLeftPix.isNull()) { XFreePixmap(display, reinterpret_cast(createPixmap(m_emptyCornerLeftPix))); } if (!m_emptyCornerRightPix.isNull()) { XFreePixmap(display, reinterpret_cast(createPixmap(m_emptyCornerRightPix))); } if (!m_emptyCornerTopPix.isNull()) { XFreePixmap(display, reinterpret_cast(createPixmap(m_emptyCornerTopPix))); } if (!m_emptyVerticalPix.isNull()) { XFreePixmap(display, reinterpret_cast(createPixmap(m_emptyVerticalPix))); } if (!m_emptyHorizontalPix.isNull()) { XFreePixmap(display, reinterpret_cast(createPixmap(m_emptyHorizontalPix))); } #endif } void DialogShadows::Private::clearPixmaps() { #if HAVE_X11 freeX11Pixmaps(); m_emptyCornerPix = QPixmap(); m_emptyCornerBottomPix = QPixmap(); m_emptyCornerLeftPix = QPixmap(); m_emptyCornerRightPix = QPixmap(); m_emptyCornerTopPix = QPixmap(); m_emptyVerticalPix = QPixmap(); m_emptyHorizontalPix = QPixmap(); #endif freeWaylandBuffers(); m_shadowPixmaps.clear(); data.clear(); } void DialogShadows::Private::freeWaylandBuffers() { #if HAVE_KWAYLAND m_wayland.shadowBuffers.clear(); #endif } void DialogShadows::Private::updateShadow(const QWindow *window, Plasma::FrameSvg::EnabledBorders enabledBorders) { #if HAVE_X11 if (m_isX11) { updateShadowX11(window, enabledBorders); } #endif #if HAVE_KWAYLAND if (WaylandIntegration::self()->waylandShadowManager()) { updateShadowWayland(window, enabledBorders); } #endif } void DialogShadows::Private::updateShadowX11(const QWindow *window, Plasma::FrameSvg::EnabledBorders enabledBorders) { #if HAVE_X11 if (m_shadowPixmaps.isEmpty()) { setupPixmaps(); } if (!data.contains(enabledBorders)) { setupData(enabledBorders); } Display *dpy = QX11Info::display(); Atom atom = XInternAtom(dpy, "_KDE_NET_WM_SHADOW", False); //qDebug() << "going to set the shadow of" << window->winId() << "to" << data; XChangeProperty(dpy, window->winId(), atom, XA_CARDINAL, 32, PropModeReplace, reinterpret_cast(data[enabledBorders].constData()), data[enabledBorders].size()); #endif } void DialogShadows::Private::updateShadowWayland(const QWindow *window, Plasma::FrameSvg::EnabledBorders enabledBorders) { #if HAVE_KWAYLAND if (!WaylandIntegration::self()->waylandShmPool()) { return; } if (m_wayland.shadowBuffers.isEmpty()) { setupPixmaps(); } // TODO: check whether the surface already has a shadow KWayland::Client::Surface *surface = KWayland::Client::Surface::fromWindow(const_cast(window)); if (!surface) { return; } KWayland::Client::ShadowManager *manager = WaylandIntegration::self()->waylandShadowManager(); auto shadow = manager->createShadow(surface, surface); //shadow-top if (enabledBorders & Plasma::FrameSvg::TopBorder) { shadow->attachTop(m_wayland.shadowBuffers.at(0)); } //shadow-topright if (enabledBorders & Plasma::FrameSvg::TopBorder && enabledBorders & Plasma::FrameSvg::RightBorder) { shadow->attachTopRight(m_wayland.shadowBuffers.at(1)); } //shadow-right if (enabledBorders & Plasma::FrameSvg::RightBorder) { shadow->attachRight(m_wayland.shadowBuffers.at(2)); } //shadow-bottomright if (enabledBorders & Plasma::FrameSvg::BottomBorder && enabledBorders & Plasma::FrameSvg::RightBorder) { shadow->attachBottomRight(m_wayland.shadowBuffers.at(3)); } //shadow-bottom if (enabledBorders & Plasma::FrameSvg::BottomBorder) { shadow->attachBottom(m_wayland.shadowBuffers.at(4)); } //shadow-bottomleft if (enabledBorders & Plasma::FrameSvg::BottomBorder && enabledBorders & Plasma::FrameSvg::LeftBorder) { shadow->attachBottomLeft(m_wayland.shadowBuffers.at(5)); } //shadow-left if (enabledBorders & Plasma::FrameSvg::LeftBorder) { shadow->attachLeft(m_wayland.shadowBuffers.at(6)); } //shadow-topleft if (enabledBorders & Plasma::FrameSvg::TopBorder && enabledBorders & Plasma::FrameSvg::LeftBorder) { shadow->attachTopLeft(m_wayland.shadowBuffers.at(7)); } QSize marginHint; QMarginsF margins; if (enabledBorders & Plasma::FrameSvg::TopBorder) { marginHint = q->elementSize(QStringLiteral("shadow-hint-top-margin")); if (marginHint.isValid()) { margins.setTop(marginHint.height()); } else { margins.setTop(m_shadowPixmaps[0].height()); } } if (enabledBorders & Plasma::FrameSvg::RightBorder) { marginHint = q->elementSize(QStringLiteral("shadow-hint-right-margin")); if (marginHint.isValid()) { margins.setRight(marginHint.width()); } else { margins.setRight(m_shadowPixmaps[2].width()); } } if (enabledBorders & Plasma::FrameSvg::BottomBorder) { marginHint = q->elementSize(QStringLiteral("shadow-hint-bottom-margin")); if (marginHint.isValid()) { margins.setBottom(marginHint.height()); } else { margins.setBottom(m_shadowPixmaps[4].height()); } } if (enabledBorders & Plasma::FrameSvg::LeftBorder) { marginHint = q->elementSize(QStringLiteral("shadow-hint-left-margin")); if (marginHint.isValid()) { margins.setLeft(marginHint.width()); } else { margins.setLeft(m_shadowPixmaps[6].width()); } } shadow->setOffsets(margins); shadow->commit(); surface->commit(KWayland::Client::Surface::CommitFlag::None); #endif } void DialogShadows::Private::clearShadow(const QWindow *window) { if (!static_cast(window)->surfaceHandle()) { qWarning() << "Cannot clear shadow from window without native surface!"; return; } #if HAVE_X11 if (m_isX11) { clearShadowX11(window); } #endif #if HAVE_KWAYLAND if (WaylandIntegration::self()->waylandShadowManager()) { clearShadowWayland(window); } #endif } void DialogShadows::Private::clearShadowX11(const QWindow* window) { #if HAVE_X11 Display *dpy = QX11Info::display(); Atom atom = XInternAtom(dpy, "_KDE_NET_WM_SHADOW", False); XDeleteProperty(dpy, window->winId(), atom); #endif } void DialogShadows::Private::clearShadowWayland(const QWindow *window) { #if HAVE_KWAYLAND KWayland::Client::Surface *surface = KWayland::Client::Surface::fromWindow(const_cast(window)); if (!surface) { return; } KWayland::Client::ShadowManager *manager = WaylandIntegration::self()->waylandShadowManager(); manager->removeShadow(surface); surface->commit(KWayland::Client::Surface::CommitFlag::None); #endif } bool DialogShadows::enabled() const { return hasElement(QStringLiteral("shadow-left")); } #include "moc_dialogshadows_p.cpp" diff --git a/src/scriptengines/qml/plasmoid/appletinterface.cpp b/src/scriptengines/qml/plasmoid/appletinterface.cpp index c2974748f..42d2cd2e3 100644 --- a/src/scriptengines/qml/plasmoid/appletinterface.cpp +++ b/src/scriptengines/qml/plasmoid/appletinterface.cpp @@ -1,884 +1,885 @@ /* * Copyright 2008-2013 Aaron Seigo * Copyright 2010-2013 Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "appletinterface.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "containmentinterface.h" #include "wallpaperinterface.h" #include #include Q_DECLARE_METATYPE(AppletInterface *) AppletInterface::AppletInterface(DeclarativeAppletScript *script, const QVariantList &args, QQuickItem *parent) : AppletQuickItem(script->applet(), parent), m_configuration(nullptr), m_appletScriptEngine(script), m_toolTipTextFormat(0), m_toolTipItem(nullptr), m_args(args), m_hideOnDeactivate(true), m_oldKeyboardShortcut(0), m_dummyNativeInterface(nullptr), m_positionBeforeRemoval(QPointF(-1, -1)) { qmlRegisterType(); connect(this, &AppletInterface::configNeedsSaving, applet(), &Plasma::Applet::configNeedsSaving); connect(applet(), &Plasma::Applet::immutabilityChanged, this, &AppletInterface::immutabilityChanged); connect(applet(), &Plasma::Applet::userConfiguringChanged, this, &AppletInterface::userConfiguringChanged); connect(applet(), &Plasma::Applet::contextualActionsAboutToShow, this, &AppletInterface::contextualActionsAboutToShow); connect(applet(), &Plasma::Applet::statusChanged, this, &AppletInterface::statusChanged); connect(applet(), &Plasma::Applet::destroyedChanged, this, &AppletInterface::destroyedChanged); connect(applet(), &Plasma::Applet::titleChanged, this, &AppletInterface::titleChanged); connect(applet(), &Plasma::Applet::titleChanged, this, [this]() { if (m_toolTipMainText.isNull()) { emit toolTipMainTextChanged(); } }); connect(applet(), &Plasma::Applet::iconChanged, this, &AppletInterface::iconChanged); connect(applet(), &Plasma::Applet::busyChanged, this, &AppletInterface::busyChanged); connect(applet(), &Plasma::Applet::backgroundHintsChanged, this, &AppletInterface::backgroundHintsChanged); connect(applet(), &Plasma::Applet::effectiveBackgroundHintsChanged, this, &AppletInterface::effectiveBackgroundHintsChanged); connect(applet(), &Plasma::Applet::userBackgroundHintsChanged, this, &AppletInterface::userBackgroundHintsChanged); connect(applet(), &Plasma::Applet::configurationRequiredChanged, this, [this](bool configurationRequired, const QString &reason) { Q_UNUSED(configurationRequired); Q_UNUSED(reason); emit configurationRequiredChanged(); emit configurationRequiredReasonChanged(); }); connect(applet(), &Plasma::Applet::activated, this, &AppletInterface::activated); connect(appletScript(), &DeclarativeAppletScript::formFactorChanged, this, &AppletInterface::formFactorChanged); connect(appletScript(), &DeclarativeAppletScript::locationChanged, this, &AppletInterface::locationChanged); connect(appletScript(), &DeclarativeAppletScript::contextChanged, this, &AppletInterface::contextChanged); if (applet()->containment()) { connect(applet()->containment(), &Plasma::Containment::screenChanged, this, &AppletInterface::screenChanged); // Screen change implies geo change for good measure. connect(applet()->containment(), &Plasma::Containment::screenChanged, this, &AppletInterface::screenGeometryChanged); connect(applet()->containment()->corona(), &Plasma::Corona::screenGeometryChanged, this, [this](int id) { if (id == applet()->containment()->screen()) { emit screenGeometryChanged(); } }); connect(applet()->containment()->corona(), &Plasma::Corona::availableScreenRegionChanged, this, &ContainmentInterface::availableScreenRegionChanged); connect(applet()->containment()->corona(), &Plasma::Corona::availableScreenRectChanged, this, &ContainmentInterface::availableScreenRectChanged); } connect(this, &AppletInterface::expandedChanged, [=](bool expanded) { //if both compactRepresentationItem and fullRepresentationItem exist, //the applet is in a popup if (expanded) { if (compactRepresentationItem() && fullRepresentationItem() && fullRepresentationItem()->window() && compactRepresentationItem()->window() && fullRepresentationItem()->window() != compactRepresentationItem()->window() && fullRepresentationItem()->parentItem()) { fullRepresentationItem()->parentItem()->installEventFilter(this); } else if (fullRepresentationItem() && fullRepresentationItem()->parentItem()) { fullRepresentationItem()->parentItem()->removeEventFilter(this); } } }); } AppletInterface::~AppletInterface() { } DeclarativeAppletScript *AppletInterface::appletScript() const { return m_appletScriptEngine; } void AppletInterface::init() { if (qmlObject()->rootObject() && m_configuration) { return; } m_configuration = new KDeclarative::ConfigPropertyMap(applet()->configScheme(), this); AppletQuickItem::init(); geometryChanged(QRectF(), QRectF(x(), y(), width(), height())); emit busyChanged(); updateUiReadyConstraint(); connect(this, &AppletInterface::isLoadingChanged, this, &AppletInterface::updateUiReadyConstraint); connect(applet(), &Plasma::Applet::activated, this, [ = ]() { // in case the applet doesn't want to get shrunk on reactivation, // we always expand it again (only in order to conform with legacy behaviour) bool activate = !( isExpanded() && isActivationTogglesExpanded() ); setExpanded(activate); if (activate) { if (QQuickItem *i = qobject_cast(fullRepresentationItem())) { // Bug 372476: never pull focus away from it, only setFocus(true) i->setFocus(true, Qt::ShortcutFocusReason); } } }); if (m_args.count() == 1) { emit externalData(QString(), m_args.first()); } else if (!m_args.isEmpty()) { emit externalData(QString(), m_args); } } void AppletInterface::destroyedChanged(bool destroyed) { //if an item loses its scene before losing the focus, will never //be able to gain focus again if (destroyed && window() && window()->activeFocusItem()) { QQuickItem *focus = window()->activeFocusItem(); QQuickItem *candidate = focus; bool isAncestor = false; //search if the current focus item is a child or grandchild of the applet while (candidate) { if (candidate == this) { isAncestor = true; break; } candidate = candidate->parentItem(); } if (isAncestor) { //Found? remove focus for the whole hierarchy candidate = focus; while (candidate && candidate != this) { candidate->setFocus(false); candidate = candidate->parentItem(); } } } setVisible(!destroyed); } Plasma::Types::FormFactor AppletInterface::formFactor() const { return applet()->formFactor(); } Plasma::Types::Location AppletInterface::location() const { return applet()->location(); } QString AppletInterface::currentActivity() const { if (applet()->containment()) { return applet()->containment()->activity(); } else { return QString(); } } QObject *AppletInterface::configuration() const { return m_configuration; } uint AppletInterface::id() const { return applet()->id(); } QString AppletInterface::pluginName() const { return applet()->pluginMetaData().isValid() ? applet()->pluginMetaData().pluginId() : QString(); } QString AppletInterface::icon() const { return applet()->icon(); } void AppletInterface::setIcon(const QString &icon) { if (applet()->icon() == icon) { return; } applet()->setIcon(icon); } QString AppletInterface::title() const { return applet()->title(); } void AppletInterface::setTitle(const QString &title) { if (applet()->title() == title) { return; } applet()->setTitle(title); } QString AppletInterface::toolTipMainText() const { if (m_toolTipMainText.isNull()) { return title(); } else { return m_toolTipMainText; } } void AppletInterface::setToolTipMainText(const QString &text) { //Here we are abusing the difference between a null and an empty string. //by default is null so falls back to the name //the fist time it gets set, an empty non null one is set, and won't fallback anymore if (!m_toolTipMainText.isNull() && m_toolTipMainText == text) { return; } if (text.isEmpty()) { m_toolTipMainText = QStringLiteral("");//this "" makes it non-null } else { m_toolTipMainText = text; } emit toolTipMainTextChanged(); } QString AppletInterface::toolTipSubText() const { if (m_toolTipSubText.isNull() && applet()->pluginMetaData().isValid()) { return applet()->pluginMetaData().description(); } else { return m_toolTipSubText; } } void AppletInterface::setToolTipSubText(const QString &text) { //Also there the difference between null and empty gets exploited if (!m_toolTipSubText.isNull() && m_toolTipSubText == text) { return; } if (text.isEmpty()) { m_toolTipSubText = QStringLiteral("");//this "" makes it non-null } else { m_toolTipSubText = text; } emit toolTipSubTextChanged(); } int AppletInterface::toolTipTextFormat() const { return m_toolTipTextFormat; } void AppletInterface::setToolTipTextFormat(int format) { if (m_toolTipTextFormat == format) { return; } m_toolTipTextFormat = format; emit toolTipTextFormatChanged(); } QQuickItem *AppletInterface::toolTipItem() const { return m_toolTipItem.data(); } void AppletInterface::setToolTipItem(QQuickItem *toolTipItem) { if (m_toolTipItem.data() == toolTipItem) { return; } m_toolTipItem = toolTipItem; connect(m_toolTipItem.data(), &QObject::destroyed, this, &AppletInterface::toolTipItemChanged); emit toolTipItemChanged(); } bool AppletInterface::isBusy() const { return applet()->isBusy(); } void AppletInterface::setBusy(bool busy) { applet()->setBusy(busy); } Plasma::Types::BackgroundHints AppletInterface::backgroundHints() const { return applet()->backgroundHints(); } void AppletInterface::setBackgroundHints(Plasma::Types::BackgroundHints hint) { applet()->setBackgroundHints(hint); } Plasma::Types::BackgroundHints AppletInterface::effectiveBackgroundHints() const { return applet()->effectiveBackgroundHints(); } Plasma::Types::BackgroundHints AppletInterface::userBackgroundHints() const { return applet()->userBackgroundHints(); } void AppletInterface::setUserBackgroundHints(Plasma::Types::BackgroundHints hint) { applet()->setUserBackgroundHints(hint); } void AppletInterface::setConfigurationRequired(bool needsConfiguring, const QString &reason) { appletScript()->setConfigurationRequired(needsConfiguring, reason); } QString AppletInterface::file(const QString &fileType) { return appletScript()->filePath(fileType, QString()); } QString AppletInterface::file(const QString &fileType, const QString &filePath) { return appletScript()->filePath(fileType, filePath); } QList AppletInterface::contextualActions() const { QList actions; Plasma::Applet *a = applet(); if (a->failedToLaunch()) { return actions; } - foreach (const QString &name, m_actions) { + for (const QString &name : qAsConst(m_actions)) { QAction *action = a->actions()->action(name); if (action) { actions << action; } } return actions; } void AppletInterface::setActionSeparator(const QString &name) { Plasma::Applet *a = applet(); QAction *action = a->actions()->action(name); if (action) { action->setSeparator(true); } else { action = new QAction(this); action->setSeparator(true); a->actions()->addAction(name, action); m_actions.append(name); } } void AppletInterface::setAction(const QString &name, const QString &text, const QString &icon, const QString &shortcut) { Plasma::Applet *a = applet(); QAction *action = a->actions()->action(name); if (action) { action->setText(text); } else { action = new QAction(text, this); a->actions()->addAction(name, action); Q_ASSERT(!m_actions.contains(name)); m_actions.append(name); connect(action, &QAction::triggered, this, [this, name] { executeAction(name); }); } if (!icon.isEmpty()) { action->setIcon(QIcon::fromTheme(icon)); } if (!shortcut.isEmpty()) { action->setShortcut(shortcut); } action->setObjectName(name); } void AppletInterface::removeAction(const QString &name) { Plasma::Applet *a = applet(); QAction *action = a->actions()->action(name); delete action; m_actions.removeAll(name); } void AppletInterface::clearActions() { - Q_FOREACH (const QString &action, m_actions) { + const auto oldActionsList = m_actions; + for (const QString &action : oldActionsList) { removeAction(action); } } QAction *AppletInterface::action(QString name) const { return applet()->actions()->action(name); } bool AppletInterface::immutable() const { return applet()->immutability() != Plasma::Types::Mutable; } Plasma::Types::ImmutabilityType AppletInterface::immutability() const { return applet()->immutability(); } bool AppletInterface::userConfiguring() const { return applet()->isUserConfiguring(); } int AppletInterface::apiVersion() const { // Look for C++ plugins first auto filter = [](const KPluginMetaData &md) -> bool { return md.value(QStringLiteral("X-Plasma-API")) == QLatin1String("declarativeappletscript") && md.value(QStringLiteral("X-Plasma-ComponentTypes")).contains(QLatin1String("Applet")); }; QVector plugins = KPluginLoader::findPlugins(QStringLiteral("plasma/scriptengines"), filter); if (plugins.isEmpty()) { return -1; } return plugins.first().value(QStringLiteral("X-KDE-PluginInfo-Version")).toInt(); } void AppletInterface::setAssociatedApplication(const QString &string) { if (applet()->associatedApplication() == string) { return; } applet()->setAssociatedApplication(string); emit associatedApplicationChanged(); } QString AppletInterface::associatedApplication() const { return applet()->associatedApplication(); } void AppletInterface::setAssociatedApplicationUrls(const QList &urls) { if (applet()->associatedApplicationUrls() == urls) { return; } applet()->setAssociatedApplicationUrls(urls); emit associatedApplicationUrlsChanged(); } QList AppletInterface::associatedApplicationUrls() const { return applet()->associatedApplicationUrls(); } void AppletInterface::setStatus(const Plasma::Types::ItemStatus &status) { applet()->setStatus(status); } Plasma::Types::ItemStatus AppletInterface::status() const { return applet()->status(); } int AppletInterface::screen() const { if (Plasma::Containment* c = applet()->containment()) { return c->screen(); } return -1; } QRect AppletInterface::screenGeometry() const { if (!applet() || !applet()->containment() || !applet()->containment()->corona()) { return QRect(); } return applet()->containment()->corona()->screenGeometry(applet()->containment()->screen()); } void AppletInterface::setHideOnWindowDeactivate(bool hide) { if (m_hideOnDeactivate != hide) { m_hideOnDeactivate = hide; emit hideOnWindowDeactivateChanged(); } } bool AppletInterface::hideOnWindowDeactivate() const { return m_hideOnDeactivate; } QKeySequence AppletInterface::globalShortcut() const { return applet()->globalShortcut(); } void AppletInterface::setGlobalShortcut(const QKeySequence &sequence) { applet()->setGlobalShortcut(sequence); } QObject *AppletInterface::nativeInterface() { if (qstrcmp(applet()->metaObject()->className(),"Plasma::Applet") != 0) { return applet(); } else { if (!m_dummyNativeInterface) { m_dummyNativeInterface = new QObject(this); } return m_dummyNativeInterface; } } bool AppletInterface::configurationRequired() const { return applet()->configurationRequired(); } void AppletInterface::setConfigurationRequiredProperty(bool needsConfiguring) { appletScript()->setConfigurationRequired(needsConfiguring, applet()->configurationRequiredReason()); } QString AppletInterface::configurationRequiredReason() const { return applet()->configurationRequiredReason(); } void AppletInterface::setConfigurationRequiredReason(const QString &reason) { appletScript()->setConfigurationRequired(applet()->configurationRequired(), reason); } QString AppletInterface::downloadPath(const QString &file) { Q_UNUSED(file); return downloadPath(); } QString AppletInterface::downloadPath() const { const QString downloadDir = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + QStringLiteral("/Plasma/") + applet()->pluginMetaData().pluginId() + QLatin1Char('/'); if (!QFile::exists(downloadDir)) { QDir dir({ QLatin1Char('/') }); dir.mkpath(downloadDir); } return downloadDir; } QStringList AppletInterface::downloadedFiles() const { const QString downloadDir = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + QStringLiteral("/Plasma/") + applet()->pluginMetaData().pluginId() + QLatin1Char('/'); QDir dir(downloadDir); return dir.entryList(QDir::Files | QDir::NoSymLinks | QDir::Readable); } void AppletInterface::executeAction(const QString &name) { if (qmlObject()->rootObject()) { const QMetaObject *metaObj = qmlObject()->rootObject()->metaObject(); const QByteArray actionMethodName = "action_" + name.toUtf8(); const QByteArray actionFunctionName = actionMethodName + QByteArray("()"); if (metaObj->indexOfMethod(QMetaObject::normalizedSignature(actionFunctionName.constData()).constData()) != -1) { QMetaObject::invokeMethod(qmlObject()->rootObject(), actionMethodName.constData(), Qt::DirectConnection); } else { QMetaObject::invokeMethod(qmlObject()->rootObject(), "actionTriggered", Qt::DirectConnection, Q_ARG(QVariant, name)); } } } QVariantList AppletInterface::availableScreenRegion() const { QVariantList regVal; if (!applet()->containment() || !applet()->containment()->corona()) { return regVal; } QRegion reg = QRect(0, 0, width(), height()); int screenId = screen(); if (screenId > -1) { reg = applet()->containment()->corona()->availableScreenRegion(screenId); } auto it = reg.begin(); const auto itEnd = reg.end(); for (; it != itEnd; ++it) { QRect rect = *it; //make it relative QRect geometry = applet()->containment()->corona()->screenGeometry(screenId); rect.moveTo(rect.topLeft() - geometry.topLeft()); regVal << QVariant::fromValue(QRectF(rect)); } return regVal; } QRect AppletInterface::availableScreenRect() const { if (!applet()->containment() || !applet()->containment()->corona()) { return QRect(); } QRect rect(0, 0, width(), height()); int screenId = screen(); if (screenId > -1) { rect = applet()->containment()->corona()->availableScreenRect(screenId); //make it relative QRect geometry = applet()->containment()->corona()->screenGeometry(screenId); rect.moveTo(rect.topLeft() - geometry.topLeft()); } return rect; } bool AppletInterface::event(QEvent *event) { // QAction keyboard shortcuts cannot work with QML2 (and probably newver will // since in Qt qtquick and qwidgets cannot depend from each other in any way) // so do a simple keyboard shortcut matching here if (event->type() == QEvent::KeyPress) { QKeyEvent *ke = static_cast(event); QKeySequence seq(ke->key()|ke->modifiers()); QList actions = applet()->actions()->actions(); //find the wallpaper action if we are a containment ContainmentInterface *ci = qobject_cast(this); if (ci) { WallpaperInterface *wi = ci->wallpaperInterface(); if (wi) { actions << wi->contextualActions(); } } //add any actions of the corona if (applet()->containment() && applet()->containment()->corona()) { actions << applet()->containment()->corona()->actions()->actions(); } bool keySequenceUsed = false; - foreach (auto a, actions) { + for (auto a : qAsConst(actions)) { if (a->shortcut().isEmpty()) { continue; } //this will happen on a normal, non emacs shortcut if (seq.matches(a->shortcut()) == QKeySequence::ExactMatch) { event->accept(); a->trigger(); m_oldKeyboardShortcut = 0; return true; //first part of an emacs style shortcut? } else if (seq.matches(a->shortcut()) == QKeySequence::PartialMatch) { keySequenceUsed = true; m_oldKeyboardShortcut = ke->key()|ke->modifiers(); //no match at all, but it can be the second part of an emacs style shortcut } else { QKeySequence seq(m_oldKeyboardShortcut, ke->key()|ke->modifiers()); if (seq.matches(a->shortcut()) == QKeySequence::ExactMatch) { event->accept(); a->trigger(); return true; } } } if (!keySequenceUsed) { m_oldKeyboardShortcut = 0; } } return AppletQuickItem::event(event); } void AppletInterface::prepareContextualActions() { emit applet()->contextualActionsAboutToShow(); } bool AppletInterface::eventFilter(QObject *watched, QEvent *event) { if (event->type() == QEvent::MouseButtonPress) { QMouseEvent *e = static_cast(event); //pass it up to the applet //well, actually we have to pass it to the *containment* //because all the code for showing an applet's contextmenu is actually in Containment. Plasma::Containment *c = applet()->containment(); if (c) { const QString trigger = Plasma::ContainmentActions::eventToString(event); Plasma::ContainmentActions *plugin = c->containmentActions().value(trigger); if (!plugin) { return false; } ContainmentInterface *ci = c->property("_plasma_graphicObject").value(); if (!ci) { return false; } //the plugin can be a single action or a context menu //Don't have an action list? execute as single action //and set the event position as action data if (plugin->contextualActions().length() == 1) { // but first check whether we are not a popup // we don't want to randomly creates applets without confirmation if (static_cast(watched)->window() != ci->window()) { return true; } QAction *action = plugin->contextualActions().at(0); action->setData(e->globalPos()); action->trigger(); return true; } QMenu *desktopMenu = new QMenu; if (desktopMenu->winId()) { desktopMenu->windowHandle()->setTransientParent(window()); } prepareContextualActions(); ci->addAppletActions(desktopMenu, applet(), event); if (!desktopMenu->isEmpty()) { desktopMenu->setAttribute(Qt::WA_DeleteOnClose); desktopMenu->popup(e->globalPos()); return true; } delete desktopMenu; return false; } } return AppletQuickItem::eventFilter(watched, event); } void AppletInterface::updateUiReadyConstraint() { if (!isLoading()) { applet()->updateConstraints(Plasma::Types::UiReadyConstraint); } } bool AppletInterface::isLoading() const { return m_loading; } #include "moc_appletinterface.cpp" diff --git a/src/scriptengines/qml/plasmoid/containmentinterface.cpp b/src/scriptengines/qml/plasmoid/containmentinterface.cpp index 68ac59061..89b3a744f 100644 --- a/src/scriptengines/qml/plasmoid/containmentinterface.cpp +++ b/src/scriptengines/qml/plasmoid/containmentinterface.cpp @@ -1,1148 +1,1152 @@ /* * Copyright 2008 Chani Armitage * Copyright 2008, 2009 Aaron Seigo * Copyright 2010 Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "containmentinterface.h" #include "wallpaperinterface.h" #include "dropmenu.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kdeclarative/configpropertymap.h" #include ContainmentInterface::ContainmentInterface(DeclarativeAppletScript *parent, const QVariantList &args) : AppletInterface(parent, args), m_wallpaperInterface(nullptr), m_activityInfo(nullptr), m_wheelDelta(0) { m_containment = static_cast(appletScript()->applet()->containment()); setAcceptedMouseButtons(Qt::AllButtons); connect(m_containment.data(), &Plasma::Containment::appletRemoved, this, &ContainmentInterface::appletRemovedForward); connect(m_containment.data(), &Plasma::Containment::appletAdded, this, &ContainmentInterface::appletAddedForward); connect(m_containment->corona(), &Plasma::Corona::editModeChanged, this, &ContainmentInterface::editModeChanged); if (!m_appletInterfaces.isEmpty()) { emit appletsChanged(); } } void ContainmentInterface::init() { if (qmlObject()->rootObject()) { return; } m_activityInfo = new KActivities::Info(m_containment->activity(), this); connect(m_activityInfo, &KActivities::Info::nameChanged, this, &ContainmentInterface::activityNameChanged); emit activityNameChanged(); if (!m_containment->wallpaper().isEmpty()) { loadWallpaper(); } AppletInterface::init(); //Create the ToolBox if (m_containment) { KConfigGroup defaults; if (m_containment->containmentType() == Plasma::Types::DesktopContainment) { defaults = KConfigGroup(KSharedConfig::openConfig(m_containment->corona()->kPackage().filePath("defaults")), "Desktop"); } else if (m_containment->containmentType() == Plasma::Types::PanelContainment) { defaults = KConfigGroup(KSharedConfig::openConfig(m_containment->corona()->kPackage().filePath("defaults")), "Panel"); } if (defaults.isValid()) { KPackage::Package pkg = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("KPackage/GenericQML")); pkg.setDefaultPackageRoot(QStringLiteral("plasma/packages")); if (defaults.isValid()) { pkg.setPath(defaults.readEntry("ToolBox", "org.kde.desktoptoolbox")); } else { pkg.setPath(QStringLiteral("org.kde.desktoptoolbox")); } PlasmaQuick::PackageUrlInterceptor *interceptor = dynamic_cast(qmlObject()->engine()->urlInterceptor()); if (interceptor) { interceptor->addAllowedPath(pkg.path()); } if (pkg.metadata().isValid() && !pkg.metadata().isHidden()) { if (pkg.isValid()) { QObject *containmentGraphicObject = qmlObject()->rootObject(); QVariantHash toolboxProperties; toolboxProperties[QStringLiteral("parent")] = QVariant::fromValue(this); QObject *toolBoxObject = qmlObject()->createObjectFromSource(pkg.fileUrl("mainscript"), nullptr, toolboxProperties); if (toolBoxObject && containmentGraphicObject) { containmentGraphicObject->setProperty("toolBox", QVariant::fromValue(toolBoxObject)); } } else { qWarning() << "Could not load toolbox package." << pkg.path(); } } else { qWarning() << "Toolbox not loading, toolbox package is either invalid or disabled."; } } } //set parent, both as object hierarchically and visually //do this only for containments, applets will do it in compactrepresentationcheck if (qmlObject()->rootObject()) { qmlObject()->rootObject()->setProperty("parent", QVariant::fromValue(this)); //set anchors QQmlExpression expr(qmlObject()->engine()->rootContext(), qmlObject()->rootObject(), QStringLiteral("parent")); QQmlProperty prop(qmlObject()->rootObject(), QStringLiteral("anchors.fill")); prop.write(expr.evaluate()); } connect(m_containment.data(), &Plasma::Containment::activityChanged, this, &ContainmentInterface::activityChanged); connect(m_containment.data(), &Plasma::Containment::activityChanged, this, [ = ]() { delete m_activityInfo; m_activityInfo = new KActivities::Info(m_containment->activity(), this); connect(m_activityInfo, &KActivities::Info::nameChanged, this, &ContainmentInterface::activityNameChanged); emit activityNameChanged(); }); connect(m_containment.data(), &Plasma::Containment::wallpaperChanged, this, &ContainmentInterface::loadWallpaper); connect(m_containment.data(), &Plasma::Containment::containmentTypeChanged, this, &ContainmentInterface::containmentTypeChanged); connect(m_containment.data()->actions(), &KActionCollection::changed, this, &ContainmentInterface::actionsChanged); } QList ContainmentInterface::applets() { return m_appletInterfaces; } Plasma::Types::ContainmentType ContainmentInterface::containmentType() const { return appletScript()->containmentType(); } void ContainmentInterface::setContainmentType(Plasma::Types::ContainmentType type) { appletScript()->setContainmentType(type); } Plasma::Applet *ContainmentInterface::createApplet(const QString &plugin, const QVariantList &args, const QPoint &pos) { return createApplet(plugin, args, QRectF(pos, QSize())); } Plasma::Applet *ContainmentInterface::createApplet(const QString &plugin, const QVariantList &args, const QRectF &geom) { //HACK //This is necessary to delay the appletAdded signal (of containmentInterface) AFTER the applet graphics object has been created blockSignals(true); Plasma::Applet *applet = m_containment->createApplet(plugin, args); if (applet) { QQuickItem *appletGraphicObject = applet->property("_plasma_graphicObject").value(); //invalid applet? if (!appletGraphicObject) { blockSignals(false); return applet; } if (geom.width() > 0 && geom.height() > 0) { appletGraphicObject->setSize(geom.size()); } blockSignals(false); emit appletAdded(appletGraphicObject, geom.x(), geom.y()); emit appletsChanged(); } else { blockSignals(false); } return applet; } void ContainmentInterface::setAppletArgs(Plasma::Applet *applet, const QString &mimetype, const QString &data) { if (!applet) { return; } AppletInterface *appletInterface = applet->property("_plasma_graphicObject").value(); if (appletInterface) { emit appletInterface->externalData(mimetype, data); } } QObject *ContainmentInterface::containmentAt(int x, int y) { QObject *desktop = nullptr; - foreach (Plasma::Containment *c, m_containment->corona()->containments()) { + const auto lst = m_containment->corona()->containments(); + for (Plasma::Containment *c : lst) { ContainmentInterface *contInterface = c->property("_plasma_graphicObject").value(); if (contInterface && contInterface->isVisible()) { QWindow *w = contInterface->window(); if (w && w->geometry().contains(QPoint(window()->x(), window()->y()) + QPoint(x, y))) { if (c->containmentType() == Plasma::Types::CustomEmbeddedContainment) { continue; } if (c->containmentType() == Plasma::Types::DesktopContainment) { desktop = contInterface; } else { return contInterface; } } } } return desktop; } void ContainmentInterface::addApplet(AppletInterface *applet, int x, int y) { if (!applet || applet->applet()->containment() == m_containment) { return; } blockSignals(true); m_containment->addApplet(applet->applet()); blockSignals(false); emit appletAdded(applet, x, y); } QPointF ContainmentInterface::mapFromApplet(AppletInterface *applet, int x, int y) { if (!applet->window() || !window()) { return QPointF(); } //x,y in absolute screen coordinates of current view QPointF pos = applet->mapToScene(QPointF(x, y)); pos = QPointF(pos + applet->window()->geometry().topLeft()); //return the coordinate in the relative view's coords return pos - window()->geometry().topLeft(); } QPointF ContainmentInterface::mapToApplet(AppletInterface *applet, int x, int y) { if (!applet->window() || !window()) { return QPointF(); } //x,y in absolute screen coordinates of current view QPointF pos(x, y); pos = QPointF(pos + window()->geometry().topLeft()); //the coordinate in the relative view's coords pos = pos - applet->window()->geometry().topLeft(); //make it relative to applet coords return pos - applet->mapToScene(QPointF(0, 0)); } QPointF ContainmentInterface::adjustToAvailableScreenRegion(int x, int y, int w, int h) const { QRegion reg; int screenId = screen(); if (screenId > -1 && m_containment->corona()) { reg = m_containment->corona()->availableScreenRegion(screenId); } if (!reg.isEmpty()) { //make it relative QRect geometry = m_containment->corona()->screenGeometry(screenId); reg.translate(- geometry.topLeft()); } else { reg = QRect(0, 0, width(), height()); } const QRect rect(qBound(reg.boundingRect().left(), x, reg.boundingRect().right() + 1 - w), qBound(reg.boundingRect().top(), y, reg.boundingRect().bottom() + 1 - h), w, h); const QRectF ar = availableScreenRect(); QRect tempRect(rect); // in the case we are in the topleft quadrant // * see if the passed rect is completely in the region, if yes, return // * otherwise, try to move it horizontally to the screenrect x // * if now fits, return // * if fail, move vertically // * as last resort, move horizontally and vertically // top left corner if (rect.center().x() <= ar.center().x() && rect.center().y() <= ar.center().y()) { //QRegion::contains doesn't do what it would suggest, so do reg.intersected(rect) != rect instead if (reg.intersected(rect) != rect) { tempRect = QRect(qMax(rect.left(), (int)ar.left()), rect.top(), w, h); if (reg.intersected(tempRect) == tempRect) { return tempRect.topLeft(); } tempRect = QRect(rect.left(), qMax(rect.top(), (int)ar.top()), w, h); if (reg.intersected(tempRect) == tempRect) { return tempRect.topLeft(); } tempRect = QRect(qMax(rect.left(), (int)ar.left()), qMax(rect.top(), (int)ar.top()), w, h); return tempRect.topLeft(); } else { return rect.topLeft(); } //bottom left corner } else if (rect.center().x() <= ar.center().x() && rect.center().y() > ar.center().y()) { if (reg.intersected(rect) != rect) { tempRect = QRect(qMax(rect.left(), (int)ar.left()), rect.top(), w, h); if (reg.intersected(tempRect) == tempRect) { return tempRect.topLeft(); } tempRect = QRect(rect.left(), qMin(rect.top(), (int)(ar.bottom() + 1 - h)), w, h); if (reg.intersected(tempRect) == tempRect) { return tempRect.topLeft(); } tempRect = QRect(qMax(rect.left(), (int)ar.left()), qMin(rect.top(), (int)(ar.bottom() + 1 - h)), w, h); return tempRect.topLeft(); } else { return rect.topLeft(); } //top right corner } else if (rect.center().x() > ar.center().x() && rect.center().y() <= ar.center().y()) { if (reg.intersected(rect) != rect) { tempRect = QRect(qMin(rect.left(), (int)(ar.right() + 1 - w)), rect.top(), w, h); if (reg.intersected(tempRect) == tempRect) { return tempRect.topLeft(); } tempRect = QRect(rect.left(), qMax(rect.top(), (int)ar.top()), w, h); if (reg.intersected(tempRect) == tempRect) { return tempRect.topLeft(); } tempRect = QRect(qMin(rect.left(), (int)(ar.right() + 1 - w)), qMax(rect.top(), (int)ar.top()), w, h); return tempRect.topLeft(); } else { return rect.topLeft(); } //bottom right corner } else if (rect.center().x() > ar.center().x() && rect.center().y() > ar.center().y()) { if (reg.intersected(rect) != rect) { tempRect = QRect(qMin(rect.left(), (int)(ar.right() + 1 - w)), rect.top(), w, h); if (reg.intersected(tempRect) == tempRect) { return tempRect.topLeft(); } tempRect = QRect(rect.left(), qMin(rect.top(), (int)(ar.bottom() + 1 - h)), w, h); if (reg.intersected(tempRect) == tempRect) { return tempRect.topLeft(); } tempRect = QRect(qMin(rect.left(), (int)(ar.right() + 1 - w)), qMin(rect.top(), (int)(ar.bottom() + 1 - h)), w, h); return tempRect.topLeft(); } else { return rect.topLeft(); } } return rect.topLeft(); } QAction *ContainmentInterface::globalAction(QString name) const { return m_containment->corona()->actions()->action(name); } bool ContainmentInterface::isEditMode() const { return m_containment->corona()->isEditMode(); } void ContainmentInterface::setEditMode(bool edit) { m_containment->corona()->setEditMode(edit); } void ContainmentInterface::processMimeData(QObject *mimeDataProxy, int x, int y, KIO::DropJob *dropJob) { QMimeData* mime = qobject_cast(mimeDataProxy); if (mime) { processMimeData(mime, x, y, dropJob); } else { processMimeData(mimeDataProxy->property("mimeData").value(), x, y, dropJob); } } void ContainmentInterface::processMimeData(QMimeData *mimeData, int x, int y, KIO::DropJob *dropJob) { if (!mimeData) { return; } if (m_dropMenu) { if (dropJob) { dropJob->kill(); } return; } m_dropMenu = QPointer(new DropMenu(dropJob, QPoint(x, y), this)); if (dropJob) { dropJob->setParent(m_dropMenu); } //const QMimeData *mimeData = data; qDebug() << "Arrived mimeData" << mimeData->urls() << mimeData->formats() << "at" << x << ", " << y; // Catch drops from a Task Manager and convert to usable URL. if (!mimeData->hasUrls() && mimeData->hasFormat(QStringLiteral("text/x-orgkdeplasmataskmanager_taskurl"))) { QList urls = {QUrl(QString::fromUtf8(mimeData->data(QStringLiteral("text/x-orgkdeplasmataskmanager_taskurl"))))}; mimeData->setUrls(urls); } if (mimeData->hasFormat(QStringLiteral("text/x-plasmoidservicename"))) { QString data = QString::fromUtf8( mimeData->data(QStringLiteral("text/x-plasmoidservicename")) ); const QStringList appletNames = data.split(QLatin1Char('\n'), QString::SkipEmptyParts); - foreach (const QString &appletName, appletNames) { + for (const QString &appletName : appletNames) { qDebug() << "adding" << appletName; metaObject()->invokeMethod(this, "createApplet", Qt::QueuedConnection, Q_ARG(QString, appletName), Q_ARG(QVariantList, QVariantList()), Q_ARG(QRectF, QRectF(x, y, -1, -1))); } delete m_dropMenu.data(); } else if (mimeData->hasUrls()) { //TODO: collect the mimetypes of available script engines and offer // to create widgets out of the matching URLs, if any const QList urls = KUrlMimeData::urlsFromMimeData(mimeData); m_dropMenu->setUrls(urls); if (!urls.at(0).isLocalFile()) { QApplication::setOverrideCursor(Qt::WaitCursor); } QMimeDatabase db; QMimeType firstMimetype = db.mimeTypeForUrl(urls.at(0)); - foreach (const QUrl &url, urls) { + for (const QUrl &url : urls) { if (firstMimetype != db.mimeTypeForUrl(url)) { m_dropMenu->setMultipleMimetypes(true); break; } } // It may be a directory or a file, let's stat KIO::JobFlags flags = KIO::HideProgressInfo; KIO::MimetypeJob *job = KIO::mimetype(m_dropMenu->urls().at(0), flags); job->setParent(m_dropMenu.data()); QObject::connect(job, &KJob::result, this, &ContainmentInterface::dropJobResult); QObject::connect(job, SIGNAL(mimetype(KIO::Job*, QString)), this, SLOT(mimeTypeRetrieved(KIO::Job*,QString))); } else { bool deleteDropMenu = true; - QStringList formats = mimeData->formats(); + const QStringList formats = mimeData->formats(); QHash seenPlugins; QHash pluginFormats; - foreach (const QString &format, formats) { + for (const QString &format : formats) { const auto plugins = Plasma::PluginLoader::self()->listAppletMetaDataForMimeType(format); - foreach (const auto &plugin, plugins) { + for (const auto &plugin : plugins) { if (seenPlugins.contains(plugin.pluginId())) { continue; } seenPlugins.insert(plugin.pluginId(), plugin); pluginFormats.insert(plugin.pluginId(), format); } } //qDebug() << "Mimetype ..." << formats << seenPlugins.keys() << pluginFormats.values(); QString selectedPlugin; if (seenPlugins.isEmpty()) { //do nothing //directly create if only one offer only if the containment didn't pass an existing plugin } else if (seenPlugins.count() == 1) { selectedPlugin = seenPlugins.constBegin().key(); Plasma::Applet *applet = createApplet(selectedPlugin, QVariantList(), QRect(x, y, -1, -1)); setAppletArgs(applet, pluginFormats[selectedPlugin], QString::fromUtf8(mimeData->data(pluginFormats[selectedPlugin]))); } else { QHash actionsToPlugins; - foreach (const auto &info, seenPlugins) { + for (const auto &info : qAsConst(seenPlugins)) { QAction *action; if (!info.iconName().isEmpty()) { action = new QAction(QIcon::fromTheme(info.iconName()), info.name(), m_dropMenu); } else { action = new QAction(info.name(), m_dropMenu); } m_dropMenu->addAction(action); action->setData(info.pluginId()); connect(action, &QAction::triggered, this, [this, x, y, mimeData, action]() { const QString selectedPlugin = action->data().toString(); Plasma::Applet *applet = createApplet(selectedPlugin, QVariantList(), QRect(x, y, -1, -1)); setAppletArgs(applet, selectedPlugin, QString::fromUtf8(mimeData->data(selectedPlugin))); }); actionsToPlugins.insert(action, info.pluginId()); } m_dropMenu->show(); deleteDropMenu = false; } if (deleteDropMenu) { // in case m_dropMenu has not been shown delete m_dropMenu.data(); } } } void ContainmentInterface::clearDataForMimeJob(KIO::Job *job) { QObject::disconnect(job, nullptr, this, nullptr); job->kill(); m_dropMenu->show(); if (!m_dropMenu->urls().at(0).isLocalFile()) { QApplication::restoreOverrideCursor(); } } void ContainmentInterface::dropJobResult(KJob *job) { if (job->error()) { qDebug() << "ERROR" << job->error() << ' ' << job->errorString(); clearDataForMimeJob(dynamic_cast(job)); } } void ContainmentInterface::mimeTypeRetrieved(KIO::Job *job, const QString &mimetype) { qDebug() << "Mimetype Job returns." << mimetype; KIO::TransferJob *tjob = dynamic_cast(job); if (!tjob) { qDebug() << "job should be a TransferJob, but isn't"; clearDataForMimeJob(job); return; } QList appletList = Plasma::PluginLoader::self()->listAppletMetaDataForUrl(tjob->url()); if (mimetype.isEmpty() && appletList.isEmpty()) { clearDataForMimeJob(job); qDebug() << "No applets found matching the url (" << tjob->url() << ") or the mimetype (" << mimetype << ")"; return; } else { qDebug() << "Received a suitable dropEvent at " << m_dropMenu->dropPoint(); qDebug() << "Bailing out. Cannot find associated dropEvent related to the TransferJob"; qDebug() << "Creating menu for: " << mimetype; appletList << Plasma::PluginLoader::self()->listAppletMetaDataForMimeType(mimetype); QList wallpaperList; if (m_containment->containmentType() != Plasma::Types::PanelContainment && m_containment->containmentType() != Plasma::Types::CustomPanelContainment) { if (m_wallpaperInterface && m_wallpaperInterface->supportsMimetype(mimetype)) { wallpaperList << m_wallpaperInterface->kPackage().metadata(); } else { wallpaperList = WallpaperInterface::listWallpaperMetadataForMimetype(mimetype); } } const bool isPlasmaPackage = (mimetype == QLatin1String("application/x-plasma")); if ((!appletList.isEmpty() || !wallpaperList.isEmpty() || isPlasmaPackage) && !m_dropMenu->isMultipleMimetypes()) { QAction *installPlasmaPackageAction = nullptr; if (isPlasmaPackage) { QAction *action = new QAction(i18n("Plasma Package"), m_dropMenu); action->setSeparator(true); m_dropMenu->addAction(action); installPlasmaPackageAction = new QAction(QIcon::fromTheme(QStringLiteral("application-x-plasma")), i18n("Install"), m_dropMenu); m_dropMenu->addAction(installPlasmaPackageAction); const QString &packagePath = tjob->url().toLocalFile(); connect(installPlasmaPackageAction, &QAction::triggered, this, [this, packagePath]() { using namespace KPackage; PackageStructure *structure = PackageLoader::self()->loadPackageStructure(QStringLiteral("Plasma/Applet")); Package package(structure); KJob *installJob = package.update(packagePath); connect(installJob, &KJob::result, this, [this, packagePath, structure](KJob *job) { auto fail = [](const QString &text) { KNotification::event(QStringLiteral("plasmoidInstallationFailed"), i18n("Package Installation Failed"), text, QStringLiteral("dialog-error"), nullptr, KNotification::CloseOnTimeout, QStringLiteral("plasma_workspace")); }; // if the applet is already installed, just add it to the containment if (job->error() != KJob::NoError && job->error() != Package::PackageAlreadyInstalledError && job->error() != Package::NewerVersionAlreadyInstalledError) { fail(job->errorText()); return; } using namespace KPackage; Package package(structure); // TODO how can I get the path of the actual package? package.setPath(packagePath); // TODO how can I get the plugin id? Package::metadata() is deprecated if (!package.isValid() || !package.metadata().isValid()) { fail(i18n("The package you just dropped is invalid.")); return; } createApplet(package.metadata().pluginId(), QVariantList(), QRect(m_dropMenu->dropPoint(), QSize(-1,-1))); }); }); } QAction *action = new QAction(i18n("Widgets"), m_dropMenu); action->setSeparator(true); m_dropMenu->addAction(action); - foreach (const auto &info, appletList) { + for (const auto &info : qAsConst(appletList)) { const QString actionText = i18nc("Add widget", "Add %1", info.name()); QAction *action = new QAction(actionText, m_dropMenu); if (!info.iconName().isEmpty()) { action->setIcon(QIcon::fromTheme(info.iconName())); } m_dropMenu->addAction(action); action->setData(info.pluginId()); const QUrl url = tjob->url(); connect(action, &QAction::triggered, this, [this, action, mimetype, url]() { Plasma::Applet *applet = createApplet(action->data().toString(), QVariantList(), QRect(m_dropMenu->dropPoint(), QSize(-1,-1))); setAppletArgs(applet, mimetype, url.toString()); }); } { QAction *action = new QAction(i18nc("Add icon widget", "Add Icon"), m_dropMenu); m_dropMenu->addAction(action); action->setData(QStringLiteral("org.kde.plasma.icon")); const QUrl url = tjob->url(); connect(action, &QAction::triggered, this, [this, action, mimetype, url](){ Plasma::Applet *applet = createApplet(action->data().toString(), QVariantList(), QRect(m_dropMenu->dropPoint(), QSize(-1,-1))); setAppletArgs(applet, mimetype, url.toString()); }); } QHash actionsToWallpapers; if (!wallpaperList.isEmpty()) { QAction *action = new QAction(i18n("Wallpaper"), m_dropMenu); action->setSeparator(true); m_dropMenu->addAction(action); QMap sorted; - foreach (const auto &info, appletList) { + for (const auto &info : qAsConst(appletList)) { sorted.insert(info.name(), info); } - foreach (const KPluginMetaData &info, wallpaperList) { + for (const KPluginMetaData &info : qAsConst(wallpaperList)) { const QString actionText = i18nc("Set wallpaper", "Set %1", info.name()); QAction *action = new QAction(actionText, m_dropMenu); if (!info.iconName().isEmpty()) { action->setIcon(QIcon::fromTheme(info.iconName())); } m_dropMenu->addAction(action); actionsToWallpapers.insert(action, info.pluginId()); const QUrl url = tjob->url(); connect(action, &QAction::triggered, this, [this, url]() { //set wallpapery stuff if (m_wallpaperInterface && url.isValid()) { m_wallpaperInterface->setUrl(url); } }); } } } else { //case in which we created the menu ourselves, just the "fetching type entry, directly create the icon applet if (!m_dropMenu->isDropjobMenu()) { Plasma::Applet *applet = createApplet(QStringLiteral("org.kde.plasma.icon"), QVariantList(), QRect(m_dropMenu->dropPoint(), QSize(-1,-1))); setAppletArgs(applet, mimetype, tjob->url().toString()); } else { QAction *action; QAction *sep = new QAction(i18n("Widgets"), m_dropMenu); sep->setSeparator(true); m_dropMenu->addAction(sep); // we can at least create an icon as a link to the URL action = new QAction(i18nc("Add icon widget", "Add Icon"), m_dropMenu); m_dropMenu->addAction(action); const QUrl url = tjob->url(); connect(action, &QAction::triggered, this, [this, mimetype, url](){ Plasma::Applet *applet = createApplet(QStringLiteral("org.kde.plasma.icon"), QVariantList(), QRect(m_dropMenu->dropPoint(), QSize(-1,-1))); setAppletArgs(applet, mimetype, url.toString()); }); } } clearDataForMimeJob(tjob); } } void ContainmentInterface::appletAddedForward(Plasma::Applet *applet) { if (!applet) { return; } AppletInterface *appletGraphicObject = applet->property("_plasma_graphicObject").value(); AppletInterface *contGraphicObject = m_containment->property("_plasma_graphicObject").value(); // qDebug() << "Applet added on containment:" << m_containment->title() << contGraphicObject // << "Applet: " << applet << applet->title() << appletGraphicObject; //applets can not have a graphic object if they don't have a script engine loaded //this can happen if they were loaded with an invalid metadata if (!appletGraphicObject) { return; } if (contGraphicObject) { appletGraphicObject->setProperty("visible", false); appletGraphicObject->setProperty("parent", QVariant::fromValue(contGraphicObject)); } m_appletInterfaces << appletGraphicObject; connect(appletGraphicObject, &QObject::destroyed, this, [this](QObject *obj) { m_appletInterfaces.removeAll(obj); }); emit appletAdded(appletGraphicObject, appletGraphicObject->m_positionBeforeRemoval.x(), appletGraphicObject->m_positionBeforeRemoval.y()); emit appletsChanged(); } void ContainmentInterface::appletRemovedForward(Plasma::Applet *applet) { AppletInterface *appletGraphicObject = applet->property("_plasma_graphicObject").value(); if (appletGraphicObject) { m_appletInterfaces.removeAll(appletGraphicObject); appletGraphicObject->m_positionBeforeRemoval = appletGraphicObject->mapToItem(this, QPointF()); } emit appletRemoved(appletGraphicObject); emit appletsChanged(); } void ContainmentInterface::loadWallpaper() { if (m_containment->containmentType() != Plasma::Types::DesktopContainment && m_containment->containmentType() != Plasma::Types::CustomContainment) { return; } if (!m_wallpaperInterface && !m_containment->wallpaper().isEmpty()) { m_wallpaperInterface = new WallpaperInterface(this); m_wallpaperInterface->setZ(-1000); //Qml seems happier if the parent gets set in this way m_wallpaperInterface->setProperty("parent", QVariant::fromValue(this)); connect(m_wallpaperInterface, &WallpaperInterface::isLoadingChanged, this, &AppletInterface::updateUiReadyConstraint); //set anchors QQmlExpression expr(qmlObject()->engine()->rootContext(), m_wallpaperInterface, QStringLiteral("parent")); QQmlProperty prop(m_wallpaperInterface, QStringLiteral("anchors.fill")); prop.write(expr.evaluate()); m_containment->setProperty("wallpaperGraphicsObject", QVariant::fromValue(m_wallpaperInterface)); } else if (m_wallpaperInterface && m_containment->wallpaper().isEmpty()) { m_wallpaperInterface->deleteLater(); m_wallpaperInterface = nullptr; } } QString ContainmentInterface::activity() const { return m_containment->activity(); } QString ContainmentInterface::activityName() const { if (!m_activityInfo) { return QString(); } return m_activityInfo->name(); } QList ContainmentInterface::actions() const { //FIXME: giving directly a QList crashes QStringList actionOrder; actionOrder << QStringLiteral("add widgets") << QStringLiteral("manage activities") << QStringLiteral("remove") << QStringLiteral("lock widgets") << QStringLiteral("run associated application") << QStringLiteral("configure"); QHash orderedActions; //use a multimap to sort by action type QMultiMap actions; int i = 0; - foreach (QAction *a, m_containment->actions()->actions()) { + auto listActions = m_containment->actions()->actions(); + for (QAction *a : qAsConst(listActions)) { if (!actionOrder.contains(a->objectName())) { //FIXME QML visualizations don't support menus for now, *and* there is no way to //distinguish them on QML side if (!a->menu()) { actions.insert(a->data().toInt()*100 + i, a); ++i; } } else { orderedActions[a->objectName()] = a; } } i = 0; - foreach (QAction *a, m_containment->corona()->actions()->actions()) { + listActions = m_containment->corona()->actions()->actions(); + for (QAction *a : qAsConst(listActions)) { if (a->objectName() == QLatin1String("lock widgets") || a->menu()) { //It is up to the Containment to decide if the user is allowed or not //to lock/unluck the widgets, so corona should not add one when there is none //(user is not allow) and it shouldn't add another one when there is already //one continue; } if (!actionOrder.contains(a->objectName())) { actions.insert(a->data().toInt()*100 + i, a); } else { orderedActions[a->objectName()] = a; } ++i; } QList actionList = actions.values(); - foreach (const QString &name, actionOrder) { + for (const QString &name : qAsConst(actionOrder)) { QAction *a = orderedActions.value(name); if (a && !a->menu()) { actionList << a; } ++i; } return actionList; } //PROTECTED-------------------- void ContainmentInterface::mouseReleaseEvent(QMouseEvent *event) { event->setAccepted(m_containment->containmentActions().contains(Plasma::ContainmentActions::eventToString(event))); } void ContainmentInterface::mousePressEvent(QMouseEvent *event) { //even if the menu is executed synchronously, other events may be processed //by the qml incubator when plasma is loading, so we need to guard there if (m_contextMenu) { m_contextMenu.data()->close(); return; } const QString trigger = Plasma::ContainmentActions::eventToString(event); Plasma::ContainmentActions *plugin = m_containment->containmentActions().value(trigger); if (!plugin || plugin->contextualActions().isEmpty()) { event->setAccepted(false); return; } //the plugin can be a single action or a context menu //Don't have an action list? execute as single action //and set the event position as action data if (plugin->contextualActions().length() == 1) { QAction *action = plugin->contextualActions().at(0); action->setData(event->pos()); action->trigger(); event->accept(); return; } //FIXME: very inefficient appletAt() implementation Plasma::Applet *applet = nullptr; - foreach (QObject *appletObject, m_appletInterfaces) { + for (QObject *appletObject : qAsConst(m_appletInterfaces)) { if (AppletInterface *ai = qobject_cast(appletObject)) { if (ai->isVisible() && ai->contains(ai->mapFromItem(this, event->localPos()))) { applet = ai->applet(); break; } else { ai = nullptr; } } } //qDebug() << "Invoking menu for applet" << applet; QMenu *desktopMenu = new QMenu; //this is a workaround where Qt now creates the menu widget //in .exec before oxygen can polish it and set the following attribute desktopMenu->setAttribute(Qt::WA_TranslucentBackground); //end workaround if (desktopMenu->winId()) { desktopMenu->windowHandle()->setTransientParent(window()); } desktopMenu->setAttribute(Qt::WA_DeleteOnClose); m_contextMenu = desktopMenu; emit m_containment->contextualActionsAboutToShow(); if (applet) { emit applet->contextualActionsAboutToShow(); addAppletActions(desktopMenu, applet, event); } else { addContainmentActions(desktopMenu, event); } //this is a workaround where Qt will fail to realize a mouse has been released // this happens if a window which does not accept focus spawns a new window that takes focus and X grab // whilst the mouse is depressed // https://bugreports.qt.io/browse/QTBUG-59044 // this causes the next click to go missing //by releasing manually we avoid that situation auto ungrabMouseHack = [this]() { if (window() && window()->mouseGrabberItem()) { window()->mouseGrabberItem()->ungrabMouse(); } }; //pre 5.8.0 QQuickWindow code is "item->grabMouse(); sendEvent(item, mouseEvent)" //post 5.8.0 QQuickWindow code is sendEvent(item, mouseEvent); item->grabMouse() if (QVersionNumber::fromString(QLatin1String(qVersion())) > QVersionNumber(5, 8, 0)) { QTimer::singleShot(0, this, ungrabMouseHack); } else { ungrabMouseHack(); } //end workaround QPoint pos = event->globalPos(); if (window() && m_containment->containmentType() == Plasma::Types::PanelContainment) { desktopMenu->adjustSize(); if (QScreen *screen = window()->screen()) { const QRect geo = screen->availableGeometry(); pos = QPoint(qBound(geo.left(), pos.x(), geo.right() + 1 - desktopMenu->width()), qBound(geo.top(), pos.y(), geo.bottom() + 1 - desktopMenu->height())); } } if (desktopMenu->isEmpty()) { delete desktopMenu; event->accept(); return; } // Bug 344205 keep panel visible while menu is open const auto oldStatus = m_containment->status(); m_containment->setStatus(Plasma::Types::RequiresAttentionStatus); connect(desktopMenu, &QMenu::aboutToHide, m_containment, [this, oldStatus] { m_containment->setStatus(oldStatus); }); KAcceleratorManager::manage(desktopMenu); desktopMenu->popup(pos); event->setAccepted(true); } void ContainmentInterface::wheelEvent(QWheelEvent *event) { const QString trigger = Plasma::ContainmentActions::eventToString(event); Plasma::ContainmentActions *plugin = m_containment->containmentActions().value(trigger); if (!plugin) { event->setAccepted(false); return; } m_wheelDelta += event->angleDelta().y(); // Angle delta 120 for common "one click" // See: https://doc.qt.io/qt-5/qml-qtquick-wheelevent.html#angleDelta-prop while (m_wheelDelta >= 120) { m_wheelDelta -= 120; plugin->performPreviousAction(); } while (m_wheelDelta <= -120) { m_wheelDelta += 120; plugin->performNextAction(); } } void ContainmentInterface::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Menu) { QMouseEvent me(QEvent::MouseButtonRelease, QPoint(), Qt::RightButton, Qt::RightButton, event->modifiers()); mousePressEvent(&me); event->accept(); } AppletInterface::keyPressEvent(event); } void ContainmentInterface::addAppletActions(QMenu *desktopMenu, Plasma::Applet *applet, QEvent *event) { - foreach (QAction *action, applet->contextualActions()) { + const auto listActions = applet->contextualActions(); + for (QAction *action : listActions) { if (action) { desktopMenu->addAction(action); } } if (!applet->failedToLaunch()) { QAction *runAssociatedApplication = applet->actions()->action(QStringLiteral("run associated application")); if (runAssociatedApplication && runAssociatedApplication->isEnabled()) { desktopMenu->addAction(runAssociatedApplication); } QAction *configureApplet = applet->actions()->action(QStringLiteral("configure")); if (configureApplet && configureApplet->isEnabled()) { desktopMenu->addAction(configureApplet); } QAction *appletAlternatives = applet->actions()->action(QStringLiteral("alternatives")); if (appletAlternatives && appletAlternatives->isEnabled()) { desktopMenu->addAction(appletAlternatives); } } desktopMenu->addSeparator(); if (m_containment->containmentType() == Plasma::Types::DesktopContainment) { auto action = m_containment->corona()->actions()->action(QStringLiteral("edit mode")); if (action) { desktopMenu->addAction(action); } } else { addContainmentActions(desktopMenu, event); } if (m_containment->immutability() == Plasma::Types::Mutable && (m_containment->containmentType() != Plasma::Types::PanelContainment || m_containment->isUserConfiguring())) { QAction *closeApplet = applet->actions()->action(QStringLiteral("remove")); //qDebug() << "checking for removal" << closeApplet; if (closeApplet) { if (!desktopMenu->isEmpty()) { desktopMenu->addSeparator(); } //qDebug() << "adding close action" << closeApplet->isEnabled() << closeApplet->isVisible(); desktopMenu->addAction(closeApplet); } } } void ContainmentInterface::addContainmentActions(QMenu *desktopMenu, QEvent *event) { if (m_containment->corona()->immutability() != Plasma::Types::Mutable && !KAuthorized::authorizeAction(QStringLiteral("plasma/containment_actions"))) { //qDebug() << "immutability"; return; } //this is what ContainmentPrivate::prepareContainmentActions was const QString trigger = Plasma::ContainmentActions::eventToString(event); Plasma::ContainmentActions *plugin = m_containment->containmentActions().value(trigger); if (!plugin) { return; } if (plugin->containment() != m_containment) { plugin->setContainment(m_containment); // now configure it KConfigGroup cfg(m_containment->corona()->config(), "ActionPlugins"); cfg = KConfigGroup(&cfg, QString::number(m_containment->containmentType())); KConfigGroup pluginConfig = KConfigGroup(&cfg, trigger); plugin->restore(pluginConfig); } QList actions = plugin->contextualActions(); if (actions.isEmpty()) { //it probably didn't bother implementing the function. give the user a chance to set //a better plugin. note that if the user sets no-plugin this won't happen... if ((m_containment->containmentType() != Plasma::Types::PanelContainment && m_containment->containmentType() != Plasma::Types::CustomPanelContainment) && m_containment->actions()->action(QStringLiteral("configure"))) { desktopMenu->addAction(m_containment->actions()->action(QStringLiteral("configure"))); } } else { desktopMenu->addActions(actions); } return; } bool ContainmentInterface::isLoading() const { bool loading = AppletInterface::isLoading(); if (m_wallpaperInterface) { loading |= m_wallpaperInterface->isLoading(); } return loading; } #include "moc_containmentinterface.cpp" diff --git a/src/scriptengines/qml/plasmoid/dropmenu.cpp b/src/scriptengines/qml/plasmoid/dropmenu.cpp index c72e910e3..2974ebe32 100644 --- a/src/scriptengines/qml/plasmoid/dropmenu.cpp +++ b/src/scriptengines/qml/plasmoid/dropmenu.cpp @@ -1,110 +1,108 @@ /* * Copyright 2008 Chani Armitage * Copyright 2008, 2009 Aaron Seigo * Copyright 2010 Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "dropmenu.h" #include "containmentinterface.h" #include #include #include #include #include #include #include #include #include DropMenu::DropMenu(KIO::DropJob *dropJob, const QPoint &dropPoint, ContainmentInterface *parent) : QObject(parent), m_dropPoint(dropPoint), m_dropJob(dropJob) { if (!dropJob) { m_menu = new QMenu(i18n("Content dropped")); if (m_menu->winId()) { m_menu->windowHandle()->setTransientParent(parent->window()); } connect(m_menu, &QMenu::aboutToHide, this, &QObject::deleteLater); } else { connect(m_dropJob, &QObject::destroyed, this, &QObject::deleteLater); } } DropMenu::~DropMenu() { if (m_menu) { delete m_menu; } } QList DropMenu::urls() const { return m_urls; } void DropMenu::setUrls(const QList &urls) { m_urls = urls; } QPoint DropMenu::dropPoint() const { return m_dropPoint; } void DropMenu::show() { if (m_dropJob) { m_dropJob->setApplicationActions(m_dropActions); m_dropJob->showMenu(m_dropPoint); } else if (m_menu) { - foreach (QAction *action, m_dropActions) { - m_menu->addAction(action); - } + m_menu->addActions(m_dropActions); m_menu->popup(m_dropPoint); } } void DropMenu::addAction(QAction *action) { m_dropActions << action; } bool DropMenu::isDropjobMenu() const { return (m_dropJob? true : false); } void DropMenu::setMultipleMimetypes(bool multipleMimetypes) { if (m_multipleMimetypes != multipleMimetypes) { m_multipleMimetypes = multipleMimetypes; } } bool DropMenu::isMultipleMimetypes() const { return m_multipleMimetypes; } #include "moc_dropmenu.cpp" diff --git a/tests/kplugins/plugintest.cpp b/tests/kplugins/plugintest.cpp index 828da79a3..828200c6f 100644 --- a/tests/kplugins/plugintest.cpp +++ b/tests/kplugins/plugintest.cpp @@ -1,188 +1,188 @@ /****************************************************************************** * Copyright 2013 Sebastian Kügler * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public License * * along with this library; see the file COPYING.LIB. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * *******************************************************************************/ #include "plugintest.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Plasma { class PluginTestPrivate { public: QString pluginName; QCommandLineParser *parser; }; PluginTest::PluginTest(int &argc, char **argv, QCommandLineParser *parser) : QApplication(argc, argv) // QApp needed for CursorNotificationHandler (QWidget) { d = new PluginTestPrivate; d->parser = parser; QTimer::singleShot(0, this, &PluginTest::runMain); } PluginTest::~PluginTest() { delete d; } void PluginTest::runMain() { qDebug() << "plugin test runs: "; //loadDataEngine(); // qDebug() << " - - - -- - - - - ------------------------------------\n"; // qDebug() << " libs are in: " << QCoreApplication::libraryPaths(); // //loadKQPlugin(); qDebug() << "::: loadKPlugin() == " << loadKPlugin(); // qDebug() << " - - - -- - - - - ------------------------------------\n"; qDebug() << "::: loadFromPlasma() == " << loadFromPlasma(); exit(0); return; } bool PluginTest::loadKPlugin() { bool ok = false; qDebug() << "Load KPlugin"; QString pluginPath = QStringLiteral("/home/sebas/kf5/install/lib/x86_64-linux-gnu/kplugins/"); QCoreApplication::addLibraryPath(pluginPath); //QPluginLoader loader("/home/sebas/kf5/install/lib/x86_64-linux-gnu/kplugins/libkqpluginfactory.so", this); QPluginLoader loader(QStringLiteral("/home/sebas/kf5/install/lib/x86_64-linux-gnu/plugins/kf5/kplugins/libplasma_engine_time.so"), this); KPluginFactory *factory = qobject_cast(loader.instance()); //QObject *factory = loader.instance(); if (factory) { qDebug() << "loaded successfully and cast"; qDebug() << "metadata: " << loader.metaData(); //QObject *o = factory->createPlugin("time"); //qDebug() << " objec name:" << o->objectName(); //Plasma::DataEngine *time_engine = qobject_cast(factory->create(this, QVariantList())); Plasma::DataEngine *time_engine = nullptr; // Plasma::DataEngine *time_engine = factory->create(this, QVariantList()); time_engine = factory->create(this, QVariantList()); if (time_engine) { qDebug() << "Successfully loaded timeengine"; time_engine->connectSource(QStringLiteral("Europe/Amsterdam"), this); qDebug() << "SOURCE: " << time_engine->sources(); ok = true; } else { qDebug() << "Timeengine failed to load. :("; } } else { qDebug() << "loading failed somehow"; } //KQPluginFactory* factory = new KQPluginFactory(KPluginInfo(), this); return ok; } bool PluginTest::loadFromPlasma() { bool ok = false; const QStringList allEngines = Plasma::PluginLoader::self()->listAllEngines(); qDebug() << "All engines: " << allEngines; - foreach (const QString &e, allEngines) { + for (const QString &e : allEngines) { Plasma::DataEngine *engine = Plasma::PluginLoader::self()->loadDataEngine(e); if (engine) { engine->connectSource(QStringLiteral("Europe/Amsterdam"), this); engine->connectSource(QStringLiteral("Battery"), this); engine->connectAllSources(this); qDebug() << "SOURCE: " << engine->sources(); ok = true; } } return ok; } void PluginTest::loadKQPlugin() { qDebug() << "Load KQPlugin"; #if 0 QString pluginPath = "/home/sebas/kf5/install/lib/x86_64-linux-gnu/kplugins/"; QCoreApplication::addLibraryPath(pluginPath); //QPluginLoader loader("/home/sebas/kf5/install/lib/x86_64-linux-gnu/kplugins/libkqpluginfactory.so", this); QPluginLoader loader("/home/sebas/kf5/install/lib/x86_64-linux-gnu/plugins/kf5/kplugins/libplasma_engine_time.so", this); KPluginFactory *factory = qobject_cast(loader.instance()); //QObject *factory = loader.instance(); if (factory) { qDebug() << "loaded successfully and cast"; qDebug() << "metadata: " << loader.metaData(); //QObject *o = factory->createPlugin("time"); //qDebug() << " objec name:" << o->objectName(); //Plasma::DataEngine *time_engine = qobject_cast(factory->create(this, QVariantList())); //Plasma::DataEngine *time_engine = factory->create(this, QVariantList()); Plasma::DataEngine *time_engine = factory->createInstance(0, this, QVariantList()); if (time_engine) { qDebug() << "Successfully loaded timeengine"; time_engine->connectSource("Europe/Amsterdam", this); qDebug() << "SOURCE: " << time_engine->sources(); } else { qDebug() << "Timeengine failed to load. :("; } } else { qDebug() << "loading failed somehow"; } //KQPluginFactory* factory = new KQPluginFactory(KPluginInfo(), this); #endif } void PluginTest::dataUpdated(QString s, Plasma::DataEngine::Data d) { qDebug() << "new data for source:" << s << d; } } // namespace Plasma #include "moc_plugintest.cpp"